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 {
ColumnDef,
ColumnFiltersState,
PaginationState,
SortingState,
VisibilityState,
PaginationState,
} from '@tanstack/react-table';
import {
flexRender,
@ -87,6 +87,7 @@ interface DataTableProps<TData, TValue> {
showSelection?: boolean;
rowSelection?: any;
onRowSelectionChange?: (selection: any) => void;
meta?: any; // PaginatedData<TData> from Inertia
}
export function DataTable<TData, TValue>({
@ -98,6 +99,7 @@ export function DataTable<TData, TValue>({
showSelection = true,
rowSelection = {},
onRowSelectionChange,
meta,
}: DataTableProps<TData, TValue>) {
const [sorting, setSorting] = React.useState<SortingState>([]);
const [columnFilters, setColumnFilters] =
@ -182,6 +184,7 @@ export function DataTable<TData, TValue>({
},
onPaginationChange: setPagination,
autoResetPageIndex: false,
manualPagination: !!meta,
});
const selectedRows = table.getFilteredSelectedRowModel().rows;
@ -499,38 +502,50 @@ export function DataTable<TData, TValue>({
<div className="flex flex-col items-center justify-between gap-4 py-4 sm:flex-row">
<div className="text-sm text-muted-foreground">
{table.getFilteredSelectedRowModel().rows.length} dari{' '}
{table.getFilteredRowModel().rows.length} baris dipilih.
{meta
? meta.total
: table.getFilteredRowModel().rows.length}{' '}
baris dipilih.
</div>
<div className="flex flex-col items-center gap-4 sm:flex-row sm:gap-6 lg:gap-8">
<div className="flex items-center gap-2">
<p className="text-sm font-medium">Baris per halaman</p>
<Select
value={`${table.getState().pagination.pageSize}`}
onValueChange={(value) => {
table.setPageSize(Number(value));
}}
>
<SelectTrigger className="h-8 w-[70px]">
<SelectValue
placeholder={
table.getState().pagination.pageSize
}
/>
</SelectTrigger>
<SelectContent side="top">
{[10, 20, 30, 40, 50].map((pageSize) => (
<SelectItem
key={pageSize}
value={`${pageSize}`}
>
{pageSize}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{!meta && (
<div className="flex items-center gap-2">
<p className="text-sm font-medium">
Baris per halaman
</p>
<Select
value={`${table.getState().pagination.pageSize}`}
onValueChange={(value) => {
table.setPageSize(Number(value));
}}
>
<SelectTrigger className="h-8 w-[70px]">
<SelectValue
placeholder={
table.getState().pagination.pageSize
}
/>
</SelectTrigger>
<SelectContent side="top">
{[10, 20, 30, 40, 50].map((pageSize) => (
<SelectItem
key={pageSize}
value={`${pageSize}`}
>
{pageSize}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
)}
<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{' '}
{table.getState().pagination.pageIndex *
@ -550,46 +565,114 @@ export function DataTable<TData, TValue>({
'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>
{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">
<Button
variant="outline"
className="hidden h-8 w-8 p-0 lg:flex"
onClick={() => table.setPageIndex(0)}
disabled={!table.getCanPreviousPage()}
>
<ChevronsLeft className="h-4 w-4" />
</Button>
<Button
variant="outline"
className="h-8 w-8 p-0"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
<ChevronLeft className="h-4 w-4" />
</Button>
<Button
variant="outline"
className="h-8 w-8 p-0"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
<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()}
>
<ChevronsRight className="h-4 w-4" />
</Button>
</div>
)}
</div>
</div>
</div>

View File

@ -12,3 +12,4 @@ export type * from './system-log';
export type * from './dashboard';
export type * from './role';
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;
}