import React, { useCallback, useEffect, useMemo, useState } from "react"; import { Container, Box, Paper, Typography, Alert, Accordion, AccordionSummary, AccordionDetails, } from "@mui/material"; import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; import AssessmentIcon from "@mui/icons-material/Assessment"; import { useResource, useAppContext } from "../../react-openapi"; import { useToast } from "../ui/Toast"; import { PageHeader } from "../ui/PageHeader"; import { EmptyState } from "../ui/EmptyState"; import { StatCard } from "../common/components/StatCard"; import { buildTxnFieldConfigs } from "../common/utils/fieldConfigs"; import { GenerateReportPanel } from "./GenerateReportPanel"; import { ReportList } from "./ReportList"; import { ReportViewer } from "./ReportViewer"; import { apiErrorMessage, buildReportFieldConfigs } from "./types"; export default function Report() { const { resources } = useAppContext(); const { showToast } = useToast(); const { list, loading, error } = useResource("reports"); const { create, remove } = useResource("reports"); const [reports, setReports] = useState(null); const [selectedId, setSelectedId] = useState(null); const [viewerVersion, setViewerVersion] = useState(0); const reportFields = useMemo(() => buildReportFieldConfigs(resources), [resources]); const txnFields = useMemo(() => buildTxnFieldConfigs(resources), [resources]); const load = useCallback(async () => { const res = await list({ limit: 200 }); setReports(res.items ?? []); }, [list]); useEffect(() => { load(); }, [load]); const handleGenerated = useCallback( (created: any) => { load(); if (created?.id) setSelectedId(created.id); }, [load], ); const handleRegenerate = useCallback( async (report: any) => { try { const created = await create(report.query ?? {}); showToast("Report regenerated"); setSelectedId(created?.id ?? report.id); setViewerVersion((v) => v + 1); load(); } catch (e: any) { showToast(apiErrorMessage(e), "error"); } }, [create, load, showToast], ); const handleDelete = useCallback( async (id: string) => { try { await remove(id); showToast("Report deleted"); if (selectedId === id) setSelectedId(null); load(); } catch (e: any) { showToast(apiErrorMessage(e), "error"); } }, [remove, load, selectedId, showToast], ); const summary = useMemo(() => { const rows = reports ?? []; const granularities = new Set(); for (const r of rows) for (const g of r.query?.granularities ?? []) granularities.add(g); return { count: rows.length, granularities: [...granularities].join(", ") }; }, [reports]); return ( {error && ( Failed to load reports: {error} )} } sx={{ px: 2.5, py: 1, "& .MuiAccordionSummary-content": { alignItems: "center", gap: 1.5, minWidth: 0, }, }} > Saved reports {reports !== null && ( {reports.length} report{reports.length === 1 ? "" : "s"} )} {reports !== null && reports.length === 0 ? ( } title="No reports yet" description="Generate your first snapshot above — choose granularities and grouping dimensions, then slice the cached cube by period, payee and tag." /> ) : ( setSelectedId(id)} onRegenerate={handleRegenerate} onDelete={handleDelete} /> )} {selectedId && ( setSelectedId(null)} onRegenerated={handleRegenerate} /> )} ); }