mirror of
https://github.com/sergeyyarkov/educt-client.git
synced 2026-10-11 10:19:22 +03:00
feat: added delete actions on users in table
This commit is contained in:
parent
ef07c9c7be
commit
08d2e0ccde
@ -3,19 +3,27 @@ import React, { useState } from 'react';
|
|||||||
import BaseModal from '@educt/components/Modal';
|
import BaseModal from '@educt/components/Modal';
|
||||||
import { MdGroup } from 'react-icons/md';
|
import { MdGroup } from 'react-icons/md';
|
||||||
import { UserRoleEnum } from '@educt/enums';
|
import { UserRoleEnum } from '@educt/enums';
|
||||||
import SelectUsersInput from '@educt/components/SelectUsersInput';
|
import SelectStudentsInput from '@educt/components/SelectStudentsInput';
|
||||||
import { IUser } from '@educt/interfaces';
|
import { ICourse, IUser } from '@educt/interfaces';
|
||||||
import { CourseServiceInstance } from '@educt/services';
|
import { CourseServiceInstance } from '@educt/services';
|
||||||
import { useToast } from '@chakra-ui/react';
|
import { useToast } from '@chakra-ui/react';
|
||||||
import useIsMountedRef from '@educt/hooks/useIsMountedRef';
|
import useIsMountedRef from '@educt/hooks/useIsMountedRef';
|
||||||
|
|
||||||
type AddStudentsModalPropsType = {
|
type AddStudentsModalPropsType = {
|
||||||
|
course: Omit<ICourse, 'students_count' | 'likes_count' | 'lessons_count'>;
|
||||||
|
currentStudents: ICourse['students'];
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onClose: () => void;
|
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 [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
const isMountedRef = useIsMountedRef();
|
const isMountedRef = useIsMountedRef();
|
||||||
const [students, setStudents] = useState<Array<IUser>>([]);
|
const [students, setStudents] = useState<Array<IUser>>([]);
|
||||||
@ -27,9 +35,10 @@ const AddStudentsModal: React.FC<AddStudentsModalPropsType> = ({ isOpen, onClose
|
|||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const ids = students.map(s => s.id);
|
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();
|
onClose();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@ -51,7 +60,8 @@ const AddStudentsModal: React.FC<AddStudentsModalPropsType> = ({ isOpen, onClose
|
|||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
loadingText='Saving...'
|
loadingText='Saving...'
|
||||||
>
|
>
|
||||||
<SelectUsersInput
|
<SelectStudentsInput
|
||||||
|
currentStudents={currentStudents}
|
||||||
onSelect={selected => handleSelectStudents(selected)}
|
onSelect={selected => handleSelectStudents(selected)}
|
||||||
placeholder='Search for students...'
|
placeholder='Search for students...'
|
||||||
searchParams={{ role: UserRoleEnum.STUDENT }}
|
searchParams={{ role: UserRoleEnum.STUDENT }}
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import {
|
|||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { AddIcon, SearchIcon } from '@chakra-ui/icons';
|
import { AddIcon, SearchIcon } from '@chakra-ui/icons';
|
||||||
import { UserServiceInstance } from '@educt/services';
|
import { UserServiceInstance } from '@educt/services';
|
||||||
import { IUser } from '@educt/interfaces';
|
import { ICourse, IUser } from '@educt/interfaces';
|
||||||
import { useErrorHandler } from 'react-error-boundary';
|
import { useErrorHandler } from 'react-error-boundary';
|
||||||
import { BeatLoader } from 'react-spinners';
|
import { BeatLoader } from 'react-spinners';
|
||||||
import { Virtuoso } from 'react-virtuoso';
|
import { Virtuoso } from 'react-virtuoso';
|
||||||
@ -26,14 +26,22 @@ type SelectUsersInputPropsType = {
|
|||||||
placeholder?: string | undefined;
|
placeholder?: string | undefined;
|
||||||
searchParams?: FetchUsersParamsType | undefined;
|
searchParams?: FetchUsersParamsType | undefined;
|
||||||
onSelect: (selected: Array<IUser>) => void;
|
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 [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
const [search, setSearch] = useState<string>('');
|
const [search, setSearch] = useState<string>('');
|
||||||
const [selected, setSelected] = useState<Array<IUser>>([]);
|
const [selected, setSelected] = useState<Array<IUser>>([]);
|
||||||
const [users, setUsers] = 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 handleError = useErrorHandler();
|
||||||
const isEmpty = filtered.length === 0;
|
const isEmpty = filtered.length === 0;
|
||||||
|
|
||||||
3
src/components/SelectStudentsInput/index.ts
Normal file
3
src/components/SelectStudentsInput/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import SelectStudentsInput from './SelectStudentsInput';
|
||||||
|
|
||||||
|
export default SelectStudentsInput;
|
||||||
@ -1,3 +0,0 @@
|
|||||||
import SelectUsersInput from './SelectUsersInput';
|
|
||||||
|
|
||||||
export default SelectUsersInput;
|
|
||||||
@ -51,15 +51,23 @@ export const RegisteredCell: React.FC<RegisteredCellPropsType> = memo(({ registe
|
|||||||
<Td>{new Date(registered).toLocaleDateString()}</Td>
|
<Td>{new Date(registered).toLocaleDateString()}</Td>
|
||||||
));
|
));
|
||||||
|
|
||||||
type ActionsCellPropsType = Pick<IUser, 'id'>;
|
type ActionsCellPropsType = {
|
||||||
export const ActionsCell: React.FC<ActionsCellPropsType> = memo(({ id }) => (
|
onEdit: () => void;
|
||||||
|
onRemove: () => void;
|
||||||
|
onDelete: () => void;
|
||||||
|
};
|
||||||
|
export const ActionsCell: React.FC<ActionsCellPropsType> = memo(({ onEdit, onRemove, onDelete }) => (
|
||||||
<Td textAlign='center'>
|
<Td textAlign='center'>
|
||||||
<Menu isLazy>
|
<Menu isLazy>
|
||||||
<MenuButton as={IconButton} aria-label='Actions' icon={<MdMoreHoriz />} variant='ghost' />
|
<MenuButton as={IconButton} aria-label='Actions' icon={<MdMoreHoriz />} variant='ghost' />
|
||||||
<MenuList>
|
<MenuList>
|
||||||
<MenuItem icon={<MdEdit />}>Edit</MenuItem>
|
<MenuItem onClick={onEdit} icon={<MdEdit />}>
|
||||||
<MenuItem icon={<MdRemove />}>Remove from course</MenuItem>
|
Edit
|
||||||
<MenuItem icon={<DeleteIcon />} color='red.400'>
|
</MenuItem>
|
||||||
|
<MenuItem onClick={onRemove} icon={<MdRemove />}>
|
||||||
|
Remove from course
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem onClick={onDelete} icon={<DeleteIcon />} color='red.400'>
|
||||||
Delete
|
Delete
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</MenuList>
|
</MenuList>
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
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 { MdSearch } from 'react-icons/md';
|
||||||
|
|
||||||
import AddStudentsModal from '@educt/components/Modals/AddStudentsModal';
|
import AddStudentsModal from '@educt/components/Modals/AddStudentsModal';
|
||||||
|
import { BeatLoader } from 'react-spinners';
|
||||||
import { AddButton } from '@educt/components/Buttons';
|
import { AddButton } from '@educt/components/Buttons';
|
||||||
import BulkActionsMenu from './BulkActionsMenu';
|
import BulkActionsMenu from './BulkActionsMenu';
|
||||||
import StudentTableHead from './StudentTableHead';
|
import StudentTableHead from './StudentTableHead';
|
||||||
@ -18,6 +18,7 @@ import type { ICourse } from '@educt/interfaces';
|
|||||||
*/
|
*/
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { useDisclosure } from '@chakra-ui/react';
|
import { useDisclosure } from '@chakra-ui/react';
|
||||||
|
import { UserServiceInstance } from '@educt/services';
|
||||||
|
|
||||||
type StudentTableListPropsType = {
|
type StudentTableListPropsType = {
|
||||||
render: React.FC<StudentTableRowPropsType>;
|
render: React.FC<StudentTableRowPropsType>;
|
||||||
@ -28,6 +29,7 @@ const StudentTableList: React.FC<StudentTableListPropsType> = props => {
|
|||||||
const { render: Row, course } = props;
|
const { render: Row, course } = props;
|
||||||
const { students } = course;
|
const { students } = course;
|
||||||
|
|
||||||
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
const [rows, setRows] = useState<ICourse['students']>(students);
|
const [rows, setRows] = useState<ICourse['students']>(students);
|
||||||
const [selected, setSelected] = useState<ICourse['students']>([]);
|
const [selected, setSelected] = useState<ICourse['students']>([]);
|
||||||
const [search, setSearch] = useState<string>('');
|
const [search, setSearch] = useState<string>('');
|
||||||
@ -36,6 +38,7 @@ const StudentTableList: React.FC<StudentTableListPropsType> = props => {
|
|||||||
onOpen: onOpenAddStudentModal,
|
onOpen: onOpenAddStudentModal,
|
||||||
onClose: onCloseAddStudentModal,
|
onClose: onCloseAddStudentModal,
|
||||||
} = useDisclosure();
|
} = useDisclosure();
|
||||||
|
const toast = useToast();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search for a student by input
|
* 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));
|
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) {
|
if (rows.length === 0) {
|
||||||
return (
|
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'>
|
<Box textAlign='center' mt='10'>
|
||||||
<Text>There are no students in the course yet</Text>
|
<Text>There are no students in the course yet</Text>
|
||||||
<Button mt='2' onClick={onOpenAddStudentModal} size='sm' colorScheme='blue' variant='outline'>
|
<Button mt='2' onClick={onOpenAddStudentModal} size='sm' colorScheme='blue' variant='outline'>
|
||||||
@ -89,7 +134,13 @@ const StudentTableList: React.FC<StudentTableListPropsType> = props => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<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 justifyContent='space-between' flexDir={{ base: 'column', lg: 'row' }}>
|
||||||
<Flex mb='2'>
|
<Flex mb='2'>
|
||||||
<InputGroup mr='2' borderRadius='lg'>
|
<InputGroup mr='2' borderRadius='lg'>
|
||||||
@ -112,6 +163,14 @@ const StudentTableList: React.FC<StudentTableListPropsType> = props => {
|
|||||||
<AddButton onClick={onOpenAddStudentModal} />
|
<AddButton onClick={onOpenAddStudentModal} />
|
||||||
</Flex>
|
</Flex>
|
||||||
</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'>
|
<Table overflow='hidden' borderRadius='lg' mt='2'>
|
||||||
<StudentTableHead onSelectAll={handleSelectAll} />
|
<StudentTableHead onSelectAll={handleSelectAll} />
|
||||||
<Tbody>
|
<Tbody>
|
||||||
@ -121,6 +180,11 @@ const StudentTableList: React.FC<StudentTableListPropsType> = props => {
|
|||||||
student={student}
|
student={student}
|
||||||
isSelected={!!selected.find(s => s.id === student.id)}
|
isSelected={!!selected.find(s => s.id === student.id)}
|
||||||
onSelect={handleSelect}
|
onSelect={handleSelect}
|
||||||
|
actions={{
|
||||||
|
onDelete: handleDelete,
|
||||||
|
onEdit: handleEdit,
|
||||||
|
onRemove: handleRemove,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Tbody>
|
</Tbody>
|
||||||
|
|||||||
@ -11,15 +11,24 @@ export type StudentTableRowPropsType = {
|
|||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
student: ICourse['students'][number];
|
student: ICourse['students'][number];
|
||||||
onSelect: (e: React.ChangeEvent<HTMLInputElement>, student: ICourse['students'][number]) => void;
|
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 (
|
return (
|
||||||
<Tr>
|
<Tr>
|
||||||
<CheckBoxCell student={student} isSelected={isSelected} onSelect={onSelect} />
|
<CheckBoxCell student={student} isSelected={isSelected} onSelect={onSelect} />
|
||||||
<InfoCell id={student.id} fullname={student.fullname} email={student.email} />
|
<InfoCell id={student.id} fullname={student.fullname} email={student.email} />
|
||||||
<RegisteredCell registered={student.created_at} />
|
<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>
|
</Tr>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user