diff --git a/src/components/Buttons/index.tsx b/src/components/Buttons/index.tsx index 6b3b170..212f487 100644 --- a/src/components/Buttons/index.tsx +++ b/src/components/Buttons/index.tsx @@ -9,14 +9,20 @@ export const CreateButton: React.FC = props => ( ); +export const AddButton: React.FC = props => ( + +); + export const DeleteButton: React.FC = props => ( - ); export const EditButton: React.FC = props => ( - ); diff --git a/src/components/Modal/BaseModal.tsx b/src/components/Modal/BaseModal.tsx new file mode 100644 index 0000000..8dd2701 --- /dev/null +++ b/src/components/Modal/BaseModal.tsx @@ -0,0 +1,66 @@ +import React from 'react'; +import { Button } from '@chakra-ui/button'; +import { Text, Flex, Box, Divider } from '@chakra-ui/react'; +import { + Icon, + ModalProps, + Modal, + ModalOverlay, + ModalContent, + ModalHeader, + ModalFooter, + ModalBody, + ModalCloseButton, +} from '@chakra-ui/react'; +import { IconType } from 'react-icons'; + +export interface IBaseModalProps extends ModalProps { + isOpen: boolean; + onClose: () => void; + heading: string; + icon?: IconType; + description?: string; + confirmText?: string; +} + +const BaseModal: React.FC = props => { + const { heading, description, isOpen, onClose, confirmText, icon, ...modalProps } = props; + + return ( + + + + + + {icon && } + {heading} + + {description && ( + + + {description} + + + )} + + + + + + + {props.children} + + + + + + + + ); +}; + +export default BaseModal; diff --git a/src/components/Modal/index.ts b/src/components/Modal/index.ts new file mode 100644 index 0000000..76e3dba --- /dev/null +++ b/src/components/Modal/index.ts @@ -0,0 +1,3 @@ +import BaseModal from './BaseModal'; + +export default BaseModal; diff --git a/src/components/Modals/AddStudentsModal/AddSudentsModal.tsx b/src/components/Modals/AddStudentsModal/AddSudentsModal.tsx index 5a6ec6c..e357a24 100644 --- a/src/components/Modals/AddStudentsModal/AddSudentsModal.tsx +++ b/src/components/Modals/AddStudentsModal/AddSudentsModal.tsx @@ -1,48 +1,147 @@ -import React from 'react'; -import { Button } from '@chakra-ui/button'; -import { Text, Flex } from '@chakra-ui/react'; +import React, { useCallback, useEffect, useState } from 'react'; + import { - Modal, - ModalOverlay, - ModalContent, - ModalHeader, - ModalFooter, - ModalBody, - ModalCloseButton, + Tag, + TagLabel, + TagCloseButton, + Input, + Box, + Flex, + IconButton, + BoxProps, + InputGroup, + Text, + Stack, + InputRightElement, + Avatar, } from '@chakra-ui/react'; -import { useDisclosure, Divider } from '@chakra-ui/react'; +import BaseModal from '@educt/components/Modal'; import { MdGroup } from 'react-icons/md'; +import { AddIcon, SearchIcon } from '@chakra-ui/icons'; +import { UserServiceInstance } from '@educt/services'; +import { IUser } from '@educt/interfaces'; +import { useErrorHandler } from 'react-error-boundary'; +import { BeatLoader } from 'react-spinners'; +import { Virtuoso } from 'react-virtuoso'; +import { FetchUsersParamsType } from '@educt/types'; type AddStudentsModalPropsType = { isOpen: boolean; onClose: () => void; }; +const SelectUsersInput: React.FC = props => { + const [isLoading, setIsLoading] = useState(false); + const [search, setSearch] = useState(''); + const [selected, setSelected] = useState>([]); + const [users, setUsers] = useState>([]); + const [limit, setLimit] = useState(5); + const filtered = users.filter(u => selected.every(s => s.id !== u.id)); + const handleError = useErrorHandler(); + const isEmpty = filtered.length === 0; + + const fetch = async (params?: FetchUsersParamsType | undefined) => { + try { + setIsLoading(true); + const { data } = await UserServiceInstance.fetchAll({ search, limit }); + setUsers(data); + } catch (error: any) { + handleError(error); + } finally { + setIsLoading(false); + } + }; + + const handleSearch = (e: React.ChangeEvent) => setSearch(e.target.value); + const handleSelect = (user: IUser) => () => setSelected(prev => [...prev, user]); + const handleRemoveSelected = (id: IUser['id']) => () => setSelected(prev => prev.filter(u => u.id !== id)); + + useEffect(() => { + fetch(); + }, [search]); + + return ( + + {selected.length !== 0 && ( + + {selected.map(selected => ( + + + {selected.fullname} + + + ))} + + )} + + fetch()} + onChange={handleSearch} + type='text' + placeholder='Search for users...' + /> + } /> + + + {isLoading && ( + + Loading...   + + + )} + + {!isLoading && isEmpty && ( + + Cannot find any user + + )} + + {!isLoading && !isEmpty && ( + + ( + + + + + {user.fullname} + + + {user.email} + + + } + ml='auto' + borderRadius='full' + size='sm' + /> + + )} + /> + + )} + + ); +}; + const AddStudentsModal: React.FC = ({ isOpen, onClose }) => { return ( - <> - - - - - - - Add students - - - - - - - - - - - - - + + + ); }; diff --git a/src/pages/courses/courses.tsx b/src/pages/courses/courses.tsx index 5a36232..c678e1d 100644 --- a/src/pages/courses/courses.tsx +++ b/src/pages/courses/courses.tsx @@ -10,6 +10,7 @@ import { IPageProps } from '@educt/interfaces'; */ import { PageWrapper, PageHeading, PageContent } from '@educt/components/PageElements'; import { CreateButton } from '@educt/components/Buttons'; +import { Box } from '@chakra-ui/layout'; import LoadingPage from '@educt/components/LoadingPage'; import CourseList from './components/CourseList'; import CategoryList from './components/CategoryList'; @@ -32,12 +33,17 @@ const CoursesPage: React.FC = () => { } = useRootStore(); const history = useHistory(); + const handleCreateCourse = () => history.push('/courses/create'); + if (me === null) return ; return ( - - {(me.isAdmin || me.isTeacher) && history.push('/courses/create')} />} + + + {(me.isAdmin || me.isTeacher) && } + + diff --git a/src/pages/courses/edit/components/StudentTableList/StudentTableList.tsx b/src/pages/courses/edit/components/StudentTableList/StudentTableList.tsx index 4034568..562b08f 100644 --- a/src/pages/courses/edit/components/StudentTableList/StudentTableList.tsx +++ b/src/pages/courses/edit/components/StudentTableList/StudentTableList.tsx @@ -3,6 +3,7 @@ import { Table, Tbody, Button, Input, InputGroup, InputLeftElement, Flex, Box, T import { MdSearch } from 'react-icons/md'; import AddStudentsModal from '@educt/components/Modals/AddStudentsModal'; +import { AddButton } from '@educt/components/Buttons'; import BulkActionsMenu from './BulkActionsMenu'; import StudentTableHead from './StudentTableHead'; @@ -17,8 +18,6 @@ import type { ICourse } from '@educt/interfaces'; */ import { useCallback, useState } from 'react'; import { useDisclosure } from '@chakra-ui/react'; -import { AddIcon } from '@chakra-ui/icons'; -import { CreateButton } from '@educt/components/Buttons'; type StudentTableListPropsType = { render: React.FC; @@ -105,7 +104,7 @@ const StudentTableList: React.FC = ({ render: Row, st )} - + diff --git a/src/pages/courses/edit/edit.tsx b/src/pages/courses/edit/edit.tsx index d644009..5bf060a 100644 --- a/src/pages/courses/edit/edit.tsx +++ b/src/pages/courses/edit/edit.tsx @@ -24,7 +24,7 @@ import { useParams } from 'react-router-dom'; import { useRootStore } from '@educt/hooks/useRootStore'; import { useFetchCourse } from '@educt/hooks/queries'; import { useDisclosure } from '@chakra-ui/hooks'; -import { PageContent, PageWrapper } from '@educt/components/PageElements'; +import { PageContent, PageHeading, PageWrapper } from '@educt/components/PageElements'; import { DeleteButton } from '@educt/components/Buttons'; import { StatusButton } from './components'; @@ -60,18 +60,12 @@ const EditCoursePage: React.FC = () => { - - - - Course editor - {course.title} - - + + - - - + + diff --git a/src/services/UserService.ts b/src/services/UserService.ts index 16ab024..8737e65 100644 --- a/src/services/UserService.ts +++ b/src/services/UserService.ts @@ -28,7 +28,7 @@ class UserService { * @returns Array of users */ public async fetchAll(params?: FetchUsersParamsType): Promise> { - const { page = 1, limit = 6, role, search } = params || {}; + const { page, limit, role, search } = params || {}; const result = await this.api.get('/v1/users', { params: { page,