diff --git a/apps/web/src/app/(overview)/history/page.tsx b/apps/web/src/app/(overview)/history/page.tsx index 83111b4..82fb5e6 100644 --- a/apps/web/src/app/(overview)/history/page.tsx +++ b/apps/web/src/app/(overview)/history/page.tsx @@ -7,7 +7,6 @@ import { useWallet } from "@/providers/StellarWalletProvider"; import DashboardLayout from "@/components/layouts/DashboardLayout"; import { ConnectWalletPrompt } from "@/components/layouts/ProtectedRoute"; import HistoryTable from "@/components/modules/history/HistoryTable"; -import HistoryTableSkeleton from "@/components/modules/history/HistoryTableSkeleton"; import { columns } from "@/components/modules/history/columns"; import { HistoryRecord } from "@/services/types"; import AppSelect from "@/components/molecules/AppSelect"; @@ -250,8 +249,10 @@ const HistoryPage = () => { page={page} limit={limit} totalCount={filteredData.length} - onPageChange={setPage} - onLimitChange={setLimit} + onPageChange={(nextPage) => updateHistoryParams({ page: nextPage })} + onLimitChange={(nextLimit) => + updateHistoryParams({ page: 1, limit: nextLimit }) + } onExport={handleExportCSV} isLoading={isLoading} /> diff --git a/apps/web/src/components/modules/history/HistoryTable.tsx b/apps/web/src/components/modules/history/HistoryTable.tsx index 9e313af..95cdb18 100644 --- a/apps/web/src/components/modules/history/HistoryTable.tsx +++ b/apps/web/src/components/modules/history/HistoryTable.tsx @@ -19,20 +19,12 @@ import { TableHeader, } from "@/components/ui/table"; -import { - Pagination, - PaginationContent, - PaginationItem, - PaginationLink, - PaginationNext, - PaginationPrevious, -} from "@/components/ui/pagination"; - import { HistoryRecord } from "@/services/types"; import AppSelect from "@/components/molecules/AppSelect"; import { validPageLimits } from "@/lib/constants"; import { Download } from "lucide-react"; import { Button } from "@/components/ui/button"; +import SlidingPagination from "@/components/molecules/SlidingPagination"; import ActionsCell from "./ActionsCell"; import { format } from "date-fns"; @@ -335,44 +327,12 @@ const HistoryTable = ({

Showing {data.length} of {totalCount} transactions

- - - - { - e.preventDefault(); - if (page > 1) onPageChange(page - 1); - }} - className={page <= 1 ? "pointer-events-none opacity-50" : "cursor-pointer"} - /> - - {Array.from({ length: Math.min(5, pageCount) }).map((_, i) => ( - - { - e.preventDefault(); - onPageChange(i + 1); - }} - isActive={page === i + 1} - > - {i + 1} - - - ))} - - { - e.preventDefault(); - if (page < pageCount) onPageChange(page + 1); - }} - className={page >= pageCount ? "pointer-events-none opacity-50" : "cursor-pointer"} - /> - - - + ); diff --git a/apps/web/src/components/modules/payment-stream/StreamsTable.tsx b/apps/web/src/components/modules/payment-stream/StreamsTable.tsx index 8c85d3d..ede22df 100644 --- a/apps/web/src/components/modules/payment-stream/StreamsTable.tsx +++ b/apps/web/src/components/modules/payment-stream/StreamsTable.tsx @@ -23,19 +23,11 @@ import { TableHeader, } from "@/components/ui/table"; -import { - Pagination, - PaginationNext, - PaginationLink, - PaginationContent, - PaginationPrevious, - PaginationEllipsis, -} from "@/components/ui/pagination"; - import { streamColumns } from "./streamColumns"; import { StreamRecord } from "@/lib/validations"; import { validPageLimits } from "@/lib/constants"; import AppSelect from "@/components/molecules/AppSelect"; +import SlidingPagination from "@/components/molecules/SlidingPagination"; interface StreamsTableProps { data: StreamRecord[]; @@ -86,9 +78,6 @@ function StreamsTable({ router.push(`?${params.toString()}`); }; - const canPreviousPage = page > 1; - const canNextPage = page < pageCount; - const pageSize = validPageLimits.map((limit) => ({ label: `${limit} per page`, value: limit.toString(), @@ -204,8 +193,8 @@ function StreamsTable({ {/* Pagination */} {totalCount > 0 && ( - - +
+

Showing

- +
Page {page} of {pageCount}
-
- - - updatePage(page - 1)} - disabled={!canPreviousPage} - /> - {Array.from( - { length: pageCount > 3 ? 3 : pageCount }, - (_, index) => ( - updatePage(index + 1)} - isActive={page === index + 1} - > - {index + 1} - - ) - )} - - {pageCount > 3 ? ( - - - updatePage(pageCount)} - isActive={page === pageCount} - > - {pageCount} - - - ) : null} - - updatePage(page + 1)} - disabled={!canNextPage} - /> - - +
+ + + )} ); diff --git a/apps/web/src/components/molecules/SlidingPagination.tsx b/apps/web/src/components/molecules/SlidingPagination.tsx new file mode 100644 index 0000000..558346d --- /dev/null +++ b/apps/web/src/components/molecules/SlidingPagination.tsx @@ -0,0 +1,123 @@ +"use client"; + +import { + Pagination, + PaginationContent, + PaginationEllipsis, + PaginationItem, + PaginationLink, + PaginationNext, + PaginationPrevious, +} from "@/components/ui/pagination"; +import { usePagination } from "@/hooks/use-pagination"; + +type SlidingPaginationProps = { + page: number; + pageCount: number; + onPageChange: (p: number) => void; + className?: string; +}; + +const clampPage = (value: number, min: number, max: number) => + Math.min(Math.max(value, min), max); + +const SlidingPagination = ({ + page, + pageCount, + onPageChange, + className, +}: SlidingPaginationProps) => { + const safePageCount = Math.max(1, pageCount); + const currentPage = clampPage(page, 1, safePageCount); + const items = usePagination({ + currentPage, + pageCount: safePageCount, + siblingCount: 2, + }); + + const navigate = (targetPage: number) => { + const nextPage = clampPage(targetPage, 1, safePageCount); + if (nextPage !== currentPage) { + onPageChange(nextPage); + } + }; + + return ( + + + + { + e.preventDefault(); + navigate(1); + }} + > + First + + + + + { + e.preventDefault(); + navigate(currentPage - 1); + }} + /> + + + {items.map((item) => ( + + {item.type === "ellipsis" ? ( + + ) : ( + { + e.preventDefault(); + navigate(item.page); + }} + > + {item.page} + + )} + + ))} + + + { + e.preventDefault(); + navigate(currentPage + 1); + }} + /> + + + + { + e.preventDefault(); + navigate(safePageCount); + }} + > + Last + + + + + ); +}; + +export default SlidingPagination; diff --git a/apps/web/src/hooks/use-pagination.ts b/apps/web/src/hooks/use-pagination.ts new file mode 100644 index 0000000..24d6ef9 --- /dev/null +++ b/apps/web/src/hooks/use-pagination.ts @@ -0,0 +1,69 @@ +import { useMemo } from "react"; + +export type PaginationRangeItem = + | { type: "page"; page: number } + | { type: "ellipsis"; key: "left" | "right" }; + +type UsePaginationParams = { + currentPage: number; + pageCount: number; + siblingCount?: number; +}; + +const range = (start: number, end: number) => + Array.from({ length: end - start + 1 }, (_, index) => start + index); + +export const getPaginationRange = ({ + currentPage, + pageCount, + siblingCount = 2, +}: UsePaginationParams): PaginationRangeItem[] => { + const safePageCount = Math.max(1, pageCount); + const safeCurrentPage = Math.min(Math.max(1, currentPage), safePageCount); + const windowSize = siblingCount * 2 + 1; + + if (safePageCount <= 7) { + return range(1, safePageCount).map((page) => ({ type: "page", page })); + } + + let left = Math.max(2, safeCurrentPage - siblingCount); + let right = Math.min(safePageCount - 1, safeCurrentPage + siblingCount); + + while (right - left + 1 < windowSize) { + if (left > 2) { + left -= 1; + continue; + } + + if (right < safePageCount - 1) { + right += 1; + continue; + } + + break; + } + + const showLeftEllipsis = left > 2; + const showRightEllipsis = right < safePageCount - 1; + + const items: PaginationRangeItem[] = [{ type: "page", page: 1 }]; + + if (showLeftEllipsis) { + items.push({ type: "ellipsis", key: "left" }); + } + + range(left, right).forEach((page) => { + items.push({ type: "page", page }); + }); + + if (showRightEllipsis) { + items.push({ type: "ellipsis", key: "right" }); + } + + items.push({ type: "page", page: safePageCount }); + + return items; +}; + +export const usePagination = (params: UsePaginationParams) => + useMemo(() => getPaginationRange(params), [params]);