diff --git a/apps/web/__tests__/unit/cap-pagination.test.ts b/apps/web/__tests__/unit/cap-pagination.test.ts new file mode 100644 index 00000000000..6f07948408d --- /dev/null +++ b/apps/web/__tests__/unit/cap-pagination.test.ts @@ -0,0 +1,44 @@ +import { describe, expect, it } from "vitest"; +import { getPaginationItems } from "../../lib/pagination-items"; + +describe("CapPagination getPaginationItems (#3566)", () => { + it("returns single page when totalPages <= 1", () => { + expect(getPaginationItems(1, 0)).toEqual([1]); + expect(getPaginationItems(1, 1)).toEqual([1]); + }); + + it("returns all pages for small page counts without ellipses (totalPages <= 5)", () => { + expect(getPaginationItems(1, 4)).toEqual([1, 2, 3, 4]); + expect(getPaginationItems(2, 4)).toEqual([1, 2, 3, 4]); + expect(getPaginationItems(3, 4)).toEqual([1, 2, 3, 4]); + expect(getPaginationItems(4, 4)).toEqual([1, 2, 3, 4]); + expect(getPaginationItems(3, 5)).toEqual([1, 2, 3, 4, 5]); + }); + + it("always links first and last page with ellipses for large page counts", () => { + // Viewing start + expect(getPaginationItems(1, 10)).toEqual([1, 2, "ellipsis", 10]); + expect(getPaginationItems(2, 10)).toEqual([1, 2, 3, "ellipsis", 10]); + expect(getPaginationItems(3, 10)).toEqual([1, 2, 3, 4, "ellipsis", 10]); + + // Viewing middle + expect(getPaginationItems(5, 10)).toEqual([ + 1, + "ellipsis", + 4, + 5, + 6, + "ellipsis", + 10, + ]); + + // Viewing end + expect(getPaginationItems(8, 10)).toEqual([1, "ellipsis", 7, 8, 9, 10]); + expect(getPaginationItems(9, 10)).toEqual([1, "ellipsis", 8, 9, 10]); + expect(getPaginationItems(10, 10)).toEqual([1, "ellipsis", 9, 10]); + }); + + it("inserts bridging page number instead of ellipsis when difference is 2", () => { + expect(getPaginationItems(3, 6)).toEqual([1, 2, 3, 4, 5, 6]); + }); +}); diff --git a/apps/web/app/(org)/dashboard/caps/components/CapPagination.tsx b/apps/web/app/(org)/dashboard/caps/components/CapPagination.tsx index 10ccca102c6..4f0e810aa2a 100644 --- a/apps/web/app/(org)/dashboard/caps/components/CapPagination.tsx +++ b/apps/web/app/(org)/dashboard/caps/components/CapPagination.tsx @@ -7,6 +7,9 @@ import { PaginationNext, PaginationPrevious, } from "@cap/ui"; +import { getPaginationItems } from "@/lib/pagination-items"; + +export { getPaginationItems }; interface CapPaginationProps { currentPage: number; @@ -19,6 +22,8 @@ export const CapPagination: React.FC = ({ totalPages, hrefForPage = (page) => `/dashboard/caps?page=${page}`, }) => { + const items = getPaginationItems(currentPage, totalPages); + return ( @@ -30,46 +35,33 @@ export const CapPagination: React.FC = ({ /> )} - - - 1 - - - {currentPage !== 1 && ( - - - {currentPage} - - + {items.map((item, index) => + item === "ellipsis" ? ( + + ) : ( + + + {item} + + + ), )} - {totalPages > currentPage + 1 && ( + {currentPage < totalPages && ( - - {currentPage + 1} - + /> )} - {currentPage > 2 && } - - - ); diff --git a/apps/web/lib/pagination-items.ts b/apps/web/lib/pagination-items.ts new file mode 100644 index 00000000000..92ce4a1bcb8 --- /dev/null +++ b/apps/web/lib/pagination-items.ts @@ -0,0 +1,39 @@ +export function getPaginationItems( + currentPage: number, + totalPages: number, +): (number | "ellipsis")[] { + if (totalPages <= 1) return [1]; + + if (totalPages <= 5) { + return Array.from({ length: totalPages }, (_, i) => i + 1); + } + + const pages = new Set(); + pages.add(1); + pages.add(totalPages); + pages.add(currentPage); + if (currentPage > 1) pages.add(currentPage - 1); + if (currentPage < totalPages) pages.add(currentPage + 1); + + const sorted = Array.from(pages).sort((a, b) => a - b); + const items: (number | "ellipsis")[] = []; + + for (let i = 0; i < sorted.length; i++) { + const current = sorted[i]; + if (current === undefined) continue; + items.push(current); + + if (i < sorted.length - 1) { + const next = sorted[i + 1]; + if (next !== undefined) { + if (next - current === 2) { + items.push(current + 1); + } else if (next - current > 2) { + items.push("ellipsis"); + } + } + } + } + + return items; +}