refactor: refactored user list & alert dialog

This commit is contained in:
Sergey Yarkov 2021-11-08 21:57:06 +03:00
parent 479a6291eb
commit 2bcaad519e
16 changed files with 363 additions and 355 deletions

View File

@ -0,0 +1,63 @@
import React, { useState } from 'react';
import {
AlertDialog,
AlertDialogBody,
AlertDialogOverlay,
AlertDialogContent,
AlertDialogHeader,
AlertDialogFooter,
Button,
} from '@chakra-ui/react';
import { useRef } from 'react';
export interface IConfirmDialogProps {
title?: string;
message?: string;
confirmMessage?: string;
isOpen: boolean;
isLoading?: boolean;
onClose: (isConfirmed: boolean) => void;
}
const ConfirmDialog: React.FC<IConfirmDialogProps> = ({
title,
message,
confirmMessage,
isOpen,
isLoading,
onClose,
}) => {
const cancelRef = useRef<HTMLButtonElement | null>(null);
return (
<AlertDialog isOpen={isOpen} leastDestructiveRef={cancelRef} onClose={() => onClose(false)}>
<AlertDialogOverlay>
<AlertDialogContent>
<AlertDialogHeader fontSize='lg' fontWeight='bold'>
{title || 'Confirm that action'}
</AlertDialogHeader>
<AlertDialogBody>{message || 'Would you like to perform this action?'}</AlertDialogBody>
<AlertDialogFooter>
<Button ref={cancelRef} onClick={() => onClose(false)}>
Cancel
</Button>
<Button
isLoading={isLoading}
loadingText='Loading...'
colorScheme='red'
onClick={() => onClose(true)}
ml={3}
>
{confirmMessage || 'Confirm'}
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogOverlay>
</AlertDialog>
);
};
export default ConfirmDialog;

View File

