import {
columnFilteringFeature,
columnVisibilityFeature,
createCoreRowModel,
createFilteredRowModel,
createPaginatedRowModel,
createSortedRowModel,
filterFn_includesString,
flexRender,
globalFilteringFeature,
rowPaginationFeature,
rowSortingFeature,
sortFn_alphanumeric,
sortFn_basic,
sortFn_text,
tableFeatures,
useTable,
type ColumnDef,
type RowData,
type SortingState,
} from "@tanstack/react-table"
import { ArrowUpDown, ChevronLeft, ChevronRight } from "lucide-react"
import { useMemo, useState } from "react"
import { Button } from "@/components/ui/button"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import { cn } from "@/lib/utils"
/* ===========================================================================
A sortable, filterable, paginated table built on TanStack Table v9.
Horizontal overflow is contained by the
wrapper, so a wide table
never makes the application itself scroll sideways (Section 21).
=========================================================================== */
const features = tableFeatures({
columnFilteringFeature,
columnVisibilityFeature,
globalFilteringFeature,
rowSortingFeature,
rowPaginationFeature,
coreRowModel: createCoreRowModel(),
sortedRowModel: createSortedRowModel(),
filteredRowModel: createFilteredRowModel(),
paginatedRowModel: createPaginatedRowModel(),
filterFns: { includesString: filterFn_includesString },
sortFns: {
alphanumeric: sortFn_alphanumeric,
basic: sortFn_basic,
text: sortFn_text,
},
})
type Features = typeof features
/** Column definition bound to this demo's feature set. */
export type DemoColumnDef = ColumnDef
export function DataTable({
columns,
data,
globalFilter,
pageSize = 8,
emptyMessage = "Nothing to show.",
onRowClick,
}: {
columns: DemoColumnDef[]
data: T[]
globalFilter?: string
pageSize?: number
emptyMessage?: string
onRowClick?: (row: T) => void
}) {
const [sorting, setSorting] = useState([])
// Stable identities: a fresh `state`/`initialState` object on every render
// makes the table store notify, re-render, and notify again.
const state = useMemo(
() => ({ sorting, globalFilter: globalFilter ?? "" }),
[sorting, globalFilter]
)
const initialState = useMemo(
() => ({ pagination: { pageIndex: 0, pageSize } }),
[pageSize]
)
const table = useTable({
features,
data,
columns,
state,
onSortingChange: setSorting,
initialState,
globalFilterFn: "includesString",
})
const rows = table.getRowModel().rows
const pageCount = table.getPageCount()
const pageIndex = table.state.pagination?.pageIndex ?? 0
return (
{table.getHeaderGroups().map((hg) => (
{hg.headers.map((header) => {
const sortable = header.column.getCanSort()
return (
{header.isPlaceholder ? null : sortable ? (
) : (
flexRender(header.column.columnDef.header, header.getContext())
)}
)
})}
))}
{rows.length === 0 ? (
{emptyMessage}
) : (
rows.map((row) => (
onRowClick(row.original) : undefined}
className={cn(onRowClick && "cursor-pointer")}
>
{row.getVisibleCells().map((cell) => (
{flexRender(cell.column.columnDef.cell, cell.getContext())}
))}
))
)}
{pageCount > 1 && (
Page {pageIndex + 1} of {pageCount} ยท{" "}
{table.getFilteredRowModel().rows.length} records
)}
)
}