mirror of
https://github.com/sergeyyarkov/educt-client.git
synced 2026-10-11 10:19:22 +03:00
feat: edit user form setup layout
This commit is contained in:
parent
07dc9c7f73
commit
99e22a4c69
@ -32,6 +32,7 @@ import { UserRoleEnum } from 'enums';
|
||||
/**
|
||||
* Hooks
|
||||
*/
|
||||
import useIsMountedRef from 'hooks/useIsMountedRef';
|
||||
import { useDisclosure } from '@chakra-ui/hooks';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useRootStore } from 'hooks/useRootStore';
|
||||
@ -63,6 +64,7 @@ const CreateUserModal: React.FC<CreateUserModalPropsType> = ({ me }) => {
|
||||
resolver: yupResolver(CreateUserSchema),
|
||||
});
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const isMountedRef = useIsMountedRef();
|
||||
const toast = useToast();
|
||||
const handleError = useErrorHandler();
|
||||
|
||||
@ -84,12 +86,18 @@ const CreateUserModal: React.FC<CreateUserModalPropsType> = ({ me }) => {
|
||||
onClose();
|
||||
} catch (error: any) {
|
||||
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 {
|
||||
handleError(error);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
if (isMountedRef.current) {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
3
src/pages/users/components/CreateUserForm/index.ts
Normal file
3
src/pages/users/components/CreateUserForm/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import CreateUserForm from './CreateUserForm';
|
||||
|
||||
export default CreateUserForm;
|
||||
@ -1,3 +0,0 @@
|
||||
import CreateUserModal from './CreateUserModal';
|
||||
|
||||
export default CreateUserModal;
|
||||
98
src/pages/users/components/UpdateUserForm/UpdateUserForm.tsx
Normal file
98
src/pages/users/components/UpdateUserForm/UpdateUserForm.tsx
Normal 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;
|
||||
@ -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;
|
||||
3
src/pages/users/components/UpdateUserForm/index.ts
Normal file
3
src/pages/users/components/UpdateUserForm/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import UpdateUserForm from './UpdateUserForm';
|
||||
|
||||
export default UpdateUserForm;
|
||||
@ -1,14 +1,46 @@
|
||||
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 UserBadge from 'components/UserBadge';
|
||||
|
||||
/**
|
||||
* Types
|
||||
*/
|
||||
import { IUser } from 'interfaces';
|
||||
import { UserRoleEnum } from 'enums';
|
||||
|
||||
/**
|
||||
* Hooks
|
||||
*/
|
||||
import { useRootStore } from 'hooks/useRootStore';
|
||||
|
||||
type UserItemPropsType = {
|
||||
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 (
|
||||
<Box borderWidth='1px' borderRadius='lg' w='full' p='3'>
|
||||
<Flex alignItems='center' justifyContent='space-between' sx={{ gap: '20px' }}>
|
||||
@ -25,9 +57,13 @@ const UserItem: React.FC<UserItemPropsType> = ({ user }) => {
|
||||
<UserBadge roles={user.roles} />
|
||||
</Flex>
|
||||
<Box>
|
||||
<Tooltip label='Edit user' openDelay={400}>
|
||||
<IconButton aria-label='Actions' variant='ghost' icon={<MdMoreHoriz />} />
|
||||
</Tooltip>
|
||||
<IconButton
|
||||
disabled={isDisabledActions()}
|
||||
onClick={() => onEdit(user)}
|
||||
aria-label='Actions'
|
||||
variant='ghost'
|
||||
icon={<MdMoreHoriz />}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Box>
|
||||
|
||||
@ -1,26 +1,43 @@
|
||||
import React from 'react';
|
||||
import { observer } from 'mobx-react';
|
||||
import { ChevronLeftIcon, ChevronRightIcon } from '@chakra-ui/icons';
|
||||
import { Flex, Box, Stack, Text } from '@chakra-ui/layout';
|
||||
import { Button } from '@chakra-ui/button';
|
||||
import { IPaginationMeta, IUser } from 'interfaces';
|
||||
import React, { useContext } from 'react';
|
||||
import UserItem from './UserItem';
|
||||
import { useRootStore } from 'hooks/useRootStore';
|
||||
import { useErrorHandler } from 'react-error-boundary';
|
||||
import { UsersPageContextType } from 'types';
|
||||
import { UsersPageContext } from 'contexts';
|
||||
import { observer } from 'mobx-react';
|
||||
import UpdateUserForm from '../UpdateUserForm';
|
||||
|
||||
type UserListPropsType = {
|
||||
users: IUser[];
|
||||
pagination: IPaginationMeta;
|
||||
};
|
||||
/**
|
||||
* Types
|
||||
*/
|
||||
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 { userStore } = useRootStore();
|
||||
const [editingUser, setEditingUser] = useState<IUser | undefined>(undefined);
|
||||
const { searchingRole, loading, setLoading, search } = useContext<UsersPageContextType>(UsersPageContext);
|
||||
const handleError = useErrorHandler();
|
||||
const pagesCount = Math.ceil(pagination.total / pagination.per_page);
|
||||
const { onOpen: onOpenEditModal, onClose: onCloseEditModal, isOpen: isOpenEditModal } = useDisclosure();
|
||||
|
||||
/**
|
||||
* Handle next page
|
||||
*/
|
||||
const nextPage = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
@ -37,6 +54,9 @@ const UserList: React.FC<UserListPropsType> = ({ users, pagination }) => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle prev page
|
||||
*/
|
||||
const prevPage = async () => {
|
||||
try {
|
||||
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 (
|
||||
<>
|
||||
{pagination.total !== 0 ? (
|
||||
<>
|
||||
{editingUser && <UpdateUserForm user={editingUser} onClose={onCloseEditModal} isOpen={isOpenEditModal} />}
|
||||
<Box>
|
||||
<Flex mt='7' p='0 10px' fontWeight='bold' alignItems='center' justifyContent='space-between'>
|
||||
<Text>Total: ({pagination.total})</Text>
|
||||
@ -69,7 +99,7 @@ const UserList: React.FC<UserListPropsType> = ({ users, pagination }) => {
|
||||
) : (
|
||||
<Stack mt='4' spacing='2'>
|
||||
{users.map(user => (
|
||||
<UserItem key={user.id} user={user} />
|
||||
<UserItem key={user.id} user={user} onEdit={onEditUser} />
|
||||
))}
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
@ -27,7 +27,7 @@ import { useLocation } from 'react-router';
|
||||
* Context
|
||||
*/
|
||||
import { UsersPageContext } from 'contexts';
|
||||
import CreateUserModal from './components/CreateUserModal';
|
||||
import CreateUserForm from './components/CreateUserForm';
|
||||
|
||||
/**
|
||||
* Users Page
|
||||
@ -54,8 +54,8 @@ const UsersPage: React.FC<IPageProps> = ({ title }) => {
|
||||
<Flex justifyContent='space-between' sx={{ gap: '10px' }} flexWrap='wrap'>
|
||||
<UserSearch />
|
||||
<Flex sx={{ gap: '10px' }}>
|
||||
<CreateUserModal me={userStore.me} />
|
||||
<Button variant='outline' colorScheme='green' leftIcon={<FaFileExcel />}>
|
||||
<CreateUserForm me={userStore.me} />
|
||||
<Button disabled variant='outline' colorScheme='green' leftIcon={<FaFileExcel />}>
|
||||
Import
|
||||
</Button>
|
||||
</Flex>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user