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

+32 -1
View File
@@ -14,7 +14,7 @@ import "server-only";
import { db } from "@/db";
import { gridRows } from "@/db/schema";
import { and, asc, desc, eq, ilike, or, sql, lt, type SQL } from "drizzle-orm";
import { and, asc, desc, eq, ilike, inArray, or, sql, lt, type SQL } from "drizzle-orm";
import type { ProductRow } from "@/types/grid";
/** Colonnes autorisées au tri — liste blanche, jamais l'entrée utilisateur brute. */
@@ -147,6 +147,37 @@ export async function upsertGridRows(codeFournisseur: string, rows: ProductRow[]
return written;
}
/**
* Reporte des gammes enregistrées dans l'instantané persisté (colonne et payload),
* pour que /api/v1 les voie sans attendre un recalcul complet de la grille.
*/
export async function updateGridRowsGamme(
codeFournisseur: string,
changes: ReadonlyArray<{ codein: string; codeGamme: string }>,
): Promise<void> {
const parGamme = new Map<string, string[]>();
for (const c of changes) {
const codeins = parGamme.get(c.codeGamme) ?? [];
codeins.push(c.codein);
parGamme.set(c.codeGamme, codeins);
}
const CHUNK = 5000; // bien en deçà des 65 535 paramètres liés de PostgreSQL
for (const [gamme, codeins] of parGamme) {
for (let i = 0; i < codeins.length; i += CHUNK) {
await db
.update(gridRows)
.set({
codeGamme: gamme,
payload: sql`jsonb_set(${gridRows.payload}, '{codeGamme}', to_jsonb(${gamme}::text))`,
})
.where(and(
eq(gridRows.codeFournisseur, codeFournisseur),
inArray(gridRows.codein, codeins.slice(i, i + CHUNK)),
));
}
}
}
/** Construit la clause WHERE commune à la grille et à la recherche transversale. */
function buildWhere(q: GridQuery): SQL | undefined {
const clauses: SQL[] = [];