diff --git a/src/components/Dashboard/Dashboard.tsx b/src/components/Dashboard/Dashboard.tsx index 66b678d..8c12da0 100644 --- a/src/components/Dashboard/Dashboard.tsx +++ b/src/components/Dashboard/Dashboard.tsx @@ -11,15 +11,20 @@ export default function Dashboard(props: DashboardProps) { comparison: false, }); - const toggleMode = () => { - setState(prev => { - const next = { - ...prev, - mode: prev.mode === "expense" ? "income" as const : "expense" as const, - }; - props.onModeChange?.(next); - return next; - }); + const toggleMode = ( + event: React.MouseEvent, + newMode: "expense" | "income" | null + ) => { + if (newMode !== null && newMode !== state.mode) { + setState(prev => { + const next = { + ...prev, + mode: newMode, + }; + props.onModeChange?.(next); + return next; + }); + } }; const togglePeriodType = () => { diff --git a/src/components/Dashboard/Dashboard.view.tsx b/src/components/Dashboard/Dashboard.view.tsx index 55793e2..127c308 100644 --- a/src/components/Dashboard/Dashboard.view.tsx +++ b/src/components/Dashboard/Dashboard.view.tsx @@ -14,7 +14,7 @@ import { DashboardProps, DashboardState } from "./Dashboard.models"; interface ViewProps extends DashboardProps { state: DashboardState; setState: React.Dispatch>; - toggleMode: () => void; + toggleMode: (event: React.MouseEvent, newMode: "expense" | "income" | null) => void; togglePeriodType: () => void; setSelectedPeriodId: (id: string | null) => void; setSelectedGroupKey: (groupKey: GroupKey | null) => void; diff --git a/src/components/ProgressCard/TopTags.adapter.ts b/src/components/ProgressCard/TopTags.adapter.ts index d4fca18..c614369 100644 --- a/src/components/ProgressCard/TopTags.adapter.ts +++ b/src/components/ProgressCard/TopTags.adapter.ts @@ -1,32 +1,9 @@ import { ReportData } from "../../features/report"; import { - getAmount, - DecoratedPeriod, + mergeBucketPeriods, + periodIdToKey, } from "../report.helpers"; -// ─── Helpers ───────────────────────────────────────────────── - -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 - ); -} - -// ─── Main adapter ──────────────────────────────────────────── - export interface TagItem { tag: string; amount: number; @@ -39,24 +16,34 @@ export function extractTopTags( ): { items: TagItem[]; total: number } { const tagMap = new Map(); - for (const bucket of reportData.buckets) { - const tags = bucket.group_key.tags; - if (!tags || tags.length === 0) continue; + let periodKey: ReturnType = "all"; + if (selectedPeriodId) { + periodKey = periodIdToKey(selectedPeriodId); + } - // Prefer ALL if available - const allPeriods = (bucket.periods.all || []) as DecoratedPeriod[]; + const periods = mergeBucketPeriods(reportData.buckets, periodKey); - const periodsToUse = selectedPeriodId - ? (Object.values(bucket.periods).flat() as DecoratedPeriod[]) - : allPeriods; + let period = periods[0]; + if (selectedPeriodId) { + period = periods.find(p => p.id === selectedPeriodId) || period; + } else if (periods.length > 0) { + period = periods.reduce((latest, p) => + new Date(p.start).getTime() > new Date(latest.start).getTime() + ? p + : latest + ); + } - const period = findPeriod(periodsToUse, selectedPeriodId); - if (!period) continue; - - const amount = getAmount(period); - - for (const tag of tags) { - tagMap.set(tag, (tagMap.get(tag) || 0) + amount); + if (period && period.metric && period.metric.transactions) { + for (const txn of period.metric.transactions) { + if (txn.tags && txn.tags.length > 0) { + for (const tagObj of txn.tags) { + const tagName = typeof tagObj === "string" ? tagObj : tagObj.name; + tagMap.set(tagName, (tagMap.get(tagName) || 0) + txn.amount); + } + } else { + tagMap.set("Untagged", (tagMap.get("Untagged") || 0) + txn.amount); + } } }