import { useSignal } from '@preact/signals'; import { useEffect, useRef } from 'preact/hooks'; import { Chart } from 'chart.js'; import { formatNumber } from '/lib/utils/misc.ts'; import { Budget, SupportedCurrencySymbol } from '/lib/types.ts'; interface ListBudgetsProps { budgets: Budget[]; month: string; currency: SupportedCurrencySymbol; onClickEditBudget: (budgetId: string) => void; } export default function ListBudgets( { budgets, month, currency, onClickEditBudget, }: ListBudgetsProps, ) { const view = useSignal<'list' | 'chart'>('list'); const chartRef = useRef(null); // Calculate a total budget to show before all others const totalBudget: Omit = { id: 'total', name: 'Total', month, value: budgets.reduce((accumulatedValue, budget) => accumulatedValue + budget.value, 0), extra: { usedValue: budgets.reduce((accumulatedValue, budget) => accumulatedValue + budget.extra.usedValue, 0), availableValue: budgets.reduce((accumulatedValue, budget) => accumulatedValue + budget.extra.availableValue, 0), }, }; function swapView(newView: 'list' | 'chart') { view.value = view.value === newView ? 'list' : newView; } useEffect(() => { if (view.value === 'chart') { const budgetColors = [totalBudget, ...budgets].map((_, index) => index === 0 ? 'rgba(59, 130, 246, 0.8)' : `hsl(${(index - 1) * (360 / budgets.length)}, ${index % 2 ? 85 : 70}%, ${index % 2 ? 55 : 65}%, 0.8)` ); new Chart(chartRef.current as HTMLCanvasElement, { type: 'doughnut', data: { labels: [{ name: 'Available' }, ...budgets].map((budget) => budget.name), datasets: [{ label: '', data: [totalBudget, ...budgets].map((budget) => budget.id === 'total' ? budget.extra.availableValue : budget.extra.usedValue ), backgroundColor: budgetColors, borderWidth: 1, borderColor: '#222', }], }, options: { backgroundColor: '#222', plugins: { legend: { position: 'bottom', fullSize: true, labels: { usePointStyle: true, pointStyle: 'circle', padding: 10, }, }, }, }, }); } }, [view.value]); return (
{budgets.length === 0 ? (
No budgets to show for {month}
) : (
{view.value === 'list' ? [totalBudget, ...budgets].map((budget) => { let backgroundColorClass = 'bg-green-600'; let usedValuePercentage = Math.ceil(100 * budget.extra.usedValue / budget.value); if (usedValuePercentage >= 100) { usedValuePercentage = 100; backgroundColorClass = 'bg-red-600'; } return (
budget.id === 'total' ? swapView('chart') : onClickEditBudget(budget.id)} class='flex max-w-sm gap-y-4 gap-x-4 rounded shadow-md bg-slate-700 relative cursor-pointer py-4 px-6 hover:opacity-80' >
{formatNumber(currency, budget.extra.usedValue)} of {formatNumber(currency, budget.value)} {budget.name}
{formatNumber(currency, budget.extra.availableValue)}
); }) : (
swapView('list')}>
)}
)}
); }