import { useMemo, useState } from "react"; import { useParentSize } from "@visx/responsive"; import { DonutChart } from "@/components/charts/DonutChart"; import { BubbleChart } from "@/components/charts/BubbleChart"; import { ChartTypeToggle } from "@/components/charts/ChartTypeToggle"; import { categorySpendBreakdown } from "@/lib/categorySpend"; import { shareColor } from "@/components/dashboard/shareColors"; import { useMoney } from "@/hooks/useMoney"; import { PieChart, Circle } from "lucide-react"; import type { Transaction } from "@/types/finance"; //: Slices beyond this rank fold into a single "Other" row, the same cutoff //: (and the same colour-cycling-to-muted fallback) the balance sheet's //: account list already uses via shareColor. const MAX_SLICES = 5; /** * Dashboard "Outflows": this period's spending by category as a donut with * a ranked list beside it, matching Sure's dashboard widget of the same * name. Shares `categorySpendBreakdown` with the Analytics page's category * chart, so the two surfaces never disagree on what counts as spending. */ const CHART_TYPES = [ { value: "donut", icon: PieChart, label: "Donut" }, { value: "bubbles", icon: Circle, label: "Bubbles" }, ] as const; export function OutflowsWidget({ transactions, donutOnly = false }: { transactions: Transaction[]; donutOnly?: boolean }) { const { money, compact } = useMoney(); const { parentRef, width } = useParentSize({ debounceTime: 30 }); const [chartType, setChartType] = useState<"donut" | "bubbles">("donut"); const breakdown = useMemo(() => categorySpendBreakdown(transactions), [transactions]); const total = breakdown.reduce((s, d) => s + d.value, 0); const top = breakdown.slice(0, MAX_SLICES); const rest = breakdown.slice(MAX_SLICES); const otherValue = rest.reduce((s, d) => s + d.value, 0); const rows = otherValue > 0 ? [...top, { name: "Other", value: otherValue, pct: total > 0 ? (otherValue / total) * 100 : 0 }] : top; if (breakdown.length === 0) { return (