feat(grille): filtre nomenclature en multi-sélection

Le filtre était un <select> à valeur unique : impossible de retenir plusieurs
nomenclatures, ni de partir de tout pour retirer les postes non voulus — ce qui
est la manière naturelle de travailler quand un fournisseur en compte des
dizaines.

Remplacé par une liste déroulante à cases, avec recherche, « Tout cocher »,
« Tout décocher », « Ne garder que ces N » sur le résultat d'une recherche, et
une croix pour tout réafficher. Décocher depuis l'état « tout » matérialise la
liste complète puis en retire le poste, sinon le premier décochage n'aurait
rien fait.

filters.code3 passe de `string | null` à `string[] | null` : `null` = aucun
filtre, `[]` = rien de coché. Distinguer les deux évite la valeur sentinelle
qu'imposerait un simple tableau, et rend « Tout décocher » sans ambiguïté.
L'URL accepte une liste séparée par des virgules.

Au passage : la Grille rechargeait tout depuis le serveur à chaque changement
de nomenclature, alors que getProductRows ignore ce filtre et que le tri se
fait en local. Le paramètre n'est plus transmis ni dans les dépendances de
l'effet — le filtrage est désormais instantané, ce qui compte d'autant plus
avec plusieurs cases à cocher.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y26nRZxTR57K7h8yqsF675
This commit is contained in:
Claude committed 2026-08-07 08:46:39 +00:00
1 parent 0bd7ec9798
commit 720cf3c7a1
7 files changed
+167 -32

No files matched your search

+7 -4
View File
@@ -58,6 +58,7 @@ export function GridClient({ codeFournisseur, nomFournisseur, fournisseurs, maga
const rows = useGridStore((s) => s.rows);
const setActiveGridQuery = useGridStore((s) => s.setActiveGridQuery);
const setFilter = useGridStore((s) => s.setFilter);
const setCode3Filter = useGridStore((s) => s.setCode3Filter);
const setActiveMagasin = useGridStore((s) => s.setActiveMagasin);
const searchParams = useSearchParams();
const router = useRouter();
@@ -95,10 +96,10 @@ export function GridClient({ codeFournisseur, nomFournisseur, fournisseurs, maga
if (!isMounted) return;
if (prevFournisseurRef.current !== codeFournisseur) {
setFilter("codeGamme", null);
setFilter("code3", null);
setCode3Filter(null);
prevFournisseurRef.current = codeFournisseur;
}
}, [codeFournisseur, setFilter, isMounted]);
}, [codeFournisseur, setFilter, setCode3Filter, isMounted]);
const refreshToken = searchParams.get("_refresh");
useEffect(() => {
@@ -108,9 +109,11 @@ export function GridClient({ codeFournisseur, nomFournisseur, fournisseurs, maga
const params = new URLSearchParams();
params.set("fournisseur", codeFournisseur);
params.set("magasin", magasin || "TOTAL");
// `code3` n'est volontairement pas transmis : le serveur ignore ce filtre
// (getProductRows ne s'en sert pas) et la Grille l'applique en local. Le
// faire voyager relançait un chargement complet à chaque case cochée.
if (filters.code1) params.set("code1", filters.code1);
if (filters.code2) params.set("code2", filters.code2);
if (filters.code3) params.set("code3", filters.code3);
if (refreshToken) params.set("refresh", "1");
let accumulatedRows: ProductRow[] = [];
@@ -173,7 +176,7 @@ export function GridClient({ codeFournisseur, nomFournisseur, fournisseurs, maga
loadRows();
return () => controller.abort();
}, [codeFournisseur, magasin, filters.code1, filters.code2, filters.code3, refreshToken, setRows, isMounted]);
}, [codeFournisseur, magasin, filters.code1, filters.code2, refreshToken, setRows, isMounted]);
// Synchroniser le magasin actif depuis la prop URL (changement de magasin sans rechargement)
useEffect(() => {