mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-23 15:08:12 +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
@@ -13,6 +13,22 @@ import { ApiConnectionInfo } from "@/features/admin/components/api-connection-in
|
||||
import { GridWarmup } from "@/features/admin/components/grid-warmup";
|
||||
import { ServerLogs } from "@/features/settings/components/server-logs";
|
||||
|
||||
/**
|
||||
* Configuration enregistrée, lue UNE fois pour toute la page : trois sections
|
||||
* (connexion, API FF, Qlik) la demandaient chacune au montage, et les actions
|
||||
* serveur s'exécutent les unes après les autres.
|
||||
*/
|
||||
let savedConfigPromise: ReturnType<typeof getSavedDatabaseConfig> | null = null;
|
||||
function loadSavedConfig(force = false): ReturnType<typeof getSavedDatabaseConfig> {
|
||||
if (force || !savedConfigPromise) {
|
||||
savedConfigPromise = getSavedDatabaseConfig().catch((e) => {
|
||||
savedConfigPromise = null;
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
return savedConfigPromise;
|
||||
}
|
||||
|
||||
function Section({ title, subtitle, children }: { title: string; subtitle?: string; children: React.ReactNode }) {
|
||||
return (
|
||||
<section className="apple-card">
|
||||
@@ -51,7 +67,7 @@ function FfApiStatusSection() {
|
||||
|
||||
// Charge l'URL enregistrée pour la préremplir (vide = valeur par défaut).
|
||||
useEffect(() => {
|
||||
getSavedDatabaseConfig()
|
||||
loadSavedConfig()
|
||||
.then((cfg) => {
|
||||
setSavedUrl(cfg?.ffApiBaseUrl ?? null);
|
||||
if (cfg?.ffApiBaseUrl) setUrl(cfg.ffApiBaseUrl);
|
||||
@@ -177,7 +193,7 @@ function QlikSettingsSection() {
|
||||
const [status, setStatus] = useState<{ type: "ok" | "err"; msg: string } | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
getSavedDatabaseConfig().then((c) => {
|
||||
loadSavedConfig().then((c) => {
|
||||
if (!c) return;
|
||||
setHost(c.qlikHost ?? "");
|
||||
setUser(c.qlikUser ?? "");
|
||||
@@ -265,8 +281,8 @@ export default function SettingsPage() {
|
||||
getDatabaseUrl
|
||||
} = useDbSettingsStore();
|
||||
|
||||
const reloadFromServer = useCallback(async () => {
|
||||
const config = await getSavedDatabaseConfig();
|
||||
const reloadFromServer = useCallback(async (force = false) => {
|
||||
const config = await loadSavedConfig(force);
|
||||
if (config) {
|
||||
if (config.url) {
|
||||
// Parser l'URL pour remettre dans le store
|
||||
@@ -290,7 +306,9 @@ export default function SettingsPage() {
|
||||
// au montage, ce que la règle déconseille en général mais qui est ici le but.
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||
setIsMounted(true);
|
||||
reloadFromServer();
|
||||
// Lecture fraîche à chaque ouverture de la page ; les sections, montées
|
||||
// juste après (cf. `isMounted`), réutilisent cette même requête.
|
||||
reloadFromServer(true);
|
||||
}, [reloadFromServer]);
|
||||
|
||||
const testDb = async () => {
|
||||
@@ -410,7 +428,7 @@ export default function SettingsPage() {
|
||||
</div>
|
||||
<div className="pt-2">
|
||||
<button
|
||||
onClick={reloadFromServer}
|
||||
onClick={() => reloadFromServer(true)}
|
||||
className="apple-btn-secondary h-8 px-3 text-[11px] opacity-80 hover:opacity-100"
|
||||
>
|
||||
<RotateCcw className="w-3 h-3" />
|
||||
|
||||
Reference in new issue
Block a user