feat: enhance DataTable component with row numbering and selection features
This commit is contained in:
parent
c0ea08f1e9
commit
1613cdfc09
@ -19,6 +19,7 @@ import {
|
|||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { Button } from "./ui/button";
|
import { Button } from "./ui/button";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
@ -34,7 +35,7 @@ import {
|
|||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Input } from "./ui/input";
|
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 {
|
interface DataTableFilterOption {
|
||||||
columnId: string;
|
columnId: string;
|
||||||
@ -55,6 +56,8 @@ interface DataTableProps<TData, TValue> {
|
|||||||
searchKey?: string;
|
searchKey?: string;
|
||||||
filters?: DataTableFilterOption[];
|
filters?: DataTableFilterOption[];
|
||||||
bulkActions?: DataTableBulkAction<TData>[];
|
bulkActions?: DataTableBulkAction<TData>[];
|
||||||
|
showNumbering?: boolean;
|
||||||
|
showSelection?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DataTable<TData, TValue>({
|
export function DataTable<TData, TValue>({
|
||||||
@ -62,6 +65,8 @@ export function DataTable<TData, TValue>({
|
|||||||
data,
|
data,
|
||||||
filters,
|
filters,
|
||||||
bulkActions,
|
bulkActions,
|
||||||
|
showNumbering = true,
|
||||||
|
showSelection = true,
|
||||||
}: DataTableProps<TData, TValue>) {
|
}: DataTableProps<TData, TValue>) {
|
||||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([]);
|
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([]);
|
||||||
@ -69,9 +74,55 @@ export function DataTable<TData, TValue>({
|
|||||||
const [rowSelection, setRowSelection] = React.useState({});
|
const [rowSelection, setRowSelection] = React.useState({});
|
||||||
const [globalFilter, setGlobalFilter] = 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({
|
const table = useReactTable({
|
||||||
data,
|
data,
|
||||||
columns,
|
columns: finalColumns,
|
||||||
getCoreRowModel: getCoreRowModel(),
|
getCoreRowModel: getCoreRowModel(),
|
||||||
getPaginationRowModel: getPaginationRowModel(),
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
onSortingChange: setSorting,
|
onSortingChange: setSorting,
|
||||||
@ -214,7 +265,14 @@ export function DataTable<TData, TValue>({
|
|||||||
<TableRow key={headerGroup.id}>
|
<TableRow key={headerGroup.id}>
|
||||||
{headerGroup.headers.map((header) => {
|
{headerGroup.headers.map((header) => {
|
||||||
return (
|
return (
|
||||||
<TableHead key={header.id}>
|
<TableHead
|
||||||
|
key={header.id}
|
||||||
|
className={
|
||||||
|
header.column.id === "select" || header.column.id === "numbering"
|
||||||
|
? "w-10 text-center"
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
>
|
||||||
{header.isPlaceholder
|
{header.isPlaceholder
|
||||||
? null
|
? null
|
||||||
: flexRender(header.column.columnDef.header, header.getContext())}
|
: flexRender(header.column.columnDef.header, header.getContext())}
|
||||||
@ -229,7 +287,14 @@ export function DataTable<TData, TValue>({
|
|||||||
table.getRowModel().rows.map((row) => (
|
table.getRowModel().rows.map((row) => (
|
||||||
<TableRow key={row.id} data-state={row.getIsSelected() && "selected"}>
|
<TableRow key={row.id} data-state={row.getIsSelected() && "selected"}>
|
||||||
{row.getVisibleCells().map((cell) => (
|
{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())}
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
))}
|
))}
|
||||||
@ -237,7 +302,7 @@ export function DataTable<TData, TValue>({
|
|||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={columns.length} className="h-24 text-center">
|
<TableCell colSpan={finalColumns.length} className="h-24 text-center">
|
||||||
Tidak ada data yang ditemukan.
|
Tidak ada data yang ditemukan.
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@ -247,27 +312,60 @@ export function DataTable<TData, TValue>({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between py-4">
|
<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.getFilteredSelectedRowModel().rows.length} dari{" "}
|
||||||
{table.getFilteredRowModel().rows.length} baris dipilih.
|
{table.getFilteredRowModel().rows.length} baris dipilih.
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-6 lg:space-x-8">
|
||||||
<Button
|
<div className="flex items-center justify-center text-sm font-medium">
|
||||||
variant="outline"
|
{table.getFilteredRowModel().rows.length > 0 ? (
|
||||||
size="sm"
|
<>
|
||||||
onClick={() => table.previousPage()}
|
Menampilkan {table.getState().pagination.pageIndex * table.getState().pagination.pageSize + 1} sampai{" "}
|
||||||
disabled={!table.getCanPreviousPage()}
|
{Math.min((table.getState().pagination.pageIndex + 1) * table.getState().pagination.pageSize, table.getFilteredRowModel().rows.length)}{" "}
|
||||||
>
|
dari {table.getFilteredRowModel().rows.length} data
|
||||||
Previous
|
</>
|
||||||
</Button>
|
) : (
|
||||||
<Button
|
"Tidak ada data"
|
||||||
variant="outline"
|
)}
|
||||||
size="sm"
|
</div>
|
||||||
onClick={() => table.nextPage()}
|
<div className="flex items-center space-x-2">
|
||||||
disabled={!table.getCanNextPage()}
|
<Button
|
||||||
>
|
variant="outline"
|
||||||
Next
|
className="hidden h-8 w-8 p-0 lg:flex"
|
||||||
</Button>
|
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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user