mirror of
https://github.com/sergeyyarkov/educt-client.git
synced 2026-10-11 10:19:22 +03:00
refactor: refactored user list & alert dialog
This commit is contained in:
parent
479a6291eb
commit
2bcaad519e
63
src/components/ConfirmDialog/ConfirmDialog.tsx
Normal file
63
src/components/ConfirmDialog/ConfirmDialog.tsx
Normal 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;
|
||||||
@ -14,8 +14,8 @@ export const ChangeEmailPageContext = createContext<ChangeEmailPageContextType>(
|
|||||||
export const UsersPageContext = createContext<UsersPageContextType>({
|
export const UsersPageContext = createContext<UsersPageContextType>({
|
||||||
searchingRole: undefined,
|
searchingRole: undefined,
|
||||||
setSearchingRole: () => {},
|
setSearchingRole: () => {},
|
||||||
loading: false,
|
searchingPage: 1,
|
||||||
setLoading: () => {},
|
setSearchingPage: () => {},
|
||||||
search: undefined,
|
search: undefined,
|
||||||
setSearch: () => {},
|
setSearch: () => {},
|
||||||
editingUser: undefined,
|
editingUser: undefined,
|
||||||
|
|||||||
@ -92,6 +92,7 @@ const CourseItem: React.FC<CourseItemPropsType> = ({ course, onDelete, onSetStat
|
|||||||
to={`/course/${course.id}`}
|
to={`/course/${course.id}`}
|
||||||
as={ReactRouterLink}
|
as={ReactRouterLink}
|
||||||
display='block'
|
display='block'
|
||||||
|
h='full'
|
||||||
textDecoration='none'
|
textDecoration='none'
|
||||||
_visited={{ textDecoration: 'none' }}
|
_visited={{ textDecoration: 'none' }}
|
||||||
_hover={{ textDecoration: 'none' }}
|
_hover={{ textDecoration: 'none' }}
|
||||||
|
|||||||
@ -56,15 +56,6 @@ const CourseList: React.FC<CourseListPropsType> = ({ render: Item }) => {
|
|||||||
}
|
}
|
||||||
}, [courseStore, selectedCategory, courseStatus]);
|
}, [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
|
* Handle update course status
|
||||||
*/
|
*/
|
||||||
@ -81,29 +72,38 @@ const CourseList: React.FC<CourseListPropsType> = ({ render: Item }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Loading
|
|
||||||
*/
|
|
||||||
if (courses === null || isLoading) return <LoadingList />;
|
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 (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
{deletingCourse && <DeleteCourseDialog onClose={onCloseDeleteDialog} isOpen={isOpenDeleteDialog} />}
|
{deletingCourse && (
|
||||||
<Grid templateColumns={{ sm: 'repeat(auto-fill, minmax(400px, 1fr))' }} gap='4'>
|
<DeleteCourseDialog course={deletingCourse} onClose={onCloseDeleteDialog} isOpen={isOpenDeleteDialog} />
|
||||||
{courses.map(course => (
|
)}
|
||||||
<Item key={course.id} course={course} onDelete={onDeleteCourse} onSetStatus={onSetStatus} />
|
{courses.length !== 0 ? (
|
||||||
))}
|
<Grid templateColumns={{ sm: 'repeat(auto-fill, minmax(400px, 1fr))' }} gap='4'>
|
||||||
</Grid>
|
{courses.map(course => (
|
||||||
|
<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
|
||||||
|
{!selectedCategory && !courseStatus
|
||||||
|
? 'in the system yet'
|
||||||
|
: `in this category${courseStatus ? ' or with this status' : ''}`}
|
||||||
|
.
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -10,6 +10,11 @@ import {
|
|||||||
useToast,
|
useToast,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Components
|
||||||
|
*/
|
||||||
|
import ConfirmDialog from '@educt/components/ConfirmDialog/ConfirmDialog';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contexts
|
* Contexts
|
||||||
*/
|
*/
|
||||||
@ -18,87 +23,62 @@ import { CoursesPageContext } from '@educt/contexts';
|
|||||||
/**
|
/**
|
||||||
* Hooks
|
* Hooks
|
||||||
*/
|
*/
|
||||||
import useIsMountedRef from '@educt/hooks/useIsMountedRef';
|
|
||||||
import { useErrorHandler } from 'react-error-boundary';
|
import { useErrorHandler } from 'react-error-boundary';
|
||||||
import { useRootStore } from '@educt/hooks/useRootStore';
|
import { useRootStore } from '@educt/hooks/useRootStore';
|
||||||
|
import { ICourse } from '@educt/interfaces';
|
||||||
|
|
||||||
type DeleteCourseDialogProps = {
|
type DeleteCourseDialogPropsType = {
|
||||||
|
course: Pick<ICourse, 'id' | 'title'>;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DeleteCourseDialog: React.FC<DeleteCourseDialogProps> = ({ onClose, isOpen }) => {
|
const DeleteCourseDialog: React.FC<DeleteCourseDialogPropsType> = ({ onClose, isOpen, course }) => {
|
||||||
const { courseStore } = useRootStore();
|
const { courseStore } = useRootStore();
|
||||||
const { deletingCourse, setDeletingCourse } = useContext(CoursesPageContext);
|
const { setDeletingCourse } = useContext(CoursesPageContext);
|
||||||
const isMountedRef = useIsMountedRef();
|
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
const cancelRef = useRef<HTMLButtonElement | null>(null);
|
|
||||||
const handleError = useErrorHandler();
|
const handleError = useErrorHandler();
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete course handler
|
* Delete course handler
|
||||||
*
|
*
|
||||||
|
* @param isConfirmed Is dialog confirmed
|
||||||
* @param id Course id
|
* @param id Course id
|
||||||
*/
|
*/
|
||||||
const onDelete = async (id: string) => {
|
const deleteCourseHandler = async (isConfirmed: boolean, id: string) => {
|
||||||
try {
|
if (isConfirmed) {
|
||||||
setIsLoading(true);
|
try {
|
||||||
await courseStore.deleteCourse(id);
|
setIsLoading(true);
|
||||||
toast({ title: 'Course deleted.', status: 'info' });
|
await courseStore.deleteCourse(id);
|
||||||
onClose();
|
toast({ title: 'Course deleted.', status: 'info' });
|
||||||
setDeletingCourse(undefined);
|
} catch (error: any) {
|
||||||
} catch (error: any) {
|
if (error.response) {
|
||||||
if (error.response) {
|
toast({ title: error.message, status: 'error' });
|
||||||
toast({ title: error.message, status: 'error' });
|
} else {
|
||||||
} else {
|
handleError(error);
|
||||||
handleError(error);
|
}
|
||||||
}
|
} finally {
|
||||||
} finally {
|
|
||||||
if (isMountedRef.current) {
|
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
setDeletingCourse(undefined);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close dialog
|
||||||
|
*/
|
||||||
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (deletingCourse === undefined) return null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<ConfirmDialog
|
||||||
<AlertDialog isOpen={isOpen} leastDestructiveRef={cancelRef} onClose={onClose}>
|
isOpen={isOpen}
|
||||||
<AlertDialogOverlay>
|
isLoading={isLoading}
|
||||||
<AlertDialogContent>
|
onClose={isConfirmed => deleteCourseHandler(isConfirmed, course.id)}
|
||||||
<AlertDialogHeader fontSize='lg' fontWeight='bold'>
|
title='Confirm deleting'
|
||||||
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.`}
|
||||||
</AlertDialogHeader>
|
confirmMessage='Delete'
|
||||||
<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'
|
|
||||||
isLoading={isLoading}
|
|
||||||
loadingText='Deleting...'
|
|
||||||
onClick={() => onDelete(deletingCourse.id)}
|
|
||||||
ml={3}
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</Button>
|
|
||||||
</AlertDialogFooter>
|
|
||||||
</AlertDialogContent>
|
|
||||||
</AlertDialogOverlay>
|
|
||||||
</AlertDialog>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -45,9 +45,9 @@ type CreateCourseInputType = {
|
|||||||
description: string;
|
description: string;
|
||||||
teacher_id: string;
|
teacher_id: string;
|
||||||
};
|
};
|
||||||
type CreateCourseFormProps = {};
|
type CreateCourseFormPropsType = {};
|
||||||
|
|
||||||
const CreateCourseForm: React.FC<CreateCourseFormProps> = () => {
|
const CreateCourseForm: React.FC<CreateCourseFormPropsType> = () => {
|
||||||
const {
|
const {
|
||||||
userStore: { userService },
|
userStore: { userService },
|
||||||
categoryStore: { categoryService },
|
categoryStore: { categoryService },
|
||||||
@ -58,8 +58,6 @@ const CreateCourseForm: React.FC<CreateCourseFormProps> = () => {
|
|||||||
handleSubmit,
|
handleSubmit,
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
control,
|
control,
|
||||||
watch,
|
|
||||||
reset,
|
|
||||||
} = useForm<CreateCourseInputType>({
|
} = useForm<CreateCourseInputType>({
|
||||||
resolver: yupResolver(CreateCourseSchema),
|
resolver: yupResolver(CreateCourseSchema),
|
||||||
});
|
});
|
||||||
@ -205,6 +203,7 @@ const CreateCourseForm: React.FC<CreateCourseFormProps> = () => {
|
|||||||
{errors.category_id?.message}
|
{errors.category_id?.message}
|
||||||
</Text>
|
</Text>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormControl
|
<FormControl
|
||||||
isRequired
|
isRequired
|
||||||
id='teacher_id'
|
id='teacher_id'
|
||||||
@ -231,6 +230,7 @@ const CreateCourseForm: React.FC<CreateCourseFormProps> = () => {
|
|||||||
{errors.teacher_id?.message}
|
{errors.teacher_id?.message}
|
||||||
</Text>
|
</Text>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormControl isRequired id='description' gridColumn='1' isInvalid={!!errors.description}>
|
<FormControl isRequired id='description' gridColumn='1' isInvalid={!!errors.description}>
|
||||||
<FormLabel>Description</FormLabel>
|
<FormLabel>Description</FormLabel>
|
||||||
<Textarea
|
<Textarea
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { useContext, useState } from 'react';
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
Modal,
|
Modal,
|
||||||
ModalOverlay,
|
ModalOverlay,
|
||||||
@ -21,18 +21,17 @@ import {
|
|||||||
import { AddIcon } from '@chakra-ui/icons';
|
import { AddIcon } from '@chakra-ui/icons';
|
||||||
import { MdSupervisorAccount } from 'react-icons/md';
|
import { MdSupervisorAccount } from 'react-icons/md';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
import { SubmitHandler } from 'react-hook-form';
|
|
||||||
import CreateUserSchema from './CreateUserModal.validator';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Types
|
* Types
|
||||||
*/
|
*/
|
||||||
import { IMe } from '@educt/interfaces';
|
|
||||||
import { UserRoleEnum } from '@educt/enums';
|
import { UserRoleEnum } from '@educt/enums';
|
||||||
|
import { SubmitHandler } from 'react-hook-form';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hooks
|
* Hooks
|
||||||
*/
|
*/
|
||||||
|
import { useContext, useState } from 'react';
|
||||||
import useIsMountedRef from '@educt/hooks/useIsMountedRef';
|
import useIsMountedRef from '@educt/hooks/useIsMountedRef';
|
||||||
import { useDisclosure } from '@chakra-ui/hooks';
|
import { useDisclosure } from '@chakra-ui/hooks';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
@ -45,9 +44,12 @@ import { useToast } from '@chakra-ui/react';
|
|||||||
*/
|
*/
|
||||||
import { UsersPageContext } from '@educt/contexts';
|
import { UsersPageContext } from '@educt/contexts';
|
||||||
|
|
||||||
type CreateUserModalPropsType = {
|
/**
|
||||||
me: IMe;
|
* Schema
|
||||||
};
|
*/
|
||||||
|
import CreateUserSchema from './CreateUserForm.validator';
|
||||||
|
|
||||||
|
type CreateUserFormPropsType = {};
|
||||||
|
|
||||||
type CreateUserInputType = {
|
type CreateUserInputType = {
|
||||||
first_name: string;
|
first_name: string;
|
||||||
@ -58,7 +60,7 @@ type CreateUserInputType = {
|
|||||||
password: string;
|
password: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const CreateUserModal: React.FC<CreateUserModalPropsType> = ({ me }) => {
|
const CreateUserModal: React.FC<CreateUserFormPropsType> = () => {
|
||||||
const { userStore } = useRootStore();
|
const { userStore } = useRootStore();
|
||||||
const { searchingRole, search } = useContext(UsersPageContext);
|
const { searchingRole, search } = useContext(UsersPageContext);
|
||||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
@ -75,8 +77,12 @@ const CreateUserModal: React.FC<CreateUserModalPropsType> = ({ me }) => {
|
|||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const handleError = useErrorHandler();
|
const handleError = useErrorHandler();
|
||||||
|
|
||||||
|
const { me } = userStore;
|
||||||
|
|
||||||
|
if (me === null) return null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create new user handler
|
* Submit handler
|
||||||
*/
|
*/
|
||||||
const onSubmit: SubmitHandler<CreateUserInputType> = async data => {
|
const onSubmit: SubmitHandler<CreateUserInputType> = async data => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -1,102 +1,87 @@
|
|||||||
import React, { useContext, useRef, useState } from 'react';
|
import React from 'react';
|
||||||
import {
|
|
||||||
AlertDialog,
|
/**
|
||||||
AlertDialogBody,
|
* Types
|
||||||
AlertDialogFooter,
|
*/
|
||||||
AlertDialogHeader,
|
import { IUser } from '@educt/interfaces';
|
||||||
AlertDialogContent,
|
|
||||||
AlertDialogOverlay,
|
/**
|
||||||
Button,
|
* Components
|
||||||
} from '@chakra-ui/react';
|
*/
|
||||||
|
import ConfirmDialog from '@educt/components/ConfirmDialog/ConfirmDialog';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contexts
|
||||||
|
*/
|
||||||
import { UsersPageContext } from '@educt/contexts';
|
import { UsersPageContext } from '@educt/contexts';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hooks
|
* Hooks
|
||||||
*/
|
*/
|
||||||
|
import { useContext, useState } from 'react';
|
||||||
import { useRootStore } from '@educt/hooks/useRootStore';
|
import { useRootStore } from '@educt/hooks/useRootStore';
|
||||||
import useIsMountedRef from '@educt/hooks/useIsMountedRef';
|
|
||||||
import { useToast } from '@chakra-ui/react';
|
import { useToast } from '@chakra-ui/react';
|
||||||
import { useErrorHandler } from 'react-error-boundary';
|
import { useErrorHandler } from 'react-error-boundary';
|
||||||
|
|
||||||
type DeleteUserDialogPropsType = {
|
type DeleteUserDialogPropsType = {
|
||||||
onClose: () => void;
|
user: IUser;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DeleteUserDialog: React.FC<DeleteUserDialogPropsType> = ({ onClose, isOpen }) => {
|
const DeleteUserDialog: React.FC<DeleteUserDialogPropsType> = ({ isOpen, onClose, user }) => {
|
||||||
const { userStore } = useRootStore();
|
const { userStore } = useRootStore();
|
||||||
const { deletingUser, setDeletingUser, searchingRole, search } = useContext(UsersPageContext);
|
const { setDeletingUser, searchingRole, searchingPage, search } = useContext(UsersPageContext);
|
||||||
const isMountedRef = useIsMountedRef();
|
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
const cancelRef = useRef<HTMLButtonElement | null>(null);
|
|
||||||
const handleError = useErrorHandler();
|
const handleError = useErrorHandler();
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
|
const { pagination } = userStore;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete user handler
|
* Delete user handler
|
||||||
|
*
|
||||||
|
* @param isConfirmed Is dialog confirmed
|
||||||
|
* @param id User id
|
||||||
*/
|
*/
|
||||||
const onDelete = async (id: string) => {
|
const deleteUserHandler = async (isConfirmed: boolean, id: string) => {
|
||||||
try {
|
if (isConfirmed) {
|
||||||
setIsLoading(true);
|
try {
|
||||||
await userStore.deleteUser(id, {
|
setIsLoading(true);
|
||||||
page: userStore.pagination?.current_page,
|
await userStore.deleteUser(id, {
|
||||||
limit: userStore.pagination?.per_page,
|
page: searchingPage,
|
||||||
role: searchingRole,
|
limit: pagination?.per_page,
|
||||||
search,
|
role: searchingRole,
|
||||||
});
|
search,
|
||||||
toast({ title: 'User deleted.', status: 'info' });
|
});
|
||||||
onClose();
|
toast({ title: 'User deleted.', status: 'info' });
|
||||||
setDeletingUser(undefined);
|
} catch (error: any) {
|
||||||
} catch (error: any) {
|
if (error.response) {
|
||||||
if (error.response) {
|
toast({ title: error.message, status: 'error' });
|
||||||
toast({ title: error.message, status: 'error' });
|
} else {
|
||||||
} else {
|
handleError(error);
|
||||||
handleError(error);
|
}
|
||||||
}
|
} finally {
|
||||||
} finally {
|
|
||||||
if (isMountedRef.current) {
|
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
setDeletingUser(undefined);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close dialog
|
||||||
|
*/
|
||||||
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (deletingUser === undefined) return null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<ConfirmDialog
|
||||||
<AlertDialog isOpen={isOpen} leastDestructiveRef={cancelRef} onClose={onClose}>
|
isOpen={isOpen}
|
||||||
<AlertDialogOverlay>
|
isLoading={isLoading}
|
||||||
<AlertDialogContent>
|
onClose={isConfirmed => deleteUserHandler(isConfirmed, user.id)}
|
||||||
<AlertDialogHeader fontSize='lg' fontWeight='bold'>
|
title='Confirm deleting'
|
||||||
Confirm deleting
|
message={`The user named "${user.fullname}" will be removed from the system.`}
|
||||||
</AlertDialogHeader>
|
confirmMessage='Delete'
|
||||||
<AlertDialogBody>
|
/>
|
||||||
The user named "{deletingUser.first_name} {deletingUser.last_name}" will be removed from the system.
|
|
||||||
</AlertDialogBody>
|
|
||||||
<AlertDialogFooter>
|
|
||||||
<Button
|
|
||||||
ref={cancelRef}
|
|
||||||
onClick={() => {
|
|
||||||
setDeletingUser(undefined);
|
|
||||||
onClose();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
colorScheme='red'
|
|
||||||
isLoading={isLoading}
|
|
||||||
loadingText='Deleting...'
|
|
||||||
onClick={() => onDelete(deletingUser.id)}
|
|
||||||
ml={3}
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</Button>
|
|
||||||
</AlertDialogFooter>
|
|
||||||
</AlertDialogContent>
|
|
||||||
</AlertDialogOverlay>
|
|
||||||
</AlertDialog>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -25,7 +25,7 @@ import { yupResolver } from '@hookform/resolvers/yup';
|
|||||||
/**
|
/**
|
||||||
* Types
|
* Types
|
||||||
*/
|
*/
|
||||||
import { IMe } from '@educt/interfaces';
|
import { IMe, IUser } from '@educt/interfaces';
|
||||||
import { UserRoleEnum } from '@educt/enums';
|
import { UserRoleEnum } from '@educt/enums';
|
||||||
import { SubmitHandler } from 'react-hook-form';
|
import { SubmitHandler } from 'react-hook-form';
|
||||||
|
|
||||||
@ -49,9 +49,9 @@ import { UsersPageContext } from '@educt/contexts';
|
|||||||
import UpdateUserSchema from './EditUserForm.validator';
|
import UpdateUserSchema from './EditUserForm.validator';
|
||||||
|
|
||||||
type UpdateUserFormPropsType = {
|
type UpdateUserFormPropsType = {
|
||||||
onClose: () => void;
|
user: IUser;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
me: IMe;
|
onClose: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type UpdateUserInputType = {
|
type UpdateUserInputType = {
|
||||||
@ -63,9 +63,9 @@ type UpdateUserInputType = {
|
|||||||
password: string | null;
|
password: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const UpdateUserForm: React.FC<UpdateUserFormPropsType> = ({ onClose, isOpen, me }) => {
|
const UpdateUserForm: React.FC<UpdateUserFormPropsType> = ({ user, onClose, isOpen }) => {
|
||||||
const { userStore } = useRootStore();
|
const { userStore } = useRootStore();
|
||||||
const { editingUser, setEditingUser } = useContext(UsersPageContext);
|
const { setEditingUser } = useContext(UsersPageContext);
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
@ -75,10 +75,10 @@ const UpdateUserForm: React.FC<UpdateUserFormPropsType> = ({ onClose, isOpen, me
|
|||||||
} = useForm<UpdateUserInputType>({
|
} = useForm<UpdateUserInputType>({
|
||||||
resolver: yupResolver(UpdateUserSchema),
|
resolver: yupResolver(UpdateUserSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
first_name: editingUser && editingUser.first_name,
|
first_name: user.first_name,
|
||||||
last_name: editingUser && editingUser.last_name,
|
last_name: user.last_name,
|
||||||
email: editingUser && editingUser.email,
|
email: user.email,
|
||||||
role: editingUser && editingUser.roles[0].slug,
|
role: user.roles[0].slug,
|
||||||
login: '',
|
login: '',
|
||||||
password: '',
|
password: '',
|
||||||
},
|
},
|
||||||
@ -87,13 +87,18 @@ const UpdateUserForm: React.FC<UpdateUserFormPropsType> = ({ onClose, isOpen, me
|
|||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const handleError = useErrorHandler();
|
const handleError = useErrorHandler();
|
||||||
|
|
||||||
if (editingUser === undefined) return null;
|
const { me } = userStore;
|
||||||
|
|
||||||
|
if (me === null) return null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit handler
|
||||||
|
*/
|
||||||
const onSubmit: SubmitHandler<UpdateUserInputType> = async data => {
|
const onSubmit: SubmitHandler<UpdateUserInputType> = async data => {
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const params = Object.fromEntries(Object.keys(dirtyFields).map(k => [k, data[k as keyof UpdateUserInputType]]));
|
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' });
|
toast({ title: 'User updated.', status: 'info' });
|
||||||
onCloseModal();
|
onCloseModal();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@ -136,7 +141,7 @@ const UpdateUserForm: React.FC<UpdateUserFormPropsType> = ({ onClose, isOpen, me
|
|||||||
<Stack spacing='4'>
|
<Stack spacing='4'>
|
||||||
<FormControl isDisabled>
|
<FormControl isDisabled>
|
||||||
<FormLabel>Identificator</FormLabel>
|
<FormLabel>Identificator</FormLabel>
|
||||||
<Input value={editingUser?.id} />
|
<Input value={user.id} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormControl>
|
<FormControl>
|
||||||
|
|||||||
@ -14,7 +14,6 @@ import DeleteUserDialog from '../DeleteUserDialog';
|
|||||||
/**
|
/**
|
||||||
* Types
|
* Types
|
||||||
*/
|
*/
|
||||||
import type { IUser } from '@educt/interfaces';
|
|
||||||
import type { UsersPageContextType } from '@educt/types';
|
import type { UsersPageContextType } from '@educt/types';
|
||||||
import type { UserItemPropsType } from './UserItem';
|
import type { UserItemPropsType } from './UserItem';
|
||||||
|
|
||||||
@ -27,137 +26,111 @@ import { UsersPageContext } from '@educt/contexts';
|
|||||||
* Hooks
|
* Hooks
|
||||||
*/
|
*/
|
||||||
import { useEffect, useContext } from 'react';
|
import { useEffect, useContext } from 'react';
|
||||||
import { useLocation } from 'react-router';
|
|
||||||
import { useErrorHandler } from 'react-error-boundary';
|
import { useErrorHandler } from 'react-error-boundary';
|
||||||
import { useRootStore } from '@educt/hooks/useRootStore';
|
import { useRootStore } from '@educt/hooks/useRootStore';
|
||||||
import { useDisclosure } from '@chakra-ui/hooks';
|
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 { userStore } = useRootStore();
|
||||||
const { searchingRole, loading, setLoading, search, editingUser, setEditingUser, deletingUser, setDeletingUser } =
|
const {
|
||||||
useContext<UsersPageContextType>(UsersPageContext);
|
searchingRole,
|
||||||
const location = useLocation();
|
search,
|
||||||
|
searchingPage,
|
||||||
|
setSearchingPage,
|
||||||
|
editingUser,
|
||||||
|
setEditingUser,
|
||||||
|
deletingUser,
|
||||||
|
setDeletingUser,
|
||||||
|
} = useContext<UsersPageContextType>(UsersPageContext);
|
||||||
const handleError = useErrorHandler();
|
const handleError = useErrorHandler();
|
||||||
const { onOpen: onOpenEditModal, onClose: onCloseEditModal, isOpen: isOpenEditModal } = useDisclosure();
|
const { onOpen: onOpenEditModal, onClose: onCloseEditModal, isOpen: isOpenEditModal } = useDisclosure();
|
||||||
const { onOpen: onOpenDeleteDialog, onClose: onCloseDeleteDialog, isOpen: isOpenDeleteDialog } = useDisclosure();
|
const { onOpen: onOpenDeleteDialog, onClose: onCloseDeleteDialog, isOpen: isOpenDeleteDialog } = useDisclosure();
|
||||||
const { users, pagination, me } = userStore;
|
const { users, pagination, me, isLoading } = userStore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch users handler
|
||||||
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
userStore.loadUsersData().catch(error => handleError(error));
|
userStore
|
||||||
}, [handleError, userStore, location.search]);
|
.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 />;
|
if (pagination === undefined || users === null || me === null) return <LoadingList />;
|
||||||
|
|
||||||
const pagesCount = Math.ceil(pagination.total / pagination.per_page);
|
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 (
|
return (
|
||||||
<>
|
<Box>
|
||||||
{editingUser && <EditUserForm me={me} onClose={onCloseEditModal} isOpen={isOpenEditModal} />}
|
{editingUser && <EditUserForm user={editingUser} isOpen={isOpenEditModal} onClose={onCloseEditModal} />}
|
||||||
{deletingUser && <DeleteUserDialog onClose={onCloseDeleteDialog} isOpen={isOpenDeleteDialog} />}
|
{deletingUser && (
|
||||||
<Box>
|
<DeleteUserDialog user={deletingUser} isOpen={isOpenDeleteDialog} onClose={onCloseDeleteDialog} />
|
||||||
<Flex mt='7' p='0 10px' fontWeight='bold' alignItems='center' justifyContent='space-between'>
|
)}
|
||||||
<Text>Total: ({pagination.total})</Text>
|
{users.length !== 0 ? (
|
||||||
<Text>Actions</Text>
|
<>
|
||||||
</Flex>
|
<Box>
|
||||||
{!loading ? (
|
<Flex mt='7' p='0 10px' fontWeight='bold' alignItems='center' justifyContent='space-between'>
|
||||||
<Stack mt='4' spacing='2'>
|
<Text>Total: ({pagination.total})</Text>
|
||||||
{users.map(user => (
|
<Text>Actions</Text>
|
||||||
<Item key={user.id} user={user} onEdit={onEditUser} onDelete={onDeleteUser} />
|
</Flex>
|
||||||
))}
|
{!isLoading ? (
|
||||||
</Stack>
|
<Stack mt='4' spacing='2'>
|
||||||
) : (
|
{users.map(user => (
|
||||||
<LoadingList />
|
<Item
|
||||||
)}
|
key={user.id}
|
||||||
</Box>
|
user={user}
|
||||||
<Flex margin='2rem 0' flexDirection={{ base: 'column', sm: 'column', md: 'row' }}>
|
onEdit={user => {
|
||||||
<Box textAlign={{ base: 'center', sm: 'center', md: 'left' }} mb={{ base: '2', md: '0' }}>
|
setEditingUser(user);
|
||||||
Page <b>{pagination.current_page}</b> of {pagesCount}
|
onOpenEditModal();
|
||||||
|
}}
|
||||||
|
onDelete={user => {
|
||||||
|
setDeletingUser(user);
|
||||||
|
onOpenDeleteDialog();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
) : (
|
||||||
|
<LoadingList />
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
<Flex margin='2rem 0' flexDirection={{ base: 'column', sm: 'column', md: 'row' }}>
|
||||||
|
<Box textAlign={{ base: 'center', sm: 'center', md: 'left' }} mb={{ base: '2', md: '0' }}>
|
||||||
|
Page <b>{pagination.current_page}</b> of {pagesCount}
|
||||||
|
</Box>
|
||||||
|
<Flex ml='auto' mr='auto' justifyContent='center' flexWrap='wrap' alignItems='center' sx={{ gap: '30px' }}>
|
||||||
|
<Button
|
||||||
|
onClick={() => setSearchingPage(prevP => prevP - 1)}
|
||||||
|
disabled={pagination.current_page <= 1}
|
||||||
|
variant='link'
|
||||||
|
leftIcon={<ChevronLeftIcon />}
|
||||||
|
>
|
||||||
|
Previous page
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => setSearchingPage(prevP => prevP + 1)}
|
||||||
|
disabled={pagination.current_page === pagesCount}
|
||||||
|
variant='link'
|
||||||
|
rightIcon={<ChevronRightIcon />}
|
||||||
|
>
|
||||||
|
Next page
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Box textAlign='center' mt='10' userSelect='none'>
|
||||||
|
<Text color='gray.500'>Cannot find any users.</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Flex ml='auto' mr='auto' justifyContent='center' flexWrap='wrap' alignItems='center' sx={{ gap: '30px' }}>
|
)}
|
||||||
<Button
|
</Box>
|
||||||
onClick={prevPage}
|
|
||||||
disabled={pagination.current_page <= 1}
|
|
||||||
variant='link'
|
|
||||||
leftIcon={<ChevronLeftIcon />}
|
|
||||||
>
|
|
||||||
Previous page
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={nextPage}
|
|
||||||
disabled={pagination.current_page === pagesCount}
|
|
||||||
variant='link'
|
|
||||||
rightIcon={<ChevronRightIcon />}
|
|
||||||
>
|
|
||||||
Next page
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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 { Flex, Select, Input, InputGroup, InputLeftElement } from '@chakra-ui/react';
|
||||||
import { SearchIcon } from '@chakra-ui/icons';
|
import { SearchIcon } from '@chakra-ui/icons';
|
||||||
import { useRootStore } from '@educt/hooks/useRootStore';
|
|
||||||
import { UserRoleEnum } from '@educt/enums';
|
/**
|
||||||
import { useErrorHandler } from 'react-error-boundary';
|
* Types
|
||||||
import { UsersPageContext } from '@educt/contexts';
|
*/
|
||||||
import { UsersPageContextType } from '@educt/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';
|
type SelectRoleStateType = UserRoleEnum | 'any';
|
||||||
|
|
||||||
const UserSearch: React.FC = () => {
|
const UserSearch: React.FC = () => {
|
||||||
const { userStore } = useRootStore();
|
const { searchingRole, setSearchingRole, setSearchingPage, search, setSearch } =
|
||||||
const { searchingRole, setSearchingRole, setLoading, search, setSearch } =
|
|
||||||
useContext<UsersPageContextType>(UsersPageContext);
|
useContext<UsersPageContextType>(UsersPageContext);
|
||||||
const { pagination } = userStore;
|
|
||||||
const handleError = useErrorHandler();
|
|
||||||
|
|
||||||
if (pagination === undefined) return null;
|
/**
|
||||||
|
* Role change handler
|
||||||
const onSearchRole: React.ChangeEventHandler<HTMLSelectElement> | undefined = async e => {
|
*/
|
||||||
try {
|
const onSearchRole: React.ChangeEventHandler<HTMLSelectElement> | undefined = e => {
|
||||||
setLoading(true);
|
setSearchingPage(1);
|
||||||
const value = e.target.value as SelectRoleStateType;
|
setSearchingRole(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);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSearch: React.ChangeEventHandler<HTMLInputElement> | undefined = async e => {
|
/**
|
||||||
try {
|
* Search box change handler
|
||||||
const value = e.target.value;
|
*/
|
||||||
setLoading(true);
|
const onSearch: React.ChangeEventHandler<HTMLInputElement> | undefined = e => {
|
||||||
setSearch(value);
|
setSearchingPage(1);
|
||||||
await userStore.loadUsersData({
|
setSearch(e.target.value);
|
||||||
page: 1,
|
|
||||||
limit: pagination.per_page,
|
|
||||||
role: searchingRole,
|
|
||||||
search: value,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
handleError(error);
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -71,4 +57,4 @@ const UserSearch: React.FC = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default observer(UserSearch);
|
export default UserSearch;
|
||||||
|
|||||||
@ -43,9 +43,9 @@ const UsersPage: React.FC<IPageProps> = () => {
|
|||||||
<Flex mt='5' flexDir='column' h='full'>
|
<Flex mt='5' flexDir='column' h='full'>
|
||||||
<Flex justifyContent='space-between' sx={{ gap: '10px' }} flexWrap='wrap'>
|
<Flex justifyContent='space-between' sx={{ gap: '10px' }} flexWrap='wrap'>
|
||||||
<UserSearch />
|
<UserSearch />
|
||||||
<CreateUserForm me={me} />
|
<CreateUserForm />
|
||||||
</Flex>
|
</Flex>
|
||||||
<UserList render={UserItem} />
|
<UserList render={UserItem} limit={6} />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Box>
|
</Box>
|
||||||
</UsersPageContextProvider>
|
</UsersPageContextProvider>
|
||||||
|
|||||||
@ -33,7 +33,7 @@ export const ChangeEmailPageContextProvider: React.FC = ({ children }) => {
|
|||||||
*/
|
*/
|
||||||
export const UsersPageContextProvider: React.FC = ({ children }) => {
|
export const UsersPageContextProvider: React.FC = ({ children }) => {
|
||||||
const [searchingRole, setSearchingRole] = useState<SearchingRoleStateType>(undefined);
|
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 [search, setSearch] = useState<string | undefined>(undefined);
|
||||||
const [editingUser, setEditingUser] = useState<IUser | undefined>(undefined);
|
const [editingUser, setEditingUser] = useState<IUser | undefined>(undefined);
|
||||||
const [deletingUser, setDeletingUser] = useState<IUser | undefined>(undefined);
|
const [deletingUser, setDeletingUser] = useState<IUser | undefined>(undefined);
|
||||||
@ -43,8 +43,8 @@ export const UsersPageContextProvider: React.FC = ({ children }) => {
|
|||||||
value={{
|
value={{
|
||||||
searchingRole,
|
searchingRole,
|
||||||
setSearchingRole,
|
setSearchingRole,
|
||||||
loading,
|
searchingPage,
|
||||||
setLoading,
|
setSearchingPage,
|
||||||
search,
|
search,
|
||||||
setSearch,
|
setSearch,
|
||||||
editingUser,
|
editingUser,
|
||||||
|
|||||||
@ -30,14 +30,21 @@ export default class UserStore {
|
|||||||
|
|
||||||
public pagination: IPaginationMeta | undefined;
|
public pagination: IPaginationMeta | undefined;
|
||||||
|
|
||||||
|
public isLoading: boolean = false;
|
||||||
|
|
||||||
constructor(root: RootStore, api: AxiosInstance) {
|
constructor(root: RootStore, api: AxiosInstance) {
|
||||||
this.root = root;
|
this.root = root;
|
||||||
this.userService = new UserService(api);
|
this.userService = new UserService(api);
|
||||||
makeAutoObservable(this);
|
makeAutoObservable(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private setLoading(loading: boolean) {
|
||||||
|
this.isLoading = loading;
|
||||||
|
}
|
||||||
|
|
||||||
public async loadUsersData(params?: FetchUsersParamsType) {
|
public async loadUsersData(params?: FetchUsersParamsType) {
|
||||||
try {
|
try {
|
||||||
|
this.setLoading(true);
|
||||||
const result = await this.userService.fetchAll(params);
|
const result = await this.userService.fetchAll(params);
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
@ -48,6 +55,8 @@ export default class UserStore {
|
|||||||
return result;
|
return result;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
throw error;
|
throw error;
|
||||||
|
} finally {
|
||||||
|
this.setLoading(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
12
src/types/index.d.ts
vendored
12
src/types/index.d.ts
vendored
@ -59,12 +59,6 @@ export type UsersPageContextType = {
|
|||||||
searchingRole: SearchingRoleStateType;
|
searchingRole: SearchingRoleStateType;
|
||||||
setSearchingRole: React.Dispatch<React.SetStateAction<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
|
* Serach user by fullname or email
|
||||||
*/
|
*/
|
||||||
@ -77,6 +71,12 @@ export type UsersPageContextType = {
|
|||||||
editingUser: IUser | undefined;
|
editingUser: IUser | undefined;
|
||||||
setEditingUser: React.Dispatch<React.SetStateAction<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
|
* Set deleting user state when press on delete button
|
||||||
*/
|
*/
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user