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:
Tola Leng
2025-07-15 15:26:23 +07:00
parent 73f2566d78
commit 6512d4974a
5 changed files with 213 additions and 9 deletions
@@ -47,21 +47,21 @@ export const ServerTable = ({ servers, isLoading, onRefresh }: ServerTableProps)
newSet.delete(serverId);
return newSet;
});
console.log('Resume server monitoring:', serverId);
// console.log('Resume server monitoring:', serverId);
} else {
setPausedServers(prev => new Set(prev).add(serverId));
console.log('Pause server monitoring:', serverId);
// console.log('Pause server monitoring:', serverId);
}
};
const handleEdit = (serverId: string) => {
// TODO: Implement edit functionality
console.log('Edit server:', serverId);
// console.log('Edit server:', serverId);
};
const handleDelete = (serverId: string) => {
// TODO: Implement delete functionality
console.log('Delete server:', serverId);
// console.log('Delete server:', serverId);
};
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 { Service } from "@/types/service.types";
import { ServicesTableView } from "./ServicesTableView";
import { ServicesPagination } from "./ServicesPagination";
import { ServiceDeleteDialog } from "./ServiceDeleteDialog";
import { ServiceHistoryDialog } from "./ServiceHistoryDialog";
import { ServiceEditDialog } from "./ServiceEditDialog";
import { useServiceActions, useDialogState } from "./hooks";
import { useServicesPagination } from "@/hooks/useServicesPagination";
interface ServicesTableContainerProps {
services: Service[];
@@ -36,6 +38,16 @@ export const ServicesTableContainer = ({ services }: ServicesTableContainerProps
handleDeleteDialogChange
} = useDialogState();
const {
paginatedServices,
currentPage,
totalPages,
pageSize,
totalItems,
handlePageChange,
handlePageSizeChange,
} = useServicesPagination({ services: localServices });
// Update local services state when props change
useEffect(() => {
updateServices(services);
@@ -62,7 +74,7 @@ export const ServicesTableContainer = ({ services }: ServicesTableContainerProps
return (
<div className="flex-1 flex flex-col h-full">
<ServicesTableView
services={localServices}
services={paginatedServices}
onViewDetail={handleViewDetail}
onPauseResume={handlePauseResume}
onEdit={onEdit}
@@ -70,6 +82,15 @@ export const ServicesTableContainer = ({ services }: ServicesTableContainerProps
onMuteAlerts={handleMuteAlerts}
/>
<ServicesPagination
currentPage={currentPage}
totalPages={totalPages}
pageSize={pageSize}
totalItems={totalItems}
onPageChange={handlePageChange}
onPageSizeChange={handlePageSizeChange}
/>
<ServiceHistoryDialog
isOpen={isHistoryDialogOpen}
onOpenChange={setIsHistoryDialogOpen}
@@ -91,4 +112,4 @@ export const ServicesTableContainer = ({ services }: ServicesTableContainerProps
/>
</div>
);
}
}
@@ -26,8 +26,8 @@ export const ServicesTableView = ({
const { t } = useLanguage();
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="flex-1 overflow-auto">
<div className={`${theme === 'dark' ? 'bg-gray-900' : 'bg-white'} rounded-lg overflow-hidden border border-border shadow-sm`}>
<div className="overflow-auto">
<Table>
<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'}`}>
@@ -65,4 +65,4 @@ export const ServicesTableView = ({
</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,
};
};