- Transform /reports into a hub: highlights panel + 4 nav cards - New service: reportService.getHighlights (parameterised SQL, deterministic via referenceDate argument for tests, computes current-month balance, YTD, 12-month sparkline series, top expense movers vs previous month, top recent transactions within configurable 30/60/90 day window) - Extended types: HighlightsData, HighlightMover, MonthBalance - Wired useHighlights hook with reducer + window-days state - Hub tiles (flat naming under src/components/reports): HubNetBalanceTile, HubTopMoversTile, HubTopTransactionsTile, HubHighlightsPanel, HubReportNavCard - Detailed ReportsHighlightsPage: balance tiles, sortable top movers table, diverging bar chart (Recharts + patterns SVG), top transactions list with 30/60/90 window toggle; ViewModeToggle persistence keyed as reports-viewmode-highlights - New i18n keys: reports.hub.*, reports.highlights.* - 5 new vitest cases: empty profile, parameterised queries, window sizing, delta computation, zero-previous divisor handling Fixes #71 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
73 lines
2.5 KiB
TypeScript
73 lines
2.5 KiB
TypeScript
import { useTranslation } from "react-i18next";
|
|
import { Sparkles, TrendingUp, Scale, Search } from "lucide-react";
|
|
import { PageHelp } from "../components/shared/PageHelp";
|
|
import PeriodSelector from "../components/dashboard/PeriodSelector";
|
|
import HubHighlightsPanel from "../components/reports/HubHighlightsPanel";
|
|
import HubReportNavCard from "../components/reports/HubReportNavCard";
|
|
import { useHighlights } from "../hooks/useHighlights";
|
|
import { useReportsPeriod } from "../hooks/useReportsPeriod";
|
|
|
|
export default function ReportsPage() {
|
|
const { t } = useTranslation();
|
|
const { period, setPeriod, from, to, setCustomDates } = useReportsPeriod();
|
|
const { data, isLoading, error } = useHighlights();
|
|
|
|
const preserveSearch = typeof window !== "undefined" ? window.location.search : "";
|
|
const navCards = [
|
|
{
|
|
to: `/reports/highlights${preserveSearch}`,
|
|
icon: <Sparkles size={24} />,
|
|
title: t("reports.hub.highlights"),
|
|
description: t("reports.hub.highlightsDescription"),
|
|
},
|
|
{
|
|
to: `/reports/trends${preserveSearch}`,
|
|
icon: <TrendingUp size={24} />,
|
|
title: t("reports.hub.trends"),
|
|
description: t("reports.hub.trendsDescription"),
|
|
},
|
|
{
|
|
to: `/reports/compare${preserveSearch}`,
|
|
icon: <Scale size={24} />,
|
|
title: t("reports.hub.compare"),
|
|
description: t("reports.hub.compareDescription"),
|
|
},
|
|
{
|
|
to: `/reports/category${preserveSearch}`,
|
|
icon: <Search size={24} />,
|
|
title: t("reports.hub.categoryZoom"),
|
|
description: t("reports.hub.categoryZoomDescription"),
|
|
},
|
|
];
|
|
|
|
return (
|
|
<div>
|
|
<div className="relative flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-6">
|
|
<div className="flex items-center gap-3">
|
|
<h1 className="text-2xl font-bold">{t("reports.hub.title")}</h1>
|
|
<PageHelp helpKey="reports" />
|
|
</div>
|
|
<PeriodSelector
|
|
value={period}
|
|
onChange={setPeriod}
|
|
customDateFrom={from}
|
|
customDateTo={to}
|
|
onCustomDateChange={setCustomDates}
|
|
/>
|
|
</div>
|
|
|
|
<HubHighlightsPanel data={data} isLoading={isLoading} error={error} />
|
|
|
|
<section>
|
|
<h2 className="text-sm font-semibold uppercase tracking-wide text-[var(--muted-foreground)] mb-3">
|
|
{t("reports.hub.explore")}
|
|
</h2>
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3">
|
|
{navCards.map((card) => (
|
|
<HubReportNavCard key={card.to} {...card} />
|
|
))}
|
|
</div>
|
|
</section>
|
|
</div>
|
|
);
|
|
}
|