diff --git a/src/utils/dashboardLoader.ts b/src/utils/dashboardLoader.ts index 082822b..dae695a 100644 --- a/src/utils/dashboardLoader.ts +++ b/src/utils/dashboardLoader.ts @@ -9,12 +9,35 @@ 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 res = await api.get("/expenses", { + params: { limit: 100, sort: "-occurred_at" } }); const items = res.data?.items || res.data || []; @@ -54,11 +77,11 @@ export interface AggregatedDashboardData { topPayees: Array<{ payeeName: string; amount: number }>; } -// ---------------- GENERIC AGGREGATOR ---------------- +// ---------------- AGGREGATION ---------------- export async function fetchAggregatedData( type: "expense" | "income" ): Promise { - const res = await api.get('/expenses', { params: { limit: 0 } }); + const res = await api.get("/expenses", { params: { limit: 0 } }); const all: any[] = res.data?.items || res.data || []; const now = new Date(); @@ -71,56 +94,58 @@ export async function fetchAggregatedData( const normalize = (amt: number) => Math.abs(amt); + // ---------------- WEEK ---------------- const weekBuckets: Record = { - "Mon":0,"Tue":0,"Wed":0,"Thu":0, - "Fri":0,"Sat":0,"Sun":0 + Mon: 0, Tue: 0, Wed: 0, Thu: 0, + Fri: 0, Sat: 0, Sun: 0 }; - const monthBuckets: Record = {}; - const yearBuckets: Record = {}; + const weekStart = getStartOfWeek(now); + const weekEnd = endOfDay(new Date(weekStart.getTime() + 6 * 86400000)); - 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; - }; + // ---------------- MONTH (5 rolling weeks) ---------------- + const monthBuckets: { + label: string; + start: Date; + end: Date; + amount: number; + }[] = []; - 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 end = endOfDay(new Date(now.getTime() - i * 7 * 86400000)); + const start = startOfDay(new Date(end.getTime() - 6 * 86400000)); - const start = new Date(end); - start.setDate(start.getDate() - 6); - - const key = `${format(start)} - ${format(end)}`; - - monthBuckets[key] = { amount: 0, range: key }; + monthBuckets.push({ + label: `${format(start)} - ${format(end)}`, + start, + end, + amount: 0 + }); } - // -------- YEAR (12 months rolling) -------- + // ---------------- YEAR (12 months) ---------------- + const yearBuckets: { + label: string; + start: Date; + end: Date; + amount: number; + }[] = []; + 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 end = endOfDay(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 }; + yearBuckets.push({ + label: d.toLocaleString("default", { month: "short" }), + start, + end, + amount: 0 + }); } - const weekStart = getStartOfWeek(now); - const weekEnd = new Date(weekStart); - weekEnd.setDate(weekStart.getDate() + 6); - // ---------------- LOOP ---------------- for (const item of all) { const d = new Date( @@ -137,7 +162,7 @@ export async function fetchAggregatedData( const payee = item.payee?.name || item.payee || "Unknown"; payeeMap[payee] = (payeeMap[payee] || 0) + amt; - // ---- WEEK + // WEEK if (d >= weekStart && d <= weekEnd) { const day = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"][d.getDay()]; if (weekBuckets[day] !== undefined) { @@ -145,25 +170,17 @@ export async function fetchAggregatedData( } } - // ---- 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; + // MONTH + monthBuckets.forEach(b => { + if (d >= b.start && d <= b.end) { + b.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; + // YEAR + yearBuckets.forEach(b => { + if (d >= b.start && d <= b.end) { + b.amount += amt; } }); } @@ -184,7 +201,9 @@ export async function fetchAggregatedData( // highlight max Object.values(chartData).forEach(group => { let max = group[0]; - for (const g of group) if (g.amount > max.amount) max = g; + for (const g of group) { + if (g.amount > max.amount) max = g; + } if (max.amount > 0) max.highlighted = true; });