mirror of
https://github.com/sergeyyarkov/educt-client.git
synced 2026-10-11 10:19:22 +03:00
Merge branch 'feature/delete-lesson' into develop
This commit is contained in:
commit
bc5b493640
@ -0,0 +1,55 @@
|
||||
import React from 'react';
|
||||
|
||||
/**
|
||||
* Types
|
||||
*/
|
||||
import { ILesson } from '@educt/interfaces';
|
||||
|
||||
/**
|
||||
* Components
|
||||
*/
|
||||
import ConfirmDialog from '@educt/components/ConfirmDialog/ConfirmDialog';
|
||||
|
||||
/**
|
||||
* Hooks
|
||||
*/
|
||||
import { useDeleteLesson } from '@educt/hooks/queries';
|
||||
import { useRootStore } from '@educt/hooks/useRootStore';
|
||||
|
||||
type DeleteLessonDialogPropsType = {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
const DeleteLessonDialog: React.FC<DeleteLessonDialogPropsType> = ({ isOpen, onClose }) => {
|
||||
const {
|
||||
pageStore: {
|
||||
editCourseStore: { deletingLesson },
|
||||
},
|
||||
} = useRootStore();
|
||||
const { deleteLesson, isLoading } = useDeleteLesson();
|
||||
|
||||
const handleDelete = async (isConfirmed: boolean) => {
|
||||
if (isConfirmed && deletingLesson) {
|
||||
await deleteLesson(deletingLesson.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close dialog
|
||||
*/
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<ConfirmDialog
|
||||
isOpen={isOpen}
|
||||
isLoading={isLoading}
|
||||
onClose={isConfirmed => handleDelete(isConfirmed)}
|
||||
title='Confirm deleting'
|
||||
message={`Lesson with will be removed from the course. Attached materials will also be removed.`}
|
||||
confirmMessage='Delete'
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeleteLessonDialog;
|
||||
3
src/components/Dialogs/DeleteLessonDialog/index.ts
Normal file
3
src/components/Dialogs/DeleteLessonDialog/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import DeleteLessonDialog from './DeleteLessonDialog';
|
||||
|
||||
export default DeleteLessonDialog;
|
||||
3
src/hooks/queries/index.ts
Normal file
3
src/hooks/queries/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { useDeleteLesson } from './useDeleteLesson';
|
||||
|
||||
export { useDeleteLesson };
|
||||
33
src/hooks/queries/useDeleteLesson.ts
Normal file
33
src/hooks/queries/useDeleteLesson.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { useToast } from '@chakra-ui/toast';
|
||||
import { ILesson } from '@educt/interfaces';
|
||||
import { useErrorHandler } from 'react-error-boundary';
|
||||
import useAsync from '../useAsync';
|
||||
import { useRootStore } from '../useRootStore';
|
||||
|
||||
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' });
|
||||
return result.data;
|
||||
} catch (error: any) {
|
||||
if (error.response) {
|
||||
console.error('Error: ', error);
|
||||
} else {
|
||||
handleError(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const { execute, ...state } = useAsync<ILesson | undefined, Parameters<typeof deleteLesson>>(deleteLesson);
|
||||
|
||||
return { deleteLesson: execute, ...state };
|
||||
};
|
||||
|
||||
export { useDeleteLesson };
|
||||
@ -2,11 +2,11 @@
|
||||
* Types
|
||||
*/
|
||||
import { ICourse } from '@educt/interfaces';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
/**
|
||||
* Hooks
|
||||
*/
|
||||
import { useEffect } from 'react';
|
||||
import useAsync from './useAsync';
|
||||
import { useRootStore } from './useRootStore';
|
||||
|
||||
@ -14,11 +14,11 @@ type QueryResponseDataType = Omit<ICourse, 'students_count' | 'likes_count' | 'l
|
||||
|
||||
const useFetchCourseQuery = (id: string) => {
|
||||
const {
|
||||
courseStore: { courseService },
|
||||
pageStore: { editCourseStore },
|
||||
} = useRootStore();
|
||||
|
||||
const fetch = async (id: string) => {
|
||||
const result = await courseService.fetchById(id);
|
||||
const result = await editCourseStore.loadCourseById(id);
|
||||
return result.data;
|
||||
};
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useToast } from '@chakra-ui/react';
|
||||
import { IApiRespose, IToken } from '@educt/interfaces';
|
||||
import { IToken } from '@educt/interfaces';
|
||||
import { useErrorHandler } from 'react-error-boundary';
|
||||
import useAsync from './useAsync';
|
||||
import { useRootStore } from './useRootStore';
|
||||
|
||||
@ -1,16 +1,21 @@
|
||||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
import { observer } from 'mobx-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 { Flex, Box, Text, IconButton, Icon, Button } from '@chakra-ui/react';
|
||||
import { DeleteIcon, DragHandleIcon } from '@chakra-ui/icons';
|
||||
import moment from 'moment';
|
||||
|
||||
/**
|
||||
* Types
|
||||
*/
|
||||
import type { ICourse, ILesson } from '@educt/interfaces';
|
||||
|
||||
/**
|
||||
* Components
|
||||
*/
|
||||
import DeleteLessonDialog from '@educt/components/Dialogs/DeleteLessonDialog';
|
||||
|
||||
/**
|
||||
* Hooks
|
||||
*/
|
||||
@ -19,6 +24,12 @@ 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';
|
||||
|
||||
/**
|
||||
* Contexts
|
||||
*/
|
||||
import { useErrorHandler } from 'react-error-boundary';
|
||||
|
||||
type LessonListPropsType = {
|
||||
course: Omit<ICourse, 'students_count' | 'likes_count' | 'lessons_count'>;
|
||||
@ -38,26 +49,40 @@ const CreateLessonButton: React.FC<{ id: string }> = ({ id }) => {
|
||||
|
||||
const LessonList: React.FC<LessonListPropsType> = ({ course }) => {
|
||||
const history = useHistory();
|
||||
const { lessonService } = useRootStore();
|
||||
const [lessons, setLessons] = useState<ILesson[]>(course.lessons);
|
||||
const {
|
||||
lessonService,
|
||||
pageStore: { editCourseStore },
|
||||
} = useRootStore();
|
||||
const { onOpen: onOpenDeleteDialog, onClose: onCloseDeleteDialog, isOpen: isOpenDeleteDialog } = useDisclosure();
|
||||
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 = (id: string): void => undefined;
|
||||
const handleDeleteLesson = (lesson: ILesson): void => {
|
||||
editCourseStore.setDeletingLesson(lesson);
|
||||
onOpenDeleteDialog();
|
||||
};
|
||||
|
||||
const handleChangeOrder = async (ids: string[]) => {
|
||||
try {
|
||||
const data = await lessonService.saveOrder(ids);
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} catch (error: any) {
|
||||
if (error.response) {
|
||||
console.error(error);
|
||||
} else {
|
||||
handleError(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleDragEnd = (result: DropResult) => {
|
||||
if (!result.destination) {
|
||||
return;
|
||||
}
|
||||
|
||||
setLessons(helpers.arrayMove(lessons, result.source.index, result.destination.index));
|
||||
editCourseStore.reorderLessons(result.source.index, result.destination.index);
|
||||
};
|
||||
|
||||
const LessonItem = React.useMemo(() => {
|
||||
@ -117,7 +142,13 @@ const LessonList: React.FC<LessonListPropsType> = ({ course }) => {
|
||||
<Button size='md' p='0 60px' mr='1'>
|
||||
Edit
|
||||
</Button>
|
||||
<IconButton aria-label='Delete lesson' variant='ghost' colorScheme='red' icon={<DeleteIcon />} />
|
||||
<IconButton
|
||||
onClick={() => handleDeleteLesson(lesson)}
|
||||
aria-label='Delete lesson'
|
||||
variant='ghost'
|
||||
colorScheme='red'
|
||||
icon={<DeleteIcon />}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex justifyContent='flex-end' mt={{ base: '2' }}>
|
||||
<Text as='span' color='gray.500' mr='3'>
|
||||
@ -165,6 +196,7 @@ const LessonList: React.FC<LessonListPropsType> = ({ course }) => {
|
||||
<Box style={{ overflow: 'auto' }}>
|
||||
{lessons.length !== 0 ? (
|
||||
<>
|
||||
<DeleteLessonDialog isOpen={isOpenDeleteDialog} onClose={onCloseDeleteDialog} />
|
||||
<Flex mt='2' mb='3' padding='0 20px' alignItems='center' justifyContent='space-between'>
|
||||
<Text fontWeight='medium'>Total: ({lessons.length})</Text>
|
||||
<Button onClick={handleCreateLesson}>Create new</Button>
|
||||
@ -219,4 +251,4 @@ const LessonList: React.FC<LessonListPropsType> = ({ course }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default LessonList;
|
||||
export default observer(LessonList);
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
import Helmet from 'react-helmet';
|
||||
import { observer } from 'mobx-react';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
import { Box, Heading, Text, Tab, TabList, Flex, Tabs, TabPanels, TabPanel } from '@chakra-ui/react';
|
||||
|
||||
@ -22,6 +21,7 @@ import { StudentList, StudentItem } from './components/StudentList';
|
||||
* Hooks
|
||||
*/
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useRootStore } from '@educt/hooks/useRootStore';
|
||||
import useFetchCourseQuery from '@educt/hooks/useFetchCourseQuery';
|
||||
|
||||
/**
|
||||
@ -29,7 +29,12 @@ import useFetchCourseQuery from '@educt/hooks/useFetchCourseQuery';
|
||||
*/
|
||||
const EditCoursePage: React.FC<IPageProps> = () => {
|
||||
const params = useParams<{ id: string }>();
|
||||
const { data: course, error, isLoading } = useFetchCourseQuery(params.id);
|
||||
const {
|
||||
pageStore: {
|
||||
editCourseStore: { course },
|
||||
},
|
||||
} = useRootStore();
|
||||
const { error, isLoading } = useFetchCourseQuery(params.id);
|
||||
|
||||
/**
|
||||
* Not Found
|
||||
@ -41,7 +46,7 @@ const EditCoursePage: React.FC<IPageProps> = () => {
|
||||
/**
|
||||
* Loading
|
||||
*/
|
||||
if (isLoading || !course) return <LoadingPage />;
|
||||
if (!course || isLoading) return <LoadingPage />;
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -92,4 +97,4 @@ const EditCoursePage: React.FC<IPageProps> = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default observer(EditCoursePage);
|
||||
export default EditCoursePage;
|
||||
|
||||
@ -21,7 +21,7 @@ import CreateUserForm from './components/CreateUserForm';
|
||||
import { useRootStore } from '@educt/hooks/useRootStore';
|
||||
|
||||
/**
|
||||
* Context
|
||||
* Context Providers
|
||||
*/
|
||||
import { UsersPageContextProvider } from '@educt/providers';
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { IApiRespose } from '@educt/interfaces';
|
||||
import { IApiRespose, ILesson } from '@educt/interfaces';
|
||||
import { AxiosInstance } from 'axios';
|
||||
|
||||
export default class LessonService {
|
||||
@ -8,6 +8,17 @@ export default class LessonService {
|
||||
this.api = api;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete lesson by id
|
||||
*
|
||||
* @param id Lesson id
|
||||
* @returns Deleted lesson
|
||||
*/
|
||||
public async deleteLesson(id: string): Promise<IApiRespose<ILesson>> {
|
||||
const result = await this.api.delete(`/v1/lessons/${id}`);
|
||||
return result.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save order of lessons
|
||||
*
|
||||
|
||||
21
src/stores/PageStore.ts
Normal file
21
src/stores/PageStore.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { AxiosInstance } from 'axios';
|
||||
import { makeAutoObservable } from 'mobx';
|
||||
import EditCourseStore from './pages/EditCourseStore';
|
||||
import RootStore from './RootStore';
|
||||
|
||||
export default class PageStore {
|
||||
public root: RootStore;
|
||||
|
||||
public editCourseStore: EditCourseStore;
|
||||
|
||||
constructor(root: RootStore, api: AxiosInstance) {
|
||||
this.root = root;
|
||||
|
||||
/**
|
||||
* Pages
|
||||
*/
|
||||
this.editCourseStore = new EditCourseStore(this, api);
|
||||
|
||||
makeAutoObservable(this);
|
||||
}
|
||||
}
|
||||
@ -15,8 +15,11 @@ import UIStore from './UIStore';
|
||||
import CategoryStore from './CategoryStore';
|
||||
import CourseStore from './CourseStore';
|
||||
import LessonService from '@educt/services/LessonService';
|
||||
import PageStore from './PageStore';
|
||||
|
||||
export default class RootStore {
|
||||
public pageStore: PageStore;
|
||||
|
||||
public courseStore: CourseStore;
|
||||
|
||||
public categoryStore: CategoryStore;
|
||||
@ -33,15 +36,16 @@ export default class RootStore {
|
||||
|
||||
constructor() {
|
||||
/**
|
||||
* Api service
|
||||
* Services
|
||||
*/
|
||||
// TODO add other services
|
||||
this.apiService = new ApiService(this, apiConfig);
|
||||
|
||||
this.lessonService = new LessonService(this.apiService.api);
|
||||
|
||||
/**
|
||||
* Stores
|
||||
*/
|
||||
this.pageStore = new PageStore(this, this.apiService.api);
|
||||
this.courseStore = new CourseStore(this, this.apiService.api);
|
||||
this.categoryStore = new CategoryStore(this, this.apiService.api);
|
||||
this.uiStore = new UIStore(this);
|
||||
|
||||
82
src/stores/pages/EditCourseStore.ts
Normal file
82
src/stores/pages/EditCourseStore.ts
Normal file
@ -0,0 +1,82 @@
|
||||
import * as helpers from '@educt/helpers';
|
||||
import { ICourse, ILesson } from '@educt/interfaces';
|
||||
import { CourseService } from '@educt/services/CourseService';
|
||||
import LessonService from '@educt/services/LessonService';
|
||||
import { AxiosInstance } from 'axios';
|
||||
import { makeAutoObservable, runInAction } from 'mobx';
|
||||
import PageStore from '../PageStore';
|
||||
|
||||
export default class EditCourseStore {
|
||||
public pageStore: PageStore;
|
||||
|
||||
public course: Omit<ICourse, 'students_count' | 'likes_count' | 'lessons_count'> | undefined;
|
||||
|
||||
public deletingLesson: ILesson | undefined;
|
||||
|
||||
private courseService: CourseService;
|
||||
|
||||
constructor(pageStore: PageStore, api: AxiosInstance) {
|
||||
this.pageStore = pageStore;
|
||||
this.courseService = new CourseService(api);
|
||||
|
||||
makeAutoObservable(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load course by id into store
|
||||
*
|
||||
* @param id Course id
|
||||
* @returns Course response
|
||||
*/
|
||||
public async loadCourseById(id: string) {
|
||||
try {
|
||||
const result = await this.courseService.fetchById(id);
|
||||
|
||||
runInAction(() => {
|
||||
this.course = result.data;
|
||||
});
|
||||
|
||||
return result;
|
||||
} catch (error: any) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove lesson from store
|
||||
*
|
||||
* @param id Lesson id
|
||||
* @returns Deleted lesson response
|
||||
*/
|
||||
public async deleteLessonById(id: string) {
|
||||
try {
|
||||
const result = await this.pageStore.root.lessonService.deleteLesson(id);
|
||||
|
||||
runInAction(() => {
|
||||
if (this.course) {
|
||||
this.course.lessons = this.course.lessons.filter(lesson => lesson.id !== result.data.id);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
setDeletingLesson(lesson: ILesson) {
|
||||
this.deletingLesson = lesson;
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user