From 12c448aef93aed1f809a4164f6e4947c890b87d4 Mon Sep 17 00:00:00 2001 From: andrei Date: Fri, 28 Nov 2025 10:05:52 +0500 Subject: [PATCH] =?UTF-8?q?=D1=81=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20=D1=81?= =?UTF-8?q?=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=BF=D0=BE=D0=BB?= =?UTF-8?q?=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5=D0=BB=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/app/application/roles_repository.py | 7 +- api/app/application/users_repository.py | 7 +- api/app/controllers/roles_router.py | 26 ++ api/app/controllers/users_router.py | 35 ++- api/app/domain/entities/users.py | 10 +- api/app/infrastructure/roles_service.py | 20 ++ api/app/infrastructure/users_service.py | 11 +- api/app/main.py | 2 + web/src/Api/rolesApi.js | 20 ++ web/src/Api/usersApi.js | 14 + web/src/App/AdminRoute.jsx | 3 +- web/src/App/AppRouter.jsx | 7 +- web/src/Components/Layouts/MainLayout.jsx | 16 +- .../Components/Pages/AdminPage/AdminPage.jsx | 104 +++++++ .../CreateUserModalForm.jsx | 108 ++++++++ .../useCreateUserModalForm.js | 79 ++++++ .../Pages/AdminPage/useAdminPage.js | 65 +++++ .../Pages/ProfilePage/ProfilePage.jsx | 255 ++++++++++++------ web/src/Core/сonfig.js | 1 + web/src/Redux/Slices/usersSlice.js | 23 ++ web/src/Redux/store.js | 8 +- 21 files changed, 727 insertions(+), 94 deletions(-) create mode 100644 api/app/controllers/roles_router.py create mode 100644 api/app/infrastructure/roles_service.py create mode 100644 web/src/Api/rolesApi.js create mode 100644 web/src/Components/Pages/AdminPage/AdminPage.jsx create mode 100644 web/src/Components/Pages/AdminPage/CreateUserModalForm/CreateUserModalForm.jsx create mode 100644 web/src/Components/Pages/AdminPage/CreateUserModalForm/useCreateUserModalForm.js create mode 100644 web/src/Components/Pages/AdminPage/useAdminPage.js create mode 100644 web/src/Redux/Slices/usersSlice.js diff --git a/api/app/application/roles_repository.py b/api/app/application/roles_repository.py index af7b4c7..c89cdef 100644 --- a/api/app/application/roles_repository.py +++ b/api/app/application/roles_repository.py @@ -10,10 +10,15 @@ class RolesRepository: def __init__(self, db: AsyncSession) -> None: self.db = db + async def get_all(self) -> list[Role]: + query = select(Role) + result = await self.db.execute(query) + return result.scalars().all() + async def get_by_id(self, role_id: int) -> Optional[Role]: query = ( select(Role) - .filter_by(role_id=role_id) + .filter_by(id=role_id) ) result = await self.db.execute(query) return result.scalars().first() diff --git a/api/app/application/users_repository.py b/api/app/application/users_repository.py index e89f40c..a1b9e81 100644 --- a/api/app/application/users_repository.py +++ b/api/app/application/users_repository.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Optional, List from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession @@ -10,6 +10,11 @@ class UsersRepository: def __init__(self, db: AsyncSession) -> None: self.db = db + async def get_all(self) -> List[User]: + query = select(User) + result = await self.db.execute(query) + return result.scalars().all() + async def get_by_id(self, user_id: int) -> Optional[User]: query = ( select(User) diff --git a/api/app/controllers/roles_router.py b/api/app/controllers/roles_router.py new file mode 100644 index 0000000..beb45ed --- /dev/null +++ b/api/app/controllers/roles_router.py @@ -0,0 +1,26 @@ +from typing import List, Optional + +from fastapi import APIRouter, Depends, Response +from sqlalchemy.ext.asyncio import AsyncSession + +from app.database.session import get_db +from app.domain.entities.roles import RoleRead +from app.domain.models import User +from app.infrastructure.dependencies import require_admin +from app.infrastructure.roles_service import RolesService + +roles_router = APIRouter() + + +@roles_router.get( + '/', + response_model=List[RoleRead], + summary='Returns all roles', + description='Returns all roles', +) +async def get_all_roles( + db: AsyncSession = Depends(get_db), + user: User = Depends(require_admin), +): + roles_service = RolesService(db) + return await roles_service.get_all() diff --git a/api/app/controllers/users_router.py b/api/app/controllers/users_router.py index 7825c0a..224e5a5 100644 --- a/api/app/controllers/users_router.py +++ b/api/app/controllers/users_router.py @@ -4,14 +4,29 @@ from fastapi import APIRouter, Depends, Response from sqlalchemy.ext.asyncio import AsyncSession from app.database.session import get_db -from app.domain.entities.users import UserRead, UserUpdate, PasswordChangeRequest +from app.domain.entities.users import UserRead, UserUpdate, PasswordChangeRequest, UserCreate from app.domain.models import User -from app.infrastructure.dependencies import require_auth_user +from app.infrastructure.dependencies import require_auth_user, require_admin +from app.infrastructure.register_service import RegisterService from app.infrastructure.users_service import UsersService users_router = APIRouter() +@users_router.get( + '/', + response_model=List[UserRead], + summary='Return all users', + description='Return all users', +) +async def get_all_users( + db: AsyncSession = Depends(get_db), + user: User = Depends(require_admin) +): + users_service = UsersService(db) + return await users_service.get_all() + + @users_router.get( '/me/', response_model=Optional[UserRead], @@ -25,6 +40,7 @@ async def get_authenticated_user_data( users_service = UsersService(db) return await users_service.get_by_id(user.id) + @users_router.put( '/{user_id}/', response_model=Optional[UserRead], @@ -40,6 +56,7 @@ async def update_user( users_service = UsersService(db) return await users_service.update(user_id, user_data, user) + @users_router.post( '/change-password/{user_id}/', response_model=Optional[UserRead], @@ -54,3 +71,17 @@ async def change_password( ): users_service = UsersService(db) return await users_service.change_password(user_id, password_data, user) + +@users_router.post( + '/create/', + response_model=Optional[UserRead], + summary='Creates a new user', + description='Creates a new user', +) +async def create_user( + user: UserCreate, + db: AsyncSession = Depends(get_db), + current_user: User = Depends(require_admin) +): + register_service = RegisterService(db) + return await register_service.create_user(user) diff --git a/api/app/domain/entities/users.py b/api/app/domain/entities/users.py index 1fd5c0c..df2d91c 100644 --- a/api/app/domain/entities/users.py +++ b/api/app/domain/entities/users.py @@ -18,7 +18,15 @@ class UserRegister(BaseModel): repeat_password: str = Field(min_length=8) -class UserCreate(UserRegister): +class UserCreate(BaseModel): + first_name: str = Field(max_length=250) + last_name: str = Field(max_length=250) + patronymic: Optional[str] = Field(default=None, max_length=250) + login: str = Field(max_length=250) + email: Optional[EmailStr] = None + birthdate: date + password: str = Field(min_length=8) + repeat_password: str = Field(min_length=8) role_id: int = Field() diff --git a/api/app/infrastructure/roles_service.py b/api/app/infrastructure/roles_service.py new file mode 100644 index 0000000..e9da001 --- /dev/null +++ b/api/app/infrastructure/roles_service.py @@ -0,0 +1,20 @@ +from typing import List + +from sqlalchemy.ext.asyncio import AsyncSession + +from app.application.roles_repository import RolesRepository +from app.domain.entities.roles import RoleRead + + +class RolesService: + def __init__(self, db: AsyncSession): + self.roles_repository = RolesRepository(db) + + async def get_all(self) -> List[RoleRead]: + roles = await self.roles_repository.get_all() + response = [] + + for role in roles: + response.append(RoleRead.model_validate(role)) + + return response diff --git a/api/app/infrastructure/users_service.py b/api/app/infrastructure/users_service.py index 841b27c..634da3b 100644 --- a/api/app/infrastructure/users_service.py +++ b/api/app/infrastructure/users_service.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Optional, List from fastapi import HTTPException, status from sqlalchemy.ext.asyncio import AsyncSession @@ -15,6 +15,15 @@ class UsersService: self.users_repository = UsersRepository(db) self.settings = Settings() + async def get_all(self) -> List[UserRead]: + users = await self.users_repository.get_all() + response = [] + + for user in users: + response.append(UserRead.model_validate(user)) + + return response + async def get_by_id(self, user_id: int) -> UserRead: user = await self.users_repository.get_by_id(user_id) if not user: diff --git a/api/app/main.py b/api/app/main.py index c817535..c4669ec 100644 --- a/api/app/main.py +++ b/api/app/main.py @@ -3,6 +3,7 @@ from starlette.middleware.cors import CORSMiddleware from app.controllers.auth_router import auth_router from app.controllers.register_router import register_router +from app.controllers.roles_router import roles_router from app.controllers.users_router import users_router from app.settings import Settings @@ -21,6 +22,7 @@ def start_app(): api_app.include_router(auth_router, prefix=f'{settings.prefix}/auth', tags=['auth']) 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(users_router, prefix=f'{settings.prefix}/users', tags=['users']) return api_app diff --git a/web/src/Api/rolesApi.js b/web/src/Api/rolesApi.js new file mode 100644 index 0000000..0a92201 --- /dev/null +++ b/web/src/Api/rolesApi.js @@ -0,0 +1,20 @@ +import {baseQueryWithAuth} from "./baseQuery.js"; +import {createApi} from "@reduxjs/toolkit/query/react"; + + +export const rolesApi = createApi({ + reducerPath: "rolesApi", + baseQuery: baseQueryWithAuth, + tagTypes: ["role"], + endpoints: (builder) => ({ + getAllRoles: builder.query({ + query: () => ({ + url: "/roles/", + method: "GET", + }), + providesTags: ["role"], + }), + }), +}); + +export const {useGetAllRolesQuery} = rolesApi; \ No newline at end of file diff --git a/web/src/Api/usersApi.js b/web/src/Api/usersApi.js index a7882da..4328428 100644 --- a/web/src/Api/usersApi.js +++ b/web/src/Api/usersApi.js @@ -7,6 +7,10 @@ export const usersApi = createApi({ baseQuery: baseQueryWithAuth, tagTypes: ["user"], endpoints: (builder) => ({ + getAllUsers: builder.query({ + query: () => "/users/", + providesTags: ["user"], + }), getAuthenticatedUserData: builder.query({ query: () => "/users/me/", }), @@ -26,11 +30,21 @@ export const usersApi = createApi({ }), invalidatesTags: ["user"], }), + createUser: builder.mutation({ + query: (data) => ({ + url: "/users/create/", + method: "POST", + body: data, + }), + invalidatesTags: ["user"], + }), }), }); export const { + useGetAllUsersQuery, useGetAuthenticatedUserDataQuery, useUpdateUserMutation, useUpdateUserPasswordMutation, + useCreateUserMutation, } = usersApi; \ No newline at end of file diff --git a/web/src/App/AdminRoute.jsx b/web/src/App/AdminRoute.jsx index c7c858a..30a7bf4 100644 --- a/web/src/App/AdminRoute.jsx +++ b/web/src/App/AdminRoute.jsx @@ -2,6 +2,7 @@ import {Navigate, Outlet} from "react-router-dom"; import {useGetAuthenticatedUserDataQuery} from "../Api/usersApi.js"; import LoadingIndicator from "../Components/Widgets/LoadingIndicator/LoadingIndicator.jsx"; import {Result} from "antd"; +import CONFIG from "../Core/сonfig.js"; const AdminRoute = () => { const { @@ -24,7 +25,7 @@ const AdminRoute = () => { return ; } - if (!user.role || user.role.title !== "root") { + if (!user.role || user.role.title !== CONFIG.ROOT_ROLE_NAME) { return ; } diff --git a/web/src/App/AppRouter.jsx b/web/src/App/AppRouter.jsx index 478da4c..e4ca10f 100644 --- a/web/src/App/AppRouter.jsx +++ b/web/src/App/AppRouter.jsx @@ -5,6 +5,7 @@ import LoginPage from "../Components/Pages/LoginPage/LoginPage.jsx"; import CoursesPage from "../Components/Pages/Courses/CoursesPage.jsx"; import MainLayout from "../Components/Layouts/MainLayout.jsx"; import ProfilePage from "../Components/Pages/ProfilePage/ProfilePage.jsx"; +import AdminPage from "../Components/Pages/AdminPage/AdminPage.jsx"; const AppRouter = () => ( @@ -20,9 +21,9 @@ const AppRouter = () => ( }> - {/*}>*/} - {/* } />*/} - {/**/} + }> + } /> + }/> diff --git a/web/src/Components/Layouts/MainLayout.jsx b/web/src/Components/Layouts/MainLayout.jsx index 3ea8d7d..8b399a6 100644 --- a/web/src/Components/Layouts/MainLayout.jsx +++ b/web/src/Components/Layouts/MainLayout.jsx @@ -3,7 +3,8 @@ import {Layout, Menu} from "antd"; import CoursesPage from "../Pages/Courses/CoursesPage.jsx"; import LoadingIndicator from "../Widgets/LoadingIndicator/LoadingIndicator.jsx"; import {Outlet} from "react-router-dom"; -import {BookOutlined, LogoutOutlined, UserOutlined} from "@ant-design/icons"; +import {BookOutlined, ControlOutlined, LogoutOutlined, UserOutlined} from "@ant-design/icons"; +import CONFIG from "../../Core/сonfig.js"; const {Content, Footer, Sider} = Layout; @@ -22,11 +23,20 @@ const MainLayout = () => { const menuItems = [ getItem("Мои курсы", "/courses", ), - getItem("Профиль", "/profile", ), - getItem("Выйти", "logout", ), {type: "divider"} ]; + if (user.role.title === CONFIG.ROOT_ROLE_NAME) { + menuItems.push( + getItem("Панель администратора", "/admin", ) + ) + } + + menuItems.push( + getItem("Профиль", "/profile", ), + getItem("Выйти", "logout", ), + ) + return ( { + const { + handleSelectUserToEdit, + rolesData, + filteredUsers, + handleSearch, + isLoading, + isError, + openCreateModal, + } = useAdminPage(); + + const columns = [ + { + title: "ID", + dataIndex: "id", + key: "id", + }, + { + title: "Фамилия", + dataIndex: "last_name", + key: "lastName", + sorter: (a, b) => a.last_name.localeCompare(b.last_name), + }, + { + title: "Имя", + dataIndex: "first_name", + key: "firstName", + sorter: (a, b) => a.first_name.localeCompare(b.first_name), + }, + { + title: "Отчество", + dataIndex: "patronymic", + key: "patronymic", + }, + { + title: "Роль", + dataIndex: ["role", "title"], + key: "role", + filters: rolesData.map(role => ({text: role.title, value: role.title})), + onFilter: (value, record) => record.role.title === value, + }, + { + title: "Статус", + dataIndex: "status", + key: "status", + }, + { + title: "Действия", + key: "actions", + render: (_, record) => ( + + ), + }, + ]; + + if (isError) { + return ; + } + + if (isLoading) { + return ; + } + + return ( + + + + <ControlOutlined/> Панель администратора + + + + + + + } type={"primary"}/> + + + + + + ) +}; + +export default AdminPage; \ No newline at end of file diff --git a/web/src/Components/Pages/AdminPage/CreateUserModalForm/CreateUserModalForm.jsx b/web/src/Components/Pages/AdminPage/CreateUserModalForm/CreateUserModalForm.jsx new file mode 100644 index 0000000..0f92d60 --- /dev/null +++ b/web/src/Components/Pages/AdminPage/CreateUserModalForm/CreateUserModalForm.jsx @@ -0,0 +1,108 @@ +import {Button, DatePicker, Form, Input, Modal, Select, Tooltip, Typography} from "antd"; +import useCreateUserModalForm from "./useCreateUserModalForm.js"; +import {CalendarOutlined, InfoCircleOutlined} from "@ant-design/icons"; +import dayjs from "dayjs"; + + +const CreateUserModalForm = () => { + const { + modalVisible, + handleCancel, + handleFinish, + form, + roles, + isLoading, + isError, + } = useCreateUserModalForm(); + + return ( + +
+ + + + + + + + + + + + + + + + + } + format="DD.MM.YYYY" + style={{width: "100%"}} + size="large" + maxDate={dayjs()} + /> + + + + + + + + + + + + + ({ + validator(_, value) { + if (!value || getFieldValue("password") === value) { + return Promise.resolve(); + } + return Promise.reject(new Error("Пароли не совпадают")); + }, + }),]}> + + + + + + + +
+ ) +}; + +export default CreateUserModalForm; \ No newline at end of file diff --git a/web/src/Components/Pages/AdminPage/CreateUserModalForm/useCreateUserModalForm.js b/web/src/Components/Pages/AdminPage/CreateUserModalForm/useCreateUserModalForm.js new file mode 100644 index 0000000..bd5a68f --- /dev/null +++ b/web/src/Components/Pages/AdminPage/CreateUserModalForm/useCreateUserModalForm.js @@ -0,0 +1,79 @@ +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"; + + +const useCreateUserModalForm = () => { + const dispatch = useDispatch(); + const [form] = Form.useForm(); + + const { + openModalCreateUser + } = useSelector(state => state.users); + + const modalVisible = openModalCreateUser; + + const handleCancel = () => { + dispatch(setOpenModalCreateUser(false)); + }; + + const [ + registerUser, + { + isLoading: isLoadingRegister, + isError: isErrorRegister, + } + ] = useCreateUserMutation(); + + const handleFinish = async () => { + const values = form.getFieldsValue(); + const payload = { + ...values, + birthdate: values.birthdate + ? values.birthdate.format("YYYY-MM-DD") + : null, + }; + + console.log(payload); + + try { + await registerUser(payload).unwrap(); + notification.success({ + title: "Пользователь зарегистрирован", + description: "Пользователь успешно зарегистрирован", + placement: "topRight", + }); + form.resetFields(); + handleCancel(); + } catch (error) { + notification.error({ + title: "Ошибка регистрации пользователя", + description: error?.data?.detail || "Не удалось зарегистрировать пользователя", + placement: "topRight", + }); + } + }; + + const { + data: roles = [], + isLoading: isLoadingRoles, + isError: isErrorRoles, + } = useGetAllRolesQuery(undefined, { + pollingInterval: 60000, + }); + + + return { + modalVisible, + handleCancel, + handleFinish, + form, + roles, + isLoading: isLoadingRoles, + isError: isErrorRoles, + }; +}; + +export default useCreateUserModalForm; \ No newline at end of file diff --git a/web/src/Components/Pages/AdminPage/useAdminPage.js b/web/src/Components/Pages/AdminPage/useAdminPage.js new file mode 100644 index 0000000..e9ae146 --- /dev/null +++ b/web/src/Components/Pages/AdminPage/useAdminPage.js @@ -0,0 +1,65 @@ +import {useGetAllUsersQuery} from "../../../Api/usersApi.js"; +import {useGetAllRolesQuery} from "../../../Api/rolesApi.js"; +import {useMemo, useState} from "react"; +import {useDispatch} from "react-redux"; +import {setOpenModalCreateUser, setSelectedUserToUpdate} from "../../../Redux/Slices/usersSlice.js"; + + +const useAdminPage = () => { + const dispatch = useDispatch(); + + const [ + searchString, + setSearchString, + ] = useState(""); + + const { + data: usersData = [], + isLoading: usersIsLoading, + isError: usersIsError, + } = useGetAllUsersQuery(undefined, { + pollingInterval: 20000, + }); + + const { + data: rolesData = [], + isLoading: rolesIsLoading, + isError: rolesIsError, + } = useGetAllRolesQuery(undefined, { + pollingInterval: 20000, + }); + + const handleSearch = (e) => { + setSearchString(e.target.value); + }; + + const filteredUsers = useMemo(() => { + return usersData.filter((user) => { + return Object.entries(user).some(([key, value]) => { + if (typeof value === "string") { + return value.toString().toLowerCase().includes(searchString.toLowerCase()); + } + }); + }); + }, [usersData, searchString]); + + const handleSelectUserToEdit = (user) => { + dispatch(setSelectedUserToUpdate(user)); + }; + + const openCreateModal = () => { + dispatch(setOpenModalCreateUser(true)); + }; + + return { + handleSelectUserToEdit, + rolesData, + filteredUsers, + handleSearch, + isLoading: usersIsLoading | rolesIsLoading, + isError: usersIsError | rolesIsError, + openCreateModal, + }; +}; + +export default useAdminPage; \ No newline at end of file diff --git a/web/src/Components/Pages/ProfilePage/ProfilePage.jsx b/web/src/Components/Pages/ProfilePage/ProfilePage.jsx index 58121f5..e7bbc50 100644 --- a/web/src/Components/Pages/ProfilePage/ProfilePage.jsx +++ b/web/src/Components/Pages/ProfilePage/ProfilePage.jsx @@ -1,10 +1,31 @@ import useProfilePage from "./useProfilePage.js"; -import {Button, Col, DatePicker, Divider, Form, Input, Result, Row, Typography} from "antd"; -import {UserOutlined} from "@ant-design/icons"; +import { + Button, + Card, + Col, + DatePicker, + Form, + Input, + Result, + Row, + Typography, + Avatar, + Space, + Divider, + Upload, +} from "antd"; +import { + UserOutlined, + MailOutlined, + LockOutlined, + CalendarOutlined, + SaveOutlined, + UploadOutlined, +} from "@ant-design/icons"; import dayjs from "dayjs"; import LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.jsx"; -const {Title} = Typography; +const {Title, Text} = Typography; const ProfilePage = () => { const { @@ -16,15 +37,15 @@ const ProfilePage = () => { isUpdateUserLoading, isUpdateUserError, onFinishProfileForm, - onFinishPasswordForm + onFinishPasswordForm, } = useProfilePage(); if (isError) { return ( ); } @@ -34,83 +55,157 @@ const ProfilePage = () => { } return ( -
- - - <UserOutlined/> Управление профилем - - + + + + } style={{backgroundColor: "#1890ff"}}/> +
+ + {userData?.first_name} {userData?.last_name} + + Роль: {userData?.role?.title || "Пользователь"} +
+ + } + style={{borderRadius: 12, boxShadow: "0 4px 12px rgba(0,0,0,0.05)", marginBottom: 24}} + > +
+ +
+ + } placeholder="Иван" size="large"/> + + - - - - - - - - - - - - - - - - - - - - - - - - - + + + } placeholder="Иванов" size="large"/> + + - Смена пароля - - + + } placeholder="Иванович" size="large"/> + + + + + + } placeholder="ivan@example.com" size="large"/> + + + + + + } + format="DD.MM.YYYY" + placeholder="15.03.1995" + style={{width: "100%"}} + size="large" + maxDate={dayjs()} + /> + + + + + + } size="large"/> + + + + + + + + + + + {/* Карточка смены пароля */} + Смена пароля} + bordered={false} + style={{borderRadius: 12, boxShadow: "0 4px 12px rgba(0,0,0,0.05)"}} > - - - - - - - - - - +
+ +
+ + } placeholder="••••••••" size="large"/> + + + + + ({ + validator(_, value) { + if (!value || getFieldValue("password") === value) { + return Promise.resolve(); + } + return Promise.reject(new Error("Пароли не совпадают")); + }, + }), + ]} + > + } placeholder="••••••••" size="large"/> + + + + + + + + + + + ); }; diff --git a/web/src/Core/сonfig.js b/web/src/Core/сonfig.js index 9196e01..6708d58 100644 --- a/web/src/Core/сonfig.js +++ b/web/src/Core/сonfig.js @@ -1,5 +1,6 @@ const CONFIG = { BASE_URL: import.meta.env.VITE_BASE_URL, + ROOT_ROLE_NAME: import.meta.env.VITE_ROOT_ROLE_NAME }; export default CONFIG; \ No newline at end of file diff --git a/web/src/Redux/Slices/usersSlice.js b/web/src/Redux/Slices/usersSlice.js new file mode 100644 index 0000000..02364bc --- /dev/null +++ b/web/src/Redux/Slices/usersSlice.js @@ -0,0 +1,23 @@ +import {createSlice} from "@reduxjs/toolkit"; + +const initialState = { + selectedUserToUpdate: null, + openModalCreateUser: false, +}; + +const usersSlice = createSlice({ + name: "users", + initialState, + reducers: { + setSelectedUserToUpdate(state, action) { + state.selectedUserToUpdate = action.payload; + }, + setOpenModalCreateUser(state, action) { + state.openModalCreateUser = action.payload; + }, + }, +}); + +export const {setSelectedUserToUpdate, setOpenModalCreateUser} = usersSlice.actions; + +export default usersSlice.reducer; \ No newline at end of file diff --git a/web/src/Redux/store.js b/web/src/Redux/store.js index 2b1845c..361d648 100644 --- a/web/src/Redux/store.js +++ b/web/src/Redux/store.js @@ -1,19 +1,25 @@ import {configureStore} from "@reduxjs/toolkit"; import authReducer from "./Slices/authSlice.js"; +import usersReducer from "./Slices/usersSlice.js"; import {authApi} from "../Api/authApi.js"; import {usersApi} from "../Api/usersApi.js"; +import {rolesApi} from "../Api/rolesApi.js"; export const store = configureStore({ reducer: { auth: authReducer, [authApi.reducerPath]: authApi.reducer, - [usersApi.reducerPath]: usersApi.reducer + users: usersReducer, + [usersApi.reducerPath]: usersApi.reducer, + + [rolesApi.reducerPath]: rolesApi.reducer, }, middleware: (getDefaultMiddleware) => ( getDefaultMiddleware().concat( authApi.middleware, usersApi.middleware, + rolesApi.middleware, ) ), });