add pagination

This commit is contained in:
Do Manh Phuong 2026-03-04 16:43:45 +07:00
parent 5e29ac78f7
commit 3776ce6e22
13 changed files with 228 additions and 7 deletions

View File

@ -0,0 +1,78 @@
import { Button } from "@/components/ui/button";
import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from "lucide-react";
import { RowsPerPage } from "./rows-per-page";
interface PaginationProps {
currentPage: number;
totalPages: number;
totalItems: number;
itemsPerPage: number;
onPageChange: (page: number) => void;
onPageSizeChange?: (pageSize: number) => void;
pageSizeOptions?: number[];
}
export function CustomPagination({
currentPage,
totalPages,
totalItems,
itemsPerPage,
onPageChange,
onPageSizeChange,
pageSizeOptions
}: PaginationProps) {
let startItem = (currentPage - 1) * itemsPerPage + 1;
let endItem = Math.min(currentPage * itemsPerPage, totalItems);
if (currentPage === totalPages) {
startItem = totalItems - itemsPerPage + 1;
endItem = totalItems;
}
return (
<div className="flex flex-col sm:flex-row items-center gap-4">
{onPageSizeChange && (
<RowsPerPage
pageSize={itemsPerPage}
onPageSizeChange={onPageSizeChange}
options={pageSizeOptions}
/>
)}
<div className="flex items-center gap-1">
<Button
variant="outline"
size="icon"
onClick={() => onPageChange(1)}
disabled={currentPage === 1}>
<ChevronsLeft className="h-4 w-4" />
</Button>
<Button
variant="outline"
size="icon"
onClick={() => onPageChange(currentPage - 1)}
disabled={currentPage === 1}>
<ChevronLeft className="h-4 w-4" />
</Button>
<span className="mx-2 text-sm">
{startItem}-{endItem} của {totalItems}
</span>
<Button
variant="outline"
size="icon"
onClick={() => onPageChange(currentPage + 1)}
disabled={currentPage === totalPages}>
<ChevronRight className="h-4 w-4" />
</Button>
<Button
variant="outline"
size="icon"
onClick={() => onPageChange(totalPages)}
disabled={currentPage === totalPages}>
<ChevronsRight className="h-4 w-4" />
</Button>
</div>
</div>
);
}

View File

@ -0,0 +1,45 @@
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue
} from "@/components/ui/select";
interface RowsPerPageProps {
pageSize: number;
onPageSizeChange: (pageSize: number) => void;
options?: number[];
}
export function RowsPerPage({
pageSize,
onPageSizeChange,
options = [5, 10, 15, 20]
}: RowsPerPageProps) {
return (
<div className="flex items-center gap-2">
<span className="text-sm text-muted-foreground">Hiển thị</span>
<Select
value={pageSize?.toString()}
onValueChange={(value) => onPageSizeChange(Number(value))}>
<SelectTrigger className="h-8 w-[70px]">
<SelectValue placeholder={pageSize?.toString()} />
</SelectTrigger>
<SelectContent>
{!options.includes(pageSize) && (
<SelectItem value={pageSize?.toString()} disabled>
{pageSize}
</SelectItem>
)}
{options.map((option) => (
<SelectItem key={option} value={option?.toString()}>
{option}
</SelectItem>
))}
</SelectContent>
</Select>
<span className="text-sm text-muted-foreground">mục</span>
</div>
);
}

View File

