Compare commits

..

36 Commits

Author SHA1 Message Date
df5cf9fbb6 reading reportData instead of customized data. self massaging data for needed format 2026-05-07 15:04:28 +05:30
4b046c15a5 passing reportData instead of data 2026-05-07 15:04:02 +05:30
02eb55995e passing needed periods and group_by 2026-05-07 15:03:37 +05:30
4e56d86cdb passing clean periods and group_by params instead of periods[] and group_by[] params 2026-05-07 15:02:52 +05:30
15f76eb5f0 new report structure 2026-05-06 14:11:03 +05:30
7470da6d2d all period types 2026-05-05 13:26:35 +05:30
34594215f9 using new state management 2026-05-05 12:58:59 +05:30
0a92126b92 toggleComparison 2026-05-05 12:58:45 +05:30
30cf227050 state controlled by Dashboard.models.ts 2026-05-05 12:26:02 +05:30
a0e62b1bc4 fixes 2026-05-05 11:42:33 +05:30
ea3b451266 removed latest items for now 2026-05-05 11:42:27 +05:30
4b4875c3f5 removed latest items for now 2026-05-05 11:39:38 +05:30
25bd882b75 dashboard using report feature 2026-05-05 11:39:23 +05:30
f684083496 report models and prepareReport function 2026-05-05 11:39:07 +05:30
0e0928af95 report models and prepareReport function 2026-05-02 14:25:55 +05:30
7b0b3fb615 period selection 2026-05-01 17:40:54 +05:30
38f7416942 refactor period (rolling/calender) to periodType 2026-05-01 17:17:17 +05:30
e82cad4f21 refactor period (rolling/calender) to periodType 2026-05-01 17:15:16 +05:30
1daa90d091 refactor period (rolling/calender) to periodType 2026-05-01 17:13:13 +05:30
2d0b0bc470 fixes for correct comparison period 2026-05-01 17:07:35 +05:30
5f85abdf86 use full for fast loading than weekly for payee report 2026-05-01 16:59:56 +05:30
cc7e6509d2 theme changes 2026-04-25 13:47:42 +05:30
8a3ebdb1be better name 2026-04-25 13:22:49 +05:30
a36d9119bb configurable Dashboard.tsx 2026-04-25 13:21:34 +05:30
67d4c85146 fixes 2026-04-25 12:49:58 +05:30
89ad8e376e Progress Cards for Top Payees 2026-04-25 12:41:05 +05:30
71afc157ff proper use of react-openapi for resource api calls 2026-04-25 12:13:28 +05:30
5acbb7ccdd hiding report resource 2026-04-24 14:55:38 +05:30
3fd20f11ab hidden resource 2026-04-24 14:55:29 +05:30
922d05ae37 dashboard feature to use dashboard data 2026-04-24 14:41:19 +05:30
1fe44abfde removed client data massaging with backend report using feature/report 2026-04-24 14:27:20 +05:30
49bdb85088 refactored ProgressCard to component 2026-04-24 14:04:24 +05:30
b1509fd5ab refactored LatestItems to component 2026-04-24 14:01:49 +05:30
175ca64d1f refactored HistoryChart to component 2026-04-24 13:58:12 +05:30
c9e609fee6 header fixes 2026-04-11 11:23:06 +05:30
82264a5c34 color pallete 2026-04-07 13:47:55 +05:30
7 changed files with 34 additions and 168 deletions

View File

