Skip to content

Commit

Permalink
Add pagination and search to orgs page (#32)
Browse files Browse the repository at this point in the history
* wip pagination

* feat add pagination and search

* wip

* wip add load orgs instead reload

* update after review

* add desc for search in enum

* fix search

* fix after review

* update after review

* minor fixes

* fix: orgs list response type

* fix: orgs api helper

* feat: remove isEmpty in useLoading

* feat: return isEmpty in useLoading

* fix: change useLoading types in List

* add todo

* add second todo
  • Loading branch information
WhiteNik16 authored Feb 2, 2024
1 parent 39739c0 commit b596145
Show file tree
Hide file tree
Showing 10 changed files with 150 additions and 29 deletions.
7 changes: 7 additions & 0 deletions src/api/modules/orgs/enums/orgs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export enum OrgsRequestFilters {
Owner = 'owner',
UserDid = 'user_did',
Status = 'status',
//metadata is responsible for searching by org name or org description
Metadata = 'metadata',
}

export enum OrgsIncludes {
Expand All @@ -19,3 +21,8 @@ export enum OrgUserRoles {
Admin = '2',
SuperAdmin = '3',
}

export enum OrgsRequestPage {
Limit = 'limit',
Number = 'number',
}
12 changes: 8 additions & 4 deletions src/api/modules/orgs/helpers/orgs.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { JsonApiResponse } from '@distributedlab/jac'

import { api } from '@/api/clients'
import {
type Organization,
type OrganizationCreate,
OrgListMeta,
type OrgMetadata,
type OrgsRequestQueryParams,
OrgsStatuses,
Expand Down Expand Up @@ -95,12 +98,13 @@ export const DUMMY_ORGS: Organization[] = [
},
]

export const loadOrgs = async (query: OrgsRequestQueryParams) => {
const { data } = await api.get<Organization[]>(`${ApiServicePaths.Orgs}/v1/orgs`, {
export const loadOrgs = (
query: OrgsRequestQueryParams,
//Todo: change other api methods response to this for consistency
): Promise<JsonApiResponse<Organization[], OrgListMeta>> => {
return api.get<Organization[], OrgListMeta>(`${ApiServicePaths.Orgs}/v1/orgs`, {
query,
})

return data
}

export const loadOrgsCount = async () => {
Expand Down
21 changes: 19 additions & 2 deletions src/api/modules/orgs/types/orgs.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import type { OrgsIncludes, OrgsRequestFilters, OrgsStatuses } from '@/api/modules/orgs'
import type {
OrgsIncludes,
OrgsRequestFilters,
OrgsRequestPage,
OrgsStatuses,
} from '@/api/modules/orgs'

export type OrgMetadataLink = {
title: string
Expand Down Expand Up @@ -53,16 +58,28 @@ export type OrgsRequestFiltersMap = {
[OrgsRequestFilters.Owner]?: string
[OrgsRequestFilters.UserDid]?: string
[OrgsRequestFilters.Status]?: OrgsStatuses
[OrgsRequestFilters.Metadata]?: string
}

export type OrgsRequestPageMap = {
[OrgsRequestPage.Limit]: number
[OrgsRequestPage.Number]: number
}

export type OrgsRequestQueryParams = {
include?: OrgsIncludes
filter?: OrgsRequestFiltersMap
// TODO: page, limit, sort, ...etc
page?: OrgsRequestPageMap
// TODO: sort, ...etc
}

export type OrgVerificationCode = {
id: string
type: string
code: string
}

//Todo: change other api methods response to this for consistency
export type OrgListMeta = {
count: number
}
81 changes: 61 additions & 20 deletions src/pages/Orgs/pages/OrgsList/components/List.tsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,87 @@
import { Grid, Stack, StackProps } from '@mui/material'
import { useCallback } from 'react'
import { JsonApiResponse } from '@distributedlab/jac'
import { Grid, Pagination, Stack, StackProps } from '@mui/material'
import { useCallback, useEffect, useState } from 'react'

import { loadOrgs, Organization, type OrgsRequestFiltersMap } from '@/api/modules/orgs'
import {
loadOrgs,
Organization,
OrgListMeta,
OrgsRequestFilters,
type OrgsRequestFiltersMap,
OrgsRequestPage,
} from '@/api/modules/orgs'
import { useLoading } from '@/hooks'

import ListCard from './ListCard'
import ListSkeleton from './ListSkeleton'

interface Props extends StackProps {
filter: OrgsRequestFiltersMap
}

const ORGS_PAGE_LIMIT = 10

export default function List({ filter, ...rest }: Props) {
// TODO: add pagination
const [page, setPage] = useState({
[OrgsRequestPage.Number]: 1,
[OrgsRequestPage.Limit]: ORGS_PAGE_LIMIT,
})

const loadList = useCallback(async () => {
return loadOrgs({ filter })
}, [filter])
return loadOrgs({ filter, page })
}, [filter, page])

const {
data: orgList,
data: { data: orgList, meta },
isLoading,
isLoadingError,
isEmpty,
} = useLoading<Organization[]>([], loadList, {
loadOnMount: true,
})
} = useLoading<JsonApiResponse<Organization[], OrgListMeta>>(
{} as JsonApiResponse<Organization[], OrgListMeta>,
loadList,
{
loadArgs: [filter, page],
loadOnMount: true,
},
)

useEffect(() => {
setPage(prevState => ({
...prevState,
[OrgsRequestPage.Number]: 1,
}))
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [filter[OrgsRequestFilters.Metadata]])

return (
<Stack {...rest}>
{isLoading ? (
<></>
<ListSkeleton cardsCount={ORGS_PAGE_LIMIT} />
) : isLoadingError ? (
<></>
) : isEmpty ? (
) : !orgList?.length ? (
<></>
) : (
<Grid container spacing={6}>
{orgList.map(org => (
<Grid key={org.id} item xs={6}>
<ListCard org={org} />
</Grid>
))}
</Grid>
<>
<Grid container spacing={6}>
{orgList.map(org => (
<Grid key={org.id} item xs={6}>
<ListCard org={org} />
</Grid>
))}
</Grid>
<Stack alignItems='center' mt={6} {...rest}>
<Pagination
count={Math.ceil(meta.count / ORGS_PAGE_LIMIT)}
page={page[OrgsRequestPage.Number]}
onChange={(_, page) =>
setPage(prevState => ({
...prevState,
[OrgsRequestPage.Number]: page,
}))
}
/>
</Stack>
</>
)}
</Stack>
)
Expand Down
25 changes: 25 additions & 0 deletions src/pages/Orgs/pages/OrgsList/components/ListSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Grid, Stack, useTheme } from '@mui/material'

import { UiSkeleton } from '@/ui'

interface Props {
cardsCount: number
}

export default function ListSkeleton({ cardsCount }: Props) {
const { spacing } = useTheme()
return (
<>
<Grid container spacing={6}>
{Array.from({ length: cardsCount }).map((_, idx) => (
<Grid key={idx} item xs={6}>
<UiSkeleton variant='rounded' sx={{ borderRadius: 3 }} height={spacing(60)} />
</Grid>
))}
</Grid>
<Stack alignItems='center' mt={6}>
<UiSkeleton variant='rounded' height={spacing(6)} width={spacing(80)} />
</Stack>
</>
)
}
1 change: 0 additions & 1 deletion src/pages/Orgs/pages/OrgsList/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export { default as List } from './List'
export { default as ListCard } from './ListCard'
3 changes: 1 addition & 2 deletions src/pages/Orgs/pages/OrgsList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,7 @@ export default function OrgsList() {
setFilter(prev => ({
...prev,

// FIXME: remove this and add searching orgs by name in backend
[OrgsRequestFilters.UserDid]: value,
[OrgsRequestFilters.Metadata]: value,
}))
}
actionBar={
Expand Down
20 changes: 20 additions & 0 deletions src/theme/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,11 @@ export const components: Components<Omit<Theme, 'components'>> = {
defaultProps: {
animation: 'wave',
},
styleOverrides: {
root: ({ theme }) => ({
backgroundColor: theme.palette.divider,
}),
},
},
MuiSwitch: {
styleOverrides: {
Expand Down Expand Up @@ -326,6 +331,21 @@ export const components: Components<Omit<Theme, 'components'>> = {
}),
},
},
MuiPagination: {
defaultProps: {
color: 'primary',
hidePrevButton: true,
hideNextButton: true,
},
styleOverrides: {
root: ({ theme }) => ({
'& .MuiButtonBase-root': {
...typography.subtitle4,
color: theme.palette.text.secondary,
},
}),
},
},
MuiDrawer: {
defaultProps: {
anchor: 'right',
Expand Down
8 changes: 8 additions & 0 deletions src/ui/UiSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Skeleton, type SkeletonProps, useTheme } from '@mui/material'

interface Props extends SkeletonProps {}

export default function UiSkeleton({ ...rest }: Props) {
const { palette } = useTheme()
return <Skeleton {...rest} sx={{ bgcolor: palette.divider, ...rest.sx }} />
}
1 change: 1 addition & 0 deletions src/ui/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export { default as UiNavTabs } from './UiNavTabs'
export { default as UiRadioGroup } from './UiRadioGroup'
export { default as UiSearchField } from './UiSearchField'
export { default as UiSelect } from './UiSelect'
export { default as UiSkeleton } from './UiSkeleton'
export { default as UiStepper } from './UiStepper'
export { default as UiSwitch } from './UiSwitch'
export { default as UiTabs } from './UiTabs'
Expand Down

0 comments on commit b596145

Please sign in to comment.