@ -1,6 +1,7 @@
import {
flexRender,
getCoreRowModel,
getPaginationRowModel,
useReactTable,
type ColumnDef,
} from "@tanstack/react-table";
@ -13,7 +14,8 @@ import {
TableRow,
} from "@/components/ui/table";
import { ScrollArea } from "@/components/ui/scroll-area";
import { useEffect } from "react";
import { CustomPagination } from "@/components/pagination/pagination";
import { useEffect, useState } from "react";
interface VersionTableProps<TData> {
data: TData[];
@ -23,6 +25,10 @@ interface VersionTableProps<TData> {
onRowClick?: (row: TData) => void;
scrollable?: boolean;
maxHeight?: string;
// Pagination options
enablePagination?: boolean;
defaultPageSize?: number;
pageSizeOptions?: number[];
}
export function VersionTable<TData>({
@ -33,11 +39,24 @@ export function VersionTable<TData>({
onRowClick,
scrollable = false,
maxHeight = "calc(100vh - 320px)",
enablePagination = false,
defaultPageSize = 10,
pageSizeOptions = [5, 10, 15, 20],
}: VersionTableProps<TData>) {
const [pagination, setPagination] = useState({
pageIndex: 0,
pageSize: defaultPageSize,
});
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
...(enablePagination && {
getPaginationRowModel: getPaginationRowModel(),
state: { pagination },
onPaginationChange: setPagination,
}),
getRowId: (row: any) => row.id?.toString(),
enableRowSelection: true,
});
@ -96,13 +115,41 @@ export function VersionTable<TData>({
if (scrollable) {
return (
<div className="rounded-md border">
<ScrollArea className="w-full" style={{ height: maxHeight }}>
{tableContent}
</ScrollArea>
<div className="space-y-4">
<div className="rounded-md border">
<ScrollArea className="w-full" style={{ height: maxHeight }}>
{tableContent}
</ScrollArea>
</div>
{enablePagination && data.length > 0 && (
<CustomPagination
currentPage={table.getState().pagination.pageIndex + 1}
totalPages={table.getPageCount()}
totalItems={data.length}
itemsPerPage={table.getState().pagination.pageSize}
onPageChange={(page) => table.setPageIndex(page - 1)}
onPageSizeChange={(size) => table.setPageSize(size)}
pageSizeOptions={pageSizeOptions}
/>
)}
</div>
);
}
return <div className="rounded-md border">{tableContent}</div>;
return (
<div className="space-y-4">
<div className="rounded-md border">{tableContent}</div>
{enablePagination && data.length > 0 && (
<CustomPagination
currentPage={table.getState().pagination.pageIndex + 1}
totalPages={table.getPageCount()}
totalItems={data.length}
itemsPerPage={table.getState().pagination.pageSize}
onPageChange={(page) => table.setPageIndex(page - 1)}
onPageSizeChange={(size) => table.setPageSize(size)}
pageSizeOptions={pageSizeOptions}
/>
)}
</div>
);
}

View File

@ -104,6 +104,8 @@ function AgentsPage() {
onUpdate={handleUpdate}
updateLoading={updateMutation.isPending}
rooms={roomData}
enablePagination
defaultPageSize={10}
/>
);
}

View File

@ -293,6 +293,8 @@ function AppsComponent() {
addToRequiredLoading={addRequiredFileMutation.isPending}
onTableInit={setTable}
rooms={roomData}
enablePagination
defaultPageSize={10}
/>
</>
);

View File

@ -159,6 +159,8 @@ function BlacklistComponent() {
onAdd={handleAddNewBlacklist}
onDelete={handleDeleteBlacklist}
onUpdate={handleUpdateDevice}
enablePagination
defaultPageSize={10}
/>
);
}

View File

@ -310,6 +310,8 @@ function CommandPage() {
onRowClick={(row) => setDetailPanelCommand(row)}
scrollable={true}
maxHeight="500px"
enablePagination
defaultPageSize={10}
/>
{/* Detail Dialog Popup */}

View File

@ -127,6 +127,8 @@ function RoleComponent() {
tableDescription="Các vai trò trong hệ thống và quyền hạn tương ứng"
createButtonLabel="Tạo role mới"
createLink="/role/create"
enablePagination
defaultPageSize={10}
/>
);
}

View File

@ -189,7 +189,7 @@ function RoomComponent() {
className="cursor-pointer hover:bg-muted/50 transition-colors"
onClick={() =>
navigate({
to: "/room/$roomName",
to: "/rooms/$roomName",
params: { roomName: row.original.name },
})
}

View File

@ -46,6 +46,10 @@ interface AppManagerTemplateProps<TData> {
onTableInit?: (table: any) => void;
rooms?: Room[];
devices?: string[];
// Pagination options
enablePagination?: boolean;
defaultPageSize?: number;
pageSizeOptions?: number[];
}
export function AppManagerTemplate<TData>({
@ -69,6 +73,9 @@ export function AppManagerTemplate<TData>({
onTableInit,
rooms = [],
devices = [],
enablePagination = false,
defaultPageSize = 10,
pageSizeOptions = [5, 10, 15, 20],
}: AppManagerTemplateProps<TData>) {
const [dialogOpen, setDialogOpen] = useState(false);
const [dialogType, setDialogType] = useState<"room" | "device" | "download-room" | "download-device" | null>(null);
@ -146,6 +153,9 @@ export function AppManagerTemplate<TData>({
isLoading={isLoading}
columns={columns}
onTableInit={onTableInit}
enablePagination={enablePagination}
defaultPageSize={defaultPageSize}
pageSizeOptions={pageSizeOptions}
/>
</CardContent>

View File

@ -63,6 +63,11 @@ interface CommandSubmitTemplateProps<T extends { id: number }> {
onRowClick?: (row: T) => void;
scrollable?: boolean;
maxHeight?: string;
// Pagination options
enablePagination?: boolean;
defaultPageSize?: number;
pageSizeOptions?: number[];
}
export function CommandSubmitTemplate<T extends { id: number }>({
@ -86,6 +91,9 @@ export function CommandSubmitTemplate<T extends { id: number }>({
onRowClick,
scrollable = true,
maxHeight = "calc(100vh - 320px)",
enablePagination = false,
defaultPageSize = 10,
pageSizeOptions = [5, 10, 15, 20],
}: CommandSubmitTemplateProps<T>) {
const [activeTab, setActiveTab] = useState<"list" | "execute">("list");
const [customCommand, setCustomCommand] = useState("");
@ -252,6 +260,9 @@ export function CommandSubmitTemplate<T extends { id: number }>({
onRowClick={onRowClick}
scrollable={scrollable}
maxHeight={maxHeight}
enablePagination={enablePagination}
defaultPageSize={defaultPageSize}
pageSizeOptions={pageSizeOptions}
/>
</div>
<div className="flex-shrink-0">

View File

@ -26,6 +26,10 @@ interface RoleManagerTemplateProps<TData> {
createButtonLabel?: string;
createLink?: string;
headerActions?: ReactNode;
// Pagination options
enablePagination?: boolean;
defaultPageSize?: number;
pageSizeOptions?: number[];
}
export function RoleManagerTemplate<TData>({
@ -41,6 +45,9 @@ export function RoleManagerTemplate<TData>({
createButtonLabel = "Tạo mới",
createLink,
headerActions,
enablePagination = false,
defaultPageSize = 10,
pageSizeOptions = [5, 10, 15, 20],
}: RoleManagerTemplateProps<TData>) {
return (
<div className="w-full px-6 space-y-4">
@ -79,6 +86,9 @@ export function RoleManagerTemplate<TData>({
isLoading={isLoading}
columns={columns}
onTableInit={onTableInit}
enablePagination={enablePagination}
defaultPageSize={defaultPageSize}
pageSizeOptions={pageSizeOptions}
/>
</CardContent>
</Card>

View File

@ -32,6 +32,10 @@ interface BlackListManagerTemplateProps<TData> {
updateLoading?: boolean;
onTableInit?: (table: any) => void;
rooms: Room[];
// Pagination options
enablePagination?: boolean;
defaultPageSize?: number;
pageSizeOptions?: number[];
}
export function BlackListManagerTemplate<TData>({
@ -45,6 +49,9 @@ export function BlackListManagerTemplate<TData>({
updateLoading,
onTableInit,
rooms = [],
enablePagination = false,
defaultPageSize = 10,
pageSizeOptions = [5, 10, 15, 20],
}: BlackListManagerTemplateProps<TData>) {
const [dialogOpen, setDialogOpen] = useState(false);
const [dialogType, setDialogType] = useState<"room" | "device" | null>(null);
@ -102,6 +109,9 @@ export function BlackListManagerTemplate<TData>({
isLoading={isLoading}
columns={columns}
onTableInit={onTableInit}
enablePagination={enablePagination}
defaultPageSize={defaultPageSize}
pageSizeOptions={pageSizeOptions}
/>
</CardContent>