diff --git a/src/components/pagination/pagination.tsx b/src/components/pagination/pagination.tsx new file mode 100644 index 0000000..4081f81 --- /dev/null +++ b/src/components/pagination/pagination.tsx @@ -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 ( +
+ {onPageSizeChange && ( + + )} + +
+ + + + + {startItem}-{endItem} của {totalItems} + + + + +
+
+ ); +} diff --git a/src/components/pagination/rows-per-page.tsx b/src/components/pagination/rows-per-page.tsx new file mode 100644 index 0000000..af4e41a --- /dev/null +++ b/src/components/pagination/rows-per-page.tsx @@ -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 ( +
+ Hiển thị + + mục +
+ ); +} diff --git a/src/components/tables/version-table.tsx b/src/components/tables/version-table.tsx index 9ddbf6c..8fae802 100644 --- a/src/components/tables/version-table.tsx +++ b/src/components/tables/version-table.tsx @@ -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 { data: TData[]; @@ -23,6 +25,10 @@ interface VersionTableProps { onRowClick?: (row: TData) => void; scrollable?: boolean; maxHeight?: string; + // Pagination options + enablePagination?: boolean; + defaultPageSize?: number; + pageSizeOptions?: number[]; } export function VersionTable({ @@ -33,11 +39,24 @@ export function VersionTable({ onRowClick, scrollable = false, maxHeight = "calc(100vh - 320px)", + enablePagination = false, + defaultPageSize = 10, + pageSizeOptions = [5, 10, 15, 20], }: VersionTableProps) { + 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({ if (scrollable) { return ( -
- - {tableContent} - +
+
+ + {tableContent} + +
+ {enablePagination && data.length > 0 && ( + table.setPageIndex(page - 1)} + onPageSizeChange={(size) => table.setPageSize(size)} + pageSizeOptions={pageSizeOptions} + /> + )}
); } - return
{tableContent}
; + return ( +
+
{tableContent}
+ {enablePagination && data.length > 0 && ( + table.setPageIndex(page - 1)} + onPageSizeChange={(size) => table.setPageSize(size)} + pageSizeOptions={pageSizeOptions} + /> + )} +
+ ); } diff --git a/src/routes/_auth/agent/index.tsx b/src/routes/_auth/agent/index.tsx index 8a137f6..f6442fb 100644 --- a/src/routes/_auth/agent/index.tsx +++ b/src/routes/_auth/agent/index.tsx @@ -104,6 +104,8 @@ function AgentsPage() { onUpdate={handleUpdate} updateLoading={updateMutation.isPending} rooms={roomData} + enablePagination + defaultPageSize={10} /> ); } diff --git a/src/routes/_auth/apps/index.tsx b/src/routes/_auth/apps/index.tsx index f61b3aa..2d655da 100644 --- a/src/routes/_auth/apps/index.tsx +++ b/src/routes/_auth/apps/index.tsx @@ -293,6 +293,8 @@ function AppsComponent() { addToRequiredLoading={addRequiredFileMutation.isPending} onTableInit={setTable} rooms={roomData} + enablePagination + defaultPageSize={10} /> ); diff --git a/src/routes/_auth/blacklists/index.tsx b/src/routes/_auth/blacklists/index.tsx index 64b13b1..c0b9f22 100644 --- a/src/routes/_auth/blacklists/index.tsx +++ b/src/routes/_auth/blacklists/index.tsx @@ -159,6 +159,8 @@ function BlacklistComponent() { onAdd={handleAddNewBlacklist} onDelete={handleDeleteBlacklist} onUpdate={handleUpdateDevice} + enablePagination + defaultPageSize={10} /> ); } diff --git a/src/routes/_auth/commands/index.tsx b/src/routes/_auth/commands/index.tsx index a9a2bc7..58713a8 100644 --- a/src/routes/_auth/commands/index.tsx +++ b/src/routes/_auth/commands/index.tsx @@ -310,6 +310,8 @@ function CommandPage() { onRowClick={(row) => setDetailPanelCommand(row)} scrollable={true} maxHeight="500px" + enablePagination + defaultPageSize={10} /> {/* Detail Dialog Popup */} diff --git a/src/routes/_auth/role/index.tsx b/src/routes/_auth/role/index.tsx index b059f56..2940308 100644 --- a/src/routes/_auth/role/index.tsx +++ b/src/routes/_auth/role/index.tsx @@ -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} /> ); } diff --git a/src/routes/_auth/rooms/index.tsx b/src/routes/_auth/rooms/index.tsx index 62e6c89..698aa02 100644 --- a/src/routes/_auth/rooms/index.tsx +++ b/src/routes/_auth/rooms/index.tsx @@ -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 }, }) } diff --git a/src/template/app-manager-template.tsx b/src/template/app-manager-template.tsx index dcef799..9cc9e7f 100644 --- a/src/template/app-manager-template.tsx +++ b/src/template/app-manager-template.tsx @@ -46,6 +46,10 @@ interface AppManagerTemplateProps { onTableInit?: (table: any) => void; rooms?: Room[]; devices?: string[]; + // Pagination options + enablePagination?: boolean; + defaultPageSize?: number; + pageSizeOptions?: number[]; } export function AppManagerTemplate({ @@ -69,6 +73,9 @@ export function AppManagerTemplate({ onTableInit, rooms = [], devices = [], + enablePagination = false, + defaultPageSize = 10, + pageSizeOptions = [5, 10, 15, 20], }: AppManagerTemplateProps) { const [dialogOpen, setDialogOpen] = useState(false); const [dialogType, setDialogType] = useState<"room" | "device" | "download-room" | "download-device" | null>(null); @@ -146,6 +153,9 @@ export function AppManagerTemplate({ isLoading={isLoading} columns={columns} onTableInit={onTableInit} + enablePagination={enablePagination} + defaultPageSize={defaultPageSize} + pageSizeOptions={pageSizeOptions} /> diff --git a/src/template/command-submit-template.tsx b/src/template/command-submit-template.tsx index bb226e6..9ee46d7 100644 --- a/src/template/command-submit-template.tsx +++ b/src/template/command-submit-template.tsx @@ -63,6 +63,11 @@ interface CommandSubmitTemplateProps { onRowClick?: (row: T) => void; scrollable?: boolean; maxHeight?: string; + + // Pagination options + enablePagination?: boolean; + defaultPageSize?: number; + pageSizeOptions?: number[]; } export function CommandSubmitTemplate({ @@ -86,6 +91,9 @@ export function CommandSubmitTemplate({ onRowClick, scrollable = true, maxHeight = "calc(100vh - 320px)", + enablePagination = false, + defaultPageSize = 10, + pageSizeOptions = [5, 10, 15, 20], }: CommandSubmitTemplateProps) { const [activeTab, setActiveTab] = useState<"list" | "execute">("list"); const [customCommand, setCustomCommand] = useState(""); @@ -252,6 +260,9 @@ export function CommandSubmitTemplate({ onRowClick={onRowClick} scrollable={scrollable} maxHeight={maxHeight} + enablePagination={enablePagination} + defaultPageSize={defaultPageSize} + pageSizeOptions={pageSizeOptions} />
diff --git a/src/template/role-manager-template.tsx b/src/template/role-manager-template.tsx index 0e8b398..feeb2e6 100644 --- a/src/template/role-manager-template.tsx +++ b/src/template/role-manager-template.tsx @@ -26,6 +26,10 @@ interface RoleManagerTemplateProps { createButtonLabel?: string; createLink?: string; headerActions?: ReactNode; + // Pagination options + enablePagination?: boolean; + defaultPageSize?: number; + pageSizeOptions?: number[]; } export function RoleManagerTemplate({ @@ -41,6 +45,9 @@ export function RoleManagerTemplate({ createButtonLabel = "Tạo mới", createLink, headerActions, + enablePagination = false, + defaultPageSize = 10, + pageSizeOptions = [5, 10, 15, 20], }: RoleManagerTemplateProps) { return (
@@ -79,6 +86,9 @@ export function RoleManagerTemplate({ isLoading={isLoading} columns={columns} onTableInit={onTableInit} + enablePagination={enablePagination} + defaultPageSize={defaultPageSize} + pageSizeOptions={pageSizeOptions} /> diff --git a/src/template/table-manager-template.tsx b/src/template/table-manager-template.tsx index 033f1c1..54839be 100644 --- a/src/template/table-manager-template.tsx +++ b/src/template/table-manager-template.tsx @@ -32,6 +32,10 @@ interface BlackListManagerTemplateProps { updateLoading?: boolean; onTableInit?: (table: any) => void; rooms: Room[]; + // Pagination options + enablePagination?: boolean; + defaultPageSize?: number; + pageSizeOptions?: number[]; } export function BlackListManagerTemplate({ @@ -45,6 +49,9 @@ export function BlackListManagerTemplate({ updateLoading, onTableInit, rooms = [], + enablePagination = false, + defaultPageSize = 10, + pageSizeOptions = [5, 10, 15, 20], }: BlackListManagerTemplateProps) { const [dialogOpen, setDialogOpen] = useState(false); const [dialogType, setDialogType] = useState<"room" | "device" | null>(null); @@ -102,6 +109,9 @@ export function BlackListManagerTemplate({ isLoading={isLoading} columns={columns} onTableInit={onTableInit} + enablePagination={enablePagination} + defaultPageSize={defaultPageSize} + pageSizeOptions={pageSizeOptions} />