заготовка под создание лекции
This commit is contained in:
parent
9fe0d1e3e9
commit
87a14f0eb1
45
api/app/application/lessons_repository.py
Normal file
45
api/app/application/lessons_repository.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from sqlalchemy import select
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from app.domain.models import Lesson
|
||||||
|
|
||||||
|
|
||||||
|
class LessonsRepository:
|
||||||
|
def __init__(self, db: AsyncSession) -> None:
|
||||||
|
self.db = db
|
||||||
|
|
||||||
|
async def get_all_by_course(self, course_id: int) -> List[Lesson]:
|
||||||
|
query = (
|
||||||
|
select(Lesson)
|
||||||
|
.filter_by(course_id=course_id)
|
||||||
|
.order_by(Lesson.number)
|
||||||
|
)
|
||||||
|
result = await self.db.execute(query)
|
||||||
|
return result.scalars().all()
|
||||||
|
|
||||||
|
async def get_by_id(self, lesson_id: int) -> Optional[Lesson]:
|
||||||
|
query = (
|
||||||
|
select(Lesson)
|
||||||
|
.filter_by(id=lesson_id)
|
||||||
|
)
|
||||||
|
result = await self.db.execute(query)
|
||||||
|
return result.scalars().first()
|
||||||
|
|
||||||
|
async def create(self, lesson: Lesson) -> Lesson:
|
||||||
|
self.db.add(lesson)
|
||||||
|
await self.db.commit()
|
||||||
|
await self.db.refresh(lesson)
|
||||||
|
return lesson
|
||||||
|
|
||||||
|
async def update(self, lesson: Lesson) -> Lesson:
|
||||||
|
await self.db.merge(lesson)
|
||||||
|
await self.db.commit()
|
||||||
|
await self.db.refresh(lesson)
|
||||||
|
return lesson
|
||||||
|
|
||||||
|
async def delete(self, lesson: Lesson) -> Lesson:
|
||||||
|
await self.db.delete(lesson)
|
||||||
|
await self.db.commit()
|
||||||
|
return lesson
|
||||||
@ -30,6 +30,21 @@ async def get_all_courses(
|
|||||||
return await courses_service.get_all()
|
return await courses_service.get_all()
|
||||||
|
|
||||||
|
|
||||||
|
@courses_router.get(
|
||||||
|
'/{course_id}/',
|
||||||
|
response_model=Optional[CourseRead],
|
||||||
|
summary='Return a specific course',
|
||||||
|
description='Return a specific course',
|
||||||
|
)
|
||||||
|
async def get_course(
|
||||||
|
course_id: int,
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
user: User = Depends(require_auth_user),
|
||||||
|
):
|
||||||
|
courses_service = CoursesService(db)
|
||||||
|
return await courses_service.get_by_id(course_id)
|
||||||
|
|
||||||
|
|
||||||
@courses_router.post(
|
@courses_router.post(
|
||||||
'/',
|
'/',
|
||||||
response_model=Optional[CourseCreated],
|
response_model=Optional[CourseCreated],
|
||||||
|
|||||||
90
api/app/controllers/lessons_router.py
Normal file
90
api/app/controllers/lessons_router.py
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends, status
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from app.database.session import get_db
|
||||||
|
from app.domain.entities.lessons import LessonCreate, LessonUpdate, LessonRead
|
||||||
|
from app.domain.models import User
|
||||||
|
from app.infrastructure.dependencies import require_auth_user, require_teacher, require_admin
|
||||||
|
from app.infrastructure.lessons_service import LessonsService
|
||||||
|
|
||||||
|
lessons_router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@lessons_router.get(
|
||||||
|
'/course/{course_id}/',
|
||||||
|
response_model=Optional[List[LessonRead]],
|
||||||
|
summary='Get all lessons by course',
|
||||||
|
description='Get all lessons by course',
|
||||||
|
)
|
||||||
|
async def get_course_lessons(
|
||||||
|
course_id: int,
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
current_user: User = Depends(require_auth_user),
|
||||||
|
):
|
||||||
|
lessons_service = LessonsService(db)
|
||||||
|
return await lessons_service.get_all_by_course(course_id)
|
||||||
|
|
||||||
|
|
||||||
|
@lessons_router.get(
|
||||||
|
'/{lesson_id}/',
|
||||||
|
response_model=Optional[LessonRead],
|
||||||
|
summary='Get lesson by lesson ID',
|
||||||
|
description='Get lesson by lesson ID',
|
||||||
|
)
|
||||||
|
async def get_lesson(
|
||||||
|
lesson_id: int,
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
current_user: User = Depends(require_auth_user),
|
||||||
|
):
|
||||||
|
lessons_service = LessonsService(db)
|
||||||
|
return await lessons_service.get_by_id(lesson_id)
|
||||||
|
|
||||||
|
|
||||||
|
@lessons_router.post(
|
||||||
|
'/{course_id}/',
|
||||||
|
response_model=Optional[LessonRead],
|
||||||
|
summary='Create a new lesson',
|
||||||
|
description='Create a new lesson',
|
||||||
|
)
|
||||||
|
async def create_lesson(
|
||||||
|
course_id: int,
|
||||||
|
lesson_data: LessonCreate,
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
current_user: User = Depends(require_teacher),
|
||||||
|
):
|
||||||
|
lessons_service = LessonsService(db)
|
||||||
|
return await lessons_service.create(lesson_data, current_user, course_id)
|
||||||
|
|
||||||
|
|
||||||
|
@lessons_router.put(
|
||||||
|
'/{lesson_id}/',
|
||||||
|
response_model=Optional[LessonRead],
|
||||||
|
summary='Update a lesson',
|
||||||
|
description='Update a lesson',
|
||||||
|
)
|
||||||
|
async def update_lesson(
|
||||||
|
lesson_id: int,
|
||||||
|
lesson_data: LessonUpdate,
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
current_user: User = Depends(require_teacher),
|
||||||
|
):
|
||||||
|
lessons_service = LessonsService(db)
|
||||||
|
return await lessons_service.update(lesson_id, lesson_data, current_user)
|
||||||
|
|
||||||
|
|
||||||
|
@lessons_router.delete(
|
||||||
|
'/{lesson_id}',
|
||||||
|
response_model=Optional[LessonRead],
|
||||||
|
summary='Delete a lesson',
|
||||||
|
description='Delete a lesson',
|
||||||
|
)
|
||||||
|
async def delete_lesson(
|
||||||
|
lesson_id: int,
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
current_user: User = Depends(require_teacher),
|
||||||
|
):
|
||||||
|
lessons_service = LessonsService(db)
|
||||||
|
await lessons_service.delete(lesson_id, current_user)
|
||||||
|
return None
|
||||||
29
api/app/domain/entities/lessons.py
Normal file
29
api/app/domain/entities/lessons.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
from typing import Optional
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
|
class LessonBase(BaseModel):
|
||||||
|
title: str = Field(..., max_length=250)
|
||||||
|
description: Optional[str] = None
|
||||||
|
text: Optional[str] = None
|
||||||
|
number: int = Field(..., ge=1)
|
||||||
|
|
||||||
|
|
||||||
|
class LessonCreate(LessonBase):
|
||||||
|
course_id: int
|
||||||
|
|
||||||
|
|
||||||
|
class LessonUpdate(BaseModel):
|
||||||
|
title: Optional[str] = Field(None, max_length=250)
|
||||||
|
description: Optional[str] = None
|
||||||
|
text: Optional[str] = None
|
||||||
|
number: Optional[int] = Field(None, ge=1)
|
||||||
|
|
||||||
|
|
||||||
|
class LessonRead(LessonBase):
|
||||||
|
id: int
|
||||||
|
course_id: int
|
||||||
|
creator_id: int
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
@ -22,6 +22,13 @@ class CoursesService:
|
|||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
async def get_by_id(self, course_id: int) -> Optional[CourseRead]:
|
||||||
|
course = await self.courses_repository.get_by_id(course_id)
|
||||||
|
if course is None:
|
||||||
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Курс с таким Id не найден')
|
||||||
|
|
||||||
|
return CourseRead.model_validate(course)
|
||||||
|
|
||||||
async def create(self, course: CourseCreate) -> CourseCreated: # ← возвращаем CourseCreated
|
async def create(self, course: CourseCreate) -> CourseCreated: # ← возвращаем CourseCreated
|
||||||
course_model = Course(
|
course_model = Course(
|
||||||
title=course.title,
|
title=course.title,
|
||||||
|
|||||||
@ -48,6 +48,7 @@ def require_admin(user: User = Depends(require_auth_user)):
|
|||||||
|
|
||||||
|
|
||||||
def require_teacher(user: User = Depends(require_auth_user)):
|
def require_teacher(user: User = Depends(require_auth_user)):
|
||||||
|
print(user.role.title, user.role.title not in [UserRoles.TEACHER, Settings().root_role_name], [UserRoles.TEACHER, Settings().root_role_name])
|
||||||
if user.role.title not in [UserRoles.TEACHER, Settings().root_role_name]:
|
if user.role.title not in [UserRoles.TEACHER, Settings().root_role_name]:
|
||||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail='Ошибка доступа')
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail='Ошибка доступа')
|
||||||
|
|
||||||
|
|||||||
94
api/app/infrastructure/lessons_service.py
Normal file
94
api/app/infrastructure/lessons_service.py
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
from fastapi import HTTPException, status
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from app.application.courses_repository import CoursesRepository
|
||||||
|
from app.application.lessons_repository import LessonsRepository
|
||||||
|
from app.domain.entities.lessons import LessonCreate, LessonUpdate, LessonRead
|
||||||
|
from app.domain.models import Lesson, User
|
||||||
|
from app.settings import Settings
|
||||||
|
|
||||||
|
|
||||||
|
class LessonsService:
|
||||||
|
def __init__(self, db: AsyncSession):
|
||||||
|
self.lessons_repository = LessonsRepository(db)
|
||||||
|
self.courses_repository = CoursesRepository(db)
|
||||||
|
self.settings = Settings()
|
||||||
|
|
||||||
|
async def get_all_by_course(self, course_id: int) -> List[LessonRead]:
|
||||||
|
lessons = await self.lessons_repository.get_all_by_course(course_id)
|
||||||
|
response = []
|
||||||
|
|
||||||
|
for lesson in lessons:
|
||||||
|
response.append(LessonRead.model_validate(lesson))
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
async def get_by_id(self, lesson_id: int) -> LessonRead:
|
||||||
|
lesson = await self.lessons_repository.get_by_id(lesson_id)
|
||||||
|
if not lesson:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Урок не найден"
|
||||||
|
)
|
||||||
|
return LessonRead.model_validate(lesson)
|
||||||
|
|
||||||
|
async def create(self, lesson_data: LessonCreate, creator: User, course_id) -> LessonRead:
|
||||||
|
course_model = await self.courses_repository.get_by_id(course_id)
|
||||||
|
if not course_model:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Курс не найден"
|
||||||
|
)
|
||||||
|
|
||||||
|
lesson = Lesson(
|
||||||
|
title=lesson_data.title,
|
||||||
|
description=lesson_data.description,
|
||||||
|
text=lesson_data.text,
|
||||||
|
number=lesson_data.number,
|
||||||
|
course_id=course_id,
|
||||||
|
creator_id=creator.id
|
||||||
|
)
|
||||||
|
|
||||||
|
created_lesson = await self.lessons_repository.create(lesson)
|
||||||
|
return LessonRead.model_validate(created_lesson)
|
||||||
|
|
||||||
|
async def update(self, lesson_id: int, lesson_data: LessonUpdate, current_user: User) -> Optional[LessonRead]:
|
||||||
|
lesson = await self.lessons_repository.get_by_id(lesson_id)
|
||||||
|
if not lesson:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Урок не найден"
|
||||||
|
)
|
||||||
|
|
||||||
|
is_admin = current_user.role.title == self.settings.root_role_name
|
||||||
|
if lesson.creator_id != current_user.id and not is_admin:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
|
detail="Доступ запрещён"
|
||||||
|
)
|
||||||
|
|
||||||
|
update_dict = lesson_data.dict(exclude_unset=True)
|
||||||
|
for key, value in update_dict.items():
|
||||||
|
setattr(lesson, key, value)
|
||||||
|
|
||||||
|
updated_lesson = await self.lessons_repository.update(lesson)
|
||||||
|
return LessonRead.model_validate(updated_lesson)
|
||||||
|
|
||||||
|
async def delete(self, lesson_id: int, current_user: User) -> None:
|
||||||
|
lesson = await self.lessons_repository.get_by_id(lesson_id)
|
||||||
|
if not lesson:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Урок не найден"
|
||||||
|
)
|
||||||
|
|
||||||
|
is_admin = current_user.role.title == self.settings.root_role_name
|
||||||
|
if lesson.creator_id != current_user.id and not is_admin:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
|
detail="Доступ запрещён"
|
||||||
|
)
|
||||||
|
|
||||||
|
await self.lessons_repository.delete(lesson)
|
||||||
@ -3,6 +3,7 @@ from starlette.middleware.cors import CORSMiddleware
|
|||||||
|
|
||||||
from app.controllers.auth_router import auth_router
|
from app.controllers.auth_router import auth_router
|
||||||
from app.controllers.courses_router import courses_router
|
from app.controllers.courses_router import courses_router
|
||||||
|
from app.controllers.lessons_router import lessons_router
|
||||||
from app.controllers.register_router import register_router
|
from app.controllers.register_router import register_router
|
||||||
from app.controllers.roles_router import roles_router
|
from app.controllers.roles_router import roles_router
|
||||||
from app.controllers.statuses_router import statuses_router
|
from app.controllers.statuses_router import statuses_router
|
||||||
@ -24,6 +25,7 @@ def start_app():
|
|||||||
|
|
||||||
api_app.include_router(auth_router, prefix=f'{settings.prefix}/auth', tags=['auth'])
|
api_app.include_router(auth_router, prefix=f'{settings.prefix}/auth', tags=['auth'])
|
||||||
api_app.include_router(courses_router, prefix=f'{settings.prefix}/courses', tags=['courses'])
|
api_app.include_router(courses_router, prefix=f'{settings.prefix}/courses', tags=['courses'])
|
||||||
|
api_app.include_router(lessons_router, prefix=f'{settings.prefix}/lessons', tags=['lessons'])
|
||||||
api_app.include_router(register_router, prefix=f'{settings.prefix}/register', tags=['register'])
|
api_app.include_router(register_router, prefix=f'{settings.prefix}/register', tags=['register'])
|
||||||
api_app.include_router(roles_router, prefix=f'{settings.prefix}/roles', tags=['roles'])
|
api_app.include_router(roles_router, prefix=f'{settings.prefix}/roles', tags=['roles'])
|
||||||
api_app.include_router(statuses_router, prefix=f'{settings.prefix}/statuses', tags=['statuses'])
|
api_app.include_router(statuses_router, prefix=f'{settings.prefix}/statuses', tags=['statuses'])
|
||||||
|
|||||||
20
web/package-lock.json
generated
20
web/package-lock.json
generated
@ -12,6 +12,7 @@
|
|||||||
"@reduxjs/toolkit": "^2.11.0",
|
"@reduxjs/toolkit": "^2.11.0",
|
||||||
"antd": "^6.0.0",
|
"antd": "^6.0.0",
|
||||||
"dayjs": "^1.11.19",
|
"dayjs": "^1.11.19",
|
||||||
|
"jodit-react": "^5.2.38",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"react-redux": "^9.2.0",
|
"react-redux": "^9.2.0",
|
||||||
@ -3128,6 +3129,25 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/jodit": {
|
||||||
|
"version": "4.7.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/jodit/-/jodit-4.7.9.tgz",
|
||||||
|
"integrity": "sha512-dPlewnu2+vVXELh9BEJTNQoSBL3Cf2E0fNh30yjSEgUtJHYSUYToDjMDEqr0T/L9iNpqPTODy2b4pzyGpPRUog==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/jodit-react": {
|
||||||
|
"version": "5.2.38",
|
||||||
|
"resolved": "https://registry.npmjs.org/jodit-react/-/jodit-react-5.2.38.tgz",
|
||||||
|
"integrity": "sha512-k98vjch0JWX13Dlf7tWv3wPo6dgO9bpi0NUBC6YrpGLYtRJx8d5Ttz5TLWuydEReedWWyNKobSGIGYQDihP8Vw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"jodit": "^4.7.9"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "~0.14 || ^15 || ^16 || ^17 || ^18 || ^19",
|
||||||
|
"react-dom": "~0.14 || ^15 || ^16 || ^17 || ^18 || ^19"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/js-tokens": {
|
"node_modules/js-tokens": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
|
|||||||
@ -14,6 +14,7 @@
|
|||||||
"@reduxjs/toolkit": "^2.11.0",
|
"@reduxjs/toolkit": "^2.11.0",
|
||||||
"antd": "^6.0.0",
|
"antd": "^6.0.0",
|
||||||
"dayjs": "^1.11.19",
|
"dayjs": "^1.11.19",
|
||||||
|
"jodit-react": "^5.2.38",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"react-redux": "^9.2.0",
|
"react-redux": "^9.2.0",
|
||||||
|
|||||||
@ -14,6 +14,13 @@ export const coursesApi = createApi({
|
|||||||
}),
|
}),
|
||||||
providesTags: ['course'],
|
providesTags: ['course'],
|
||||||
}),
|
}),
|
||||||
|
getCourseById: builder.query({
|
||||||
|
query: (courseId) => ({
|
||||||
|
url: `/courses/${courseId}/`,
|
||||||
|
method: "GET",
|
||||||
|
}),
|
||||||
|
providesTags: ['course'],
|
||||||
|
}),
|
||||||
createCourse: builder.mutation({
|
createCourse: builder.mutation({
|
||||||
query: (data) => ({
|
query: (data) => ({
|
||||||
url: "/courses/",
|
url: "/courses/",
|
||||||
@ -65,6 +72,7 @@ export const coursesApi = createApi({
|
|||||||
|
|
||||||
export const {
|
export const {
|
||||||
useGetAllCoursesQuery,
|
useGetAllCoursesQuery,
|
||||||
|
useGetCourseByIdQuery,
|
||||||
useCreateCourseMutation,
|
useCreateCourseMutation,
|
||||||
useUpdateCourseMutation,
|
useUpdateCourseMutation,
|
||||||
useGetCourseTeachersQuery,
|
useGetCourseTeachersQuery,
|
||||||
|
|||||||
56
web/src/Api/lessonsApi.js
Normal file
56
web/src/Api/lessonsApi.js
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import {createApi} from "@reduxjs/toolkit/query/react";
|
||||||
|
import {baseQueryWithAuth} from "./baseQuery.js";
|
||||||
|
|
||||||
|
|
||||||
|
export const lessonsApi = createApi({
|
||||||
|
reducerPath: "lessonsApi",
|
||||||
|
baseQuery: baseQueryWithAuth,
|
||||||
|
tagTypes: ["lesson"],
|
||||||
|
endpoints: (builder) => ({
|
||||||
|
getLessonsByCourseId: builder.query({
|
||||||
|
query: (courseId) => ({
|
||||||
|
url: `/lessons/course/${courseId}/`,
|
||||||
|
method: "GET",
|
||||||
|
}),
|
||||||
|
providesTags: ["lesson"],
|
||||||
|
}),
|
||||||
|
getLessonById: builder.query({
|
||||||
|
query: (lessonId) => ({
|
||||||
|
url: `/lessons/${lessonId}/`,
|
||||||
|
method: "GET",
|
||||||
|
}),
|
||||||
|
providesTags: ["lesson"],
|
||||||
|
}),
|
||||||
|
createLesson: builder.mutation({
|
||||||
|
query: ({courseId, lessonData}) => ({
|
||||||
|
url: `/lessons/${courseId}/`,
|
||||||
|
method: "POST",
|
||||||
|
body: lessonData,
|
||||||
|
}),
|
||||||
|
invalidatesTags: ["lesson"],
|
||||||
|
}),
|
||||||
|
updateLesson: builder.mutation({
|
||||||
|
query: ({lessonId, lessonData}) => ({
|
||||||
|
url: `/lessons/${lessonId}/`,
|
||||||
|
method: "PUT",
|
||||||
|
body: lessonData,
|
||||||
|
}),
|
||||||
|
invalidatesTags: ["lesson"],
|
||||||
|
}),
|
||||||
|
deleteLesson: builder.mutation({
|
||||||
|
query: (lessonId) => ({
|
||||||
|
url: `/lessons/${lessonId}/`,
|
||||||
|
method: "DELETE",
|
||||||
|
}),
|
||||||
|
invalidatesTags: ["lesson"],
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const {
|
||||||
|
useGetLessonsByCourseIdQuery,
|
||||||
|
useGetLessonByIdQuery,
|
||||||
|
useCreateLessonMutation,
|
||||||
|
useUpdateLessonMutation,
|
||||||
|
useDeleteLessonMutation,
|
||||||
|
} = lessonsApi;
|
||||||
@ -2,10 +2,11 @@ import {Routes, Route, Navigate} from "react-router-dom";
|
|||||||
import PrivateRoute from "./PrivateRoute.jsx";
|
import PrivateRoute from "./PrivateRoute.jsx";
|
||||||
import AdminRoute from "./AdminRoute.jsx";
|
import AdminRoute from "./AdminRoute.jsx";
|
||||||
import LoginPage from "../Components/Pages/LoginPage/LoginPage.jsx";
|
import LoginPage from "../Components/Pages/LoginPage/LoginPage.jsx";
|
||||||
import CoursesPage from "../Components/Pages/Courses/CoursesPage.jsx";
|
import CoursesPage from "../Components/Pages/CoursesPage/CoursesPage.jsx";
|
||||||
import MainLayout from "../Components/Layouts/MainLayout.jsx";
|
import MainLayout from "../Components/Layouts/MainLayout.jsx";
|
||||||
import ProfilePage from "../Components/Pages/ProfilePage/ProfilePage.jsx";
|
import ProfilePage from "../Components/Pages/ProfilePage/ProfilePage.jsx";
|
||||||
import AdminPage from "../Components/Pages/AdminPage/AdminPage.jsx";
|
import AdminPage from "../Components/Pages/AdminPage/AdminPage.jsx";
|
||||||
|
import CourseDetailPage from "../Components/Pages/CourseDetailPage/CourseDetailPage.jsx";
|
||||||
|
|
||||||
|
|
||||||
const AppRouter = () => (
|
const AppRouter = () => (
|
||||||
@ -16,6 +17,7 @@ const AppRouter = () => (
|
|||||||
<Route element={<MainLayout/>}>
|
<Route element={<MainLayout/>}>
|
||||||
<Route path={"/courses"} element={<CoursesPage/>}/>
|
<Route path={"/courses"} element={<CoursesPage/>}/>
|
||||||
<Route path={"/profile"} element={<ProfilePage/>}/>
|
<Route path={"/profile"} element={<ProfilePage/>}/>
|
||||||
|
<Route path="/courses/:courseId" element={<CourseDetailPage />} />
|
||||||
<Route path={"*"} element={<Navigate to={"/courses"}/>}/>
|
<Route path={"*"} element={<Navigate to={"/courses"}/>}/>
|
||||||
</Route>
|
</Route>
|
||||||
</Route>
|
</Route>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import useMainLayout from "./useMainLayout.js";
|
import useMainLayout from "./useMainLayout.js";
|
||||||
import {Layout, Menu} from "antd";
|
import {Layout, Menu} from "antd";
|
||||||
import CoursesPage from "../Pages/Courses/CoursesPage.jsx";
|
import CoursesPage from "../Pages/CoursesPage/CoursesPage.jsx";
|
||||||
import LoadingIndicator from "../Widgets/LoadingIndicator/LoadingIndicator.jsx";
|
import LoadingIndicator from "../Widgets/LoadingIndicator/LoadingIndicator.jsx";
|
||||||
import {Outlet} from "react-router-dom";
|
import {Outlet} from "react-router-dom";
|
||||||
import {BookOutlined, ControlOutlined, LogoutOutlined, UserOutlined} from "@ant-design/icons";
|
import {BookOutlined, ControlOutlined, LogoutOutlined, UserOutlined} from "@ant-design/icons";
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import {useGetAllUsersQuery, useGetAuthenticatedUserDataQuery} from "../../../Api/usersApi.js";
|
import {useGetAllUsersQuery, useGetAuthenticatedUserDataQuery} from "../../../Api/usersApi.js";
|
||||||
import {useGetAllRolesQuery} from "../../../Api/rolesApi.js";
|
import {useGetAllRolesQuery} from "../../../Api/rolesApi.js";
|
||||||
import {useMemo, useState} from "react";
|
import {useEffect, useMemo, useState} from "react";
|
||||||
import {useDispatch} from "react-redux";
|
import {useDispatch} from "react-redux";
|
||||||
import {setOpenModalCreateUser, setSelectedUserToUpdate} from "../../../Redux/Slices/usersSlice.js";
|
import {setOpenModalCreateUser, setSelectedUserToUpdate} from "../../../Redux/Slices/usersSlice.js";
|
||||||
|
|
||||||
@ -18,6 +18,10 @@ const useAdminPage = () => {
|
|||||||
setSearchString,
|
setSearchString,
|
||||||
] = useState("");
|
] = useState("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.document.title = "Система обучения lectio - Панель администратора";
|
||||||
|
}, []);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: usersData = [],
|
data: usersData = [],
|
||||||
isLoading: usersIsLoading,
|
isLoading: usersIsLoading,
|
||||||
|
|||||||
@ -0,0 +1,77 @@
|
|||||||
|
import useCreateLessonModalForm from "./useCreateLessonModalForm.js";
|
||||||
|
import {Button, Form, Input, InputNumber, Modal, Upload} from "antd";
|
||||||
|
import JoditEditor from "jodit-react";
|
||||||
|
import {UploadOutlined} from "@ant-design/icons";
|
||||||
|
|
||||||
|
|
||||||
|
const CreateLessonModalForm = () => {
|
||||||
|
const {
|
||||||
|
isModalOpen,
|
||||||
|
handleCancel,
|
||||||
|
form,
|
||||||
|
joditConfig,
|
||||||
|
editorRef,
|
||||||
|
} = useCreateLessonModalForm();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
open={isModalOpen}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
footer={null}
|
||||||
|
title={"Создание лекционного материала"}
|
||||||
|
style={{
|
||||||
|
minWidth: "70%",
|
||||||
|
minHeight: "80%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
name={"lesson"}
|
||||||
|
form={form}
|
||||||
|
layout={"vertical"}
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
name="title"
|
||||||
|
label="Название"
|
||||||
|
rules={[{required: true, message: "Пожалуйста, введите название"}]}
|
||||||
|
>
|
||||||
|
<Input/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="description"
|
||||||
|
label="Описание"
|
||||||
|
>
|
||||||
|
<Input/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="text"
|
||||||
|
label="Текстовый материал"
|
||||||
|
>
|
||||||
|
<div className="jodit-container">
|
||||||
|
<JoditEditor
|
||||||
|
ref={editorRef}
|
||||||
|
config={joditConfig}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="number"
|
||||||
|
label="Порядковый номер отображения"
|
||||||
|
>
|
||||||
|
<InputNumber min={1} defaultValue={1}/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name="files" label="Прикрепить файлы">
|
||||||
|
<Upload
|
||||||
|
multiple
|
||||||
|
>
|
||||||
|
<Button icon={<UploadOutlined/>}>Выбрать файлы</Button>
|
||||||
|
</Upload>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item>
|
||||||
|
<Button type="primary" htmlType="submit">Сохранить</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreateLessonModalForm;
|
||||||
@ -0,0 +1,95 @@
|
|||||||
|
import {useDispatch, useSelector} from "react-redux";
|
||||||
|
import {setOpenModalCreateLesson} from "../../../../../Redux/Slices/lessonsSlice.js";
|
||||||
|
import {Form, notification} from "antd";
|
||||||
|
import {useMemo, useRef} from "react";
|
||||||
|
|
||||||
|
|
||||||
|
const useCreateLessonModalForm = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const {
|
||||||
|
openModalCreateLesson
|
||||||
|
} = useSelector((state) => state.lessons);
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
const isModalOpen = openModalCreateLesson;
|
||||||
|
const editorRef = useRef(null);
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
dispatch(setOpenModalCreateLesson(false));
|
||||||
|
};
|
||||||
|
|
||||||
|
const joditConfig = useMemo(
|
||||||
|
() => ({
|
||||||
|
readonly: false,
|
||||||
|
height: 150,
|
||||||
|
toolbarAdaptive: false,
|
||||||
|
buttons: [
|
||||||
|
"bold", "italic", "underline", "strikethrough", "|",
|
||||||
|
"superscript", "subscript", "|",
|
||||||
|
"ul", "ol", "outdent", "indent", "|",
|
||||||
|
"font", "fontsize", "brush", "paragraph", "|",
|
||||||
|
"align", "hr", "|",
|
||||||
|
"table", "link", "image", "video", "symbols", "|",
|
||||||
|
"undo", "redo", "cut", "copy", "paste", "selectall", "eraser", "|",
|
||||||
|
"find", "source", "fullsize", "print", "preview",
|
||||||
|
],
|
||||||
|
autofocus: false,
|
||||||
|
preserveSelection: true,
|
||||||
|
askBeforePasteHTML: false,
|
||||||
|
askBeforePasteFromWord: false,
|
||||||
|
defaultActionOnPaste: "insert_clear_html",
|
||||||
|
spellcheck: true,
|
||||||
|
placeholder: "Введите результаты приёма...",
|
||||||
|
showCharsCounter: true,
|
||||||
|
showWordsCounter: true,
|
||||||
|
showXPathInStatusbar: false,
|
||||||
|
toolbarSticky: true,
|
||||||
|
toolbarButtonSize: "middle",
|
||||||
|
cleanHTML: {
|
||||||
|
removeEmptyElements: true,
|
||||||
|
replaceNBSP: false,
|
||||||
|
},
|
||||||
|
hotkeys: {
|
||||||
|
"ctrl + shift + f": "find",
|
||||||
|
"ctrl + b": "bold",
|
||||||
|
"ctrl + i": "italic",
|
||||||
|
"ctrl + u": "underline",
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
editSrc: true,
|
||||||
|
editTitle: true,
|
||||||
|
editAlt: true,
|
||||||
|
openOnDblClick: false,
|
||||||
|
},
|
||||||
|
video: {
|
||||||
|
allowedSources: ["youtube", "vimeo"],
|
||||||
|
},
|
||||||
|
uploader: {
|
||||||
|
insertImageAsBase64URI: true,
|
||||||
|
},
|
||||||
|
paste: {
|
||||||
|
insertAsBase64: true,
|
||||||
|
mimeTypes: ["image/png", "image/jpeg", "image/gif"],
|
||||||
|
maxFileSize: 5 * 1024 * 1024,
|
||||||
|
error: () => {
|
||||||
|
notification.error({
|
||||||
|
title: "Ошибка вставки",
|
||||||
|
description: "Файл слишком большой или неподдерживаемый формат.",
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
isModalOpen,
|
||||||
|
handleCancel,
|
||||||
|
form,
|
||||||
|
joditConfig,
|
||||||
|
editorRef,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useCreateLessonModalForm;
|
||||||
@ -0,0 +1,73 @@
|
|||||||
|
import useCourseDetailPage from "./useCourseDetailPage.js";
|
||||||
|
import {Button, Col, FloatButton, Result, Row, Tooltip, Typography} from "antd";
|
||||||
|
import {ArrowLeftOutlined, BookOutlined, FormOutlined, PlusOutlined} from "@ant-design/icons";
|
||||||
|
import {useNavigate, useParams} from "react-router-dom";
|
||||||
|
import {ROLES} from "../../../Core/constants.js";
|
||||||
|
import CONFIG from "../../../Core/сonfig.js";
|
||||||
|
import LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.jsx";
|
||||||
|
import CreateLessonModalForm from "./Components/CreateLessonModalForm/CreateLessonModalForm.jsx";
|
||||||
|
|
||||||
|
|
||||||
|
const {Title} = Typography;
|
||||||
|
|
||||||
|
const CourseDetailPage = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const {courseId} = useParams();
|
||||||
|
const {
|
||||||
|
userData,
|
||||||
|
courseData,
|
||||||
|
isLoading,
|
||||||
|
isError,
|
||||||
|
handleCreateLesson,
|
||||||
|
} = useCourseDetailPage(courseId);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <LoadingIndicator/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isError) {
|
||||||
|
return <Result status="500" title="500" subTitle="Произошла ошибка при загрузке курса"/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{minHeight: "100vh"}}>
|
||||||
|
<Row justify="space-between" align="middle" style={{marginBottom: 24}}>
|
||||||
|
<Col>
|
||||||
|
<Button
|
||||||
|
icon={<ArrowLeftOutlined/>}
|
||||||
|
onClick={() => navigate(-1)}
|
||||||
|
style={{marginBottom: 16}}
|
||||||
|
>
|
||||||
|
Назад
|
||||||
|
</Button>
|
||||||
|
<Title level={2} style={{margin: 0}}>
|
||||||
|
<BookOutlined/> Курс - {courseData.title}
|
||||||
|
</Title>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<CreateLessonModalForm/>
|
||||||
|
{[CONFIG.ROOT_ROLE_NAME, ROLES.TEACHER].includes(userData.role.title) && (
|
||||||
|
<FloatButton.Group
|
||||||
|
placement={"left"}
|
||||||
|
trigger="hover"
|
||||||
|
type="primary"
|
||||||
|
icon={<PlusOutlined/>}
|
||||||
|
tooltip="Добавить элемент курса"
|
||||||
|
>
|
||||||
|
<FloatButton
|
||||||
|
icon={<PlusOutlined/>}
|
||||||
|
tooltip="Лекционный материал"
|
||||||
|
onClick={handleCreateLesson}
|
||||||
|
/>
|
||||||
|
<FloatButton
|
||||||
|
icon={<FormOutlined/>}
|
||||||
|
tooltip="Задание"
|
||||||
|
/>
|
||||||
|
</FloatButton.Group>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CourseDetailPage;
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
import {useGetAuthenticatedUserDataQuery} from "../../../Api/usersApi.js";
|
||||||
|
import {useGetCourseByIdQuery} from "../../../Api/coursesApi.js";
|
||||||
|
import {useEffect} from "react";
|
||||||
|
import {useDispatch} from "react-redux";
|
||||||
|
import {setOpenModalCreateLesson} from "../../../Redux/Slices/lessonsSlice.js";
|
||||||
|
|
||||||
|
|
||||||
|
const useCourseDetailPage = (courseId) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: userData,
|
||||||
|
isLoading: isUserLoading,
|
||||||
|
isError: isUserError,
|
||||||
|
} = useGetAuthenticatedUserDataQuery(undefined, {
|
||||||
|
pollingInterval: 60000,
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: courseData,
|
||||||
|
isLoading: isCourseLoading,
|
||||||
|
isError: isCourseError,
|
||||||
|
} = useGetCourseByIdQuery(courseId, {
|
||||||
|
pollingInterval: 60000,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.document.title = `Система обучения lectio - Курс: ${courseData?.title}`;
|
||||||
|
}, [courseData]);
|
||||||
|
|
||||||
|
const handleCreateLesson = () => {
|
||||||
|
dispatch(setOpenModalCreateLesson(true))
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
userData,
|
||||||
|
courseData,
|
||||||
|
isLoading: isUserLoading || isCourseLoading,
|
||||||
|
isError: isUserError || isCourseError,
|
||||||
|
handleCreateLesson,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useCourseDetailPage;
|
||||||
@ -88,14 +88,14 @@ const useUpdateCourseModalForm = () => {
|
|||||||
}).unwrap();
|
}).unwrap();
|
||||||
|
|
||||||
notification.success({
|
notification.success({
|
||||||
message: "Успех",
|
title: "Успех",
|
||||||
description: "Курс успешно обновлён!",
|
description: "Курс успешно обновлён!",
|
||||||
});
|
});
|
||||||
|
|
||||||
handleCancel();
|
handleCancel();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notification.error({
|
notification.error({
|
||||||
message: "Ошибка",
|
title: "Ошибка",
|
||||||
description: error?.data?.detail || "Не удалось обновить курс",
|
description: error?.data?.detail || "Не удалось обновить курс",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -19,10 +19,12 @@ import useCoursesPage from "./useCoursesPage.js";
|
|||||||
import LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.jsx";
|
import LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.jsx";
|
||||||
import CreateCourseModalForm from "./Components/CreateCourseModalForm/CreateCourseModalForm.jsx";
|
import CreateCourseModalForm from "./Components/CreateCourseModalForm/CreateCourseModalForm.jsx";
|
||||||
import UpdateCourseModalForm from "./Components/UpdateCourseModalForm/UpdateCourseModalForm.jsx";
|
import UpdateCourseModalForm from "./Components/UpdateCourseModalForm/UpdateCourseModalForm.jsx";
|
||||||
|
import {useNavigate} from "react-router-dom";
|
||||||
|
|
||||||
const {Title, Text} = Typography;
|
const {Title, Text} = Typography;
|
||||||
|
|
||||||
const CoursesPage = () => {
|
const CoursesPage = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
const {
|
const {
|
||||||
courses,
|
courses,
|
||||||
isLoading,
|
isLoading,
|
||||||
@ -69,6 +71,8 @@ const CoursesPage = () => {
|
|||||||
{courses.map((course) => (
|
{courses.map((course) => (
|
||||||
<Col xs={24} sm={12} lg={8} xl={6} key={course.id}>
|
<Col xs={24} sm={12} lg={8} xl={6} key={course.id}>
|
||||||
<Card
|
<Card
|
||||||
|
onClick={() => navigate(`/courses/${course.id}`)}
|
||||||
|
|
||||||
hoverable
|
hoverable
|
||||||
cover={
|
cover={
|
||||||
<div
|
<div
|
||||||
@ -4,6 +4,7 @@ import CONFIG from "../../../Core/сonfig.js";
|
|||||||
import {ROLES} from "../../../Core/constants.js";
|
import {ROLES} from "../../../Core/constants.js";
|
||||||
import {useDispatch} from "react-redux";
|
import {useDispatch} from "react-redux";
|
||||||
import {setOpenCreateCourseModal, setSelectedCourseToUpdate} from "../../../Redux/Slices/coursesSlice.js";
|
import {setOpenCreateCourseModal, setSelectedCourseToUpdate} from "../../../Redux/Slices/coursesSlice.js";
|
||||||
|
import {useEffect} from "react";
|
||||||
|
|
||||||
|
|
||||||
const useCoursesPage = () => {
|
const useCoursesPage = () => {
|
||||||
@ -25,6 +26,10 @@ const useCoursesPage = () => {
|
|||||||
dispatch(setSelectedCourseToUpdate(course));
|
dispatch(setSelectedCourseToUpdate(course));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.document.title = "Система обучения lectio - Курсы";
|
||||||
|
}, []);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
courses,
|
courses,
|
||||||
isLoading: isCoursesLoading || isUserLoading || isLoading,
|
isLoading: isCoursesLoading || isUserLoading || isLoading,
|
||||||
@ -22,7 +22,7 @@ const useLoginPage = () => {
|
|||||||
hasRedirected.current = true;
|
hasRedirected.current = true;
|
||||||
navigate("/");
|
navigate("/");
|
||||||
}
|
}
|
||||||
document.title = "Аутентификация";
|
document.title = "Система обучения lectio - Аутентификация";
|
||||||
}, [user, userData, isLoading, navigate]);
|
}, [user, userData, isLoading, navigate]);
|
||||||
|
|
||||||
const onFinish = async (loginData) => {
|
const onFinish = async (loginData) => {
|
||||||
|
|||||||
@ -13,7 +13,7 @@ const useProfilePage = () => {
|
|||||||
const [passwordForm] = Form.useForm();
|
const [passwordForm] = Form.useForm();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.document.title = "Профиль";
|
window.document.title = "Система обучения lectio - Профиль";
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|||||||
24
web/src/Redux/Slices/lessonsSlice.js
Normal file
24
web/src/Redux/Slices/lessonsSlice.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import {createSlice} from "@reduxjs/toolkit";
|
||||||
|
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
selectedLessonToUpdate: null,
|
||||||
|
openModalCreateLesson: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const lessonSlice = createSlice({
|
||||||
|
name: "lessons",
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
setSelectedLessonToUpdate(state, action) {
|
||||||
|
state.selectedLessonToUpdate = action.payload;
|
||||||
|
},
|
||||||
|
setOpenModalCreateLesson(state, action) {
|
||||||
|
state.openModalCreateLesson = action.payload;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const {setSelectedLessonToUpdate, setOpenModalCreateLesson} = lessonSlice.actions;
|
||||||
|
|
||||||
|
export default lessonSlice.reducer;
|
||||||
@ -2,11 +2,13 @@ import {configureStore} from "@reduxjs/toolkit";
|
|||||||
import authReducer from "./Slices/authSlice.js";
|
import authReducer from "./Slices/authSlice.js";
|
||||||
import usersReducer from "./Slices/usersSlice.js";
|
import usersReducer from "./Slices/usersSlice.js";
|
||||||
import coursesReducer from "./Slices/coursesSlice.js";
|
import coursesReducer from "./Slices/coursesSlice.js";
|
||||||
|
import lessonReducer from "./Slices/lessonsSlice.js";
|
||||||
import {authApi} from "../Api/authApi.js";
|
import {authApi} from "../Api/authApi.js";
|
||||||
import {usersApi} from "../Api/usersApi.js";
|
import {usersApi} from "../Api/usersApi.js";
|
||||||
import {rolesApi} from "../Api/rolesApi.js";
|
import {rolesApi} from "../Api/rolesApi.js";
|
||||||
import {statusesApi} from "../Api/statusesApi.js";
|
import {statusesApi} from "../Api/statusesApi.js";
|
||||||
import {coursesApi} from "../Api/coursesApi.js";
|
import {coursesApi} from "../Api/coursesApi.js";
|
||||||
|
import {lessonsApi} from "../Api/lessonsApi.js";
|
||||||
|
|
||||||
export const store = configureStore({
|
export const store = configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
@ -21,7 +23,10 @@ export const store = configureStore({
|
|||||||
[statusesApi.reducerPath]: statusesApi.reducer,
|
[statusesApi.reducerPath]: statusesApi.reducer,
|
||||||
|
|
||||||
courses: coursesReducer,
|
courses: coursesReducer,
|
||||||
[coursesApi.reducerPath]: coursesApi.reducer
|
[coursesApi.reducerPath]: coursesApi.reducer,
|
||||||
|
|
||||||
|
lessons: lessonReducer,
|
||||||
|
[lessonsApi.reducerPath]: lessonsApi.reducer
|
||||||
},
|
},
|
||||||
middleware: (getDefaultMiddleware) => (
|
middleware: (getDefaultMiddleware) => (
|
||||||
getDefaultMiddleware().concat(
|
getDefaultMiddleware().concat(
|
||||||
@ -29,7 +34,8 @@ export const store = configureStore({
|
|||||||
usersApi.middleware,
|
usersApi.middleware,
|
||||||
rolesApi.middleware,
|
rolesApi.middleware,
|
||||||
statusesApi.middleware,
|
statusesApi.middleware,
|
||||||
coursesApi.middleware
|
coursesApi.middleware,
|
||||||
|
lessonsApi.middleware
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user