From 0cca71c7cbc437f806110ffe046622d96b5fd9a1 Mon Sep 17 00:00:00 2001 From: Adam Tomaszczyk Date: Wed, 27 Aug 2025 16:04:23 +0200 Subject: [PATCH] More DRep search improvements #4030 --- govtool/backend/sql/list-dreps.sql | 13 ++ govtool/backend/src/VVA/API.hs | 3 + govtool/backend/src/VVA/API/Types.hs | 4 +- govtool/backend/src/VVA/DRep.hs | 4 +- govtool/backend/src/VVA/Types.hs | 2 + .../components/molecules/PaginationFooter.tsx | 137 ++++++++++++++++++ .../src/consts/dRepDirectory/sorting.ts | 4 +- .../frontend/src/context/contextProviders.tsx | 13 +- .../frontend/src/context/dataActionsBar.tsx | 2 + govtool/frontend/src/context/index.ts | 1 + govtool/frontend/src/context/pagination.tsx | 56 +++++++ .../queries/useGetDRepListInfiniteQuery.ts | 50 +------ .../src/hooks/queries/useGetDRepListQuery.ts | 46 +++--- govtool/frontend/src/hooks/useUpdateEffect.ts | 16 ++ govtool/frontend/src/models/api.ts | 2 +- .../src/pages/DRepDirectoryContent.tsx | 53 ++++--- 16 files changed, 297 insertions(+), 109 deletions(-) create mode 100644 govtool/frontend/src/components/molecules/PaginationFooter.tsx create mode 100644 govtool/frontend/src/context/pagination.tsx create mode 100644 govtool/frontend/src/hooks/useUpdateEffect.ts diff --git a/govtool/backend/sql/list-dreps.sql b/govtool/backend/sql/list-dreps.sql index 0d7be1697..5ba8e3250 100644 --- a/govtool/backend/sql/list-dreps.sql +++ b/govtool/backend/sql/list-dreps.sql @@ -32,6 +32,16 @@ LatestVoteEpoch AS ( JOIN tx ON tx.id = lvp.tx_id JOIN block ON block.id = tx.block_id ), +VotesLastYear AS ( + SELECT + vp.drep_voter AS drep_id, + COUNT(DISTINCT vp.gov_action_proposal_id) AS votes_last_year + FROM voting_procedure vp + JOIN tx ON tx.id = vp.tx_id + JOIN block ON block.id = tx.block_id + WHERE block.time >= now() - INTERVAL '1 year' + GROUP BY vp.drep_voter +), RankedDRepRegistration AS ( SELECT DISTINCT ON (dr.drep_hash_id) dr.id, @@ -127,6 +137,7 @@ DRepData AS ( off_chain_vote_drep_data.qualifications, off_chain_vote_drep_data.image_url, off_chain_vote_drep_data.image_hash, + COALESCE(vly.votes_last_year, 0) AS votes_last_year, COALESCE( ( SELECT jsonb_agg( @@ -239,6 +250,7 @@ DRepData AS ( LEFT JOIN tx AS tx_first_register ON tx_first_register.id = dr_first_register.tx_id LEFT JOIN block AS block_first_register ON block_first_register.id = tx_first_register.block_id LEFT JOIN LatestVoteEpoch lve ON lve.drep_id = dh.id + LEFT JOIN VotesLastYear vly ON vly.drep_id = dh.id CROSS JOIN DRepActivity GROUP BY dh.raw, @@ -265,6 +277,7 @@ DRepData AS ( off_chain_vote_drep_data.qualifications, off_chain_vote_drep_data.image_url, off_chain_vote_drep_data.image_hash, + vly.votes_last_year, ( SELECT jsonb_agg( jsonb_build_object( diff --git a/govtool/backend/src/VVA/API.hs b/govtool/backend/src/VVA/API.hs index 5185c3882..de95e6301 100644 --- a/govtool/backend/src/VVA/API.hs +++ b/govtool/backend/src/VVA/API.hs @@ -160,6 +160,7 @@ drepRegistrationToDrep Types.DRepRegistration {..} = dRepQualifications = dRepRegistrationQualifications, dRepImageUrl = dRepRegistrationImageUrl, dRepImageHash = HexText <$> dRepRegistrationImageHash, + dRepVotesLastYear = dRepRegistrationVotesLastYear, dRepIdentityReferences = DRepReferences <$> dRepRegistrationIdentityReferences, dRepLinkReferences = DRepReferences <$> dRepRegistrationLinkReferences } @@ -205,6 +206,8 @@ drepList mSearchQuery statuses mSortMode mPage mPageSize = do Just Random -> fmap snd . sortOn fst . Prelude.zip randomizedOrderList Just VotingPower -> sortOn $ \Types.DRepRegistration {..} -> Down dRepRegistrationVotingPower + Just Activity -> sortOn $ \Types.DRepRegistration {..} -> + Down dRepRegistrationVotesLastYear Just RegistrationDate -> sortOn $ \Types.DRepRegistration {..} -> Down dRepRegistrationLatestRegistrationDate Just Status -> sortOn $ \Types.DRepRegistration {..} -> diff --git a/govtool/backend/src/VVA/API/Types.hs b/govtool/backend/src/VVA/API/Types.hs index 2b1badf27..e0afad810 100644 --- a/govtool/backend/src/VVA/API/Types.hs +++ b/govtool/backend/src/VVA/API/Types.hs @@ -205,7 +205,7 @@ instance ToParamSchema GovernanceActionType where & enum_ ?~ map toJSON (enumFromTo minBound maxBound :: [GovernanceActionType]) -data DRepSortMode = Random | VotingPower | RegistrationDate | Status deriving +data DRepSortMode = Random | VotingPower | Activity | RegistrationDate | Status deriving ( Bounded , Enum , Eq @@ -917,6 +917,7 @@ data DRep , dRepQualifications :: Maybe Text , dRepImageUrl :: Maybe Text , dRepImageHash :: Maybe HexText + , dRepVotesLastYear :: Maybe Integer , dRepIdentityReferences :: Maybe DRepReferences , dRepLinkReferences :: Maybe DRepReferences } @@ -944,6 +945,7 @@ exampleDrep = <> "\"qualifications\": \"Some Qualifications\"," <> "\"qualifications\": \"Some Qualifications\"," <> "\"imageUrl\": \"https://image.url\"," + <> "\"votesLastYear\": 15," <> "\"imageHash\": \"9198b1b204273ba5c67a13310b5a806034160f6a063768297e161d9b759cad61\"}" -- ToSchema instance for DRep diff --git a/govtool/backend/src/VVA/DRep.hs b/govtool/backend/src/VVA/DRep.hs index 8e6f7ceae..44b172f7f 100644 --- a/govtool/backend/src/VVA/DRep.hs +++ b/govtool/backend/src/VVA/DRep.hs @@ -59,6 +59,7 @@ data DRepQueryResult , queryQualifications :: Maybe Text , queryImageUrl :: Maybe Text , queryImageHash :: Maybe Text + , queryVotesLastYear :: Maybe Integer , queryIdentityReferences :: Maybe Value , queryLinkReferences :: Maybe Value } @@ -69,7 +70,7 @@ instance FromRow DRepQueryResult where <$> field <*> field <*> field <*> field <*> field <*> field <*> field <*> field <*> field <*> field <*> field <*> field <*> field <*> field <*> field <*> field <*> field <*> field - <*> field <*> field <*> field <*> field + <*> field <*> field <*> field <*> field <*> field sqlFrom :: ByteString -> SQL.Query sqlFrom bs = fromString $ unpack $ Text.decodeUtf8 bs @@ -113,6 +114,7 @@ listDReps mSearchQuery = withPool $ \conn -> do (queryQualifications result) (queryImageUrl result) (queryImageHash result) + (queryVotesLastYear result) (queryIdentityReferences result) (queryLinkReferences result) | result <- results diff --git a/govtool/backend/src/VVA/Types.hs b/govtool/backend/src/VVA/Types.hs index 98fb7b44e..9ace0e5bc 100644 --- a/govtool/backend/src/VVA/Types.hs +++ b/govtool/backend/src/VVA/Types.hs @@ -160,6 +160,7 @@ data DRepRegistration , dRepRegistrationQualifications :: Maybe Text , dRepRegistrationImageUrl :: Maybe Text , dRepRegistrationImageHash :: Maybe Text + , dRepRegistrationVotesLastYear :: Maybe Integer , dRepRegistrationIdentityReferences :: Maybe Value , dRepRegistrationLinkReferences :: Maybe Value } @@ -187,6 +188,7 @@ instance FromRow DRepRegistration where <*> field -- dRepRegistrationQualifications <*> field -- dRepRegistrationImageUrl <*> field -- dRepRegistrationImageHash + <*> field -- dRepRegistrationVotesLastYear <*> field -- dRepRegistrationIdentityReferences <*> field -- dRepRegistrationLinkReferences diff --git a/govtool/frontend/src/components/molecules/PaginationFooter.tsx b/govtool/frontend/src/components/molecules/PaginationFooter.tsx new file mode 100644 index 000000000..77a751105 --- /dev/null +++ b/govtool/frontend/src/components/molecules/PaginationFooter.tsx @@ -0,0 +1,137 @@ +import { + Box, + Typography, + IconButton, + Select, + MenuItem, + SelectChangeEvent, +} from "@mui/material"; +import KeyboardArrowLeft from "@mui/icons-material/KeyboardArrowLeft"; +import KeyboardArrowRight from "@mui/icons-material/KeyboardArrowRight"; +import { FC } from "react"; + +type Props = { + page: number; + total: number; + pageSize: number; + onPageChange: (nextPage: number) => void; + onPageSizeChange: (nextRpp: number) => void; + pageSizeOptions?: number[]; +}; + +export const PaginationFooter: FC = ({ + page, + total, + pageSize, + onPageChange, + onPageSizeChange, + pageSizeOptions = [5, 10, 25, 50], +}) => { + const pageCount = Math.max(1, Math.ceil((total || 0) / (pageSize || 1))); + const clampedPage = Math.min(Math.max(page, 1), pageCount); + + const start = total === 0 ? 0 : (clampedPage - 1) * pageSize + 1; + const end = total === 0 ? 0 : Math.min(clampedPage * pageSize, total); + + const handlePrev = () => onPageChange(Math.max(clampedPage - 1, 1)); + const handleNext = () => onPageChange(Math.min(clampedPage + 1, pageCount)); + + const handlePageSizeChange = (e: SelectChangeEvent) => { + const next = Number(e.target.value); + onPageSizeChange(next); + + const nextPageCount = Math.max(1, Math.ceil((total || 0) / next)); + if (clampedPage > nextPageCount) { + onPageChange(nextPageCount); + } + }; + + return ( + + + + Rows per page: + + + + + + + {start}-{end} of {total} + + + + + + + + + {clampedPage} + + + = pageCount || total === 0} + aria-label="Next page" + > + + + + + ); +}; diff --git a/govtool/frontend/src/consts/dRepDirectory/sorting.ts b/govtool/frontend/src/consts/dRepDirectory/sorting.ts index 0e6c4d016..2ae238b15 100644 --- a/govtool/frontend/src/consts/dRepDirectory/sorting.ts +++ b/govtool/frontend/src/consts/dRepDirectory/sorting.ts @@ -1,7 +1,7 @@ export const DREP_DIRECTORY_SORTING = [ { - key: "Random", - label: "Random", + key: "Activity", + label: "Activity", }, { key: "RegistrationDate", diff --git a/govtool/frontend/src/context/contextProviders.tsx b/govtool/frontend/src/context/contextProviders.tsx index f456018bb..7337418b1 100644 --- a/govtool/frontend/src/context/contextProviders.tsx +++ b/govtool/frontend/src/context/contextProviders.tsx @@ -3,6 +3,7 @@ import { CardanoProvider, useCardano } from "./wallet"; import { ModalProvider, useModal } from "./modal"; import { SnackbarProvider, useSnackbar } from "./snackbar"; import { DataActionsBarProvider } from "./dataActionsBar"; +import { PaginationProvider } from "./pagination"; import { FeatureFlagProvider } from "./featureFlag"; import { GovernanceActionProvider } from "./governanceAction"; import { AdaHandleProvider } from "./adaHandle"; @@ -23,11 +24,13 @@ const ContextProviders = ({ children }: Props) => ( - - - {children} - - + + + + {children} + + + diff --git a/govtool/frontend/src/context/dataActionsBar.tsx b/govtool/frontend/src/context/dataActionsBar.tsx index e2776346c..2bf291edd 100644 --- a/govtool/frontend/src/context/dataActionsBar.tsx +++ b/govtool/frontend/src/context/dataActionsBar.tsx @@ -24,6 +24,7 @@ interface DataActionsBarContextType { debouncedSearchText: string; filtersOpen: boolean; searchText: string; + lastPath: string; setChosenFilters: Dispatch>; setChosenSorting: Dispatch>; setFiltersOpen: Dispatch>; @@ -120,6 +121,7 @@ const DataActionsBarProvider: FC = ({ children }) => { debouncedSearchText, filtersOpen, searchText, + lastPath, setChosenFilters, setChosenSorting, setFiltersOpen, diff --git a/govtool/frontend/src/context/index.ts b/govtool/frontend/src/context/index.ts index 0ece5a1b5..6914a1289 100644 --- a/govtool/frontend/src/context/index.ts +++ b/govtool/frontend/src/context/index.ts @@ -1,6 +1,7 @@ export * from "./appContext"; export * from "./contextProviders"; export * from "./dataActionsBar"; +export * from "./pagination"; export * from "./modal"; export * from "./pendingTransaction"; export * from "./snackbar"; diff --git a/govtool/frontend/src/context/pagination.tsx b/govtool/frontend/src/context/pagination.tsx new file mode 100644 index 000000000..f979ec00a --- /dev/null +++ b/govtool/frontend/src/context/pagination.tsx @@ -0,0 +1,56 @@ +import React, { + createContext, + useContext, + Dispatch, + SetStateAction, + FC, + useState, + useMemo, +} from "react"; + +type PaginationContextType = { + page: number; + pageSize: number; + setPage: Dispatch>; + setPageSize: Dispatch>; +}; + +const PaginationContext = createContext< + PaginationContextType | undefined>( + undefined); +PaginationContext.displayName = "PaginationContext"; + +type PaginationProviderProps = { + children: React.ReactNode; +}; + +const PaginationProvider: FC = ({ children }) => { + const [page, setPage] = useState(1); + const [pageSize, setPageSize] = useState(5); + + const contextValue = useMemo( + () => ({ + page, + pageSize, + setPage, + setPageSize, + }), + [page, pageSize], + ); + + return ( + + {children} + + ); +}; + +function usePagination() { + const ctx = useContext(PaginationContext); + if (!ctx) { + throw new Error("usePagination must be used within a PaginationProvider"); + } + return ctx; +} + +export { PaginationProvider, usePagination }; diff --git a/govtool/frontend/src/hooks/queries/useGetDRepListInfiniteQuery.ts b/govtool/frontend/src/hooks/queries/useGetDRepListInfiniteQuery.ts index 2465d9926..51d6930e4 100644 --- a/govtool/frontend/src/hooks/queries/useGetDRepListInfiniteQuery.ts +++ b/govtool/frontend/src/hooks/queries/useGetDRepListInfiniteQuery.ts @@ -1,18 +1,10 @@ -import { - UseInfiniteQueryOptions, - useInfiniteQuery, - useQuery, -} from "react-query"; -import { useRef, useMemo } from "react"; +import { UseInfiniteQueryOptions, useInfiniteQuery } from "react-query"; import { QUERY_KEYS } from "@consts"; import { useCardano } from "@context"; import { GetDRepListArguments, getDRepList } from "@services"; import { DRepData, Infinite } from "@/models"; -const makeStatusKey = (status?: string[] | undefined) => - (status && status.length ? [...status].sort().join("|") : "__EMPTY__"); - export const useGetDRepListInfiniteQuery = ( { filters = [], @@ -24,8 +16,6 @@ export const useGetDRepListInfiniteQuery = ( options?: UseInfiniteQueryOptions>, ) => { const { pendingTransaction } = useCardano(); - const totalsByStatusRef = useRef>({}); - const statusKey = useMemo(() => makeStatusKey(status), [status]); const { data, @@ -65,41 +55,6 @@ export const useGetDRepListInfiniteQuery = ( }, enabled: options?.enabled, keepPreviousData: options?.keepPreviousData, - onSuccess: (pagesData) => { - if (!searchPhrase) { - const firstPage = pagesData.pages?.[0]; - if (firstPage && typeof firstPage.total === "number") { - totalsByStatusRef.current[statusKey] = firstPage.total; - } - } - options?.onSuccess?.(pagesData); - }, - }, - ); - - useQuery( - [QUERY_KEYS.useGetDRepListInfiniteKey, "baseline", statusKey], - async () => { - const resp = await getDRepList({ - page: 0, - pageSize: 1, - filters, - searchPhrase: "", - sorting, - status, - }); - return resp; - }, - { - enabled: - options?.enabled && - searchPhrase !== "" && - totalsByStatusRef.current[statusKey] === undefined, - onSuccess: (resp) => { - if (typeof resp.total === "number") { - totalsByStatusRef.current[statusKey] = resp.total; - } - }, }, ); @@ -111,8 +66,5 @@ export const useGetDRepListInfiniteQuery = ( isDRepListLoading: isLoading, dRepData: data?.pages.flatMap((page) => page.elements), isPreviousData, - dRepListTotal: data?.pages[0].total, - dRepTotalsByStatus: totalsByStatusRef.current, - dRepBaselineTotalForStatus: totalsByStatusRef.current[statusKey], }; }; diff --git a/govtool/frontend/src/hooks/queries/useGetDRepListQuery.ts b/govtool/frontend/src/hooks/queries/useGetDRepListQuery.ts index 5eedc12df..db62f9c09 100644 --- a/govtool/frontend/src/hooks/queries/useGetDRepListQuery.ts +++ b/govtool/frontend/src/hooks/queries/useGetDRepListQuery.ts @@ -1,5 +1,5 @@ -import { useMemo, useRef } from "react"; -import { useQuery, UseQueryOptions } from "react-query"; +import { useMemo } from "react"; +import { useQuery, UseQueryOptions, useQueryClient } from "react-query"; import { QUERY_KEYS } from "@consts"; import { useCardano } from "@context"; @@ -28,10 +28,11 @@ export function useGetDRepListPaginatedQuery( options?: UseQueryOptions>, ): PaginatedResult { const { pendingTransaction } = useCardano(); - const totalsByStatusRef = useRef>({}); + const queryClient = useQueryClient(); + const statusKey = useMemo(() => makeStatusKey(status), [status]); - const queryKey = [ + const listKey = [ QUERY_KEYS.useGetDRepListInfiniteKey, ( pendingTransaction.registerAsDirectVoter || @@ -48,9 +49,14 @@ export function useGetDRepListPaginatedQuery( status?.length ? status : "", ]; + const baselineKey = useMemo( + () => [QUERY_KEYS.useGetDRepListInfiniteKey, "baseline", statusKey], + [statusKey], + ); + const { data, isLoading, isFetching, isPreviousData } = useQuery( - queryKey, - async () => + listKey, + () => getDRepList({ page, pageSize, @@ -64,36 +70,32 @@ export function useGetDRepListPaginatedQuery( enabled: options?.enabled, onSuccess: (resp) => { if (!searchPhrase && typeof resp?.total === "number") { - totalsByStatusRef.current[statusKey] = resp.total; + queryClient.setQueryData(baselineKey, resp); } options?.onSuccess?.(resp); }, }, ); - useQuery( - [QUERY_KEYS.useGetDRepListInfiniteKey, "baseline", statusKey], - async () => { - const resp = await getDRepList({ + const { data: baselineResp } = useQuery( + baselineKey, + async () => + getDRepList({ page: 0, pageSize: 1, filters, searchPhrase: "", sorting, status, - }); - return resp; - }, + }), { + initialData: () => + queryClient.getQueryData>(baselineKey), enabled: options?.enabled && - searchPhrase !== "" && - totalsByStatusRef.current[statusKey] === undefined, - onSuccess: (resp) => { - if (typeof resp.total === "number") { - totalsByStatusRef.current[statusKey] = resp.total; - } - }, + !queryClient.getQueryData(baselineKey) && + searchPhrase !== "", + staleTime: Infinity, }, ); @@ -103,6 +105,6 @@ export function useGetDRepListPaginatedQuery( isFetching, isPreviousData, total: data?.total, - baselineTotalForStatus: totalsByStatusRef.current[statusKey], + baselineTotalForStatus: baselineResp?.total, }; } diff --git a/govtool/frontend/src/hooks/useUpdateEffect.ts b/govtool/frontend/src/hooks/useUpdateEffect.ts new file mode 100644 index 000000000..192b2a96d --- /dev/null +++ b/govtool/frontend/src/hooks/useUpdateEffect.ts @@ -0,0 +1,16 @@ +import React, { useEffect, useRef } from "react"; + +export function useUpdateEffect( + effect: React.EffectCallback, + deps: React.DependencyList, +) { + const isFirst = useRef(true); + + useEffect(() => { + if (isFirst.current) { + isFirst.current = false; + return; + } + return effect(); + }, deps); +} diff --git a/govtool/frontend/src/models/api.ts b/govtool/frontend/src/models/api.ts index 8ba3a6a32..4f48125a6 100644 --- a/govtool/frontend/src/models/api.ts +++ b/govtool/frontend/src/models/api.ts @@ -144,7 +144,7 @@ export enum DRepStatus { } export enum DRepListSort { - Random = "Random", + Activity = "Activity", VotingPower = "VotingPower", RegistrationDate = "RegistrationDate", Status = "Status", diff --git a/govtool/frontend/src/pages/DRepDirectoryContent.tsx b/govtool/frontend/src/pages/DRepDirectoryContent.tsx index f95ad447c..d66588127 100644 --- a/govtool/frontend/src/pages/DRepDirectoryContent.tsx +++ b/govtool/frontend/src/pages/DRepDirectoryContent.tsx @@ -1,10 +1,10 @@ import React, { FC, useEffect, useState } from "react"; import { Trans, useTranslation } from "react-i18next"; -import { Box, CircularProgress, Pagination } from "@mui/material"; +import { Box, CircularProgress } from "@mui/material"; import { Typography } from "@atoms"; import { DREP_DIRECTORY_FILTERS, DREP_DIRECTORY_SORTING } from "@consts"; -import { useCardano, useDataActionsBar } from "@context"; +import { useCardano, useDataActionsBar, usePagination } from "@context"; import { useDelegateTodRep, useGetAdaHolderCurrentDelegationQuery, @@ -26,6 +26,8 @@ import { AutomatedVotingOptionDelegationId, } from "@/types/automatedVotingOptions"; import usePrevious from "@/hooks/usePrevious"; +import { PaginationFooter } from "@/components/molecules/PaginationFooter"; +import { useUpdateEffect } from "@/hooks/useUpdateEffect"; interface DRepDirectoryContentProps { isConnected?: boolean; @@ -55,31 +57,31 @@ export const DRepDirectoryContent: FC = ({ searchText, debouncedSearchText, setSearchText, + lastPath, ...dataActionsBarProps } = useDataActionsBar(); + const { page, pageSize, setPage, setPageSize } = usePagination(); + const { chosenFilters, chosenSorting, setChosenFilters, setChosenSorting } = dataActionsBarProps; const [inProgressDelegationDRepData, setInProgressDelegationDRepData] = useState(undefined); - const [page, setPage] = useState(1); - const pageSize = 10; - // Set initial filters and sort useEffect(() => { - // TODO: it should be done only if last page URL WASN'T like /drep_directory/drep1.* - setChosenFilters([DRepStatus.Active]); - setSearchText(""); // <--- Clear the search field on mount + if (!lastPath.includes("drep_directory")) { + setChosenFilters([DRepStatus.Active]); + setSearchText(""); + } }, []); useEffect(() => { - if (!chosenSorting) setChosenSorting(DRepListSort.Random); + if (!chosenSorting) setChosenSorting(DRepListSort.Activity); }, [chosenSorting, setChosenSorting]); - useEffect(() => { - // TODO: it should be done only if last page URL WASN'T like /drep_directory/drep1.* + useUpdateEffect(() => { setPage(1); }, [debouncedSearchText, chosenSorting, JSON.stringify(chosenFilters)]); @@ -142,9 +144,6 @@ export const DRepDirectoryContent: FC = ({ "view", ); - const totalForPaging = typeof total === "number" ? total : 0; - const pageCount = Math.max(1, Math.ceil(totalForPaging / pageSize)); - const isAnAutomatedVotingOptionChosen = currentDelegation?.dRepView && (currentDelegation?.dRepView === @@ -236,9 +235,10 @@ export const DRepDirectoryContent: FC = ({ 1 ? "s" : "", total: baselineTotalForStatus ?? "", }} components={{ @@ -288,19 +288,16 @@ export const DRepDirectoryContent: FC = ({ ))} - {pageCount > 1 && ( - - setPage(newPage)} - shape="rounded" - variant="outlined" - siblingCount={1} - boundaryCount={1} - /> - - )} + { + setPageSize(n); + setPage(1); + }} + /> );