perf: lot 1 — chargements plus rapides et corrections, sans changement visuel

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
This commit is contained in:
Claude committed 2026-10-03 20:28:14 +00:00
1 parent 2c84cfda53
commit 7da4c6d5fd
35 files changed
+1010 -240

No files matched your search

+3 -1
View File
@@ -27,6 +27,8 @@ export async function getSnapshots(type?: "snapshot" | "export") {
} catch (err: any) {
const msg = (err?.message || String(err)).split("\n")[0];
console.error(`[getSnapshots] ERROR (type=${type}):`, msg, err);
return [];
// Remonter l'erreur : une liste vide faisait afficher « Aucun snapshot »
// au lieu de signaler que le chargement a échoué.
throw new Error("Chargement de l'historique impossible.");
}
}
@@ -13,6 +13,7 @@ import {
History,
ChevronRight,
Loader2,
AlertCircle,
} from "lucide-react";
import { SuccessModal } from "@/components/shared/success-modal";
import { isStaleServerActionError, STALE_ACTION_MESSAGE } from "@/lib/stale-action";
@@ -78,6 +79,7 @@ export function SnapshotList({ type }: SnapshotListProps) {
const fetchSnapshots = async () => {
setLoading(true);
setError(null);
try {
const data = await getSnapshots(type);
setSnapshots(data);
@@ -146,6 +148,19 @@ export function SnapshotList({ type }: SnapshotListProps) {
);
}
if (error && snapshots.length === 0) {
return (
<div className="border border-[var(--accent-error)] rounded-2xl p-10 text-center bg-[var(--accent-error-bg)]">
<AlertCircle className="w-8 h-8 mx-auto mb-3 text-[var(--accent-error)]" />
<h3 className="text-[var(--text-primary)] font-bold mb-1">L&apos;historique n&apos;a pas pu être chargé</h3>
<p className="text-[var(--text-secondary)] text-sm mb-4">{error}</p>
<button onClick={fetchSnapshots} className="btn-action btn-action-secondary">
Réessayer
</button>
</div>
);
}
if (snapshots.length === 0) {
return (
<div className="border border-dashed border-[var(--border-strong)] rounded-2xl p-16 text-center bg-[var(--bg-elevated)]/30">