major refactor of the dashboard and react-openapi integration #1
98
src/components/ProgressCard/TopTags.tsx
Normal file
98
src/components/ProgressCard/TopTags.tsx
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { Box } from "@mui/material";
|
||||||
|
import { ReportData, ReportPeriod } from "../../features/report";
|
||||||
|
import ProgressCard from "./ProgressCard";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
reportData: ReportData;
|
||||||
|
mode: "expense" | "income";
|
||||||
|
selectedPeriodId?: string | null;
|
||||||
|
compact?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type DecoratedPeriod = ReportPeriod & {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getAmount(p: ReportPeriod, mode: "expense" | "income") {
|
||||||
|
return mode === "expense" ? p.expenses.sum : p.incomes.sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findPeriod(
|
||||||
|
periods: DecoratedPeriod[],
|
||||||
|
selectedPeriodId?: string | null
|
||||||
|
) {
|
||||||
|
if (!periods.length) return null;
|
||||||
|
|
||||||
|
if (selectedPeriodId) {
|
||||||
|
const match = periods.find((p) => p.id === selectedPeriodId);
|
||||||
|
if (match) return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback → latest
|
||||||
|
return periods.reduce((latest, p) =>
|
||||||
|
new Date(p.start).getTime() > new Date(latest.start).getTime()
|
||||||
|
? p
|
||||||
|
: latest
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TopTags({
|
||||||
|
reportData,
|
||||||
|
mode,
|
||||||
|
selectedPeriodId,
|
||||||
|
compact = true
|
||||||
|
}: Props) {
|
||||||
|
const { items, total } = React.useMemo(() => {
|
||||||
|
const tagMap = new Map<string, number>();
|
||||||
|
|
||||||
|
for (const bucket of reportData.buckets) {
|
||||||
|
const tags = bucket.group_key.tags;
|
||||||
|
if (!tags || tags.length === 0) continue;
|
||||||
|
|
||||||
|
// Prefer FULL if available
|
||||||
|
const fullPeriods = (bucket.periods.full || []) as DecoratedPeriod[];
|
||||||
|
|
||||||
|
const periodsToUse =
|
||||||
|
selectedPeriodId
|
||||||
|
? Object.values(bucket.periods).flat() as DecoratedPeriod[]
|
||||||
|
: fullPeriods;
|
||||||
|
|
||||||
|
const period = findPeriod(periodsToUse, selectedPeriodId);
|
||||||
|
if (!period) continue;
|
||||||
|
|
||||||
|
const amount = getAmount(period, mode);
|
||||||
|
|
||||||
|
for (const tag of tags) {
|
||||||
|
tagMap.set(tag, (tagMap.get(tag) || 0) + amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const arr = Array.from(tagMap.entries()).map(([tag, amount]) => ({
|
||||||
|
tag,
|
||||||
|
amount
|
||||||
|
}));
|
||||||
|
|
||||||
|
arr.sort((a, b) => b.amount - a.amount);
|
||||||
|
|
||||||
|
const top = arr.slice(0, 5);
|
||||||
|
const total = top.reduce((sum, t) => sum + t.amount, 0);
|
||||||
|
|
||||||
|
return { items: top, total };
|
||||||
|
}, [reportData, mode, selectedPeriodId]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: "grid", gap: 2 }}>
|
||||||
|
{items.map((item) => (
|
||||||
|
<ProgressCard
|
||||||
|
key={item.tag}
|
||||||
|
header={item.tag}
|
||||||
|
progressAmount={item.amount}
|
||||||
|
totalAmount={total}
|
||||||
|
compact={compact}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import HistoryChart from "./components/HistoryChart";
|
import HistoryChart from "./components/HistoryChart";
|
||||||
import ProgressCard from "./components/ProgressCard";
|
|
||||||
import LatestItems from "./components/LatestItems";
|
import LatestItems from "./components/LatestItems";
|
||||||
import { DashboardConfig } from "./components/Dashboard";
|
import { DashboardConfig } from "./components/Dashboard";
|
||||||
|
import TopTags from "./components/ProgressCard/TopTags";
|
||||||
|
|
||||||
export const configuration: DashboardConfig = {
|
export const configuration: DashboardConfig = {
|
||||||
sections: [
|
sections: [
|
||||||
@@ -10,7 +10,6 @@ export const configuration: DashboardConfig = {
|
|||||||
title: "Breakdown",
|
title: "Breakdown",
|
||||||
summary: "Interactive chronological tracking",
|
summary: "Interactive chronological tracking",
|
||||||
component: HistoryChart,
|
component: HistoryChart,
|
||||||
dataKey: "chartData",
|
|
||||||
settings: {
|
settings: {
|
||||||
tabs: ["Weekly", "Monthly"],
|
tabs: ["Weekly", "Monthly"],
|
||||||
// tabs: ["Weekly", "Monthly", "Yearly", "Financial Year", "All Time"],
|
// tabs: ["Weekly", "Monthly", "Yearly", "Financial Year", "All Time"],
|
||||||
@@ -22,8 +21,7 @@ export const configuration: DashboardConfig = {
|
|||||||
{
|
{
|
||||||
id: "top-payees",
|
id: "top-payees",
|
||||||
title: 'Top Payees',
|
title: 'Top Payees',
|
||||||
component: ProgressCard,
|
component: TopTags,
|
||||||
dataKey: "topPayees",
|
|
||||||
isList: true,
|
isList: true,
|
||||||
settings: {
|
settings: {
|
||||||
compact: true,
|
compact: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user