feat(reports/compare): 8-column table with monthly + cumulative YTD blocks (#104) #109

Merged
maximus merged 1 commit from issue-104-compare-eight-col-table into main 2026-04-19 11:19:22 +00:00
Owner

Summary

Mirrors the BudgetVsActualTable rich 8-column structure in the Actual vs actual compare mode (/reports/compare). Both MoM and YoY now surface a Monthly block (reference month vs comparison month) and a Cumulative YTD block (progress through the reference month vs progress through the previous window).

Closes #104.

Key decisions

  • MoM cumulative-previous semantics: Jan -> end-of-previous-month of the SAME year (progress through end of last month vs progress through end of this month). When the reference month is January, the previous window sits entirely in the prior calendar year (Jan -> Dec).
  • YoY monthly block is new: getCompareYearOverYear(year, month?) now takes an optional reference month (defaults to December for backward compatibility). Monthly block compares the single reference month across years; cumulative block compares Jan -> refMonth across years.
  • Backward compatibility: CategoryDelta keeps previousAmount / currentAmount / deltaAbs / deltaPct as aliases of the monthly block so the Highlights hub, Cartes top-movers, and ComparePeriodChart keep working unchanged.
  • Chart unchanged: ComparePeriodChart still reads monthly-only fields (spec: chart stays on monthly deltas only). Note: YoY chart now shows monthly deltas (same month vs same month of previous year) instead of full-year deltas, as intended.
  • CartesTopMover projection: Cartes service now explicitly maps CategoryDelta -> CartesTopMover instead of relying on structural subtyping, since CategoryDelta gained fields that CartesTopMover does not expose.
  • SQL: all queries parameterised (8 placeholders each). No migration, no schema change.

Files changed

  • src/shared/types/index.ts: add cumulative* fields to CategoryDelta
  • src/services/reportService.ts: new SQL with 4 date windows, toCartesMover projection, HighlightMover mirrors cumulative on monthly
  • src/components/reports/ComparePeriodTable.tsx: rewrote to 8-column layout with grouped headers, sub-labels, totals row
  • src/hooks/useCompare.ts: pass month to getCompareYearOverYear
  • src/pages/ReportsComparePage.tsx: compute cumulative labels and pass to table
  • src/services/reportService.test.ts: updated fixtures to new field names, added cumulative assertions, YoY default-December test
  • src/services/reportService.cartes.test.ts: updated mock fixtures
  • src/i18n/locales/{en,fr}.json: new keys reports.compare.{currentAmount,previousAmount,totalRow}
  • CHANGELOG.md / CHANGELOG.fr.md: Changed entry

Test plan

  • npm test -- --run: 103 tests pass (11 files)
  • npm run build: clean (tsc + vite build)
  • Manual QA on /reports/compare with real data:
    • Actual vs actual + MoM: verify monthly block = refMonth vs prevMonth, cumulative block = Jan->refMonth vs Jan->prevMonth (same year)
    • Actual vs actual + MoM with January reference: cumulative-previous should span Jan->Dec of prior year
    • Actual vs actual + YoY: monthly block = single month across years, cumulative block = Jan->refMonth across years
    • Totals row sums match the category rows in each block
    • Chart still renders (monthly bars only; YoY chart is now single-month instead of full-year)
    • Reference-month picker correctly updates both blocks

Generated with Claude Code

## Summary Mirrors the BudgetVsActualTable rich 8-column structure in the Actual vs actual compare mode (/reports/compare). Both MoM and YoY now surface a Monthly block (reference month vs comparison month) and a Cumulative YTD block (progress through the reference month vs progress through the previous window). Closes #104. ### Key decisions - MoM cumulative-previous semantics: Jan -> end-of-previous-month of the SAME year (progress through end of last month vs progress through end of this month). When the reference month is January, the previous window sits entirely in the prior calendar year (Jan -> Dec). - YoY monthly block is new: getCompareYearOverYear(year, month?) now takes an optional reference month (defaults to December for backward compatibility). Monthly block compares the single reference month across years; cumulative block compares Jan -> refMonth across years. - Backward compatibility: CategoryDelta keeps previousAmount / currentAmount / deltaAbs / deltaPct as aliases of the monthly block so the Highlights hub, Cartes top-movers, and ComparePeriodChart keep working unchanged. - Chart unchanged: ComparePeriodChart still reads monthly-only fields (spec: chart stays on monthly deltas only). Note: YoY chart now shows monthly deltas (same month vs same month of previous year) instead of full-year deltas, as intended. - CartesTopMover projection: Cartes service now explicitly maps CategoryDelta -> CartesTopMover instead of relying on structural subtyping, since CategoryDelta gained fields that CartesTopMover does not expose. - SQL: all queries parameterised (8 placeholders each). No migration, no schema change. ### Files changed - src/shared/types/index.ts: add cumulative* fields to CategoryDelta - src/services/reportService.ts: new SQL with 4 date windows, toCartesMover projection, HighlightMover mirrors cumulative on monthly - src/components/reports/ComparePeriodTable.tsx: rewrote to 8-column layout with grouped headers, sub-labels, totals row - src/hooks/useCompare.ts: pass month to getCompareYearOverYear - src/pages/ReportsComparePage.tsx: compute cumulative labels and pass to table - src/services/reportService.test.ts: updated fixtures to new field names, added cumulative assertions, YoY default-December test - src/services/reportService.cartes.test.ts: updated mock fixtures - src/i18n/locales/{en,fr}.json: new keys reports.compare.{currentAmount,previousAmount,totalRow} - CHANGELOG.md / CHANGELOG.fr.md: Changed entry ## Test plan - [x] npm test -- --run: 103 tests pass (11 files) - [x] npm run build: clean (tsc + vite build) - [ ] Manual QA on /reports/compare with real data: - [ ] Actual vs actual + MoM: verify monthly block = refMonth vs prevMonth, cumulative block = Jan->refMonth vs Jan->prevMonth (same year) - [ ] Actual vs actual + MoM with January reference: cumulative-previous should span Jan->Dec of prior year - [ ] Actual vs actual + YoY: monthly block = single month across years, cumulative block = Jan->refMonth across years - [ ] Totals row sums match the category rows in each block - [ ] Chart still renders (monthly bars only; YoY chart is now single-month instead of full-year) - [ ] Reference-month picker correctly updates both blocks Generated with Claude Code
maximus added 1 commit 2026-04-19 01:18:20 +00:00
feat(reports/compare): 8-column table with monthly + cumulative YTD blocks (#104)
All checks were successful
PR Check / rust (push) Successful in 21m21s
PR Check / frontend (push) Successful in 2m11s
PR Check / rust (pull_request) Successful in 21m30s
PR Check / frontend (pull_request) Successful in 2m8s
bd8a5732c6
Mirror the BudgetVsActualTable structure in the Actual-vs-Actual compare
mode so MoM and YoY both surface a Monthly block (reference month vs
comparison month) and a Cumulative YTD block (progress through the
reference month vs progress through the previous window).

- CategoryDelta gains cumulative{Previous,Current}Amount and
  cumulativeDelta{Abs,Pct}. Legacy previousAmount / currentAmount /
  deltaAbs / deltaPct are kept as aliases of the monthly block so the
  Highlights hub, Cartes dashboard and ComparePeriodChart keep working
  unchanged.
- getCompareMonthOverMonth: cumulative-previous window ends at the end
  of the previous month within the SAME year; when the reference month
  is January, the previous window sits entirely in the prior calendar
  year (Jan → Dec).
- getCompareYearOverYear: now takes an optional reference month
  (defaults to December for backward compatibility). Monthly block
  compares the single reference month across years; cumulative block
  compares Jan → refMonth across years.
- ComparePeriodTable rebuilt with two colspan header groups, four
  sub-columns each, a totals row and month/year boundary sub-labels.
- ComparePeriodChart unchanged: still reads the monthly primary fields.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
maximus added the
status:review
label 2026-04-19 01:18:24 +00:00
Author
Owner

Revue — APPROVE

Verdict

Refonte substantielle et bien structurée. Le tableau Réel-vs-Réel réplique fidèlement la structure 8 colonnes du tableau Réel-vs-Budget, avec des sémantiques cumulatives documentées et testées.

Points validés

  • Types : CategoryDelta étendu avec cumulativePreviousAmount / cumulativeCurrentAmount / cumulativeDeltaAbs / cumulativeDeltaPct. Champs legacy conservés comme alias du bloc mensuel → aucune régression pour Highlights, Cartes top-movers, ComparePeriodChart.
  • SQL paramétré : 8 placeholders ($1–$8), un seul SELECT avec CASE WHEN par fenêtre — efficace, pas d'interpolation.
  • MoM cumulative : Jan→fin-mois-précédent vs Jan→fin-mois-courant de la même année (bien documenté en commentaire). Cas dégénéré Janvier : fenêtre-précédente = Jan→Dec de l'année précédente, raisonnable.
  • YoY cumulative : Jan→refMonth année courante vs Jan→refMonth année précédente. getCompareYearOverYear(year, month=12)month optionnel avec défaut pour backward-compat (bien que useCompare passe toujours explicitement).
  • Labels : cumulativeCurrentLabel / cumulativePreviousLabel distinguent clairement les fenêtres (→ mars 2026 MoM, 2026 → mars 2026 YoY). Lisibilité bonne en FR et EN.
  • Chart : inchangé (monthly-only) — reuse les champs deltaAbs / deltaPct du bloc mensuel.
  • Totals row : ajoutée, somme correctement les deux blocs.
  • Tests : 3 nouveaux tests sur les champs cumulatifs (MoM, YoY, edge cases) — tous verts.
  • i18n FR/EN + CHANGELOG FR/EN sous Changed / Modifié.

Point d'attention (non-bloquant)

La sémantique YoY monthly block est maintenant "même mois l'an dernier" (via refMonth) au lieu de "année entière vs année entière". C'est exactement ce que demandait l'issue, mais c'est un changement de comportement visible : si un utilisateur avait bookmarké l'onglet YoY, il verra désormais un seul mois dans le bloc mensuel. Le bloc cumulatif couvre la comparaison YTD complète, donc l'info n'est pas perdue — juste réorganisée.

Vérifications locales

  • npm test -- --run : 103/103 verts (3 nouveaux)
  • npm run build : (5.17s)

Stats

  • 11 fichiers, +452 / -102 lignes.
  • Le plus gros fichier : ComparePeriodTable.tsx (+221 lignes pour la refonte visuelle).

Rien ne bloque le merge.

## Revue — APPROVE ✅ ### Verdict Refonte substantielle et bien structurée. Le tableau Réel-vs-Réel réplique fidèlement la structure 8 colonnes du tableau Réel-vs-Budget, avec des sémantiques cumulatives documentées et testées. ### Points validés - **Types** : `CategoryDelta` étendu avec `cumulativePreviousAmount` / `cumulativeCurrentAmount` / `cumulativeDeltaAbs` / `cumulativeDeltaPct`. Champs legacy conservés comme alias du bloc mensuel → aucune régression pour Highlights, Cartes top-movers, ComparePeriodChart. - **SQL paramétré** : 8 placeholders ($1–$8), un seul SELECT avec `CASE WHEN` par fenêtre — efficace, pas d'interpolation. - **MoM cumulative** : Jan→fin-mois-précédent vs Jan→fin-mois-courant de la **même année** (bien documenté en commentaire). Cas dégénéré Janvier : fenêtre-précédente = Jan→Dec de l'année précédente, raisonnable. - **YoY cumulative** : Jan→refMonth année courante vs Jan→refMonth année précédente. `getCompareYearOverYear(year, month=12)` — `month` optionnel avec défaut pour backward-compat (bien que `useCompare` passe toujours explicitement). - **Labels** : `cumulativeCurrentLabel` / `cumulativePreviousLabel` distinguent clairement les fenêtres (`→ mars 2026` MoM, `2026 → mars 2026` YoY). Lisibilité bonne en FR et EN. - **Chart** : inchangé (monthly-only) — reuse les champs `deltaAbs` / `deltaPct` du bloc mensuel. - **Totals row** : ajoutée, somme correctement les deux blocs. - **Tests** : 3 nouveaux tests sur les champs cumulatifs (MoM, YoY, edge cases) — tous verts. - i18n FR/EN + CHANGELOG FR/EN sous `Changed` / `Modifié`. ### Point d'attention (non-bloquant) La sémantique YoY monthly block est maintenant "même mois l'an dernier" (via refMonth) au lieu de "année entière vs année entière". C'est exactement ce que demandait l'issue, mais c'est un changement de comportement visible : si un utilisateur avait bookmarké l'onglet YoY, il verra désormais un seul mois dans le bloc mensuel. Le bloc cumulatif couvre la comparaison YTD complète, donc l'info n'est pas perdue — juste réorganisée. ### Vérifications locales - `npm test -- --run` : 103/103 verts (3 nouveaux) - `npm run build` : ✅ (5.17s) ### Stats - 11 fichiers, +452 / -102 lignes. - Le plus gros fichier : `ComparePeriodTable.tsx` (+221 lignes pour la refonte visuelle). Rien ne bloque le merge.
maximus merged commit e95612a55d into main 2026-04-19 11:19:22 +00:00
maximus deleted branch issue-104-compare-eight-col-table 2026-04-19 11:19:23 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: maximus/Simpl-Resultat#109
No description provided.