"use client"; import * as React from "react"; import { Table, flexRender } from "@tanstack/react-table"; import { useVirtualizer } from "@tanstack/react-virtual"; import { DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors, DragEndEvent, } from "@dnd-kit/core"; import { arrayMove, SortableContext, horizontalListSortingStrategy, } from "@dnd-kit/sortable"; import { cn } from "@/lib/utils"; import { Loader2, ChevronRight, ChevronDown } from "lucide-react"; import { ClientTableToolbar } from "../client-table/client-table-toolbar"; import { exportToExcel } from "../client-table/export-utils"; import { ClientDataTablePagination } from "@/components/client-data-table/data-table-pagination"; import { ClientTableViewOptions } from "../client-table/client-table-view-options"; import { ClientTableColumnHeader } from "./client-table-column-header"; import { ClientTablePreset } from "./client-table-preset"; import { ClientVirtualTableProps } from "./types"; export function ClientVirtualTable({ table, isLoading = false, height = "100%", estimateRowHeight = 40, className, actions, customToolbar, enableExport = true, onExport, enableUserPreset = false, tableKey, getRowClassName, onRowClick, renderHeaderVisualFeedback, }: ClientVirtualTableProps) { // --- DnD Sensors --- const sensors = useSensors( useSensor(PointerSensor, { activationConstraint: { distance: 8, }, }), useSensor(KeyboardSensor) ); // --- Drag Handler --- const handleDragEnd = (event: DragEndEvent) => { const { active, over } = event; if (active && over && active.id !== over.id) { const activeId = active.id as string; const overId = over.id as string; const activeColumn = table.getColumn(activeId); const overColumn = table.getColumn(overId); if (activeColumn && overColumn) { const activePinState = activeColumn.getIsPinned(); const overPinState = overColumn.getIsPinned(); // If dragging between different pin states, update the pin state if (activePinState !== overPinState) { activeColumn.pin(overPinState); } // Reorder const currentOrder = table.getState().columnOrder; const oldIndex = currentOrder.indexOf(activeId); const newIndex = currentOrder.indexOf(overId); if (oldIndex !== -1 && newIndex !== -1) { table.setColumnOrder(arrayMove(currentOrder, oldIndex, newIndex)); } } } }; // --- Virtualization --- const tableContainerRef = React.useRef(null); const { rows } = table.getRowModel(); const rowVirtualizer = useVirtualizer({ count: rows.length, getScrollElement: () => tableContainerRef.current, estimateSize: () => estimateRowHeight, overscan: 10, }); const virtualRows = rowVirtualizer.getVirtualItems(); const totalSize = rowVirtualizer.getTotalSize(); const paddingTop = virtualRows.length > 0 ? virtualRows?.[0]?.start || 0 : 0; const paddingBottom = virtualRows.length > 0 ? totalSize - (virtualRows?.[virtualRows.length - 1]?.end || 0) : 0; // --- Export --- const handleExport = async () => { if (onExport) { onExport(table.getFilteredRowModel().rows.map((r) => r.original)); return; } const currentData = table.getFilteredRowModel().rows.map((row) => row.original); // Note: exportToExcel needs columns definition. table.getAllColumns() or visible columns? // Using table.getAllLeafColumns() usually. await exportToExcel(currentData, table.getAllLeafColumns(), `export-${new Date().toISOString().slice(0, 10)}.xlsx`); }; const columns = table.getVisibleLeafColumns(); const data = table.getFilteredRowModel().rows; // or just rows which is from getRowModel return (
{enableUserPreset && tableKey && ( )} } customToolbar={customToolbar} actions={actions} />
{isLoading && (
)} {table.getHeaderGroups().map((headerGroup) => ( h.id)} strategy={horizontalListSortingStrategy} > {headerGroup.headers.map((header) => ( ))} ))} {paddingTop > 0 && ( )} {virtualRows.length === 0 && !isLoading ? ( ) : ( virtualRows.map((virtualRow) => { const row = rows[virtualRow.index]; // --- Group Header Rendering --- if (row.getIsGrouped()) { const groupingColumnId = row.groupingColumnId ?? ""; const groupingValue = row.getGroupingValue(groupingColumnId); return ( ); } // --- Normal Row Rendering --- return ( onRowClick?.(row, e)} > {row.getVisibleCells().map((cell) => { const isPinned = cell.column.getIsPinned(); const isGrouped = cell.column.getIsGrouped(); const style: React.CSSProperties = { width: cell.column.getSize(), }; if (isPinned === "left") { style.position = "sticky"; style.left = `${cell.column.getStart("left")}px`; style.zIndex = 20; } else if (isPinned === "right") { style.position = "sticky"; style.right = `${cell.column.getAfter("right")}px`; style.zIndex = 20; } return ( ); })} ); }) )} {paddingBottom > 0 && ( )}
No results.
{row.getIsExpanded() ? ( ) : ( )} {groupingColumnId}: {String(groupingValue)} ({row.subRows.length})
{cell.getIsGrouped() ? null : cell.getIsAggregated() ? ( flexRender( cell.column.columnDef.aggregatedCell ?? cell.column.columnDef.cell, cell.getContext() ) ) : cell.getIsPlaceholder() ? null : ( flexRender(cell.column.columnDef.cell, cell.getContext()) )}
); }