feat: enhance DataTable component with pagination support and meta data handling

This commit is contained in:
Yoga Pangestu 2026-04-30 20:06:11 +07:00
parent 4df6c215ad
commit f5698ee47f
3 changed files with 169 additions and 70 deletions

View File

@ -1,9 +1,9 @@
import type { import type {
ColumnDef, ColumnDef,
ColumnFiltersState, ColumnFiltersState,
PaginationState,
SortingState, SortingState,
VisibilityState, VisibilityState,
PaginationState,
} from '@tanstack/react-table'; } from '@tanstack/react-table';
import { import {
flexRender, flexRender,
@ -87,6 +87,7 @@ interface DataTableProps<TData, TValue> {
showSelection?: boolean; showSelection?: boolean;
rowSelection?: any; rowSelection?: any;
onRowSelectionChange?: (selection: any) => void; onRowSelectionChange?: (selection: any) => void;
meta?: any; // PaginatedData<TData> from Inertia
} }
export function DataTable<TData, TValue>({ export function DataTable<TData, TValue>({
@ -98,6 +99,7 @@ export function DataTable<TData, TValue>({
showSelection = true, showSelection = true,
rowSelection = {}, rowSelection = {},
onRowSelectionChange, onRowSelectionChange,
meta,
}: DataTableProps<TData, TValue>) { }: DataTableProps<TData, TValue>) {
const [sorting, setSorting] = React.useState<SortingState>([]); const [sorting, setSorting] = React.useState<SortingState>([]);
const [columnFilters, setColumnFilters] = const [columnFilters, setColumnFilters] =
@ -182,6 +184,7 @@ export function DataTable<TData, TValue>({
}, },
onPaginationChange: setPagination, onPaginationChange: setPagination,
autoResetPageIndex: false, autoResetPageIndex: false,
manualPagination: !!meta,
}); });
const selectedRows = table.getFilteredSelectedRowModel().rows; const selectedRows = table.getFilteredSelectedRowModel().rows;
@ -499,11 +502,17 @@ export function DataTable<TData, TValue>({
<div className="flex flex-col items-center justify-between gap-4 py-4 sm:flex-row"> <div className="flex flex-col items-center justify-between gap-4 py-4 sm:flex-row">
<div className="text-sm text-muted-foreground"> <div className="text-sm text-muted-foreground">
{table.getFilteredSelectedRowModel().rows.length} dari{' '} {table.getFilteredSelectedRowModel().rows.length} dari{' '}
{table.getFilteredRowModel().rows.length} baris dipilih. {meta
? meta.total
: table.getFilteredRowModel().rows.length}{' '}
baris dipilih.
</div> </div>
<div className="flex flex-col items-center gap-4 sm:flex-row sm:gap-6 lg:gap-8"> <div className="flex flex-col items-center gap-4 sm:flex-row sm:gap-6 lg:gap-8">
{!meta && (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<p className="text-sm font-medium">Baris per halaman</p> <p className="text-sm font-medium">
Baris per halaman
</p>
<Select <Select
value={`${table.getState().pagination.pageSize}`} value={`${table.getState().pagination.pageSize}`}
onValueChange={(value) => { onValueChange={(value) => {
@ -529,8 +538,14 @@ export function DataTable<TData, TValue>({
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
)}
<div className="flex items-center justify-center text-sm font-medium"> <div className="flex items-center justify-center text-sm font-medium">
{table.getFilteredRowModel().rows.length > 0 ? ( {meta ? (
<>
Menampilkan {meta.from || 0} sampai{' '}
{meta.to || 0} dari {meta.total} data
</>
) : table.getFilteredRowModel().rows.length > 0 ? (
<> <>
Menampilkan{' '} Menampilkan{' '}
{table.getState().pagination.pageIndex * {table.getState().pagination.pageIndex *
@ -550,6 +565,77 @@ export function DataTable<TData, TValue>({
'Tidak ada data' 'Tidak ada data'
)} )}
</div> </div>
{meta ? (() => {
const { current_page, last_page, prev_page_url, next_page_url } = meta;
const leftPages = [];
for (let i = current_page - 3; i < current_page; i++) {
if (i > 0) leftPages.push(i);
}
const rightPages = [];
for (let i = current_page + 1; i <= current_page + 2; i++) {
if (i <= last_page) rightPages.push(i);
}
const handlePageChange = (page: number) => {
const url = new URL(window.location.href);
url.searchParams.set('page', page.toString());
import('@inertiajs/react').then(({ router }) => {
router.get(url.toString(), {}, { preserveState: true, preserveScroll: true });
});
};
return (
<div className="flex items-center space-x-2">
<Button
variant="outline"
className="h-8 w-8 p-0"
onClick={() => {
if (prev_page_url) {
import('@inertiajs/react').then(({ router }) => {
router.get(prev_page_url, {}, { preserveState: true, preserveScroll: true });
});
}
}}
disabled={!prev_page_url}
>
<ChevronLeft className="h-4 w-4" />
</Button>
{leftPages.map(p => (
<Button key={p} variant="outline" className="h-8 px-3" onClick={() => handlePageChange(p)}>
{p}
</Button>
))}
<Button variant="outline" className="h-8 px-3 border-none bg-transparent shadow-none" disabled>
...
</Button>
{rightPages.map(p => (
<Button key={p} variant="outline" className="h-8 px-3" onClick={() => handlePageChange(p)}>
{p}
</Button>
))}
<Button
variant="outline"
className="h-8 w-8 p-0"
onClick={() => {
if (next_page_url) {
import('@inertiajs/react').then(({ router }) => {
router.get(next_page_url, {}, { preserveState: true, preserveScroll: true });
});
}
}}
disabled={!next_page_url}
>
<ChevronRight className="h-4 w-4" />
</Button>
</div>
);
})() : (
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<Button <Button
variant="outline" variant="outline"
@ -557,7 +643,6 @@ export function DataTable<TData, TValue>({
onClick={() => table.setPageIndex(0)} onClick={() => table.setPageIndex(0)}
disabled={!table.getCanPreviousPage()} disabled={!table.getCanPreviousPage()}
> >
<span className="sr-only">Halaman pertama</span>
<ChevronsLeft className="h-4 w-4" /> <ChevronsLeft className="h-4 w-4" />
</Button> </Button>
<Button <Button
@ -566,7 +651,6 @@ export function DataTable<TData, TValue>({
onClick={() => table.previousPage()} onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()} disabled={!table.getCanPreviousPage()}
> >
<span className="sr-only">Halaman sebelumnya</span>
<ChevronLeft className="h-4 w-4" /> <ChevronLeft className="h-4 w-4" />
</Button> </Button>
<Button <Button
@ -575,7 +659,6 @@ export function DataTable<TData, TValue>({
onClick={() => table.nextPage()} onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()} disabled={!table.getCanNextPage()}
> >
<span className="sr-only">Halaman selanjutnya</span>
<ChevronRight className="h-4 w-4" /> <ChevronRight className="h-4 w-4" />
</Button> </Button>
<Button <Button
@ -586,10 +669,10 @@ export function DataTable<TData, TValue>({
} }
disabled={!table.getCanNextPage()} disabled={!table.getCanNextPage()}
> >
<span className="sr-only">Halaman terakhir</span>
<ChevronsRight className="h-4 w-4" /> <ChevronsRight className="h-4 w-4" />
</Button> </Button>
</div> </div>
)}
</div> </div>
</div> </div>
</div> </div>

View File

@ -12,3 +12,4 @@ export type * from './system-log';
export type * from './dashboard'; export type * from './dashboard';
export type * from './role'; export type * from './role';
export type * from './permission'; export type * from './permission';
export type * from './pagination';

View File

@ -0,0 +1,15 @@
export interface PaginatedData<T> {
current_page: number;
data: T[];
first_page_url: string;
from: number;
last_page: number;
last_page_url: string;
links: { url: string | null; label: string; active: boolean }[];
next_page_url: string | null;
path: string;
per_page: number;
prev_page_url: string | null;
to: number;
total: number;
}