Simpl-Resultat/src/components/reports/CompareModeTabs.tsx
le king fu ff350d75e7
Some checks failed
PR Check / rust (push) Successful in 23m24s
PR Check / frontend (push) Successful in 2m17s
PR Check / frontend (pull_request) Has been cancelled
PR Check / rust (pull_request) Has been cancelled
feat: compare report — MoM / YoY / budget with view toggle (#73)
- Services: getCompareMonthOverMonth(year, month) and getCompareYearOverYear(year)
  return CategoryDelta[] (expense-side, ABS aggregates, parameterised SQL only)
- Shared CategoryDelta type with HighlightMover now aliased to it
- Flesh out useCompare hook: reducer + fetch + automatic year/month inference
  from the shared useReportsPeriod `to` date; budget mode skips fetch and
  delegates to CompareBudgetView which wraps the existing BudgetVsActualTable
- Components: CompareModeTabs (MoM/YoY/Budget tabs), ComparePeriodTable (sortable
  table with signed delta coloring), ComparePeriodChart (diverging horizontal
  bar chart with ChartPatternDefs for SVG patterns), CompareBudgetView
  (fetches budget rows for the current target year/month)
- ReportsComparePage wires everything with PeriodSelector + ViewModeToggle
  (storage key reports-viewmode-compare); chart/table toggle is hidden in budget
  mode since the budget table has its own presentation
- i18n keys: reports.compare.modeMoM / modeYoY / modeBudget in FR + EN
- 4 new vitest cases for the compare services: parameterised boundaries,
  January wrap-around to December previous year, delta conversion with
  previous=0 fallback to null pct, year-over-year spans

Fixes #73

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 14:57:13 -04:00

38 lines
1.2 KiB
TypeScript

import { useTranslation } from "react-i18next";
import type { CompareMode } from "../../hooks/useCompare";
export interface CompareModeTabsProps {
value: CompareMode;
onChange: (mode: CompareMode) => void;
}
export default function CompareModeTabs({ value, onChange }: CompareModeTabsProps) {
const { t } = useTranslation();
const modes: { id: CompareMode; labelKey: string }[] = [
{ id: "mom", labelKey: "reports.compare.modeMoM" },
{ id: "yoy", labelKey: "reports.compare.modeYoY" },
{ id: "budget", labelKey: "reports.compare.modeBudget" },
];
return (
<div className="inline-flex gap-1" role="tablist">
{modes.map(({ id, labelKey }) => (
<button
key={id}
type="button"
role="tab"
onClick={() => onChange(id)}
aria-selected={value === id}
className={`px-3 py-2 rounded-lg text-sm font-medium transition-colors ${
value === id
? "bg-[var(--primary)] text-white"
: "bg-[var(--card)] border border-[var(--border)] text-[var(--foreground)] hover:bg-[var(--muted)]"
}`}
>
{t(labelKey)}
</button>
))}
</div>
);
}