Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix filter pagination #115

Merged
merged 6 commits into from
Aug 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading