dashboard loader

This commit is contained in:
2026-04-04 20:14:39 +05:30
parent 84059a84b5
commit 68337ba312
2 changed files with 243 additions and 121 deletions

View File

@@ -0,0 +1,154 @@
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";
// Helper generic icon
const DEFAULT_ICON = React.createElement(MonetizationOnIcon, { sx: { color: "#388e3c" } });
export async function fetchLatestExpenses(): Promise<LatestItem[]> {
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 diffTime = Math.abs(Date.now() - time);
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
const timeText = diffDays === 0 ? "Today" : `${diffDays} days ago`;
return {
id: exp.id || index,
icon: DEFAULT_ICON,
iconBgColor: "#e8f5e9", // soft green
title: exp.payee?.name || exp.payee || "Unknown Payee",
subtitle: exp.category?.name || exp.account?.name || "Expense",
amount: `Rs ${exp.amount || 0}`,
timeAgo: timeText,
};
});
}
export interface AggregatedDashboardData {
chartData: Record<string, ChartDataPoint[]>;
totalAmount: number;
topPayees: Array<{ payeeName: string; amount: number }>;
}
export async function fetchAggregatedExpenses(): Promise<AggregatedDashboardData> {
const res = await api.get('/expenses', { params: { limit: 0 } });
const allExpenses: any[] = res.data?.items || res.data || [];
let maxTime = 0;
let totalAmount = 0;
const payeeMap: Record<string, number> = {};
// 1. Gather Total Amount, Max Time, and Payee accumulations
for (const exp of allExpenses) {
const amt = Number(exp.amount) || 0;
if (amt >= 0) continue; // skip income / non-expense
totalAmount += amt;
const payeeName = exp.payee?.name || exp.payee || "Unknown";
payeeMap[payeeName] = (payeeMap[payeeName] || 0) + amt;
const time = new Date(exp.occurred_at || exp.created_at || Date.now()).getTime();
if (time > maxTime) maxTime = time;
}
if (maxTime === 0) maxTime = Date.now();
const baseDate = new Date(maxTime);
// 2. Chart Groups
const chartData: Record<string, ChartDataPoint[]> = {
today: [],
week: [],
month: [],
year: []
};
const todayBuckets: Record<string, number> = { "12am":0, "3am":0, "6am":0, "9am":0, "12pm":0, "3pm":0, "6pm":0, "9pm":0 };
const weekBuckets: Record<string, number> = { "Mon":0, "Tue":0, "Wed":0, "Thu":0, "Fri":0, "Sat":0, "Sun":0 };
const monthBuckets: Record<string, number> = { "Week 1":0, "Week 2":0, "Week 3":0, "Week 4":0, "Week 5":0 };
const yearBuckets: Record<string, number> = { "Jan":0, "Feb":0, "Mar":0, "Apr":0, "May":0, "Jun":0, "Jul":0, "Aug":0, "Sep":0, "Oct":0, "Nov":0, "Dec":0 };
const getDayName = (dayIdx: number) => ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][dayIdx];
const getMonthName = (monthIdx: number) => ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][monthIdx];
for (const exp of allExpenses) {
const d = new Date(exp.occurred_at || exp.created_at || Date.now());
const amt = Number(exp.amount) || 0;
if (amt >= 0) continue; // skip income / non-expense
// Check Today
if (d.getFullYear() === baseDate.getFullYear() && d.getMonth() === baseDate.getMonth() && d.getDate() === baseDate.getDate()) {
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;
}
// Check Week
const diffDays = (baseDate.getTime() - d.getTime()) / (1000 * 3600 * 24);
if (diffDays >= 0 && diffDays < 7) {
weekBuckets[getDayName(d.getDay())] += amt;
}
// Check Month
if (d.getFullYear() === baseDate.getFullYear() && d.getMonth() === baseDate.getMonth()) {
const dNum = d.getDate();
let wLabel = "Week 1";
if (dNum > 7 && dNum <= 14) wLabel = "Week 2";
else if (dNum > 14 && dNum <= 21) wLabel = "Week 3";
else if (dNum > 21 && dNum <= 28) wLabel = "Week 4";
else if (dNum > 28) wLabel = "Week 5";
monthBuckets[wLabel] += amt;
}
// Check Year
if (d.getFullYear() === baseDate.getFullYear()) {
yearBuckets[getMonthName(d.getMonth())] += amt;
}
}
const convertBucket = (b: Record<string, number>): ChartDataPoint[] => {
return Object.keys(b).map(k => ({ id: k, amount: b[k] }));
};
chartData.today = convertBucket(todayBuckets);
chartData.week = convertBucket(weekBuckets);
chartData.month = convertBucket(monthBuckets);
chartData.year = convertBucket(yearBuckets);
for (const group of Object.values(chartData)) {
if(group.length === 0) continue;
let maxObj = group[0];
for (const p of group) {
if (p.amount > maxObj.amount) maxObj = p;
}
if (maxObj.amount > 0) {
maxObj.highlighted = true;
}
}
// 3. Top Payees
const topPayees = Object.entries(payeeMap)
.map(([name, amt]) => ({ payeeName: name, amount: amt }))
.sort((a, b) => b.amount - a.amount)
.slice(0, 3);
return {
chartData,
totalAmount,
topPayees
};
}