diff --git a/components/dashboard/src/Pagination/Pagination.tsx b/components/dashboard/src/Pagination/Pagination.tsx new file mode 100644 index 00000000000000..c4717821b341f9 --- /dev/null +++ b/components/dashboard/src/Pagination/Pagination.tsx @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2022 Gitpod GmbH. All rights reserved. + * Licensed under the GNU Affero General Public License (AGPL). + * See License-AGPL.txt in the project root for license information. + */ + +import { getPaginationNumbers } from "./getPagination"; +import Arrow from "../components/Arrow"; +import React from "react"; + +interface PaginationProps { + totalResults: number; + totalNumberOfPages: number; + currentPage: number; + setCurrentPage: React.Dispatch>; +} + +function Pagination({ totalNumberOfPages, currentPage, setCurrentPage }: PaginationProps) { + const calculatedPagination = getPaginationNumbers(totalNumberOfPages, currentPage); + + const nextPage = () => { + if (currentPage !== totalNumberOfPages) setCurrentPage(currentPage + 1); + }; + const prevPage = () => { + if (currentPage !== 1) setCurrentPage(currentPage - 1); + }; + const getClassnames = (pageNumber: string | number) => { + if (pageNumber === currentPage) { + return "text-gray-500 w-8 text-center rounded-md hover:bg-gray-50 bg-gray-100 disabled pointer-events-none"; + } + if (pageNumber === "...") { + return "text-gray-500 w-8 text-center rounded-md hover:bg-gray-50 disabled pointer-events-none"; + } + return "text-gray-500 w-8 text-center rounded-md hover:bg-gray-50 cursor-pointer"; + }; + + return ( + + ); +} + +export default Pagination; diff --git a/components/dashboard/src/Pagination/getPagination.spec.ts b/components/dashboard/src/Pagination/getPagination.spec.ts new file mode 100644 index 00000000000000..cf63c6506c9eeb --- /dev/null +++ b/components/dashboard/src/Pagination/getPagination.spec.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2022 Gitpod GmbH. All rights reserved. + * Licensed under the GNU Affero General Public License (AGPL). + * See License-AGPL.txt in the project root for license information. + */ + +import { getPaginationNumbers } from "./getPagination"; + +test("getPagination", () => { + const totalNumberOfPages = 15; + const currentPage = 1; + + expect(getPaginationNumbers(totalNumberOfPages, currentPage)).toStrictEqual([1, 2, 3, "...", 15]); + + expect(getPaginationNumbers(37, 4)).toStrictEqual([1, "...", 3, 4, 5, "...", 37]); + + expect(getPaginationNumbers(28, 7)).toStrictEqual([1, "...", 6, 7, 8, "...", 28]); + + expect(getPaginationNumbers(5, 1)).toStrictEqual([1, 2, 3, 4, 5]); +}); diff --git a/components/dashboard/src/Pagination/getPagination.ts b/components/dashboard/src/Pagination/getPagination.ts new file mode 100644 index 00000000000000..6834c00cc02a36 --- /dev/null +++ b/components/dashboard/src/Pagination/getPagination.ts @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2022 Gitpod GmbH. All rights reserved. + * Licensed under the GNU Affero General Public License (AGPL). + * See License-AGPL.txt in the project root for license information. + */ + +export function getPaginationNumbers(totalNumberOfPages: number, currentPage: number) { + const adjacentToCurrentPage = 1; // This is the number(s) we see next to the currentPage + const totalNumbersShownInPagination = 6; + let calculatedPagination: number[] = []; + + const pageNumbersAsArray = (startRange: number, endRange: number) => { + return [...Array(endRange + 1).keys()].slice(startRange); + }; + + const minimumAmountInBetweenToShowEllipsis = 2; + // Without ellipsis aka normal case + if (totalNumberOfPages <= totalNumbersShownInPagination) { + return (calculatedPagination = pageNumbersAsArray(1, totalNumberOfPages)); + } + + // Otherwise, we show the ellipses + const toTheRightOfCurrent = Math.min(currentPage + adjacentToCurrentPage, totalNumberOfPages); + const toTheLeftOfCurrent = Math.max(currentPage - adjacentToCurrentPage, 1); + + const showRightEllipsis = toTheRightOfCurrent < totalNumberOfPages - minimumAmountInBetweenToShowEllipsis; // e.g. "1 2 3 ... 7" + const showLeftEllipsis = toTheLeftOfCurrent > minimumAmountInBetweenToShowEllipsis; // e.g. 1 ... 5 6 7" + + if (showRightEllipsis && !showLeftEllipsis) { + let leftSideNumbers = 3; + let leftPageNumbersAsArray = pageNumbersAsArray(1, leftSideNumbers); + return [...leftPageNumbersAsArray, "...", totalNumberOfPages]; + } + + if (showLeftEllipsis && !showRightEllipsis) { + let rightSideNumbers = 3; + let rightPageNumbersAsArray = pageNumbersAsArray(totalNumberOfPages - rightSideNumbers, totalNumberOfPages); + return [1, "...", ...rightPageNumbersAsArray]; + } + + if (showRightEllipsis && showLeftEllipsis) { + let middleNumbers = pageNumbersAsArray(toTheLeftOfCurrent, toTheRightOfCurrent); + return [1, "...", ...middleNumbers, "...", totalNumberOfPages]; + } + + return calculatedPagination; +} diff --git a/components/dashboard/src/components/Pagination.tsx b/components/dashboard/src/components/Pagination.tsx deleted file mode 100644 index 69177626ab9850..00000000000000 --- a/components/dashboard/src/components/Pagination.tsx +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright (c) 2022 Gitpod GmbH. All rights reserved. - * Licensed under the GNU Affero General Public License (AGPL). - * See License-AGPL.txt in the project root for license information. - */ - -import Arrow from "./Arrow"; - -function Pagination(props: { numberOfPages: number; currentPage: number; setCurrentPage: any }) { - const { numberOfPages, currentPage, setCurrentPage } = props; - const availablePageNumbers = [...Array(numberOfPages + 1).keys()].slice(1); - - const nextPage = () => { - if (currentPage !== numberOfPages) setCurrentPage(currentPage + 1); - }; - const prevPage = () => { - if (currentPage !== 1) setCurrentPage(currentPage - 1); - }; - - return ( - - ); -} - -export default Pagination; diff --git a/components/dashboard/src/teams/TeamUsage.tsx b/components/dashboard/src/teams/TeamUsage.tsx index 66913ea289a09c..a12b094edc0a98 100644 --- a/components/dashboard/src/teams/TeamUsage.tsx +++ b/components/dashboard/src/teams/TeamUsage.tsx @@ -17,7 +17,7 @@ import { import { AttributionId } from "@gitpod/gitpod-protocol/lib/attribution"; import { Item, ItemField, ItemsList } from "../components/ItemsList"; import moment from "moment"; -import Pagination from "../components/Pagination"; +import Pagination from "../Pagination/Pagination"; import Header from "../components/Header"; import { ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error"; import { FeatureFlagContext } from "../contexts/FeatureFlagContext"; @@ -32,7 +32,7 @@ function TeamUsage() { const team = getCurrentTeam(location, teams); const [billedUsage, setBilledUsage] = useState([]); const [currentPage, setCurrentPage] = useState(1); - const [resultsPerPage] = useState(10); + const [resultsPerPage] = useState(15); const [errorMessage, setErrorMessage] = useState(""); const today = new Date(); const startOfCurrentMonth = new Date(today.getFullYear(), today.getMonth(), 1); @@ -126,7 +126,7 @@ function TeamUsage() { const lastResultOnCurrentPage = currentPage * resultsPerPage; const firstResultOnCurrentPage = lastResultOnCurrentPage - resultsPerPage; - const numberOfPages = Math.ceil(billedUsage.length / resultsPerPage); + const totalNumberOfPages = Math.ceil(billedUsage.length / resultsPerPage); const currentPaginatedResults = billedUsage.slice(firstResultOnCurrentPage, lastResultOnCurrentPage); return ( @@ -236,9 +236,10 @@ function TeamUsage() { {billedUsage.length > resultsPerPage && ( )}