@ -14,8 +14,8 @@ export const ChangeEmailPageContext = createContext<ChangeEmailPageContextType>(
export const UsersPageContext = createContext<UsersPageContextType>({
searchingRole: undefined,
setSearchingRole: () => {},
loading: false,
setLoading: () => {},
searchingPage: 1,
setSearchingPage: () => {},
search: undefined,
setSearch: () => {},
editingUser: undefined,

View File

@ -92,6 +92,7 @@ const CourseItem: React.FC<CourseItemPropsType> = ({ course, onDelete, onSetStat
to={`/course/${course.id}`}
as={ReactRouterLink}
display='block'
h='full'
textDecoration='none'
_visited={{ textDecoration: 'none' }}
_hover={{ textDecoration: 'none' }}

View File

@ -56,15 +56,6 @@ const CourseList: React.FC<CourseListPropsType> = ({ render: Item }) => {
}
}, [courseStore, selectedCategory, courseStatus]);
/**
* Handle delete course
* Set deleting course to context and open dialog
*/
const onDeleteCourse = (course: Pick<ICourse, 'id' | 'title'>) => {
setDeletingCourse(course);
onOpenDeleteDialog();
};
/**
* Handle update course status
*/
@ -81,29 +72,38 @@ const CourseList: React.FC<CourseListPropsType> = ({ render: Item }) => {
}
};
/**
* Loading
*/
if (courses === null || isLoading) return <LoadingList />;
/**
* No courses
*/
if (courses.length === 0)
return (
<Box textAlign='center' mt='10' userSelect='none'>
<Text color='gray.500'>There are no courses in this category {courseStatus && 'or with this status'}.</Text>
</Box>
);
return (
<Box>
{deletingCourse && <DeleteCourseDialog onClose={onCloseDeleteDialog} isOpen={isOpenDeleteDialog} />}
{deletingCourse && (
<DeleteCourseDialog course={deletingCourse} onClose={onCloseDeleteDialog} isOpen={isOpenDeleteDialog} />
)}
{courses.length !== 0 ? (
<Grid templateColumns={{ sm: 'repeat(auto-fill, minmax(400px, 1fr))' }} gap='4'>
{courses.map(course => (
<Item key={course.id} course={course} onDelete={onDeleteCourse} onSetStatus={onSetStatus} />
<Item
key={course.id}
course={course}
onDelete={() => {
setDeletingCourse(course);
onOpenDeleteDialog();
}}
onSetStatus={onSetStatus}
/>
))}
</Grid>
) : (
<Box textAlign='center' mt='10' userSelect='none'>
<Text color='gray.500'>
There are no courses&nbsp;
{!selectedCategory && !courseStatus
? 'in the system yet'
: `in this category${courseStatus ? ' or with this status' : ''}`}
.
</Text>
</Box>
)}
</Box>
);
};

View File

@ -10,6 +10,11 @@ import {
useToast,
} from '@chakra-ui/react';
/**
* Components
*/
import ConfirmDialog from '@educt/components/ConfirmDialog/ConfirmDialog';
/**
* Contexts
*/
@ -18,36 +23,35 @@ import { CoursesPageContext } from '@educt/contexts';
/**
* Hooks
*/
import useIsMountedRef from '@educt/hooks/useIsMountedRef';
import { useErrorHandler } from 'react-error-boundary';
import { useRootStore } from '@educt/hooks/useRootStore';
import { ICourse } from '@educt/interfaces';
type DeleteCourseDialogProps = {
type DeleteCourseDialogPropsType = {
course: Pick<ICourse, 'id' | 'title'>;
onClose: () => void;
isOpen: boolean;
};
const DeleteCourseDialog: React.FC<DeleteCourseDialogProps> = ({ onClose, isOpen }) => {
const DeleteCourseDialog: React.FC<DeleteCourseDialogPropsType> = ({ onClose, isOpen, course }) => {
const { courseStore } = useRootStore();
const { deletingCourse, setDeletingCourse } = useContext(CoursesPageContext);
const isMountedRef = useIsMountedRef();
const { setDeletingCourse } = useContext(CoursesPageContext);
const [isLoading, setIsLoading] = useState<boolean>(false);
const cancelRef = useRef<HTMLButtonElement | null>(null);
const handleError = useErrorHandler();
const toast = useToast();
/**
* Delete course handler
*
* @param isConfirmed Is dialog confirmed
* @param id Course id
*/
const onDelete = async (id: string) => {
const deleteCourseHandler = async (isConfirmed: boolean, id: string) => {
if (isConfirmed) {
try {
setIsLoading(true);
await courseStore.deleteCourse(id);
toast({ title: 'Course deleted.', status: 'info' });
onClose();
setDeletingCourse(undefined);
} catch (error: any) {
if (error.response) {
toast({ title: error.message, status: 'error' });
@ -55,50 +59,26 @@ const DeleteCourseDialog: React.FC<DeleteCourseDialogProps> = ({ onClose, isOpen
handleError(error);
}
} finally {
if (isMountedRef.current) {
setIsLoading(false);
setDeletingCourse(undefined);
}
}
/**
* Close dialog
*/
onClose();
};
if (deletingCourse === undefined) return null;
return (
<>
<AlertDialog isOpen={isOpen} leastDestructiveRef={cancelRef} onClose={onClose}>
<AlertDialogOverlay>
<AlertDialogContent>
<AlertDialogHeader fontSize='lg' fontWeight='bold'>
Confirm deleting
</AlertDialogHeader>
<AlertDialogBody>
The course with title "{deletingCourse.title}" will be removed from the system. After deleting a course,
the lessons in it will also be deleted.
</AlertDialogBody>
<AlertDialogFooter>
<Button
ref={cancelRef}
onClick={() => {
setDeletingCourse(undefined);
onClose();
}}
>
Cancel
</Button>
<Button
colorScheme='red'
<ConfirmDialog
isOpen={isOpen}
isLoading={isLoading}
loadingText='Deleting...'
onClick={() => onDelete(deletingCourse.id)}
ml={3}
>
Delete
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogOverlay>
</AlertDialog>
</>
onClose={isConfirmed => deleteCourseHandler(isConfirmed, course.id)}
title='Confirm deleting'
message={`The course with title "${course.title}" will be removed from the system. After deleting a course, the lessons in it will also be deleted.`}
confirmMessage='Delete'
/>
);
};

View File

@ -45,9 +45,9 @@ type CreateCourseInputType = {
description: string;
teacher_id: string;
};
type CreateCourseFormProps = {};
type CreateCourseFormPropsType = {};
const CreateCourseForm: React.FC<CreateCourseFormProps> = () => {
const CreateCourseForm: React.FC<CreateCourseFormPropsType> = () => {
const {
userStore: { userService },
categoryStore: { categoryService },
@ -58,8 +58,6 @@ const CreateCourseForm: React.FC<CreateCourseFormProps> = () => {
handleSubmit,
formState: { errors },
control,
watch,
reset,
} = useForm<CreateCourseInputType>({
resolver: yupResolver(CreateCourseSchema),
});
@ -205,6 +203,7 @@ const CreateCourseForm: React.FC<CreateCourseFormProps> = () => {
{errors.category_id?.message}
</Text>
</FormControl>
<FormControl
isRequired
id='teacher_id'
@ -231,6 +230,7 @@ const CreateCourseForm: React.FC<CreateCourseFormProps> = () => {
{errors.teacher_id?.message}
</Text>
</FormControl>
<FormControl isRequired id='description' gridColumn='1' isInvalid={!!errors.description}>
<FormLabel>Description</FormLabel>
<Textarea

View File

@ -1,4 +1,4 @@
import React, { useContext, useState } from 'react';
import React from 'react';
import {
Modal,
ModalOverlay,
@ -21,18 +21,17 @@ import {
import { AddIcon } from '@chakra-ui/icons';
import { MdSupervisorAccount } from 'react-icons/md';
import { yupResolver } from '@hookform/resolvers/yup';
import { SubmitHandler } from 'react-hook-form';
import CreateUserSchema from './CreateUserModal.validator';
/**
* Types
*/
import { IMe } from '@educt/interfaces';
import { UserRoleEnum } from '@educt/enums';
import { SubmitHandler } from 'react-hook-form';
/**
* Hooks
*/
import { useContext, useState } from 'react';
import useIsMountedRef from '@educt/hooks/useIsMountedRef';
import { useDisclosure } from '@chakra-ui/hooks';
import { useForm } from 'react-hook-form';
@ -45,9 +44,12 @@ import { useToast } from '@chakra-ui/react';
*/
import { UsersPageContext } from '@educt/contexts';
type CreateUserModalPropsType = {
me: IMe;
};
/**
* Schema
*/
import CreateUserSchema from './CreateUserForm.validator';
type CreateUserFormPropsType = {};
type CreateUserInputType = {
first_name: string;
@ -58,7 +60,7 @@ type CreateUserInputType = {
password: string;
};
const CreateUserModal: React.FC<CreateUserModalPropsType> = ({ me }) => {
const CreateUserModal: React.FC<CreateUserFormPropsType> = () => {
const { userStore } = useRootStore();
const { searchingRole, search } = useContext(UsersPageContext);
const { isOpen, onOpen, onClose } = useDisclosure();
@ -75,8 +77,12 @@ const CreateUserModal: React.FC<CreateUserModalPropsType> = ({ me }) => {
const toast = useToast();
const handleError = useErrorHandler();
const { me } = userStore;
if (me === null) return null;
/**
* Create new user handler
* Submit handler
*/
const onSubmit: SubmitHandler<CreateUserInputType> = async data => {
try {

View File

@ -1,52 +1,60 @@
import React, { useContext, useRef, useState } from 'react';
import {
AlertDialog,
AlertDialogBody,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogContent,
AlertDialogOverlay,
Button,
} from '@chakra-ui/react';
import React from 'react';
/**
* Types
*/
import { IUser } from '@educt/interfaces';
/**
* Components
*/
import ConfirmDialog from '@educt/components/ConfirmDialog/ConfirmDialog';
/**
* Contexts
*/
import { UsersPageContext } from '@educt/contexts';
/**
* Hooks
*/
import { useContext, useState } from 'react';
import { useRootStore } from '@educt/hooks/useRootStore';
import useIsMountedRef from '@educt/hooks/useIsMountedRef';
import { useToast } from '@chakra-ui/react';
import { useErrorHandler } from 'react-error-boundary';
type DeleteUserDialogPropsType = {
onClose: () => void;
user: IUser;
isOpen: boolean;
onClose: () => void;
};
const DeleteUserDialog: React.FC<DeleteUserDialogPropsType> = ({ onClose, isOpen }) => {
const DeleteUserDialog: React.FC<DeleteUserDialogPropsType> = ({ isOpen, onClose, user }) => {
const { userStore } = useRootStore();
const { deletingUser, setDeletingUser, searchingRole, search } = useContext(UsersPageContext);
const isMountedRef = useIsMountedRef();
const { setDeletingUser, searchingRole, searchingPage, search } = useContext(UsersPageContext);
const [isLoading, setIsLoading] = useState<boolean>(false);
const cancelRef = useRef<HTMLButtonElement | null>(null);
const handleError = useErrorHandler();
const toast = useToast();
const { pagination } = userStore;
/**
* Delete user handler
*
* @param isConfirmed Is dialog confirmed
* @param id User id
*/
const onDelete = async (id: string) => {
const deleteUserHandler = async (isConfirmed: boolean, id: string) => {
if (isConfirmed) {
try {
setIsLoading(true);
await userStore.deleteUser(id, {
page: userStore.pagination?.current_page,
limit: userStore.pagination?.per_page,
page: searchingPage,
limit: pagination?.per_page,
role: searchingRole,
search,
});
toast({ title: 'User deleted.', status: 'info' });
onClose();
setDeletingUser(undefined);
} catch (error: any) {
if (error.response) {
toast({ title: error.message, status: 'error' });
@ -54,49 +62,26 @@ const DeleteUserDialog: React.FC<DeleteUserDialogPropsType> = ({ onClose, isOpen
handleError(error);
}
} finally {
if (isMountedRef.current) {
setIsLoading(false);
setDeletingUser(undefined);
}
}
/**
* Close dialog
*/
onClose();
};
if (deletingUser === undefined) return null;
return (
<>
<AlertDialog isOpen={isOpen} leastDestructiveRef={cancelRef} onClose={onClose}>
<AlertDialogOverlay>
<AlertDialogContent>
<AlertDialogHeader fontSize='lg' fontWeight='bold'>
Confirm deleting
</AlertDialogHeader>
<AlertDialogBody>
The user named "{deletingUser.first_name}&nbsp;{deletingUser.last_name}" will be removed from the system.
</AlertDialogBody>
<AlertDialogFooter>
<Button
ref={cancelRef}
onClick={() => {
setDeletingUser(undefined);
onClose();
}}
>
Cancel
</Button>
<Button
colorScheme='red'
<ConfirmDialog
isOpen={isOpen}
isLoading={isLoading}
loadingText='Deleting...'
onClick={() => onDelete(deletingUser.id)}
ml={3}
>
Delete
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogOverlay>
</AlertDialog>
</>
onClose={isConfirmed => deleteUserHandler(isConfirmed, user.id)}
title='Confirm deleting'
message={`The user named "${user.fullname}" will be removed from the system.`}
confirmMessage='Delete'
/>
);
};

View File

@ -25,7 +25,7 @@ import { yupResolver } from '@hookform/resolvers/yup';
/**
* Types
*/
import { IMe } from '@educt/interfaces';
import { IMe, IUser } from '@educt/interfaces';
import { UserRoleEnum } from '@educt/enums';
import { SubmitHandler } from 'react-hook-form';
@ -49,9 +49,9 @@ import { UsersPageContext } from '@educt/contexts';
import UpdateUserSchema from './EditUserForm.validator';
type UpdateUserFormPropsType = {
onClose: () => void;
user: IUser;
isOpen: boolean;
me: IMe;
onClose: () => void;
};
type UpdateUserInputType = {
@ -63,9 +63,9 @@ type UpdateUserInputType = {
password: string | null;
};
const UpdateUserForm: React.FC<UpdateUserFormPropsType> = ({ onClose, isOpen, me }) => {
const UpdateUserForm: React.FC<UpdateUserFormPropsType> = ({ user, onClose, isOpen }) => {
const { userStore } = useRootStore();
const { editingUser, setEditingUser } = useContext(UsersPageContext);
const { setEditingUser } = useContext(UsersPageContext);
const [isLoading, setIsLoading] = useState<boolean>(false);
const {
register,
@ -75,10 +75,10 @@ const UpdateUserForm: React.FC<UpdateUserFormPropsType> = ({ onClose, isOpen, me
} = useForm<UpdateUserInputType>({
resolver: yupResolver(UpdateUserSchema),
defaultValues: {
first_name: editingUser && editingUser.first_name,
last_name: editingUser && editingUser.last_name,
email: editingUser && editingUser.email,
role: editingUser && editingUser.roles[0].slug,
first_name: user.first_name,
last_name: user.last_name,
email: user.email,
role: user.roles[0].slug,
login: '',
password: '',
},
@ -87,13 +87,18 @@ const UpdateUserForm: React.FC<UpdateUserFormPropsType> = ({ onClose, isOpen, me
const toast = useToast();
const handleError = useErrorHandler();
if (editingUser === undefined) return null;
const { me } = userStore;
if (me === null) return null;
/**
* Submit handler
*/
const onSubmit: SubmitHandler<UpdateUserInputType> = async data => {
try {
setIsLoading(true);
const params = Object.fromEntries(Object.keys(dirtyFields).map(k => [k, data[k as keyof UpdateUserInputType]]));
await userStore.updateUser(editingUser.id, params);
await userStore.updateUser(user.id, params);
toast({ title: 'User updated.', status: 'info' });
onCloseModal();
} catch (error: any) {
@ -136,7 +141,7 @@ const UpdateUserForm: React.FC<UpdateUserFormPropsType> = ({ onClose, isOpen, me
<Stack spacing='4'>
<FormControl isDisabled>
<FormLabel>Identificator</FormLabel>
<Input value={editingUser?.id} />
<Input value={user.id} />
</FormControl>
<FormControl>

View File

@ -14,7 +14,6 @@ import DeleteUserDialog from '../DeleteUserDialog';
/**
* Types
*/
import type { IUser } from '@educt/interfaces';
import type { UsersPageContextType } from '@educt/types';
import type { UserItemPropsType } from './UserItem';
@ -27,107 +26,75 @@ import { UsersPageContext } from '@educt/contexts';
* Hooks
*/
import { useEffect, useContext } from 'react';
import { useLocation } from 'react-router';
import { useErrorHandler } from 'react-error-boundary';
import { useRootStore } from '@educt/hooks/useRootStore';
import { useDisclosure } from '@chakra-ui/hooks';
type UserListPropsType = { render: React.FC<UserItemPropsType> };
type UserListPropsType = { render: React.FC<UserItemPropsType>; limit?: number };
const UserList: React.FC<UserListPropsType> = ({ render: Item }) => {
const UserList: React.FC<UserListPropsType> = ({ render: Item, limit }) => {
const { userStore } = useRootStore();
const { searchingRole, loading, setLoading, search, editingUser, setEditingUser, deletingUser, setDeletingUser } =
useContext<UsersPageContextType>(UsersPageContext);
const location = useLocation();
const {
searchingRole,
search,
searchingPage,
setSearchingPage,
editingUser,
setEditingUser,
deletingUser,
setDeletingUser,
} = useContext<UsersPageContextType>(UsersPageContext);
const handleError = useErrorHandler();
const { onOpen: onOpenEditModal, onClose: onCloseEditModal, isOpen: isOpenEditModal } = useDisclosure();
const { onOpen: onOpenDeleteDialog, onClose: onCloseDeleteDialog, isOpen: isOpenDeleteDialog } = useDisclosure();
const { users, pagination, me } = userStore;
const { users, pagination, me, isLoading } = userStore;
/**
* Fetch users handler
*/
useEffect(() => {
userStore.loadUsersData().catch(error => handleError(error));
}, [handleError, userStore, location.search]);
userStore
.loadUsersData({
page: searchingPage,
limit: limit || pagination?.per_page,
search,
role: searchingRole,
})
.catch(error => handleError(error));
}, [handleError, userStore, search, searchingRole, searchingPage]);
if (pagination === undefined || users === null || me === null) return <LoadingList />;
const pagesCount = Math.ceil(pagination.total / pagination.per_page);
/**
* Handle next page
*/
const nextPage = async () => {
try {
setLoading(true);
await userStore.loadUsersData({
page: pagination.current_page + 1,
limit: pagination.per_page,
role: searchingRole,
search,
});
} catch (error: any) {
handleError(error);
} finally {
setLoading(false);
}
};
/**
* Handle prev page
*/
const prevPage = async () => {
try {
setLoading(true);
await userStore.loadUsersData({
page: pagination.current_page - 1,
limit: pagination.per_page,
role: searchingRole,
search,
});
} catch (error: any) {
handleError(error);
} finally {
setLoading(false);
}
};
/**
* Set editing state when click on edit button
* and open editing user form modal
*/
const onEditUser = (user: IUser) => {
setEditingUser(user);
onOpenEditModal();
};
/**
* Set deleting state when click on delete button
* and open delete user dialog
*/
const onDeleteUser = (user: IUser) => {
setDeletingUser(user);
onOpenDeleteDialog();
};
if (pagination.total === 0)
return (
<Box textAlign='center' mt='10' userSelect='none'>
<Text color='gray.500'>Cannot find any users.</Text>
</Box>
);
return (
<Box>
{editingUser && <EditUserForm user={editingUser} isOpen={isOpenEditModal} onClose={onCloseEditModal} />}
{deletingUser && (
<DeleteUserDialog user={deletingUser} isOpen={isOpenDeleteDialog} onClose={onCloseDeleteDialog} />
)}
{users.length !== 0 ? (
<>
{editingUser && <EditUserForm me={me} onClose={onCloseEditModal} isOpen={isOpenEditModal} />}
{deletingUser && <DeleteUserDialog onClose={onCloseDeleteDialog} isOpen={isOpenDeleteDialog} />}
<Box>
<Flex mt='7' p='0 10px' fontWeight='bold' alignItems='center' justifyContent='space-between'>
<Text>Total: ({pagination.total})</Text>
<Text>Actions</Text>
</Flex>
{!loading ? (
{!isLoading ? (
<Stack mt='4' spacing='2'>
{users.map(user => (
<Item key={user.id} user={user} onEdit={onEditUser} onDelete={onDeleteUser} />
<Item
key={user.id}
user={user}
onEdit={user => {
setEditingUser(user);
onOpenEditModal();
}}
onDelete={user => {
setDeletingUser(user);
onOpenDeleteDialog();
}}
/>
))}
</Stack>
) : (
@ -140,7 +107,7 @@ const UserList: React.FC<UserListPropsType> = ({ render: Item }) => {
</Box>
<Flex ml='auto' mr='auto' justifyContent='center' flexWrap='wrap' alignItems='center' sx={{ gap: '30px' }}>
<Button
onClick={prevPage}
onClick={() => setSearchingPage(prevP => prevP - 1)}
disabled={pagination.current_page <= 1}
variant='link'
leftIcon={<ChevronLeftIcon />}
@ -148,7 +115,7 @@ const UserList: React.FC<UserListPropsType> = ({ render: Item }) => {
Previous page
</Button>
<Button
onClick={nextPage}
onClick={() => setSearchingPage(prevP => prevP + 1)}
disabled={pagination.current_page === pagesCount}
variant='link'
rightIcon={<ChevronRightIcon />}
@ -158,6 +125,12 @@ const UserList: React.FC<UserListPropsType> = ({ render: Item }) => {
</Flex>
</Flex>
</>
) : (
<Box textAlign='center' mt='10' userSelect='none'>
<Text color='gray.500'>Cannot find any users.</Text>
</Box>
)}
</Box>
);
};

View File

@ -1,58 +1,44 @@
import React, { useContext } from 'react';
import React from 'react';
import { observer } from 'mobx-react';
import { Flex, Select, Input, InputGroup, InputLeftElement } from '@chakra-ui/react';
import { SearchIcon } from '@chakra-ui/icons';
import { useRootStore } from '@educt/hooks/useRootStore';
import { UserRoleEnum } from '@educt/enums';
import { useErrorHandler } from 'react-error-boundary';
import { UsersPageContext } from '@educt/contexts';
/**
* Types
*/
import { UsersPageContextType } from '@educt/types';
import { observer } from 'mobx-react';
import { UserRoleEnum } from '@educt/enums';
/**
* Contexts
*/
import { UsersPageContext } from '@educt/contexts';
/**
* Hooks
*/
import { useContext } from 'react';
type SelectRoleStateType = UserRoleEnum | 'any';
const UserSearch: React.FC = () => {
const { userStore } = useRootStore();
const { searchingRole, setSearchingRole, setLoading, search, setSearch } =
const { searchingRole, setSearchingRole, setSearchingPage, search, setSearch } =
useContext<UsersPageContextType>(UsersPageContext);
const { pagination } = userStore;
const handleError = useErrorHandler();
if (pagination === undefined) return null;
const onSearchRole: React.ChangeEventHandler<HTMLSelectElement> | undefined = async e => {
try {
setLoading(true);
const value = e.target.value as SelectRoleStateType;
await userStore.loadUsersData({
page: 1,
limit: pagination.per_page,
role: value,
search,
});
setSearchingRole(value);
} catch (error) {
handleError(error);
} finally {
setLoading(false);
}
/**
* Role change handler
*/
const onSearchRole: React.ChangeEventHandler<HTMLSelectElement> | undefined = e => {
setSearchingPage(1);
setSearchingRole(e.target.value as SelectRoleStateType);
};
const onSearch: React.ChangeEventHandler<HTMLInputElement> | undefined = async e => {
try {
const value = e.target.value;
setLoading(true);
setSearch(value);
await userStore.loadUsersData({
page: 1,
limit: pagination.per_page,
role: searchingRole,
search: value,
});
} catch (error) {
handleError(error);
} finally {
setLoading(false);
}
/**
* Search box change handler
*/
const onSearch: React.ChangeEventHandler<HTMLInputElement> | undefined = e => {
setSearchingPage(1);
setSearch(e.target.value);
};
return (
@ -71,4 +57,4 @@ const UserSearch: React.FC = () => {
);
};
export default observer(UserSearch);
export default UserSearch;

View File

@ -43,9 +43,9 @@ const UsersPage: React.FC<IPageProps> = () => {
<Flex mt='5' flexDir='column' h='full'>
<Flex justifyContent='space-between' sx={{ gap: '10px' }} flexWrap='wrap'>
<UserSearch />
<CreateUserForm me={me} />
<CreateUserForm />
</Flex>
<UserList render={UserItem} />
<UserList render={UserItem} limit={6} />
</Flex>
</Box>
</UsersPageContextProvider>

View File

@ -33,7 +33,7 @@ export const ChangeEmailPageContextProvider: React.FC = ({ children }) => {
*/
export const UsersPageContextProvider: React.FC = ({ children }) => {
const [searchingRole, setSearchingRole] = useState<SearchingRoleStateType>(undefined);
const [loading, setLoading] = useState<boolean>(false);
const [searchingPage, setSearchingPage] = useState<number>(1);
const [search, setSearch] = useState<string | undefined>(undefined);
const [editingUser, setEditingUser] = useState<IUser | undefined>(undefined);
const [deletingUser, setDeletingUser] = useState<IUser | undefined>(undefined);
@ -43,8 +43,8 @@ export const UsersPageContextProvider: React.FC = ({ children }) => {
value={{
searchingRole,
setSearchingRole,
loading,
setLoading,
searchingPage,
setSearchingPage,
search,
setSearch,
editingUser,

View File

@ -30,14 +30,21 @@ export default class UserStore {
public pagination: IPaginationMeta | undefined;
public isLoading: boolean = false;
constructor(root: RootStore, api: AxiosInstance) {
this.root = root;
this.userService = new UserService(api);
makeAutoObservable(this);
}
private setLoading(loading: boolean) {
this.isLoading = loading;
}
public async loadUsersData(params?: FetchUsersParamsType) {
try {
this.setLoading(true);
const result = await this.userService.fetchAll(params);
runInAction(() => {
@ -48,6 +55,8 @@ export default class UserStore {
return result;
} catch (error: any) {
throw error;
} finally {
this.setLoading(false);
}
}

12
src/types/index.d.ts vendored
View File

@ -59,12 +59,6 @@ export type UsersPageContextType = {
searchingRole: SearchingRoleStateType;
setSearchingRole: React.Dispatch<React.SetStateAction<SearchingRoleStateType>>;
/**
* Set loading state while loading users
*/
loading: boolean;
setLoading: React.Dispatch<React.SetStateAction<boolean>>;
/**
* Serach user by fullname or email
*/
@ -77,6 +71,12 @@ export type UsersPageContextType = {
editingUser: IUser | undefined;
setEditingUser: React.Dispatch<React.SetStateAction<IUser | undefined>>;
/**
* Search users by page
*/
searchingPage: number;
setSearchingPage: React.Dispatch<React.SetStateAction<number>>;
/**
* Set deleting user state when press on delete button
*/