feat: enhance DataTable component with pagination support and meta data handling
This commit is contained in:
parent
4df6c215ad
commit
f5698ee47f
@ -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,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="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">
|
||||||
<div className="flex items-center gap-2">
|
{!meta && (
|
||||||
<p className="text-sm font-medium">Baris per halaman</p>
|
<div className="flex items-center gap-2">
|
||||||
<Select
|
<p className="text-sm font-medium">
|
||||||
value={`${table.getState().pagination.pageSize}`}
|
Baris per halaman
|
||||||
onValueChange={(value) => {
|
</p>
|
||||||
table.setPageSize(Number(value));
|
<Select
|
||||||
}}
|
value={`${table.getState().pagination.pageSize}`}
|
||||||
>
|
onValueChange={(value) => {
|
||||||
<SelectTrigger className="h-8 w-[70px]">
|
table.setPageSize(Number(value));
|
||||||
<SelectValue
|
}}
|
||||||
placeholder={
|
>
|
||||||
table.getState().pagination.pageSize
|
<SelectTrigger className="h-8 w-[70px]">
|
||||||
}
|
<SelectValue
|
||||||
/>
|
placeholder={
|
||||||
</SelectTrigger>
|
table.getState().pagination.pageSize
|
||||||
<SelectContent side="top">
|
}
|
||||||
{[10, 20, 30, 40, 50].map((pageSize) => (
|
/>
|
||||||
<SelectItem
|
</SelectTrigger>
|
||||||
key={pageSize}
|
<SelectContent side="top">
|
||||||
value={`${pageSize}`}
|
{[10, 20, 30, 40, 50].map((pageSize) => (
|
||||||
>
|
<SelectItem
|
||||||
{pageSize}
|
key={pageSize}
|
||||||
</SelectItem>
|
value={`${pageSize}`}
|
||||||
))}
|
>
|
||||||
</SelectContent>
|
{pageSize}
|
||||||
</Select>
|
</SelectItem>
|
||||||
</div>
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</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,46 +565,114 @@ export function DataTable<TData, TValue>({
|
|||||||
'Tidak ada data'
|
'Tidak ada data'
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-2">
|
{meta ? (() => {
|
||||||
<Button
|
const { current_page, last_page, prev_page_url, next_page_url } = meta;
|
||||||
variant="outline"
|
|
||||||
className="hidden h-8 w-8 p-0 lg:flex"
|
const leftPages = [];
|
||||||
onClick={() => table.setPageIndex(0)}
|
for (let i = current_page - 3; i < current_page; i++) {
|
||||||
disabled={!table.getCanPreviousPage()}
|
if (i > 0) leftPages.push(i);
|
||||||
>
|
}
|
||||||
<span className="sr-only">Halaman pertama</span>
|
|
||||||
<ChevronsLeft className="h-4 w-4" />
|
const rightPages = [];
|
||||||
</Button>
|
for (let i = current_page + 1; i <= current_page + 2; i++) {
|
||||||
<Button
|
if (i <= last_page) rightPages.push(i);
|
||||||
variant="outline"
|
}
|
||||||
className="h-8 w-8 p-0"
|
|
||||||
onClick={() => table.previousPage()}
|
const handlePageChange = (page: number) => {
|
||||||
disabled={!table.getCanPreviousPage()}
|
const url = new URL(window.location.href);
|
||||||
>
|
url.searchParams.set('page', page.toString());
|
||||||
<span className="sr-only">Halaman sebelumnya</span>
|
import('@inertiajs/react').then(({ router }) => {
|
||||||
<ChevronLeft className="h-4 w-4" />
|
router.get(url.toString(), {}, { preserveState: true, preserveScroll: true });
|
||||||
</Button>
|
});
|
||||||
<Button
|
};
|
||||||
variant="outline"
|
|
||||||
className="h-8 w-8 p-0"
|
return (
|
||||||
onClick={() => table.nextPage()}
|
<div className="flex items-center space-x-2">
|
||||||
disabled={!table.getCanNextPage()}
|
<Button
|
||||||
>
|
variant="outline"
|
||||||
<span className="sr-only">Halaman selanjutnya</span>
|
className="h-8 w-8 p-0"
|
||||||
<ChevronRight className="h-4 w-4" />
|
onClick={() => {
|
||||||
</Button>
|
if (prev_page_url) {
|
||||||
<Button
|
import('@inertiajs/react').then(({ router }) => {
|
||||||
variant="outline"
|
router.get(prev_page_url, {}, { preserveState: true, preserveScroll: true });
|
||||||
className="hidden h-8 w-8 p-0 lg:flex"
|
});
|
||||||
onClick={() =>
|
}
|
||||||
table.setPageIndex(table.getPageCount() - 1)
|
}}
|
||||||
}
|
disabled={!prev_page_url}
|
||||||
disabled={!table.getCanNextPage()}
|
>
|
||||||
>
|
<ChevronLeft className="h-4 w-4" />
|
||||||
<span className="sr-only">Halaman terakhir</span>
|
</Button>
|
||||||
<ChevronsRight className="h-4 w-4" />
|
|
||||||
</Button>
|
{leftPages.map(p => (
|
||||||
</div>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -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';
|
||||||
|
|||||||
15
resources/js/types/pagination.ts
Normal file
15
resources/js/types/pagination.ts
Normal 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;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user