import * as React from "react"; import { Box, Container, CircularProgress, Alert, TextField, Paper, Autocomplete, Button } from "@mui/material"; import DashboardView from "./components/Dashboard"; import { DashboardState, DashboardStateSetters, DashboardFlow, } from "./components/Dashboard"; import { configuration } from "./dashboard-config"; import { useReport, prepareReport, } from "./features/report"; import { useReportSnapshotsList } from "./features/report-snapshots"; function formatSnapshotDate(iso: string) { const d = new Date(iso); return d.toLocaleString(undefined, { year: "numeric", month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", }); } export default function Dashboard() { const [state, setState] = React.useState({ flow: "outflows", periodType: "rolling", selectedPeriodId: null, selectedGroupKey: null, comparison: false, }); const [appliedPayees, setAppliedPayees] = React.useState([]); const [appliedTags, setAppliedTags] = React.useState([]); const [payeeInput, setPayeeInput] = React.useState([]); const [tagsInput, setTagsInput] = React.useState([]); const [loadedPayees, setLoadedPayees] = React.useState([]); const [loadedTags, setLoadedTags] = React.useState([]); const [selectedSnapshotId, setSelectedSnapshotId] = React.useState(null); const { data: snapshotsData } = useReportSnapshotsList(); const snapshotOptions = React.useMemo(() => { const options: { label: string; value: string | null }[] = [ { label: "Latest (auto)", value: null }, ]; if (snapshotsData?.items) { for (const snap of snapshotsData.items) { options.push({ label: `Snapshot from ${formatSnapshotDate(snap.created_at)}`, value: snap.snapshot_id, }); } } return options; }, [snapshotsData]); const selectedSnapshotOption = snapshotOptions.find((o) => o.value === selectedSnapshotId) ?? snapshotOptions[0]; const report = useReport({ snapshot_id: selectedSnapshotId ?? undefined, periods: ["daily", "weekly", "monthly", "all"], flow: state.flow, payee: appliedPayees.length > 0 ? appliedPayees : undefined, tags: appliedTags.length > 0 ? appliedTags : undefined, }); React.useEffect(() => { if (report.data) { setLoadedPayees(prev => { const pSet = new Set(prev); report.data.buckets.forEach((b: any) => { Object.values(b.periods).forEach((periodArray: any) => { periodArray?.forEach((p: any) => { p.metric?.transactions?.forEach((t: any) => { if (t.payee?.name) pSet.add(t.payee.name); }); }); }); }); return Array.from(pSet).sort(); }); setLoadedTags(prev => { const tSet = new Set(prev); report.data.buckets.forEach((b: any) => { Object.values(b.periods).forEach((periodArray: any) => { periodArray?.forEach((p: any) => { p.metric?.transactions?.forEach((t: any) => { t.tags?.forEach((tag: any) => tSet.add(tag.name || tag)); }); }); }); }); return Array.from(tSet).sort(); }); } }, [report.data]); const toggleFlow = React.useCallback(() => { setState((prev) => ({ ...prev, flow: prev.flow === "outflows" ? "inflows" : "outflows", selectedGroupKey: null, selectedPeriodId: null, })); }, []); const setFlow = React.useCallback( ( flow: DashboardFlow ) => { setState((prev) => ({ ...prev, flow, selectedGroupKey: null, selectedPeriodId: null, })); }, [] ); const togglePeriodType = React.useCallback(() => { setState((prev) => ({ ...prev, periodType: prev.periodType === "rolling" ? "calendar" : "rolling", })); }, []); const toggleComparison = React.useCallback(() => { setState((prev) => ({ ...prev, comparison: !prev.comparison, })); }, []); const setSelectedPeriodId = React.useCallback( ( selectedPeriodId: DashboardState["selectedPeriodId"] ) => { setState((prev) => ({ ...prev, selectedPeriodId, })); }, [] ); const setSelectedGroupKey = React.useCallback( ( selectedGroupKey: DashboardState["selectedGroupKey"] ) => { setState((prev) => ({ ...prev, selectedGroupKey, })); }, [] ); const stateSetters: DashboardStateSetters = React.useMemo( () => ({ toggleFlow, setFlow, togglePeriodType, toggleComparison, setSelectedPeriodId, setSelectedGroupKey, }), [ toggleFlow, setFlow, togglePeriodType, toggleComparison, setSelectedPeriodId, setSelectedGroupKey, ] ); const isLoading = report.isLoading; const error = report.error; if (isLoading && !report.data) { return ( ); } if (error) { return ( {String(error)} ); } if (!report.data) { return null; } const data = prepareReport(report.data); return ( Filter by Payee setPayeeInput(val as string[])} renderInput={(params) => } sx={{ '& .MuiOutlinedInput-root': { height: 'auto', minHeight: '2.5rem', py: 0.5 } }} /> Filter by Tags setTagsInput(val as string[])} renderInput={(params) => } sx={{ '& .MuiOutlinedInput-root': { height: 'auto', minHeight: '2.5rem', py: 0.5 } }} /> Snapshot setSelectedSnapshotId(option?.value ?? null)} getOptionLabel={(o) => o.label} isOptionEqualToValue={(o, v) => o.value === v.value} renderInput={(params) => } sx={{ '& .MuiOutlinedInput-root': { height: 'auto', minHeight: '2.5rem', py: 0.5 } }} /> ); }