From 1423f889ba245033f3b0c0095918013a2a7454c7 Mon Sep 17 00:00:00 2001 From: Vishesh 'ironeagle' Bangotra Date: Sat, 9 May 2026 13:29:49 +0530 Subject: [PATCH] cleanup --- src/components/HistoryChart/HistoryChart.tsx | 128 +---------- .../LatestItems/LatestItems.models.ts | 10 +- src/components/LatestItems/LatestItems.tsx | 200 ++---------------- .../LatestItems/LatestItems.view.tsx | 90 +++++++- src/components/ProgressCard/ProgressCard.tsx | 2 +- .../ProgressCard/ProgressCard.utils.ts | 15 -- src/components/ProgressCard/TopTags.tsx | 73 +------ src/features/report/index.ts | 1 + 8 files changed, 117 insertions(+), 402 deletions(-) delete mode 100644 src/components/ProgressCard/ProgressCard.utils.ts diff --git a/src/components/HistoryChart/HistoryChart.tsx b/src/components/HistoryChart/HistoryChart.tsx index 4488676..09f8124 100644 --- a/src/components/HistoryChart/HistoryChart.tsx +++ b/src/components/HistoryChart/HistoryChart.tsx @@ -1,133 +1,13 @@ import * as React from "react"; -import { HistoryChartProps, ChartDataPoint } from "./HistoryChart.models"; +import { HistoryChartProps } from "./HistoryChart.models"; import HistoryChartView from "./HistoryChart.view"; -import { ReportPeriod } from "../../features/report"; - -type DecoratedPeriod = ReportPeriod & { - id: string; - label: string; -}; - -const TAB_TO_KEY: Record = { - Weekly: "weekly", - Monthly: "monthly", - Yearly: "yearly", - 'Financial Year': "fyly", - 'All Time': "full" -}; - -function getAmount(p: ReportPeriod, mode: "expense" | "income") { - return mode === "expense" ? p.expenses.sum : p.incomes.sum; -} - -function mergeMetric(a: any, b: any) { - const sum = a.sum + b.sum; - const count = a.count + b.count; - - return { - ...a, - sum, - count, - average: count > 0 ? sum / count : 0, - transactions: a.transactions || b.transactions - ? [ - ...(a.transactions || []), - ...(b.transactions || []) - ] - : undefined - }; -} - -function mergeBuckets( - buckets: any[], - key: "weekly" | "monthly" | "yearly" | "fyly" | "full" -): DecoratedPeriod[] { - const map = new Map(); - - for (const bucket of buckets) { - const periods = (bucket.periods[key] || []) as DecoratedPeriod[]; - - for (const p of periods) { - const existing = map.get(p.id); - - if (!existing) { - map.set(p.id, { - ...p, - expenses: { ...p.expenses }, - incomes: { ...p.incomes } - }); - } else { - map.set(p.id, { - ...existing, - expenses: mergeMetric(existing.expenses, p.expenses), - incomes: mergeMetric(existing.incomes, p.incomes) - }); - } - } - } - - return Array.from(map.values()).sort( - (a, b) => new Date(a.start).getTime() - new Date(b.start).getTime() - ); -} - -function attachComparison( - points: ChartDataPoint[], - key: "weekly" | "monthly" | "yearly" | "fyly" | "full" -): ChartDataPoint[] { - const getCompareIndex = (i: number) => { - if (key === "weekly") return i - 4; - if (key === "monthly") return i - 12; - if (key === "yearly") return i - 1; - if (key === "fyly") return i - 1; - return -1; - }; - - return points.map((p, i) => { - const ci = getCompareIndex(i); - - return { - ...p, - compare: - ci >= 0 && points[ci] - ? { - id: points[ci].id, - label: points[ci].label, - amount: points[ci].amount - } - : undefined - }; - }); -} - -function buildChartData( - reportData: HistoryChartProps["reportData"], - key: "weekly" | "monthly" | "yearly" | "fyly" | "full", - mode: "expense" | "income", - comparison: boolean -): ChartDataPoint[] { - const merged = mergeBuckets(reportData.buckets, key); - console.log("Merged periods:", merged); - - let points: ChartDataPoint[] = merged.map((p) => ({ - id: p.id, - label: p.label, - amount: getAmount(p, mode) - })); - - if (comparison) { - points = attachComparison(points, key); - } - - return points; -} +import { buildChartData, tabToKey } from "./HistoryChart.adapter"; export default function HistoryChart(props: HistoryChartProps) { const { tabs, reportData, mode, - periodType, comparison, selectedPeriodId, setSelectedPeriodId @@ -136,7 +16,7 @@ export default function HistoryChart(props: HistoryChartProps) { const [activeTab, setActiveTab] = React.useState(tabs[0] || ""); const [startIndex, setStartIndex] = React.useState(0); - const activeDataKey = TAB_TO_KEY[activeTab]; + const activeDataKey = tabToKey(activeTab); const currentData = React.useMemo(() => { return buildChartData(reportData, activeDataKey, mode, comparison); @@ -184,7 +64,7 @@ export default function HistoryChart(props: HistoryChartProps) { React.useEffect(() => { setSelectedPeriodId(null); - }, [activeTab, periodType]); + }, [activeTab]); React.useEffect(() => { if ( diff --git a/src/components/LatestItems/LatestItems.models.ts b/src/components/LatestItems/LatestItems.models.ts index bd15502..d953bdf 100644 --- a/src/components/LatestItems/LatestItems.models.ts +++ b/src/components/LatestItems/LatestItems.models.ts @@ -1,18 +1,14 @@ -import * as React from "react"; - export interface LatestItem { id: string | number; - icon: React.ReactNode; - iconBgColor?: string; title: string; subtitle: string; amount: string; timeAgo: string; } -export interface LatestItemsListProps { - title?: string; +export interface LatestItemsViewProps { items: LatestItem[]; - onViewAll?: () => void; accentColor: string; + canExpand: boolean; + onExpand: () => void; } diff --git a/src/components/LatestItems/LatestItems.tsx b/src/components/LatestItems/LatestItems.tsx index 1439727..882c0f9 100644 --- a/src/components/LatestItems/LatestItems.tsx +++ b/src/components/LatestItems/LatestItems.tsx @@ -1,18 +1,7 @@ import * as React from "react"; -import { - List, - ListItem, - ListItemAvatar, - ListItemText, - Avatar, - Typography, - Box, - IconButton -} from "@mui/material"; -import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; - -import { ReportData, Transaction, ReportPeriod } from "../../features/report"; -import { formatCurrency } from "../ProgressCard/ProgressCard.utils"; +import { ReportData } from "../../features/report"; +import { buildLatestItems } from "./LatestItems.adapter"; +import LatestItemsView from "./LatestItems.view"; type Props = { reportData: ReportData; @@ -21,190 +10,33 @@ type Props = { accentColor: string; }; -type DecoratedPeriod = ReportPeriod & { - id: string; - label: string; -}; - -function mergePeriods( - reportData: ReportData, - key: "weekly" | "monthly" | "yearly" | "fyly" | "full" -): DecoratedPeriod[] { - const map = new Map(); - - for (const bucket of reportData.buckets) { - const periods = (bucket.periods[key] || []) as DecoratedPeriod[]; - - for (const p of periods) { - const existing = map.get(p.id); - - if (!existing) { - map.set(p.id, { - ...p, - expenses: { - ...p.expenses, - transactions: [...(p.expenses.transactions || [])], - }, - incomes: { - ...p.incomes, - transactions: [...(p.incomes.transactions || [])], - }, - }); - } else { - existing.expenses.transactions?.push(...(p.expenses.transactions || [])); - existing.incomes.transactions?.push(...(p.incomes.transactions || [])); - } - } - } - - return Array.from(map.values()); -} - -function extractTransactions( - reportData: ReportData, - selectedPeriodId: string | null, - mode: "expense" | "income", -): Transaction[] { - let periods: DecoratedPeriod[] = []; - - if (selectedPeriodId) { - const prefix = selectedPeriodId.split(":")[0]; - - const map: any = { - W: "weekly", - M: "monthly", - Y: "yearly", - FY: "fyly", - FULL: "full" - }; - - const key = map[prefix]; - - periods = mergePeriods(reportData, key); - const selected = periods.find(p => p.id === selectedPeriodId); - - if (!selected) return []; - - return mode === "expense" - ? (selected.expenses.transactions || []) - : (selected.incomes.transactions || []); - } - - periods = mergePeriods(reportData, "full"); - - if (!periods.length) return []; - - const full = periods[0]; - - return mode === "expense" - ? (full.expenses.transactions || []) - : (full.incomes.transactions || []); -} - export default function LatestItems({ reportData, mode, selectedPeriodId, - accentColor + accentColor, }: Props) { const [visibleCount, setVisibleCount] = React.useState(5); - const items = React.useMemo(() => { - const txns = extractTransactions(reportData, selectedPeriodId, mode); - - return txns - .filter((t) => (mode === "expense" ? t.amount < 0 : t.amount >= 0)) - .sort( - (a, b) => - new Date(b.occurred_at).getTime() - - new Date(a.occurred_at).getTime() - ) - .map((t, index) => ({ - id: index + 1, - title: t.payee.name, - subtitle: t.tags.map((tag) => tag.name).join(", "), - amount: formatCurrency(t.amount), - timeAgo: new Date(t.occurred_at).toLocaleDateString("en-IN"), - })); + const allItems = React.useMemo(() => { + return buildLatestItems(reportData, selectedPeriodId, mode); }, [reportData, selectedPeriodId, mode]); const isPeriodSelected = Boolean(selectedPeriodId); const visibleItems = React.useMemo(() => { - if (!isPeriodSelected) return items.slice(0, 5); - return items.slice(0, visibleCount); - }, [items, isPeriodSelected, visibleCount]); + if (!isPeriodSelected) return allItems.slice(0, 5); + return allItems.slice(0, visibleCount); + }, [allItems, isPeriodSelected, visibleCount]); - const canExpand = isPeriodSelected && visibleCount < items.length; + const canExpand = isPeriodSelected && visibleCount < allItems.length; return ( - - - - Recent Transactions - - - - - {visibleItems.map((item, index) => ( - - - - - - - {item.title} - - } - secondary={ - - {item.subtitle} - - } - /> - - - - {item.amount} - - - {item.timeAgo} - - - - ))} - - {canExpand && ( - - setVisibleCount((prev) => prev + 5)} - > - - - - )} - - + setVisibleCount((prev) => prev + 5)} + /> ); } diff --git a/src/components/LatestItems/LatestItems.view.tsx b/src/components/LatestItems/LatestItems.view.tsx index 71a7983..f29a6ae 100644 --- a/src/components/LatestItems/LatestItems.view.tsx +++ b/src/components/LatestItems/LatestItems.view.tsx @@ -1,6 +1,88 @@ -import LatestItemsListView from "./LatestItems.view"; -import { LatestItemsListProps } from "./LatestItems.models"; +import * as React from "react"; +import { + List, + ListItem, + ListItemAvatar, + ListItemText, + Avatar, + Typography, + Box, + IconButton, +} from "@mui/material"; +import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; +import { LatestItemsViewProps } from "./LatestItems.models"; -export default function LatestItemsList(props: LatestItemsListProps) { - return ; +export default function LatestItemsView({ + items, + accentColor, + canExpand, + onExpand, +}: LatestItemsViewProps) { + return ( + + + + Recent Transactions + + + + + {items.map((item, index) => ( + + + + + + + {item.title} + + } + secondary={ + + {item.subtitle} + + } + /> + + + + {item.amount} + + + {item.timeAgo} + + + + ))} + + {canExpand && ( + + + + + + )} + + + ); } diff --git a/src/components/ProgressCard/ProgressCard.tsx b/src/components/ProgressCard/ProgressCard.tsx index 3441a2f..d31cd36 100644 --- a/src/components/ProgressCard/ProgressCard.tsx +++ b/src/components/ProgressCard/ProgressCard.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import ProgressCardView from "./ProgressCard.view"; import { ProgressCardProps } from "./ProgressCard.models"; -import { getPercentage, formatCurrency } from "./ProgressCard.utils"; +import { getPercentage, formatCurrency } from "../report.helpers"; export default function ProgressCard(props: ProgressCardProps) { const { progressAmount, totalAmount, compact = false } = props; diff --git a/src/components/ProgressCard/ProgressCard.utils.ts b/src/components/ProgressCard/ProgressCard.utils.ts deleted file mode 100644 index de50ef5..0000000 --- a/src/components/ProgressCard/ProgressCard.utils.ts +++ /dev/null @@ -1,15 +0,0 @@ -export const getPercentage = (progressAmount: number, totalAmount: number) => { - if (!totalAmount) return 0; - return Math.min(100, Math.max(0, (progressAmount / totalAmount) * 100)); -}; - -export const formatCurrency = (val: number) => { - const absVal = Math.abs(val); - if (absVal >= 100000) { - return `₹ ${(val / 100000).toFixed(2)}L`; - } - if (absVal >= 1000) { - return `₹ ${(val / 1000).toFixed(2)}k`; - } - return `₹ ${val.toFixed(2)}`; -}; diff --git a/src/components/ProgressCard/TopTags.tsx b/src/components/ProgressCard/TopTags.tsx index 2de4cdf..5e8860a 100644 --- a/src/components/ProgressCard/TopTags.tsx +++ b/src/components/ProgressCard/TopTags.tsx @@ -1,7 +1,8 @@ import * as React from "react"; import { Box } from "@mui/material"; -import { ReportData, ReportPeriod } from "../../features/report"; +import { ReportData } from "../../features/report"; import ProgressCard from "./ProgressCard"; +import { extractTopTags } from "./TopTags.adapter"; type Props = { reportData: ReportData; @@ -10,76 +11,14 @@ type Props = { 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 + 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, 4); - const total = top.reduce((sum, t) => sum + t.amount, 0); - - return { items: top, total }; + return extractTopTags(reportData, mode, selectedPeriodId); }, [reportData, mode, selectedPeriodId]); return ( @@ -89,9 +28,9 @@ export default function TopTags({ gridTemplateColumns: { xs: "1fr", sm: "repeat(2, 1fr)", - md: "repeat(4, 1fr)" + md: "repeat(4, 1fr)", }, - gap: 2 + gap: 2, }} > {items.map((item) => ( diff --git a/src/features/report/index.ts b/src/features/report/index.ts index 9092544..ceb4378 100644 --- a/src/features/report/index.ts +++ b/src/features/report/index.ts @@ -4,6 +4,7 @@ export { export type { Transaction, ReportData, + ReportBucket, ReportPeriod, } from './report.models' export {