diff --git a/CHANGELOG.fr.md b/CHANGELOG.fr.md
index 58ee54e..61e944f 100644
--- a/CHANGELOG.fr.md
+++ b/CHANGELOG.fr.md
@@ -2,6 +2,9 @@
## [Non publié]
+### Modifié
+- **Rapport Comparables** (`/reports/compare`) : passage de trois onglets (MoM / YoY / Budget) à deux modes (Réel vs réel / Réel vs budget). La vue « Réel vs réel » affiche désormais un sélecteur de mois de référence en en-tête (défaut : mois précédent), un sous-toggle MoM ↔ YoY, et un graphique en barres groupées côte-à-côte (deux barres par catégorie : période de référence vs période comparée). Le `PeriodSelector` d'URL reste synchronisé avec le sélecteur de mois (#96)
+
## [0.8.0] - 2026-04-14
### Ajouté
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 088f71a..23d39ef 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,9 @@
## [Unreleased]
+### Changed
+- **Compare report** (`/reports/compare`): reduced from three tabs (MoM / YoY / Budget) to two modes (Actual vs. actual / Actual vs. budget). The actual-vs-actual view now has an explicit reference-month dropdown in the header (defaults to the previous month), a MoM ↔ YoY sub-toggle, and a grouped side-by-side bar chart (two bars per category: reference period vs. comparison period). The URL `PeriodSelector` stays in sync with the reference month picker (#96)
+
## [0.8.0] - 2026-04-14
### Added
diff --git a/docs/architecture.md b/docs/architecture.md
index 755a346..5415bec 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -149,7 +149,7 @@ Chaque hook encapsule la logique d'état via `useReducer` :
| `useReportsPeriod` | Période de reporting synchronisée via query string (bookmarkable) |
| `useHighlights` | Panneau de faits saillants du hub rapports |
| `useTrends` | Rapport Tendances (sous-vue flux global / par catégorie) |
-| `useCompare` | Rapport Comparables (mode MoM / YoY / budget) |
+| `useCompare` | Rapport Comparables (mode `actual`/`budget`, sous-toggle MoM ↔ YoY, mois de référence explicite avec wrap-around janvier) |
| `useCategoryZoom` | Rapport Zoom catégorie avec rollup sous-catégories |
| `useDataExport` | Export de données |
| `useTheme` | Thème clair/sombre |
diff --git a/src/components/reports/CompareModeTabs.tsx b/src/components/reports/CompareModeTabs.tsx
index a33d985..745b09f 100644
--- a/src/components/reports/CompareModeTabs.tsx
+++ b/src/components/reports/CompareModeTabs.tsx
@@ -10,8 +10,7 @@ export default function CompareModeTabs({ value, onChange }: CompareModeTabsProp
const { t } = useTranslation();
const modes: { id: CompareMode; labelKey: string }[] = [
- { id: "mom", labelKey: "reports.compare.modeMoM" },
- { id: "yoy", labelKey: "reports.compare.modeYoY" },
+ { id: "actual", labelKey: "reports.compare.modeActual" },
{ id: "budget", labelKey: "reports.compare.modeBudget" },
];
diff --git a/src/components/reports/ComparePeriodChart.tsx b/src/components/reports/ComparePeriodChart.tsx
index edd946b..688707f 100644
--- a/src/components/reports/ComparePeriodChart.tsx
+++ b/src/components/reports/ComparePeriodChart.tsx
@@ -4,16 +4,17 @@ import {
Bar,
XAxis,
YAxis,
- Cell,
- ReferenceLine,
Tooltip,
+ Legend,
ResponsiveContainer,
+ CartesianGrid,
} from "recharts";
import type { CategoryDelta } from "../../shared/types";
-import { ChartPatternDefs, getPatternFill } from "../../utils/chartPatterns";
export interface ComparePeriodChartProps {
rows: CategoryDelta[];
+ previousLabel: string;
+ currentLabel: string;
}
function formatCurrency(amount: number, language: string): string {
@@ -24,7 +25,11 @@ function formatCurrency(amount: number, language: string): string {
}).format(amount);
}
-export default function ComparePeriodChart({ rows }: ComparePeriodChartProps) {
+export default function ComparePeriodChart({
+ rows,
+ previousLabel,
+ currentLabel,
+}: ComparePeriodChartProps) {
const { t, i18n } = useTranslation();
if (rows.length === 0) {
@@ -35,31 +40,44 @@ export default function ComparePeriodChart({ rows }: ComparePeriodChartProps) {
);
}
- const chartData = rows
- .map((r, i) => ({
+ // Sort by current-period amount (largest spending first) so the user's eye
+ // lands on the biggest categories, then reverse so the biggest appears at
+ // the top of the vertical bar chart.
+ const chartData = [...rows]
+ .sort((a, b) => b.currentAmount - a.currentAmount)
+ .map((r) => ({
name: r.categoryName,
+ previousAmount: r.previousAmount,
+ currentAmount: r.currentAmount,
color: r.categoryColor,
- delta: r.deltaAbs,
- index: i,
- }))
- .sort((a, b) => a.delta - b.delta);
+ }));
+
+ const previousFill = "var(--muted-foreground)";
+ const currentFill = "var(--primary)";
return (
-
-
- ({ color: d.color, index: d.index }))}
- />
+
+
+
formatCurrency(v, i18n.language)}
stroke="var(--muted-foreground)"
fontSize={11}
/>
-
-
+
typeof value === "number" ? formatCurrency(value, i18n.language) : String(value)
@@ -69,15 +87,23 @@ export default function ComparePeriodChart({ rows }: ComparePeriodChartProps) {
border: "1px solid var(--border)",
borderRadius: "0.5rem",
}}
+ cursor={{ fill: "var(--muted)", fillOpacity: 0.2 }}
+ />
+
+
+
-
- {chartData.map((entry) => (
- |
- ))}
-
diff --git a/src/components/reports/CompareReferenceMonthPicker.tsx b/src/components/reports/CompareReferenceMonthPicker.tsx
new file mode 100644
index 0000000..edb7501
--- /dev/null
+++ b/src/components/reports/CompareReferenceMonthPicker.tsx
@@ -0,0 +1,93 @@
+import { useMemo } from "react";
+import { useTranslation } from "react-i18next";
+
+export interface CompareReferenceMonthPickerProps {
+ year: number;
+ month: number;
+ onChange: (year: number, month: number) => void;
+ /** Number of recent months to show in the dropdown. Default: 24. */
+ monthCount?: number;
+ /** "today" override for tests. */
+ today?: Date;
+}
+
+interface Option {
+ value: string; // "YYYY-MM"
+ year: number;
+ month: number;
+ label: string;
+}
+
+function formatMonth(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 CompareReferenceMonthPicker({
+ year,
+ month,
+ onChange,
+ monthCount = 24,
+ today = new Date(),
+}: CompareReferenceMonthPickerProps) {
+ const { t, i18n } = useTranslation();
+
+ const options = useMemo