From 44ceb937290cf63e939b5afcb8f427c205b1cd2d Mon Sep 17 00:00:00 2001 From: andrei Date: Fri, 28 Nov 2025 17:25:29 +0500 Subject: [PATCH] =?UTF-8?q?=D0=B7=D0=B0=D0=B3=D0=BE=D1=82=D0=BE=D0=B2?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=BF=D0=BE=D0=B4=20=D1=83=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BA=D1=83=D1=80=D1=81?= =?UTF-8?q?=D0=B0=D0=BC=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/app/application/users_repository.py | 10 ++ api/app/controllers/users_router.py | 15 ++ api/app/infrastructure/users_service.py | 8 + web/src/Api/coursesApi.js | 74 +++++++++ web/src/Api/usersApi.js | 8 + .../Components/Pages/AdminPage/AdminPage.jsx | 4 +- .../CreateUserModalForm.jsx | 0 .../useCreateUserModalForm.js | 6 +- .../UpdateUserModalForm.jsx | 2 +- .../useUpdateUserModalForm.js | 8 +- .../CreateCourseModalForm.jsx | 98 ++++++++++++ .../useCreateCourseModalForm.js | 60 +++++++ .../Components/Pages/Courses/CoursesPage.jsx | 148 +++++++++++++++++- .../Pages/Courses/useCoursesPage.js | 37 +++++ web/src/Core/constants.js | 5 + web/src/Redux/Slices/coursesSlice.js | 23 +++ web/src/Redux/store.js | 8 +- 17 files changed, 500 insertions(+), 14 deletions(-) create mode 100644 web/src/Api/coursesApi.js rename web/src/Components/Pages/AdminPage/{ => Components}/CreateUserModalForm/CreateUserModalForm.jsx (100%) rename web/src/Components/Pages/AdminPage/{ => Components}/CreateUserModalForm/useCreateUserModalForm.js (92%) rename web/src/Components/Pages/AdminPage/{ => Components}/UpdateUserModalForm/UpdateUserModalForm.jsx (99%) rename web/src/Components/Pages/AdminPage/{ => Components}/UpdateUserModalForm/useUpdateUserModalForm.js (94%) create mode 100644 web/src/Components/Pages/Courses/Components/CreateCourseModalForm/CreateCourseModalForm.jsx create mode 100644 web/src/Components/Pages/Courses/Components/CreateCourseModalForm/useCreateCourseModalForm.js create mode 100644 web/src/Core/constants.js create mode 100644 web/src/Redux/Slices/coursesSlice.js diff --git a/api/app/application/users_repository.py b/api/app/application/users_repository.py index a1b9e81..d061f0c 100644 --- a/api/app/application/users_repository.py +++ b/api/app/application/users_repository.py @@ -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() diff --git a/api/app/controllers/users_router.py b/api/app/controllers/users_router.py index b4d59ad..dfa31ac 100644 --- a/api/app/controllers/users_router.py +++ b/api/app/controllers/users_router.py @@ -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) diff --git a/api/app/infrastructure/users_service.py b/api/app/infrastructure/users_service.py index 3a110ee..d84ae13 100644 --- a/api/app/infrastructure/users_service.py +++ b/api/app/infrastructure/users_service.py @@ -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 diff --git a/web/src/Api/coursesApi.js b/web/src/Api/coursesApi.js new file mode 100644 index 0000000..c526836 --- /dev/null +++ b/web/src/Api/coursesApi.js @@ -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; \ No newline at end of file diff --git a/web/src/Api/usersApi.js b/web/src/Api/usersApi.js index 280b620..e1a2333 100644 --- a/web/src/Api/usersApi.js +++ b/web/src/Api/usersApi.js @@ -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; \ No newline at end of file diff --git a/web/src/Components/Pages/AdminPage/AdminPage.jsx b/web/src/Components/Pages/AdminPage/AdminPage.jsx index 33d3290..dcefc4e 100644 --- a/web/src/Components/Pages/AdminPage/AdminPage.jsx +++ b/web/src/Components/Pages/AdminPage/AdminPage.jsx @@ -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; diff --git a/web/src/Components/Pages/AdminPage/CreateUserModalForm/CreateUserModalForm.jsx b/web/src/Components/Pages/AdminPage/Components/CreateUserModalForm/CreateUserModalForm.jsx similarity index 100% rename from web/src/Components/Pages/AdminPage/CreateUserModalForm/CreateUserModalForm.jsx rename to web/src/Components/Pages/AdminPage/Components/CreateUserModalForm/CreateUserModalForm.jsx diff --git a/web/src/Components/Pages/AdminPage/CreateUserModalForm/useCreateUserModalForm.js b/web/src/Components/Pages/AdminPage/Components/CreateUserModalForm/useCreateUserModalForm.js similarity index 92% rename from web/src/Components/Pages/AdminPage/CreateUserModalForm/useCreateUserModalForm.js rename to web/src/Components/Pages/AdminPage/Components/CreateUserModalForm/useCreateUserModalForm.js index 559bfdd..94bbe43 100644 --- a/web/src/Components/Pages/AdminPage/CreateUserModalForm/useCreateUserModalForm.js +++ b/web/src/Components/Pages/AdminPage/Components/CreateUserModalForm/useCreateUserModalForm.js @@ -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 = () => { diff --git a/web/src/Components/Pages/AdminPage/UpdateUserModalForm/UpdateUserModalForm.jsx b/web/src/Components/Pages/AdminPage/Components/UpdateUserModalForm/UpdateUserModalForm.jsx similarity index 99% rename from web/src/Components/Pages/AdminPage/UpdateUserModalForm/UpdateUserModalForm.jsx rename to web/src/Components/Pages/AdminPage/Components/UpdateUserModalForm/UpdateUserModalForm.jsx index b364ece..991d690 100644 --- a/web/src/Components/Pages/AdminPage/UpdateUserModalForm/UpdateUserModalForm.jsx +++ b/web/src/Components/Pages/AdminPage/Components/UpdateUserModalForm/UpdateUserModalForm.jsx @@ -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; diff --git a/web/src/Components/Pages/AdminPage/UpdateUserModalForm/useUpdateUserModalForm.js b/web/src/Components/Pages/AdminPage/Components/UpdateUserModalForm/useUpdateUserModalForm.js similarity index 94% rename from web/src/Components/Pages/AdminPage/UpdateUserModalForm/useUpdateUserModalForm.js rename to web/src/Components/Pages/AdminPage/Components/UpdateUserModalForm/useUpdateUserModalForm.js index d749512..8267780 100644 --- a/web/src/Components/Pages/AdminPage/UpdateUserModalForm/useUpdateUserModalForm.js +++ b/web/src/Components/Pages/AdminPage/Components/UpdateUserModalForm/useUpdateUserModalForm.js @@ -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 = () => { diff --git a/web/src/Components/Pages/Courses/Components/CreateCourseModalForm/CreateCourseModalForm.jsx b/web/src/Components/Pages/Courses/Components/CreateCourseModalForm/CreateCourseModalForm.jsx new file mode 100644 index 0000000..87a2f32 --- /dev/null +++ b/web/src/Components/Pages/Courses/Components/CreateCourseModalForm/CreateCourseModalForm.jsx @@ -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 ( + + ); + } + + if (isError) { + return ( + + ); + } + + return ( + + Отмена + , + , + ]} + width={800} + > +
+ + + + + +