add pagination
This commit is contained in:
parent
5e29ac78f7
commit
3776ce6e22
78
src/components/pagination/pagination.tsx
Normal file
78
src/components/pagination/pagination.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
45
src/components/pagination/rows-per-page.tsx
Normal file
45
src/components/pagination/rows-per-page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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="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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,6 +104,8 @@ function AgentsPage() {
|
|||
onUpdate={handleUpdate}
|
||||
updateLoading={updateMutation.isPending}
|
||||
rooms={roomData}
|
||||
enablePagination
|
||||
defaultPageSize={10}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -293,6 +293,8 @@ function AppsComponent() {
|
|||
addToRequiredLoading={addRequiredFileMutation.isPending}
|
||||
onTableInit={setTable}
|
||||
rooms={roomData}
|
||||
enablePagination
|
||||
defaultPageSize={10}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -159,6 +159,8 @@ function BlacklistComponent() {
|
|||
onAdd={handleAddNewBlacklist}
|
||||
onDelete={handleDeleteBlacklist}
|
||||
onUpdate={handleUpdateDevice}
|
||||
enablePagination
|
||||
defaultPageSize={10}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -310,6 +310,8 @@ function CommandPage() {
|
|||
onRowClick={(row) => setDetailPanelCommand(row)}
|
||||
scrollable={true}
|
||||
maxHeight="500px"
|
||||
enablePagination
|
||||
defaultPageSize={10}
|
||||
/>
|
||||
|
||||
{/* Detail Dialog Popup */}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user