Files
khata-ui/src/features/dashboard/dashboard.mapper.ts

177 lines
4.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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,
};
}