"use client"; import { ColumnDef, flexRender, getCoreRowModel, useReactTable, getPaginationRowModel, SortingState, getSortedRowModel, ColumnFiltersState, getFilteredRowModel } from "@tanstack/react-table"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Button } from "@app/components/ui/button"; import { useEffect, useMemo, useState } from "react"; import { Input } from "@app/components/ui/input"; import { DataTablePagination } from "@app/components/DataTablePagination"; import { Plus, Search, RefreshCw, Filter, X, Download, ChevronRight, ChevronDown } from "lucide-react"; import { Card, CardContent, CardHeader, CardTitle } from "@app/components/ui/card"; import { Tabs, TabsList, TabsTrigger } from "@app/components/ui/tabs"; import { useTranslations } from "next-intl"; import { DateRangePicker, DateTimeValue } from "@app/components/DateTimePicker"; const STORAGE_KEYS = { PAGE_SIZE: "datatable-page-size", getTablePageSize: (tableId?: string) => tableId ? `${tableId}-size` : STORAGE_KEYS.PAGE_SIZE }; export const getStoredPageSize = (tableId?: string, defaultSize = 20): number => { if (typeof window === "undefined") return defaultSize; try { const key = STORAGE_KEYS.getTablePageSize(tableId); const stored = localStorage.getItem(key); if (stored) { const parsed = parseInt(stored, 10); // Validate that it's a reasonable page size if (parsed > 0 && parsed <= 1000) { return parsed; } } } catch (error) { console.warn("Failed to read page size from localStorage:", error); } return defaultSize; }; export const setStoredPageSize = (pageSize: number, tableId?: string): void => { if (typeof window === "undefined") return; try { const key = STORAGE_KEYS.getTablePageSize(tableId); localStorage.setItem(key, pageSize.toString()); } catch (error) { console.warn("Failed to save page size to localStorage:", error); } }; type TabFilter = { id: string; label: string; filterFn: (row: any) => boolean; }; type DataTableProps = { columns: ColumnDef[]; data: TData[]; title?: string; addButtonText?: string; onRefresh?: () => void; onExport?: () => void; isExporting?: boolean; isRefreshing?: boolean; searchPlaceholder?: string; searchColumn?: string; defaultSort?: { id: string; desc: boolean; }; tabs?: TabFilter[]; defaultTab?: string; disabled?: boolean; onDateRangeChange?: ( startDate: DateTimeValue, endDate: DateTimeValue ) => void; dateRange?: { start: DateTimeValue; end: DateTimeValue; }; // Server-side pagination props totalCount?: number; pageSize: number; currentPage?: number; onPageChange?: (page: number) => void; onPageSizeChange?: (pageSize: number) => void; isLoading?: boolean; // Row expansion props expandable?: boolean; renderExpandedRow?: (row: TData) => React.ReactNode; }; export function LogDataTable({ columns, data, title, onRefresh, isRefreshing, onExport, isExporting, // searchPlaceholder = "Search...", // searchColumn = "name", defaultSort, tabs, defaultTab, onDateRangeChange, pageSize, dateRange, totalCount, currentPage = 0, onPageChange, onPageSizeChange: onPageSizeChangeProp, isLoading = false, expandable = false, disabled=false, renderExpandedRow }: DataTableProps) { const t = useTranslations(); const [sorting, setSorting] = useState( defaultSort ? [defaultSort] : [] ); const [columnFilters, setColumnFilters] = useState([]); const [globalFilter, setGlobalFilter] = useState([]); const [activeTab, setActiveTab] = useState( defaultTab || tabs?.[0]?.id || "" ); const [startDate, setStartDate] = useState( dateRange?.start || {} ); const [endDate, setEndDate] = useState(dateRange?.end || {}); const [expandedRows, setExpandedRows] = useState>(new Set()); // Sync internal date state with external dateRange prop useEffect(() => { if (dateRange?.start) { setStartDate(dateRange.start); } if (dateRange?.end) { setEndDate(dateRange.end); } }, [dateRange?.start, dateRange?.end]); // Apply tab filter to data const filteredData = useMemo(() => { // If disabled, return empty array to prevent data loading if (disabled) { return []; } if (!tabs || activeTab === "") { return data; } const activeTabFilter = tabs.find((tab) => tab.id === activeTab); if (!activeTabFilter) { return data; } return data.filter(activeTabFilter.filterFn); }, [data, tabs, activeTab, disabled]); // Toggle row expansion const toggleRowExpansion = (rowId: string) => { setExpandedRows((prev) => { const newSet = new Set(prev); if (newSet.has(rowId)) { newSet.delete(rowId); } else { newSet.add(rowId); } return newSet; }); }; // Determine if using server-side pagination const isServerPagination = totalCount !== undefined; // Create columns with expansion column if expandable const enhancedColumns = useMemo(() => { if (!expandable) { return columns; } const expansionColumn: ColumnDef = { id: "expand", header: () => null, cell: ({ row }) => { const isExpanded = expandedRows.has(row.id); return ( ); }, size: 40 }; return [expansionColumn, ...columns]; }, [columns, expandable, expandedRows, toggleRowExpansion, disabled]); const table = useReactTable({ data: filteredData, columns: enhancedColumns, getCoreRowModel: getCoreRowModel(), // Only use client-side pagination if totalCount is not provided ...(isServerPagination ? {} : { getPaginationRowModel: getPaginationRowModel() }), onSortingChange: setSorting, getSortedRowModel: getSortedRowModel(), onColumnFiltersChange: setColumnFilters, getFilteredRowModel: getFilteredRowModel(), onGlobalFilterChange: setGlobalFilter, // Configure pagination state ...(isServerPagination ? { manualPagination: true, pageCount: totalCount ? Math.ceil(totalCount / pageSize) : 0 } : {}), initialState: { pagination: { pageSize: pageSize, pageIndex: currentPage } }, state: { sorting, columnFilters, globalFilter, pagination: { pageSize: pageSize, pageIndex: currentPage } } }); // useEffect(() => { // const currentPageSize = table.getState().pagination.pageSize; // if (currentPageSize !== pageSize) { // table.setPageSize(pageSize); // // Persist to localStorage if enabled // if (persistPageSize) { // setStoredPageSize(pageSize, tableId); // } // } // }, [pageSize, table, persistPageSize, tableId]); // Update table page index when currentPage prop changes (server pagination) useEffect(() => { if (isServerPagination) { const currentPageIndex = table.getState().pagination.pageIndex; if (currentPageIndex !== currentPage) { table.setPageIndex(currentPage); } } }, [currentPage, table, isServerPagination]); const handleTabChange = (value: string) => { if (disabled) return; setActiveTab(value); // Reset to first page when changing tabs table.setPageIndex(0); }; // Enhanced pagination component that updates our local state const handlePageSizeChange = (newPageSize: number) => { if (disabled) return; // setPageSize(newPageSize); table.setPageSize(newPageSize); // Persist immediately when changed // if (persistPageSize) { // setStoredPageSize(newPageSize, tableId); // } // For server pagination, notify parent component if (isServerPagination && onPageSizeChangeProp) { onPageSizeChangeProp(newPageSize); } }; // Handle page changes for server pagination const handlePageChange = (newPageIndex: number) => { if (disabled) return; if (isServerPagination && onPageChange) { onPageChange(newPageIndex); } }; const handleDateRangeChange = ( start: DateTimeValue, end: DateTimeValue ) => { if (disabled) return; setStartDate(start); setEndDate(end); onDateRangeChange?.(start, end); }; return (
{/*
table.setGlobalFilter( String(e.target.value) ) } className="w-full pl-8 m-0" />
*/}
{onRefresh && ( )} {onExport && ( )}
{table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => ( {header.isPlaceholder ? null : flexRender( header.column.columnDef .header, header.getContext() )} ))} ))} {table.getRowModel().rows?.length ? ( table.getRowModel().rows.map((row) => { const isExpanded = expandable && expandedRows.has(row.id); return [ expandable && !disabled ? toggleRowExpansion( row.id ) : undefined } className="text-xs" // made smaller > {row .getVisibleCells() .map((cell) => { const originalRow = row.original as any; const actionValue = originalRow?.action; let className = ""; if ( typeof actionValue === "boolean" ) { className = actionValue ? "bg-green-100 dark:bg-green-900/50" : "bg-red-100 dark:bg-red-900/50"; } return ( {flexRender( cell.column .columnDef .cell, cell.getContext() )} ); })} , isExpanded && renderExpandedRow && ( {renderExpandedRow( row.original )} ) ].filter(Boolean); }).flat() ) : ( No results found. )}
); }