E3WSKRJTPPRD6ZEEURTO77N6GTHYJY7ISWQL2LN2VCCXA4YRH2CAC 4FBIL6IZUDNCXTM6EUHTEOJRHVI4LIIX4BU2IXPXKR362GKIAJMQC M3JUJ2WWZGCVMBITKRM5FUJMHFYL2QRMXJUVRUE4AC2RF74AOL5AC HM75N4NTZ4BBSSDC7TUSYOQ4SIF3G6KPZA5QRYCVCVRSKQVTJAXAC PVQBFR72OCQGYF2G2KDWNKBHWJ24N6D653X6KARBGUSYBIHIXPRQC 4M3EBLTLSS2BRCM42ZP7WVD4YMRRLGV2P2XF47IAV5XHHJD52HTQC RLH37YB4D7O42IFM2T7GJG4AVVAURWBZ7AOTHAWR7YJZRG3JOPLQC ROQGXQWL2V363K3W7TVVYKIAX4N4IWRERN5BJ7NYJRRVB6OMIJ4QC LYPSC7BOH6T45FCPRHSCXILAJSJ74D5WSQTUIKPWD5ECXOYGUY5AC import React from "react";import Button from "./button";import { cn } from "@/utils";export interface PaginationProps {/*** Current page number (1-based)*/currentPage: number;/*** Total number of pages*/totalPages: number;/*** Callback when page changes*/onPageChange: (page: number) => void;/*** Additional CSS class names*/className?: string;/*** Show empty pagination (for loading states)*/disabled?: boolean;}/*** Shared pagination component that provides consistent navigation across the application*/export function Pagination({currentPage,totalPages,onPageChange,className,disabled = false,}: PaginationProps) {// Don't render pagination if there's only one page or we're in disabled stateif (totalPages <= 1 || disabled) {return null;}// Function to handle page navigationconst handlePageChange = (newPage: number) => {if (newPage >= 1 && newPage <= totalPages) {onPageChange(newPage);}};// Generate the page buttonsconst renderPageButtons = () => {const pages: React.ReactNode[] = [];// Always show first pageif (currentPage > 3) {pages.push(<Buttonkey="page-1"variant={currentPage === 1 ? "default" : "outline"}size="icon"className="h-8 w-8 rounded-md"onClick={() => handlePageChange(1)}>1</Button>);// Add ellipsis if not showing page 2if (currentPage > 4) {pages.push(<span key="ellipsis1" className="px-1">…</span>);}}// Show current page and surrounding pagesconst startPage = Math.max(1, currentPage - 1);const endPage = Math.min(totalPages, currentPage + 1);for (let i = startPage; i <= endPage; i++) {if (i === 1 || i === totalPages) continue; // Skip first and last pages as they're handled separatelypages.push(<Buttonkey={`page-${i}`}variant={currentPage === i ? "default" : "outline"}size="icon"className="h-8 w-8 rounded-md"onClick={() => handlePageChange(i)}>{i}</Button>);}// Add intermediate points for large page countsconst checkpoints = [10, 20, 30, 40];if (totalPages > 5) {for (const checkpoint of checkpoints) {if (checkpoint > currentPage + 2 && checkpoint < totalPages - 2) {// Only insert checkpoint if it's not close to what we already displayif (!pages.some(p => React.isValidElement(p) && p.key === `page-${checkpoint}`)) {pages.push(<Buttonkey={`page-${checkpoint}`}variant="outline"size="icon"className="h-8 w-8 rounded-md"onClick={() => handlePageChange(checkpoint)}>{checkpoint}</Button>);// Insert only one checkpoint buttonbreak;}}}}// Add ellipsis if neededif (currentPage < totalPages - 3) {pages.push(<span key="ellipsis2" className="px-1">…</span>);}// Always show last pageif (totalPages > 1) {pages.push(<Buttonkey={`page-${totalPages}`}variant={currentPage === totalPages ? "default" : "outline"}size="icon"className="h-8 w-8 rounded-md"onClick={() => handlePageChange(totalPages)}>{totalPages}</Button>);}return pages;};return (<div className={cn("flex justify-center items-center mt-6", className)}><nav className="flex items-center gap-1" aria-label="Pagination">{/* First page button */}<Buttonvariant="outline"size="icon"className="h-8 w-8 rounded-md"onClick={() => handlePageChange(1)}disabled={currentPage === 1}aria-label="First page"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="11 17 6 12 11 7"></polyline><polyline points="18 17 13 12 18 7"></polyline></svg></Button>{/* Previous page button */}<Buttonvariant="outline"size="icon"className="h-8 w-8 rounded-md"onClick={() => handlePageChange(currentPage - 1)}disabled={currentPage === 1}aria-label="Previous page"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg></Button>{/* Page number buttons */}{renderPageButtons()}{/* Next page button */}<Buttonvariant="outline"size="icon"className="h-8 w-8 rounded-md"onClick={() => handlePageChange(currentPage + 1)}disabled={currentPage === totalPages}aria-label="Next page"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg></Button>{/* Last page button */}<Buttonvariant="outline"size="icon"className="h-8 w-8 rounded-md"onClick={() => handlePageChange(totalPages)}disabled={currentPage === totalPages}aria-label="Last page"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="13 17 18 12 13 7"></polyline><polyline points="6 17 11 12 6 7"></polyline></svg></Button></nav></div>);}
<div className="flex justify-center items-center mt-6"><nav className="flex items-center gap-1" aria-label="Pagination">{/* First page button */}<Buttonvariant="outline"size="icon"className="h-8 w-8 rounded-md"onClick={() => handlePageChange(1)}disabled={currentPage === 1}aria-label="First page"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="11 17 6 12 11 7"></polyline><polyline points="18 17 13 12 18 7"></polyline></svg></Button>{/* Previous page button */}<Buttonvariant="outline"size="icon"className="h-8 w-8 rounded-md"onClick={() => handlePageChange(currentPage - 1)}disabled={currentPage === 1}aria-label="Previous page"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg></Button>{/* Page number buttons */}{(() => {const totalPages = pagination.totalPages;const current = currentPage;const pages = [];// Always show first pageif (current > 3) {pages.push(<Buttonkey="page-1"variant={current === 1 ? "default" : "outline"}size="icon"className="h-8 w-8 rounded-md"onClick={() => handlePageChange(1)}>1</Button>);// Add ellipsis if not showing page 2if (current > 4) {pages.push(<span key="ellipsis1" className="px-1">…</span>);}}// Show current page and surrounding pagesconst startPage = Math.max(1, current - 1);const endPage = Math.min(totalPages, current + 1);for (let i = startPage; i <= endPage; i++) {if (i === 1 || i === totalPages) continue; // Skip first and last pages as they're handled separatelypages.push(<Buttonkey={`page-${i}`}variant={current === i ? "default" : "outline"}size="icon"className="h-8 w-8 rounded-md"onClick={() => handlePageChange(i)}>{i}</Button>);}// Add intermediate pointsconst checkpoints = [10, 20, 30, 40];if (totalPages > 5) {for (const checkpoint of checkpoints) {if (checkpoint > current + 2 && checkpoint < totalPages - 2) {// Only insert checkpoint if it's not close to what we already displayif (!pages.some(p => p.key === `page-${checkpoint}`)) {pages.push(<Buttonkey={`page-${checkpoint}`}variant="outline"size="icon"className="h-8 w-8 rounded-md"onClick={() => handlePageChange(checkpoint)}>{checkpoint}</Button>);// Insert only one checkpoint buttonbreak;}}}}// Add ellipsis if neededif (current < totalPages - 3) {pages.push(<span key="ellipsis2" className="px-1">…</span>);}// Always show last pageif (totalPages > 1) {pages.push(<Buttonkey={`page-${totalPages}`}variant={current === totalPages ? "default" : "outline"}size="icon"className="h-8 w-8 rounded-md"onClick={() => handlePageChange(totalPages)}>{totalPages}</Button>);}return pages;})()}{/* Next page button */}<Buttonvariant="outline"size="icon"className="h-8 w-8 rounded-md"onClick={() => handlePageChange(currentPage + 1)}disabled={currentPage === pagination.totalPages}aria-label="Next page"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg></Button>{/* Last page button */}<Buttonvariant="outline"size="icon"className="h-8 w-8 rounded-md"onClick={() => handlePageChange(pagination.totalPages)}disabled={currentPage === pagination.totalPages}aria-label="Last page"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="13 17 18 12 13 7"></polyline><polyline points="6 17 11 12 6 7"></polyline></svg></Button></nav></div>
<PaginationcurrentPage={currentPage}totalPages={pagination.totalPages}onPageChange={handlePageChange}/>
<div className="flex justify-center items-center mt-6"><nav className="flex items-center gap-1" aria-label="Pagination">{/* First page button */}<Buttonvariant="outline"size="icon"className="h-8 w-8 rounded-md"onClick={() => handlePageChange(1)}disabled={currentPage === 1}aria-label="First page"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="11 17 6 12 11 7"></polyline><polyline points="18 17 13 12 18 7"></polyline></svg></Button>{/* Previous page button */}<Buttonvariant="outline"size="icon"className="h-8 w-8 rounded-md"onClick={() => handlePageChange(currentPage - 1)}disabled={currentPage === 1}aria-label="Previous page"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg></Button>{/* Page number buttons */}{(() => {const totalPagesCount = totalPages;const current = currentPage;const pages = [];// Always show first pageif (current > 3) {pages.push(<Buttonkey="page-1"variant={current === 1 ? "default" : "outline"}size="icon"className="h-8 w-8 rounded-md"onClick={() => handlePageChange(1)}>1</Button>);// Add ellipsis if not showing page 2if (current > 4) {pages.push(<span key="ellipsis1" className="px-1">…</span>);}}// Show current page and surrounding pagesconst startPage = Math.max(1, current - 1);const endPage = Math.min(totalPagesCount, current + 1);for (let i = startPage; i <= endPage; i++) {if (i === 1 || i === totalPagesCount) continue; // Skip first and last pages as they're handled separatelypages.push(<Buttonkey={`page-${i}`}variant={current === i ? "default" : "outline"}size="icon"className="h-8 w-8 rounded-md"onClick={() => handlePageChange(i)}>{i}</Button>);}// Add ellipsis if neededif (current < totalPagesCount - 3) {pages.push(<span key="ellipsis2" className="px-1">…</span>);}// Always show last pageif (totalPagesCount > 1) {pages.push(<Buttonkey={`page-${totalPagesCount}`}variant={current === totalPagesCount ? "default" : "outline"}size="icon"className="h-8 w-8 rounded-md"onClick={() => handlePageChange(totalPagesCount)}>{totalPagesCount}</Button>);}return pages;})()}{/* Next page button */}<Buttonvariant="outline"size="icon"className="h-8 w-8 rounded-md"onClick={() => handlePageChange(currentPage + 1)}disabled={currentPage === totalPages}aria-label="Next page"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg></Button>{/* Last page button */}<Buttonvariant="outline"size="icon"className="h-8 w-8 rounded-md"onClick={() => handlePageChange(totalPages)}disabled={currentPage === totalPages}aria-label="Last page"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="13 17 18 12 13 7"></polyline><polyline points="6 17 11 12 6 7"></polyline></svg></Button></nav></div>
<PaginationcurrentPage={currentPage}totalPages={totalPages}onPageChange={handlePageChange}/>
<div className="flex justify-center items-center mt-6"><nav className="flex items-center gap-1" aria-label="Pagination">{/* First page button */}<Buttonvariant="outline"size="icon"className="h-8 w-8 rounded-md"onClick={() => handlePageChange(1)}disabled={currentPage === 1}aria-label="First page"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="11 17 6 12 11 7"></polyline><polyline points="18 17 13 12 18 7"></polyline></svg></Button>{/* Previous page button */}<Buttonvariant="outline"size="icon"className="h-8 w-8 rounded-md"onClick={() => handlePageChange(currentPage - 1)}disabled={currentPage === 1}aria-label="Previous page"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg></Button>{/* Page number buttons */}{(() => {const totalPages = pagination.totalPages;const current = currentPage;const pages = [];// Always show first pageif (current > 3) {pages.push(<Buttonkey="page-1"variant={current === 1 ? "default" : "outline"}size="icon"className="h-8 w-8 rounded-md"onClick={() => handlePageChange(1)}>1</Button>);// Add ellipsis if not showing page 2if (current > 4) {pages.push(<span key="ellipsis1" className="px-1">…</span>);}}// Show current page and surrounding pagesconst startPage = Math.max(1, current - 1);const endPage = Math.min(totalPages, current + 1);for (let i = startPage; i <= endPage; i++) {if (i === 1 || i === totalPages) continue; // Skip first and last pages as they're handled separatelypages.push(<Buttonkey={`page-${i}`}variant={current === i ? "default" : "outline"}size="icon"className="h-8 w-8 rounded-md"onClick={() => handlePageChange(i)}>{i}</Button>);}// Add intermediate pointsconst checkpoints = [10, 20, 30, 40];if (totalPages > 5) {for (const checkpoint of checkpoints) {if (checkpoint > current + 2 && checkpoint < totalPages - 2) {// Only insert checkpoint if it's not close to what we already displayif (!pages.some(p => p.key === `page-${checkpoint}`)) {pages.push(<Buttonkey={`page-${checkpoint}`}variant="outline"size="icon"className="h-8 w-8 rounded-md"onClick={() => handlePageChange(checkpoint)}>{checkpoint}</Button>);// Insert only one checkpoint buttonbreak;}}}}// Add ellipsis if neededif (current < totalPages - 3) {pages.push(<span key="ellipsis2" className="px-1">…</span>);}// Always show last pageif (totalPages > 1) {pages.push(<Buttonkey={`page-${totalPages}`}variant={current === totalPages ? "default" : "outline"}size="icon"className="h-8 w-8 rounded-md"onClick={() => handlePageChange(totalPages)}>{totalPages}</Button>);}return pages;})()}{/* Next page button */}<Buttonvariant="outline"size="icon"className="h-8 w-8 rounded-md"onClick={() => handlePageChange(currentPage + 1)}disabled={currentPage === pagination.totalPages}aria-label="Next page"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg></Button>{/* Last page button */}<Buttonvariant="outline"size="icon"className="h-8 w-8 rounded-md"onClick={() => handlePageChange(pagination.totalPages)}disabled={currentPage === pagination.totalPages}aria-label="Last page"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="13 17 18 12 13 7"></polyline><polyline points="6 17 11 12 6 7"></polyline></svg></Button></nav></div>
<PaginationcurrentPage={currentPage}totalPages={pagination.totalPages}onPageChange={handlePageChange}/>