fixed compare

This commit is contained in:
2026-04-07 10:49:10 +05:30
parent e6c7778c08
commit f4e5979c00
5 changed files with 257 additions and 193 deletions

View File

@@ -4,38 +4,18 @@ import { ChartDataPoint } from "../components/HistoryChart";
import * as React from "react";
import MonetizationOnIcon from "@mui/icons-material/MonetizationOn";
// ---------------- ICON ----------------
import {
buildDailyBuckets,
buildWeeklyRolling,
buildWeeklyCalendar,
buildMonthlyRolling,
buildMonthlyCalendar
} from "./periodBuilders";
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);
};
const shiftDate = (d: Date, days: number) =>
new Date(d.getTime() + days * 86400000);
// ---------------- LATEST ----------------
export async function fetchLatestTransactions(
type: "expense" | "income"
): Promise<LatestItem[]> {
@@ -66,35 +46,17 @@ export async function fetchLatestTransactions(
iconBgColor:
type === "expense" ? "#ffebee" : "#e8f5e9",
title: exp.payee?.name || exp.payee || "Unknown Payee",
subtitle: exp.category?.name || exp.account?.name || "Transaction",
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 ChartSeries {
rolling: ChartDataPoint[];
calendar: ChartDataPoint[];
}
export interface ChartData {
daily: ChartDataPoint[];
weekly: ChartSeries;
monthly: ChartSeries;
}
export interface AggregatedDashboardData {
chartData: ChartData;
totalAmount: number;
topPayees: Array<{ payeeName: string; amount: number }>;
}
// ---------------- AGGREGATION ----------------
export async function fetchAggregatedData(
type: "expense" | "income"
): Promise<AggregatedDashboardData> {
) {
const res = await api.get("/expenses", { params: { limit: 0 } });
const all: any[] = res.data?.items || res.data || [];
@@ -108,126 +70,19 @@ export async function fetchAggregatedData(
const normalize = (amt: number) => Math.abs(amt);
// ---------------- DAILY ----------------
const dailyBuckets: Record<string, any> = {
Mon: { amount: 0, compare: 0 },
Tue: { amount: 0, compare: 0 },
Wed: { amount: 0, compare: 0 },
Thu: { amount: 0, compare: 0 },
Fri: { amount: 0, compare: 0 },
Sat: { amount: 0, compare: 0 },
Sun: { amount: 0, compare: 0 }
};
const {
buckets: dailyBuckets,
weekStart,
weekEnd,
prevWeekStart,
prevWeekEnd
} = buildDailyBuckets(now);
const weekStart = getStartOfWeek(now);
const weekEnd = endOfDay(new Date(weekStart.getTime() + 6 * 86400000));
const prevWeekStart = shiftDate(weekStart, -7);
const prevWeekEnd = shiftDate(weekEnd, -7);
const weeklyRolling = buildWeeklyRolling(now);
const weeklyCalendar = buildWeeklyCalendar(now);
const monthlyRolling = buildMonthlyRolling(now);
const monthlyCalendar = buildMonthlyCalendar(now);
// ---------------- WEEKLY ----------------
const weeklyRolling: any[] = [];
const weeklyCalendar: any[] = [];
const currentWeekStart = getStartOfWeek(now);
// rolling (last 5 weeks)
for (let i = 4; i >= 0; i--) {
const start = new Date(currentWeekStart.getTime() - i * 7 * 86400000);
const end = endOfDay(new Date(start.getTime() + 6 * 86400000));
weeklyRolling.push({
label: `${format(start)} - ${format(end)}`,
start,
end,
amount: 0,
compare: 0,
prevStart: shiftDate(start, -7),
prevEnd: shiftDate(end, -7)
});
}
// calendar weeks
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
const endOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0);
const firstWeekStart = getStartOfWeek(startOfMonth);
const totalWeeks = Math.ceil(
(endOfMonth.getTime() - firstWeekStart.getTime()) / (7 * 86400000)
) + 1;
for (let i = 0; i < totalWeeks; i++) {
const start = new Date(firstWeekStart.getTime() + i * 7 * 86400000);
const end = endOfDay(new Date(start.getTime() + 6 * 86400000));
weeklyCalendar.push({
label: `${format(start)} - ${format(end)}`,
start,
end,
amount: 0,
compare: 0,
prevStart: shiftDate(start, -7),
prevEnd: shiftDate(end, -7)
});
}
// ---------------- MONTHLY ----------------
const monthlyRolling: any[] = [];
const monthlyCalendar: any[] = [];
// rolling (last 12 months)
for (let i = 11; i >= 0; 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)
: endOfDay(new Date(d.getFullYear(), d.getMonth() + 1, 0));
const prevStart = new Date(start);
prevStart.setMonth(prevStart.getMonth() - 1);
const prevEnd = new Date(end);
prevEnd.setMonth(prevEnd.getMonth() - 1);
monthlyRolling.push({
label: `${d.toLocaleString("default", { month: "short" })}-${String(
d.getFullYear()
).slice(2)}`,
start,
end,
amount: 0,
compare: 0,
prevStart,
prevEnd
});
}
// calendar (JanDec)
for (let i = 0; i < 12; i++) {
const start = new Date(now.getFullYear(), i, 1);
const end = endOfDay(new Date(now.getFullYear(), i + 1, 0));
const prevStart = new Date(start);
prevStart.setFullYear(prevStart.getFullYear() - 1);
const prevEnd = new Date(end);
prevEnd.setFullYear(prevEnd.getFullYear() - 1);
monthlyCalendar.push({
label: `${start.toLocaleString("default", { month: "short" })}-${String(
start.getFullYear()
).slice(2)}`,
start,
end,
amount: 0,
compare: 0,
prevStart,
prevEnd
});
}
// ---------------- LOOP ----------------
for (const item of all) {
const d = new Date(
item.occurred_at || item.created_at || Date.now()
@@ -237,46 +92,37 @@ export async function fetchAggregatedData(
if (!isValid(amtRaw)) continue;
const amt = normalize(amtRaw);
totalAmount += amt;
const payee = item.payee?.name || item.payee || "Unknown";
payeeMap[payee] = (payeeMap[payee] || 0) + amt;
// DAILY
const day = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"][d.getDay()];
if (d >= weekStart && d <= weekEnd) {
const day = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"][d.getDay()];
if (dailyBuckets[day]) {
dailyBuckets[day].amount += amt;
}
}
if (d >= prevWeekStart && d <= prevWeekEnd) {
const day = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"][d.getDay()];
if (dailyBuckets[day]) {
dailyBuckets[day].compare += amt;
}
}
// WEEKLY
for (const b of weeklyRolling) {
if (d >= b.start && d <= b.end) b.amount += amt;
if (d >= b.prevStart && d <= b.prevEnd) b.compare += amt;
}
for (const b of weeklyCalendar) {
if (d >= b.start && d <= b.end) b.amount += amt;
if (d >= b.prevStart && d <= b.prevEnd) b.compare += amt;
}
const apply = (arr: any[]) => {
for (const b of arr) {
if (d >= b.start && d <= b.end) b.amount += amt;
if (d >= b.prevStart && d <= b.prevEnd)
b.compare += amt;
}
};
// MONTHLY
for (const b of monthlyRolling) {
if (d >= b.start && d <= b.end) b.amount += amt;
if (d >= b.prevStart && d <= b.prevEnd) b.compare += amt;
}
for (const b of monthlyCalendar) {
if (d >= b.start && d <= b.end) b.amount += amt;
if (d >= b.prevStart && d <= b.prevEnd) b.compare += amt;
}
apply(weeklyRolling);
apply(weeklyCalendar);
apply(monthlyRolling);
apply(monthlyCalendar);
}
const toPoints = (arr: any[]): ChartDataPoint[] =>
@@ -286,7 +132,7 @@ export async function fetchAggregatedData(
compareAmount: x.compare
}));
const chartData: ChartData = {
const chartData = {
daily: Object.entries(dailyBuckets).map(([k, v]: any) => ({
id: k,
amount: v.amount,
@@ -302,7 +148,6 @@ export async function fetchAggregatedData(
}
};
// highlight max (current only)
Object.values(chartData).forEach((group: any) => {
const arr = Array.isArray(group) ? group : group.rolling;
if (!arr?.length) return;
@@ -322,7 +167,6 @@ export async function fetchAggregatedData(
return { chartData, totalAmount, topPayees };
}
// ---------------- EXPORTS ----------------
export const fetchAggregatedExpenses = () =>
fetchAggregatedData("expense");

33
src/utils/dateUtils.ts Normal file
View File

@@ -0,0 +1,33 @@
export const format = (d: Date) =>
`${d.getDate()} ${d.toLocaleString("default", { month: "short" })}`;
export const startOfDay = (d: Date) => {
const x = new Date(d);
x.setHours(0, 0, 0, 0);
return x;
};
export const endOfDay = (d: Date) => {
const x = new Date(d);
x.setHours(23, 59, 59, 999);
return x;
};
export 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);
};
export const shiftDate = (d: Date, days: number) =>
new Date(d.getTime() + days * 86400000);
export const getWeekIndex = (date: Date) => {
const firstDay = new Date(date.getFullYear(), date.getMonth(), 1);
const firstWeekStart = getStartOfWeek(firstDay);
return Math.floor(
(startOfDay(date).getTime() - firstWeekStart.getTime()) /
(7 * 86400000)
);
};

178
src/utils/periodBuilders.ts Normal file
View File

@@ -0,0 +1,178 @@
import {
format,
endOfDay,
getStartOfWeek,
shiftDate,
getWeekIndex
} from "./dateUtils";
export const buildDailyBuckets = (now: Date) => {
const buckets: Record<string, any> = {
Mon: { amount: 0, compare: 0 },
Tue: { amount: 0, compare: 0 },
Wed: { amount: 0, compare: 0 },
Thu: { amount: 0, compare: 0 },
Fri: { amount: 0, compare: 0 },
Sat: { amount: 0, compare: 0 },
Sun: { amount: 0, compare: 0 }
};
const weekStart = getStartOfWeek(now);
const weekEnd = endOfDay(new Date(weekStart.getTime() + 6 * 86400000));
const prevWeekStart = shiftDate(weekStart, -7);
const prevWeekEnd = shiftDate(weekEnd, -7);
return { buckets, weekStart, weekEnd, prevWeekStart, prevWeekEnd };
};
const getPrevMonthWeek = (start: Date) => {
const prevMonthDate = new Date(start);
prevMonthDate.setMonth(prevMonthDate.getMonth() - 1);
const prevMonthFirst = new Date(
prevMonthDate.getFullYear(),
prevMonthDate.getMonth(),
1
);
const prevFirstWeekStart = getStartOfWeek(prevMonthFirst);
const weekIndex = getWeekIndex(start);
const prevStart = new Date(
prevFirstWeekStart.getTime() + weekIndex * 7 * 86400000
);
const prevEnd = endOfDay(new Date(prevStart.getTime() + 6 * 86400000));
return { prevStart, prevEnd };
};
export const buildWeeklyRolling = (now: Date) => {
const arr: any[] = [];
const currentWeekStart = getStartOfWeek(now);
for (let i = 4; i >= 0; i--) {
const start = new Date(
currentWeekStart.getTime() - i * 7 * 86400000
);
const end = endOfDay(new Date(start.getTime() + 6 * 86400000));
const { prevStart, prevEnd } = getPrevMonthWeek(start);
arr.push({
label: `${format(start)} - ${format(end)}`,
start,
end,
amount: 0,
compare: 0,
prevStart,
prevEnd
});
}
return arr;
};
export const buildWeeklyCalendar = (now: Date) => {
const arr: any[] = [];
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
const endOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0);
const firstWeekStart = getStartOfWeek(startOfMonth);
const totalWeeks =
Math.ceil(
(endOfMonth.getTime() - firstWeekStart.getTime()) /
(7 * 86400000)
) + 1;
for (let i = 0; i < totalWeeks; i++) {
const start = new Date(
firstWeekStart.getTime() + i * 7 * 86400000
);
const end = endOfDay(new Date(start.getTime() + 6 * 86400000));
const { prevStart, prevEnd } = getPrevMonthWeek(start);
arr.push({
label: `${format(start)} - ${format(end)}`,
start,
end,
amount: 0,
compare: 0,
prevStart,
prevEnd
});
}
return arr;
};
export const buildMonthlyRolling = (now: Date) => {
const arr: any[] = [];
for (let i = 11; i >= 0; 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)
: endOfDay(new Date(d.getFullYear(), d.getMonth() + 1, 0));
const prevStart = new Date(start);
prevStart.setFullYear(prevStart.getFullYear() - 1);
let prevEnd = new Date(end);
prevEnd.setFullYear(prevEnd.getFullYear() - 1);
if (i === 0) {
prevEnd = new Date(prevStart);
prevEnd.setDate(now.getDate());
prevEnd = endOfDay(prevEnd);
}
arr.push({
label: `${d.toLocaleString("default", {
month: "short"
})}-${String(d.getFullYear()).slice(2)}`,
start,
end,
amount: 0,
compare: 0,
prevStart,
prevEnd
});
}
return arr;
};
export const buildMonthlyCalendar = (now: Date) => {
const arr: any[] = [];
for (let i = 0; i < 12; i++) {
const start = new Date(now.getFullYear(), i, 1);
const end = endOfDay(new Date(now.getFullYear(), i + 1, 0));
const prevStart = new Date(start);
prevStart.setFullYear(prevStart.getFullYear() - 1);
const prevEnd = new Date(end);
prevEnd.setFullYear(prevEnd.getFullYear() - 1);
arr.push({
label: `${start.toLocaleString("default", {
month: "short"
})}-${String(start.getFullYear()).slice(2)}`,
start,
end,
amount: 0,
compare: 0,
prevStart,
prevEnd
});
}
return arr;
};