Simpl-Resultat/src/pages/ReportsComparePage.tsx
le king fu fe9ae0118c feat(reports): adopt shared FilterPanel on Compare and Budget
Mounts <FilterPanel> on ReportsComparePage (next to the existing
CompareReferenceMonthPicker) and BudgetPage (next to the existing
YearNavigator), keeping each page's temporal control unchanged. Threads
accountIds from useReportsPeriod through useCompare into
getCompareMonthOverMonth/getCompareYearOverYear, and through
CompareBudgetView into getBudgetVsActualData — closing the gap where
that sub-tab silently ignored the filter while the rest of the Compare
page respected it.

Budget: the issue named getBudgetVsActualData as the target for
useBudget, but that hook never calls it (it's exclusive to
CompareBudgetView/Dashboard/Cartes) — its only real actuals fetch is
getActualTotalsForYear(year - 1), the previous-year reference column.
Extended that function with the same optional accountIds pass-through
established by #273, rather than wiring in an unrelated call shape.

Both hooks fetch their account checkbox list via getAllImportSources,
mirroring the useTrends/#275 pattern. Empty selection = no filter,
byte-identical to pre-#276 output (regression-tested).

Resolves #276

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 15:52:21 -04:00

141 lines
5.3 KiB
TypeScript

import { useState } from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { ArrowLeft } from "lucide-react";
import PeriodSelector from "../components/dashboard/PeriodSelector";
import FilterPanel from "../components/reports/FilterPanel";
import CompareModeTabs from "../components/reports/CompareModeTabs";
import CompareSubModeToggle from "../components/reports/CompareSubModeToggle";
import CompareReferenceMonthPicker from "../components/reports/CompareReferenceMonthPicker";
import ComparePeriodTable from "../components/reports/ComparePeriodTable";
import ComparePeriodChart from "../components/reports/ComparePeriodChart";
import CompareBudgetView from "../components/reports/CompareBudgetView";
import ViewModeToggle, { readViewMode, type ViewMode } from "../components/reports/ViewModeToggle";
import { useCompare, comparisonMeta } from "../hooks/useCompare";
import { useReportsPeriod } from "../hooks/useReportsPeriod";
const STORAGE_KEY = "reports-viewmode-compare";
function formatMonthLabel(year: number, month: number, language: string): string {
const date = new Date(year, month - 1, 1);
return new Intl.DateTimeFormat(language === "fr" ? "fr-CA" : "en-CA", {
month: "long",
year: "numeric",
}).format(date);
}
export default function ReportsComparePage() {
const { t, i18n } = useTranslation();
const { period, setPeriod, from, to, setCustomDates, accountIds, setAccountIds } =
useReportsPeriod();
const {
mode,
subMode,
setMode,
setSubMode,
setReferencePeriod,
year,
month,
rows,
accounts,
isLoading,
error,
} = useCompare();
const [viewMode, setViewMode] = useState<ViewMode>(() => readViewMode(STORAGE_KEY));
const preserveSearch = typeof window !== "undefined" ? window.location.search : "";
const { previousYear, previousMonth: prevMonth } = comparisonMeta(subMode, year, month);
// Monthly block labels: a specific month on both sides. For MoM the
// previous = previous month of same year; for YoY the previous = same
// month one year earlier (comparisonMeta already resolves both).
const currentLabel = formatMonthLabel(year, month, i18n.language);
const previousLabel = formatMonthLabel(previousYear, prevMonth, i18n.language);
// Cumulative YTD block labels. For MoM both sides live in the same year
// but the previous side only covers up to the end of the previous month,
// so we surface the end-of-window month label on each side. For YoY we
// compare Jan→refMonth across two different years; the year + month label
// makes the window boundary unambiguous.
const cumulativeCurrentLabel =
subMode === "mom"
? `${formatMonthLabel(year, month, i18n.language)}`
: `${String(year)}${formatMonthLabel(year, month, i18n.language)}`;
const cumulativePreviousLabel =
subMode === "mom"
? `${formatMonthLabel(previousYear, prevMonth, i18n.language)}`
: `${String(previousYear)}${formatMonthLabel(previousYear, prevMonth, i18n.language)}`;
const showActualControls = mode === "actual";
return (
<div className={isLoading ? "opacity-60" : ""}>
<div className="flex items-center gap-3 mb-4">
<Link
to={`/reports${preserveSearch}`}
className="text-[var(--muted-foreground)] hover:text-[var(--foreground)] p-1 rounded-md hover:bg-[var(--muted)]"
aria-label={t("reports.hub.title")}
>
<ArrowLeft size={18} />
</Link>
<h1 className="text-2xl font-bold">{t("reports.hub.compare")}</h1>
</div>
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-4 flex-wrap">
<PeriodSelector
value={period}
onChange={setPeriod}
customDateFrom={from}
customDateTo={to}
onCustomDateChange={setCustomDates}
/>
<div className="flex gap-2 items-center flex-wrap">
<CompareModeTabs value={mode} onChange={setMode} />
</div>
</div>
<FilterPanel
temporalControl={
<CompareReferenceMonthPicker
year={year}
month={month}
onChange={setReferencePeriod}
/>
}
accountIds={accountIds}
onAccountIdsChange={setAccountIds}
accounts={accounts}
/>
{showActualControls && (
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 mb-6 flex-wrap">
<CompareSubModeToggle value={subMode} onChange={setSubMode} />
<ViewModeToggle value={viewMode} onChange={setViewMode} storageKey={STORAGE_KEY} />
</div>
)}
{error && (
<div className="bg-[var(--negative)]/10 text-[var(--negative)] rounded-xl p-4 mb-6">
{error}
</div>
)}
{mode === "budget" ? (
<CompareBudgetView year={year} month={month} accountIds={accountIds} />
) : viewMode === "chart" ? (
<ComparePeriodChart
rows={rows}
previousLabel={previousLabel}
currentLabel={currentLabel}
/>
) : (
<ComparePeriodTable
rows={rows}
previousLabel={previousLabel}
currentLabel={currentLabel}
cumulativePreviousLabel={cumulativePreviousLabel}
cumulativeCurrentLabel={cumulativeCurrentLabel}
/>
)}
</div>
);
}