177 lines
4.4 KiB
TypeScript
177 lines
4.4 KiB
TypeScript
import * as React from "react";
|
||
import MonetizationOnIcon from "@mui/icons-material/MonetizationOn";
|
||
import { LatestItem } from "../../components/LatestItems";
|
||
import {
|
||
ChartData,
|
||
ChartDataPoint,
|
||
} from "../../components/HistoryChart";
|
||
|
||
const DEFAULT_ICON = React.createElement(MonetizationOnIcon, {
|
||
sx: { color: "#388e3c" }
|
||
});
|
||
type ReportBucket = any;
|
||
|
||
export function mapToLatestItems(
|
||
items: any[],
|
||
type: "expense" | "income"
|
||
): LatestItem[] {
|
||
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).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,
|
||
subtitle: exp.account.name,
|
||
amount: `Rs ${Math.abs(exp.amount || 0)}`,
|
||
timeAgo: diffDays === 0 ? "Today" : `${diffDays} days ago`
|
||
};
|
||
});
|
||
}
|
||
|
||
const sumBucket = (bucket: ReportBucket, flow: "expenses" | "incomes") =>
|
||
bucket.groups.reduce(
|
||
(acc: number, g: any) => acc + (g?.[flow]?.sum || 0),
|
||
0
|
||
);
|
||
|
||
const toLabel = (start: string, end: string, type: "weekly" | "monthly") => {
|
||
const s = new Date(start);
|
||
const e = new Date(end);
|
||
|
||
if (type === "monthly") {
|
||
return s.toLocaleString("default", { month: "short" });
|
||
}
|
||
|
||
return `${s.getDate()}–${e.getDate()} ${e.toLocaleString("default", {
|
||
month: "short",
|
||
})}`;
|
||
};
|
||
|
||
const getWeekOfMonth = (date: Date) => {
|
||
const firstDay = new Date(date.getFullYear(), date.getMonth(), 1);
|
||
return Math.ceil((date.getDate() + firstDay.getDay()) / 7);
|
||
};
|
||
|
||
const findCompareBucket = (
|
||
current: ReportBucket,
|
||
buckets: ReportBucket[],
|
||
type: "weekly" | "monthly"
|
||
): ReportBucket | undefined => {
|
||
const start = new Date(current.start);
|
||
|
||
if (type === "monthly") {
|
||
const targetYear = start.getFullYear() - 1;
|
||
const targetMonth = start.getMonth();
|
||
|
||
return buckets.find(b => {
|
||
const d = new Date(b.start);
|
||
return (
|
||
d.getFullYear() === targetYear &&
|
||
d.getMonth() === targetMonth
|
||
);
|
||
});
|
||
}
|
||
|
||
if (type === "weekly") {
|
||
const weekIndex = getWeekOfMonth(start); // you must define this
|
||
const target = new Date(start);
|
||
target.setMonth(target.getMonth() - 1);
|
||
|
||
return buckets.find(b => {
|
||
const d = new Date(b.start);
|
||
return (
|
||
d.getFullYear() === target.getFullYear() &&
|
||
d.getMonth() === target.getMonth() &&
|
||
getWeekOfMonth(d) === weekIndex
|
||
);
|
||
});
|
||
}
|
||
|
||
return undefined;
|
||
};
|
||
|
||
const toPoints = (
|
||
buckets: ReportBucket[],
|
||
type: "weekly" | "monthly",
|
||
flow: "expenses" | "incomes"
|
||
): ChartDataPoint[] => {
|
||
return buckets.map((b) => {
|
||
const amount = sumBucket(b, flow);
|
||
const prev = findCompareBucket(b, buckets, type);
|
||
|
||
return {
|
||
id: toLabel(b.start, b.end, type),
|
||
amount,
|
||
compare: prev
|
||
? {
|
||
id: toLabel(prev.start, prev.end, type),
|
||
amount: sumBucket(prev, flow),
|
||
}
|
||
: undefined,
|
||
};
|
||
});
|
||
};
|
||
|
||
export function mapReportToDashboard(
|
||
weekly: ReportBucket[],
|
||
monthly: ReportBucket[],
|
||
payeeBuckets: ReportBucket[],
|
||
type: "expense" | "income"
|
||
) {
|
||
const flow = type === "expense" ? "expenses" : "incomes";
|
||
|
||
const chartData: ChartData = {
|
||
weekly: {
|
||
rolling: toPoints(weekly, "weekly", flow),
|
||
calendar: toPoints(weekly, "weekly", flow),
|
||
},
|
||
|
||
monthly: {
|
||
rolling: toPoints(monthly, "monthly", flow),
|
||
calendar: toPoints(monthly, "monthly", flow),
|
||
},
|
||
};
|
||
|
||
const totalAmount = weekly.reduce(
|
||
(acc, b) => acc + sumBucket(b, flow),
|
||
0
|
||
);
|
||
|
||
const payeeMap: Record<string, number> = {};
|
||
|
||
const sourceForPayees = (payeeBuckets && payeeBuckets.length > 0) ? payeeBuckets : weekly;
|
||
|
||
for (const b of sourceForPayees) {
|
||
for (const g of b.groups) {
|
||
const key = g.group_key || "Unknown";
|
||
const amt = g?.[flow]?.sum || 0;
|
||
payeeMap[key] = (payeeMap[key] || 0) + amt;
|
||
}
|
||
}
|
||
|
||
const topPayees = Object.entries(payeeMap)
|
||
// .filter(([name]) => name !== "Unknown")
|
||
.map(([payeeName, amount]) => ({ payeeName, amount }))
|
||
.sort((a, b) => b.amount - a.amount)
|
||
.slice(0, 5);
|
||
|
||
return {
|
||
chartData,
|
||
totalAmount,
|
||
topPayees,
|
||
};
|
||
}
|