major refactor of the dashboard and react-openapi integration (#1)
## Summary This MR introduces a major refactor of the dashboard and react-openapi integration, focusing on configurability, separation of concerns, and improved extensibility. --- ## Key Changes ### 1. OpenAPI / Admin Refactor * Extracted `ConfigContext` into a dedicated provider. * Introduced `AppProvider` to encapsulate: * Config loading * API client initialization * React Query setup * Removed internal `QueryClientProvider` from `Admin` for better composability. * `Admin` now supports both: * Standalone usage * Nested usage inside an existing provider ### 2. Resource System Improvements * Added `hidden` flag to `ResourceConfig` and overrides. * Admin UI now filters out hidden resources. * Added `useResourceByName` helper for dynamic resource access. * Improved `useResource`: * Handles undefined config safely * Adds guards for missing endpoints * Disables queries when endpoint is absent ### 3. API Client Enhancements * Added custom `paramsSerializer`: * Serializes arrays without `[]` * Ensures backend-compatible query formats ### 4. Dashboard Architecture Overhaul * Replaced hardcoded dashboard with **config-driven system**: * Introduced `ConfigurableDashboard` * Dashboard sections defined via config * New state model: * `mode` (expense/income) * `periodType` (rolling/calendar) * `comparison` * `selectedPeriodId` ### 5. Component Refactor (View / Logic Split) * Split major components into: * `.tsx` (logic/controller) * `.view.tsx` (presentation) * `.models.ts` (types) * Applied to: * Dashboard * HistoryChart * ProgressCard * LatestItems ### 6. HistoryChart Redesign * Fully rebuilt using report-driven data * Supports: * Weekly / Monthly / Yearly / FY / Full views * Rolling vs Calendar periods * Comparison mode (auto-aligned offsets) * Introduced: * Bucket merging logic * Dynamic comparison attachment ### 7. Reporting Integration * Dashboard now powered by: * `useReport` * `prepareReport` * Removes need for multiple manual API calls ### 8. UI / UX Improvements * Theme-aware color system * Dynamic accent colors per mode * Cleaner layout using section-based rendering * Improved selection and interaction in charts ### 9. Cleanup & Removals * Removed legacy components: * Old `HistoryChart` * Old `ProgressCard` * Simplified Header layout spacing --- ## Behavior Changes * Hidden resources are no longer visible in Admin UI. * Dashboard is now entirely configuration-driven. * API query params for arrays no longer use `[]`. * Resource hooks no longer crash on missing config. --- ## Risks / Considerations * Dashboard depends on correct configuration structure. * Hidden flag may unintentionally hide resources if misconfigured. * Query param serialization change must align with backend expectations. --- ## Follow-ups * Add typing improvements to remove `@ts-ignore` in `GenericForm`. * Extend dashboard config with more reusable section presets. * Add tests for report aggregation and comparison logic. --- Reviewed-on: #1 Co-authored-by: Vishesh 'ironeagle' Bangotra <aetoskia@gmail.com> Co-committed-by: Vishesh 'ironeagle' Bangotra <aetoskia@gmail.com>
This commit is contained in:
@@ -2,100 +2,30 @@ import * as React from "react";
|
||||
import {
|
||||
Box,
|
||||
Container,
|
||||
Grid,
|
||||
CircularProgress,
|
||||
Alert,
|
||||
ToggleButton,
|
||||
ToggleButtonGroup
|
||||
Alert
|
||||
} from "@mui/material";
|
||||
|
||||
import LatestItemsList, { LatestItem } from "./components/LatestItemsList";
|
||||
import HistoryChart from "./components/HistoryChart";
|
||||
import ConfigurableDashboard from "./components/Dashboard";
|
||||
import { configuration } from "./dashboard-config";
|
||||
import {
|
||||
AggregatedDashboardData
|
||||
} from "./types/historyChart";
|
||||
|
||||
import {
|
||||
fetchLatestTransactions,
|
||||
fetchAggregatedExpenses,
|
||||
fetchAggregatedIncome,
|
||||
} from "./utils/dashboardLoader";
|
||||
useReport,
|
||||
prepareReport,
|
||||
} from "./features/report";
|
||||
|
||||
export default function Dashboard() {
|
||||
const [latest, setLatest] = React.useState<{
|
||||
expense: LatestItem[];
|
||||
income: LatestItem[];
|
||||
}>({
|
||||
expense: [],
|
||||
income: []
|
||||
});
|
||||
const report = useReport({
|
||||
periods: ["weekly", "monthly", "full"],
|
||||
rolling: true,
|
||||
include_transactions: true,
|
||||
group_by: ["tags"],
|
||||
})
|
||||
|
||||
const [aggregated, setAggregated] = React.useState<{
|
||||
expense: AggregatedDashboardData | null;
|
||||
income: AggregatedDashboardData | null;
|
||||
}>({
|
||||
expense: null,
|
||||
income: null
|
||||
});
|
||||
const isLoading = report.isLoading;
|
||||
const error = report.error;
|
||||
|
||||
const [mode, setMode] = React.useState<"expense" | "income">("expense");
|
||||
const [period, setPeriod] = React.useState<"rolling" | "calendar">("rolling");
|
||||
const [comparison, setComparison] = React.useState(false);
|
||||
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
const [error, setError] = React.useState<string | null>(null);
|
||||
|
||||
// -------- LOAD ONCE --------
|
||||
React.useEffect(() => {
|
||||
async function loadData() {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
const [
|
||||
latestExpense,
|
||||
latestIncome,
|
||||
expenseData,
|
||||
incomeData
|
||||
] = await Promise.all([
|
||||
fetchLatestTransactions("expense"),
|
||||
fetchLatestTransactions("income"),
|
||||
fetchAggregatedExpenses(),
|
||||
fetchAggregatedIncome()
|
||||
]);
|
||||
|
||||
setLatest({
|
||||
expense: latestExpense,
|
||||
income: latestIncome
|
||||
});
|
||||
|
||||
setAggregated({
|
||||
expense: expenseData,
|
||||
income: incomeData
|
||||
});
|
||||
|
||||
} catch (err: any) {
|
||||
console.error(err);
|
||||
setError(err.message || "Failed to load dashboard data");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
loadData();
|
||||
}, []);
|
||||
|
||||
const currentData = aggregated[mode];
|
||||
if (!currentData) {
|
||||
return (
|
||||
<Box sx={{ display: "flex", justifyContent: "center", alignItems: "center", height: "60vh" }}>
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
const currentLatest = latest[mode];
|
||||
|
||||
// -------- UI STATES --------
|
||||
if (loading) {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Box sx={{ display: "flex", justifyContent: "center", alignItems: "center", height: "60vh" }}>
|
||||
<CircularProgress />
|
||||
@@ -106,49 +36,20 @@ export default function Dashboard() {
|
||||
if (error) {
|
||||
return (
|
||||
<Container sx={{ mt: 4 }}>
|
||||
<Alert severity="error">{error}</Alert>
|
||||
<Alert severity="error">{String(error)}</Alert>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
if (!report) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = prepareReport(report.data?.data);
|
||||
return (
|
||||
<Container sx={{ mt: 4, mb: 4 }}>
|
||||
{/* -------- TOGGLE -------- */}
|
||||
<Box sx={{ display: "flex", justifyContent: "center", mb: 3 }}>
|
||||
<ToggleButtonGroup
|
||||
value={mode}
|
||||
exclusive
|
||||
onChange={(_, val) => val && setMode(val)}
|
||||
>
|
||||
<ToggleButton value="expense">Expenses</ToggleButton>
|
||||
<ToggleButton value="income">Income</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</Box>
|
||||
|
||||
<Grid container spacing={4} direction="row">
|
||||
|
||||
<Grid size={12}>
|
||||
<HistoryChart
|
||||
header={`${mode === "expense" ? "Expense" : "Income"} Breakdown`}
|
||||
summary="Interactive chronological tracking"
|
||||
tabs={["Daily", "Weekly", "Monthly"]}
|
||||
data={currentData.chartData}
|
||||
period={period}
|
||||
onPeriodChange={setPeriod}
|
||||
comparison={comparison}
|
||||
setComparison={setComparison}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid size={12}>
|
||||
<LatestItemsList
|
||||
title={`Recent ${mode === "expense" ? "Expenses" : "Income"}`}
|
||||
items={currentLatest}
|
||||
onViewAll={() => {}}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
</Container>
|
||||
<ConfigurableDashboard
|
||||
config={configuration}
|
||||
data={data}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user