mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-22 06:28:09 +02:00
feat: introduce heatmap grid component for displaying and managing product data with virtualization and performance optimizations.
This commit is contained in:
1 parent
eb5bceb5bf
commit
57449ec603
1 file changed
+77
-75
@@ -490,27 +490,19 @@ export function HeatmapGrid({ onSelectionChange }: HeatmapGridProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="flex flex-col h-full w-full gap-3 relative">
|
||||||
ref={tableContainerRef}
|
<div className="flex justify-end items-center w-full px-1">
|
||||||
className="h-full w-full overflow-auto rounded-[12px] scroll-smooth relative"
|
|
||||||
style={{
|
|
||||||
background: "var(--bg-surface)",
|
|
||||||
border: "1px solid var(--border)",
|
|
||||||
boxShadow: "var(--shadow-sm)"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="absolute top-2 right-4 z-50">
|
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<button
|
<button
|
||||||
className="flex items-center gap-2 px-3 py-1.5 rounded-md text-xs font-semibold bg-[var(--bg-elevated)] border border-[var(--border-strong)] hover:border-emerald-500 hover:text-emerald-500 transition-colors shadow-sm"
|
className="flex items-center justify-center gap-2 px-3 py-1.5 rounded-md text-sm font-semibold bg-[var(--action-secondary-bg)] text-[var(--action-secondary-text)] border border-[var(--border-strong)] hover:border-[var(--accent)] hover:text-[var(--accent)] transition-all shadow-sm apple-btn-secondary"
|
||||||
aria-label="Afficher/masquer les colonnes"
|
aria-label="Afficher/masquer les colonnes"
|
||||||
>
|
>
|
||||||
<SlidersHorizontal className="w-3.5 h-3.5" />
|
<SlidersHorizontal className="w-4 h-4" />
|
||||||
Colonnes
|
Colonnes
|
||||||
</button>
|
</button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end" className="w-[200px] max-h-[400px] overflow-y-auto z-50">
|
<DropdownMenuContent align="end" className="w-[220px] max-h-[50vh] overflow-y-auto z-50">
|
||||||
{table
|
{table
|
||||||
.getAllLeafColumns()
|
.getAllLeafColumns()
|
||||||
.filter((column: any) => column.getCanHide())
|
.filter((column: any) => column.getCanHide())
|
||||||
@@ -518,7 +510,7 @@ export function HeatmapGrid({ onSelectionChange }: HeatmapGridProps) {
|
|||||||
return (
|
return (
|
||||||
<DropdownMenuCheckboxItem
|
<DropdownMenuCheckboxItem
|
||||||
key={column.id}
|
key={column.id}
|
||||||
className="capitalize text-xs cursor-pointer"
|
className="capitalize text-xs cursor-pointer font-medium"
|
||||||
checked={column.getIsVisible()}
|
checked={column.getIsVisible()}
|
||||||
onCheckedChange={(value) => column.toggleVisibility(!!value)}
|
onCheckedChange={(value) => column.toggleVisibility(!!value)}
|
||||||
onSelect={(e: Event) => e.preventDefault()}
|
onSelect={(e: Event) => e.preventDefault()}
|
||||||
@@ -535,68 +527,78 @@ export function HeatmapGrid({ onSelectionChange }: HeatmapGridProps) {
|
|||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table className="text-sm block" style={{ width: "100%", minWidth: totalWidth }}>
|
<div
|
||||||
<thead className="sticky top-0 z-10 block" style={{
|
ref={tableContainerRef}
|
||||||
background: "linear-gradient(to bottom, var(--bg-elevated), var(--bg-surface))",
|
className="flex-1 min-h-0 w-full overflow-auto rounded-[12px] scroll-smooth relative"
|
||||||
borderBottom: "1px solid var(--border-strong)",
|
style={{
|
||||||
backdropFilter: "blur(10px)",
|
background: "var(--bg-surface)",
|
||||||
WebkitBackdropFilter: "blur(10px)"
|
border: "1px solid var(--border)",
|
||||||
}}>
|
boxShadow: "var(--shadow-sm)"
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
}}
|
||||||
<tr key={headerGroup.id} className="flex w-full">
|
>
|
||||||
{headerGroup.headers.map((header) => {
|
<table className="text-sm block" style={{ width: "100%", minWidth: totalWidth }}>
|
||||||
const isFlexible = header.column.id === "libelle1" || header.column.id === "ai" || header.column.id === "libelle3";
|
<thead className="sticky top-0 z-10 block" style={{
|
||||||
const isCenter = header.column.id === "totalQuantite" || header.column.id === "totalCa" || header.column.id === "totalMarge" || header.column.id.startsWith("month_") || header.column.id === "gammeInitial" || header.column.id === "score" || header.column.id === "gamme";
|
background: "linear-gradient(to bottom, var(--bg-elevated), var(--bg-surface))",
|
||||||
const size = header.getSize();
|
borderBottom: "1px solid var(--border-strong)",
|
||||||
return (
|
backdropFilter: "blur(10px)",
|
||||||
<th
|
WebkitBackdropFilter: "blur(10px)"
|
||||||
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"
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
style={{
|
<tr key={headerGroup.id} className="flex w-full">
|
||||||
width: isFlexible ? "100%" : size,
|
{headerGroup.headers.map((header) => {
|
||||||
flex: isFlexible ? `1 1 ${size}px` : `0 0 ${size}px`,
|
const isFlexible = header.column.id === "libelle1" || header.column.id === "ai" || header.column.id === "libelle3";
|
||||||
minWidth: size,
|
const isCenter = header.column.id === "totalQuantite" || header.column.id === "totalCa" || header.column.id === "totalMarge" || header.column.id.startsWith("month_") || header.column.id === "gammeInitial" || header.column.id === "score" || header.column.id === "gamme";
|
||||||
maxWidth: isFlexible ? "none" : size,
|
const size = header.getSize();
|
||||||
color: "var(--text-muted)",
|
return (
|
||||||
justifyContent: isCenter ? "center" : "flex-start"
|
<th
|
||||||
}}
|
key={header.id}
|
||||||
onClick={header.column.getToggleSortingHandler()}
|
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"
|
||||||
>
|
style={{
|
||||||
<div className={`flex items-center gap-1.5 cursor-pointer ${isCenter ? "justify-center w-full" : ""}`}>
|
width: isFlexible ? "100%" : size,
|
||||||
{flexRender(header.column.columnDef.header, header.getContext())}
|
flex: isFlexible ? `1 1 ${size}px` : `0 0 ${size}px`,
|
||||||
{header.column.getCanSort() && (
|
minWidth: size,
|
||||||
<div className="shrink-0 opacity-40">
|
maxWidth: isFlexible ? "none" : size,
|
||||||
{header.column.getIsSorted() === "asc" ? <ChevronUp className="w-3 h-3 text-emerald-500" />
|
color: "var(--text-muted)",
|
||||||
: header.column.getIsSorted() === "desc" ? <ChevronDown className="w-3 h-3 text-emerald-500" />
|
justifyContent: isCenter ? "center" : "flex-start"
|
||||||
: <ChevronsUpDown className="w-3 h-3" />
|
}}
|
||||||
}
|
onClick={header.column.getToggleSortingHandler()}
|
||||||
</div>
|
>
|
||||||
)}
|
<div className={`flex items-center gap-1.5 cursor-pointer ${isCenter ? "justify-center w-full" : ""}`}>
|
||||||
</div>
|
{flexRender(header.column.columnDef.header, header.getContext())}
|
||||||
</th>
|
{header.column.getCanSort() && (
|
||||||
);
|
<div className="shrink-0 opacity-40">
|
||||||
})}
|
{header.column.getIsSorted() === "asc" ? <ChevronUp className="w-3 h-3 text-emerald-500" />
|
||||||
</tr>
|
: header.column.getIsSorted() === "desc" ? <ChevronDown className="w-3 h-3 text-emerald-500" />
|
||||||
))}
|
: <ChevronsUpDown className="w-3 h-3" />
|
||||||
</thead>
|
}
|
||||||
<tbody key={displayDensity} style={{ height: rowVirtualizer.getTotalSize(), position: "relative", display: "block" }}>
|
</div>
|
||||||
{rowVirtualizer.getVirtualItems().map((virtualRow) => {
|
)}
|
||||||
const row = tableRows[virtualRow.index];
|
</div>
|
||||||
const isSelected = row.getIsSelected();
|
</th>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</thead>
|
||||||
|
<tbody key={displayDensity} style={{ height: rowVirtualizer.getTotalSize(), position: "relative", display: "block" }}>
|
||||||
|
{rowVirtualizer.getVirtualItems().map((virtualRow) => {
|
||||||
|
const row = tableRows[virtualRow.index];
|
||||||
|
const isSelected = row.getIsSelected();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={row.id} ref={rowVirtualizer.measureElement} style={{ position: 'absolute', top: 0, left: 0, width: '100%' }}>
|
<div key={row.id} ref={rowVirtualizer.measureElement} style={{ position: 'absolute', top: 0, left: 0, width: '100%' }}>
|
||||||
<GridRow
|
<GridRow
|
||||||
virtualRow={virtualRow}
|
virtualRow={virtualRow}
|
||||||
row={row}
|
row={row}
|
||||||
rowHeight={rowHeight}
|
rowHeight={rowHeight}
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user