major refactor of the dashboard and react-openapi integration #1
@@ -1,3 +1,13 @@
|
|||||||
export {
|
export {
|
||||||
useReport
|
useReport
|
||||||
} from './useReport'
|
} from './useReport'
|
||||||
|
export type {
|
||||||
|
Transaction,
|
||||||
|
PeriodData,
|
||||||
|
PeriodGroup,
|
||||||
|
Period,
|
||||||
|
ReportData,
|
||||||
|
} from './report.models'
|
||||||
|
export {
|
||||||
|
prepareReport
|
||||||
|
} from './report.utils'
|
||||||
|
|||||||
@@ -1,143 +0,0 @@
|
|||||||
import {
|
|
||||||
AggregatedDashboardData,
|
|
||||||
ChartData,
|
|
||||||
ChartDataPoint,
|
|
||||||
} from "../../components/HistoryChart";
|
|
||||||
|
|
||||||
type ReportBucket = any;
|
|
||||||
|
|
||||||
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"
|
|
||||||
): AggregatedDashboardData {
|
|
||||||
const flow = type === "expense" ? "expenses" : "incomes";
|
|
||||||
|
|
||||||
const chartData: ChartData = {
|
|
||||||
daily: [],
|
|
||||||
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,14 +1,18 @@
|
|||||||
import { useResourceByName } from "../../../react-openapi";
|
import { useResourceByName } from "../../../react-openapi";
|
||||||
|
|
||||||
export interface ReportParams {
|
export interface ReportParams {
|
||||||
period: "weekly" | "monthly" | "yearly" | "fyly";
|
period: "weekly" | "monthly" | "yearly" | "fyly" | "full";
|
||||||
rolling?: boolean;
|
rolling?: boolean;
|
||||||
report_date?: string;
|
report_date?: string;
|
||||||
group_by?: ("flow" | "payee" | "tags")[];
|
group_by?: ("payee" | "tags")[];
|
||||||
ignore_self?: boolean;
|
ignore_self?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useReport(params: ReportParams) {
|
export function useReport(params: ReportParams) {
|
||||||
const { useList } = useResourceByName("reports");
|
const { useList } = useResourceByName("reports");
|
||||||
|
if (params.group_by) {
|
||||||
|
// @ts-ignore
|
||||||
|
params.group_by = params.group_by[0]
|
||||||
|
}
|
||||||
return useList(params);
|
return useList(params);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user