feat: editing students from table

This commit is contained in:
Sergey Yarkov 2021-12-16 00:01:04 +03:00
parent 21779419c9
commit 1dff6be5e4
10 changed files with 202 additions and 201 deletions

View File

@ -16,8 +16,9 @@ import { IconType } from 'react-icons';
export interface IBaseModalProps extends ModalProps {
isOpen: boolean;
isDisabled: boolean;
onClose: () => void;
onProceed: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
onProceed?: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
heading: string;
icon?: IconType;
description?: string;
@ -27,8 +28,19 @@ export interface IBaseModalProps extends ModalProps {
}
const BaseModal: React.FC<IBaseModalProps> = props => {
const { heading, description, isOpen, onClose, onProceed, confirmText, icon, isLoading, loadingText, ...modalProps } =
props;
const {
heading,
description,
isOpen,
onClose,
onProceed,
confirmText,
icon,
isLoading,
isDisabled,
loadingText,
...modalProps
} = props;
return (
<Modal isOpen={isOpen} onClose={onClose} {...modalProps}>
@ -61,6 +73,7 @@ const BaseModal: React.FC<IBaseModalProps> = props => {
<Button
onClick={onProceed}
isLoading={isLoading}
isDisabled={isDisabled}
loadingText={loadingText || 'Loading...'}
borderRadius='md'
variant='outline'

View File

@ -0,0 +1,152 @@
import React from 'react';
import BaseModal from '@educt/components/Modal';
import * as helpers from '@educt/helpers';
import { FormControl, FormLabel, FormHelperText, Input, Select, Stack } from '@chakra-ui/react';
import { CheckIcon } from '@chakra-ui/icons';
import { MdModeEdit } from 'react-icons/md';
import { yupResolver } from '@hookform/resolvers/yup';
/**
* Types
*/
import { IUser } from '@educt/interfaces';
import { UserRoleEnum } from '@educt/enums';
import { SubmitHandler } from 'react-hook-form';
/**
* Hooks
*/
import { useEffect, useMemo } from 'react';
import { useForm } from 'react-hook-form';
import { useRootStore } from '@educt/hooks/useRootStore';
import { useUpdateUser } from '@educt/hooks/queries';
/**
* Schema
*/
import EditUserSchema from './EditUserModal.validator';
type EditUserModalPropsType = {
user: IUser;
isOpen: boolean;
onClose: () => void;
onEdited?: (updatedUser: IUser) => void | undefined;
};
type UpdateUserInputType = {
first_name: string;
last_name: string;
login: string | null;
email: string;
role: UserRoleEnum;
password: string | null;
};
const EditUserModal: React.FC<EditUserModalPropsType> = props => {
const { user, onClose, onEdited, isOpen } = props;
const {
userStore: { me },
} = useRootStore();
const { updateUser, isLoading } = useUpdateUser();
const {
register,
handleSubmit,
reset,
formState: { errors, isDirty, dirtyFields },
} = useForm<UpdateUserInputType>({
resolver: yupResolver(EditUserSchema),
defaultValues: useMemo(() => {
return props.user;
}, [props]),
});
if (me === null) return null;
/**
* Submit handler
*/
const onSubmit: SubmitHandler<UpdateUserInputType> = async data => {
try {
const params = helpers.getDirtyFields<UpdateUserInputType>(dirtyFields, data);
const updated = await updateUser(user.id, params);
onClose();
onEdited && onEdited(updated);
} catch (error) {
console.error(error);
}
};
useEffect(() => {
reset(props.user);
}, [props.user]);
return (
<BaseModal
icon={MdModeEdit}
heading='Edit user'
confirmText='Save'
loadingText='Saving...'
isLoading={isLoading}
isDisabled={!isDirty}
isOpen={isOpen}
onClose={onClose}
onProceed={handleSubmit(onSubmit)}
>
<Stack spacing='4'>
<FormControl isDisabled>
<FormLabel>Identificator</FormLabel>
<Input value={user.id} />
</FormControl>
<FormControl>
<FormLabel>New Login</FormLabel>
<Input placeholder='e.g student001' {...register('login')} isInvalid={!!errors.login} />
{errors.login && <FormHelperText color='red.500'>{errors.login.message}</FormHelperText>}
</FormControl>
<FormControl isRequired>
<FormLabel>First name</FormLabel>
<Input placeholder='First name' {...register('first_name')} isInvalid={!!errors.first_name} />
{errors.first_name && <FormHelperText color='red.500'>{errors.first_name.message}</FormHelperText>}
</FormControl>
<FormControl isRequired>
<FormLabel>Last name</FormLabel>
<Input placeholder='Last name' {...register('last_name')} isInvalid={!!errors.last_name} />
{errors.last_name && <FormHelperText color='red.500'>{errors.last_name.message}</FormHelperText>}
</FormControl>
<FormControl isRequired>
<FormLabel>Email</FormLabel>
<Input placeholder='example@email.com' {...register('email')} isInvalid={!!errors.email} />
{errors.email && <FormHelperText color='red.500'>{errors.email.message}</FormHelperText>}
</FormControl>
<FormControl isRequired>
<FormLabel>Role</FormLabel>
<Select w='full' mr='2' {...register('role')} isInvalid={!!errors.role}>
<option value={UserRoleEnum.STUDENT}>Student</option>
{me.isAdmin && (
<>
<option value={UserRoleEnum.TEACHER}>Teacher</option>
<option value={UserRoleEnum.ADMIN}>Administrator</option>
</>
)}
</Select>
{errors.role ? <FormHelperText color='red.500'>{errors.role.message}</FormHelperText> : null}
</FormControl>
<FormControl>
<FormLabel>New Password</FormLabel>
<Input placeholder='******' {...register('password')} isInvalid={!!errors.password} />
<FormHelperText color={errors.password ? 'red' : 'gray.500'}>
{errors.password ? errors.password.message : 'Must be at least 6 characters.'}
</FormHelperText>
</FormControl>
</Stack>
</BaseModal>
);
};
export default EditUserModal;

View File

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

View File

@ -27,7 +27,7 @@ export interface ICourse {
teacher: Pick<IUser, 'id' | 'first_name' | 'fullname' | 'last_name' | 'email' | 'created_at' | 'updated_at'>;
category: ICategory;
lessons: ILesson[];
students: Array<Pick<IUser, 'id' | 'first_name' | 'fullname' | 'last_name' | 'email' | 'created_at' | 'updated_at'>>;
students: IUser[];
color: ColorType | null;
students_count: string;
likes_count: string;

View File

@ -1,7 +1,12 @@
import React from 'react';
import { Table, Tbody, Input, InputGroup, InputLeftElement, Flex, Box, Text } from '@chakra-ui/react';
import { MdSearch } from 'react-icons/md';
/**
* Components
*/
import AddStudentsModal from '@educt/components/Modals/AddStudentsModal';
import EditUserModal from '@educt/components/Modals/EditUserModal';
import { AddButton } from '@educt/components/Buttons';
import BulkActionsMenu from './BulkActionsMenu';
import StudentTableHead from './StudentTableHead';
@ -31,11 +36,17 @@ const StudentTableList: React.FC<StudentTableListPropsType> = props => {
const [rows, setRows] = useState<ICourse['students']>(props.course.students);
const [selected, setSelected] = useState<ICourse['students']>([]);
const [search, setSearch] = useState<string>('');
const [editing, setEditing] = useState<ICourse['students'][number] | null>(null);
const {
isOpen: isOpenAddStudentModal,
onOpen: onOpenAddStudentModal,
onClose: onCloseAddStudentModal,
} = useDisclosure();
const {
isOpen: isOpenEditStudentModal,
onOpen: onOpenEditStudentModal,
onClose: onCloseEditStudentModal,
} = useDisclosure();
const { deleteUser } = useDeleteUser();
const { detachStudents } = useDetachStudents();
@ -63,6 +74,10 @@ const StudentTableList: React.FC<StudentTableListPropsType> = props => {
setRows(prev => [...prev, ...users]);
};
const onEdited = (user: IUser) => {
setRows(prev => prev.map(u => (u.id === user.id ? { ...user } : u)));
};
/**
* Select all students in a table
*/
@ -104,10 +119,10 @@ const StudentTableList: React.FC<StudentTableListPropsType> = props => {
/**
* Edit student handler
*/
const handleEdit = (id: string) => {
const handleEdit = (student: ICourse['students'][number]) => {
return () => {
// TODO open the edit user modal
console.log(`editing id: ${id}`);
setEditing(student);
onOpenEditStudentModal();
};
};
@ -134,6 +149,14 @@ const StudentTableList: React.FC<StudentTableListPropsType> = props => {
currentStudents={rows}
onAdded={onAdded}
/>
{editing && (
<EditUserModal
user={editing}
isOpen={isOpenEditStudentModal}
onClose={onCloseEditStudentModal}
onEdited={onEdited}
/>
)}
<Flex justifyContent='space-between' flexDir={{ base: 'column', lg: 'row' }}>
<Flex mb='2' alignItems='center'>
<Box>

View File

@ -12,7 +12,7 @@ export type StudentTableRowPropsType = {
student: ICourse['students'][number];
onSelect: (e: React.ChangeEvent<HTMLInputElement>, student: ICourse['students'][number]) => void;
actions: {
onEdit: (id: string) => () => void;
onEdit: (student: ICourse['students'][number]) => () => void;
onRemove: (id: string) => () => void;
onDelete: (id: string) => () => void;
};
@ -25,7 +25,7 @@ const StudentTableItem: React.FC<StudentTableRowPropsType> = ({ student, isSelec
<InfoCell id={student.id} fullname={student.fullname} email={student.email} />
<RegisteredCell registered={student.created_at} />
<ActionsCell
onEdit={actions.onEdit(student.id)}
onEdit={actions.onEdit(student)}
onRemove={actions.onRemove(student.id)}
onDelete={actions.onDelete(student.id)}
/>

View File

@ -1,187 +0,0 @@
import React from 'react';
import * as helpers from '@educt/helpers';
import {
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalCloseButton,
ModalBody,
ModalFooter,
FormControl,
FormLabel,
FormHelperText,
Input,
Button,
Flex,
Text,
Divider,
Select,
Stack,
} from '@chakra-ui/react';
import { CheckIcon } from '@chakra-ui/icons';
import { MdModeEdit } from 'react-icons/md';
import { yupResolver } from '@hookform/resolvers/yup';
/**
* Types
*/
import { IUser } from '@educt/interfaces';
import { UserRoleEnum } from '@educt/enums';
import { SubmitHandler } from 'react-hook-form';
/**
* Hooks
*/
import { useEffect, useMemo } from 'react';
import { useForm } from 'react-hook-form';
import { useRootStore } from '@educt/hooks/useRootStore';
import { useUpdateUser } from '@educt/hooks/queries';
/**
* Schema
*/
import UpdateUserSchema from './EditUserForm.validator';
type UpdateUserFormPropsType = {
user: IUser;
isOpen: boolean;
onClose: () => void;
};
type UpdateUserInputType = {
first_name: string;
last_name: string;
login: string | null;
email: string;
role: UserRoleEnum;
password: string | null;
};
const UpdateUserForm: React.FC<UpdateUserFormPropsType> = props => {
const { user, onClose, isOpen } = props;
const {
userStore: { me },
} = useRootStore();
const { updateUser, isLoading } = useUpdateUser();
const {
register,
handleSubmit,
reset,
formState: { errors, isDirty, dirtyFields },
} = useForm<UpdateUserInputType>({
resolver: yupResolver(UpdateUserSchema),
defaultValues: useMemo(() => {
return props.user;
}, [props]),
});
if (me === null) return null;
/**
* Submit handler
*/
const onSubmit: SubmitHandler<UpdateUserInputType> = async data => {
try {
const params = helpers.getDirtyFields<UpdateUserInputType>(dirtyFields, data);
await updateUser(user.id, params);
onClose();
} catch (error: any) {
console.error(error);
}
};
useEffect(() => {
reset(props.user);
}, [props.user]);
return (
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<form onSubmit={handleSubmit(onSubmit)}>
<ModalHeader>
<Flex alignItems='center'>
<MdModeEdit />
<Text ml={2}>Edit user</Text>
</Flex>
</ModalHeader>
<ModalCloseButton />
<Divider />
<ModalBody pb={6}>
<Stack spacing='4'>
<FormControl isDisabled>
<FormLabel>Identificator</FormLabel>
<Input value={user.id} />
</FormControl>
<FormControl>
<FormLabel>New Login</FormLabel>
<Input placeholder='e.g student001' {...register('login')} isInvalid={!!errors.login} />
{errors.login && <FormHelperText color='red.500'>{errors.login.message}</FormHelperText>}
</FormControl>
<FormControl isRequired>
<FormLabel>First name</FormLabel>
<Input placeholder='First name' {...register('first_name')} isInvalid={!!errors.first_name} />
{errors.first_name && <FormHelperText color='red.500'>{errors.first_name.message}</FormHelperText>}
</FormControl>
<FormControl isRequired>
<FormLabel>Last name</FormLabel>
<Input placeholder='Last name' {...register('last_name')} isInvalid={!!errors.last_name} />
{errors.last_name && <FormHelperText color='red.500'>{errors.last_name.message}</FormHelperText>}
</FormControl>
<FormControl isRequired>
<FormLabel>Email</FormLabel>
<Input placeholder='example@email.com' {...register('email')} isInvalid={!!errors.email} />
{errors.email && <FormHelperText color='red.500'>{errors.email.message}</FormHelperText>}
</FormControl>
<FormControl isRequired>
<FormLabel>Role</FormLabel>
<Select w='full' mr='2' {...register('role')} isInvalid={!!errors.role}>
<option value={UserRoleEnum.STUDENT}>Student</option>
{me.isAdmin && (
<>
<option value={UserRoleEnum.TEACHER}>Teacher</option>
<option value={UserRoleEnum.ADMIN}>Administrator</option>
</>
)}
</Select>
{errors.role ? <FormHelperText color='red.500'>{errors.role.message}</FormHelperText> : null}
</FormControl>
<FormControl>
<FormLabel>New Password</FormLabel>
<Input placeholder='******' {...register('password')} isInvalid={!!errors.password} />
<FormHelperText color={errors.password ? 'red' : 'gray.500'}>
{errors.password ? errors.password.message : 'Must be at least 6 characters.'}
</FormHelperText>
</FormControl>
</Stack>
</ModalBody>
<ModalFooter>
<Button
type='submit'
variant='outline'
mr={3}
colorScheme='blue'
isLoading={isLoading}
loadingText='Saving...'
isDisabled={!isDirty}
leftIcon={<CheckIcon />}
>
Save
</Button>
<Button onClick={onClose}>Cancel</Button>
</ModalFooter>
</form>
</ModalContent>
</Modal>
);
};
export default UpdateUserForm;

View File

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

View File

@ -8,7 +8,7 @@ import { Button } from '@chakra-ui/button';
* Components
*/
import LoadingList from '@educt/components/LoadingList';
import EditUserForm from '../EditUserForm';
import EditUserModal from '@educt/components/Modals/EditUserModal';
import DeleteUserDialog from '@educt/components/Dialogs/DeleteUserDialog';
/**
@ -74,7 +74,7 @@ const UserList: React.FC<UserListPropsType> = ({ render: Item, limit }) => {
return (
<Box>
{editing && <EditUserForm user={editing} isOpen={isOpenEditModal} onClose={onCloseEditModal} />}
{editing && <EditUserModal user={editing} isOpen={isOpenEditModal} onClose={onCloseEditModal} />}
{deleting && <DeleteUserDialog user={deleting} isOpen={isOpenDeleteDialog} onClose={onCloseDeleteDialog} />}
<Flex justifyContent='space-between' sx={{ gap: '10px' }} flexWrap='wrap'>