revert: restore grid to pre-infinite-scroll state

Remove infinite grid loading, useInfiniteGrid hook, and server-side
pagination API route. Revert grid components to the simpler version
that avoids 2MB+ cache failures from unstable_cache.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
MichaelandClaude Sonnet 4.6 committed 2026-04-03 11:26:13 +02:00
1 parent bd21f07c8e
commit 2aa9cbecaa
8 files changed
+40 -422

No files matched your search

+11 -60
View File
@@ -1,6 +1,6 @@
"use client";
import { useEffect, useRef, useState, useTransition, useCallback } from "react";
import { useEffect, useRef, useState, useTransition } from "react";
import { HeatmapGrid } from "@/features/grid/components/heatmap-grid";
import { FloatingSummaryBar } from "@/features/grid/components/floating-summary-bar";
import { BulkActionToolbar } from "@/features/grid/components/bulk-action-toolbar";
@@ -8,15 +8,15 @@ import { GridFilterBar } from "@/features/grid/components/grid-filter-bar";
import { ExportDropdown } from "@/features/grid/components/export-dropdown";
import { useGridStore } from "@/features/grid/store/use-grid-store";
import { useSaveDrafts } from "@/features/grid/hooks/use-save-drafts";
import { useInfiniteGrid } from "@/features/grid/hooks/use-infinite-grid";
import type { ProductRow } from "@/types/grid";
import { CheckCircle, AlertCircle, Loader2, StopCircle } from "lucide-react";
import { CheckCircle, AlertCircle, Loader2 } from "lucide-react";
import { useSession } from "next-auth/react";
import { useSearchParams } from "next/navigation";
import { computeProductScores } from "@/lib/score-engine";
interface GridClientProps {
initialRows: ProductRow[];
initialTotal: number;
codeFournisseur: string;
nomFournisseur: string;
fournisseurs: { code: string; nom: string }[];
@@ -24,12 +24,10 @@ interface GridClientProps {
magasin: string;
}
export function GridClient({ initialRows, initialTotal, codeFournisseur, nomFournisseur, fournisseurs, magasins, magasin }: GridClientProps) {
export function GridClient({ initialRows, codeFournisseur, nomFournisseur, fournisseurs, magasins, magasin }: GridClientProps) {
const { data: session } = useSession();
const isAdmin = (session?.user as { role?: string })?.role === "admin";
const isAdmin = (session?.user as any)?.role === "admin";
const setRows = useGridStore((s) => s.setRows);
const appendRows = useGridStore((s) => s.appendRows);
const rows = useGridStore((s) => s.rows);
const setActiveGridQuery = useGridStore((s) => s.setActiveGridQuery);
const setFilter = useGridStore((s) => s.setFilter);
const setActiveMagasin = useGridStore((s) => s.setActiveMagasin);
@@ -44,20 +42,6 @@ export function GridClient({ initialRows, initialTotal, codeFournisseur, nomFour
const [isMounted, setIsMounted] = useState(false);
// ─── Chargement progressif des pages suivantes ─────────────────────────────
const handlePageLoaded = useCallback((newRows: ProductRow[]) => {
// Ajout O(n_nouveaux_items) au lieu de O(N_total)
appendRows(newRows);
}, [appendRows]);
const { total, isLoadingMore, hasMore, loadNextPage, stopLoading, progress } = useInfiniteGrid({
codeFournisseur,
magasin: magasin || "TOTAL",
initialTotal,
pageSize: 10000,
onPageLoaded: handlePageLoaded
});
useEffect(() => {
setIsMounted(true);
}, []);
@@ -66,6 +50,7 @@ export function GridClient({ initialRows, initialTotal, codeFournisseur, nomFour
useEffect(() => {
if (!isMounted) return;
const currentQueryString = searchParams.toString();
// Set the active query, ensuring it starts with ? if not empty
setActiveGridQuery(currentQueryString ? `?${currentQueryString}` : "");
}, [searchParams, setActiveGridQuery, isMounted]);
@@ -78,24 +63,16 @@ export function GridClient({ initialRows, initialTotal, codeFournisseur, nomFour
setFilter("code3", null);
prevFournisseurRef.current = codeFournisseur;
}
// Reset du store pour ce fournisseur
setRows(initialRows);
const scoredRows = computeProductScores([...initialRows]);
setRows(scoredRows);
}, [codeFournisseur, initialRows, setRows, setFilter, isMounted]);
// Synchroniser le magasin actif
// Synchroniser le magasin actif depuis la prop URL (changement de magasin sans rechargement)
useEffect(() => {
if (!isMounted) return;
setActiveMagasin(magasin || "TOTAL");
}, [magasin, setActiveMagasin, isMounted]);
// Chargement automatique des pages suivantes en arrière-plan
useEffect(() => {
if (!isMounted || !hasMore || isLoadingMore) return;
// Délai court pour ne pas bloquer le rendu
const timer = setTimeout(loadNextPage, 300);
return () => clearTimeout(timer);
}, [isMounted, hasMore, isLoadingMore, loadNextPage]);
const handleSave = () => {
startTransition(async () => {
const result = await save();
@@ -119,7 +96,7 @@ export function GridClient({ initialRows, initialTotal, codeFournisseur, nomFour
<p className="text-[13px] mt-0.5" style={{ color: "var(--text-secondary)" }}>
<span className="font-semibold" style={{ color: "var(--text-primary)" }}>{nomFournisseur}</span>
{" "}• <span className="font-medium" style={{ color: "var(--text-muted)" }}>{activeStoreNom}</span>
{" "}• {total.toLocaleString("fr-FR")} références
{" "}• {initialRows.length} références
</p>
</div>
<div className="flex items-center gap-3">
@@ -150,32 +127,6 @@ export function GridClient({ initialRows, initialTotal, codeFournisseur, nomFour
</div>
</div>
{/* Barre de progression chargement en arrière-plan */}
{(hasMore || isLoadingMore) && (
<div className="shrink-0 flex items-center gap-2 px-3 py-1.5 rounded-lg text-[11px] font-medium"
style={{ background: "var(--bg-elevated)", border: "1px solid var(--border)", color: "var(--text-muted)" }}
>
<Loader2 className="w-3 h-3 animate-spin shrink-0" style={{ color: "var(--accent)" }} />
<span>
Téléchargement&nbsp;: {rows.length.toLocaleString("fr-FR")} / {total.toLocaleString("fr-FR")} références
</span>
<div className="flex-1 h-1 w-[100px] rounded-full overflow-hidden" style={{ background: "var(--border)" }}>
<div
className="h-full rounded-full transition-all duration-500"
style={{ width: `${progress}%`, background: "var(--accent)" }}
/>
</div>
<button
onClick={stopLoading}
className="ml-2 flex items-center gap-1 px-2 py-0.5 rounded-full hover:bg-red-500/10 text-red-600 transition-colors"
title="Arrêter le téléchargement du reste des références"
>
<StopCircle className="w-3.5 h-3.5" />
<span className="text-[10px] uppercase font-bold">Stop</span>
</button>
</div>
)}
{/* Filters */}
<div className="shrink-0 print:hidden">
<GridFilterBar fournisseurs={fournisseurs} magasins={magasins} />
+6 -18
View File
@@ -268,39 +268,27 @@ function CellDetailModal({ d, activeMagasin, onClose }: { d: CellDetailData; act
// =========================================================================
export function HeatmapGrid({ onSelectionChange, isAdmin }: HeatmapGridProps) {
// L'abonnement doit être ultra-minimal ! JAMAIS de destructuring brut sur useGridStore().
const rows = useGridStore((s) => s.rows);
const filters = useGridStore((s) => s.filters);
const displayDensity = useGridStore((s) => s.displayDensity);
const activeMagasin = useGridStore((s) => s.activeMagasin);
// L'abonnement doit être minimal ici ! PAS de draftChanges ni de setDraftGamme.
const { rows, filters, displayDensity, draftChanges, activeMagasin } = useGridStore();
// Filtre client-side par code3 (famille) et codeGamme sans abonnement réactif aux brouillons pour éviter le freeze.
// Filtre client-side par code3 (famille) et codeGamme
const filteredData = useMemo(() => {
const { code3, codeGamme } = filters;
if (!code3 && !codeGamme) return rows;
// On récupère le state de façon non-réactive pour ne pas trigger 130k renders
const currentDrafts = useGridStore.getState().draftChanges;
return rows.filter(r => {
if (code3 && r.code3 !== code3) return false;
if (codeGamme) {
const g = currentDrafts[r.codein] ?? r.codeGamme ?? "";
const g = draftChanges[r.codein] ?? r.codeGamme ?? "";
const norm = g.trim() === "" ? "Aucune" : g;
if (norm !== codeGamme) return false;
}
return true;
});
}, [rows, filters]); // ❌ Surtout pas draftChanges ici
}, [rows, filters, draftChanges]);
const [sorting, setSorting] = useState<SortingState>([]);
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
// Selectors granulaires
const columnVisibility = useGridStore((s) => s.columnVisibility);
const setColumnVisibility = useGridStore((s) => s.setColumnVisibility);
const columnSizing = useGridStore((s) => s.columnSizing);
const setColumnSizing = useGridStore((s) => s.setColumnSizing);
const { columnVisibility, setColumnVisibility, columnSizing, setColumnSizing } = useGridStore();
const [isMounted, setIsMounted] = useState(false);
const [portalContainer, setPortalContainer] = useState<HTMLElement | null>(null);