diff --git a/src/features/grid/components/heatmap-grid.tsx b/src/features/grid/components/heatmap-grid.tsx
index 065bd09..e4b05ba 100644
--- a/src/features/grid/components/heatmap-grid.tsx
+++ b/src/features/grid/components/heatmap-grid.tsx
@@ -74,8 +74,103 @@ interface HeatmapGridProps {
onSelectionChange?: (codeins: string[]) => void;
}
+// =========================================================================
+// OPTIMISATION PERFORMANCES (React.memo + Zustand Selectors granulaires)
+// =========================================================================
+
+// 1. Composant isolé pour la Cellule Gamme (évite le re-render des colonnes)
+const GammeCell = React.memo(({ row }: { row: ProductRow }) => {
+ // Abonnement ultra-ciblé : la cellule ne re-render que si SA valeur change
+ const codein = row.codein;
+ const isDraft = useGridStore((s) => s.draftChanges[codein] !== undefined);
+ const effectiveGamme = useGridStore((s) => s.draftChanges[codein] ?? row.codeGamme);
+ const setDraftGamme = useGridStore((s) => s.setDraftGamme);
+
+ const isModified = row.codeGamme !== row.codeGammeInit && row.codeGammeInit !== null;
+
+ return (
+
+
setDraftGamme(codein, g)}
+ />
+ {isModified && !isDraft && (
+
+
+
+ )}
+
+ );
+});
+GammeCell.displayName = "GammeCell";
+
+import { Row } from "@tanstack/react-table";
+import { VirtualItem } from "@tanstack/react-virtual";
+
+// 2. Composant isolé pour la Ligne Virtuelle
+// (Gère l'état grisé "Z" localement sans faire re-render toute la grid)
+interface VirtualRowProps {
+ virtualRow: VirtualItem;
+ row: Row;
+ rowHeight: number;
+ isSelected: boolean;
+}
+
+const GridRow = React.memo(({ virtualRow, row, rowHeight, isSelected }: VirtualRowProps) => {
+ // Uniquement la ligne concernée écoute son propre changement pour l'effet visuel
+ const effectiveGamme = useGridStore((s) => s.draftChanges[row.original.codein] ?? row.original.codeGamme);
+
+ return (
+ row.toggleSelected()}
+ className={cn(
+ "absolute w-full flex items-center cursor-pointer transition-all duration-200 group/row",
+ effectiveGamme === "Z" && "opacity-40 grayscale-[0.5] hover:grayscale-0 hover:opacity-100"
+ )}
+ style={{
+ height: `${rowHeight}px`,
+ transform: `translateY(${virtualRow.start}px)`,
+ borderBottom: "1px solid var(--border)",
+ background: isSelected ? "var(--accent-bg)" : "transparent",
+ borderLeft: isSelected ? "3px solid var(--accent)" : "3px solid transparent",
+ }}
+ >
+ {row.getVisibleCells().map((cell: any) => {
+ const isFlexible = cell.column.id === "libelle1" || cell.column.id === "ai" || cell.column.id === "libelle3";
+ const isCenter = cell.column.id === "totalQuantite" || cell.column.id === "totalCa" || cell.column.id === "totalMarge" || cell.column.id.startsWith("month_") || cell.column.id === "gammeInitial" || cell.column.id === "score" || cell.column.id === "gamme";
+ const size = cell.column.getSize();
+ return (
+ |
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
+ |
+ );
+ })}
+
+ );
+});
+GridRow.displayName = "GridRow";
+
+// =========================================================================
+
export function HeatmapGrid({ onSelectionChange }: HeatmapGridProps) {
- const { rows, draftChanges, setDraftGamme, filters, displayDensity } = useGridStore();
+ // L'abonnement doit être minimal ici ! PAS de draftChanges ni de setDraftGamme.
+ const { rows, filters, displayDensity } = useGridStore();
const [sorting, setSorting] = useState([]);
const [rowSelection, setRowSelection] = useState({});
const [isMounted, setIsMounted] = useState(false);
@@ -303,25 +398,7 @@ export function HeatmapGrid({ onSelectionChange }: HeatmapGridProps) {
id: "gamme",
header: () => Gamme
,
size: 110,
- cell: ({ row }) => {
- const effectiveGamme = (draftChanges[row.original.codein] ?? row.original.codeGamme) as GammeCode | null;
- const isModified = row.original.codeGamme !== row.original.codeGammeInit && row.original.codeGammeInit !== null;
-
- return (
-
-
setDraftGamme(row.original.codein, g)}
- />
- {isModified && !draftChanges[row.original.codein] && (
-
-
-
- )}
-
- );
- },
+ cell: ({ row }) => ,
},
{
id: "ai",
@@ -330,7 +407,7 @@ export function HeatmapGrid({ onSelectionChange }: HeatmapGridProps) {
enableSorting: false,
cell: ({ row }) => ,
},
- ], [draftChanges, setDraftGamme, MONTHS_12]);
+ ], [MONTHS_12]); // Dépendances extrêmement stables : pas de re-render du header !
const table = useReactTable({
data: rows,
@@ -428,51 +505,17 @@ export function HeatmapGrid({ onSelectionChange }: HeatmapGridProps) {
{rowVirtualizer.getVirtualItems().map((virtualRow) => {
const row = tableRows[virtualRow.index];
- const effectiveGamme = draftChanges[row.original.codein] ?? row.original.codeGamme;
const isSelected = row.getIsSelected();
+
return (
- row.toggleSelected()}
- className={cn(
- "absolute w-full flex items-center cursor-pointer transition-all duration-200 group/row",
- effectiveGamme === "Z" && "opacity-40 grayscale-[0.5] hover:grayscale-0 hover:opacity-100"
- )}
- style={{
- height: `${rowHeight}px`,
- transform: `translateY(${virtualRow.start}px)`,
- borderBottom: "1px solid var(--border)",
- background: isSelected ? "var(--accent-bg)" : "transparent",
- borderLeft: isSelected ? "3px solid var(--accent)" : "3px solid transparent",
- }}
- >
- {row.getVisibleCells().map((cell) => {
- const isFlexible = cell.column.id === "libelle1" || cell.column.id === "ai" || cell.column.id === "libelle3";
- const isCenter = cell.column.id === "totalQuantite" || cell.column.id === "totalCa" || cell.column.id === "totalMarge" || cell.column.id.startsWith("month_") || cell.column.id === "gammeInitial" || cell.column.id === "score" || cell.column.id === "gamme";
- const size = cell.column.getSize();
- return (
- |
- {flexRender(cell.column.columnDef.cell, cell.getContext())}
- |
- );
- })}
-
+
+
+
);
})}