diff --git a/src/components/UserBadge/UserBadge.tsx b/src/components/UserBadge/UserBadge.tsx index b450fc0..e153374 100644 --- a/src/components/UserBadge/UserBadge.tsx +++ b/src/components/UserBadge/UserBadge.tsx @@ -12,6 +12,7 @@ const UserBadge: React.FC<{ roles: IUserRole[] }> = ({ roles }) => { if (userHasRoles(roles, [UserRoleEnum.ADMIN])) return { name: 'Administrator', color: 'purple' }; if (userHasRoles(roles, [UserRoleEnum.TEACHER])) return { name: 'Teacher', color: 'orange' }; if (userHasRoles(roles, [UserRoleEnum.STUDENT])) return { name: 'Student', color: 'blue' }; + if (roles.length === 0) return { name: 'Guest', color: 'gray' }; }; const role = getCurrentRole(); diff --git a/src/contexts/index.ts b/src/contexts/index.ts index af2a695..96ccd51 100644 --- a/src/contexts/index.ts +++ b/src/contexts/index.ts @@ -1,10 +1,17 @@ -import React from 'react'; -import { ProfilePageViewStatusContextType } from 'types'; +import { createContext } from 'react'; +import { ProfilePageViewStatusContextType, UsersPageContextType } from 'types'; import RootStore from '../stores/RootStore'; -export const StoreContext = React.createContext(undefined); +export const StoreContext = createContext(undefined); -export const ProfilePageViewContext = React.createContext({ +export const ProfilePageViewContext = createContext({ statusPageView: 'default', setStatusPageView: () => {}, }); + +export const UsersPageContext = createContext({ + searchingRole: undefined, + setSearchingRole: () => {}, + loading: false, + setLoading: () => {}, +}); diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index 21a3efe..e45c2e6 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -112,6 +112,9 @@ export interface IUserRole { export interface IApiRespose { data: T; meta?: { + /** + * The server can return the pagination meta data + */ pagination?: IPaginationMeta; }; message: string; diff --git a/src/pages/profile/index.tsx b/src/pages/profile/index.tsx index fbac1c4..600fcc5 100644 --- a/src/pages/profile/index.tsx +++ b/src/pages/profile/index.tsx @@ -75,6 +75,7 @@ const ProfilePage: React.FC = () => { {statusPageView.status === 'default' && ( My account + Update you contacts information or password here. = ({ users, pagination }) => { const { userStore } = useRootStore(); + const { searchingRole, loading, setLoading } = useContext(UsersPageContext); const handleError = useErrorHandler(); - const [loading, setLoading] = useState(false); - const pagesCount = Math.round(pagination.total / pagination.per_page); - const [pages] = useState(pagesCount < 1 ? 1 : pagesCount); - const [currentPage, setCurrentPage] = useState(pagination.current_page); + const pagesCount = Math.ceil(pagination.total / pagination.per_page); const nextPage = async () => { - if (currentPage === pages) return; try { setLoading(true); await userStore.loadUsersData({ - page: currentPage + 1, + page: pagination.current_page + 1, limit: pagination.per_page, + role: searchingRole, }); - setCurrentPage(p => p + 1); } catch (error: any) { handleError(error); } finally { @@ -37,14 +36,13 @@ const UserList: React.FC = ({ users, pagination }) => { }; const prevPage = async () => { - if (currentPage <= 1) return; try { setLoading(true); await userStore.loadUsersData({ - page: currentPage - 1, + page: pagination.current_page - 1, limit: pagination.per_page, + role: searchingRole, }); - setCurrentPage(p => p - 1); } catch (error: any) { handleError(error); } finally { @@ -52,46 +50,56 @@ const UserList: React.FC = ({ users, pagination }) => { } }; - if (pagination.total === 0) { - return ( - - There are no users in the system - - ); - } - return ( <> - - - Total users: ({pagination.total}) - Actions - - {loading ? ( - - Wait a second... + {pagination.total !== 0 ? ( + <> + + + Total: ({pagination.total}) + Actions + + {loading ? ( + + Wait a second... + + ) : ( + + {users.map(user => ( + + ))} + + )} - ) : ( - - {users.map(user => ( - - ))} - - )} - - - - Page {currentPage} of {pages} + + + Page {pagination.current_page} of {pagesCount} + + + + + + + + ) : ( + + Cannot find any users. - - - - - + )} ); }; diff --git a/src/pages/users/components/UserSearch/UserSearch.tsx b/src/pages/users/components/UserSearch/UserSearch.tsx new file mode 100644 index 0000000..cc19cf0 --- /dev/null +++ b/src/pages/users/components/UserSearch/UserSearch.tsx @@ -0,0 +1,53 @@ +import React, { useContext } from 'react'; +import { Flex, Select, Input, InputGroup, InputLeftElement } from '@chakra-ui/react'; +import { SearchIcon } from '@chakra-ui/icons'; +import { useRootStore } from 'hooks/useRootStore'; +import { UserRoleEnum } from 'enums'; +import { useErrorHandler } from 'react-error-boundary'; +import { UsersPageContext } from 'contexts'; +import { UsersPageContextType } from 'types'; + +type SelectRoleStateType = UserRoleEnum | 'any'; + +const UserSearch: React.FC = () => { + const { userStore } = useRootStore(); + const { searchingRole, setSearchingRole, setLoading } = useContext(UsersPageContext); + const { pagination } = userStore; + const handleError = useErrorHandler(); + + if (pagination === undefined) return null; + + const onSearchRole: React.ChangeEventHandler | undefined = async e => { + try { + setLoading(true); + const value = e.target.value as SelectRoleStateType; + await userStore.loadUsersData({ + page: 1, + limit: pagination.per_page, + role: value, + }); + setSearchingRole(value); + } catch (error) { + handleError(error); + } finally { + setLoading(false); + } + }; + + return ( + + + + } /> + + + + ); +}; + +export default UserSearch; diff --git a/src/pages/users/components/UserSearch/index.ts b/src/pages/users/components/UserSearch/index.ts new file mode 100644 index 0000000..d24cd19 --- /dev/null +++ b/src/pages/users/components/UserSearch/index.ts @@ -0,0 +1,3 @@ +import UserSearch from './UserSearch'; + +export default UserSearch; diff --git a/src/pages/users/index.tsx b/src/pages/users/index.tsx index 137e189..319ac2c 100644 --- a/src/pages/users/index.tsx +++ b/src/pages/users/index.tsx @@ -1,19 +1,19 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import { observer } from 'mobx-react'; -import { Select } from '@chakra-ui/react'; -import { Box, Flex, Button, Heading } from '@chakra-ui/react'; -import { Input, InputGroup, InputLeftElement } from '@chakra-ui/react'; -import { AddIcon, SearchIcon } from '@chakra-ui/icons'; +import { Box, Flex, Button, Heading, Text } from '@chakra-ui/react'; +import { AddIcon } from '@chakra-ui/icons'; import { FaFileExcel } from 'react-icons/fa'; /** * Types */ import { IPageProps } from 'interfaces'; +import { SearchingRoleStateType } from 'types'; /** * Components */ +import UserSearch from './components/UserSearch'; import UserList from './components/UserList'; import LoadingPage from './components/LoadingPage'; @@ -24,53 +24,50 @@ import { useRootStore } from 'hooks/useRootStore'; import { useErrorHandler } from 'react-error-boundary'; import { useLocation } from 'react-router'; +/** + * Context + */ +import { UsersPageContext } from 'contexts'; + /** * Users Page */ const UsersPage: React.FC = ({ title }) => { const { userStore } = useRootStore(); + const [searchingRole, setSearchingRole] = useState(undefined); + const [loading, setLoading] = useState(false); const handleError = useErrorHandler(); const location = useLocation(); useEffect(() => { - userStore.loadUsersData({ page: 1, limit: 2 }).catch(error => handleError(error)); + userStore.loadUsersData().catch(error => handleError(error)); }, [handleError, userStore, location.search]); return ( - - User management - {userStore.users !== null ? ( - - - - - - } /> - - - - - - + + + User management + You can add or delete your users on this page. + {userStore.users !== null ? ( + + + + + + + + {userStore.pagination && } - {userStore.users.meta.pagination && ( - - )} - - ) : ( - - )} - + ) : ( + + )} + + ); }; diff --git a/src/services/UserService.ts b/src/services/UserService.ts index 5fecac3..87a3c2c 100644 --- a/src/services/UserService.ts +++ b/src/services/UserService.ts @@ -1,8 +1,9 @@ import { AxiosInstance } from 'axios'; import { IApiRespose, IMe, IUser, IUserContacts } from 'interfaces'; import * as helpers from 'helpers'; +import { UserRoleEnum } from 'enums'; -export type FetchUsersParamsType = { page?: number; limit?: number }; +export type FetchUsersParamsType = { page?: number; limit?: number; role?: UserRoleEnum | 'any' }; export default class UserService { public api: AxiosInstance; @@ -17,11 +18,12 @@ export default class UserService { } public async fetchAll(params?: FetchUsersParamsType): Promise> { - const { page, limit } = params || {}; + const { page = 1, limit = 1, role } = params || {}; const result = await this.api.get('/v1/users', { params: { page, limit, + role: role === 'any' ? undefined : role, }, }); return result.data; diff --git a/src/stores/UserStore.ts b/src/stores/UserStore.ts index a1c2a0d..0bf1ae7 100644 --- a/src/stores/UserStore.ts +++ b/src/stores/UserStore.ts @@ -19,7 +19,9 @@ export default class UserStore { public me: IMe | null = null; - public users: { data: IUser[]; meta: { pagination?: IPaginationMeta } } | null = null; + public users: IUser[] | null = null; + + public pagination: IPaginationMeta | undefined; constructor(root: RootStore, api: AxiosInstance) { this.root = root; @@ -33,12 +35,8 @@ export default class UserStore { console.log(`[${this.constructor.name}]: ${result.message}`, result); runInAction(() => { - this.users = { - data: result.data, - meta: { - pagination: result.meta?.pagination, - }, - }; + this.users = result.data; + this.pagination = result.meta && result.meta.pagination; }); return result; @@ -70,7 +68,7 @@ export default class UserStore { }); return result; - } catch (error) { + } catch (error: any) { throw error; } } @@ -92,7 +90,7 @@ export default class UserStore { }); return result; - } catch (error) { + } catch (error: any) { throw error; } } @@ -108,7 +106,7 @@ export default class UserStore { try { const result = await this.userService.updatePassword(oldPassword, newPassword); return result; - } catch (error) { + } catch (error: any) { throw error; } } @@ -123,7 +121,7 @@ export default class UserStore { try { const result = await this.userService.updateEmail(email); return result; - } catch (error) { + } catch (error: any) { throw error; } } @@ -144,7 +142,7 @@ export default class UserStore { } }); return result; - } catch (error) { + } catch (error: any) { throw error; } } diff --git a/src/types/index.d.ts b/src/types/index.d.ts index 996fd27..f94869a 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -1,4 +1,5 @@ import { UserRoleEnum } from 'enums'; +import React from 'react'; import { IconType } from 'react-icons/lib'; export type LinkType = { @@ -27,7 +28,22 @@ export type ProfilePageStatusType = { confirmEmailData?: ConfirmEmailContainerDataType; }; }; + export type ProfilePageViewStatusContextType = { statusPageView: ProfilePageStatusType['status']; setStatusPageView: React.Dispatch>; }; + +export type SearchingRoleStateType = UserRoleEnum | 'any' | undefined; +export type UsersPageContextType = { + /** + * Search users by user role + */ + searchingRole: SearchingRoleStateType; + setSearchingRole: React.Dispatch>; + /** + * Set loading state while loading users + */ + loading: boolean; + setLoading: React.Dispatch>; +};