feat: enhance DataTable component with row numbering and selection features

This commit is contained in:
Yoga Pangestu 2026-04-14 21:55:46 +07:00
parent c0ea08f1e9
commit 1613cdfc09

View File

@ -19,6 +19,7 @@ import {
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Checkbox } from "@/components/ui/checkbox";
import { Button } from "./ui/button";
import {
DropdownMenu,
@ -34,7 +35,7 @@ import {
} from "@/components/ui/dropdown-menu";
import React from "react";
import { Input } from "./ui/input";
import { Settings2, ChevronDown, ListFilter, Trash2 } from "lucide-react";
import { Settings2, ChevronDown, ListFilter, Trash2, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from "lucide-react";
interface DataTableFilterOption {
columnId: string;
@ -55,6 +56,8 @@ interface DataTableProps<TData, TValue> {
searchKey?: string;
filters?: DataTableFilterOption[];
bulkActions?: DataTableBulkAction<TData>[];
showNumbering?: boolean;
showSelection?: boolean;
}
export function DataTable<TData, TValue>({
@ -62,6 +65,8 @@ export function DataTable<TData, TValue>({
data,
filters,
bulkActions,
showNumbering = true,
showSelection = true,
}: DataTableProps<TData, TValue>) {
const [sorting, setSorting] = React.useState<SortingState>([]);
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([]);
@ -69,9 +74,55 @@ export function DataTable<TData, TValue>({
const [rowSelection, setRowSelection] = React.useState({});
const [globalFilter, setGlobalFilter] = React.useState("");
const finalColumns = React.useMemo(() => {
const computedColumns = [...columns];
if (showNumbering) {
const numberingColumn: ColumnDef<TData, TValue> = {
id: "numbering",
header: "No.",
cell: ({ row, table }) => {
const pageIndex = table.getState().pagination.pageIndex;
const pageSize = table.getState().pagination.pageSize;
return pageIndex * pageSize + row.index + 1;
},
enableHiding: false,
};
computedColumns.unshift(numberingColumn);
}
if (showSelection) {
const selectColumn: ColumnDef<TData, TValue> = {
id: "select",
header: ({ table }) => (
<Checkbox
checked={
table.getIsAllPageRowsSelected() ||
(table.getIsSomePageRowsSelected() && "indeterminate")
}
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
aria-label="Select all"
/>
),
cell: ({ row }) => (
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(!!value)}
aria-label="Select row"
/>
),
enableSorting: false,
enableHiding: false,
};
computedColumns.unshift(selectColumn);
}
return computedColumns;
}, [columns, showNumbering, showSelection]);
const table = useReactTable({
data,
columns,
columns: finalColumns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
onSortingChange: setSorting,
@ -214,7 +265,14 @@ export function DataTable<TData, TValue>({
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
<TableHead
key={header.id}
className={
header.column.id === "select" || header.column.id === "numbering"
? "w-10 text-center"
: ""
}
>
{header.isPlaceholder
? null
: flexRender(header.column.columnDef.header, header.getContext())}
@ -229,7 +287,14 @@ export function DataTable<TData, TValue>({
table.getRowModel().rows.map((row) => (
<TableRow key={row.id} data-state={row.getIsSelected() && "selected"}>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
<TableCell
key={cell.id}
className={
cell.column.id === "select" || cell.column.id === "numbering"
? "w-10 text-center"
: ""
}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
@ -237,7 +302,7 @@ export function DataTable<TData, TValue>({
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
<TableCell colSpan={finalColumns.length} className="h-24 text-center">
Tidak ada data yang ditemukan.
</TableCell>
</TableRow>
@ -247,27 +312,60 @@ export function DataTable<TData, TValue>({
</div>
<div className="flex items-center justify-between py-4">
<div className="text-sm text-muted-foreground">
<div className="flex-1 text-sm text-muted-foreground">
{table.getFilteredSelectedRowModel().rows.length} dari{" "}
{table.getFilteredRowModel().rows.length} baris dipilih.
</div>
<div className="flex items-center space-x-2">
<Button
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Previous
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Next
</Button>
<div className="flex items-center space-x-6 lg:space-x-8">
<div className="flex items-center justify-center text-sm font-medium">
{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"
)}
</div>
<div className="flex items-center space-x-2">
<Button
variant="outline"
className="hidden h-8 w-8 p-0 lg:flex"
onClick={() => table.setPageIndex(0)}
disabled={!table.getCanPreviousPage()}
>
<span className="sr-only">Halaman pertama</span>
<ChevronsLeft className="h-4 w-4" />
</Button>
<Button
variant="outline"
className="h-8 w-8 p-0"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
<span className="sr-only">Halaman sebelumnya</span>
<ChevronLeft className="h-4 w-4" />
</Button>
<Button
variant="outline"
className="h-8 w-8 p-0"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
<span className="sr-only">Halaman selanjutnya</span>
<ChevronRight className="h-4 w-4" />
</Button>
<Button
variant="outline"
className="hidden h-8 w-8 p-0 lg:flex"
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
disabled={!table.getCanNextPage()}
>
<span className="sr-only">Halaman terakhir</span>
<ChevronsRight className="h-4 w-4" />
</Button>
</div>
</div>
</div>
</div>