import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@/components/ui/card'; import { ChartContainer, ChartTooltip, type ChartConfig, } from '@/components/ui/chart'; import { useLocale } from '@/hooks/use-locale'; import { BudgetPeriod } from '@/types/budget'; import { formatCurrency } from '@/utils/currency'; import { formatDate } from '@/utils/date'; import { __ } from '@/utils/i18n'; import { useMemo } from 'react'; import { Area, AreaChart, Line, XAxis } from 'recharts'; interface Props { currentPeriod: BudgetPeriod; previousPeriod?: BudgetPeriod | null; budgetName: string; currencyCode: string; } interface ChartDataPoint { day: number; date: string; spent: number; allocated: number; remaining: number; prevSpent?: number; prevDate?: string; } interface CustomTooltipProps { active?: boolean; payload?: Array<{ payload: ChartDataPoint; }>; label?: string | number; currencyCode: string; hasPreviousPeriod: boolean; locale: string; } function CustomTooltip({ active, payload, currencyCode, hasPreviousPeriod, locale, }: CustomTooltipProps) { if (!active || !payload || !payload.length) { return null; } const data = payload[0].payload; const allocated = data.allocated; const spent = data.spent; const available = data.remaining; const percentage = allocated > 0 ? Math.round((available / allocated) * 100) : 0; return (

{hasPreviousPeriod ? `Day ${data.day}` : formatDate(data.date, 'MMM d, yyyy', locale)}

{__('Allocated:')} {formatCurrency(allocated, currencyCode)}
{__('Spent:')} {formatCurrency(spent, currencyCode)}
{hasPreviousPeriod && data.prevSpent !== undefined && (
{__('Last period:')} {formatCurrency(data.prevSpent, currencyCode)}
)}
{__('Available:')}
{percentage}% / {formatCurrency(available, currencyCode)}
); } function buildCumulativeSpending(period: BudgetPeriod): Map { const transactions = period.budget_transactions || []; const transactionsByDate = new Map(); transactions.forEach((t) => { if (!t.transaction) return; const date = new Date(t.transaction.transaction_date) .toISOString() .split('T')[0]; transactionsByDate.set( date, (transactionsByDate.get(date) || 0) + t.amount, ); }); return transactionsByDate; } export function BudgetSpendingChart({ currentPeriod, previousPeriod, budgetName, currencyCode, }: Props) { const locale = useLocale(); const hasPreviousPeriod = !!previousPeriod; const chartData = useMemo(() => { const currentByDate = buildCumulativeSpending(currentPeriod); const prevByDate = previousPeriod ? buildCumulativeSpending(previousPeriod) : null; const startDate = new Date(currentPeriod.start_date); const endDate = new Date(currentPeriod.end_date); const prevStartDate = previousPeriod ? new Date(previousPeriod.start_date) : null; const prevEndDate = previousPeriod ? new Date(previousPeriod.end_date) : null; const data: ChartDataPoint[] = []; let cumulativeSpent = 0; let prevCumulativeSpent = 0; const currentDate = new Date(startDate); let dayIndex = 1; while (currentDate <= endDate) { const dateStr = currentDate.toISOString().split('T')[0]; const dailySpent = currentByDate.get(dateStr) || 0; cumulativeSpent += dailySpent; const point: ChartDataPoint = { day: dayIndex, date: dateStr, spent: cumulativeSpent, allocated: currentPeriod.allocated_amount, remaining: currentPeriod.allocated_amount - cumulativeSpent, }; // Map to the same day index in the previous period if (prevByDate && prevStartDate && prevEndDate) { const prevDate = new Date(prevStartDate); prevDate.setDate(prevDate.getDate() + dayIndex - 1); if (prevDate <= prevEndDate) { const prevDateStr = prevDate.toISOString().split('T')[0]; const prevDailySpent = prevByDate.get(prevDateStr) || 0; prevCumulativeSpent += prevDailySpent; point.prevSpent = prevCumulativeSpent; point.prevDate = prevDateStr; } } data.push(point); currentDate.setDate(currentDate.getDate() + 1); dayIndex++; } return data; }, [currentPeriod, previousPeriod]); const chartConfig = { spent: { label: 'Spent', color: 'var(--spent)', }, allocated: { label: 'Budget', color: 'var(--allocated)', }, ...(hasPreviousPeriod && { prevSpent: { label: 'Last Period', color: 'var(--spent-prev)', }, }), } satisfies ChartConfig; const periodLabel = useMemo(() => { const start = formatDate(currentPeriod.start_date, 'MMM d', locale); const end = formatDate(currentPeriod.end_date, 'MMM d, yyyy', locale); return `${start} - ${end}`; }, [currentPeriod, locale]); return ( {__('Budget Spending')} {__('Tracking spending for')} {budgetName} ยท {periodLabel} { if (hasPreviousPeriod) { return `Day ${value}`; } return formatDate(value, 'MMM d', locale); }} /> } /> {hasPreviousPeriod && ( )} ); }