@@ -19,6 +19,7 @@ export interface DashboardSection {
title?: string;
summary?: string;
component: React.ComponentType<any>;
dataKey: string;
settings?: Record<string, any>;
isList?: boolean;
style?: {

View File

@@ -8,12 +8,11 @@ type DecoratedPeriod = ReportPeriod & {
label: string;
};
const TAB_TO_KEY: Record<string, "weekly" | "monthly" | "yearly" | "fyly" | "full"> = {
const TAB_TO_KEY: Record<string, "weekly" | "monthly" | "yearly" | "fyly"> = {
Weekly: "weekly",
Monthly: "monthly",
Yearly: "yearly",
'Financial Year': "fyly",
'All Time': "full"
FYLY: "fyly"
};
function getAmount(p: ReportPeriod, mode: "expense" | "income") {
@@ -40,7 +39,7 @@ function mergeMetric(a: any, b: any) {
function mergeBuckets(
buckets: any[],
key: "weekly" | "monthly" | "yearly" | "fyly" | "full"
key: "weekly" | "monthly" | "yearly" | "fyly"
): DecoratedPeriod[] {
const map = new Map<string, DecoratedPeriod>();
@@ -71,38 +70,9 @@ function mergeBuckets(
);
}
function attachComparison(
points: ChartDataPoint[],
key: "weekly" | "monthly" | "yearly" | "fyly" | "full"
): ChartDataPoint[] {
const getCompareIndex = (i: number) => {
if (key === "weekly") return i - 4;
if (key === "monthly") return i - 12;
if (key === "yearly") return i - 1;
if (key === "fyly") return i - 1;
return -1;
};
return points.map((p, i) => {
const ci = getCompareIndex(i);
return {
...p,
compare:
ci >= 0 && points[ci]
? {
id: points[ci].id,
label: points[ci].label,
amount: points[ci].amount
}
: undefined
};
});
}
function buildChartData(
reportData: HistoryChartProps["reportData"],
key: "weekly" | "monthly" | "yearly" | "fyly" | "full",
key: "weekly" | "monthly" | "yearly" | "fyly",
mode: "expense" | "income",
comparison: boolean
): ChartDataPoint[] {
@@ -116,7 +86,17 @@ function buildChartData(
}));
if (comparison) {
points = attachComparison(points, key);
points = points.map((p, i) => ({
...p,
compare:
i > 0
? {
id: points[i - 1].id,
label: points[i - 1].label,
amount: points[i - 1].amount
}
: undefined
}));
}
return points;
@@ -158,8 +138,7 @@ export default function HistoryChart(props: HistoryChartProps) {
weekly: 6,
monthly: 4,
yearly: 4,
fyly: 4,
full: 4,
fyly: 4
};
const visibleCount = visibleCountMap[activeDataKey] ?? 4;

View File

@@ -58,28 +58,19 @@ export default function HistoryChartView(props: ViewProps) {
const theme = useTheme();
const isDark = theme.palette.mode === "dark";
const total = currentData.length;
const maxStartIndex = Math.max(total - visibleCount, 0);
const clampedStartIndex = Math.min(startIndex, maxStartIndex);
const handleTabChange = (_: React.MouseEvent<HTMLElement>, newTab: string | null) => {
if (newTab !== null) setActiveTab(newTab);
};
const canGoLeft = clampedStartIndex > 0;
const canGoRight = clampedStartIndex < maxStartIndex;
const canGoLeft = startIndex > 0;
const canGoRight = startIndex + visibleCount < currentData.length;
const handlePrev = () => {
if (!canGoLeft) return;
setStartIndex((prev) => Math.max(prev - visibleCount, 0));
if (canGoLeft) setStartIndex((prev) => prev - visibleCount);
};
const handleNext = () => {
if (!canGoRight) return;
setStartIndex((prev) => {
const next = prev + visibleCount;
return Math.min(next, maxStartIndex);
});
if (canGoRight) setStartIndex((prev) => prev + visibleCount);
};
return (
@@ -157,8 +148,7 @@ export default function HistoryChartView(props: ViewProps) {
display: "flex",
flexDirection: "column",
alignItems: "center",
cursor: "pointer",
height: "100%"
cursor: "pointer"
}}
>
<Box sx={{ display: "flex", alignItems: "flex-end", gap: 1, height: "100%" }}>

View File

@@ -1,109 +0,0 @@
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, 4);
const total = top.reduce((sum, t) => sum + t.amount, 0);
return { items: top, total };
}, [reportData, mode, selectedPeriodId]);
return (
<Box
sx={{
display: "grid",
gridTemplateColumns: {
xs: "1fr",
sm: "repeat(2, 1fr)",
md: "repeat(4, 1fr)"
},
gap: 2
}}
>
{items.map((item) => (
<ProgressCard
key={item.tag}
header={item.tag}
progressAmount={item.amount}
totalAmount={total}
compact={compact}
colorTheme={mode === "expense" ? "error" : "success"}
/>
))}
</Box>
);
}

View File

@@ -1,7 +1,7 @@
import HistoryChart from "./components/HistoryChart";
import ProgressCard from "./components/ProgressCard";
import LatestItems from "./components/LatestItems";
import { DashboardConfig } from "./components/Dashboard";
import TopTags from "./components/ProgressCard/TopTags";
export const configuration: DashboardConfig = {
sections: [
@@ -10,6 +10,7 @@ export const configuration: DashboardConfig = {
title: "Breakdown",
summary: "Interactive chronological tracking",
component: HistoryChart,
dataKey: "chartData",
settings: {
tabs: ["Weekly", "Monthly"],
// tabs: ["Weekly", "Monthly", "Yearly", "Financial Year", "All Time"],
@@ -21,7 +22,9 @@ export const configuration: DashboardConfig = {
{
id: "top-payees",
title: 'Top Payees',
component: TopTags,
component: ProgressCard,
dataKey: "topPayees",
isList: true,
settings: {
compact: true,
},

View File

@@ -67,7 +67,6 @@ export interface ReportBucket {
monthly?: ReportPeriod[];
yearly?: ReportPeriod[];
fyly?: ReportPeriod[];
full?: ReportPeriod[];
};
}
@@ -76,7 +75,7 @@ export interface ReportBucket {
// -----------------------------
export interface ReportData {
periods: ("weekly" | "monthly" | "yearly" | "fyly" | "full")[];
periods: ("weekly" | "monthly" | "yearly" | "fyly")[];
rolling: boolean;
report_date?: string;

View File

@@ -83,7 +83,10 @@ function buildLabel(
return `${dayFmt.format(start)} - ${dayFmt.format(end)}`;
case "monthly":
return `${monthFmt.format(start)} ${yearFmt.format(start)}`;
if (sameMonth(start, end)) {
return `${monthFmt.format(start)} ${yearFmt.format(start)}`;
}
return `${monthDayFmt.format(start)} - ${monthDayFmt.format(end)}`;
case "yearly":
return yearFmt.format(start);
@@ -107,8 +110,8 @@ function decoratePeriods(
): (ReportPeriod & { id: string; label: string })[] {
return periods.map((p) => ({
...p,
id: buildPeriodId(type, new Date(p.start + "Z"), new Date(p.end + "Z")),
label: buildLabel(type, new Date(p.start + "Z"), new Date(p.end + "Z")),
id: buildPeriodId(type, new Date(p.start), new Date(p.end)),
label: buildLabel(type, new Date(p.start), new Date(p.end)),
}));
}