feat: Add pagination to service table
- Add pagination to the service table in the uptime monitoring dashboard, allowing display of 10 records per page by default. Also add a dropdown to select the number of records to display (10, 30, 50).
This commit is contained in:
@@ -47,21 +47,21 @@ export const ServerTable = ({ servers, isLoading, onRefresh }: ServerTableProps)
|
|||||||
newSet.delete(serverId);
|
newSet.delete(serverId);
|
||||||
return newSet;
|
return newSet;
|
||||||
});
|
});
|
||||||
console.log('Resume server monitoring:', serverId);
|
// console.log('Resume server monitoring:', serverId);
|
||||||
} else {
|
} else {
|
||||||
setPausedServers(prev => new Set(prev).add(serverId));
|
setPausedServers(prev => new Set(prev).add(serverId));
|
||||||
console.log('Pause server monitoring:', serverId);
|
// console.log('Pause server monitoring:', serverId);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEdit = (serverId: string) => {
|
const handleEdit = (serverId: string) => {
|
||||||
// TODO: Implement edit functionality
|
// TODO: Implement edit functionality
|
||||||
console.log('Edit server:', serverId);
|
// console.log('Edit server:', serverId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = (serverId: string) => {
|
const handleDelete = (serverId: string) => {
|
||||||
// TODO: Implement delete functionality
|
// TODO: Implement delete functionality
|
||||||
console.log('Delete server:', serverId);
|
// console.log('Delete server:', serverId);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
|
||||||
|
import {
|
||||||
|
Pagination,
|
||||||
|
PaginationContent,
|
||||||
|
PaginationItem,
|
||||||
|
PaginationLink,
|
||||||
|
PaginationNext,
|
||||||
|
PaginationPrevious
|
||||||
|
} from "@/components/ui/pagination";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue
|
||||||
|
} from "@/components/ui/select";
|
||||||
|
import { PageSize } from "@/hooks/useServicesPagination";
|
||||||
|
import { useLanguage } from "@/contexts/LanguageContext";
|
||||||
|
|
||||||
|
interface ServicesPaginationProps {
|
||||||
|
currentPage: number;
|
||||||
|
totalPages: number;
|
||||||
|
pageSize: PageSize;
|
||||||
|
totalItems: number;
|
||||||
|
onPageChange: (page: number) => void;
|
||||||
|
onPageSizeChange: (size: PageSize) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ServicesPagination({
|
||||||
|
currentPage,
|
||||||
|
totalPages,
|
||||||
|
pageSize,
|
||||||
|
totalItems,
|
||||||
|
onPageChange,
|
||||||
|
onPageSizeChange,
|
||||||
|
}: ServicesPaginationProps) {
|
||||||
|
const { t } = useLanguage();
|
||||||
|
|
||||||
|
// Generate page numbers to display
|
||||||
|
const getPageNumbers = () => {
|
||||||
|
const pages = [];
|
||||||
|
const maxVisiblePages = 5;
|
||||||
|
const halfVisible = Math.floor(maxVisiblePages / 2);
|
||||||
|
|
||||||
|
let startPage = Math.max(1, currentPage - halfVisible);
|
||||||
|
let endPage = Math.min(totalPages, startPage + maxVisiblePages - 1);
|
||||||
|
|
||||||
|
// Adjust start page if we don't have enough pages at the end
|
||||||
|
if (endPage - startPage + 1 < maxVisiblePages) {
|
||||||
|
startPage = Math.max(1, endPage - maxVisiblePages + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = startPage; i <= endPage; i++) {
|
||||||
|
pages.push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pages;
|
||||||
|
};
|
||||||
|
|
||||||
|
const startItem = Math.min((currentPage - 1) * pageSize + 1, totalItems);
|
||||||
|
const endItem = Math.min(currentPage * pageSize, totalItems);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-between py-4 px-4 border-t border-border">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<span className="text-sm text-muted-foreground">
|
||||||
|
{t("rowsPerPage") ?? "Rows per page"}:
|
||||||
|
</span>
|
||||||
|
<Select
|
||||||
|
value={pageSize.toString()}
|
||||||
|
onValueChange={(value) => onPageSizeChange(parseInt(value) as PageSize)}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="h-8 w-[70px]">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="10">10</SelectItem>
|
||||||
|
<SelectItem value="30">30</SelectItem>
|
||||||
|
<SelectItem value="50">50</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<span className="text-sm text-muted-foreground whitespace-nowrap">
|
||||||
|
{totalItems > 0
|
||||||
|
? `${startItem}-${endItem} of ${totalItems} ${t("services") || "services"}`
|
||||||
|
: `0 ${t("services") || "services"}`
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{totalPages > 1 && (
|
||||||
|
<Pagination>
|
||||||
|
<PaginationContent>
|
||||||
|
<PaginationItem>
|
||||||
|
<PaginationPrevious
|
||||||
|
onClick={() => onPageChange(Math.max(1, currentPage - 1))}
|
||||||
|
className={
|
||||||
|
currentPage === 1
|
||||||
|
? "pointer-events-none opacity-50"
|
||||||
|
: "cursor-pointer"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</PaginationItem>
|
||||||
|
|
||||||
|
{getPageNumbers().map((page) => (
|
||||||
|
<PaginationItem key={page}>
|
||||||
|
<PaginationLink
|
||||||
|
isActive={page === currentPage}
|
||||||
|
onClick={() => onPageChange(page)}
|
||||||
|
className="cursor-pointer"
|
||||||
|
>
|
||||||
|
{page}
|
||||||
|
</PaginationLink>
|
||||||
|
</PaginationItem>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<PaginationItem>
|
||||||
|
<PaginationNext
|
||||||
|
onClick={() => onPageChange(Math.min(totalPages, currentPage + 1))}
|
||||||
|
className={
|
||||||
|
currentPage === totalPages
|
||||||
|
? "pointer-events-none opacity-50"
|
||||||
|
: "cursor-pointer"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</PaginationItem>
|
||||||
|
</PaginationContent>
|
||||||
|
</Pagination>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,10 +2,12 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { Service } from "@/types/service.types";
|
import { Service } from "@/types/service.types";
|
||||||
import { ServicesTableView } from "./ServicesTableView";
|
import { ServicesTableView } from "./ServicesTableView";
|
||||||
|
import { ServicesPagination } from "./ServicesPagination";
|
||||||
import { ServiceDeleteDialog } from "./ServiceDeleteDialog";
|
import { ServiceDeleteDialog } from "./ServiceDeleteDialog";
|
||||||
import { ServiceHistoryDialog } from "./ServiceHistoryDialog";
|
import { ServiceHistoryDialog } from "./ServiceHistoryDialog";
|
||||||
import { ServiceEditDialog } from "./ServiceEditDialog";
|
import { ServiceEditDialog } from "./ServiceEditDialog";
|
||||||
import { useServiceActions, useDialogState } from "./hooks";
|
import { useServiceActions, useDialogState } from "./hooks";
|
||||||
|
import { useServicesPagination } from "@/hooks/useServicesPagination";
|
||||||
|
|
||||||
interface ServicesTableContainerProps {
|
interface ServicesTableContainerProps {
|
||||||
services: Service[];
|
services: Service[];
|
||||||
@@ -36,6 +38,16 @@ export const ServicesTableContainer = ({ services }: ServicesTableContainerProps
|
|||||||
handleDeleteDialogChange
|
handleDeleteDialogChange
|
||||||
} = useDialogState();
|
} = useDialogState();
|
||||||
|
|
||||||
|
const {
|
||||||
|
paginatedServices,
|
||||||
|
currentPage,
|
||||||
|
totalPages,
|
||||||
|
pageSize,
|
||||||
|
totalItems,
|
||||||
|
handlePageChange,
|
||||||
|
handlePageSizeChange,
|
||||||
|
} = useServicesPagination({ services: localServices });
|
||||||
|
|
||||||
// Update local services state when props change
|
// Update local services state when props change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
updateServices(services);
|
updateServices(services);
|
||||||
@@ -62,7 +74,7 @@ export const ServicesTableContainer = ({ services }: ServicesTableContainerProps
|
|||||||
return (
|
return (
|
||||||
<div className="flex-1 flex flex-col h-full">
|
<div className="flex-1 flex flex-col h-full">
|
||||||
<ServicesTableView
|
<ServicesTableView
|
||||||
services={localServices}
|
services={paginatedServices}
|
||||||
onViewDetail={handleViewDetail}
|
onViewDetail={handleViewDetail}
|
||||||
onPauseResume={handlePauseResume}
|
onPauseResume={handlePauseResume}
|
||||||
onEdit={onEdit}
|
onEdit={onEdit}
|
||||||
@@ -70,6 +82,15 @@ export const ServicesTableContainer = ({ services }: ServicesTableContainerProps
|
|||||||
onMuteAlerts={handleMuteAlerts}
|
onMuteAlerts={handleMuteAlerts}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<ServicesPagination
|
||||||
|
currentPage={currentPage}
|
||||||
|
totalPages={totalPages}
|
||||||
|
pageSize={pageSize}
|
||||||
|
totalItems={totalItems}
|
||||||
|
onPageChange={handlePageChange}
|
||||||
|
onPageSizeChange={handlePageSizeChange}
|
||||||
|
/>
|
||||||
|
|
||||||
<ServiceHistoryDialog
|
<ServiceHistoryDialog
|
||||||
isOpen={isHistoryDialogOpen}
|
isOpen={isHistoryDialogOpen}
|
||||||
onOpenChange={setIsHistoryDialogOpen}
|
onOpenChange={setIsHistoryDialogOpen}
|
||||||
@@ -91,4 +112,4 @@ export const ServicesTableContainer = ({ services }: ServicesTableContainerProps
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -26,8 +26,8 @@ export const ServicesTableView = ({
|
|||||||
const { t } = useLanguage();
|
const { t } = useLanguage();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${theme === 'dark' ? 'bg-gray-900' : 'bg-white'} rounded-lg overflow-hidden border border-border flex-1 flex flex-col shadow-sm`}>
|
<div className={`${theme === 'dark' ? 'bg-gray-900' : 'bg-white'} rounded-lg overflow-hidden border border-border shadow-sm`}>
|
||||||
<div className="flex-1 overflow-auto">
|
<div className="overflow-auto">
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader className={`${theme === 'dark' ? 'bg-gray-800' : 'bg-gray-50'} sticky top-0 z-10`}>
|
<TableHeader className={`${theme === 'dark' ? 'bg-gray-800' : 'bg-gray-50'} sticky top-0 z-10`}>
|
||||||
<TableRow className={`${theme === 'dark' ? 'border-gray-700 hover:bg-gray-800' : 'border-gray-200 hover:bg-gray-100'}`}>
|
<TableRow className={`${theme === 'dark' ? 'border-gray-700 hover:bg-gray-800' : 'border-gray-200 hover:bg-gray-100'}`}>
|
||||||
@@ -65,4 +65,4 @@ export const ServicesTableView = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
|
||||||
|
import { useState, useMemo } from 'react';
|
||||||
|
import { Service } from '@/types/service.types';
|
||||||
|
|
||||||
|
export type PageSize = 10 | 30 | 50;
|
||||||
|
|
||||||
|
interface UseServicesPaginationProps {
|
||||||
|
services: Service[];
|
||||||
|
initialPageSize?: PageSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useServicesPagination = ({
|
||||||
|
services,
|
||||||
|
initialPageSize = 10
|
||||||
|
}: UseServicesPaginationProps) => {
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const [pageSize, setPageSize] = useState<PageSize>(initialPageSize);
|
||||||
|
|
||||||
|
const { paginatedServices, totalPages } = useMemo(() => {
|
||||||
|
const totalItems = services.length;
|
||||||
|
const pages = Math.ceil(totalItems / pageSize);
|
||||||
|
const startIndex = (currentPage - 1) * pageSize;
|
||||||
|
const endIndex = startIndex + pageSize;
|
||||||
|
|
||||||
|
return {
|
||||||
|
paginatedServices: services.slice(startIndex, endIndex),
|
||||||
|
totalPages: Math.max(1, pages)
|
||||||
|
};
|
||||||
|
}, [services, currentPage, pageSize]);
|
||||||
|
|
||||||
|
// Reset to first page when page size changes or services change
|
||||||
|
const handlePageSizeChange = (newPageSize: PageSize) => {
|
||||||
|
setPageSize(newPageSize);
|
||||||
|
setCurrentPage(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reset to first page if current page exceeds total pages
|
||||||
|
const handlePageChange = (page: number) => {
|
||||||
|
const validPage = Math.min(Math.max(1, page), totalPages);
|
||||||
|
setCurrentPage(validPage);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
paginatedServices,
|
||||||
|
currentPage,
|
||||||
|
totalPages,
|
||||||
|
pageSize,
|
||||||
|
totalItems: services.length,
|
||||||
|
handlePageChange,
|
||||||
|
handlePageSizeChange,
|
||||||
|
};
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user