implementace Ekonomiky
This commit is contained in:
123
frontend/src/components/charts/EconomicsChart.tsx
Normal file
123
frontend/src/components/charts/EconomicsChart.tsx
Normal file
@@ -0,0 +1,123 @@
|
||||
import {
|
||||
Bar,
|
||||
CartesianGrid,
|
||||
Cell,
|
||||
ComposedChart,
|
||||
Line,
|
||||
ReferenceLine,
|
||||
ResponsiveContainer,
|
||||
Tooltip,
|
||||
XAxis,
|
||||
YAxis,
|
||||
} from 'recharts'
|
||||
import type { ChartDayPoint } from '../../types/economics'
|
||||
|
||||
type Props = {
|
||||
points: ChartDayPoint[]
|
||||
}
|
||||
|
||||
const GREEN = '#22c55e'
|
||||
const RED = '#ef4444'
|
||||
const BLUE = '#3b82f6'
|
||||
|
||||
function formatDay(iso: string): string {
|
||||
const d = new Date(iso + 'T00:00:00')
|
||||
return `${d.getDate()}.`
|
||||
}
|
||||
|
||||
type PayloadEntry = {
|
||||
name?: string
|
||||
value?: number
|
||||
color?: string
|
||||
}
|
||||
|
||||
function CustomTooltip({
|
||||
active,
|
||||
payload,
|
||||
label,
|
||||
}: {
|
||||
active?: boolean
|
||||
payload?: PayloadEntry[]
|
||||
label?: string
|
||||
}) {
|
||||
if (!active || !payload?.length || !label) return null
|
||||
const balance = payload.find((p) => p.name === 'daily_balance_czk')
|
||||
const cumulative = payload.find((p) => p.name === 'cumulative_balance_czk')
|
||||
return (
|
||||
<div className="rounded-lg border border-slate-700 bg-slate-800 px-3 py-2 text-xs shadow-lg">
|
||||
<p className="mb-1 font-medium text-slate-200">{label}</p>
|
||||
{balance && (
|
||||
<p style={{ color: (balance.value ?? 0) >= 0 ? GREEN : RED }}>
|
||||
Den: {(balance.value ?? 0) >= 0 ? '+' : ''}
|
||||
{(balance.value ?? 0).toFixed(2)} Kč
|
||||
</p>
|
||||
)}
|
||||
{cumulative && (
|
||||
<p style={{ color: BLUE }}>
|
||||
Kumulativ: {(cumulative.value ?? 0) >= 0 ? '+' : ''}
|
||||
{(cumulative.value ?? 0).toFixed(2)} Kč
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function EconomicsChart({ points }: Props) {
|
||||
if (points.length === 0) {
|
||||
return (
|
||||
<div className="flex h-64 items-center justify-center text-sm text-slate-500">
|
||||
Žádná data pro tento měsíc
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const data = points.map((p) => ({
|
||||
...p,
|
||||
label: formatDay(p.day),
|
||||
}))
|
||||
|
||||
return (
|
||||
<ResponsiveContainer width="100%" height={320}>
|
||||
<ComposedChart data={data} margin={{ top: 8, right: 16, bottom: 4, left: 0 }}>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#334155" />
|
||||
<XAxis dataKey="label" tick={{ fontSize: 11, fill: '#94a3b8' }} />
|
||||
<YAxis
|
||||
yAxisId="left"
|
||||
tick={{ fontSize: 11, fill: '#94a3b8' }}
|
||||
label={{
|
||||
value: 'Kč/den',
|
||||
angle: -90,
|
||||
position: 'insideLeft',
|
||||
style: { fontSize: 11, fill: '#94a3b8' },
|
||||
}}
|
||||
/>
|
||||
<YAxis
|
||||
yAxisId="right"
|
||||
orientation="right"
|
||||
tick={{ fontSize: 11, fill: BLUE }}
|
||||
label={{
|
||||
value: 'Kumulativ Kč',
|
||||
angle: 90,
|
||||
position: 'insideRight',
|
||||
style: { fontSize: 11, fill: BLUE },
|
||||
}}
|
||||
/>
|
||||
<Tooltip content={<CustomTooltip />} />
|
||||
<ReferenceLine yAxisId="left" y={0} stroke="#475569" strokeDasharray="2 2" />
|
||||
<Bar yAxisId="left" dataKey="daily_balance_czk" radius={[3, 3, 0, 0]} maxBarSize={32}>
|
||||
{data.map((entry, idx) => (
|
||||
<Cell key={idx} fill={entry.daily_balance_czk >= 0 ? GREEN : RED} fillOpacity={0.8} />
|
||||
))}
|
||||
</Bar>
|
||||
<Line
|
||||
yAxisId="right"
|
||||
type="monotone"
|
||||
dataKey="cumulative_balance_czk"
|
||||
stroke={BLUE}
|
||||
strokeWidth={2}
|
||||
dot={{ r: 3, fill: BLUE }}
|
||||
/>
|
||||
</ComposedChart>
|
||||
</ResponsiveContainer>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user