From 089381125f5b282b9827c0603ed89fcb86faae37 Mon Sep 17 00:00:00 2001 From: Vishesh 'ironeagle' Bangotra Date: Thu, 7 May 2026 15:24:17 +0530 Subject: [PATCH] TopTags --- src/components/ProgressCard/TopTags.tsx | 98 +++++++++++++++++++++++++ src/dashboard-config.ts | 6 +- 2 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 src/components/ProgressCard/TopTags.tsx diff --git a/src/components/ProgressCard/TopTags.tsx b/src/components/ProgressCard/TopTags.tsx new file mode 100644 index 0000000..dd8e53e --- /dev/null +++ b/src/components/ProgressCard/TopTags.tsx @@ -0,0 +1,98 @@ +import * as React from "react"; +import { Box } from "@mui/material"; +import { ReportData, ReportPeriod } from "../../features/report"; +import ProgressCard from "./ProgressCard"; + +type Props = { + reportData: ReportData; + mode: "expense" | "income"; + selectedPeriodId?: string | null; + compact?: boolean; +}; + +type DecoratedPeriod = ReportPeriod & { + id: string; + label: string; +}; + +function getAmount(p: ReportPeriod, mode: "expense" | "income") { + return mode === "expense" ? p.expenses.sum : p.incomes.sum; +} + +function findPeriod( + periods: DecoratedPeriod[], + selectedPeriodId?: string | null +) { + if (!periods.length) return null; + + if (selectedPeriodId) { + const match = periods.find((p) => p.id === selectedPeriodId); + if (match) return match; + } + + // fallback → latest + return periods.reduce((latest, p) => + new Date(p.start).getTime() > new Date(latest.start).getTime() + ? p + : latest + ); +} + +export default function TopTags({ + reportData, + mode, + selectedPeriodId, + compact = true +}: Props) { + const { items, total } = React.useMemo(() => { + const tagMap = new Map(); + + for (const bucket of reportData.buckets) { + const tags = bucket.group_key.tags; + if (!tags || tags.length === 0) continue; + + // Prefer FULL if available + const fullPeriods = (bucket.periods.full || []) as DecoratedPeriod[]; + + const periodsToUse = + selectedPeriodId + ? Object.values(bucket.periods).flat() as DecoratedPeriod[] + : fullPeriods; + + const period = findPeriod(periodsToUse, selectedPeriodId); + if (!period) continue; + + const amount = getAmount(period, mode); + + for (const tag of tags) { + tagMap.set(tag, (tagMap.get(tag) || 0) + amount); + } + } + + const arr = Array.from(tagMap.entries()).map(([tag, amount]) => ({ + tag, + amount + })); + + arr.sort((a, b) => b.amount - a.amount); + + const top = arr.slice(0, 5); + const total = top.reduce((sum, t) => sum + t.amount, 0); + + return { items: top, total }; + }, [reportData, mode, selectedPeriodId]); + + return ( + + {items.map((item) => ( + + ))} + + ); +} diff --git a/src/dashboard-config.ts b/src/dashboard-config.ts index 4da4208..d0cdfa6 100644 --- a/src/dashboard-config.ts +++ b/src/dashboard-config.ts @@ -1,7 +1,7 @@ import HistoryChart from "./components/HistoryChart"; -import ProgressCard from "./components/ProgressCard"; import LatestItems from "./components/LatestItems"; import { DashboardConfig } from "./components/Dashboard"; +import TopTags from "./components/ProgressCard/TopTags"; export const configuration: DashboardConfig = { sections: [ @@ -10,7 +10,6 @@ export const configuration: DashboardConfig = { title: "Breakdown", summary: "Interactive chronological tracking", component: HistoryChart, - dataKey: "chartData", settings: { tabs: ["Weekly", "Monthly"], // tabs: ["Weekly", "Monthly", "Yearly", "Financial Year", "All Time"], @@ -22,8 +21,7 @@ export const configuration: DashboardConfig = { { id: "top-payees", title: 'Top Payees', - component: ProgressCard, - dataKey: "topPayees", + component: TopTags, isList: true, settings: { compact: true,