mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-19 21:18:16 +02:00
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:
35 files changed
+1010
-240
No files matched your search
@@ -16,6 +16,15 @@ const CONFIG_FILE = path.join(process.cwd(), "data", ".db-config.json");
|
||||
const FF_API_BASE_DEFAULT = "https://api.ffnancy.fr";
|
||||
const FF_API_BASE_TTL_MS = 30_000;
|
||||
|
||||
/**
|
||||
* Options de tout appel à l'API FF : jamais de cache HTTP, et un délai maximal.
|
||||
* Sans délai, une API muette figeait indéfiniment la page qui l'attendait.
|
||||
*/
|
||||
const FF_FETCH_TIMEOUT_MS = Number(process.env.FF_API_TIMEOUT_MS) || 30_000;
|
||||
export function ffFetchInit(): RequestInit {
|
||||
return { cache: "no-store", signal: AbortSignal.timeout(FF_FETCH_TIMEOUT_MS) };
|
||||
}
|
||||
|
||||
let ffApiBaseCache: { value: string; at: number } | null = null;
|
||||
|
||||
/**
|
||||
@@ -201,7 +210,7 @@ async function fetchAllPages<T>(
|
||||
// Page 1 — récupère les items et tente d'obtenir le total pour paralléliser
|
||||
const url1 = buildUrl(1);
|
||||
console.log(`[api-ff] GET ${url1}`);
|
||||
const res1 = await fetch(url1, { cache: "no-store" });
|
||||
const res1 = await fetch(url1, ffFetchInit());
|
||||
if (!res1.ok) {
|
||||
console.error(`[api-ff] HTTP ${res1.status} — ${url1}`);
|
||||
return [];
|
||||
@@ -227,7 +236,7 @@ async function fetchAllPages<T>(
|
||||
const results = await Promise.all(chunk.map(async (p) => {
|
||||
const url = buildUrl(p);
|
||||
console.log(`[api-ff] GET ${url}`);
|
||||
const res = await fetch(url, { cache: "no-store" });
|
||||
const res = await fetch(url, ffFetchInit());
|
||||
if (!res.ok) return [] as T[];
|
||||
const data = await res.json();
|
||||
return extractList(data) as T[];
|
||||
@@ -245,7 +254,7 @@ async function fetchAllPages<T>(
|
||||
const results = await Promise.all(chunk.map(async (p) => {
|
||||
const url = buildUrl(p);
|
||||
console.log(`[api-ff] GET ${url}`);
|
||||
const res = await fetch(url, { cache: "no-store" });
|
||||
const res = await fetch(url, ffFetchInit());
|
||||
if (!res.ok) return { p, items: [] as T[] };
|
||||
const data = await res.json();
|
||||
return { p, items: extractList(data) as T[] };
|
||||
@@ -311,7 +320,7 @@ export async function getFournisseursFromApi(
|
||||
: `${FF_API_BASE}/api/fournisseurs?limit=500`;
|
||||
|
||||
console.log(`[api-ff] GET ${url}`);
|
||||
const res = await fetch(url, { cache: "no-store" });
|
||||
const res = await fetch(url, ffFetchInit());
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status} on ${url}`);
|
||||
const data = await res.json();
|
||||
|
||||
@@ -468,7 +477,7 @@ export async function getMensuelByArticles(
|
||||
if (!noId) return;
|
||||
try {
|
||||
const url = `${FF_API_BASE}/api/articles/${encodeURIComponent(String(noId))}/mensuel?dateDebut=${dateDebut}&dateFin=${dateFin}`;
|
||||
const res = await fetch(url, { cache: "no-store" });
|
||||
const res = await fetch(url, ffFetchInit());
|
||||
if (!res.ok) return;
|
||||
const data = await res.json();
|
||||
const entries: FfMensuelEntry[] = Array.isArray(data.data) ? data.data : [];
|
||||
@@ -555,7 +564,7 @@ export async function getReferentielByArticles(
|
||||
if (!noId) return;
|
||||
try {
|
||||
const url = `${FF_API_BASE}/api/articles/${encodeURIComponent(String(noId))}/referentiel`;
|
||||
const res = await fetch(url, { cache: "no-store" });
|
||||
const res = await fetch(url, ffFetchInit());
|
||||
if (!res.ok) return;
|
||||
const data: FfReferentiel = await res.json();
|
||||
if (data?.article) result.set(art.codein, data);
|
||||
@@ -581,7 +590,7 @@ export async function getCommandesByFournisseur(
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${FF_API_BASE}/api/commandes/articles?codefou=${encodeURIComponent(codefou)}`,
|
||||
{ cache: "no-store" }
|
||||
ffFetchInit()
|
||||
);
|
||||
if (!res.ok) return result;
|
||||
const data = await res.json();
|
||||
@@ -602,7 +611,7 @@ export async function getCommandesByFournisseur(
|
||||
export async function getSyncStatus(): Promise<FfSyncStatus | null> {
|
||||
try {
|
||||
const FF_API_BASE = await getFfApiBase();
|
||||
const res = await fetch(`${FF_API_BASE}/api/sync/status`, { cache: "no-store" });
|
||||
const res = await fetch(`${FF_API_BASE}/api/sync/status`, ffFetchInit());
|
||||
if (!res.ok) return null;
|
||||
return normalizeSyncStatus(await res.json());
|
||||
} catch (err) {
|
||||
|
||||
Reference in new issue
Block a user