diff --git a/src/components/Dialogs/DeleteLessonDialog/DeleteLessonDialog.tsx b/src/components/Dialogs/DeleteLessonDialog/DeleteLessonDialog.tsx index 788c009..c7a74d1 100644 --- a/src/components/Dialogs/DeleteLessonDialog/DeleteLessonDialog.tsx +++ b/src/components/Dialogs/DeleteLessonDialog/DeleteLessonDialog.tsx @@ -19,15 +19,17 @@ type DeleteLessonDialogPropsType = { lesson: Pick; isOpen: boolean; onClose: () => void; + onConfirmed?: (id: string) => void | undefined; }; -const DeleteLessonDialog: React.FC = ({ lesson, isOpen, onClose }) => { +const DeleteLessonDialog: React.FC = ({ lesson, isOpen, onConfirmed, onClose }) => { const { deleteLesson, isLoading } = useDeleteLesson(); const handleDelete = async (isConfirmed: boolean) => { if (isConfirmed) { try { await deleteLesson(lesson.id); + onConfirmed && onConfirmed(lesson.id); } catch (error) { console.error(error); } diff --git a/src/components/SelectStudentsInput/SelectStudentsInput.tsx b/src/components/SelectStudentsInput/SelectStudentsInput.tsx index 74d8904..d9fd707 100644 --- a/src/components/SelectStudentsInput/SelectStudentsInput.tsx +++ b/src/components/SelectStudentsInput/SelectStudentsInput.tsx @@ -22,14 +22,14 @@ import { BeatLoader } from 'react-spinners'; import { Virtuoso } from 'react-virtuoso'; import { FetchUsersParamsType } from '@educt/types'; -type SelectUsersInputPropsType = { +type SelectStudentsInputPropsType = { placeholder?: string | undefined; searchParams?: FetchUsersParamsType | undefined; onSelect: (selected: Array) => void; currentStudents: ICourse['students']; }; -const SelectUsersInput: React.FC = ({ +const SelectStudentsInput: React.FC = ({ placeholder, searchParams: params, onSelect, @@ -151,4 +151,4 @@ const SelectUsersInput: React.FC = ({ ); }; -export default SelectUsersInput; +export default SelectStudentsInput; diff --git a/src/hooks/queries/course/useFetchCourse.ts b/src/hooks/queries/course/useFetchCourse.ts index 73d4b8d..7f00f53 100644 --- a/src/hooks/queries/course/useFetchCourse.ts +++ b/src/hooks/queries/course/useFetchCourse.ts @@ -2,24 +2,20 @@ * Types */ import { ICourse } from '@educt/interfaces'; +import { CourseServiceInstance } from '@educt/services'; /** * Hooks */ import { useEffect } from 'react'; import useAsync from '../../useAsync'; -import { useRootStore } from '../../useRootStore'; type CourseResultDataType = Omit; const useFetchCourse = (id: string) => { - const { - pageStore: { editCourseStore }, - } = useRootStore(); - const fetch = async (id: string) => { - const result = await editCourseStore.loadCourseById(id); - return result.data; + const { data } = await CourseServiceInstance.fetchById(id); + return data; }; const { execute: fetchCourseById, ...state } = useAsync>(fetch); diff --git a/src/hooks/queries/lesson/useDeleteLesson.ts b/src/hooks/queries/lesson/useDeleteLesson.ts index b747115..46e2459 100644 --- a/src/hooks/queries/lesson/useDeleteLesson.ts +++ b/src/hooks/queries/lesson/useDeleteLesson.ts @@ -1,22 +1,19 @@ import { useToast } from '@chakra-ui/toast'; import { ILesson } from '@educt/interfaces'; +import { LessonServiceInstance } from '@educt/services'; import { useErrorHandler } from 'react-error-boundary'; import useAsync from '../../useAsync'; -import { useRootStore } from '../../useRootStore'; type CreatedLessonDataType = ILesson; const useDeleteLesson = () => { - const { - pageStore: { editCourseStore }, - } = useRootStore(); const toast = useToast(); const handleError = useErrorHandler(); const deleteLesson = async (id: string) => { try { - const result = await editCourseStore.deleteLessonById(id); - toast({ title: 'Lesson deleted', status: 'info' }); + const result = await LessonServiceInstance.deleteLesson(id); + toast({ title: 'Lesson deleted.', status: 'info' }); return result.data; } catch (error: any) { if (error.response) { diff --git a/src/pages/courses/edit/components/LessonList/LessonList.tsx b/src/pages/courses/edit/components/LessonList/LessonList.tsx index 9eb8949..a59c65e 100644 --- a/src/pages/courses/edit/components/LessonList/LessonList.tsx +++ b/src/pages/courses/edit/components/LessonList/LessonList.tsx @@ -1,10 +1,11 @@ -import React, { useState } from 'react'; -import { observer } from 'mobx-react'; +import React from 'react'; import { ItemProps, Virtuoso } from 'react-virtuoso'; -import { DragDropContext, Draggable, DraggableProvided, Droppable, DropResult } from 'react-beautiful-dnd'; import moment from 'moment'; +import * as helpers from '@educt/helpers'; +import { DragDropContext, Draggable, DraggableProvided, Droppable, DropResult } from 'react-beautiful-dnd'; import { Flex, Box, Text, IconButton, Icon, Button } from '@chakra-ui/react'; import { DeleteIcon, DragHandleIcon } from '@chakra-ui/icons'; +import { MdTimer, MdAttachment } from 'react-icons/md'; /** * Types @@ -19,12 +20,11 @@ import DeleteLessonDialog from '@educt/components/Dialogs/DeleteLessonDialog'; /** * Hooks */ +import { useState } from 'react'; import { useHistory } from 'react-router'; import { useColorMode } from '@chakra-ui/react'; -import { MdTimer, MdAttachment } from 'react-icons/md'; -import { useRootStore } from '@educt/hooks/useRootStore'; -import useDidMountEffect from '@educt/hooks/useDidMountEffect'; import { useDisclosure } from '@chakra-ui/hooks'; +import useDidMountEffect from '@educt/hooks/useDidMountEffect'; /** * Contexts @@ -55,19 +55,16 @@ const CreateLessonButton: React.FC<{ id: string }> = ({ id }) => { const LessonList: React.FC = ({ course }) => { const history = useHistory(); - const { - pageStore: { editCourseStore }, - } = useRootStore(); - const { onOpen: onOpenDeleteDialog, onClose: onCloseDeleteDialog, isOpen: isOpenDeleteDialog } = useDisclosure(); + const { onOpen: onOpenDelDialog, onClose: onCloseDelDialog, isOpen: isOpenDelDialog } = useDisclosure(); + const [lessons, setLessons] = useState>(course.lessons); const [deleting, setDeleting] = useState | null>(null); const handleError = useErrorHandler(); - const lessons = course.lessons; const handleCreateLesson = (): void => history.push('/lessons/create'); const handleEditLesson = (id: string): void => history.push(`/lessons/edit/${id}`); const handleDeleteLesson = (lesson: ILesson): void => { setDeleting({ id: lesson.id, title: lesson.title }); - onOpenDeleteDialog(); + onOpenDelDialog(); }; const handleChangeOrder = async (ids: string[]) => { @@ -84,9 +81,11 @@ const LessonList: React.FC = ({ course }) => { return; } - editCourseStore.reorderLessons(result.source.index, result.destination.index); + setLessons(helpers.arrayMove(lessons, result.source.index, result.destination.index)); }; + const onDeleted = (id: string) => setLessons(prev => prev.filter(l => l.id !== id)); + const LessonItem = React.useMemo(() => { return ({ provided, @@ -199,7 +198,12 @@ const LessonList: React.FC = ({ course }) => { {lessons.length !== 0 ? ( <> {deleting && ( - + )} @@ -255,4 +259,4 @@ const LessonList: React.FC = ({ course }) => { ); }; -export default observer(LessonList); +export default LessonList; diff --git a/src/pages/courses/edit/components/StudentTableList/StudentTableList.tsx b/src/pages/courses/edit/components/StudentTableList/StudentTableList.tsx index 4039a54..3be1122 100644 --- a/src/pages/courses/edit/components/StudentTableList/StudentTableList.tsx +++ b/src/pages/courses/edit/components/StudentTableList/StudentTableList.tsx @@ -1,17 +1,16 @@ import React from 'react'; -import { Table, Tbody, Button, Input, InputGroup, InputLeftElement, Flex, Box, Text } from '@chakra-ui/react'; +import { Table, Tbody, Input, InputGroup, InputLeftElement, Flex, Box, Text } from '@chakra-ui/react'; 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'; -import { AddIcon } from '@chakra-ui/icons'; /** * Types */ import type { StudentTableRowPropsType } from './StudentTableRow'; -import type { ICourse } from '@educt/interfaces'; +import type { ICourse, IUser } from '@educt/interfaces'; /** * Hooks @@ -28,9 +27,8 @@ type StudentTableListPropsType = { const StudentTableList: React.FC = props => { const { render: Row, course } = props; - const { students } = course; - const [rows, setRows] = useState(students); + const [rows, setRows] = useState(props.course.students); const [selected, setSelected] = useState([]); const [search, setSearch] = useState(''); const { @@ -50,20 +48,21 @@ const StudentTableList: React.FC = props => { */ const handleSearch = (e: React.ChangeEvent) => { setSearch(e.target.value); - setRows([ - ...students.filter( + setRows( + props.course.students.filter( s => s.fullname.toLowerCase().includes(e.target.value) || s.email.toLowerCase().includes(e.target.value) - ), - ]); + ) + ); }; - /** - * Delete student from state after removing - */ const onRemoved = (removedStudents: ICourse['students']) => { setSelected(prev => prev.filter(s => removedStudents.every(r => s.id !== r.id))); }; + const onAdded = (users: IUser[]) => { + setRows(prev => [...prev, ...users]); + }; + /** * Select all students in a table */ @@ -133,7 +132,7 @@ const StudentTableList: React.FC = props => { onClose={onCloseAddStudentModal} course={course} currentStudents={rows} - onAdded={users => setRows(prev => [...prev, ...users])} + onAdded={onAdded} /> @@ -164,7 +163,7 @@ const StudentTableList: React.FC = props => { - +
{rows.map(student => ( @@ -184,7 +183,9 @@ const StudentTableList: React.FC = props => {
{isEmptyRows && ( - Cannot find any user. + + Cannot find any user. + )} diff --git a/src/pages/courses/edit/edit.tsx b/src/pages/courses/edit/edit.tsx index a8847d9..968eabd 100644 --- a/src/pages/courses/edit/edit.tsx +++ b/src/pages/courses/edit/edit.tsx @@ -21,7 +21,6 @@ import LessonList from './components/LessonList'; * Hooks */ 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, PageHeading, PageWrapper } from '@educt/components/PageElements'; @@ -33,12 +32,7 @@ import { StatusButton } from './components'; */ const EditCoursePage: React.FC = () => { const params = useParams<{ id: string }>(); - const { - pageStore: { - editCourseStore: { course }, - }, - } = useRootStore(); - const { error, isLoading } = useFetchCourse(params.id); + const { error, data: course, isLoading } = useFetchCourse(params.id); const { onOpen: onOpenDeleteDialog, onClose: onCloseDeleteDialog, isOpen: isOpenDeleteDialog } = useDisclosure(); /** @@ -68,13 +62,12 @@ const EditCoursePage: React.FC = () => { - + Info Lessons Students - diff --git a/src/pages/users/components/UserList/UserList.tsx b/src/pages/users/components/UserList/UserList.tsx index aac103b..6039f34 100644 --- a/src/pages/users/components/UserList/UserList.tsx +++ b/src/pages/users/components/UserList/UserList.tsx @@ -76,63 +76,61 @@ const UserList: React.FC = ({ render: Item, limit }) => { {editing && } {deleting && } - {users.length !== 0 ? ( - <> - - - - - - - Total: ({pagination.total}) - Actions - - {!isLoading ? ( - - {users.map(user => ( - handleEditUser(user)} - onDelete={user => handleDeleteUser({ id: user.id, fullname: user.fullname })} - /> - ))} - - ) : ( - - )} - - - - - Page {pagination.current_page} of {pagesCount} - - - - - - - - - ) : ( - + + + + + + + + Total: ({pagination.total}) + Actions + + {!isLoading ? ( + + {users.map(user => ( + handleEditUser(user)} + onDelete={user => handleDeleteUser({ id: user.id, fullname: user.fullname })} + /> + ))} + + ) : ( + + )} + + {users.length === 0 && ( + Cannot find any users. )} + + + + Page {pagination.current_page} of {pagesCount} + + + + + + + ); }; diff --git a/src/stores/PageStore.ts b/src/stores/PageStore.ts index da2c98f..1fb7a09 100644 --- a/src/stores/PageStore.ts +++ b/src/stores/PageStore.ts @@ -1,6 +1,5 @@ import { makeAutoObservable } from 'mobx'; import CoursesStore from './pages/CoursesStore'; -import EditCourseStore from './pages/EditCourseStore'; import RootStore from './RootStore'; export default class PageStore { @@ -8,8 +7,6 @@ export default class PageStore { public coursesStore: CoursesStore; - public editCourseStore: EditCourseStore; - constructor(root: RootStore) { this.root = root; @@ -17,7 +14,6 @@ export default class PageStore { * Pages */ this.coursesStore = new CoursesStore(this); - this.editCourseStore = new EditCourseStore(this); makeAutoObservable(this); } diff --git a/src/stores/pages/EditCourseStore.ts b/src/stores/pages/EditCourseStore.ts deleted file mode 100644 index 8aa2bde..0000000 --- a/src/stores/pages/EditCourseStore.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { makeAutoObservable, runInAction } from 'mobx'; -import * as helpers from '@educt/helpers'; -import { ICourse } from '@educt/interfaces'; -import PageStore from '../PageStore'; - -import { CourseServiceInstance, LessonServiceInstance } from '@educt/services'; - -export default class EditCourseStore { - public pageStore: PageStore; - - public course: Omit | null = null; - - constructor(pageStore: PageStore) { - this.pageStore = pageStore; - - makeAutoObservable(this); - } - - /** - * Load course by id into store - * - * @param id Course id - * @returns Course response - */ - public async loadCourseById(id: string) { - const result = await CourseServiceInstance.fetchById(id); - - runInAction(() => { - this.course = result.data; - }); - - return result; - } - - // TODO update stor on detaching, attaching, deleting students - - /** - * Remove lesson from store - * - * @param id Lesson id - * @returns Deleted lesson response - */ - public async deleteLessonById(id: string) { - const result = await LessonServiceInstance.deleteLesson(id); - - runInAction(() => { - if (this.course) { - this.course.lessons = this.course.lessons.filter(lesson => lesson.id !== result.data.id); - } - }); - - return result; - } - - /** - * Reorder lessons in store list on drag - * - * @param oldIndex - * @param newIndex - */ - public reorderLessons(oldIndex: number, newIndex: number): void { - if (this.course) { - this.course.lessons = helpers.arrayMove(this.course.lessons, oldIndex, newIndex); - } - } -}