import * as React from "react"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; import type { DataTableFilterableColumn, DataTableSearchableColumn, } from "@/types"; import { flexRender, getCoreRowModel, getFacetedRowModel, getFacetedUniqueValues, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable, type ColumnDef, type ColumnFiltersState, type PaginationState, type SortingState, type VisibilityState, } from "@tanstack/react-table"; import { useDebounce } from "@/hooks/use-debounce"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { DataTablePagination } from "@/components/task-table-legacy/data-table-pagination"; import { DataTableToolbar } from "@/components/task-table-legacy/data-table-toolbar"; interface DataTableProps { columns: ColumnDef[]; data: TData[]; pageCount: number; filterableColumns?: DataTableFilterableColumn[]; searchableColumns?: DataTableSearchableColumn[]; } export function DataTable({ columns, data, pageCount, filterableColumns = [], searchableColumns = [], }: DataTableProps) { const router = useRouter(); const pathname = usePathname(); const searchParams = useSearchParams(); // Search params const page = searchParams?.get("page") ?? "1"; const per_page = searchParams?.get("per_page") ?? "10"; const sort = searchParams?.get("sort"); const [column, order] = sort?.split(".") ?? []; // Create query string const createQueryString = React.useCallback( (params: Record) => { const newSearchParams = new URLSearchParams(searchParams?.toString()); for (const [key, value] of Object.entries(params)) { if (value === null) { newSearchParams.delete(key); } else { newSearchParams.set(key, String(value)); } } return newSearchParams.toString(); }, [searchParams] ); // Table states const [rowSelection, setRowSelection] = React.useState({}); const [columnVisibility, setColumnVisibility] = React.useState({}); const [columnFilters, setColumnFilters] = React.useState( [] ); // Handle server-side pagination const [{ pageIndex, pageSize }, setPagination] = React.useState({ pageIndex: Number(page) - 1, pageSize: Number(per_page), }); const pagination = React.useMemo( () => ({ pageIndex, pageSize, }), [pageIndex, pageSize] ); React.useEffect(() => { setPagination({ pageIndex: Number(page) - 1, pageSize: Number(per_page), }); }, [page, per_page]); React.useEffect(() => { router.push( `${pathname}?${createQueryString({ page: pageIndex + 1, per_page: pageSize, })}` ); // eslint-disable-next-line react-hooks/exhaustive-deps }, [pageIndex, pageSize]); // Handle server-side sorting const [sorting, setSorting] = React.useState([ { id: column ?? "", desc: order === "desc", }, ]); React.useEffect(() => { router.push( `${pathname}?${createQueryString({ page, sort: sorting[0]?.id ? `${sorting[0]?.id}.${sorting[0]?.desc ? "desc" : "asc"}` : null, })}` ); // eslint-disable-next-line react-hooks/exhaustive-deps }, [sorting]); // Handle server-side filtering const debouncedSearchableColumnFilters = JSON.parse( useDebounce( JSON.stringify( columnFilters.filter((filter) => { return searchableColumns.find((column) => column.id === filter.id); }) ), 500 ) ) as ColumnFiltersState; const filterableColumnFilters = columnFilters.filter((filter) => { return filterableColumns.find((column) => column.id === filter.id); }); React.useEffect(() => { for (const column of debouncedSearchableColumnFilters) { if (typeof column.value === "string") { router.push( `${pathname}?${createQueryString({ page: 1, [column.id]: typeof column.value === "string" ? column.value : null, })}` ); } } for (const key of searchParams.keys()) { if ( searchableColumns.find((column) => column.id === key) && !debouncedSearchableColumnFilters.find((column) => column.id === key) ) { router.push( `${pathname}?${createQueryString({ page: 1, [key]: null, })}` ); } } // eslint-disable-next-line react-hooks/exhaustive-deps }, [JSON.stringify(debouncedSearchableColumnFilters)]); React.useEffect(() => { for (const column of filterableColumnFilters) { if (typeof column.value === "object" && Array.isArray(column.value)) { router.push( `${pathname}?${createQueryString({ page: 1, [column.id]: column.value.join("."), })}` ); } } for (const key of searchParams.keys()) { if ( filterableColumns.find((column) => column.id === key) && !filterableColumnFilters.find((column) => column.id === key) ) { router.push( `${pathname}?${createQueryString({ page: 1, [key]: null, })}` ); } } // eslint-disable-next-line react-hooks/exhaustive-deps }, [JSON.stringify(filterableColumnFilters)]); const table = useReactTable({ data, columns, pageCount: pageCount ?? -1, state: { pagination, sorting, columnVisibility, rowSelection, columnFilters, }, enableRowSelection: true, onRowSelectionChange: setRowSelection, onPaginationChange: setPagination, onSortingChange: setSorting, onColumnFiltersChange: setColumnFilters, onColumnVisibilityChange: setColumnVisibility, getCoreRowModel: getCoreRowModel(), getFilteredRowModel: getFilteredRowModel(), getPaginationRowModel: getPaginationRowModel(), getSortedRowModel: getSortedRowModel(), getFacetedRowModel: getFacetedRowModel(), getFacetedUniqueValues: getFacetedUniqueValues(), manualPagination: true, manualSorting: true, manualFiltering: true, }); return (
{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() )} ))} )) ) : ( No results. )}
); }