fix(grid): aligner la fenêtre 12 mois sur les données et tabuler les ventes du modal

Le total « Tot. 12m » est figé côté serveur sur SA fenêtre de 12 mois, tandis
que la Grille et le modal recalculaient les 12 mois depuis l'horloge du
navigateur. Dès que les deux divergeaient (onglet ouvert au changement de
mois, lignes servies depuis le cache), un mois de ventes disparaissait des
colonnes tout en restant compté dans le total : 28 sur la ligne, 20 dans le
modal.

- months.ts : `getMonthsFromRows()` lit la fenêtre dans les clés des séries
  reçues ; `getLast12Months()` ne sert plus que de repli avant chargement.
- heatmap-grid : MONTHS_12 dérivé des lignes chargées.
- product-monthly-modal : le tableau par magasin devient générique et sert
  aussi aux ventes, sous le graphique, avec une colonne de cumul « 12 m »
  qui retombe sur le total de la Grille.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0177HLQz6w2hgoXpx6t1Rkn9
This commit is contained in:
Claude committed 2026-09-02 10:43:26 +00:00
1 parent 2404b22628
commit 585e171708
3 files changed
+121 -40

No files matched your search

+26
View File
@@ -30,6 +30,32 @@ export function getLast12Months(): string[] {
return months;
}
/**
* Fenêtre de 12 mois **telle que le serveur l'a calculée**, lue dans les clés des
* séries mensuelles des lignes reçues.
*
* `getLast12Months()` recalcule la fenêtre depuis l'horloge du navigateur ; or
* les totaux (`totalQuantite`, `quantiteByStore`…) sont figés côté serveur, sur
* SA fenêtre. Dès que les deux divergent — onglet resté ouvert au changement de
* mois, lignes servies depuis le cache, horloges décalées — un mois de ventes
* disparaît des colonnes tout en restant compté dans le total : la case
* « Tot. 12m » affiche 28 quand les douze cases visibles ne font que 20.
* Les clés des données sont la seule référence commune : on les prend.
*
* Renvoie `null` si aucune ligne ne porte de série (fournisseur sans vente,
* chargement en cours) — l'appelant retombe alors sur `getLast12Months()`.
*/
export function getMonthsFromRows(rows: ReadonlyArray<{ sales12m?: Record<string, number> }>): string[] | null {
for (const r of rows) {
const keys = Object.keys(r.sales12m ?? {}).filter((k) => /^\d{6}$/.test(k));
if (keys.length < 12) continue;
// Toutes les lignes d'un même calcul partagent la même fenêtre : la
// première ligne complète suffit.
return keys.sort().slice(-12);
}
return null;
}
/** `"202601"` → `"Jan 26"`. */
export function formatMonthLabel(key: string): string {
const m = parseInt(key.slice(4, 6), 10);