- product_rows.rs : getProductRows complet (7 requêtes FF en parallèle,
métriques réseau Qlik, report du dernier snapshot, filtre gamme Y,
rattrapage d'un magasin par l'API FF), branché sur rows_cache (calculs en
cours partagés, compteur d'enregistrements, compléments par magasin),
instantané grid_rows resservi s'il date du mois (rafraîchi au-delà de 20 h),
persistance non bloquante ; getStorePatch ; get_product_rows_for_sync pour la
synchro nocturne et le préchauffage.
- store_patch.rs : buildStorePatchEntries.
- enrich.rs (api-enrich) et api_gammes.rs (affecterGammes, lireCorpsJson).
- Routes /api/grid/rows (NDJSON), /api/grid/rows/store-patch, /api/v1/grid
(diffusion complète sans limit), /products/search, /products/{codein},
/products/{codein}/gamme, /gammes, /fournisseurs, /network/{codeCentrale}.
- store.rs : upsert_grid_rows_shared, écriture par lots depuis les lignes
partagées du cache (sans copie intégrale).
Écart volontaire : après un calcul à la demande, /api/v1 attend la fin de
l'écriture de l'instantané avant de le relire (le TS ne l'attendait pas et
pouvait répondre à tort « aucun article »).
Test d'intégration sur la base ff_fixture (GRID_FF_TEST_DATABASE_URL) : calcul,
cache, instantané, complément magasin via une fausse API FF, enrichissement et
toutes les routes (session et clé d'API).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LG2LLJuswrrTTPKJmunEVf
Portage de tout ce qui, dans la Grille, ne dépend ni de la base FF ni de Qlik :
- types.rs : ProductRow et types associés, JSON identique à JSON.stringify
(camelCase, champs facultatifs omis, entiers sans décimale, NaN → null) et
relecture sans perte des payloads grid_rows écrits par la version Node ;
- store.rs : grid_rows (upsert par lots de 200 + purge, instantané, report des
gammes, requêtes filtrées/triées/paginées via QueryBuilder et liste blanche
de tri, fiche, fournisseurs, fraîcheur, nomenclatures) ;
- rows_cache.rs : cache mémoire 10 min des lignes calculées (clé, LRU,
patchGridRowsCache, invalidation, calculs en cours, compléments par magasin) ;
- network_trend.rs, months.rs, store_patch.rs : logique pure testée ;
- gammes_a_valider.rs, enregistrer.rs, snapshots.rs : actions getSnapshots,
deleteSnapshot, saveSnapshot, saveDraftChanges, getGammesAValider,
rejeterGammesAValider (validation zod reproduite, mêmes messages) ;
- exports.rs : /api/export/excel et /api/export/modified-gammes
(rust_xlsxwriter, mêmes feuilles, styles, largeurs et formats) ;
- api_schemas.rs, zod.rs : schémas /api/v1, issues zod 4 à l'identique ;
- api_v1.rs, openapi.rs : /api/v1/nomenclatures et /api/v1/openapi.json
(document identique octet pour octet à celui de la version Node).
Écart volontaire : listGridNomenclatures groupe par position (GROUP BY 1) ; la
requête drizzle répétait left(code3, $n) avec un autre paramètre et échouait
systématiquement sous PostgreSQL.
Test d'intégration (grid_rows, snapshots, gammes à valider) activé par
GRID_TEST_DATABASE_URL.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LG2LLJuswrrTTPKJmunEVf
Le serveur étant réécrit en Rust, l'interface devient une application
React 19 côté navigateur, compilée par Vite dans dist/ et servie par le
serveur Rust (STATIC_DIR). Composants, styles et logique cliente
inchangés : seule la plomberie Next.js est remplacée.
- Outillage : Vite 8, @vitejs/plugin-react, Tailwind 4 via PostCSS,
React Router 7 (mode déclaratif), index.html, src/main.tsx, alias @/,
proxy /api → localhost:5643 en développement, ESLint sans plugin Next.
- Adaptations (src/shims) : next/link, next/navigation (useRouter avec
refresh, usePathname/useSearchParams suivant aussi les replaceState de
l'interface), next-auth/react (session lue sur /api/auth/session,
déconnexion par /api/auth/logout).
- Actions serveur → POST /api/actions/{nom} (src/lib/rpc.ts) : mêmes noms,
mêmes signatures, mêmes types. La connexion passe par /api/auth/login.
- Pages → table des routes (src/router.tsx) : données lues sur
/api/pages/{nom} avec Suspense (loading.tsx au premier chargement,
page conservée pendant une navigation ou un router.refresh()),
error.tsx en frontière d'erreur, not-found.tsx, titres d'onglet,
pages d'administration réservées aux administrateurs.
- Suppression du code serveur Next.js (routes /api, accès base, Qlik,
authentification, middleware…) ; types utiles à l'interface recopiés
dans src/types/ff.ts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LG2LLJuswrrTTPKJmunEVf
- ff::pg : portage complet de pg-ff-client.ts (mêmes requêtes SQL, fragments
dynamiques via QueryBuilder, lectures tolérantes, caches process de la
colonne parent de nomenclature et du lien mvtart → fournisseur).
- ff::api : client de l'API FF Nancy (URL résolue Paramètres → env → défaut,
mémorisée 30 s ; délai FF_API_TIMEOUT_MS), pagination, mensuel, référentiel,
commandes, normalisation du statut de synchro.
- ff::cache : getFournisseursCached / getDerniereReceptionCached.
- ff::diagnostic : diagnostiquer_api_ff (DNS, refus, délai, code HTTP).
- Routes /api/ff-status, /api/diag, /api/diag/stock-fournisseur, /api/test-db,
/api/commandes-auto ; actions de la Grille et du cadencier ; pages dashboard,
analytics, hit-parade, stock-negatif, commandes-auto, publicites.
- Test d'intégration (FF_TEST_DATABASE_URL) avec fausse API FF : toutes les
requêtes et tous les appels HTTP joués sur une base de test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LG2LLJuswrrTTPKJmunEVf
Le logiciel de mise à jour des gammes refuse les fichiers trop gros
(ex. 28 590 lignes). L'export est désormais découpé en plusieurs
fichiers (_partie_1_sur_6, …) à intégrer un par un.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011jxxh5X1FHMUPFu2S2JpyT
Sur un écran très large, la colonne de libellé absorbait tout l'espace en
trop : le nom du fournisseur se retrouvait à un millier de pixels de ses
chiffres. Le tableau prend désormais la largeur de son contenu (défilement
au-delà de l'écran) et l'option « grow » devient « label » : largeur
naturelle, retour à la ligne au-delà de 28rem.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FaniLFvegAZNXFrFFSStqa
DataTable gagne quatre options de colonne :
- grow : la désignation prend la largeur restante, les chiffres restent
groupés au lieu de s'étirer sur un écran large ;
- sticky : Code et Désignation restent visibles au défilement horizontal
(désignation bornée à 45 % de la largeur sur mobile) ;
- group : en-tête commun par magasin et trait de séparation entre groupes ;
- highlight : fond teinté pour le groupe « Total nos magasins ».
Toutes les lignes alternent désormais de couleur, survol teinté.
Appliqué à Meilleures ventes (Référence déplacée après la Désignation),
Ventes par mois et Publicités.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FaniLFvegAZNXFrFFSStqa
- API /api/v1 : PUT /products/:codein/gamme et POST /gammes déposent une
proposition (table gammes_a_valider) au lieu d'enregistrer. La Grille la
charge comme modification non enregistrée (badge « API ») : Enregistrer
la valide, Annuler la rejette. Lecture : champ gammeAValider, statut
a_valider dans la réponse ; OpenAPI mise à jour.
- Meilleures ventes, Ventes par mois, Publicités : plus de largeur max
centrée, les pages occupent toute la largeur.
- Meilleures ventes : colonne Référence (référence fournisseur), recherche
et export Excel compris.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FaniLFvegAZNXFrFFSStqa
Nouveau bouton « Nomenclatures » dans la barre d'outils de la Grille : il
ouvre une modale qui regroupe les produits affichés par nomenclature (code3)
et donne, sur les 12 mois de la Grille, le CA de chaque magasin, le CA de nos
magasins, le CA réseau, le poids de la nomenclature chez nous et dans le
réseau (écart en points) et l'indice de chaque magasin face au magasin moyen
du réseau. Tri par colonne, filtre texte, ligne de total et export Excel.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KvNrtCGyjYgPHFoiUkJzhM
Deux endpoints d'écriture dans /api/v1 :
- PUT /products/{codein}/gamme { gamme, fournisseur? } pour un article ;
- POST /gammes { fournisseur, changes[] } pour plusieurs articles d'un
fournisseur, en tout ou rien.
Ils passent par le même enregistrement que la Grille (extrait de
saveDraftChanges dans enregistrerGammes) : snapshot du fournisseur, cache
de la Grille et instantané grid_rows. Gamme validée contre la liste des
gammes (A, B, C, Y, Z) ; un article déjà dans la gamme demandée n'est pas
réécrit. Spec OpenAPI et page de connexion à l'API mises à jour ; la
légende des gammes de la spec, qui citait une gamme D inexistante, suit
désormais lib/gammes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VEXT61tGbsXHUDNBeghCXQ
Les CA, marges et valeurs de stock étaient arrondis à l'euro (fmtEur0,
fmtEntier suivi de « € », Math.round dans les exports). Tous les montants
sont désormais affichés avec deux décimales : accueil, grille, fiche
produit, hit-parade, publicités, analytics, snapshots, export PDF et
export Excel des manques d'assortiment.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VEXT61tGbsXHUDNBeghCXQ
Fiabilité des données
- Contrôle de plausibilité avant d'écrire le cache (lib/qlik-validation.ts) :
extraction refusée, cache inchangé, si le dernier mois pèse moins de 40 %
des 3 précédents dans les 10 premiers jours du mois (mois pas encore
chargé dans Qlik), ou si plus de la moitié des séries vendues ont la même
valeur 12 mois (mesure insensible au mois).
- Valeurs impossibles corrigées (NaN, infini, nombre de magasins négatif).
Récupération
- Chromium relancé s'il est tombé : un navigateur déconnecté faisait échouer
toutes les extractions jusqu'au redémarrage du serveur.
- Une seule session Qlik à la fois (extraction de nuit, synchro manuelle,
recherche produit) : en parallèle, le serveur Qlik abandonnait les deux.
- Planificateur : un échec est retenté la nuit suivante (au lieu de 7 jours)
et les données d'avant le mois en cours sont rafraîchies sans attendre le
délai de fraîcheur. Page Synchronisation mise à jour.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015MouZgPPAuEZjHXXifW7bm
- Phrase de lecture en tête : la tendance du réseau avec ses deux chiffres
(qté par magasin, 4 derniers mois contre 4 premiers), puis notre
situation (ventes 12 mois, stock, couverture, commandes en cours).
- Nouvelle section « Nos magasins » : stock fin de mois, ventes 12 mois,
rythme mensuel, couverture, dernière entrée et écart au magasin moyen
du réseau, par magasin et pour l'ensemble.
- Réseau : période affichée en clair, tuile CA réseau 12 mois, part du
réseau dans la tuile des magasins vendeurs.
- Modal sorti de heatmap-grid.tsx (network-monthly-modal.tsx).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015MouZgPPAuEZjHXXifW7bm
- Grille servie depuis l'instantané (lot 4) : les colonnes réseau sont
relues dans le cache Qlik, comme le calcul en direct. Elles restaient
figées à la date du calcul de l'instantané.
- Tendance : si la fenêtre du jour est incomplète mais que les 12 mois
précédents le sont (cache Qlik pas encore resynchronisé depuis le
changement de mois), la tendance porte sur ces 12 mois au lieu de
disparaître. Le modal le signale.
- Courbe des magasins vendeurs alignée sur les mois de la tendance
(modal Grille et fiche produit).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015MouZgPPAuEZjHXXifW7bm
scripts/diagnostic-ff.js (lecture seule) relève les réglages PostgreSQL, la
taille des tables, les types des colonnes de jointure, les index, les codes
article avec espaces et les plans d'exécution de six requêtes représentatives
(EXPLAIN, ou EXPLAIN ANALYZE avec --analyze). Il prépare les réécritures SQL
qui demandent la vraie base. Mode d'emploi dans docs/plan-optimisation-webui.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AcA129WzGTH6zxPHFBGWmn
- Grille : réglages écrits dans le localStorage au plus toutes les 500 ms
(immédiatement à la fermeture), tendance réseau et texte de recherche
calculés une fois par ligne, courbe de tendance mémorisée, barre du bas
abonnée au seul nombre de modifications.
- Commandes : appels « franco » 6 par 6 ; dernière réception limitée aux
fournisseurs du cadencier (résultats identiques sur la base de test).
- Dépendances inutilisées retirées (react-query, @google/genai, ai,
react-markdown, remark-gfm, xlsx, @radix-ui/react-dropdown-menu) ;
pnpm-lock.yaml supprimé (npm est utilisé par Docker et le README).
- Code mort et journaux de build retirés.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AcA129WzGTH6zxPHFBGWmn
Grille
- Corrige le lot 4 : au changement de magasin, les chiffres et le tri
affichés suivent tout de suite (nouvelle liste), la sélection est gardée.
- Lignes toujours chargées « Nos 2 magasins » ; changement de magasin sans
aller-retour serveur (URL mise à jour par l'historique du navigateur).
- Complément de l'API FF par magasin servi à part (/api/grid/rows/store-patch),
appliqué à son arrivée, calcul identique à l'ancien (lib/store-patch.ts).
- La recherche ne reconstruit plus toutes les lignes à chaque frappe ; la
sélection n'est remise à zéro qu'au changement de fournisseur.
Sécurité
- lib/authz.ts : session relue en base (cache 1 min), rôle de la base
prioritaire ; variantes pour actions, routes et pages.
- Paramètres réservés aux administrateurs ; session vérifiée dans les actions
Grille, Commandes, Historique et les routes ; diagnostics réservés admin.
- Mots de passe et clés d'IA plus envoyés au navigateur ; champ vide = mot de
passe conservé ; plus de mot de passe dans le localStorage ; l'enregistrement
de la base n'efface plus les clés d'IA.
- admin/admin créé seulement si aucun utilisateur ; fichier de secours utilisé
uniquement si la base est injoignable ; data/ ignoré par git.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AcA129WzGTH6zxPHFBGWmn
- Grille : lecture de l'instantané grid_rows (gamme INIT et dernières gammes
enregistrées reportées), recalcul en arrière-plan au-delà de 20 h, version de
format dans chaque ligne pour écarter les anciens instantanés.
- Navigateur : lignes réutilisées au retour sur la Grille (< 10 min), Grille
conservée pendant un changement de magasin ou une actualisation, React.memo
sur la Grille, les filtres et la barre du bas, en-tête mémorisé.
- Fiche produit : toutes les lectures en parallèle.
- Ventes par mois et stock sans vente : plages de dates au lieu de TO_CHAR.
- Accueil : vagues de 12 appels, cache de 30 min.
- API v1 : comptage et page en parallèle, last_used_at au plus une fois par
minute, version interne non exposée.
- Plan : suivi du lot 4, avec ce qui reste à valider sur la base réelle.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AcA129WzGTH6zxPHFBGWmn
Chaque page reprend le socle du lot 2 : en-tête avec une phrase
d'explication, couleurs du thème (plus de pages figées en blanc), textes
de 12 px minimum, noms de magasins, infobulles sur le jargon, retours par
toast / confirmation, états vides et d'erreur expliqués.
- Accueil : CA, tickets et panier moyen d'hier vs N-1, par magasin, top 10
avec lien vers la fiche produit.
- Révision d'assortiment : un seul endroit pour enregistrer et exporter
(menu Export unique, mêmes droits pour tous), annulation confirmée, sens
des gammes affiché partout, magasin choisi en un clic, colonnes nommées
en clair, bandeau de progression au lieu de la fenêtre floue, droits lus
côté serveur, ligne sélectionnée opaque sur les colonnes figées.
- Recherche produit, Stocks à surveiller (export FF inchangé), Meilleures
ventes, Ventes par mois (dernier mois complet par défaut), Publicités,
Commandes fournisseurs, Historique, Paramètres (onglets), Synchronisation
et Connexion migrés sur DataTable / Tabs / Card.
- Le nom de l'utilisateur est transmis à la session (affiché dans
l'en-tête).
- Composants devenus inutiles supprimés (anciennes fenêtres modales,
export-dropdown, financial-cell, supplier-selection).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AcA129WzGTH6zxPHFBGWmn
Thème
- Clair par défaut ; les classes dark: suivent le bouton de thème (et non
plus le réglage du système) ; animations tw-animate-css enfin importées.
- Couleurs shadcn branchées sur les variables (survols des menus visibles),
libellés gris au contraste AA dans les deux thèmes, fond plein pour le
menu et l'en-tête.
Composants partagés (src/components/ui)
- PageHeader, Card/Field, StatCard, Badge/DeltaBadge/StoreBadge/GammeBadge,
Tabs/Segmented/useUrlTab, Select/SearchInput, EmptyState/ErrorState/
Skeleton, Tooltip/Terme (glossaire), toast et confirmer, Pagination,
DataTable (recherche, filtres, tri, pagination, export, totaux).
- Sources uniques : magasins, gammes (avec leur sens), glossaire, seuils de
marge, export Excel.
Navigation
- Menu groupé (Au quotidien / Analyses / Suivi / Administration), déplié
par défaut, libellés en français.
- En-tête : nom de la page, thème, menu du compte avec le vrai nom, la
hauteur des lignes de la Grille et la déconnexion. La recherche globale,
qui ne filtrait que la Grille, est retirée.
- Page Historique (sessions + exports) ; /snapshots et /exports y
redirigent.
- Squelettes de chargement au lieu de la fenêtre plein écran, pages
d'erreur et « introuvable » en français, arrivée sur l'Accueil après
connexion.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AcA129WzGTH6zxPHFBGWmn
Grille (navigateur)
- « Rafraîchir » ne passe plus par l'URL : le paramètre _refresh restait
mémorisé et chaque retour par le menu forçait un recalcul serveur complet
(migration du store pour nettoyer les valeurs déjà enregistrées).
- Lignes reçues regroupées toutes les 300 ms au lieu de reconstruire tout le
tableau à chaque paquet de 150 (chargement quadratique).
- Sélection indexée par code article (getRowId) : après un filtre, l'action
groupée visait d'autres produits. Seules les lignes affichées comptent.
- Recherche : un passage par ligne au lieu d'un par colonne ; formateurs de
nombres partagés (lib/format.ts) ; colonnes mensuelles mémorisées ;
plus de transition-all sur les lignes positionnées par transform.
- exceljs, jspdf et xlsx chargés au clic seulement.
Grille (serveur)
- Requêtes Qlik et snapshot lancées en parallèle de la phase SQL.
- Cache mémoire borné (LRU) ; calcul en cours réutilisé même en forcé ;
flux interrompu quand le client part.
- Après un enregistrement, les gammes sont reportées dans le cache et dans
grid_rows au lieu d'invalider (recalcul de ~40 s évité).
Données et API
- lib/ff-cache.ts : cache mémoire 30 min des lectures FF (stock, hit-parade,
CA mensuel, fournisseurs, dernière réception, publicités), vidé en fin de
synchro nocturne ; erreurs jamais gardées.
- Pool PostgreSQL unique (globalThis), réglage sans workers parallèles posé
une fois par connexion au lieu d'une transaction autour de chaque requête.
- Délai maximal sur tous les appels à l'API FF.
- Index session_snapshots ; commandes : plus de double rechargement ;
synchro : saisies regroupées ; paramètres : configuration lue une fois ;
journal de capture en ajout seul, rien de formaté sans capture ouverte.
Corrections
- Historique : l'échec de chargement s'affiche (au lieu de « Aucun snapshot »).
- Sessions et validations enregistrent le magasin affiché, plus « TOTAL ».
- Publicités : libellé du statut « passées ».
Plan complet : docs/plan-optimisation-webui.md
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AcA129WzGTH6zxPHFBGWmn
Le changement d'onglet déclenchait un router.push : la page serveur était
rejouée et les 3 requêtes SQL relancées à chaque clic, alors que les données
étaient déjà côté client. Sans aucun retour visuel, l'onglet semblait bloqué.
Interface (stock-negatif/client.tsx)
- Changement d'onglet purement client (useTransition + history.replaceState) :
plus aucune requête SQL rejouée, l'URL reste partageable.
- Modal de chargement pendant le changement de magasin (seule action qui
interroge vraiment le serveur), pendant un changement d'onglet lent et
pendant la génération de l'export Excel. Affichage différé de 120 ms pour
éviter tout clignotement.
- Pagination 50 lignes : l'onglet « Sans vente 6 mois » rendait jusqu'à 17 000
lignes d'un coup, ce qui figeait le navigateur. L'export Excel continue de
reprendre l'intégralité des lignes filtrées.
- Tri : ordre de la base conservé tant qu'aucune colonne n'est cliquée (le tri
alphabétique sur codein écrasait le classement métier), collateur Intl
réutilisé au lieu d'un localeCompare par comparaison, tri numérique robuste
aux valeurs nulles.
- Les 3 onglets partagent désormais un composant de tableau unique piloté par
une configuration de colonnes, au lieu de 3 copies quasi identiques.
- Select magasin et onglets désactivés pendant le chargement, aria-busy posé.
Données (pg-ff-client.ts)
- Un article n'est plus rattaché à son fournisseur principal mais au
fournisseur de sa dernière entrée en stock : s'il est rentré chez un autre
fournisseur, il n'apparaît plus sous le précédent. La colonne de mvtart qui
porte ce lien est détectée une fois par process parmi une liste blanche
(même principe que getNomenclatureParentCol), avec repli documenté sur
artfou1.preference = 1 si la base ne porte pas l'information.
- La jointure latérale sur la dernière entrée remplace la sous-requête
corrélée MAX(datmvt) : même coût qu'avant pour une information de plus.
- pgGetStockSansVente dédoublonne artfou1 via DISTINCT ON comme ses deux
requêtes sœurs, et exclut les stocks nuls en SQL (HAVING) au lieu de les
filtrer côté client : le compteur d'onglet correspond enfin aux lignes
réellement transmises.
Diagnostic
- GET /api/diag/stock-fournisseur : colonne détectée, colonnes réelles de
mvtart et échantillon comparant fournisseur principal et fournisseur de
dernière entrée.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174nm1nWPaypWBNHioSTJ8X
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
Sur 2600 px de tableau pour ~1150 px visibles, on voit 8 colonnes sur 18. Dès
qu'on défilait vers les totaux ou les mois, la Désignation sortait de l'écran
et plus rien ne disait quelle ligne on lisait.
Case à cocher, Code interne et Désignation restent désormais collés au bord
gauche pendant le défilement horizontal, en-tête compris.
Deux conséquences de conception :
- La Désignation a été rapprochée du Code interne dans l'ordre des colonnes.
Le figeage exige des colonnes contiguës depuis la gauche ; en la laissant
après Référence et GTIN, il aurait fallu geler ces deux-là aussi, soit 636 px
immobilisés au lieu de 406. L'ordre « code, désignation, puis codes
secondaires » est de toute façon celui qu'on lit.
- Les cellules figées doivent être OPAQUES, le reste de la ligne glissant
dessous. Le survol passe donc par une teinte pleine et non par le voile
translucide des cellules ordinaires, et l'en-tête figé reprend le dégradé du
thead pour que la bande reste d'un seul tenant. Un filet marque la limite de
la zone figée.
Les décalages sont calculés par cumul des largeurs réelles et s'arrêtent à la
première colonne non figée : masquer le Code interne réajuste tout seul, et une
colonne figée ne peut jamais se retrouver à flotter au milieu du tableau.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y26nRZxTR57K7h8yqsF675
Les deux symptômes n'en faisaient qu'un. L'ascenseur horizontal EXISTAIT déjà
(conteneur en overflow-auto, table en min-width ~2600 px), mais il était caché
sous la carte de synthèse.
Celle-ci était en `position: fixed` et ne réservait donc aucune hauteur : seul
un `pb-12` posé sur la page compensait, trop court d'une quarantaine de pixels
— exactement la bande où se trouvent la barre de défilement et la dernière
ligne. Son `left-[264px]` était en prime calé sur une barre latérale dépliée,
alors qu'elle est repliée par défaut : 200 px de vide à gauche, qui écrasaient
ses statistiques en hauteur. Elle est remise dans le flux : la grille lui cède
exactement la place nécessaire, et il n'y a plus de valeur magique à tenir en
phase avec la sidebar.
L'ascenseur devient visible : la règle générale (8 px, rail transparent, pouce
à 12 % d'opacité) convient aux panneaux où le défilement est accessoire, pas à
la Grille où il est l'outil de navigation principal. Classe `.grid-scroll`
dédiée, avec rail dessiné, pouce saisissable, et prise en charge de Firefox que
les sélecteurs -webkit- ignorent.
Corrigé aussi :
- totalWidth lisait `columnDef.size` au lieu de `getSize()`. Le redimensionnement
de colonnes étant actif et persisté, la largeur plancher restait figée sur
l'ancienne somme : dernières colonnes rognées, course de défilement trop courte.
- Conteneur non focusable : flèches, Origine et Fin ne défilaient pas la grille.
- Barre de filtres qui passait sur deux lignes sous ~1200 px, volant 48 px au
tableau : largeurs réduites en dessous de xl.
- Barre d'actions groupées sans flex-wrap, qui s'écrasait sur petit écran.
- Double croix dans la recherche : `type="search"` ajoutait la croix native du
navigateur par-dessus le bouton maison. Masquage local — la recherche de
l'en-tête n'a pas de bouton propre et garde donc le sien.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y26nRZxTR57K7h8yqsF675
Le code interne identifie l'article chez nous, la référence l'identifie chez
le fournisseur : c'est elle qu'on recopie sur une commande. La fenêtre ne
donnait que le premier, obligeant à revenir à la Grille pour retrouver le
second — l'export, lui, portait déjà les deux.
Référence absente affichée « — » plutôt que laissée vide : une cellule vide
se lit comme une colonne qui n'a pas fini de charger.
Vérifié sur build de production, avec des références longues et absentes :
colonne lisible, aucun débordement horizontal de la fenêtre.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYGgoQCG1VCe42HzbzhAXR