From b596145b52aac1f0baddb858ce9f3315051eecbb Mon Sep 17 00:00:00 2001 From: Nikita Belous <88971585+WhiteNik16@users.noreply.github.com> Date: Fri, 2 Feb 2024 21:39:46 +0200 Subject: [PATCH] Add pagination and search to orgs page (#32) * 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 --- src/api/modules/orgs/enums/orgs.ts | 7 ++ src/api/modules/orgs/helpers/orgs.ts | 12 ++- src/api/modules/orgs/types/orgs.ts | 21 ++++- .../Orgs/pages/OrgsList/components/List.tsx | 81 ++++++++++++++----- .../OrgsList/components/ListSkeleton.tsx | 25 ++++++ .../Orgs/pages/OrgsList/components/index.ts | 1 - src/pages/Orgs/pages/OrgsList/index.tsx | 3 +- src/theme/components.ts | 20 +++++ src/ui/UiSkeleton.tsx | 8 ++ src/ui/index.ts | 1 + 10 files changed, 150 insertions(+), 29 deletions(-) create mode 100644 src/pages/Orgs/pages/OrgsList/components/ListSkeleton.tsx create mode 100644 src/ui/UiSkeleton.tsx diff --git a/src/api/modules/orgs/enums/orgs.ts b/src/api/modules/orgs/enums/orgs.ts index 92d0c4da..4257b64a 100644 --- a/src/api/modules/orgs/enums/orgs.ts +++ b/src/api/modules/orgs/enums/orgs.ts @@ -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 { @@ -19,3 +21,8 @@ export enum OrgUserRoles { Admin = '2', SuperAdmin = '3', } + +export enum OrgsRequestPage { + Limit = 'limit', + Number = 'number', +} diff --git a/src/api/modules/orgs/helpers/orgs.ts b/src/api/modules/orgs/helpers/orgs.ts index ca7dfcf2..8384b668 100644 --- a/src/api/modules/orgs/helpers/orgs.ts +++ b/src/api/modules/orgs/helpers/orgs.ts @@ -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, @@ -95,12 +98,13 @@ export const DUMMY_ORGS: Organization[] = [ }, ] -export const loadOrgs = async (query: OrgsRequestQueryParams) => { - const { data } = await api.get(`${ApiServicePaths.Orgs}/v1/orgs`, { +export const loadOrgs = ( + query: OrgsRequestQueryParams, + //Todo: change other api methods response to this for consistency +): Promise> => { + return api.get(`${ApiServicePaths.Orgs}/v1/orgs`, { query, }) - - return data } export const loadOrgsCount = async () => { diff --git a/src/api/modules/orgs/types/orgs.ts b/src/api/modules/orgs/types/orgs.ts index b5d54510..166fbbab 100644 --- a/src/api/modules/orgs/types/orgs.ts +++ b/src/api/modules/orgs/types/orgs.ts @@ -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 @@ -53,12 +58,19 @@ 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 = { @@ -66,3 +78,8 @@ export type OrgVerificationCode = { type: string code: string } + +//Todo: change other api methods response to this for consistency +export type OrgListMeta = { + count: number +} diff --git a/src/pages/Orgs/pages/OrgsList/components/List.tsx b/src/pages/Orgs/pages/OrgsList/components/List.tsx index f21a5b4d..66c852dc 100644 --- a/src/pages/Orgs/pages/OrgsList/components/List.tsx +++ b/src/pages/Orgs/pages/OrgsList/components/List.tsx @@ -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([], loadList, { - loadOnMount: true, - }) + } = useLoading>( + {} as JsonApiResponse, + 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 ( {isLoading ? ( - <> + ) : isLoadingError ? ( <> - ) : isEmpty ? ( + ) : !orgList?.length ? ( <> ) : ( - - {orgList.map(org => ( - - - - ))} - + <> + + {orgList.map(org => ( + + + + ))} + + + + setPage(prevState => ({ + ...prevState, + [OrgsRequestPage.Number]: page, + })) + } + /> + + )} ) diff --git a/src/pages/Orgs/pages/OrgsList/components/ListSkeleton.tsx b/src/pages/Orgs/pages/OrgsList/components/ListSkeleton.tsx new file mode 100644 index 00000000..46e75c81 --- /dev/null +++ b/src/pages/Orgs/pages/OrgsList/components/ListSkeleton.tsx @@ -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 ( + <> + + {Array.from({ length: cardsCount }).map((_, idx) => ( + + + + ))} + + + + + + ) +} diff --git a/src/pages/Orgs/pages/OrgsList/components/index.ts b/src/pages/Orgs/pages/OrgsList/components/index.ts index 98f09d48..f12006c1 100644 --- a/src/pages/Orgs/pages/OrgsList/components/index.ts +++ b/src/pages/Orgs/pages/OrgsList/components/index.ts @@ -1,2 +1 @@ export { default as List } from './List' -export { default as ListCard } from './ListCard' diff --git a/src/pages/Orgs/pages/OrgsList/index.tsx b/src/pages/Orgs/pages/OrgsList/index.tsx index e89f5db1..37be1456 100644 --- a/src/pages/Orgs/pages/OrgsList/index.tsx +++ b/src/pages/Orgs/pages/OrgsList/index.tsx @@ -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={ diff --git a/src/theme/components.ts b/src/theme/components.ts index 8cd8e080..53cc092c 100644 --- a/src/theme/components.ts +++ b/src/theme/components.ts @@ -240,6 +240,11 @@ export const components: Components> = { defaultProps: { animation: 'wave', }, + styleOverrides: { + root: ({ theme }) => ({ + backgroundColor: theme.palette.divider, + }), + }, }, MuiSwitch: { styleOverrides: { @@ -326,6 +331,21 @@ export const components: 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', diff --git a/src/ui/UiSkeleton.tsx b/src/ui/UiSkeleton.tsx new file mode 100644 index 00000000..1b31d1e3 --- /dev/null +++ b/src/ui/UiSkeleton.tsx @@ -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 +} diff --git a/src/ui/index.ts b/src/ui/index.ts index cfa228f3..0244a914 100644 --- a/src/ui/index.ts +++ b/src/ui/index.ts @@ -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'