From 84d7777c04aceed21023e9bfd68b34475d40c0a8 Mon Sep 17 00:00:00 2001 From: Peter Makowski Date: Thu, 8 Feb 2024 10:09:04 +0100 Subject: [PATCH] feat: maas-react-components pagination (#5311) --- package.json | 2 +- .../base/components/ActionBar/ActionBar.tsx | 27 +++-- src/app/base/components/ActionBar/_index.scss | 1 - .../ListDisplayCount.test.tsx | 26 ++++ .../ListDisplayCount/ListDisplayCount.tsx | 38 ++++++ .../components/ListDisplayCount/_index.scss | 5 + .../base/components/ListDisplayCount/index.ts | 1 + .../MainContentSection/MainContentSection.tsx | 4 +- .../components/MainContentSection/index.ts | 1 + .../NodeLogs/EventLogs/EventLogs.test.tsx | 7 +- .../node/NodeLogs/EventLogs/EventLogs.tsx | 94 +++++++-------- .../MachineListDisplayCount.test.tsx | 33 ------ .../MachineListDisplayCount.tsx | 37 ------ .../MachineListDisplayCount/_index.scss | 5 - .../MachineListDisplayCount/index.ts | 1 - .../MachineListPagination.tsx | 111 +++++++----------- .../MachineListPagination/_index.scss | 2 +- .../MachineListTable/MachineListTable.tsx | 9 +- src/app/tags/views/TagList/TagList.tsx | 60 ++++++---- src/scss/index.scss | 4 +- yarn.lock | 8 +- 21 files changed, 233 insertions(+), 243 deletions(-) create mode 100644 src/app/base/components/ListDisplayCount/ListDisplayCount.test.tsx create mode 100644 src/app/base/components/ListDisplayCount/ListDisplayCount.tsx create mode 100644 src/app/base/components/ListDisplayCount/_index.scss create mode 100644 src/app/base/components/ListDisplayCount/index.ts delete mode 100644 src/app/machines/views/MachineList/MachineListTable/MachineListDisplayCount/MachineListDisplayCount.test.tsx delete mode 100644 src/app/machines/views/MachineList/MachineListTable/MachineListDisplayCount/MachineListDisplayCount.tsx delete mode 100644 src/app/machines/views/MachineList/MachineListTable/MachineListDisplayCount/_index.scss delete mode 100644 src/app/machines/views/MachineList/MachineListTable/MachineListDisplayCount/index.ts diff --git a/package.json b/package.json index d7ccfa40b3..e1ba4f9230 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "build-storybook": "storybook build" }, "dependencies": { - "@canonical/maas-react-components": "1.19.1", + "@canonical/maas-react-components": "1.21.0", "@canonical/macaroon-bakery": "1.3.2", "@canonical/react-components": "0.46.0", "@reduxjs/toolkit": "1.9.3", diff --git a/src/app/base/components/ActionBar/ActionBar.tsx b/src/app/base/components/ActionBar/ActionBar.tsx index eadc4803dd..fa944ed48c 100644 --- a/src/app/base/components/ActionBar/ActionBar.tsx +++ b/src/app/base/components/ActionBar/ActionBar.tsx @@ -1,8 +1,8 @@ import type { ReactNode } from "react"; +import { Pagination } from "@canonical/maas-react-components"; import type { SearchBoxProps } from "@canonical/react-components"; -import ArrowPagination from "@/app/base/components/ArrowPagination"; import SearchBox from "@/app/base/components/SearchBox"; type Props = { @@ -30,7 +30,7 @@ const ActionBar = ({ return (
{actions &&
{actions}
} -
+
- +
+ {}} + onInputChange={(e) => { + setCurrentPage(Number(e.target.value)); + }} + onNextClick={() => setCurrentPage(currentPage + 1)} + onPreviousClick={() => setCurrentPage(currentPage - 1)} + totalPages={Math.ceil(itemCount / pageSize)} + /> +
); diff --git a/src/app/base/components/ActionBar/_index.scss b/src/app/base/components/ActionBar/_index.scss index bc6286149d..0379b62b91 100644 --- a/src/app/base/components/ActionBar/_index.scss +++ b/src/app/base/components/ActionBar/_index.scss @@ -49,7 +49,6 @@ .action-bar__search { margin: 0 $sph--large 0 0; - max-width: 50rem; } } } diff --git a/src/app/base/components/ListDisplayCount/ListDisplayCount.test.tsx b/src/app/base/components/ListDisplayCount/ListDisplayCount.test.tsx new file mode 100644 index 0000000000..f31eece5cf --- /dev/null +++ b/src/app/base/components/ListDisplayCount/ListDisplayCount.test.tsx @@ -0,0 +1,26 @@ +import ListDisplayCount from "./ListDisplayCount"; + +import { render, screen } from "@/testing/utils"; + +it("shows the true number of items on a page if it is under the maximum items per page limit.", () => { + render( + + ); + + expect(screen.getByText("Showing 35 out of 135 tags")).toBeInTheDocument(); +}); + +it("shows the maximum number of items per page if that limit is reached", () => { + render( + + ); + + expect( + screen.getByText("Showing 50 out of 135 machines") + ).toBeInTheDocument(); +}); diff --git a/src/app/base/components/ListDisplayCount/ListDisplayCount.tsx b/src/app/base/components/ListDisplayCount/ListDisplayCount.tsx new file mode 100644 index 0000000000..9067350002 --- /dev/null +++ b/src/app/base/components/ListDisplayCount/ListDisplayCount.tsx @@ -0,0 +1,38 @@ +export const getCurrentPageDisplayedCount = ( + count: number | null, + pageSize: number, + currentPage: number +): number => { + if (!count) { + return 0; + } + + const totalPages = Math.ceil(count / pageSize); + + if (currentPage === totalPages) { + return pageSize - (totalPages * pageSize - count); + } else { + return pageSize; + } +}; + +export const ListDisplayCount = ({ + count, + pageSize, + currentPage, + type = "item", +}: { + count: number; + pageSize: number; + currentPage: number; + type: string; +}): JSX.Element => { + return ( + + Showing {getCurrentPageDisplayedCount(count, pageSize, currentPage)} out + of {count} {type}s + + ); +}; + +export default ListDisplayCount; diff --git a/src/app/base/components/ListDisplayCount/_index.scss b/src/app/base/components/ListDisplayCount/_index.scss new file mode 100644 index 0000000000..f555d2afab --- /dev/null +++ b/src/app/base/components/ListDisplayCount/_index.scss @@ -0,0 +1,5 @@ +@mixin ListDisplayCount { + .list--display-count { + padding-bottom: $spv--small; + } +} \ No newline at end of file diff --git a/src/app/base/components/ListDisplayCount/index.ts b/src/app/base/components/ListDisplayCount/index.ts new file mode 100644 index 0000000000..3a24a0f94b --- /dev/null +++ b/src/app/base/components/ListDisplayCount/index.ts @@ -0,0 +1 @@ +export { default } from "./ListDisplayCount"; diff --git a/src/app/base/components/MainContentSection/MainContentSection.tsx b/src/app/base/components/MainContentSection/MainContentSection.tsx index c6882c31d8..6ac0538c85 100644 --- a/src/app/base/components/MainContentSection/MainContentSection.tsx +++ b/src/app/base/components/MainContentSection/MainContentSection.tsx @@ -14,6 +14,8 @@ export type Props = { isNotificationListHidden?: boolean; } & HTMLProps; +export const MAIN_CONTENT_SECTION_ID = "main-content-section"; + const MainContentSection = ({ children, header, @@ -23,7 +25,7 @@ const MainContentSection = ({ }: Props): JSX.Element => { const { SIDEBAR, TOTAL } = COL_SIZES; return ( -
+
{header ? (
diff --git a/src/app/base/components/MainContentSection/index.ts b/src/app/base/components/MainContentSection/index.ts index 3e20e89a2e..e03dfd14d6 100644 --- a/src/app/base/components/MainContentSection/index.ts +++ b/src/app/base/components/MainContentSection/index.ts @@ -1 +1,2 @@ export { default } from "./MainContentSection"; +export * from "./MainContentSection"; diff --git a/src/app/base/components/node/NodeLogs/EventLogs/EventLogs.test.tsx b/src/app/base/components/node/NodeLogs/EventLogs/EventLogs.test.tsx index 6fce512640..ca75514865 100644 --- a/src/app/base/components/node/NodeLogs/EventLogs/EventLogs.test.tsx +++ b/src/app/base/components/node/NodeLogs/EventLogs/EventLogs.test.tsx @@ -6,6 +6,7 @@ import type { Mock } from "vitest"; import EventLogs, { Label } from "./EventLogs"; import { Labels as ArrowPaginationLabels } from "@/app/base/components/ArrowPagination"; +import { MAIN_CONTENT_SECTION_ID } from "@/app/base/components/MainContentSection"; import type { MachineDetails } from "@/app/store/machine/types"; import type { RootState } from "@/app/store/root/types"; import { @@ -22,6 +23,7 @@ import { screen, within, renderWithMockStore, + renderWithBrowserRouter, } from "@/testing/utils"; const mockStore = configureStore(); @@ -314,11 +316,12 @@ describe("EventLogs", () => { }) ); } - renderWithMockStore(, { + renderWithBrowserRouter(, { state, }); await userEvent.selectOptions(screen.getByRole("combobox"), "50"); + expect(window.location.hash).toBe(""); await userEvent.click(screen.getByRole("link", { name: Label.BackToTop })); - expect(scrollToSpy).toHaveBeenCalled(); + expect(window.location.hash).toBe(`#${MAIN_CONTENT_SECTION_ID}`); }); }); diff --git a/src/app/base/components/node/NodeLogs/EventLogs/EventLogs.tsx b/src/app/base/components/node/NodeLogs/EventLogs/EventLogs.tsx index a88a3cff5c..c5062d658b 100644 --- a/src/app/base/components/node/NodeLogs/EventLogs/EventLogs.tsx +++ b/src/app/base/components/node/NodeLogs/EventLogs/EventLogs.tsx @@ -1,12 +1,13 @@ import { useEffect, useState } from "react"; -import { Col, Link, Row, Select, Spinner } from "@canonical/react-components"; +import { Link, Select, Spinner } from "@canonical/react-components"; import { useDispatch, useSelector } from "react-redux"; import { useStorageState } from "react-storage-hooks"; import EventLogsTable from "./EventLogsTable"; import ArrowPagination from "@/app/base/components/ArrowPagination"; +import { MAIN_CONTENT_SECTION_ID } from "@/app/base/components/MainContentSection/MainContentSection"; import SearchBox from "@/app/base/components/SearchBox"; import type { ControllerDetails } from "@/app/store/controller/types"; import { actions as eventActions } from "@/app/store/event"; @@ -142,68 +143,53 @@ const EventLogs = ({ node }: Props): JSX.Element => { return (
- - +
+
- - - Show - ) => { + setPageSize(Number(evt.target.value)); + }} + options={[ + { + value: "25", + label: "25/page", + }, + { + value: "50", + label: "50/page", + }, + { + value: "100", + label: "100/page", + }, + { + value: "200", + label: "200/page", + }, + ]} + wrapperClassName="u-display--inline-block u-nudge-right" + /> +

{loading && } {showBackToTop && ( - ) => { - evt.preventDefault(); - window.scrollTo({ - top: 0, - left: 0, - behavior: "smooth", - }); - }} - top - > + {Label.BackToTop} )} diff --git a/src/app/machines/views/MachineList/MachineListTable/MachineListDisplayCount/MachineListDisplayCount.test.tsx b/src/app/machines/views/MachineList/MachineListTable/MachineListDisplayCount/MachineListDisplayCount.test.tsx deleted file mode 100644 index 2ed482a6e4..0000000000 --- a/src/app/machines/views/MachineList/MachineListTable/MachineListDisplayCount/MachineListDisplayCount.test.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import MachineListDisplayCount from "./MachineListDisplayCount"; - -import { render, screen } from "@/testing/utils"; - -describe("MachineListDisplayCount", () => { - it("shows the true number of machines on a page if it is under the maximum items per page limit.", () => { - render( - - ); - - expect( - screen.getByText("Showing 35 out of 135 machines") - ).toBeInTheDocument(); - }); - - it("shows the maximum number of items per page if that limit is reached", () => { - render( - - ); - - expect( - screen.getByText("Showing 50 out of 135 machines") - ).toBeInTheDocument(); - }); -}); diff --git a/src/app/machines/views/MachineList/MachineListTable/MachineListDisplayCount/MachineListDisplayCount.tsx b/src/app/machines/views/MachineList/MachineListTable/MachineListDisplayCount/MachineListDisplayCount.tsx deleted file mode 100644 index 682e88932f..0000000000 --- a/src/app/machines/views/MachineList/MachineListTable/MachineListDisplayCount/MachineListDisplayCount.tsx +++ /dev/null @@ -1,37 +0,0 @@ -export const getCurrentPageDisplayedMachineCount = ( - machineCount: number | null, - pageSize: number, - currentPage: number -): number => { - if (!machineCount) { - return 0; - } - - const totalPages = Math.ceil(machineCount / pageSize); - - if (currentPage === totalPages) { - return pageSize - (totalPages * pageSize - machineCount); - } else { - return pageSize; - } -}; - -export const MachineListDisplayCount = ({ - machineCount, - pageSize, - currentPage, -}: { - machineCount: number; - pageSize: number; - currentPage: number; -}): JSX.Element => { - return ( - - Showing{" "} - {getCurrentPageDisplayedMachineCount(machineCount, pageSize, currentPage)}{" "} - out of {machineCount} machines - - ); -}; - -export default MachineListDisplayCount; diff --git a/src/app/machines/views/MachineList/MachineListTable/MachineListDisplayCount/_index.scss b/src/app/machines/views/MachineList/MachineListTable/MachineListDisplayCount/_index.scss deleted file mode 100644 index 0ccb900842..0000000000 --- a/src/app/machines/views/MachineList/MachineListTable/MachineListDisplayCount/_index.scss +++ /dev/null @@ -1,5 +0,0 @@ -@mixin MachineListDisplayCount { - .machine-list--display-count { - padding-bottom: $spv--small; - } -} \ No newline at end of file diff --git a/src/app/machines/views/MachineList/MachineListTable/MachineListDisplayCount/index.ts b/src/app/machines/views/MachineList/MachineListTable/MachineListDisplayCount/index.ts deleted file mode 100644 index 07d3a71f6c..0000000000 --- a/src/app/machines/views/MachineList/MachineListTable/MachineListDisplayCount/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./MachineListDisplayCount"; diff --git a/src/app/machines/views/MachineList/MachineListTable/MachineListPagination/MachineListPagination.tsx b/src/app/machines/views/MachineList/MachineListTable/MachineListPagination/MachineListPagination.tsx index 8d30434f01..242c28d7af 100644 --- a/src/app/machines/views/MachineList/MachineListTable/MachineListPagination/MachineListPagination.tsx +++ b/src/app/machines/views/MachineList/MachineListTable/MachineListPagination/MachineListPagination.tsx @@ -1,10 +1,10 @@ import { useState, useRef, useEffect } from "react"; +import { Pagination } from "@canonical/maas-react-components"; import type { PaginationProps, PropsWithSpread, } from "@canonical/react-components"; -import { Button, Icon, Input } from "@canonical/react-components"; import { useFetchedCount } from "@/app/store/machine/utils"; @@ -53,72 +53,51 @@ const MachineListPagination = ({ const pages = useFetchedCount(totalPages, machinesLoading); return count > 0 ? ( - + intervalRef.current = setTimeout(() => { + if ( + e.target.valueAsNumber > pages || + e.target.valueAsNumber < 1 + ) { + setError( + `"${e.target.valueAsNumber}" is not a valid page number.` + ); + } else { + setError(""); + props.paginate(e.target.valueAsNumber); + } + }, DEFAULT_DEBOUNCE_INTERVAL); + } else { + setPageNumber(undefined); + setError("Enter a page number."); + } + }} + onNextClick={() => { + setPageNumber((page) => Number(page) + 1); + props.paginate(Number(props.currentPage) + 1); + }} + onPreviousClick={() => { + setPageNumber((page) => Number(page) - 1); + props.paginate(Number(props.currentPage) - 1); + }} + totalPages={pages} + /> +
) : null; }; diff --git a/src/app/machines/views/MachineList/MachineListTable/MachineListPagination/_index.scss b/src/app/machines/views/MachineList/MachineListTable/MachineListPagination/_index.scss index dfa27ae294..7067c79104 100644 --- a/src/app/machines/views/MachineList/MachineListTable/MachineListPagination/_index.scss +++ b/src/app/machines/views/MachineList/MachineListTable/MachineListPagination/_index.scss @@ -1,5 +1,5 @@ @mixin MachineListPagination { - .p-pagination--items { + .p-pagination--machines .p-pagination--items { padding-bottom: $spv--small; .p-button { diff --git a/src/app/machines/views/MachineList/MachineListTable/MachineListTable.tsx b/src/app/machines/views/MachineList/MachineListTable/MachineListTable.tsx index 1054fb7d5e..e7ad803b31 100644 --- a/src/app/machines/views/MachineList/MachineListTable/MachineListTable.tsx +++ b/src/app/machines/views/MachineList/MachineListTable/MachineListTable.tsx @@ -4,7 +4,6 @@ import { MainTable } from "@canonical/react-components"; import classNames from "classnames"; import AllCheckbox from "./AllCheckbox"; -import MachineListDisplayCount from "./MachineListDisplayCount"; import MachineListPagination from "./MachineListPagination"; import MachineListSelectedCount from "./MachineListSelectedCount/MachineListSelectedCount"; import PageSizeSelect from "./PageSizeSelect"; @@ -15,6 +14,7 @@ import { } from "./tableModels"; import type { MachineListTableProps } from "./types"; +import ListDisplayCount from "@/app/base/components/ListDisplayCount"; import TableHeader from "@/app/base/components/TableHeader"; import { useFetchActions, useSendAnalytics } from "@/app/base/hooks"; import { SortDirection } from "@/app/base/types"; @@ -427,10 +427,11 @@ export const MachineListTable = ({ {machineCount ? (

- ) : null} -
) : null} +
{ useWindowTitle("Tags"); + const pageSize = 50; return (
- - +
+
+ + + {}} + onInputChange={(e) => { + setCurrentPage(Number(e.target.value)); + }} + onNextClick={() => setCurrentPage(currentPage + 1)} + onPreviousClick={() => setCurrentPage(currentPage - 1)} + totalPages={Math.ceil(tags.length / pageSize)} + /> + +
+
+ +
); }; diff --git a/src/scss/index.scss b/src/scss/index.scss index 6a2a276fdb..1946c8183f 100644 --- a/src/scss/index.scss +++ b/src/scss/index.scss @@ -91,6 +91,7 @@ @import "@/app/base/components/LabelledList"; @import "@/app/base/components/Login"; @import "@/app/base/components/MachineSelectTable"; +@import "@/app/base/components/ListDisplayCount"; @import "@/app/base/components/node/HardwareCard"; @import "@/app/base/components/node/networking/NetworkTable"; @import "@/app/base/components/node/NodeDevices"; @@ -241,13 +242,12 @@ @import "@/app/machines/views/MachineDetails/MachineSummary/NumaCard"; @import "@/app/machines/views/MachineList"; @import "@/app/machines/views/MachineList/MachineListControls"; -@import "@/app/machines/views/MachineList/MachineListTable/MachineListDisplayCount"; @import "@/app/machines/views/MachineList/MachineListTable/MachineListPagination"; @include CloneFormFields; @include CloneResults; @include MachineList; @include MachineListControls; -@include MachineListDisplayCount; +@include ListDisplayCount; @include MachineListPagination; @include MachineSummary; @include MarkBrokenFormFields; diff --git a/yarn.lock b/yarn.lock index fbe22be800..345108b130 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2137,10 +2137,10 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@canonical/maas-react-components@1.19.1": - version "1.19.1" - resolved "https://registry.yarnpkg.com/@canonical/maas-react-components/-/maas-react-components-1.19.1.tgz#244989894315317a764b912288feb96aeb2fac17" - integrity sha512-0EASJlSK9Eu4r6RPcDShlawblJHWqECwzst87/LX00Ksz5c0MonRa6wonwfYhzdWI3cAfYgXvLCKdi58m0jKoQ== +"@canonical/maas-react-components@1.21.0": + version "1.21.0" + resolved "https://registry.yarnpkg.com/@canonical/maas-react-components/-/maas-react-components-1.21.0.tgz#15165669403c05c8372041d15a2a176a74e29291" + integrity sha512-KIrof++PygfcJezytmvsaXlGqMbvCUe2A3g4WYIxaCrOWnqTwp7wzTT/V6qM4upQ1lxHsXG0AdFCM6dqaLunEg== "@canonical/macaroon-bakery@1.3.2": version "1.3.2"