Skip to content

Commit

Permalink
Fix filter pagination (#115)
Browse files Browse the repository at this point in the history
* Fix account filter pagination reset

* Add not valid process id message

* Fix verify old route

The route is the same and it causes an error where accessing to verify page because it shows `//` at the end of the path.

Add the `#` to be more accurate at redirection time

* Reset page number when filter changes

* Use query params for accounts filter

* Implement useRoutedPaginationQueryParams hook
  • Loading branch information
selankon authored Aug 26, 2024
1 parent 92c9795 commit d2a765c
Show file tree
Hide file tree
Showing 10 changed files with 128 additions and 85 deletions.
38 changes: 15 additions & 23 deletions src/components/Accounts/List.tsx
Original file line number Diff line number Diff line change
@@ -1,63 +1,55 @@
import { keepPreviousData } from '@tanstack/react-query'
import { useTranslation } from 'react-i18next'
import { generatePath, useNavigate, useParams } from 'react-router-dom'
import { InputSearch } from '~components/Layout/Inputs'
import { LoadingCards } from '~components/Layout/Loading'
import { AccountCard } from '~components/Accounts/Card'
import { RoutedPaginationProvider, useRoutedPagination } from '~components/Pagination/PaginationProvider'
import { useRoutedPagination } from '~components/Pagination/PaginationProvider'
import { RoutedPagination } from '~components/Pagination/RoutedPagination'
import { RoutePath } from '~constants'
import { useOrganizationCount, useOrganizationList } from '~queries/accounts'
import { useOrganizationList } from '~queries/accounts'
import { ContentError, NoResultsError } from '~components/Layout/ContentError'
import { useRoutedPaginationQueryParams } from '~src/router/use-query-params'

type FilterQueryParams = {
accountId?: string
}

export const AccountsFilter = () => {
const { t } = useTranslation()
const navigate = useNavigate()
const { query } = useParams<{ query?: string }>()
const { queryParams, setQueryParams } = useRoutedPaginationQueryParams<FilterQueryParams>()

return (
<InputSearch
maxW={'300px'}
placeholder={t('accounts.search_by_org_id')}
onChange={(value: string) => {
navigate(generatePath(RoutePath.AccountsList, { page: '0', query: value as string }))
setQueryParams({ accountId: value })
}}
debounceTime={500}
initialValue={query}
initialValue={queryParams.accountId}
/>
)
}

export const PaginatedAccountsList = () => {
return (
<RoutedPaginationProvider path={RoutePath.AccountsList}>
<AccountsList />
</RoutedPaginationProvider>
)
}

export const AccountsList = () => {
const { page }: { page?: number } = useRoutedPagination()
const { query }: { query?: string } = useParams()
const { data: count, isLoading: isLoadingCount } = useOrganizationCount()
const { queryParams } = useRoutedPaginationQueryParams<FilterQueryParams>()
const accountId = queryParams.accountId

const {
data: orgs,
isLoading: isLoadingOrgs,
isLoading,
isFetching,
isError,
error,
} = useOrganizationList({
params: {
page,
organizationId: query,
organizationId: accountId,
},
placeholderData: keepPreviousData,
})

const isLoading = isLoadingCount || isLoadingOrgs

if (isLoading || (query && isFetching)) {
if (isLoading || (accountId && isFetching)) {
return <LoadingCards skeletonCircle />
}

Expand Down
9 changes: 7 additions & 2 deletions src/components/Layout/ListPageLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,20 @@ const ListPageLayout = ({
} & PropsWithChildren) => {
return (
<Flex direction='column' mt={10} gap={6}>
<Flex direction={{ base: 'column', md: 'row' }} justify='space-between' gap={4} align={'center'}>
<Flex
direction={{ base: 'column', md: 'row' }}
justify='space-between'
gap={4}
align={{ base: 'center', lg: 'start' }}
>
<Flex direction='column' textAlign={{ base: 'center', md: 'start' }}>
<Heading isTruncated wordBreak='break-word'>
{title}
</Heading>
{subtitle && <Text color='lighterText'>{subtitle}</Text>}
</Flex>
{rightComponent && (
<Flex align='center' justify={{ base: 'center', md: 'end' }}>
<Flex mt={{ base: 0, md: 4 }} align='center' justify={{ base: 'center', md: 'end' }}>
{rightComponent}
</Flex>
)}
Expand Down
2 changes: 1 addition & 1 deletion src/components/Layout/TopBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export const TopBar = () => {
const links: HeaderLink[] = [
{
name: t('links.accounts', { defaultValue: 'Accounts' }),
url: generatePath(RoutePath.AccountsList, { page: null, query: null }),
url: generatePath(RoutePath.AccountsList, { page: null }),
},
{
name: t('links.processes', { defaultValue: 'Processes' }),
Expand Down
14 changes: 4 additions & 10 deletions src/components/Pagination/Pagination.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Button, ButtonGroup, ButtonGroupProps, ButtonProps, Input, InputProps, Text } from '@chakra-ui/react'
import { ReactElement, useMemo, useState } from 'react'
import { generatePath, Link as RouterLink, useLocation, useNavigate, useParams } from 'react-router-dom'
import { Link as RouterLink } from 'react-router-dom'
import { usePagination, useRoutedPagination } from './PaginationProvider'
import { PaginationResponse } from '@vocdoni/sdk'
import { Trans } from 'react-i18next'
Expand Down Expand Up @@ -203,22 +203,16 @@ export const Pagination = ({ maxButtons = 10, buttonProps, inputProps, paginatio
}

export const RoutedPagination = ({ maxButtons = 10, buttonProps, pagination, ...rest }: PaginationProps) => {
const { path } = useRoutedPagination()
const { search } = useLocation()
const { page, ...extraParams }: { page?: number } = useParams()
const navigate = useNavigate()
const { getPathForPage, setPage } = useRoutedPagination()

const totalPages = pagination.lastPage + 1

const currentPage = pagination.currentPage

const _generatePath = (page: number) => generatePath(path, { page, ...extraParams }) + search

return (
<PaginationButtons
goToPage={(page) => navigate(_generatePath(page))}
goToPage={(page) => setPage(page)}
createPageButton={(i) => (
<RoutedPageButton key={i} to={_generatePath(i + 1)} page={i} currentPage={currentPage} {...buttonProps} />
<RoutedPageButton key={i} to={getPathForPage(i + 1)} page={i} currentPage={currentPage} {...buttonProps} />
)}
currentPage={currentPage}
totalPages={totalPages}
Expand Down
24 changes: 20 additions & 4 deletions src/components/Pagination/PaginationProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createContext, PropsWithChildren, useContext, useState } from 'react'
import { useParams } from 'react-router-dom'
import { createContext, PropsWithChildren, useCallback, useContext, useState } from 'react'
import { generatePath, useLocation, useNavigate, useParams } from 'react-router-dom'

export type PaginationContextProps = {
page: number
Expand All @@ -8,6 +8,10 @@ export type PaginationContextProps = {

export type RoutedPaginationContextProps = Omit<PaginationContextProps, 'setPage'> & {
path: string
// Util function that generates the path for a given page
// (it return the actual path with queryParams and other route params but changing the page)
getPathForPage: (page: number, queryParams?: string) => string
setPage: (page: number, queryParams?: string) => void
}

const PaginationContext = createContext<PaginationContextProps | undefined>(undefined)
Expand Down Expand Up @@ -36,10 +40,22 @@ export type RoutedPaginationProviderProps = PaginationProviderProps & {
}

export const RoutedPaginationProvider = ({ path, ...rest }: PropsWithChildren<RoutedPaginationProviderProps>) => {
const { page }: { page?: number } = useParams()
const { search } = useLocation()
const { page, ...extraParams }: { page?: number } = useParams()
const p = page && page > 0 ? page - 1 : 0

return <RoutedPaginationContext.Provider value={{ page: p, path }} {...rest} />
const navigate = useNavigate()

const getPathForPage = (page: number, queryParams?: string) => {
const p = queryParams || search
return generatePath(path, { page, ...extraParams }) + p
}

const setPage = (page: number, queryParams?: string) => {
navigate(getPathForPage(page, queryParams))
}

return <RoutedPaginationContext.Provider value={{ page: p, path, getPathForPage, setPage }} {...rest} />
}

export const PaginationProvider = ({ ...rest }: PropsWithChildren<PaginationProviderProps>) => {
Expand Down
63 changes: 34 additions & 29 deletions src/components/Process/ProcessList.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import {
Box,
Button,
Checkbox,
Flex,
FormControl,
FormErrorMessage,
IconButton,
Popover,
PopoverBody,
Expand All @@ -14,21 +17,22 @@ import { Trans, useTranslation } from 'react-i18next'
import { InputSearch } from '~components/Layout/Inputs'
import { LoadingCards } from '~components/Layout/Loading'
import { ContentError, NoResultsError } from '~components/Layout/ContentError'
import { RoutedPaginationProvider, useRoutedPagination } from '~components/Pagination/PaginationProvider'
import { useRoutedPagination } from '~components/Pagination/PaginationProvider'
import { RoutedPagination } from '~components/Pagination/RoutedPagination'
import { RoutePath } from '~constants'
import { useProcessList } from '~queries/processes'
import useQueryParams from '~src/router/use-query-params'
import { isEmpty } from '~utils/objects'
import { ElectionCard } from './Card'
import { LuListFilter } from 'react-icons/lu'
import { isValidPartialProcessId } from '~utils/strings'
import { useState } from 'react'
import { useRoutedPaginationQueryParams } from '~src/router/use-query-params'

type FilterQueryParams = {
[K in keyof Omit<FetchElectionsParameters, 'organizationId'>]: string
}

const PopoverFilter = () => {
const { queryParams, setQueryParams } = useQueryParams<FilterQueryParams>()
const { queryParams, setQueryParams } = useRoutedPaginationQueryParams<FilterQueryParams>()

return (
<Popover>
Expand Down Expand Up @@ -65,29 +69,38 @@ const PopoverFilter = () => {

export const ProcessSearchBox = () => {
const { t } = useTranslation()
const { queryParams, setQueryParams } = useQueryParams<FilterQueryParams>()
const [isInvalid, setIsInvalid] = useState(false)
const { queryParams, setQueryParams } = useRoutedPaginationQueryParams<FilterQueryParams>()

return (
<Flex direction={{ base: 'column', lg: 'row' }} flexDirection={{ base: 'column-reverse', lg: 'row' }} gap={4}>
<PopoverFilter />
<Flex>
<InputSearch
maxW={'300px'}
placeholder={t('process.search_by')}
onChange={(value: string) => {
setQueryParams({ ...queryParams, electionId: value })
}}
initialValue={queryParams.electionId}
debounceTime={500}
/>
<FormControl isInvalid={isInvalid}>
<Flex direction={{ base: 'column', lg: 'row' }} flexDirection={{ base: 'column', md: 'row' }} gap={4}>
<PopoverFilter />
<Flex direction={'column'}>
<InputSearch
maxW={'300px'}
placeholder={t('process.search_by')}
onChange={(value: string) => {
const isInvalid = value !== '' && !isValidPartialProcessId(value)
setIsInvalid(isInvalid)
if (!isInvalid) {
setQueryParams({ ...queryParams, electionId: value })
}
}}
initialValue={queryParams.electionId}
debounceTime={500}
isInvalid={isInvalid}
/>
<Box minHeight='25px'>{isInvalid && <FormErrorMessage>Not valid partial process id</FormErrorMessage>}</Box>
</Flex>
</Flex>
</Flex>
</FormControl>
)
}

export const ProcessByTypeFilter = () => {
const { t } = useTranslation()
const { queryParams, setQueryParams } = useQueryParams<FilterQueryParams>()
const { queryParams, setQueryParams } = useRoutedPaginationQueryParams<FilterQueryParams>()

const currentStatus = queryParams.status

Expand Down Expand Up @@ -126,17 +139,9 @@ export const ProcessByTypeFilter = () => {
)
}

export const PaginatedProcessList = () => {
return (
<RoutedPaginationProvider path={RoutePath.ProcessesList}>
<ProcessList />
</RoutedPaginationProvider>
)
}

const ProcessList = () => {
export const ProcessList = () => {
const { page }: { page?: number } = useRoutedPagination()
const { queryParams: processFilters } = useQueryParams<FilterQueryParams>()
const { queryParams: processFilters } = useRoutedPaginationQueryParams<FilterQueryParams>()

const { data, isLoading, isFetching, isError, error } = useProcessList({
filters: {
Expand Down
4 changes: 2 additions & 2 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export enum RoutePath {
BlocksList = '/blocks/:page?',
Envelope = '/envelope/:verifier/:tab?',
Account = '/account/:pid/:tab?/:page?',
AccountsList = '/accounts/:page?/:query?',
AccountsList = '/accounts/:page?',
Process = '/process/:pid/:tab?',
ProcessesList = '/processes/:page?',
Transaction = '/transactions/:block/:index/:tab?',
Expand All @@ -42,7 +42,7 @@ export enum OldRoutePath {
OrganizationDetails = '/organizations/show/#/',
ProcessDetails = '/processes/show/#/',
TransactionDetails = '/transactions/show/#/',
Verify = '/verify',
Verify = '/verify/#/',
Stats = '/stats',
OrganizationsList = '/organizations',
Organization = '/organization',
Expand Down
13 changes: 8 additions & 5 deletions src/pages/accounts.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { AccountsFilter, PaginatedAccountsList } from '~components/Accounts/List'
import { AccountsFilter, AccountsList } from '~components/Accounts/List'
import ListPageLayout from '~components/Layout/ListPageLayout'
import { useOrganizationCount } from '~queries/accounts'
import { useTranslation } from 'react-i18next'
import { RefreshIntervalPagination } from '~constants'
import { RefreshIntervalPagination, RoutePath } from '~constants'
import { RoutedPaginationProvider } from '~components/Pagination/PaginationProvider'

const OrganizationList = () => {
const { t } = useTranslation()
Expand All @@ -13,9 +14,11 @@ const OrganizationList = () => {
const subtitle = !isLoading ? t('accounts.accounts_count', { count: orgsCount || 0 }) : ''

return (
<ListPageLayout title={t('accounts.accounts_list')} subtitle={subtitle} rightComponent={<AccountsFilter />}>
<PaginatedAccountsList />
</ListPageLayout>
<RoutedPaginationProvider path={RoutePath.AccountsList}>
<ListPageLayout title={t('accounts.accounts_list')} subtitle={subtitle} rightComponent={<AccountsFilter />}>
<AccountsList />
</ListPageLayout>
</RoutedPaginationProvider>
)
}

Expand Down
19 changes: 13 additions & 6 deletions src/pages/processes.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import ListPageLayout from '~components/Layout/ListPageLayout'
import { useTranslation } from 'react-i18next'
import { useProcessesCount } from '~queries/processes'
import { PaginatedProcessList, ProcessByTypeFilter, ProcessSearchBox } from '~components/Process/ProcessList'
import { RefreshIntervalPagination } from '~constants'
import {
ProcessList as PaginatedProcessList,
ProcessByTypeFilter,
ProcessSearchBox,
} from '~components/Process/ProcessList'
import { RefreshIntervalPagination, RoutePath } from '~constants'
import { RoutedPaginationProvider } from '~components/Pagination/PaginationProvider'

const ProcessList = () => {
const { t } = useTranslation()
Expand All @@ -13,10 +18,12 @@ const ProcessList = () => {
const subtitle = !isLoading ? t('process.process_count', { count: data || 0 }) : ''

return (
<ListPageLayout title={t('process.process_list')} subtitle={subtitle} rightComponent={<ProcessSearchBox />}>
<ProcessByTypeFilter />
<PaginatedProcessList />
</ListPageLayout>
<RoutedPaginationProvider path={RoutePath.ProcessesList}>
<ListPageLayout title={t('process.process_list')} subtitle={subtitle} rightComponent={<ProcessSearchBox />}>
<ProcessByTypeFilter />
<PaginatedProcessList />
</ListPageLayout>
</RoutedPaginationProvider>
)
}

Expand Down
Loading

2 comments on commit d2a765c

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.