diff --git a/src/components/TablePagination/TablePagination.tsx b/src/components/TablePagination/TablePagination.tsx index dcaecb17a..d3caf5d50 100644 --- a/src/components/TablePagination/TablePagination.tsx +++ b/src/components/TablePagination/TablePagination.tsx @@ -1,10 +1,11 @@ +import { commonMessages } from "@dashboard/intl"; import { TableCell } from "@material-ui/core"; import { Pagination, PaginationProps as MacawPaginationProps, } from "@saleor/macaw-ui"; import React from "react"; -import { defineMessages, useIntl } from "react-intl"; +import { useIntl } from "react-intl"; import { Link, LinkProps } from "react-router-dom"; import { ListSettings } from "../../types"; @@ -14,14 +15,6 @@ export type ListSettingsUpdate = ( value: ListSettings[T], ) => void; -const messages = defineMessages({ - noOfRows: { - id: "2HfSiT", - defaultMessage: "No. of rows", - description: "pagination", - }, -}); - export interface PaginationProps extends Omit< MacawPaginationProps, @@ -57,7 +50,7 @@ export const TablePagination: React.FC = ({ hasNextPage={hasNextPage && !disabled} hasPreviousPage={hasPreviousPage && !disabled} labels={{ - noOfRows: intl.formatMessage(messages.noOfRows), + noOfRows: intl.formatMessage(commonMessages.noOfRows), }} rowNumber={settings?.rowNumber} onRowNumberUpdate={ diff --git a/src/hooks/useClientPagination/index.ts b/src/hooks/useClientPagination/index.ts new file mode 100644 index 000000000..3c3f99380 --- /dev/null +++ b/src/hooks/useClientPagination/index.ts @@ -0,0 +1 @@ +export * from "./useClientPagination"; diff --git a/src/hooks/useClientPagination/useClientPagination.test.tsx b/src/hooks/useClientPagination/useClientPagination.test.tsx new file mode 100644 index 000000000..ff592d7a4 --- /dev/null +++ b/src/hooks/useClientPagination/useClientPagination.test.tsx @@ -0,0 +1,94 @@ +import { act } from "@testing-library/react"; +import { renderHook } from "@testing-library/react-hooks"; + +import { useClientPagination } from "./useClientPagination"; + +describe("useClientPagination", () => { + test("should reset current page when row number change", () => { + // Arrange + const { result } = renderHook(() => useClientPagination()); + + // Act + act(() => result.current.changeCurrentPage(2)); + act(() => result.current.changeRowNumber(20)); + + // Assert + expect(result.current.currentPage).toEqual(1); + expect(result.current.rowNumber).toEqual(20); + }); + + test("should reset current page and row number when call restartPagination", () => { + // Arrange + const { result } = renderHook(() => useClientPagination()); + + // Act + act(() => result.current.changeCurrentPage(2)); + act(() => result.current.changeRowNumber(20)); + act(() => result.current.restartPagination()); + + // Assert + expect(result.current.currentPage).toEqual(1); + expect(result.current.rowNumber).toEqual(10); + }); + + test("should change current page when call changeCurrentPage", () => { + // Arrange + const { result } = renderHook(() => useClientPagination()); + + // Act + act(() => result.current.changeCurrentPage(2)); + + // Assert + expect(result.current.currentPage).toEqual(2); + }); + + test("should change row number when call changeRowNumber", () => { + // Arrange + const { result } = renderHook(() => useClientPagination()); + + // Act + act(() => result.current.changeRowNumber(20)); + + // Assert + expect(result.current.rowNumber).toEqual(20); + }); + + test("should return paginated data slice to first 10", () => { + // Arrange & Act + const { result } = renderHook(() => useClientPagination()); + const paginatedData = result.current.paginate(Array.from(Array(20).keys())); + + // Assert + expect(paginatedData.hasNextPage).toEqual(true); + expect(paginatedData.hasPreviousPage).toEqual(false); + expect(paginatedData.data.length).toEqual(10); + }); + + test("should return paginated data with false hasNextPage", () => { + // Arrange + const { result } = renderHook(() => useClientPagination()); + + // Act + act(() => result.current.changeCurrentPage(2)); + + // Assert + const paginatedData = result.current.paginate(Array.from(Array(20).keys())); + expect(paginatedData.hasNextPage).toEqual(false); + expect(paginatedData.hasPreviousPage).toEqual(true); + expect(paginatedData.data.length).toEqual(10); + }); + + test("should return paginated data with false hasNextPage and hasPreviousPage", () => { + // Arrange + const { result } = renderHook(() => useClientPagination()); + + // Act + act(() => result.current.changeRowNumber(25)); + + // Assert + const paginatedData = result.current.paginate(Array.from(Array(20).keys())); + expect(paginatedData.hasNextPage).toEqual(false); + expect(paginatedData.hasPreviousPage).toEqual(false); + expect(paginatedData.data.length).toEqual(20); + }); +}); diff --git a/src/hooks/useClientPagination/useClientPagination.ts b/src/hooks/useClientPagination/useClientPagination.ts new file mode 100644 index 000000000..17101319d --- /dev/null +++ b/src/hooks/useClientPagination/useClientPagination.ts @@ -0,0 +1,47 @@ +import { useCallback, useEffect, useState } from "react"; + +const DEFAULT_ROWS_COUNT = 10; +const FIRST_PAGINATED_PAGE = 1; + +export const useClientPagination = () => { + const [rowNumber, setRowNumber] = useState(DEFAULT_ROWS_COUNT); + const [currentPage, setCurrentPage] = useState(FIRST_PAGINATED_PAGE); + + const indexOfLastElement = currentPage * rowNumber; + const indexOfFirstElement = indexOfLastElement - rowNumber; + + useEffect(() => { + setCurrentPage(FIRST_PAGINATED_PAGE); + }, [rowNumber]); + + const restartPagination = useCallback(() => { + setCurrentPage(FIRST_PAGINATED_PAGE); + setRowNumber(DEFAULT_ROWS_COUNT); + }, []); + + const changeRowNumber = useCallback((rowNumber: number) => { + setRowNumber(rowNumber); + }, []); + + const changeCurrentPage = useCallback((page: number) => { + setCurrentPage(page); + }, []); + + const paginate = useCallback( + (data: T[]) => ({ + data: data.slice(indexOfFirstElement, indexOfLastElement), + hasNextPage: data.length / (rowNumber * currentPage) > 1, + hasPreviousPage: currentPage > 1, + }), + [currentPage, indexOfFirstElement, indexOfLastElement, rowNumber], + ); + + return { + rowNumber, + changeRowNumber, + changeCurrentPage, + currentPage, + restartPagination, + paginate, + }; +}; diff --git a/src/intl.ts b/src/intl.ts index 2511a594a..30140ecd7 100644 --- a/src/intl.ts +++ b/src/intl.ts @@ -190,6 +190,11 @@ export const commonMessages = defineMessages({ id: "D3idYv", defaultMessage: "Settings", }, + noOfRows: { + id: "2HfSiT", + defaultMessage: "No. of rows", + description: "pagination", + }, }); export const errorMessages = defineMessages({ diff --git a/src/taxes/components/TaxPagination/TaxPagination.tsx b/src/taxes/components/TaxPagination/TaxPagination.tsx new file mode 100644 index 000000000..80c8fea2f --- /dev/null +++ b/src/taxes/components/TaxPagination/TaxPagination.tsx @@ -0,0 +1,58 @@ +import { commonMessages } from "@dashboard/intl"; +import { makeStyles, Pagination } from "@saleor/macaw-ui"; +import React from "react"; +import { useIntl } from "react-intl"; + +interface TaxPaginationProps { + rowNumber: number; + currentPage: number; + setRowNumber: (rowNumber: number) => void; + hasNextPage: boolean; + hasPrevPage: boolean; + setCurrentPage: (currentPage: number) => void; +} + +const useStyles = makeStyles( + theme => ({ + container: { + padding: theme.spacing(0, 4), + }, + }), + { name: "TaxPagination" }, +); + +export const TaxPagination = ({ + rowNumber, + setRowNumber, + setCurrentPage, + hasNextPage, + hasPrevPage, + currentPage, +}: TaxPaginationProps) => { + const classes = useStyles(); + const intl = useIntl(); + + const handleNextPage = () => { + setCurrentPage(currentPage + 1); + }; + + const handlePrevPage = () => { + setCurrentPage(currentPage - 1); + }; + + return ( +
+ +
+ ); +}; diff --git a/src/taxes/components/TaxPagination/index.ts b/src/taxes/components/TaxPagination/index.ts new file mode 100644 index 000000000..25744e2f1 --- /dev/null +++ b/src/taxes/components/TaxPagination/index.ts @@ -0,0 +1 @@ +export * from "./TaxPagination"; diff --git a/src/taxes/pages/TaxClassesPage/TaxClassesPage.tsx b/src/taxes/pages/TaxClassesPage/TaxClassesPage.tsx index 8b5fde30b..b64620819 100644 --- a/src/taxes/pages/TaxClassesPage/TaxClassesPage.tsx +++ b/src/taxes/pages/TaxClassesPage/TaxClassesPage.tsx @@ -8,6 +8,7 @@ import Savebar from "@dashboard/components/Savebar"; import Skeleton from "@dashboard/components/Skeleton"; import { configurationMenuUrl } from "@dashboard/configuration"; import { TaxClassFragment } from "@dashboard/graphql"; +import { useClientPagination } from "@dashboard/hooks/useClientPagination/useClientPagination"; import { SubmitPromise } from "@dashboard/hooks/useForm"; import useNavigator from "@dashboard/hooks/useNavigator"; import { getById } from "@dashboard/misc"; @@ -36,10 +37,11 @@ import { PageTabs, SearchIcon, } from "@saleor/macaw-ui"; -import React from "react"; +import React, { useEffect, useMemo, useState } from "react"; import { FormattedMessage, useIntl } from "react-intl"; import TaxInput from "../../components/TaxInput"; +import { TaxPagination } from "../../components/TaxPagination"; import TaxClassesForm from "./form"; import { useStyles } from "./styles"; import TaxClassesMenu from "./TaxClassesMenu"; @@ -72,9 +74,17 @@ export const TaxClassesPage: React.FC = props => { const navigate = useNavigator(); const classes = useStyles(); - const [query, setQuery] = React.useState(""); + const [query, setQuery] = useState(""); + const { + rowNumber, + currentPage, + paginate, + restartPagination, + changeCurrentPage, + changeRowNumber, + } = useClientPagination(); - const currentTaxClass = React.useMemo( + const currentTaxClass = useMemo( () => taxClasses?.find(getById(selectedTaxClassId)), [selectedTaxClassId, taxClasses], ); @@ -83,6 +93,10 @@ export const TaxClassesPage: React.FC = props => { currentTaxClass?.id, ]); + useEffect(() => { + restartPagination(); + }, [query, restartPagination]); + return ( = props => { rate => rate.label.search(new RegExp(parseQuery(query), "i")) >= 0, ); + const { data: paginatedRates, hasNextPage, hasPreviousPage } = paginate( + filteredRates, + ); + const formErrors = getFormErrors(["name"], validationErrors); return ( @@ -204,7 +222,7 @@ export const TaxClassesPage: React.FC = props => { - {filteredRates?.map( + {paginatedRates?.map( (countryRate, countryRateIndex) => ( = props => { )} + + )}