mirror of
https://github.com/sergeyyarkov/educt-client.git
synced 2026-10-11 10:19:22 +03:00
feat: editing students from table
This commit is contained in:
parent
21779419c9
commit
1dff6be5e4
@ -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'
|
||||
|
||||
152
src/components/Modals/EditUserModal/EditUserModal.tsx
Normal file
152
src/components/Modals/EditUserModal/EditUserModal.tsx
Normal 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;
|
||||
3
src/components/Modals/EditUserModal/index.ts
Normal file
3
src/components/Modals/EditUserModal/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import EditUserModal from './EditUserModal';
|
||||
|
||||
export default EditUserModal;
|
||||
@ -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;
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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)}
|
||||
/>
|
||||
|
||||
@ -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;
|
||||
@ -1,3 +0,0 @@
|
||||
import EditUserForm from './EditUserForm';
|
||||
|
||||
export default EditUserForm;
|
||||
@ -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'>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user