feat: edit user form setup layout

This commit is contained in:
Sergey Yarkov 2021-09-22 23:50:41 +03:00
parent 07dc9c7f73
commit 99e22a4c69
10 changed files with 216 additions and 25 deletions

View File

@ -32,6 +32,7 @@ import { UserRoleEnum } from 'enums';
/** /**
* Hooks * Hooks
*/ */
import useIsMountedRef from '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';
import { useRootStore } from 'hooks/useRootStore'; import { useRootStore } from 'hooks/useRootStore';
@ -63,6 +64,7 @@ const CreateUserModal: React.FC<CreateUserModalPropsType> = ({ me }) => {
resolver: yupResolver(CreateUserSchema), resolver: yupResolver(CreateUserSchema),
}); });
const [loading, setLoading] = useState<boolean>(false); const [loading, setLoading] = useState<boolean>(false);
const isMountedRef = useIsMountedRef();
const toast = useToast(); const toast = useToast();
const handleError = useErrorHandler(); const handleError = useErrorHandler();
@ -84,12 +86,18 @@ const CreateUserModal: React.FC<CreateUserModalPropsType> = ({ me }) => {
onClose(); onClose();
} catch (error: any) { } catch (error: any) {
if (error.response) { if (error.response) {
toast({ title: `${error.message}`, status: 'error' }); if (error.response.status === 422) {
toast({ title: `${error.response.data.errors[0].message}`, status: 'error' });
} else {
toast({ title: `${error.message}`, status: 'error' });
}
} else { } else {
handleError(error); handleError(error);
} }
} finally { } finally {
setLoading(false); if (isMountedRef.current) {
setLoading(false);
}
} }
}; };

View File

@ -0,0 +1,3 @@
import CreateUserForm from './CreateUserForm';
export default CreateUserForm;

View File

@ -1,3 +0,0 @@
import CreateUserModal from './CreateUserModal';
export default CreateUserModal;

View File

@ -0,0 +1,98 @@
import React from 'react';
import {
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalCloseButton,
ModalBody,
ModalFooter,
FormControl,
FormLabel,
Input,
Button,
Select,
Flex,
Text,
Divider,
} from '@chakra-ui/react';
import { AddIcon } from '@chakra-ui/icons';
import { MdModeEdit } from 'react-icons/md';
/**
* Types
*/
import { IUser } from 'interfaces';
import { UserRoleEnum } from 'enums';
type UpdateUserFormPropsType = {
user: IUser;
onClose: () => void;
isOpen: boolean;
};
const UpdateUserForm: React.FC<UpdateUserFormPropsType> = ({ user, onClose, isOpen }) => {
return (
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<form>
<ModalHeader>
<Flex alignItems='center'>
<MdModeEdit />
<Text ml={2}>Edit user</Text>
</Flex>
</ModalHeader>
<ModalCloseButton />
<Divider />
<ModalBody pb={6}>
<FormControl>
<FormLabel>First name</FormLabel>
<Input placeholder='First name' value={user.first_name} />
</FormControl>
<FormControl mt={4}>
<FormLabel>Last name</FormLabel>
<Input placeholder='Last name' value={user.last_name} />
</FormControl>
<FormControl mt={4}>
<FormLabel>Login</FormLabel>
<Input placeholder='e.g student893' />
</FormControl>
<FormControl mt={4}>
<FormLabel>Email</FormLabel>
<Input placeholder='example@email.com' />
</FormControl>
<FormControl mt={4}>
<FormLabel>Role</FormLabel>
<Select w='full' mr='2'>
<option value={UserRoleEnum.STUDENT}>Student</option>
<option value={UserRoleEnum.TEACHER}>Teacher</option>
<option value={UserRoleEnum.ADMIN}>Administrator</option>
</Select>
</FormControl>
<FormControl mt={4}>
<FormLabel>Password</FormLabel>
<Input placeholder='******' />
</FormControl>
</ModalBody>
<ModalFooter>
<Button
type='submit'
variant='outline'
mr={3}
colorScheme='blue'
loadingText='Creating...'
leftIcon={<AddIcon />}
>
Edit
</Button>
<Button onClick={onClose}>Cancel</Button>
</ModalFooter>
</form>
</ModalContent>
</Modal>
);
};
export default UpdateUserForm;

View File

