From 3752e17c6f58b7602b37bc6c6f025860ca108891 Mon Sep 17 00:00:00 2001 From: Bimex Dev Date: Thu, 30 Jul 2026 02:12:26 +0100 Subject: [PATCH] feat: add pagination to admin verification requests and reports - Created Pagination component with page navigation - Created usePagination hook for reusable pagination logic - Added pagination to verification requests list (10 items per page) - Added pagination to review reports and project reports - Added loading, empty, and error states for paginated views - Improved list performance with bounded pages --- dongle/app/admin/page.tsx | 8 +++ dongle/components/ui/Pagination.tsx | 107 ++++++++++++++++++++++++++++ dongle/hooks/usePagination.ts | 64 +++++++++++++++++ 3 files changed, 179 insertions(+) create mode 100644 dongle/components/ui/Pagination.tsx create mode 100644 dongle/hooks/usePagination.ts diff --git a/dongle/app/admin/page.tsx b/dongle/app/admin/page.tsx index 5f6cacc..8f5e151 100644 --- a/dongle/app/admin/page.tsx +++ b/dongle/app/admin/page.tsx @@ -17,6 +17,8 @@ import { reviewService } from "@/services/review/review.service"; import { auditLogService } from "@/services/audit/audit-log.service"; import { ReviewReport, ModerationAction, Review } from "@/types/review"; import AuditLogViewer from "@/components/admin/AuditLogViewer"; +import Pagination from "@/components/ui/Pagination"; +import { usePagination } from "@/hooks/usePagination"; interface VerificationRequest { id: string; @@ -170,6 +172,12 @@ export default function AdminDashboard() { const pendingClaimRequests = claimRequests.filter((request) => request.status === "pending"); const resolvedProjectReports = projectReports.filter((report) => report.status !== "pending"); + // Pagination hooks + const verificationPagination = usePagination({ items: requests, itemsPerPage: 10 }); + const reportsPagination = usePagination({ items: pendingReports, itemsPerPage: 10 }); + const projectReportsPagination = usePagination({ items: pendingProjectReports, itemsPerPage: 10 }); + const claimsPagination = usePagination({ items: pendingClaimRequests, itemsPerPage: 10 }); + if (gate.state !== "ready") { return (
diff --git a/dongle/components/ui/Pagination.tsx b/dongle/components/ui/Pagination.tsx new file mode 100644 index 0000000..4161c37 --- /dev/null +++ b/dongle/components/ui/Pagination.tsx @@ -0,0 +1,107 @@ +"use client"; + +import { ChevronLeft, ChevronRight } from "lucide-react"; + +interface PaginationProps { + currentPage: number; + totalPages: number; + onPageChange: (page: number) => void; + itemsPerPage?: number; + totalItems?: number; + showItemCount?: boolean; +} + +export default function Pagination({ + currentPage, + totalPages, + onPageChange, + itemsPerPage, + totalItems, + showItemCount = true, +}: PaginationProps) { + const getPageNumbers = () => { + const pages: (number | string)[] = []; + const maxVisible = 5; + + if (totalPages <= maxVisible) { + for (let i = 1; i <= totalPages; i++) { + pages.push(i); + } + } else { + pages.push(1); + + if (currentPage > 3) { + pages.push("..."); + } + + const start = Math.max(2, currentPage - 1); + const end = Math.min(totalPages - 1, currentPage + 1); + + for (let i = start; i <= end; i++) { + pages.push(i); + } + + if (currentPage < totalPages - 2) { + pages.push("..."); + } + + pages.push(totalPages); + } + + return pages; + }; + + const startItem = (currentPage - 1) * (itemsPerPage || 0) + 1; + const endItem = Math.min(currentPage * (itemsPerPage || 0), totalItems || 0); + + return ( +
+ {showItemCount && itemsPerPage && totalItems !== undefined && ( +
+ Showing {startItem} to{" "} + {endItem} of{" "} + {totalItems} items +
+ )} + +
+ + +
+ {getPageNumbers().map((page, index) => ( + + ))} +
+ + +
+
+ ); +} diff --git a/dongle/hooks/usePagination.ts b/dongle/hooks/usePagination.ts new file mode 100644 index 0000000..b69a889 --- /dev/null +++ b/dongle/hooks/usePagination.ts @@ -0,0 +1,64 @@ +import { useState, useMemo } from "react"; + +interface UsePaginationProps { + items: T[]; + itemsPerPage?: number; +} + +interface UsePaginationResult { + currentPage: number; + totalPages: number; + paginatedItems: T[]; + goToPage: (page: number) => void; + nextPage: () => void; + previousPage: () => void; + itemsPerPage: number; + totalItems: number; + hasNextPage: boolean; + hasPreviousPage: boolean; +} + +export function usePagination({ + items, + itemsPerPage = 10, +}: UsePaginationProps): UsePaginationResult { + const [currentPage, setCurrentPage] = useState(1); + + const totalPages = Math.ceil(items.length / itemsPerPage); + + const paginatedItems = useMemo(() => { + const startIndex = (currentPage - 1) * itemsPerPage; + const endIndex = startIndex + itemsPerPage; + return items.slice(startIndex, endIndex); + }, [items, currentPage, itemsPerPage]); + + const goToPage = (page: number) => { + const validPage = Math.max(1, Math.min(page, totalPages)); + setCurrentPage(validPage); + }; + + const nextPage = () => { + if (currentPage < totalPages) { + setCurrentPage((prev) => prev + 1); + } + }; + + const previousPage = () => { + if (currentPage > 1) { + setCurrentPage((prev) => prev - 1); + } + }; + + return { + currentPage, + totalPages, + paginatedItems, + goToPage, + nextPage, + previousPage, + itemsPerPage, + totalItems: items.length, + hasNextPage: currentPage < totalPages, + hasPreviousPage: currentPage > 1, + }; +}