154 lines
3.8 KiB
TypeScript
154 lines
3.8 KiB
TypeScript
import * as React from "react";
|
|
import {
|
|
Box,
|
|
Container,
|
|
Grid,
|
|
CircularProgress,
|
|
Alert,
|
|
ToggleButton,
|
|
ToggleButtonGroup
|
|
} from "@mui/material";
|
|
|
|
import LatestItemsList, { LatestItem } from "./components/LatestItemsList";
|
|
import HistoryChart from "./components/HistoryChart";
|
|
import {
|
|
AggregatedDashboardData
|
|
} from "./types/historyChart";
|
|
|
|
import {
|
|
fetchLatestTransactions,
|
|
fetchAggregatedExpenses,
|
|
fetchAggregatedIncome,
|
|
} from "./utils/dashboardLoader";
|
|
|
|
export default function Dashboard() {
|
|
const [latest, setLatest] = React.useState<{
|
|
expense: LatestItem[];
|
|
income: LatestItem[];
|
|
}>({
|
|
expense: [],
|
|
income: []
|
|
});
|
|
|
|
const [aggregated, setAggregated] = React.useState<{
|
|
expense: AggregatedDashboardData | null;
|
|
income: AggregatedDashboardData | null;
|
|
}>({
|
|
expense: null,
|
|
income: null
|
|
});
|
|
|
|
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) {
|
|
return (
|
|
<Box sx={{ display: "flex", justifyContent: "center", alignItems: "center", height: "60vh" }}>
|
|
<CircularProgress />
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<Container sx={{ mt: 4 }}>
|
|
<Alert severity="error">{error}</Alert>
|
|
</Container>
|
|
);
|
|
}
|
|
|
|
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>
|
|
);
|
|
} |