From 25bd882b750ca9ddec27e97ec83d50ad64faf050 Mon Sep 17 00:00:00 2001 From: Vishesh 'ironeagle' Bangotra Date: Tue, 5 May 2026 11:39:23 +0530 Subject: [PATCH] dashboard using report feature --- src/features/dashboard/dashboard.mapper.ts | 148 ++++++++++++++++++++- src/features/dashboard/useDashboardData.ts | 31 +---- 2 files changed, 149 insertions(+), 30 deletions(-) diff --git a/src/features/dashboard/dashboard.mapper.ts b/src/features/dashboard/dashboard.mapper.ts index 4a2b3ca..e32db48 100644 --- a/src/features/dashboard/dashboard.mapper.ts +++ b/src/features/dashboard/dashboard.mapper.ts @@ -1,10 +1,15 @@ import * as React from "react"; import MonetizationOnIcon from "@mui/icons-material/MonetizationOn"; import { LatestItem } from "../../components/LatestItems"; +import { + ChartData, + ChartDataPoint, +} from "../../components/HistoryChart"; const DEFAULT_ICON = React.createElement(MonetizationOnIcon, { sx: { color: "#388e3c" } }); +type ReportBucket = any; export function mapToLatestItems( items: any[], @@ -17,9 +22,7 @@ export function mapToLatestItems( .filter((item: any) => isValid(Number(item.amount) || 0)) .slice(0, 5) .map((exp: any, index: number) => { - const time = new Date( - exp.occurred_at || exp.created_at || Date.now() - ).getTime(); + const time = new Date(exp.occurred_at).getTime(); const diffDays = Math.floor( Math.abs(Date.now() - time) / (1000 * 60 * 60 * 24) @@ -30,11 +33,144 @@ export function mapToLatestItems( icon: DEFAULT_ICON, iconBgColor: type === "expense" ? "#ffebee" : "#e8f5e9", - title: exp.payee?.name || exp.payee || "Unknown Payee", - subtitle: - exp.category?.name || exp.account?.name || "Transaction", + title: exp.payee.name, + subtitle: exp.account.name, amount: `Rs ${Math.abs(exp.amount || 0)}`, timeAgo: diffDays === 0 ? "Today" : `${diffDays} days ago` }; }); } + +const sumBucket = (bucket: ReportBucket, flow: "expenses" | "incomes") => + bucket.groups.reduce( + (acc: number, g: any) => acc + (g?.[flow]?.sum || 0), + 0 + ); + +const toLabel = (start: string, end: string, type: "weekly" | "monthly") => { + const s = new Date(start); + const e = new Date(end); + + if (type === "monthly") { + return s.toLocaleString("default", { month: "short" }); + } + + return `${s.getDate()}–${e.getDate()} ${e.toLocaleString("default", { + month: "short", + })}`; +}; + +const getWeekOfMonth = (date: Date) => { + const firstDay = new Date(date.getFullYear(), date.getMonth(), 1); + return Math.ceil((date.getDate() + firstDay.getDay()) / 7); +}; + +const findCompareBucket = ( + current: ReportBucket, + buckets: ReportBucket[], + type: "weekly" | "monthly" +): ReportBucket | undefined => { + const start = new Date(current.start); + + if (type === "monthly") { + const targetYear = start.getFullYear() - 1; + const targetMonth = start.getMonth(); + + return buckets.find(b => { + const d = new Date(b.start); + return ( + d.getFullYear() === targetYear && + d.getMonth() === targetMonth + ); + }); + } + + if (type === "weekly") { + const weekIndex = getWeekOfMonth(start); // you must define this + const target = new Date(start); + target.setMonth(target.getMonth() - 1); + + return buckets.find(b => { + const d = new Date(b.start); + return ( + d.getFullYear() === target.getFullYear() && + d.getMonth() === target.getMonth() && + getWeekOfMonth(d) === weekIndex + ); + }); + } + + return undefined; +}; + +const toPoints = ( + buckets: ReportBucket[], + type: "weekly" | "monthly", + flow: "expenses" | "incomes" +): ChartDataPoint[] => { + return buckets.map((b) => { + const amount = sumBucket(b, flow); + const prev = findCompareBucket(b, buckets, type); + + return { + id: toLabel(b.start, b.end, type), + amount, + compare: prev + ? { + id: toLabel(prev.start, prev.end, type), + amount: sumBucket(prev, flow), + } + : undefined, + }; + }); +}; + +export function mapReportToDashboard( + weekly: ReportBucket[], + monthly: ReportBucket[], + payeeBuckets: ReportBucket[], + type: "expense" | "income" +) { + const flow = type === "expense" ? "expenses" : "incomes"; + + const chartData: ChartData = { + weekly: { + rolling: toPoints(weekly, "weekly", flow), + calendar: toPoints(weekly, "weekly", flow), + }, + + monthly: { + rolling: toPoints(monthly, "monthly", flow), + calendar: toPoints(monthly, "monthly", flow), + }, + }; + + const totalAmount = weekly.reduce( + (acc, b) => acc + sumBucket(b, flow), + 0 + ); + + const payeeMap: Record = {}; + + const sourceForPayees = (payeeBuckets && payeeBuckets.length > 0) ? payeeBuckets : weekly; + + for (const b of sourceForPayees) { + for (const g of b.groups) { + const key = g.group_key || "Unknown"; + const amt = g?.[flow]?.sum || 0; + payeeMap[key] = (payeeMap[key] || 0) + amt; + } + } + + const topPayees = Object.entries(payeeMap) + // .filter(([name]) => name !== "Unknown") + .map(([payeeName, amount]) => ({ payeeName, amount })) + .sort((a, b) => b.amount - a.amount) + .slice(0, 5); + + return { + chartData, + totalAmount, + topPayees, + }; +} diff --git a/src/features/dashboard/useDashboardData.ts b/src/features/dashboard/useDashboardData.ts index c0a00d7..3051f59 100644 --- a/src/features/dashboard/useDashboardData.ts +++ b/src/features/dashboard/useDashboardData.ts @@ -1,38 +1,22 @@ -import { useResourceByName } from "../../../react-openapi"; -import { mapToLatestItems } from "./dashboard.mapper"; -import { mapReportToDashboard } from "../report/report.mapper"; +import { useReport } from "../report"; +import { mapReportToDashboard } from "./dashboard.mapper"; export function useDashboardData(type: "expense" | "income") { - const { useList: useExpenseList } = useResourceByName("expenses"); - const { useList: useReportList } = useResourceByName("reports"); - - // Fetch latest transactions - const latestQuery = useExpenseList({ - limit: 100, - sort: "-occurred_at" - }); - // Fetch reports for aggregation - const weeklyReport = useReportList({ period: "weekly", rolling: true }); - const monthlyReport = useReportList({ period: "monthly", rolling: true }); - const payeeReport = useReportList({ period: "full", rolling: true, group_by: "payee" }); + const weeklyReport = useReport({ period: "weekly", rolling: true }); + const monthlyReport = useReport({ period: "monthly", rolling: true }); + const payeeReport = useReport({ period: "full", rolling: true, group_by: ["payee"] }); const isLoading = - latestQuery.isLoading || - weeklyReport.isLoading || + weeklyReport.isLoading || monthlyReport.isLoading || payeeReport.isLoading; const error = - latestQuery.error || - weeklyReport.error || + weeklyReport.error || monthlyReport.error || payeeReport.error; - const latest = latestQuery.data?.data - ? mapToLatestItems(latestQuery.data.data, type) - : []; - const aggregatedData = weeklyReport.data?.data && monthlyReport.data?.data && payeeReport.data?.data ? mapReportToDashboard( @@ -45,7 +29,6 @@ export function useDashboardData(type: "expense" | "income") { return { data: aggregatedData, - latest: latest, isLoading, error, };