@ -0,0 +1,16 @@
import yup from 'schema';
import { UserRoleEnum } from 'enums';
const UpdateUserSchema = yup.object().shape({
first_name: yup.string().required('First field name is required'),
last_name: yup.string().required('Last field name is required'),
login: yup.string().required('Login field is required'),
email: yup.string().email('Email is invalid').required('Email field is required'),
role: yup
.mixed<UserRoleEnum>()
.required('Role field is required')
.oneOf(Object.values(UserRoleEnum), `Available roles: ${Object.keys(UserRoleEnum).join(', ')}`),
password: yup.string().required('Password field is required').min(6, 'Password must be at least 6 characters.'),
});
export default UpdateUserSchema;

View File

@ -0,0 +1,3 @@
import UpdateUserForm from './UpdateUserForm';
export default UpdateUserForm;

View File

@ -1,14 +1,46 @@
import React from 'react'; import React from 'react';
import { Box, Flex, Avatar, Text, Tooltip, IconButton } from '@chakra-ui/react'; import * as helpres from 'helpers';
import { Box, Flex, Avatar, Text, IconButton } from '@chakra-ui/react';
import { MdMoreHoriz } from 'react-icons/md'; import { MdMoreHoriz } from 'react-icons/md';
import UserBadge from 'components/UserBadge'; import UserBadge from 'components/UserBadge';
/**
* Types
*/
import { IUser } from 'interfaces'; import { IUser } from 'interfaces';
import { UserRoleEnum } from 'enums';
/**
* Hooks
*/
import { useRootStore } from 'hooks/useRootStore';
type UserItemPropsType = { type UserItemPropsType = {
user: IUser; user: IUser;
onEdit: (user: IUser) => void;
}; };
const UserItem: React.FC<UserItemPropsType> = ({ user }) => { const UserItem: React.FC<UserItemPropsType> = ({ user, onEdit }) => {
const {
userStore: { me },
} = useRootStore();
/**
* The user with role TEACHER can edit users with only STUDENT role
*/
const isDisabledActions = () => {
if (helpres.userContainRoles(user.roles, [UserRoleEnum.ADMIN, UserRoleEnum.TEACHER]) && me !== null) {
/**
* Disable editing
*/
if (me.isTeacher || me.isStudent) {
return true;
} else return false;
} else {
return false;
}
};
return ( return (
<Box borderWidth='1px' borderRadius='lg' w='full' p='3'> <Box borderWidth='1px' borderRadius='lg' w='full' p='3'>
<Flex alignItems='center' justifyContent='space-between' sx={{ gap: '20px' }}> <Flex alignItems='center' justifyContent='space-between' sx={{ gap: '20px' }}>
@ -25,9 +57,13 @@ const UserItem: React.FC<UserItemPropsType> = ({ user }) => {
<UserBadge roles={user.roles} /> <UserBadge roles={user.roles} />
</Flex> </Flex>
<Box> <Box>
<Tooltip label='Edit user' openDelay={400}> <IconButton
<IconButton aria-label='Actions' variant='ghost' icon={<MdMoreHoriz />} /> disabled={isDisabledActions()}
</Tooltip> onClick={() => onEdit(user)}
aria-label='Actions'
variant='ghost'
icon={<MdMoreHoriz />}
/>
</Box> </Box>
</Flex> </Flex>
</Box> </Box>

View File

@ -1,26 +1,43 @@
import React from 'react';
import { observer } from 'mobx-react';
import { ChevronLeftIcon, ChevronRightIcon } from '@chakra-ui/icons'; import { ChevronLeftIcon, ChevronRightIcon } from '@chakra-ui/icons';
import { Flex, Box, Stack, Text } from '@chakra-ui/layout'; import { Flex, Box, Stack, Text } from '@chakra-ui/layout';
import { Button } from '@chakra-ui/button'; import { Button } from '@chakra-ui/button';
import { IPaginationMeta, IUser } from 'interfaces';
import React, { useContext } from 'react';
import UserItem from './UserItem'; import UserItem from './UserItem';
import { useRootStore } from 'hooks/useRootStore'; import UpdateUserForm from '../UpdateUserForm';
import { useErrorHandler } from 'react-error-boundary';
import { UsersPageContextType } from 'types';
import { UsersPageContext } from 'contexts';
import { observer } from 'mobx-react';
type UserListPropsType = { /**
users: IUser[]; * Types
pagination: IPaginationMeta; */
}; import { IPaginationMeta, IUser } from 'interfaces';
import { UsersPageContextType } from 'types';
/**
* Contexts
*/
import { UsersPageContext } from 'contexts';
/**
* Hooks
*/
import { useContext, useState } from 'react';
import { useRootStore } from 'hooks/useRootStore';
import { useDisclosure } from '@chakra-ui/hooks';
import { useErrorHandler } from 'react-error-boundary';
type UserListPropsType = { users: IUser[]; pagination: IPaginationMeta };
const UserList: React.FC<UserListPropsType> = ({ users, pagination }) => { const UserList: React.FC<UserListPropsType> = ({ users, pagination }) => {
const { userStore } = useRootStore(); const { userStore } = useRootStore();
const [editingUser, setEditingUser] = useState<IUser | undefined>(undefined);
const { searchingRole, loading, setLoading, search } = useContext<UsersPageContextType>(UsersPageContext); const { searchingRole, loading, setLoading, search } = useContext<UsersPageContextType>(UsersPageContext);
const handleError = useErrorHandler(); const handleError = useErrorHandler();
const pagesCount = Math.ceil(pagination.total / pagination.per_page); const pagesCount = Math.ceil(pagination.total / pagination.per_page);
const { onOpen: onOpenEditModal, onClose: onCloseEditModal, isOpen: isOpenEditModal } = useDisclosure();
/**
* Handle next page
*/
const nextPage = async () => { const nextPage = async () => {
try { try {
setLoading(true); setLoading(true);
@ -37,6 +54,9 @@ const UserList: React.FC<UserListPropsType> = ({ users, pagination }) => {
} }
}; };
/**
* Handle prev page
*/
const prevPage = async () => { const prevPage = async () => {
try { try {
setLoading(true); setLoading(true);
@ -53,10 +73,20 @@ const UserList: React.FC<UserListPropsType> = ({ users, pagination }) => {
} }
}; };
/**
* Set editing state when click on edit button
*/
const onEditUser = (user: IUser) => {
setEditingUser(user);
onOpenEditModal();
console.log(`[LOG]: Editing user "${user.id}"`);
};
return ( return (
<> <>
{pagination.total !== 0 ? ( {pagination.total !== 0 ? (
<> <>
{editingUser && <UpdateUserForm user={editingUser} onClose={onCloseEditModal} isOpen={isOpenEditModal} />}
<Box> <Box>
<Flex mt='7' p='0 10px' fontWeight='bold' alignItems='center' justifyContent='space-between'> <Flex mt='7' p='0 10px' fontWeight='bold' alignItems='center' justifyContent='space-between'>
<Text>Total: ({pagination.total})</Text> <Text>Total: ({pagination.total})</Text>
@ -69,7 +99,7 @@ const UserList: React.FC<UserListPropsType> = ({ users, pagination }) => {
) : ( ) : (
<Stack mt='4' spacing='2'> <Stack mt='4' spacing='2'>
{users.map(user => ( {users.map(user => (
<UserItem key={user.id} user={user} /> <UserItem key={user.id} user={user} onEdit={onEditUser} />
))} ))}
</Stack> </Stack>
)} )}

View File

@ -27,7 +27,7 @@ import { useLocation } from 'react-router';
* Context * Context
*/ */
import { UsersPageContext } from 'contexts'; import { UsersPageContext } from 'contexts';
import CreateUserModal from './components/CreateUserModal'; import CreateUserForm from './components/CreateUserForm';
/** /**
* Users Page * Users Page
@ -54,8 +54,8 @@ const UsersPage: React.FC<IPageProps> = ({ title }) => {
<Flex justifyContent='space-between' sx={{ gap: '10px' }} flexWrap='wrap'> <Flex justifyContent='space-between' sx={{ gap: '10px' }} flexWrap='wrap'>
<UserSearch /> <UserSearch />
<Flex sx={{ gap: '10px' }}> <Flex sx={{ gap: '10px' }}>
<CreateUserModal me={userStore.me} /> <CreateUserForm me={userStore.me} />
<Button variant='outline' colorScheme='green' leftIcon={<FaFileExcel />}> <Button disabled variant='outline' colorScheme='green' leftIcon={<FaFileExcel />}>
Import Import
</Button> </Button>
</Flex> </Flex>