заготовка под управление курсами
This commit is contained in:
parent
bb6537533b
commit
44ceb93729
@ -3,6 +3,7 @@ from typing import Optional, List
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.domain.models import Role
|
||||
from app.domain.models.users import User
|
||||
|
||||
|
||||
@ -31,6 +32,15 @@ class UsersRepository:
|
||||
result = await self.db.execute(query)
|
||||
return result.scalars().first()
|
||||
|
||||
async def get_by_role_name(self, role_name: str) -> List[User]:
|
||||
query = (
|
||||
select(User)
|
||||
.join(User.role)
|
||||
.filter(Role.title == role_name)
|
||||
)
|
||||
result = await self.db.execute(query)
|
||||
return result.scalars().all()
|
||||
|
||||
async def create(self, user: User) -> User:
|
||||
self.db.add(user)
|
||||
await self.db.commit()
|
||||
|
||||
@ -86,3 +86,18 @@ async def create_user(
|
||||
):
|
||||
register_service = RegisterService(db)
|
||||
return await register_service.create_user(user)
|
||||
|
||||
|
||||
@users_router.get(
|
||||
'/role/{role_name}/',
|
||||
response_model=List[UserRead],
|
||||
summary='Return all users with given role',
|
||||
description='Return all users with given role',
|
||||
)
|
||||
async def get_users_by_role_name(
|
||||
role_name: str,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_auth_user),
|
||||
):
|
||||
users_service = UsersService(db)
|
||||
return await users_service.get_by_role_name(role_name)
|
||||
|
||||
@ -95,3 +95,11 @@ class UsersService:
|
||||
user_model = await self.users_repository.update(user_model)
|
||||
|
||||
return UserRead.model_validate(user_model)
|
||||
|
||||
async def get_by_role_name(self, role_name: str) -> List[UserRead]:
|
||||
users = await self.users_repository.get_by_role_name(role_name)
|
||||
response = []
|
||||
for user in users:
|
||||
response.append(UserRead.model_validate(user))
|
||||
|
||||
return response
|
||||
|
||||
74
web/src/Api/coursesApi.js
Normal file
74
web/src/Api/coursesApi.js
Normal file
@ -0,0 +1,74 @@
|
||||
import CONFIG from "../Core/сonfig.js";
|
||||
import {baseQueryWithAuth} from "./baseQuery.js";
|
||||
import {createApi} from "@reduxjs/toolkit/query/react";
|
||||
|
||||
|
||||
export const coursesApi = createApi({
|
||||
reducerPath: 'coursesApi',
|
||||
baseQuery: baseQueryWithAuth,
|
||||
endpoints: (builder) => ({
|
||||
getAllCourses: builder.query({
|
||||
query: () => ({
|
||||
url: "/courses/",
|
||||
method: "GET",
|
||||
}),
|
||||
providesTags: ['course'],
|
||||
}),
|
||||
createCourse: builder.mutation({
|
||||
query: (data) => ({
|
||||
url: "/courses/",
|
||||
method: "POST",
|
||||
body: data,
|
||||
}),
|
||||
invalidatesTags: ['course'],
|
||||
}),
|
||||
updateCourse: builder.mutation({
|
||||
query: ({courseId, ...data}) => ({
|
||||
url: `/courses/${courseId}/`,
|
||||
method: "PUT",
|
||||
body: data,
|
||||
}),
|
||||
invalidatesTags: ['course'],
|
||||
}),
|
||||
getCourseTeachers: builder.query({
|
||||
query: (courseId) => ({
|
||||
url: `/courses/${courseId}/teachers/`,
|
||||
method: "GET",
|
||||
}),
|
||||
providesTags: ['teacher'],
|
||||
}),
|
||||
replaceCourseTeachers: builder.mutation({
|
||||
query: ({courseId, ...data}) => ({
|
||||
url: `/courses/${courseId}/teachers/`,
|
||||
method: "PUT",
|
||||
body: data,
|
||||
}),
|
||||
invalidatesTags: ['teacher'],
|
||||
}),
|
||||
getCourseStudents: builder.query({
|
||||
query: (courseId) => ({
|
||||
url: `/courses/${courseId}/students/`,
|
||||
method: "GET",
|
||||
}),
|
||||
providesTags: ['student'],
|
||||
}),
|
||||
replaceCourseStudents: builder.mutation({
|
||||
query: ({courseId, ...data}) => ({
|
||||
url: `/courses/${courseId}/students/`,
|
||||
method: "PUT",
|
||||
body: data,
|
||||
}),
|
||||
invalidatesTags: ['student'],
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export const {
|
||||
useGetAllCoursesQuery,
|
||||
useCreateCourseMutation,
|
||||
useUpdateCourseMutation,
|
||||
useGetCourseTeachersQuery,
|
||||
useReplaceCourseTeachersMutation,
|
||||
useGetCourseStudentsQuery,
|
||||
useReplaceCourseStudentsMutation,
|
||||
} = coursesApi;
|
||||
@ -39,6 +39,13 @@ export const usersApi = createApi({
|
||||
}),
|
||||
invalidatesTags: ["user"],
|
||||
}),
|
||||
getUsersByRoleName: builder.query({
|
||||
query: (roleName) => ({
|
||||
url: `/users/role/${roleName}/`,
|
||||
method: "GET",
|
||||
}),
|
||||
providesTags: ["user"],
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
@ -48,4 +55,5 @@ export const {
|
||||
useUpdateUserMutation,
|
||||
useUpdateUserPasswordMutation,
|
||||
useCreateUserMutation,
|
||||
useGetUsersByRoleNameQuery,
|
||||
} = usersApi;
|
||||
@ -2,8 +2,8 @@ import {Button, Col, Flex, FloatButton, Input, Result, Row, Table, Tooltip, Typo
|
||||
import {ControlOutlined, PlusOutlined} from "@ant-design/icons";
|
||||
import useAdminPage from "./useAdminPage.js";
|
||||
import LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.jsx";
|
||||
import CreateUserModalForm from "./CreateUserModalForm/CreateUserModalForm.jsx";
|
||||
import UpdateUserModalForm from "./UpdateUserModalForm/UpdateUserModalForm.jsx";
|
||||
import CreateUserModalForm from "./Components/CreateUserModalForm/CreateUserModalForm.jsx";
|
||||
import UpdateUserModalForm from "./Components/UpdateUserModalForm/UpdateUserModalForm.jsx";
|
||||
|
||||
const {Title} = Typography;
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
import {Form, notification} from "antd";
|
||||
import {setOpenModalCreateUser, setSelectedUserToUpdate} from "../../../../Redux/Slices/usersSlice.js";
|
||||
import {useGetAllRolesQuery} from "../../../../Api/rolesApi.js";
|
||||
import {useCreateUserMutation} from "../../../../Api/usersApi.js";
|
||||
import {setOpenModalCreateUser, setSelectedUserToUpdate} from "../../../../../Redux/Slices/usersSlice.js";
|
||||
import {useGetAllRolesQuery} from "../../../../../Api/rolesApi.js";
|
||||
import {useCreateUserMutation} from "../../../../../Api/usersApi.js";
|
||||
|
||||
|
||||
const useCreateUserModalForm = () => {
|
||||
@ -23,7 +23,7 @@ import {
|
||||
TagOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import dayjs from "dayjs";
|
||||
import LoadingIndicator from "../../../Widgets/LoadingIndicator/LoadingIndicator.jsx";
|
||||
import LoadingIndicator from "../../../../Widgets/LoadingIndicator/LoadingIndicator.jsx";
|
||||
import useUpdateUserModalForm from "./useUpdateUserModalForm.js";
|
||||
|
||||
const {Title, Text} = Typography;
|
||||
@ -1,11 +1,11 @@
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
import {Form, notification} from "antd";
|
||||
import {setSelectedUserToUpdate} from "../../../../Redux/Slices/usersSlice.js";
|
||||
import {useGetAllRolesQuery} from "../../../../Api/rolesApi.js";
|
||||
import {setSelectedUserToUpdate} from "../../../../../Redux/Slices/usersSlice.js";
|
||||
import {useGetAllRolesQuery} from "../../../../../Api/rolesApi.js";
|
||||
import {useEffect} from "react";
|
||||
import dayjs from "dayjs";
|
||||
import {useUpdateUserMutation, useUpdateUserPasswordMutation} from "../../../../Api/usersApi.js";
|
||||
import {useGetStatusesQuery} from "../../../../Api/statusesApi.js";
|
||||
import {useUpdateUserMutation, useUpdateUserPasswordMutation} from "../../../../../Api/usersApi.js";
|
||||
import {useGetStatusesQuery} from "../../../../../Api/statusesApi.js";
|
||||
|
||||
|
||||
const useUpdateUserModalForm = () => {
|
||||
@ -0,0 +1,98 @@
|
||||
import useCreateCourseModalForm from "./useCreateCourseModalForm.js";
|
||||
import {Button, Col, Form, Input, Modal, Result, Row, Select} from "antd";
|
||||
import TextArea from "antd/es/input/TextArea.js";
|
||||
import LoadingIndicator from "../../../../Widgets/LoadingIndicator/LoadingIndicator.jsx";
|
||||
|
||||
|
||||
const CreateCourseModal = () => {
|
||||
const {
|
||||
openCreateCourseModal,
|
||||
handleCancel,
|
||||
form,
|
||||
isLoadinging,
|
||||
isError,
|
||||
teachers,
|
||||
students
|
||||
} = useCreateCourseModalForm();
|
||||
|
||||
if (isLoadinging) {
|
||||
return (
|
||||
<LoadingIndicator/>
|
||||
);
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
<Result status="500" title="500" subTitle="Произошла ошибка при загрузке данных пользователя"/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={"Создание курса"}
|
||||
open={openCreateCourseModal}
|
||||
onCancel={handleCancel}
|
||||
footer={[
|
||||
<Button key="cancel" onClick={handleCancel}>
|
||||
Отмена
|
||||
</Button>,
|
||||
<Button
|
||||
key="submit"
|
||||
type="primary"
|
||||
loading={isLoadinging}
|
||||
// onClick={handleOk}
|
||||
>
|
||||
Создать
|
||||
</Button>,
|
||||
]}
|
||||
width={800}
|
||||
>
|
||||
<Form form={form} layout="vertical">
|
||||
<Form.Item
|
||||
name="title"
|
||||
label="Название курса"
|
||||
rules={[{required: true, message: "Введите название"}]}
|
||||
>
|
||||
<Input size="large" placeholder="Введение в Python"/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="description" label="Описание">
|
||||
<TextArea rows={4} placeholder="Курс для начинающих..."/>
|
||||
</Form.Item>
|
||||
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Form.Item name="teacher_ids" label="Преподаватели">
|
||||
<Select
|
||||
mode="multiple"
|
||||
placeholder="Выберите преподавателей"
|
||||
loading={isLoadinging}
|
||||
>
|
||||
{teachers.map((teacher) => (
|
||||
<Select.Option key={teacher.id}
|
||||
value={teacher.id}>{teacher.last_name} {teacher.first_name} - {teacher.login}</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item name="student_ids" label="Студенты">
|
||||
<Select
|
||||
mode="multiple"
|
||||
placeholder="Выберите студентов"
|
||||
loading={isLoadinging}
|
||||
>
|
||||
{students.map((student) => (
|
||||
<Select.Option key={student.id}
|
||||
value={student.id}>{student.last_name} {student.first_name} - {student.login}</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default CreateCourseModal;
|
||||
@ -0,0 +1,60 @@
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
import {setOpenCreateCourseModal} from "../../../../../Redux/Slices/coursesSlice.js";
|
||||
import {Form} from "antd";
|
||||
import {useGetUsersByRoleNameQuery} from "../../../../../Api/usersApi.js";
|
||||
import {
|
||||
useCreateCourseMutation,
|
||||
useReplaceCourseStudentsMutation,
|
||||
useReplaceCourseTeachersMutation
|
||||
} from "../../../../../Api/coursesApi.js";
|
||||
|
||||
|
||||
const useCreateCourseModalForm = () => {
|
||||
const dispatch = useDispatch();
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const {
|
||||
data: teachers = [],
|
||||
isLoading: isTeachersLoading,
|
||||
isError: isTeachersError,
|
||||
} = useGetUsersByRoleNameQuery("teacher", {
|
||||
pollingInterval: 20000,
|
||||
});
|
||||
|
||||
const {
|
||||
data: students = [],
|
||||
isLoading: isStudentsLoading,
|
||||
isError: isStudentsError,
|
||||
} = useGetUsersByRoleNameQuery("student", {
|
||||
pollingInterval: 20000,
|
||||
});
|
||||
|
||||
const [createCourse, {isLoading: creatingCourse}] = useCreateCourseMutation();
|
||||
const [replaceTeachers, {isLoading: replacingTeachers}] = useReplaceCourseTeachersMutation();
|
||||
const [replaceStudents, {isLoading: replacingStudents}] = useReplaceCourseStudentsMutation();
|
||||
|
||||
const isLoading = isTeachersLoading || isStudentsLoading || creatingCourse || replacingTeachers || replacingStudents;
|
||||
const isError = isTeachersError || isStudentsError;
|
||||
|
||||
const {
|
||||
openCreateCourseModal
|
||||
} = useSelector((state) => state.courses);
|
||||
|
||||
const handleCancel = () => {
|
||||
form.resetFields();
|
||||
dispatch(setOpenCreateCourseModal(false));
|
||||
}
|
||||
|
||||
return {
|
||||
openCreateCourseModal,
|
||||
handleCancel,
|
||||
handleOk,
|
||||
form,
|
||||
teachers,
|
||||
students,
|
||||
isLoading,
|
||||
isError,
|
||||
}
|
||||
};
|
||||
|
||||
export default useCreateCourseModalForm;
|
||||
@ -1,10 +1,152 @@
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
Empty,
|
||||
Row,
|
||||
Space,
|
||||
Spin,
|
||||
Tag,
|
||||
Typography,
|
||||
Avatar, Result, FloatButton, Tooltip,
|
||||
} from "antd";
|
||||
import {
|
||||
PlusOutlined,
|
||||
UserOutlined,
|
||||
TeamOutlined, BookOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import useCoursesPage from "./useCoursesPage.js";
|
||||
import LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.jsx";
|
||||
import CreateCourseModalForm from "./Components/CreateCourseModalForm/CreateCourseModalForm.jsx";
|
||||
|
||||
|
||||
const {Title, Text} = Typography;
|
||||
|
||||
const CoursesPage = () => {
|
||||
const {
|
||||
courses,
|
||||
isLoading,
|
||||
isError,
|
||||
isAdmin,
|
||||
isTeacher,
|
||||
openCreateModal,
|
||||
openEditModal,
|
||||
} = useCoursesPage();
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<LoadingIndicator/>
|
||||
);
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
return <Result status="500" title="500" subTitle="Произошла ошибка при загрузке курсов"/>
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
CoursesPage
|
||||
<div style={{minHeight: "100vh"}}>
|
||||
<Row justify="space-between" align="middle" style={{marginBottom: 24}}>
|
||||
<Col>
|
||||
<Title level={2} style={{margin: 0}}>
|
||||
<BookOutlined/> Курсы
|
||||
</Title>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{courses.length === 0 ? (
|
||||
<Empty
|
||||
description="Курсов пока нет"
|
||||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||
>
|
||||
{(isAdmin || isTeacher) && (
|
||||
<Button type="primary" onClick={openCreateModal}>
|
||||
Создать первый курс
|
||||
</Button>
|
||||
)}
|
||||
</Empty>
|
||||
) : (
|
||||
<Row gutter={[24, 24]}>
|
||||
{courses.map((course) => (
|
||||
<Col xs={24} sm={12} lg={8} xl={6} key={course.id}>
|
||||
<Card
|
||||
hoverable
|
||||
cover={
|
||||
<div
|
||||
style={{
|
||||
height: 160,
|
||||
background: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
color: "white",
|
||||
fontSize: 48,
|
||||
}}
|
||||
>
|
||||
{course.title[0].toUpperCase()}
|
||||
</div>
|
||||
}
|
||||
actions={
|
||||
isAdmin || isTeacher
|
||||
? [
|
||||
<Button
|
||||
type="link"
|
||||
onClick={() => openEditModal(course)}
|
||||
>
|
||||
Редактировать
|
||||
</Button>,
|
||||
]
|
||||
: []
|
||||
}
|
||||
>
|
||||
<Card.Meta
|
||||
title={<Title level={4}>{course.title}</Title>}
|
||||
description={
|
||||
course.description || <Text type="secondary">Без описания</Text>
|
||||
}
|
||||
/>
|
||||
<Space direction="vertical" style={{width: "100%", marginTop: 16}}>
|
||||
<Space>
|
||||
<TeamOutlined/>
|
||||
<Text>
|
||||
{course.teachers?.length || 0} учител
|
||||
{course.teachers?.length === 1 ? "ь" : "ей"}
|
||||
</Text>
|
||||
</Space>
|
||||
<Space>
|
||||
<UserOutlined/>
|
||||
<Text>
|
||||
{course.enrollments?.length || 0} студент
|
||||
{course.enrollments?.length === 1 ? "" : "ов"}
|
||||
</Text>
|
||||
</Space>
|
||||
</Space>
|
||||
|
||||
{course.teachers?.length > 0 && (
|
||||
<div style={{marginTop: 16}}>
|
||||
<Text type="secondary">Преподаватели:</Text>
|
||||
<Avatar.Group maxCount={3} style={{marginTop: 8}}>
|
||||
{course.teachers.map((t) => (
|
||||
<Avatar key={t.teacher_id} style={{backgroundColor: "#1890ff"}}>
|
||||
{t.teacher?.first_name?.[0] || "У"}
|
||||
</Avatar>
|
||||
))}
|
||||
</Avatar.Group>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
)}
|
||||
|
||||
<Tooltip title="Создать курс">
|
||||
<FloatButton
|
||||
icon={<PlusOutlined/>}
|
||||
onClick={openCreateModal}
|
||||
type="primary"
|
||||
/>
|
||||
</Tooltip>
|
||||
|
||||
<CreateCourseModalForm/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
import {useGetAuthenticatedUserDataQuery} from "../../../Api/usersApi.js";
|
||||
import {useGetAllCoursesQuery} from "../../../Api/coursesApi.js";
|
||||
import CONFIG from "../../../Core/сonfig.js";
|
||||
import {ROLES} from "../../../Core/constants.js";
|
||||
import {useDispatch} from "react-redux";
|
||||
import {setOpenCreateCourseModal} from "../../../Redux/Slices/coursesSlice.js";
|
||||
|
||||
|
||||
const useCoursesPage = () => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const {data: userData, isLoading: isUserLoading} = useGetAuthenticatedUserDataQuery();
|
||||
const {data: courses = [], isLoading, isCoursesLoading, isError} = useGetAllCoursesQuery();
|
||||
|
||||
const isAdmin = userData?.role?.title === CONFIG.ROOT_ROLE_NAME;
|
||||
const isTeacher = [CONFIG.ROOT_ROLE_NAME, ROLES.TEACHER].includes(userData?.role?.title);
|
||||
|
||||
const openCreateModal = () => {
|
||||
dispatch(setOpenCreateCourseModal(true));
|
||||
};
|
||||
|
||||
const closeCreateModal = () => {
|
||||
dispatch(setOpenCreateCourseModal(false));
|
||||
};
|
||||
|
||||
return {
|
||||
courses,
|
||||
isLoading: isCoursesLoading || isUserLoading || isLoading,
|
||||
isError,
|
||||
isAdmin,
|
||||
isTeacher,
|
||||
openCreateModal,
|
||||
closeModal: closeCreateModal,
|
||||
};
|
||||
};
|
||||
|
||||
export default useCoursesPage;
|
||||
5
web/src/Core/constants.js
Normal file
5
web/src/Core/constants.js
Normal file
@ -0,0 +1,5 @@
|
||||
export const ROLES = {
|
||||
ADMIN: "root",
|
||||
TEACHER: "teacher",
|
||||
STUDENT: "student",
|
||||
}
|
||||
23
web/src/Redux/Slices/coursesSlice.js
Normal file
23
web/src/Redux/Slices/coursesSlice.js
Normal file
@ -0,0 +1,23 @@
|
||||
import {createSlice} from "@reduxjs/toolkit";
|
||||
|
||||
|
||||
const initialState = {
|
||||
selectedCourseToUpdate: null,
|
||||
openCreateCourseModal: false,
|
||||
};
|
||||
|
||||
export const coursesSlice = createSlice({
|
||||
name: "courses",
|
||||
initialState,
|
||||
reducers: {
|
||||
setSelectedCourseToUpdate(state, action) {
|
||||
state.selectedCourseToUpdate = action.payload;
|
||||
},
|
||||
setOpenCreateCourseModal(state, action) {
|
||||
state.openCreateCourseModal = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const {setSelectedCourseToUpdate, setOpenCreateCourseModal} = coursesSlice.actions;
|
||||
export default coursesSlice.reducer;
|
||||
@ -1,10 +1,12 @@
|
||||
import {configureStore} from "@reduxjs/toolkit";
|
||||
import authReducer from "./Slices/authSlice.js";
|
||||
import usersReducer from "./Slices/usersSlice.js";
|
||||
import coursesReducer from "./Slices/coursesSlice.js";
|
||||
import {authApi} from "../Api/authApi.js";
|
||||
import {usersApi} from "../Api/usersApi.js";
|
||||
import {rolesApi} from "../Api/rolesApi.js";
|
||||
import {statusesApi} from "../Api/statusesApi.js";
|
||||
import {coursesApi} from "../Api/coursesApi.js";
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
@ -16,7 +18,10 @@ export const store = configureStore({
|
||||
|
||||
[rolesApi.reducerPath]: rolesApi.reducer,
|
||||
|
||||
[statusesApi.reducerPath]: statusesApi.reducer
|
||||
[statusesApi.reducerPath]: statusesApi.reducer,
|
||||
|
||||
courses: coursesReducer,
|
||||
[coursesApi.reducerPath]: coursesApi.reducer
|
||||
},
|
||||
middleware: (getDefaultMiddleware) => (
|
||||
getDefaultMiddleware().concat(
|
||||
@ -24,6 +29,7 @@ export const store = configureStore({
|
||||
usersApi.middleware,
|
||||
rolesApi.middleware,
|
||||
statusesApi.middleware,
|
||||
coursesApi.middleware
|
||||
)
|
||||
),
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user