Files
nats-console/apps/web/components/ui/data-table.tsx
T
2025-12-04 22:31:48 +05:30

206 lines
6.9 KiB
TypeScript

'use client';
import { useState } from 'react';
import {
ColumnDef,
ColumnFiltersState,
SortingState,
VisibilityState,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from '@tanstack/react-table';
import { ChevronDown, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, Search } from 'lucide-react';
import { Button } from './button';
import { Input } from './input';
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[];
data: TData[];
searchColumn?: string;
searchPlaceholder?: string;
pageSize?: number;
showPagination?: boolean;
onRowClick?: (row: TData) => void;
}
export function DataTable<TData, TValue>({
columns,
data,
searchColumn,
searchPlaceholder = 'Search...',
pageSize = 10,
showPagination = true,
onRowClick,
}: DataTableProps<TData, TValue>) {
const [sorting, setSorting] = useState<SortingState>([]);
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
const [rowSelection, setRowSelection] = useState({});
const table = useReactTable({
data,
columns,
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
onColumnVisibilityChange: setColumnVisibility,
onRowSelectionChange: setRowSelection,
initialState: {
pagination: { pageSize },
},
state: {
sorting,
columnFilters,
columnVisibility,
rowSelection,
},
});
return (
<div className="space-y-4">
{searchColumn && (
<div className="flex items-center gap-4">
<div className="relative flex-1 max-w-sm">
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<Input
placeholder={searchPlaceholder}
value={(table.getColumn(searchColumn)?.getFilterValue() as string) ?? ''}
onChange={(event) =>
table.getColumn(searchColumn)?.setFilterValue(event.target.value)
}
className="pl-9"
/>
</div>
</div>
)}
<div className="border">
<table className="w-full">
<thead className="bg-muted/50">
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<th
key={header.id}
className="text-left p-4 font-medium text-sm"
style={{ width: header.getSize() !== 150 ? header.getSize() : undefined }}
>
{header.isPlaceholder ? null : (
<div
className={
header.column.getCanSort()
? 'flex items-center gap-2 cursor-pointer select-none hover:text-foreground'
: ''
}
onClick={header.column.getToggleSortingHandler()}
>
{flexRender(header.column.columnDef.header, header.getContext())}
{header.column.getCanSort() && (
<ChevronDown
className={`h-4 w-4 transition-transform ${
header.column.getIsSorted() === 'asc' ? 'rotate-180' : ''
} ${header.column.getIsSorted() ? 'opacity-100' : 'opacity-0'}`}
/>
)}
</div>
)}
</th>
))}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<tr
key={row.id}
data-state={row.getIsSelected() && 'selected'}
className={`border-t hover:bg-muted/30 ${
onRowClick ? 'cursor-pointer' : ''
}`}
onClick={() => onRowClick?.(row.original)}
>
{row.getVisibleCells().map((cell) => (
<td key={cell.id} className="p-4">
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
</tr>
))
) : (
<tr>
<td colSpan={columns.length} className="h-24 text-center text-muted-foreground">
No results.
</td>
</tr>
)}
</tbody>
</table>
</div>
{showPagination && (
<div className="flex items-center justify-between px-2">
<div className="text-sm text-muted-foreground">
{table.getFilteredSelectedRowModel().rows.length > 0 && (
<span>
{table.getFilteredSelectedRowModel().rows.length} of{' '}
{table.getFilteredRowModel().rows.length} row(s) selected.
</span>
)}
{table.getFilteredSelectedRowModel().rows.length === 0 && (
<span>
Showing {table.getRowModel().rows.length} of{' '}
{table.getFilteredRowModel().rows.length} row(s).
</span>
)}
</div>
<div className="flex items-center gap-2">
<Button
variant="outline"
size="sm"
onClick={() => table.setPageIndex(0)}
disabled={!table.getCanPreviousPage()}
>
<ChevronsLeft className="h-4 w-4" />
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
<ChevronLeft className="h-4 w-4" />
</Button>
<span className="text-sm">
Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount()}
</span>
<Button
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
<ChevronRight className="h-4 w-4" />
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
disabled={!table.getCanNextPage()}
>
<ChevronsRight className="h-4 w-4" />
</Button>
</div>
</div>
)}
</div>
);
}