diff --git a/src/Dashboard.tsx b/src/Dashboard.tsx
index c32e87f..2ead0a9 100644
--- a/src/Dashboard.tsx
+++ b/src/Dashboard.tsx
@@ -1,138 +1,106 @@
import * as React from "react";
-import { Box, Container, Grid, Typography, Avatar } from "@mui/material";
+import { Box, Container, Grid, Typography, Avatar, CircularProgress, Alert } from "@mui/material";
import LatestItemsList, { LatestItem } from "./components/LatestItemsList";
import ProgressCard from "./components/ProgressCard";
import HistoryChart from "./components/HistoryChart";
-import ShoppingBagIcon from "@mui/icons-material/ShoppingBag";
-import SubscriptionsIcon from "@mui/icons-material/Subscriptions";
-import RestaurantIcon from "@mui/icons-material/Restaurant";
-import FoodBankIcon from "@mui/icons-material/FoodBank";
-
-const mockLatestItems: LatestItem[] = [
- {
- id: 1,
- icon: ,
- iconBgColor: "#e8f5e9",
- title: "Grocery Shopping",
- subtitle: "Buy some grocery",
- amount: "Rs 3000",
- timeAgo: "3 days ago",
- },
- {
- id: 2,
- icon: ,
- iconBgColor: "#f3e5f5",
- title: "Subscription",
- subtitle: "Netflix monthly",
- amount: "Rs 800",
- timeAgo: "5 days ago",
- },
- {
- id: 3,
- icon: ,
- iconBgColor: "#ffebee",
- title: "Food",
- subtitle: "Buy a chinese noodles",
- amount: "Rs 1000",
- timeAgo: "6 days ago",
- },
- {
- id: 4,
- icon: ,
- iconBgColor: "#fff8e1",
- title: "Food Club",
- subtitle: "Buy a chinese noodles",
- amount: "Rs 1000",
- timeAgo: "6 days ago",
- },
-];
-
-const mockChartData = {
- today: [
- { id: "6am", amount: 100 },
- { id: "9am", amount: 500 },
- { id: "12pm", amount: 200 },
- { id: "3pm", amount: 1000, highlighted: true },
- { id: "6pm", amount: 600 },
- { id: "9pm", amount: 300 },
- ],
- week: [
- { id: "Mon", amount: 1500 },
- { id: "Tue", amount: 1000 },
- { id: "Wed", amount: 2000 },
- { id: "Thu", amount: 500, highlighted: true },
- { id: "Fri", amount: 3000 },
- { id: "Sat", amount: 4500 },
- { id: "Sun", amount: 2000 },
- ],
- month: [
- { id: "Week 1", amount: 10000 },
- { id: "Week 2", amount: 5000 },
- { id: "Week 3", amount: 12000, highlighted: true },
- { id: "Week 4", amount: 8000 },
- ],
- year: [
- { id: "Q1", amount: 50000 },
- { id: "Q2", amount: 45000 },
- { id: "Q3", amount: 60000, highlighted: true },
- { id: "Q4", amount: 48000 },
- ],
-};
+import { fetchLatestExpenses, fetchAggregatedExpenses, AggregatedDashboardData } from "./utils/dashboardLoader";
export default function Dashboard() {
- return (
-
-
- {/* Left Column */}
-
- {/* User Greeting */}
-
-
-
- Hello Ananya
- Good Morning
-
-
+ const [latest, setLatest] = React.useState([]);
+ const [aggregated, setAggregated] = React.useState(null);
+ const [loading, setLoading] = React.useState(true);
+ const [error, setError] = React.useState(null);
- {
+ async function loadData() {
+ try {
+ setLoading(true);
+ const [latestData, aggData] = await Promise.all([
+ fetchLatestExpenses(),
+ fetchAggregatedExpenses()
+ ]);
+ setLatest(latestData);
+ setAggregated(aggData);
+ } catch (err: any) {
+ console.error(err);
+ setError(err.message || "Failed to load dashboard data");
+ } finally {
+ setLoading(false);
+ }
+ }
+ loadData();
+ }, []);
+
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+
+ if (error) {
+ return (
+
+ {error}
+
+ );
+ }
+
+ const themes = ["primary", "secondary", "info", "success", "warning"] as const;
+
+ return (
+
+
+ {/* Column 1: Latest Transactions */}
+
+ {}}
/>
-
-
- {}}
- />
-
- {/* Right Column */}
-
-
- Statistics
-
-
-
+
+ Spacer
+
+
+
+
+ {/* Column 3: Top Payees Progress */}
+
+
+ Top Analytics
+
-
-
+
+
+ Top 5 Payees
+ {aggregated?.topPayees.map((payee, idx) => (
+
+
+
+ ))}
diff --git a/src/utils/dashboardLoader.ts b/src/utils/dashboardLoader.ts
new file mode 100644
index 0000000..13c3890
--- /dev/null
+++ b/src/utils/dashboardLoader.ts
@@ -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 {
+ 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;
+ totalAmount: number;
+ topPayees: Array<{ payeeName: string; amount: number }>;
+}
+
+export async function fetchAggregatedExpenses(): Promise {
+ 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 = {};
+
+ // 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 = {
+ today: [],
+ week: [],
+ month: [],
+ year: []
+ };
+
+ const todayBuckets: Record = { "12am":0, "3am":0, "6am":0, "9am":0, "12pm":0, "3pm":0, "6pm":0, "9pm":0 };
+ const weekBuckets: Record = { "Mon":0, "Tue":0, "Wed":0, "Thu":0, "Fri":0, "Sat":0, "Sun":0 };
+ const monthBuckets: Record = { "Week 1":0, "Week 2":0, "Week 3":0, "Week 4":0, "Week 5":0 };
+ const yearBuckets: Record = { "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): 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
+ };
+}