feat: added delete actions on users in table

This commit is contained in:
Sergey Yarkov 2021-12-11 23:30:26 +03:00
parent ef07c9c7be
commit 08d2e0ccde
7 changed files with 123 additions and 24 deletions

View File

@ -3,19 +3,27 @@ import React, { useState } from 'react';
import BaseModal from '@educt/components/Modal';
import { MdGroup } from 'react-icons/md';
import { UserRoleEnum } from '@educt/enums';
import SelectUsersInput from '@educt/components/SelectUsersInput';
import { IUser } from '@educt/interfaces';
import SelectStudentsInput from '@educt/components/SelectStudentsInput';
import { ICourse, IUser } from '@educt/interfaces';
import { CourseServiceInstance } from '@educt/services';
import { useToast } from '@chakra-ui/react';
import useIsMountedRef from '@educt/hooks/useIsMountedRef';
type AddStudentsModalPropsType = {
course: Omit<ICourse, 'students_count' | 'likes_count' | 'lessons_count'>;
currentStudents: ICourse['students'];
isOpen: boolean;
onClose: () => void;
courseId: string;
onAdded: (users: Array<IUser>) => void;
};
const AddStudentsModal: React.FC<AddStudentsModalPropsType> = ({ isOpen, onClose, courseId }) => {
const AddStudentsModal: React.FC<AddStudentsModalPropsType> = ({
isOpen,
onClose,
onAdded,
currentStudents,
course,
}) => {
const [isLoading, setIsLoading] = useState<boolean>(false);
const isMountedRef = useIsMountedRef();
const [students, setStudents] = useState<Array<IUser>>([]);
@ -27,9 +35,10 @@ const AddStudentsModal: React.FC<AddStudentsModalPropsType> = ({ isOpen, onClose
try {
setIsLoading(true);
const ids = students.map(s => s.id);
await CourseServiceInstance.attachStudentsList(courseId, ids);
await CourseServiceInstance.attachStudentsList(course.id, ids);
toast({ title: 'Students successfully added.', status: 'success' });
toast({ title: 'Students added.', status: 'info' });
onAdded(students);
onClose();
} catch (error) {
console.error(error);
@ -51,7 +60,8 @@ const AddStudentsModal: React.FC<AddStudentsModalPropsType> = ({ isOpen, onClose
isLoading={isLoading}
loadingText='Saving...'
>
<SelectUsersInput
<SelectStudentsInput
currentStudents={currentStudents}
onSelect={selected => handleSelectStudents(selected)}
placeholder='Search for students...'
searchParams={{ role: UserRoleEnum.STUDENT }}

View File

@ -16,7 +16,7 @@ import {
} from '@chakra-ui/react';
import { AddIcon, SearchIcon } from '@chakra-ui/icons';
import { UserServiceInstance } from '@educt/services';
import { IUser } from '@educt/interfaces';
import { ICourse, IUser } from '@educt/interfaces';
import { useErrorHandler } from 'react-error-boundary';
import { BeatLoader } from 'react-spinners';
import { Virtuoso } from 'react-virtuoso';
@ -26,14 +26,22 @@ type SelectUsersInputPropsType = {
placeholder?: string | undefined;
searchParams?: FetchUsersParamsType | undefined;
onSelect: (selected: Array<IUser>) => void;
currentStudents: ICourse['students'];
};
const SelectUsersInput: React.FC<SelectUsersInputPropsType> = ({ placeholder, searchParams: params, onSelect }) => {
const SelectUsersInput: React.FC<SelectUsersInputPropsType> = ({
placeholder,
searchParams: params,
onSelect,
currentStudents,
}) => {
const [isLoading, setIsLoading] = useState<boolean>(false);
const [search, setSearch] = useState<string>('');
const [selected, setSelected] = useState<Array<IUser>>([]);
const [users, setUsers] = useState<Array<IUser>>([]);
const filtered = users.filter(u => selected.every(s => s.id !== u.id));
const filtered = users
.filter(u => selected.every(s => s.id !== u.id))
.filter(u => currentStudents.every(s => s.id !== u.id));
const handleError = useErrorHandler();
const isEmpty = filtered.length === 0;

View File

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

View File

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

View File

@ -51,15 +51,23 @@ export const RegisteredCell: React.FC<RegisteredCellPropsType> = memo(({ registe
<Td>{new Date(registered).toLocaleDateString()}</Td>
));
type ActionsCellPropsType = Pick<IUser, 'id'>;
export const ActionsCell: React.FC<ActionsCellPropsType> = memo(({ id }) => (
type ActionsCellPropsType = {
onEdit: () => void;
onRemove: () => void;
onDelete: () => void;
};
export const ActionsCell: React.FC<ActionsCellPropsType> = memo(({ onEdit, onRemove, onDelete }) => (
<Td textAlign='center'>
<Menu isLazy>
<MenuButton as={IconButton} aria-label='Actions' icon={<MdMoreHoriz />} variant='ghost' />
<MenuList>
<MenuItem icon={<MdEdit />}>Edit</MenuItem>
<MenuItem icon={<MdRemove />}>Remove from course</MenuItem>
<MenuItem icon={<DeleteIcon />} color='red.400'>
<MenuItem onClick={onEdit} icon={<MdEdit />}>
Edit
</MenuItem>
<MenuItem onClick={onRemove} icon={<MdRemove />}>
Remove from course
</MenuItem>
<MenuItem onClick={onDelete} icon={<DeleteIcon />} color='red.400'>
Delete
</MenuItem>
</MenuList>

View File

@ -1,8 +1,8 @@
import React from 'react';
import { Table, Tbody, Button, Input, InputGroup, InputLeftElement, Flex, Box, Text } from '@chakra-ui/react';
import { Table, Tbody, Button, Input, InputGroup, InputLeftElement, Flex, Box, Text, useToast } from '@chakra-ui/react';
import { MdSearch } from 'react-icons/md';
import AddStudentsModal from '@educt/components/Modals/AddStudentsModal';
import { BeatLoader } from 'react-spinners';
import { AddButton } from '@educt/components/Buttons';
import BulkActionsMenu from './BulkActionsMenu';
import StudentTableHead from './StudentTableHead';
@ -18,6 +18,7 @@ import type { ICourse } from '@educt/interfaces';
*/
import { useCallback, useState } from 'react';
import { useDisclosure } from '@chakra-ui/react';
import { UserServiceInstance } from '@educt/services';
type StudentTableListPropsType = {
render: React.FC<StudentTableRowPropsType>;
@ -28,6 +29,7 @@ const StudentTableList: React.FC<StudentTableListPropsType> = props => {
const { render: Row, course } = props;
const { students } = course;
const [isLoading, setIsLoading] = useState<boolean>(false);
const [rows, setRows] = useState<ICourse['students']>(students);
const [selected, setSelected] = useState<ICourse['students']>([]);
const [search, setSearch] = useState<string>('');
@ -36,6 +38,7 @@ const StudentTableList: React.FC<StudentTableListPropsType> = props => {
onOpen: onOpenAddStudentModal,
onClose: onCloseAddStudentModal,
} = useDisclosure();
const toast = useToast();
/**
* Search for a student by input
@ -73,10 +76,52 @@ const StudentTableList: React.FC<StudentTableListPropsType> = props => {
setSelected(prev => prev.filter(s => s.id !== student.id));
}, []);
/**
* Delete student handler
*/
const handleDelete = (id: string) => {
return async () => {
try {
setIsLoading(true);
const { data } = await UserServiceInstance.delete(id);
toast({ title: 'Student deleted', status: 'info' });
setRows(prev => prev.filter(s => s.id !== data.id));
} catch (error) {
console.error(error);
} finally {
setIsLoading(false);
}
};
};
/**
* Edit student handler
*/
const handleEdit = (id: string) => {
return () => {
console.log(`editing id: ${id}`);
};
};
/**
* Remove from course student handler
*/
const handleRemove = (id: string) => {
return () => {
console.log(`removing id: ${id}`);
};
};
if (rows.length === 0) {
return (
<>
<AddStudentsModal isOpen={isOpenAddStudentModal} onClose={onCloseAddStudentModal} courseId={course.id} />
<AddStudentsModal
isOpen={isOpenAddStudentModal}
onClose={onCloseAddStudentModal}
course={course}
currentStudents={rows}
onAdded={users => setRows(prev => [...prev, ...users])}
/>
<Box textAlign='center' mt='10'>
<Text>There are no students in the course yet</Text>
<Button mt='2' onClick={onOpenAddStudentModal} size='sm' colorScheme='blue' variant='outline'>
@ -89,7 +134,13 @@ const StudentTableList: React.FC<StudentTableListPropsType> = props => {
return (
<Box>
<AddStudentsModal isOpen={isOpenAddStudentModal} onClose={onCloseAddStudentModal} courseId={course.id} />
<AddStudentsModal
isOpen={isOpenAddStudentModal}
onClose={onCloseAddStudentModal}
course={course}
currentStudents={rows}
onAdded={users => setRows(prev => [...prev, ...users])}
/>
<Flex justifyContent='space-between' flexDir={{ base: 'column', lg: 'row' }}>
<Flex mb='2'>
<InputGroup mr='2' borderRadius='lg'>
@ -112,6 +163,14 @@ const StudentTableList: React.FC<StudentTableListPropsType> = props => {
<AddButton onClick={onOpenAddStudentModal} />
</Flex>
</Flex>
{isLoading && (
<Flex alignItems='center' justifyContent={'flex-end'}>
<BeatLoader size={6} color='gray' />
<Text fontSize='sm' ml='3' mt='2' mb='2'>
Loading...
</Text>
</Flex>
)}
<Table overflow='hidden' borderRadius='lg' mt='2'>
<StudentTableHead onSelectAll={handleSelectAll} />
<Tbody>
@ -121,6 +180,11 @@ const StudentTableList: React.FC<StudentTableListPropsType> = props => {
student={student}
isSelected={!!selected.find(s => s.id === student.id)}
onSelect={handleSelect}
actions={{
onDelete: handleDelete,
onEdit: handleEdit,
onRemove: handleRemove,
}}
/>
))}
</Tbody>

View File

@ -11,15 +11,24 @@ export type StudentTableRowPropsType = {
isSelected: boolean;
student: ICourse['students'][number];
onSelect: (e: React.ChangeEvent<HTMLInputElement>, student: ICourse['students'][number]) => void;
actions: {
onEdit: (id: string) => () => void;
onRemove: (id: string) => () => void;
onDelete: (id: string) => () => void;
};
};
const StudentTableItem: React.FC<StudentTableRowPropsType> = ({ student, isSelected, onSelect }) => {
const StudentTableItem: React.FC<StudentTableRowPropsType> = ({ student, isSelected, onSelect, actions }) => {
return (
<Tr>
<CheckBoxCell student={student} isSelected={isSelected} onSelect={onSelect} />
<InfoCell id={student.id} fullname={student.fullname} email={student.email} />
<RegisteredCell registered={student.created_at} />
<ActionsCell id={student.id} />
<ActionsCell
onEdit={actions.onEdit(student.id)}
onRemove={actions.onRemove(student.id)}
onDelete={actions.onDelete(student.id)}
/>
</Tr>
);
};