From 787324260c5a11e57fd9d752766baa7d074a4fae Mon Sep 17 00:00:00 2001 From: Vishesh 'ironeagle' Bangotra Date: Mon, 6 Apr 2026 18:07:38 +0530 Subject: [PATCH] rolling and calender toggle --- src/Dashboard.tsx | 12 ++-- src/components/HistoryChart.tsx | 20 ++++++ src/utils/dashboardLoader.ts | 115 +++++++++++++++++++------------- 3 files changed, 97 insertions(+), 50 deletions(-) diff --git a/src/Dashboard.tsx b/src/Dashboard.tsx index 7110ade..e69d272 100644 --- a/src/Dashboard.tsx +++ b/src/Dashboard.tsx @@ -36,8 +36,8 @@ export default function Dashboard() { income: null }); - const [mode, setMode] = - React.useState<"expense" | "income">("expense"); + const [mode, setMode] = React.useState<"expense" | "income">("expense"); + const [period, setPeriod] = React.useState<"rolling" | "calendar">("rolling"); const [loading, setLoading] = React.useState(true); const [error, setError] = React.useState(null); @@ -56,8 +56,8 @@ export default function Dashboard() { ] = await Promise.all([ fetchLatestTransactions("expense"), fetchLatestTransactions("income"), - fetchAggregatedExpenses(), - fetchAggregatedIncome() + fetchAggregatedExpenses(period), + fetchAggregatedIncome(period) ]); setLatest({ @@ -79,7 +79,7 @@ export default function Dashboard() { } loadData(); - }, []); + }, [period]); const currentData = aggregated[mode]; const currentLatest = latest[mode]; @@ -132,6 +132,8 @@ export default function Dashboard() { summary="Interactive chronological tracking" tabs={["Daily", "Weekly", "Monthly"]} data={currentData?.chartData || {}} + period={period} + onPeriodChange={setPeriod} /> diff --git a/src/components/HistoryChart.tsx b/src/components/HistoryChart.tsx index 29c06fb..0d5c65b 100644 --- a/src/components/HistoryChart.tsx +++ b/src/components/HistoryChart.tsx @@ -13,6 +13,8 @@ export interface HistoryChartProps { summary?: string; tabs: string[]; data: Record; + period: "rolling" | "calendar", + onPeriodChange: (mode: "rolling" | "calendar") => void; } export default function HistoryChart({ @@ -20,6 +22,8 @@ export default function HistoryChart({ summary, tabs, data, + period, + onPeriodChange, }: HistoryChartProps) { const [activeTab, setActiveTab] = React.useState(tabs[0] || ""); @@ -102,6 +106,22 @@ export default function HistoryChart({ ))} + v && onPeriodChange(v)} + size="small" + sx={{ mb: 2 }} + > + Rolling + + Calendar + + + {/* Chart Area */} {currentData.length > 0 ? ( diff --git a/src/utils/dashboardLoader.ts b/src/utils/dashboardLoader.ts index f16e152..c99f6e1 100644 --- a/src/utils/dashboardLoader.ts +++ b/src/utils/dashboardLoader.ts @@ -80,7 +80,8 @@ export interface AggregatedDashboardData { // ---------------- AGGREGATION ---------------- // ---------------- AGGREGATION ---------------- export async function fetchAggregatedData( - type: "expense" | "income" + 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 || []; @@ -105,56 +106,76 @@ export async function fetchAggregatedData( const weekEnd = endOfDay(new Date(weekStart.getTime() + 6 * 86400000)); // ---------------- MONTH (rolling 5 weeks, Mon–Sun aligned) ---------------- - const weeklyBuckets: { - label: string; - start: Date; - end: Date; - amount: number; - }[] = []; + const weeklyBuckets = []; - const currentWeekStart = getStartOfWeek(now); + 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)); + 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 - }); + 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: { - label: string; - start: Date; - end: Date; - amount: number; - }[] = []; + const monthlyBuckets = []; - for (let i = 0; i < 12; i++) { - const d = new Date(now); - d.setMonth(d.getMonth() - i); + 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 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)); + 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)); - const label = `${d.toLocaleString("default", { - month: "short" - })}-${String(d.getFullYear()).slice(2)}`; - - monthlyBuckets.push({ - label, - start, - end, - amount: 0 - }); + monthlyBuckets.push({ + label: `${start.toLocaleString("default", { month: "short" })}-${String(start.getFullYear()).slice(2)}`, + start, + end, + amount: 0 + }); + } } // ---------------- LOOP ---------------- @@ -231,8 +252,12 @@ export async function fetchAggregatedData( } // ---------------- EXPORTS ---------------- -export const fetchAggregatedExpenses = () => - fetchAggregatedData("expense"); +export const fetchAggregatedExpenses = ( + mode: "rolling" | "calendar" +) => + fetchAggregatedData("expense", mode); -export const fetchAggregatedIncome = () => - fetchAggregatedData("income"); \ No newline at end of file +export const fetchAggregatedIncome = ( + mode: "rolling" | "calendar" +) => + fetchAggregatedData("income", mode); \ No newline at end of file