mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-21 05:58:10 +02:00
Lot 5 : changement de magasin instantané et contrôle des droits
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
This commit is contained in:
44 files changed
+1155
-531
No files matched your search
@@ -3,6 +3,19 @@
|
||||
import { create } from "zustand";
|
||||
import { persist } from "zustand/middleware";
|
||||
import type { GammeCode, GridFilters, GridSummary, ProductRow } from "@/types/grid";
|
||||
import { withStorePatch, type StorePatch } from "@/features/grid/lib/store-patch";
|
||||
import { getMonthsFromRows } from "@/features/grid/lib/months";
|
||||
|
||||
/** Ce que décrit `rowsMeta` : les lignes d'un fournisseur, tous magasins. */
|
||||
export interface RowsMeta {
|
||||
key: string;
|
||||
loadedAt: number;
|
||||
/** Magasins dont le complément de l'API FF est déjà appliqué à ces lignes. */
|
||||
patchedStores: string[];
|
||||
}
|
||||
|
||||
/** Clé des lignes chargées pour un fournisseur (toujours « tous magasins »). */
|
||||
export const rowsKeyFor = (codeFournisseur: string) => `${codeFournisseur}:TOTAL`;
|
||||
|
||||
interface GridState {
|
||||
/** Source data from server */
|
||||
@@ -25,12 +38,17 @@ interface GridState {
|
||||
refreshRequest: number;
|
||||
requestRefresh: () => void;
|
||||
/**
|
||||
* Ce que contiennent `rows` (fournisseur:magasin) et quand elles ont été
|
||||
* chargées. Non persisté : sert à ne pas retélécharger la Grille quand on y
|
||||
* revient, et à changer de magasin sans vider l'écran.
|
||||
* Ce que contiennent `rows` et quand elles ont été chargées. Non persisté :
|
||||
* sert à ne pas retélécharger la Grille quand on y revient, et à savoir quels
|
||||
* magasins ont déjà reçu leur complément.
|
||||
*/
|
||||
rowsMeta: { key: string; loadedAt: number } | null;
|
||||
setRowsMeta: (meta: { key: string; loadedAt: number } | null) => void;
|
||||
rowsMeta: RowsMeta | null;
|
||||
setRowsMeta: (meta: RowsMeta | null) => void;
|
||||
/**
|
||||
* Applique le complément d'un magasin, s'il concerne encore les lignes
|
||||
* affichées (`gen` = clé et date de chargement au moment de la demande).
|
||||
*/
|
||||
applyStorePatch: (gen: { key: string; loadedAt: number }, patch: StorePatch) => void;
|
||||
/** Persisted column visibility state */
|
||||
columnVisibility: Record<string, boolean>;
|
||||
/** Persisted column sizing state */
|
||||
@@ -231,6 +249,34 @@ export const useGridStore = create<GridState>()(
|
||||
setActiveGridQuery: (query) => set({ activeGridQuery: sansParamRefresh(query) }),
|
||||
requestRefresh: () => set((state) => ({ refreshRequest: state.refreshRequest + 1 })),
|
||||
setRowsMeta: (meta) => set({ rowsMeta: meta }),
|
||||
applyStorePatch: (gen, patch) => {
|
||||
const { rows, rowsByCodein, rowsMeta } = get();
|
||||
if (!rowsMeta || rowsMeta.key !== gen.key || rowsMeta.loadedAt !== gen.loadedAt) return;
|
||||
const patchedStores = rowsMeta.patchedStores.includes(patch.magasin)
|
||||
? rowsMeta.patchedStores
|
||||
: [...rowsMeta.patchedStores, patch.magasin];
|
||||
// Complément calculé sur une autre fenêtre de 12 mois (changement de
|
||||
// mois entre les deux requêtes) : on le laisse de côté.
|
||||
const mois = getMonthsFromRows(rows);
|
||||
const memeFenetre = !mois || mois.join(",") === patch.periods.join(",");
|
||||
const parCode = new Map(patch.entries.map((e) => [e.codein, e]));
|
||||
if (!memeFenetre || !patch.entries.some((e) => rowsByCodein[e.codein])) {
|
||||
set({ rowsMeta: { ...rowsMeta, patchedStores } });
|
||||
return;
|
||||
}
|
||||
// Nouvelles lignes pour les seuls articles complétés : les autres
|
||||
// gardent leur objet, et donc leur rendu mémorisé.
|
||||
const nouvelles = rows.map((r) => {
|
||||
const e = parCode.get(r.codein);
|
||||
return e ? withStorePatch(r, patch.magasin, e) : r;
|
||||
});
|
||||
set({ rows: nouvelles, rowsByCodein: indexRows(nouvelles), rowsMeta: { ...rowsMeta, patchedStores } });
|
||||
if (summaryTimer) clearTimeout(summaryTimer);
|
||||
summaryTimer = setTimeout(() => {
|
||||
const state = get();
|
||||
set({ summary: computeSummary(state.rows, state.draftChanges, state.activeMagasin) });
|
||||
}, 80);
|
||||
},
|
||||
restoreSnapshot: (changes) => {
|
||||
set({ draftChanges: changes, summary: computeSummary(get().rows, changes, get().activeMagasin) });
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user