import { api } from "../../react-openapi"; import { LatestItem } from "../components/LatestItemsList"; import { ChartDataPoint } from "../components/HistoryChart"; import * as React from "react"; import MonetizationOnIcon from "@mui/icons-material/MonetizationOn"; // ---------------- ICON ---------------- const DEFAULT_ICON = React.createElement(MonetizationOnIcon, { sx: { color: "#388e3c" } }); // ---------------- LATEST ---------------- export async function fetchLatestExpenses(): Promise { const res = await api.get('/expenses', { params: { limit: 10, sort: '-occurred_at' } }); const items = res.data?.items || res.data || []; return items.map((exp: any, index: number) => { const time = new Date( exp.occurred_at || exp.created_at || Date.now() ).getTime(); const diffDays = Math.floor( Math.abs(Date.now() - time) / (1000 * 60 * 60 * 24) ); return { id: exp.id || index, icon: DEFAULT_ICON, iconBgColor: "#e8f5e9", title: exp.payee?.name || exp.payee || "Unknown Payee", subtitle: exp.category?.name || exp.account?.name || "Transaction", amount: `Rs ${exp.amount || 0}`, timeAgo: diffDays === 0 ? "Today" : `${diffDays} days ago` }; }); } // ---------------- TYPES ---------------- export interface AggregatedDashboardData { chartData: Record; totalAmount: number; topPayees: Array<{ payeeName: string; amount: number }>; } // ---------------- GENERIC AGGREGATOR ---------------- export async function fetchAggregatedData( type: "expense" | "income" ): Promise { const res = await api.get('/expenses', { params: { limit: 0 } }); const all: any[] = res.data?.items || res.data || []; const now = new Date(); let totalAmount = 0; const payeeMap: Record = {}; const isValid = (amt: number) => type === "expense" ? amt < 0 : amt > 0; const normalize = (amt: number) => Math.abs(amt); // ---------------- BUCKETS ---------------- const todayBuckets: Record = { "12am":0,"3am":0,"6am":0,"9am":0, "12pm":0,"3pm":0,"6pm":0,"9pm":0 }; const weekBuckets: Record = { "Mon":0,"Tue":0,"Wed":0,"Thu":0, "Fri":0,"Sat":0,"Sun":0 }; const monthBuckets: Record = {}; const yearBuckets: Record = {}; const getStartOfWeek = (d: Date) => { const date = new Date(d); const day = date.getDay() || 7; if (day !== 1) date.setDate(date.getDate() - (day - 1)); date.setHours(0,0,0,0); return date; }; const format = (d: Date) => `${d.getDate()} ${d.toLocaleString('default', { month: 'short' })}`; // -------- MONTH (5 rolling weeks) -------- for (let i = 0; i < 5; i++) { const end = new Date(now); end.setDate(end.getDate() - i * 7); const start = new Date(end); start.setDate(start.getDate() - 6); const key = `${format(start)} - ${format(end)}`; monthBuckets[key] = { amount: 0, range: key }; } // -------- YEAR (12 months rolling) -------- for (let i = 0; i < 12; i++) { const d = new Date(now); d.setMonth(d.getMonth() - i); const start = new Date(d.getFullYear(), d.getMonth(), 1); const end = new Date(d.getFullYear(), d.getMonth() + 1, 0); const label = d.toLocaleString('default', { month: 'short' }); const range = `${format(start)} - ${format(end)}`; yearBuckets[label] = { amount: 0, range }; } const weekStart = getStartOfWeek(now); const weekEnd = new Date(weekStart); weekEnd.setDate(weekStart.getDate() + 6); // ---------------- LOOP ---------------- for (const item of all) { const d = new Date( item.occurred_at || item.created_at || Date.now() ); const amtRaw = Number(item.amount) || 0; if (!isValid(amtRaw)) continue; const amt = normalize(amtRaw); totalAmount += amt; const payee = item.payee?.name || item.payee || "Unknown"; payeeMap[payee] = (payeeMap[payee] || 0) + amt; // ---- TODAY if ( d.getDate() === now.getDate() && d.getMonth() === now.getMonth() && d.getFullYear() === now.getFullYear() ) { const hr = d.getHours(); let label = "12am"; if (hr >= 3 && hr < 6) label = "3am"; else if (hr >= 6 && hr < 9) label = "6am"; else if (hr >= 9 && hr < 12) label = "9am"; else if (hr >= 12 && hr < 15) label = "12pm"; else if (hr >= 15 && hr < 18) label = "3pm"; else if (hr >= 18 && hr < 21) label = "6pm"; else if (hr >= 21) label = "9pm"; todayBuckets[label] += amt; } // ---- WEEK if (d >= weekStart && d <= weekEnd) { const day = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"][d.getDay()]; if (weekBuckets[day] !== undefined) { weekBuckets[day] += amt; } } // ---- MONTH Object.entries(monthBuckets).forEach(([_, obj]) => { const [startStr, endStr] = obj.range.split(" - "); const start = new Date(startStr); const end = new Date(endStr); if (d >= start && d <= end) { obj.amount += amt; } }); // ---- YEAR Object.entries(yearBuckets).forEach(([_, obj]) => { const [startStr, endStr] = obj.range.split(" - "); const start = new Date(startStr); const end = new Date(endStr); if (d >= start && d <= end) { obj.amount += amt; } }); } const toPoints = (b: any): ChartDataPoint[] => Object.entries(b).map(([k, v]: any) => ({ id: k, amount: typeof v === "number" ? v : v.amount, subLabel: typeof v === "number" ? undefined : v.range })); const chartData = { today: toPoints(todayBuckets), week: toPoints(weekBuckets), month: toPoints(monthBuckets), year: toPoints(yearBuckets) }; // highlight max Object.values(chartData).forEach(group => { let max = group[0]; for (const g of group) if (g.amount > max.amount) max = g; if (max.amount > 0) max.highlighted = true; }); const topPayees = Object.entries(payeeMap) .map(([name, amt]) => ({ payeeName: name, amount: amt })) .sort((a, b) => b.amount - a.amount) .slice(0, 5); return { chartData, totalAmount, topPayees }; } // ---------------- EXPORTS ---------------- export const fetchAggregatedExpenses = () => fetchAggregatedData("expense"); export const fetchAggregatedIncome = () => fetchAggregatedData("income");