Files
khata-ui/src/utils/dashboardLoader.ts

193 lines
5.2 KiB
TypeScript

import { api } from "../../react-openapi";
import { LatestItem } from "../components/LatestItemsList";
import { ChartDataPoint } from "../types/historyChart";
import * as React from "react";
import { format } from "./dateUtils";
import MonetizationOnIcon from "@mui/icons-material/MonetizationOn";
import {
buildDailyBuckets,
buildWeeklyRolling,
buildWeeklyCalendar,
buildMonthlyRolling,
buildMonthlyCalendar
} from "./periodBuilders";
const DEFAULT_ICON = React.createElement(MonetizationOnIcon, {
sx: { color: "#388e3c" }
});
export async function fetchLatestTransactions(
type: "expense" | "income"
): Promise<LatestItem[]> {
const res = await api.get("/expenses", {
params: { limit: 100, sort: "-occurred_at" }
});
const items = res.data?.items || res.data || [];
const isValid = (amt: number) =>
type === "expense" ? amt < 0 : amt > 0;
return items
.filter((item: any) => isValid(Number(item.amount) || 0))
.slice(0, 5)
.map((exp: any, index: number) => {
const time = new Date(
exp.occurred_at || exp.created_at || Date.now()
).getTime();
const diffDays = Math.floor(
Math.abs(Date.now() - time) / (1000 * 60 * 60 * 24)
);
return {
id: exp.id || index,
icon: DEFAULT_ICON,
iconBgColor:
type === "expense" ? "#ffebee" : "#e8f5e9",
title: exp.payee?.name || exp.payee || "Unknown Payee",
subtitle:
exp.category?.name || exp.account?.name || "Transaction",
amount: `Rs ${Math.abs(exp.amount || 0)}`,
timeAgo: diffDays === 0 ? "Today" : `${diffDays} days ago`
};
});
}
export async function fetchAggregatedData(
type: "expense" | "income"
) {
const res = await api.get("/expenses", { params: { limit: 0 } });
const all: any[] = res.data?.items || res.data || [];
const now = new Date();
let totalAmount = 0;
const payeeMap: Record<string, number> = {};
const isValid = (amt: number) =>
type === "expense" ? amt < 0 : amt > 0;
const normalize = (amt: number) => Math.abs(amt);
const {
buckets: dailyBuckets,
weekStart,
weekEnd,
prevWeekStart,
prevWeekEnd
} = buildDailyBuckets(now);
const weeklyRolling = buildWeeklyRolling(now);
const weeklyCalendar = buildWeeklyCalendar(now);
const monthlyRolling = buildMonthlyRolling(now);
const monthlyCalendar = buildMonthlyCalendar(now);
for (const item of all) {
const d = new Date(
item.occurred_at || item.created_at || Date.now()
);
const amtRaw = Number(item.amount) || 0;
if (!isValid(amtRaw)) continue;
const amt = normalize(amtRaw);
totalAmount += amt;
const payee = item.payee?.name || item.payee || "Unknown";
payeeMap[payee] = (payeeMap[payee] || 0) + amt;
const day = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"][d.getDay()];
if (d >= weekStart && d <= weekEnd) {
if (dailyBuckets[day]) {
dailyBuckets[day].amount += amt;
}
}
if (d >= prevWeekStart && d <= prevWeekEnd) {
if (dailyBuckets[day]) {
dailyBuckets[day].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;
}
};
apply(weeklyRolling);
apply(weeklyCalendar);
apply(monthlyRolling);
apply(monthlyCalendar);
}
const toPoints = (arr: any[], type: "weekly" | "monthly"): ChartDataPoint[] =>
arr.map((x) => {
let compareLabel: string | undefined;
if (x.prevStart && x.prevEnd) {
if (type === "monthly") {
const year = String(x.prevStart.getFullYear()).slice(2);
compareLabel = `${x.prevStart.toLocaleString("default", {
month: "short"
})}-${year}`;
} else {
const year = String(x.prevEnd.getFullYear()).slice(2);
compareLabel = `${format(x.prevStart)} - ${format(x.prevEnd)} ${year}`;
}
}
return {
id: x.label,
amount: x.amount,
compareAmount: x.compare,
compareLabel
};
});
const chartData = {
daily: Object.entries(dailyBuckets).map(([k, v]: any) => ({
id: k,
amount: v.amount,
compareAmount: v.compare
})),
weekly: {
rolling: toPoints(weeklyRolling, "weekly"),
calendar: toPoints(weeklyCalendar, "weekly")
},
monthly: {
rolling: toPoints(monthlyRolling, "monthly"),
calendar: toPoints(monthlyCalendar, "monthly")
}
};
Object.values(chartData).forEach((group: any) => {
const arr = Array.isArray(group) ? group : group.rolling;
if (!arr?.length) return;
let max = arr[0];
for (const g of arr) {
if (g.amount > max.amount) max = g;
}
if (max.amount > 0) max.highlighted = true;
});
const topPayees = Object.entries(payeeMap)
.map(([name, amt]) => ({ payeeName: name, amount: amt }))
.sort((a, b) => b.amount - a.amount)
.slice(0, 5);
return { chartData, totalAmount, topPayees };
}
export const fetchAggregatedExpenses = () =>
fetchAggregatedData("expense");
export const fetchAggregatedIncome = () =>
fetchAggregatedData("income");