From cc6065960b243498994988e8677b2e25774c92a1 Mon Sep 17 00:00:00 2001 From: GhaZy Date: Fri, 30 Oct 2026 00:18:18 +0200 Subject: [PATCH 1/2] fix(web): render complete pagination links including last page and neighbours (#3566) CapPagination only rendered page 1, current page, and current + 1 with an arbitrary trailing ellipsis, never linking to totalPages or allowing users to jump to the last page. In addition, PaginationNext rendered unconditionally, linking back to currentPage when on the last page. Added getPaginationItems in apps/web/lib/pagination-items.ts to compute the first and last page links, neighbouring active pages, and appropriately placed ellipses. Hid Next button on the last page. Added comprehensive unit tests. Closes #3566 --- .../web/__tests__/unit/cap-pagination.test.ts | 36 +++++++++++ .../caps/components/CapPagination.tsx | 62 ++++++++----------- apps/web/lib/pagination-items.ts | 36 +++++++++++ 3 files changed, 99 insertions(+), 35 deletions(-) create mode 100644 apps/web/__tests__/unit/cap-pagination.test.ts create mode 100644 apps/web/lib/pagination-items.ts 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 0000000000..63c62ccb6f --- /dev/null +++ b/apps/web/__tests__/unit/cap-pagination.test.ts @@ -0,0 +1,36 @@ +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 10ccca102c..4f0e810aa2 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 0000000000..6af5e9d8cf --- /dev/null +++ b/apps/web/lib/pagination-items.ts @@ -0,0 +1,36 @@ +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]; + items.push(current); + + if (i < sorted.length - 1) { + const next = sorted[i + 1]; + if (next - current === 2) { + items.push(current + 1); + } else if (next - current > 2) { + items.push("ellipsis"); + } + } + } + + return items; +} From 1dc717a09fc93e71f3d112fd3bbea4e771845d24 Mon Sep 17 00:00:00 2001 From: GhaZy Date: Sat, 31 Oct 2026 00:17:25 +0200 Subject: [PATCH 2/2] style(web): format pagination items and narrow element indexing (#3566) --- apps/web/__tests__/unit/cap-pagination.test.ts | 10 +++++++++- apps/web/lib/pagination-items.ts | 11 +++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/apps/web/__tests__/unit/cap-pagination.test.ts b/apps/web/__tests__/unit/cap-pagination.test.ts index 63c62ccb6f..6f07948408 100644 --- a/apps/web/__tests__/unit/cap-pagination.test.ts +++ b/apps/web/__tests__/unit/cap-pagination.test.ts @@ -22,7 +22,15 @@ describe("CapPagination getPaginationItems (#3566)", () => { 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]); + 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]); diff --git a/apps/web/lib/pagination-items.ts b/apps/web/lib/pagination-items.ts index 6af5e9d8cf..92ce4a1bcb 100644 --- a/apps/web/lib/pagination-items.ts +++ b/apps/web/lib/pagination-items.ts @@ -20,14 +20,17 @@ export function getPaginationItems( 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 - current === 2) { - items.push(current + 1); - } else if (next - current > 2) { - items.push("ellipsis"); + if (next !== undefined) { + if (next - current === 2) { + items.push(current + 1); + } else if (next - current > 2) { + items.push("ellipsis"); + } } } }