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" } }); // ---------------- HELPERS ---------------- const format = (d: Date) => `${d.getDate()} ${d.toLocaleString("default", { month: "short" })}`; const startOfDay = (d: Date) => { const x = new Date(d); x.setHours(0, 0, 0, 0); return x; }; const endOfDay = (d: Date) => { const x = new Date(d); x.setHours(23, 59, 59, 999); return x; }; const getStartOfWeek = (d: Date) => { const date = new Date(d); const day = date.getDay() || 7; if (day !== 1) date.setDate(date.getDate() - (day - 1)); return startOfDay(date); }; // ---------------- LATEST ---------------- export async function fetchLatestTransactions( type: "expense" | "income" ): Promise { const res = await api.get("/expenses", { params: { limit: 100, sort: "-occurred_at" } }); const items = res.data?.items || res.data || []; const isValid = (amt: number) => type === "expense" ? amt < 0 : amt > 0; return items .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 diffDays = Math.floor( Math.abs(Date.now() - time) / (1000 * 60 * 60 * 24) ); return { id: exp.id || index, icon: DEFAULT_ICON, iconBgColor: type === "expense" ? "#ffebee" : "#e8f5e9", title: exp.payee?.name || exp.payee || "Unknown Payee", subtitle: exp.category?.name || exp.account?.name || "Transaction", amount: `Rs ${Math.abs(exp.amount || 0)}`, timeAgo: diffDays === 0 ? "Today" : `${diffDays} days ago` }; }); } // ---------------- TYPES ---------------- export interface AggregatedDashboardData { chartData: Record; totalAmount: number; topPayees: Array<{ payeeName: string; amount: number }>; } // ---------------- AGGREGATION ---------------- // ---------------- AGGREGATION ---------------- export async function fetchAggregatedData( type: "expense" | "income", mode: "rolling" | "calendar" = "rolling" ): 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); // ---------------- WEEK ---------------- const dailyBuckets: Record = { Mon: 0, Tue: 0, Wed: 0, Thu: 0, Fri: 0, Sat: 0, Sun: 0 }; const weekStart = getStartOfWeek(now); const weekEnd = endOfDay(new Date(weekStart.getTime() + 6 * 86400000)); // ---------------- MONTH (rolling 5 weeks, Mon–Sun aligned) ---------------- const weeklyBuckets = []; if (mode === "rolling") { const currentWeekStart = getStartOfWeek(now); for (let i = 0; i < 5; i++) { const start = new Date(currentWeekStart.getTime() - i * 7 * 86400000); const end = endOfDay(new Date(start.getTime() + 6 * 86400000)); weeklyBuckets.push({ label: `${format(start)} - ${format(end)}`, start, end, amount: 0 }); } } else { // calendar weeks within current month const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1); let cursor = getStartOfWeek(startOfMonth); while (cursor <= now) { const start = new Date(cursor); const end = endOfDay(new Date(start.getTime() + 6 * 86400000)); weeklyBuckets.push({ label: `${format(start)} - ${format(end)}`, start, end, amount: 0 }); cursor = new Date(cursor.getTime() + 7 * 86400000); } } // ---------------- YEAR (rolling 12 months) ---------------- const monthlyBuckets = []; if (mode === "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 = i === 0 ? endOfDay(now) // current month → till now : endOfDay(new Date(d.getFullYear(), d.getMonth() + 1, 0)); monthlyBuckets.push({ label: `${d.toLocaleString("default", { month: "short" })}-${String(d.getFullYear()).slice(2)}`, start, end, amount: 0 }); } } else { // calendar year (Jan → current month) for (let i = 0; i <= now.getMonth(); i++) { const start = new Date(now.getFullYear(), i, 1); const end = endOfDay(new Date(now.getFullYear(), i + 1, 0)); monthlyBuckets.push({ label: `${start.toLocaleString("default", { month: "short" })}-${String(start.getFullYear()).slice(2)}`, start, end, amount: 0 }); } } // ---------------- 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; // WEEK if (d >= weekStart && d <= weekEnd) { const day = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"][d.getDay()]; if (dailyBuckets[day] !== undefined) { dailyBuckets[day] += amt; } } // MONTH (rolling weeks) for (const b of weeklyBuckets) { if (d >= b.start && d <= b.end) { b.amount += amt; } } // YEAR (rolling months) for (const b of monthlyBuckets) { if (d >= b.start && d <= b.end) { b.amount += amt; } } } const toPoints = (b: any): ChartDataPoint[] => Array.isArray(b) ? b.map((x) => ({ id: x.label, amount: x.amount })) : Object.entries(b).map(([k, v]: any) => ({ id: k, amount: v })); const chartData = { daily: toPoints(dailyBuckets), weekly: toPoints(weeklyBuckets), monthly: toPoints(monthlyBuckets) }; // 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 = ( mode: "rolling" | "calendar" ) => fetchAggregatedData("expense", mode); export const fetchAggregatedIncome = ( mode: "rolling" | "calendar" ) => fetchAggregatedData("income", mode);