107 lines
3.1 KiB
TypeScript
107 lines
3.1 KiB
TypeScript
'use client';
|
||
|
||
import { HugeiconsIcon } from '@hugeicons/react';
|
||
import { ChevronLeftIcon, ChevronRightIcon } from '@hugeicons/core-free-icons';
|
||
import { Button } from '@/components/ui/button';
|
||
import {
|
||
Select,
|
||
SelectContent,
|
||
SelectItem,
|
||
SelectTrigger,
|
||
SelectValue,
|
||
} from '@/components/ui/select';
|
||
|
||
interface TablePaginationProps {
|
||
page: number;
|
||
pageSize: number;
|
||
totalRecords: number;
|
||
pageSizeOptions?: number[];
|
||
onPageChange: (page: number) => void;
|
||
onPageSizeChange: (size: number) => void;
|
||
}
|
||
|
||
function buildPageNumbers(page: number, totalPages: number): (number | '...')[] {
|
||
if (totalPages <= 7) return Array.from({ length: totalPages }, (_, i) => i + 1);
|
||
const pages: (number | '...')[] = [1];
|
||
if (page > 3) pages.push('...');
|
||
for (let i = Math.max(2, page - 1); i <= Math.min(totalPages - 1, page + 1); i++) {
|
||
pages.push(i);
|
||
}
|
||
if (page < totalPages - 2) pages.push('...');
|
||
pages.push(totalPages);
|
||
return pages;
|
||
}
|
||
|
||
export function TablePagination({
|
||
page,
|
||
pageSize,
|
||
totalRecords,
|
||
pageSizeOptions = [10, 25, 50, 100],
|
||
onPageChange,
|
||
onPageSizeChange,
|
||
}: TablePaginationProps) {
|
||
const totalPages = Math.max(1, Math.ceil(totalRecords / pageSize));
|
||
const from = Math.min((page - 1) * pageSize + 1, totalRecords);
|
||
const to = Math.min(page * pageSize, totalRecords);
|
||
const pages = buildPageNumbers(page, totalPages);
|
||
|
||
return (
|
||
<div className="flex flex-col gap-3 px-4 py-3 border-t sm:flex-row sm:items-center sm:justify-between">
|
||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||
<span>Rows per page:</span>
|
||
<Select
|
||
value={pageSize}
|
||
onValueChange={(v) => onPageSizeChange(Number(v))}
|
||
>
|
||
<SelectTrigger size="sm" className="w-16">
|
||
<SelectValue />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
{pageSizeOptions.map(s => (
|
||
<SelectItem key={s} value={s}>{s}</SelectItem>
|
||
))}
|
||
</SelectContent>
|
||
</Select>
|
||
<span className="ml-2">
|
||
{totalRecords === 0 ? '0 records' : `${from}–${to} of ${totalRecords} records`}
|
||
</span>
|
||
</div>
|
||
|
||
<div className="flex items-center gap-1">
|
||
<Button
|
||
variant="outline"
|
||
size="icon-sm"
|
||
onClick={() => onPageChange(page - 1)}
|
||
disabled={page <= 1}
|
||
>
|
||
<HugeiconsIcon icon={ChevronLeftIcon} className="size-4" />
|
||
</Button>
|
||
|
||
{pages.map((p, i) =>
|
||
p === '...' ? (
|
||
<span key={`ellipsis-${i}`} className="px-2 text-muted-foreground text-sm">…</span>
|
||
) : (
|
||
<Button
|
||
key={p}
|
||
variant={p === page ? 'default' : 'outline'}
|
||
size="icon-sm"
|
||
onClick={() => onPageChange(p as number)}
|
||
>
|
||
{p}
|
||
</Button>
|
||
)
|
||
)}
|
||
|
||
<Button
|
||
variant="outline"
|
||
size="icon-sm"
|
||
onClick={() => onPageChange(page + 1)}
|
||
disabled={page >= totalPages}
|
||
>
|
||
<HugeiconsIcon icon={ChevronRightIcon} className="size-4" />
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|