import type { ColumnDef, ColumnFiltersState, PaginationState, SortingState, VisibilityState, } from '@tanstack/react-table'; import { flexRender, getCoreRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable, } from '@tanstack/react-table'; import { Checkbox } from '@/components/ui/checkbox'; import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { ChevronDown, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, ListFilter, Settings2, X, } from 'lucide-react'; import React from 'react'; import { Empty, EmptyDescription, EmptyHeader, EmptyTitle, } from '@/components/ui/empty'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { Badge } from './ui/badge'; import { Button } from './ui/button'; import { Input } from './ui/input'; interface DataTableFilterOption { columnId: string; title: string; options: { label: string; value: string }[]; } export interface DataTableBulkAction { label: string; onClick: (rows: TData[]) => void; icon?: React.ElementType; variant?: 'default' | 'destructive'; } interface DataTableProps { columns: ColumnDef[]; data: TData[]; searchKey?: string; filters?: DataTableFilterOption[]; bulkActions?: DataTableBulkAction[]; showNumbering?: boolean; showSelection?: boolean; rowSelection?: any; onRowSelectionChange?: (selection: any) => void; meta?: any; // PaginatedData from Inertia } export function DataTable({ columns, data, filters, bulkActions, showNumbering = true, showSelection = true, rowSelection = {}, onRowSelectionChange, meta, }: DataTableProps) { const [sorting, setSorting] = React.useState([]); const [columnFilters, setColumnFilters] = React.useState([]); const [columnVisibility, setColumnVisibility] = React.useState({}); const [globalFilter, setGlobalFilter] = React.useState(''); const [pagination, setPagination] = React.useState({ pageIndex: 0, pageSize: 10, }); // Reset to page 1 when filters or search change React.useEffect(() => { setPagination((prev) => ({ ...prev, pageIndex: 0 })); }, [globalFilter, columnFilters]); const finalColumns = React.useMemo(() => { const computedColumns = [...columns]; if (showNumbering) { const numberingColumn: ColumnDef = { id: 'numbering', header: 'No.', cell: ({ row }) => row.index + 1, enableHiding: false, }; computedColumns.unshift(numberingColumn); } if (showSelection) { const selectColumn: ColumnDef = { id: 'select', header: ({ table }) => ( table.toggleAllPageRowsSelected(!!value) } aria-label="Select all" /> ), cell: ({ row }) => ( row.toggleSelected(!!value)} aria-label="Select row" /> ), enableSorting: false, enableHiding: false, }; computedColumns.unshift(selectColumn); } return computedColumns; }, [columns, showNumbering, showSelection]); const table = useReactTable({ data, columns: finalColumns, getCoreRowModel: getCoreRowModel(), getPaginationRowModel: getPaginationRowModel(), onSortingChange: setSorting, getSortedRowModel: getSortedRowModel(), onColumnFiltersChange: setColumnFilters, getFilteredRowModel: getFilteredRowModel(), onColumnVisibilityChange: setColumnVisibility, onRowSelectionChange: onRowSelectionChange, onGlobalFilterChange: setGlobalFilter, state: { sorting, columnFilters, columnVisibility, rowSelection, globalFilter, pagination, }, onPaginationChange: setPagination, autoResetPageIndex: false, manualPagination: !!meta, }); const selectedRows = table.getFilteredSelectedRowModel().rows; return (
setGlobalFilter(event.target.value) } className="max-w-sm" /> {columnFilters.length > 0 && (
{columnFilters.map((filter) => { const filterDef = filters?.find( (f) => f.columnId === filter.id, ); const option = filterDef?.options.find( (o) => String(o.value) === String(filter.value), ); const label = option ? option.label : String(filter.value); return ( {filterDef?.title || filter.id}: {label} ); })}
)}
{selectedRows.length > 0 && bulkActions && ( {bulkActions.map((action, i) => ( action.onClick( selectedRows.map( (r) => r.original, ), ) } className={ action.variant === 'destructive' ? 'text-red-600 focus:text-red-600' : '' } > {action.icon && ( )} {action.label} ))} )} {filters && filters.length > 0 && ( Filter Berdasarkan {filters.map((filter) => { const column = table.getColumn( filter.columnId, ); if (!column) { return null; } return ( {filter.title} e.preventDefault() } onClick={() => column.setFilterValue( undefined, ) } > Semua {filter.options.map( (option) => ( e.preventDefault() } onClick={() => { if ( option.value === 'true' ) { column.setFilterValue( true, ); } else if ( option.value === 'false' ) { column.setFilterValue( false, ); } else { column.setFilterValue( option.value, ); } }} > {option.label} ), )} ); })} )} Tampilan Kolom {table .getAllColumns() .filter((column) => column.getCanHide()) .map((column) => { return ( column.toggleVisibility(!!value) } onSelect={(e) => e.preventDefault()} > {(column.columnDef.meta as any) ?.title || (typeof column.columnDef .header === 'string' ? column.columnDef.header : String(column.id).replace( /_/g, ' ', ))} ); })}
{table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => { return ( {header.isPlaceholder ? null : flexRender( header.column.columnDef .header, header.getContext(), )} ); })} ))} {table.getRowModel().rows?.length ? ( table.getRowModel().rows.map((row) => ( {row.getVisibleCells().map((cell) => ( {flexRender( cell.column.columnDef.cell, cell.getContext(), )} ))} )) ) : ( Ooops... Tidak ada data yang ditemukan. )}
{table.getFilteredSelectedRowModel().rows.length} dari{' '} {meta ? meta.total : table.getFilteredRowModel().rows.length}{' '} baris dipilih.
{!meta && (

Baris per halaman

)}
{meta ? ( <> Menampilkan {meta.from || 0} sampai{' '} {meta.to || 0} dari {meta.total} data ) : table.getFilteredRowModel().rows.length > 0 ? ( <> Menampilkan{' '} {table.getState().pagination.pageIndex * table.getState().pagination.pageSize + 1}{' '} sampai{' '} {Math.min( (table.getState().pagination.pageIndex + 1) * table.getState().pagination.pageSize, table.getFilteredRowModel().rows.length, )}{' '} dari {table.getFilteredRowModel().rows.length}{' '} data ) : ( 'Tidak ada data' )}
{meta ? (() => { const { current_page, last_page, prev_page_url, next_page_url } = meta; const leftPages = []; for (let i = current_page - 3; i < current_page; i++) { if (i > 0) leftPages.push(i); } const rightPages = []; for (let i = current_page + 1; i <= current_page + 2; i++) { if (i <= last_page) rightPages.push(i); } const handlePageChange = (page: number) => { const url = new URL(window.location.href); url.searchParams.set('page', page.toString()); import('@inertiajs/react').then(({ router }) => { router.get(url.toString(), {}, { preserveState: true, preserveScroll: true }); }); }; return (
{leftPages.map(p => ( ))} {rightPages.map(p => ( ))}
); })() : (
)}
); }