mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-22 22:48:08 +02:00
feat: implement virtualized heatmap grid component using TanStack Table and a new Zustand store for product data.
This commit is contained in:
1 parent
1ba507da94
commit
cdc310223c
2 files changed
+29
-5
No files matched your search
@@ -120,12 +120,14 @@ interface VirtualRowProps {
|
|||||||
rowHeight: number;
|
rowHeight: number;
|
||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
columnVisibility: Record<string, boolean>;
|
columnVisibility: Record<string, boolean>;
|
||||||
|
columnSizing: Record<string, number>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const GridRow = React.memo(({ virtualRow, row, rowHeight, isSelected, columnVisibility }: VirtualRowProps) => {
|
const GridRow = React.memo(({ virtualRow, row, rowHeight, isSelected, columnVisibility, columnSizing }: VirtualRowProps) => {
|
||||||
// Uniquement la ligne concernée écoute son propre changement pour l'effet visuel
|
// 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);
|
const effectiveGamme = useGridStore((s) => s.draftChanges[row.original.codein] ?? row.original.codeGamme);
|
||||||
void columnVisibility; // Force re-render via React.memo when visibility changes
|
void columnVisibility; // Force re-render via React.memo when visibility changes
|
||||||
|
void columnSizing; // Force re-render via React.memo when resizing columns
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr
|
<tr
|
||||||
@@ -179,7 +181,7 @@ export function HeatmapGrid({ onSelectionChange }: HeatmapGridProps) {
|
|||||||
const { rows, filters, displayDensity } = useGridStore();
|
const { rows, filters, displayDensity } = useGridStore();
|
||||||
const [sorting, setSorting] = useState<SortingState>([]);
|
const [sorting, setSorting] = useState<SortingState>([]);
|
||||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
||||||
const { columnVisibility, setColumnVisibility } = useGridStore();
|
const { columnVisibility, setColumnVisibility, columnSizing, setColumnSizing } = useGridStore();
|
||||||
const [isMounted, setIsMounted] = useState(false);
|
const [isMounted, setIsMounted] = useState(false);
|
||||||
const [portalContainer, setPortalContainer] = useState<HTMLElement | null>(null);
|
const [portalContainer, setPortalContainer] = useState<HTMLElement | null>(null);
|
||||||
const tableContainerRef = useRef<HTMLDivElement>(null);
|
const tableContainerRef = useRef<HTMLDivElement>(null);
|
||||||
@@ -452,10 +454,12 @@ export function HeatmapGrid({ onSelectionChange }: HeatmapGridProps) {
|
|||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
data: rows,
|
data: rows,
|
||||||
columns,
|
columns,
|
||||||
state: { sorting, globalFilter: filters.search, rowSelection, columnVisibility },
|
state: { sorting, globalFilter: filters.search, rowSelection, columnVisibility, columnSizing },
|
||||||
onSortingChange: setSorting,
|
onSortingChange: setSorting,
|
||||||
onRowSelectionChange: handleRowSelectionChange,
|
onRowSelectionChange: handleRowSelectionChange,
|
||||||
onColumnVisibilityChange: setColumnVisibility,
|
onColumnVisibilityChange: setColumnVisibility,
|
||||||
|
onColumnSizingChange: setColumnSizing,
|
||||||
|
columnResizeMode: "onChange",
|
||||||
getCoreRowModel: getCoreRowModel(),
|
getCoreRowModel: getCoreRowModel(),
|
||||||
getSortedRowModel: getSortedRowModel(),
|
getSortedRowModel: getSortedRowModel(),
|
||||||
getFilteredRowModel: getFilteredRowModel(),
|
getFilteredRowModel: getFilteredRowModel(),
|
||||||
@@ -560,7 +564,7 @@ export function HeatmapGrid({ onSelectionChange }: HeatmapGridProps) {
|
|||||||
return (
|
return (
|
||||||
<th
|
<th
|
||||||
key={header.id}
|
key={header.id}
|
||||||
className="px-2 py-3 text-[10px] font-black uppercase tracking-[0.05em] whitespace-nowrap select-none flex items-center transition-colors hover:bg-white/5"
|
className="px-2 py-3 text-[10px] font-black uppercase tracking-[0.05em] whitespace-nowrap select-none flex items-center transition-colors hover:bg-white/5 relative group/header"
|
||||||
style={{
|
style={{
|
||||||
width: isFlexible ? "100%" : size,
|
width: isFlexible ? "100%" : size,
|
||||||
flex: isFlexible ? `1 1 ${size}px` : `0 0 ${size}px`,
|
flex: isFlexible ? `1 1 ${size}px` : `0 0 ${size}px`,
|
||||||
@@ -582,6 +586,15 @@ export function HeatmapGrid({ onSelectionChange }: HeatmapGridProps) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{/* Handle de redimensionnement de la colonne */}
|
||||||
|
{header.column.getCanResize() && (
|
||||||
|
<div
|
||||||
|
onMouseDown={header.getResizeHandler()}
|
||||||
|
onTouchStart={header.getResizeHandler()}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
className={`absolute right-0 top-0 h-full w-1.5 cursor-col-resize user-select-none touch-none hover:bg-emerald-500/50 ${header.column.getIsResizing() ? "bg-emerald-500" : ""}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</th>
|
</th>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -601,6 +614,7 @@ export function HeatmapGrid({ onSelectionChange }: HeatmapGridProps) {
|
|||||||
rowHeight={rowHeight}
|
rowHeight={rowHeight}
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
columnVisibility={columnVisibility}
|
columnVisibility={columnVisibility}
|
||||||
|
columnSizing={columnSizing}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ interface GridState {
|
|||||||
activeGridQuery: string;
|
activeGridQuery: string;
|
||||||
/** Persisted column visibility state */
|
/** Persisted column visibility state */
|
||||||
columnVisibility: Record<string, boolean>;
|
columnVisibility: Record<string, boolean>;
|
||||||
|
/** Persisted column sizing state */
|
||||||
|
columnSizing: Record<string, number>;
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
setRows: (rows: ProductRow[]) => void;
|
setRows: (rows: ProductRow[]) => void;
|
||||||
@@ -29,6 +31,7 @@ interface GridState {
|
|||||||
restoreSnapshot: (changes: Record<string, GammeCode>) => void;
|
restoreSnapshot: (changes: Record<string, GammeCode>) => void;
|
||||||
batchSetDraftGamme: (changes: Record<string, GammeCode>) => void;
|
batchSetDraftGamme: (changes: Record<string, GammeCode>) => void;
|
||||||
setColumnVisibility: (updater: Record<string, boolean> | ((old: Record<string, boolean>) => Record<string, boolean>)) => void;
|
setColumnVisibility: (updater: Record<string, boolean> | ((old: Record<string, boolean>) => Record<string, boolean>)) => void;
|
||||||
|
setColumnSizing: (updater: Record<string, number> | ((old: Record<string, number>) => Record<string, number>)) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function computeSummary(rows: ProductRow[], drafts: Record<string, GammeCode>): GridSummary {
|
function computeSummary(rows: ProductRow[], drafts: Record<string, GammeCode>): GridSummary {
|
||||||
@@ -71,6 +74,7 @@ export const useGridStore = create<GridState>()(
|
|||||||
displayDensity: "normal",
|
displayDensity: "normal",
|
||||||
activeGridQuery: "",
|
activeGridQuery: "",
|
||||||
columnVisibility: {},
|
columnVisibility: {},
|
||||||
|
columnSizing: {},
|
||||||
|
|
||||||
setRows: (rows) => {
|
setRows: (rows) => {
|
||||||
set({ rows, summary: computeSummary(rows, get().draftChanges) });
|
set({ rows, summary: computeSummary(rows, get().draftChanges) });
|
||||||
@@ -134,16 +138,22 @@ export const useGridStore = create<GridState>()(
|
|||||||
columnVisibility: typeof updater === 'function' ? updater(state.columnVisibility) : updater
|
columnVisibility: typeof updater === 'function' ? updater(state.columnVisibility) : updater
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
setColumnSizing: (updater) => {
|
||||||
|
set((state) => ({
|
||||||
|
columnSizing: typeof updater === 'function' ? updater(state.columnSizing) : updater
|
||||||
|
}));
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: "collectflow-grid-storage",
|
name: "collectflow-grid-storage",
|
||||||
// Only persist filters, display density, drafts, active grid query, and column visibility
|
// Only persist filters, display density, drafts, active grid query, and column visibility/sizing
|
||||||
partialize: (state) => ({
|
partialize: (state) => ({
|
||||||
filters: state.filters,
|
filters: state.filters,
|
||||||
displayDensity: state.displayDensity,
|
displayDensity: state.displayDensity,
|
||||||
draftChanges: state.draftChanges,
|
draftChanges: state.draftChanges,
|
||||||
activeGridQuery: state.activeGridQuery,
|
activeGridQuery: state.activeGridQuery,
|
||||||
columnVisibility: state.columnVisibility,
|
columnVisibility: state.columnVisibility,
|
||||||
|
columnSizing: state.columnSizing,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in new issue
Block a user