major refactor of the dashboard and react-openapi integration #1

Merged
aetos merged 44 commits from period-selection into main 2026-05-07 11:00:54 +00:00
2 changed files with 177 additions and 0 deletions
Showing only changes of commit 0e0928af95 - Show all commits

View File

@@ -0,0 +1,61 @@
export interface Payor {
name: string;
}
export interface Payee {
name: string;
}
export interface Account {
name: string;
number: string;
}
export interface Tag {
name: string;
icon: string;
description: string;
}
export interface Transaction {
payor: Payor;
payee: Payee;
amount: number;
account: Account;
tags: Tag[]
occurred_at: Date
}
export interface _PeriodData {
sum: number;
count: number;
average: number;
txns: Transaction[];
}
export interface PeriodData extends _PeriodData {
compare?: _PeriodData;
}
export interface PeriodGroup {
group_key: string[];
expenses: PeriodData[];
incomes: PeriodData[];
}
export interface Period {
id: string;
label: string;
start: Date;
end: Date;
groups: PeriodGroup[];
}
export interface ReportData {
period: "weekly" | "monthly" | "yearly" | "fyly" | "full";
rolling?: boolean;
report_date?: string;
group_by?: ("payee" | "tags")[];
ignore_self?: boolean;
buckets: Period[];
}

View File

@@ -0,0 +1,116 @@
import { ReportData } from "./report.models";
/* ---------- ID BUILDING ---------- */
function formatDate(d: Date): string {
const y = d.getUTCFullYear();
const m = String(d.getUTCMonth() + 1).padStart(2, "0");
const day = String(d.getUTCDate()).padStart(2, "0");
return `${y}-${m}-${day}`;
}
function buildPeriodId(
type: ReportData["period"],
start: Date,
end: Date
): string {
const s = formatDate(start);
const e = formatDate(end);
switch (type) {
case "weekly":
return `W:${s}_${e}`;
case "monthly":
return `M:${s}_${e}`;
case "yearly":
return `Y:${s}_${e}`;
case "fyly":
return `FY:${s}_${e}`;
case "full":
return `FULL:${s}_${e}`;
default:
return `${s}_${e}`;
}
}
/* ---------- LABEL BUILDING ---------- */
const dayFmt = new Intl.DateTimeFormat("en-GB", {
day: "numeric",
month: "short",
timeZone: "UTC",
});
const monthDayFmt = new Intl.DateTimeFormat("en-GB", {
month: "short",
day: "numeric",
timeZone: "UTC",
});
const monthFmt = new Intl.DateTimeFormat("en-GB", {
month: "short",
timeZone: "UTC",
});
const yearFmt = new Intl.DateTimeFormat("en-GB", {
year: "numeric",
timeZone: "UTC",
});
function sameMonth(a: Date, b: Date) {
return (
a.getUTCFullYear() === b.getUTCFullYear() &&
a.getUTCMonth() === b.getUTCMonth()
);
}
function buildLabel(
type: ReportData["period"],
start: Date,
end: Date
): string {
switch (type) {
case "weekly":
if (sameMonth(start, end)) {
const sDay = start.getUTCDate();
const eDay = end.getUTCDate();
const m = monthFmt.format(start);
return `${sDay} ${m} - ${eDay} ${m}`;
}
return `${dayFmt.format(start)} - ${dayFmt.format(end)}`;
case "monthly":
if (sameMonth(start, end)) {
return `${monthFmt.format(start)} ${yearFmt.format(start)}`;
}
return `${monthDayFmt.format(start)} - ${monthDayFmt.format(end)}`;
case "yearly":
return yearFmt.format(start);
case "fyly": {
const startY = start.getUTCFullYear();
const endY = end.getUTCFullYear();
return `FY ${startY}${String(endY).slice(-2)}`;
}
case "full":
return `${monthFmt.format(start)} ${yearFmt.format(start)} - ${monthFmt.format(end)} ${yearFmt.format(end)}`;
default:
return `${monthDayFmt.format(start)} - ${monthDayFmt.format(end)}`;
}
}
/* ---------- MAIN ---------- */
export function prepareReport(reportData: ReportData): ReportData {
return {
...reportData,
buckets: reportData.buckets.map((p) => ({
...p,
id: buildPeriodId(reportData.period, p.start, p.end),
label: buildLabel(reportData.period, p.start, p.end),
})),
};
}