'use client'; import { Alert, AlertDescription, AlertTitle } from '@shadcn/ui/alert'; import { Button } from '@shadcn/ui/button'; import { Checkbox } from '@shadcn/ui/checkbox'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@shadcn/ui/table'; import { ColumnDef, ColumnFiltersState, flexRender, getCoreRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, SortingState, useReactTable, VisibilityState, } from '@tanstack/react-table'; import { useSize } from 'ahooks'; import { ListRestart, Loader, RefreshCcw } from 'lucide-react'; import React, { Fragment, useEffect, useImperativeHandle, useRef, useState } from 'react'; import Empty from '../empty'; import { ColumnFilter, IParams } from './column-filter'; import { ColumnHeader } from './column-header'; import { ColumnToggle } from './column-toggle'; import { Pagination } from './pagination'; export interface ProTableProps { columns: ColumnDef[]; request: ( pagination: { page: number; size: number; }, filter: TValue, ) => Promise<{ list: TData[]; total: number }>; params?: IParams[]; header?: { title?: React.ReactNode; toolbar?: React.ReactNode | React.ReactNode[]; }; actions?: { render?: (row: TData) => React.ReactNode[]; batchRender?: (rows: TData[]) => React.ReactNode[]; }; action?: React.Ref; texts?: Partial<{ actions: string; asc: string; desc: string; hide: string; textRowsPerPage: string; textPageOf: (current: number, total: number) => string; selectedRowsText: (total: number) => string; }>; empty?: React.ReactNode; } export interface ProTableActions { refresh: () => void; reset: () => void; } export function ProTable>({ columns, request, params, header, actions, action, texts, empty, }: ProTableProps) { const [sorting, setSorting] = useState([]); const [columnFilters, setColumnFilters] = useState([]); const [columnVisibility, setColumnVisibility] = useState({}); const [rowSelection, setRowSelection] = useState({}); const [data, setData] = useState([]); const [rowCount, setRowCount] = useState(0); const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 50, }); const [loading, setLoading] = useState(false); const table = useReactTable({ data, columns: [ ...(actions?.batchRender ? [createSelectColumn()] : []), ...columns, ...(actions?.render ? ([ { id: 'actions', header: texts?.actions, cell: ({ row }) => (
{actions ?.render?.(row.original) .map((item, index) => {item})}
), enableSorting: false, enableHiding: false, }, ] as ColumnDef[]) : []), ], onPaginationChange: setPagination, onSortingChange: setSorting, onColumnFiltersChange: setColumnFilters, getCoreRowModel: getCoreRowModel(), getPaginationRowModel: getPaginationRowModel(), getSortedRowModel: getSortedRowModel(), getFilteredRowModel: getFilteredRowModel(), onColumnVisibilityChange: setColumnVisibility, onRowSelectionChange: setRowSelection, state: { sorting, columnFilters, columnVisibility, rowSelection, pagination, }, manualPagination: true, manualFiltering: true, rowCount: rowCount, }); const fetchData = async () => { setLoading(true); try { const response = await request( { page: pagination.pageIndex + 1, size: pagination.pageSize, }, Object.fromEntries(columnFilters.map((item) => [item.id, item.value])) as TValue, ); setData(response.list); setRowCount(response.total); } catch (error) { console.log('Fetch data error:', error); } finally { setLoading(false); } }; const reset = async () => { table.resetSorting(); table.resetColumnFilters(); table.resetGlobalFilter(true); table.resetColumnVisibility(); table.resetRowSelection(); table.resetPagination(); }; const ref = useRef(null); const size = useSize(ref); useImperativeHandle(action, () => ({ refresh: fetchData, reset, })); useEffect(() => { fetchData(); }, [pagination.pageIndex, pagination.pageSize, columnFilters]); const selectedRows = table.getSelectedRowModel().flatRows.map((row) => row.original); const selectedCount = selectedRows.length; return (
{params ? ( [item.id, item.value]))} /> ) : ( header?.title )}
{header?.toolbar}
{selectedCount > 0 && actions?.batchRender && ( {texts?.selectedRowsText?.(selectedCount) || `Selected ${selectedCount} rows`} {actions.batchRender(selectedRows)} )}
{table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => ( ))} ))} {table.getRowModel()?.rows?.length ? ( table.getRowModel().rows.map((row) => ( {row.getVisibleCells().map((cell) => ( {flexRender(cell.column.columnDef.cell, cell.getContext())} ))} )) ) : ( {empty || } )}
{loading && (
)}
{rowCount > 0 && ( )}
); } function createSelectColumn(): ColumnDef { return { id: 'selected', header: ({ table }) => ( table.toggleAllPageRowsSelected(!!value)} aria-label='Select all' /> ), cell: ({ row }) => ( row.toggleSelected(!!value)} aria-label='Select row' /> ), enableSorting: false, enableHiding: false, }; } function getTableHeaderClass(columnId: string) { if (columnId === 'selected') { return 'sticky left-0 z-10 bg-background shadow-[2px_0_5px_-2px_rgba(0,0,0,0.1)] [&:has([role=checkbox])]:pr-2'; } else if (columnId === 'actions') { return 'sticky right-0 z-10 text-right bg-background shadow-[-2px_0_5px_-2px_rgba(0,0,0,0.1)]'; } return 'truncate'; } function getTableCellClass(columnId: string) { if (columnId === 'selected') { return 'sticky left-0 bg-background shadow-[2px_0_5px_-2px_rgba(0,0,0,0.1)]'; } else if (columnId === 'actions') { return 'sticky right-0 bg-background shadow-[-2px_0_5px_-2px_rgba(0,0,0,0.1)]'; } return 'truncate'; }