From fda45296a460b8d8a3020a89a6ac4b6b3ecb3c15 Mon Sep 17 00:00:00 2001 From: andrei Date: Mon, 2 Jun 2025 20:40:08 +0500 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=9F=D1=80=D0=BE=D1=84=D0=B8=D0=BB?= =?UTF-8?q?=D1=8C=20=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82?= =?UTF-8?q?=D0=B5=D0=BB=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Добавлена функциональность профиля пользователя: * Получение данных пользователя * Редактирование профиля * Изменение пароля --- web-app/src/Api/usersApi.js | 14 ++++- web-app/src/App/App.jsx | 30 +++++++--- web-app/src/App/PrivateRoute.jsx | 10 ++-- web-app/src/App/main.jsx | 6 +- .../Pages/HomePage/useHomePageUI.js | 6 +- .../Pages/ProfilePage/ProfilePage.jsx | 56 ++++++++++++------- .../Pages/ProfilePage/useProfilePage.js | 25 +++++---- .../Pages/ProfilePage/useProfilePageUI.js | 7 ++- 8 files changed, 107 insertions(+), 47 deletions(-) diff --git a/web-app/src/Api/usersApi.js b/web-app/src/Api/usersApi.js index 22a80c2..51d249d 100644 --- a/web-app/src/Api/usersApi.js +++ b/web-app/src/Api/usersApi.js @@ -19,7 +19,19 @@ export const usersApi = createApi({ body: data, }), }), + updateUser: builder.mutation({ + query: ({userId, ...data}) => ({ + url: `/users/${userId}/`, + method: "PUT", + body: data, + }), + invalidatesTags: ['User'] + }), }), }); -export const {useGetAuthenticatedUserDataQuery} = usersApi; \ No newline at end of file +export const { + useGetAuthenticatedUserDataQuery, + useChangePasswordMutation, + useUpdateUserMutation, +} = usersApi; \ No newline at end of file diff --git a/web-app/src/App/App.jsx b/web-app/src/App/App.jsx index a33bec5..c9f1a63 100644 --- a/web-app/src/App/App.jsx +++ b/web-app/src/App/App.jsx @@ -1,22 +1,38 @@ import {BrowserRouter as Router} from "react-router-dom"; import AppRouter from "./AppRouter.jsx"; import "/src/Styles/app.css"; -import {Provider} from "react-redux"; -import store from "../Redux/store.js"; +import {useDispatch, useSelector} from "react-redux"; import dayjs from "dayjs"; import locale from 'antd/locale/ru_RU'; import {ConfigProvider} from "antd"; +import {useEffect} from "react"; +import {checkAuth} from "../Redux/Slices/authSlice.js"; +import LoadingIndicator from "../Components/Widgets/LoadingIndicator/LoadingIndicator.jsx"; +import ErrorBoundary from "./ErrorBoundary.jsx"; dayjs.locale('ru'); -const App = () => ( - +const App = () => { + const dispatch = useDispatch(); + const {isLoading} = useSelector((state) => state.auth); + + useEffect(() => { + dispatch(checkAuth()); + }, [dispatch]); + + if (isLoading) { + return ; + } + + return ( - + + + - -); + ); +}; export default App; diff --git a/web-app/src/App/PrivateRoute.jsx b/web-app/src/App/PrivateRoute.jsx index e45c724..e0a2f51 100644 --- a/web-app/src/App/PrivateRoute.jsx +++ b/web-app/src/App/PrivateRoute.jsx @@ -1,14 +1,14 @@ -import { Navigate, Outlet } from "react-router-dom"; -import { useSelector } from "react-redux"; +import {Navigate, Outlet} from "react-router-dom"; +import {useSelector} from "react-redux"; const PrivateRoute = () => { - const { user } = useSelector((state) => state.auth); + const {user} = useSelector((state) => state.auth); if (!user) { - return ; + return ; } - return ; + return ; }; export default PrivateRoute; \ No newline at end of file diff --git a/web-app/src/App/main.jsx b/web-app/src/App/main.jsx index 74ce178..3eb55c8 100644 --- a/web-app/src/App/main.jsx +++ b/web-app/src/App/main.jsx @@ -1,9 +1,13 @@ import {StrictMode} from 'react' import {createRoot} from 'react-dom/client' import App from './App.jsx' +import store from "../Redux/store.js"; +import {Provider} from "react-redux"; createRoot(document.getElementById('root')).render( - + + + ) diff --git a/web-app/src/Components/Pages/HomePage/useHomePageUI.js b/web-app/src/Components/Pages/HomePage/useHomePageUI.js index d4767df..1e50cc5 100644 --- a/web-app/src/Components/Pages/HomePage/useHomePageUI.js +++ b/web-app/src/Components/Pages/HomePage/useHomePageUI.js @@ -1,5 +1,5 @@ import { Grid } from "antd"; -import { useMemo } from "react"; +import {useEffect, useMemo} from "react"; import dayjs from "dayjs"; import isBetween from "dayjs/plugin/isBetween"; import {useSelector} from "react-redux"; // Import isBetween plugin @@ -18,6 +18,10 @@ const useHomePageUI = (appointments, scheduledAppointments, patients) => { const buttonStyle = { width: screens.xs ? "100%" : "auto" }; const chartContainerStyle = { padding: 16, background: "#fff", borderRadius: 4 }; + useEffect(() => { + document.title = "Главная страница"; + }, []); + const todayEvents = useMemo(() => { return [...appointments, ...scheduledAppointments].filter((event) => dayjs(event.appointment_datetime || event.scheduled_datetime).isSame(dayjs(), "day") diff --git a/web-app/src/Components/Pages/ProfilePage/ProfilePage.jsx b/web-app/src/Components/Pages/ProfilePage/ProfilePage.jsx index d7f5e01..0abce05 100644 --- a/web-app/src/Components/Pages/ProfilePage/ProfilePage.jsx +++ b/web-app/src/Components/Pages/ProfilePage/ProfilePage.jsx @@ -1,16 +1,32 @@ -import { Button, Card, Col, Form, Input, Modal, Row, Space, Typography, Result } from "antd"; -import { EditOutlined } from "@ant-design/icons"; +import {Button, Card, Col, Form, Input, Modal, Row, Space, Typography, Result} from "antd"; +import {EditOutlined} from "@ant-design/icons"; import LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.jsx"; import useProfilePage from "./useProfilePage.js"; import useProfilePageUI from "./useProfilePageUI.js"; -import { useSelector } from "react-redux"; +import {useSelector} from "react-redux"; const ProfilePage = () => { - const { userData, isLoading, isError, isUpdating, handleEditProfile, handleCancelEdit, handleSubmitProfile, handleSubmitPassword } = useProfilePage(); - const { containerStyle, cardStyle, buttonStyle, formStyle, profileFormRules, passwordFormRules, isMobile } = useProfilePageUI(); + const { + userData, + isLoading, + isError, + isUpdating, + handleEditProfile, + handleCancelEdit, + handleSubmitProfile, + handleSubmitPassword + } = useProfilePage(); + const { + containerStyle, + cardStyle, + buttonStyle, + formStyle, + profileFormRules, + passwordFormRules, + profileForm, + passwordForm + } = useProfilePageUI(); const editProfileModalVisible = useSelector((state) => state.usersUI.editProfileModalVisible); - const [profileForm] = Form.useForm(); - const [passwordForm] = Form.useForm(); if (isError) { return ( @@ -25,7 +41,7 @@ const ProfilePage = () => { return (
{isLoading ? ( - + ) : ( <> Профиль @@ -50,9 +66,9 @@ const ProfilePage = () => { @@ -77,16 +93,16 @@ const ProfilePage = () => { style={formStyle} > - + - + - + - + @@ -105,14 +121,16 @@ const ProfilePage = () => { style={formStyle} > Смена пароля - - + + - + - - + + diff --git a/web-app/src/Components/Pages/ProfilePage/useProfilePage.js b/web-app/src/Components/Pages/ProfilePage/useProfilePage.js index 24f5f1d..6fc0762 100644 --- a/web-app/src/Components/Pages/ProfilePage/useProfilePage.js +++ b/web-app/src/Components/Pages/ProfilePage/useProfilePage.js @@ -1,7 +1,11 @@ -import { useGetAuthenticatedUserDataQuery } from "../../../Api/usersApi.js"; -import { useDispatch } from "react-redux"; -import { openEditProfileModal, closeEditProfileModal } from "../../../Redux/Slices/usersSlice.js"; -import { notification } from "antd"; +import { + useChangePasswordMutation, + useGetAuthenticatedUserDataQuery, + useUpdateUserMutation +} from "../../../Api/usersApi.js"; +import {useDispatch} from "react-redux"; +import {openEditProfileModal, closeEditProfileModal} from "../../../Redux/Slices/usersSlice.js"; +import {notification} from "antd"; const useProfilePage = () => { const dispatch = useDispatch(); @@ -14,14 +18,9 @@ const useProfilePage = () => { pollingInterval: 20000, }); - // const [updateUserProfile, { isLoading: isUpdatingProfile }] = useUpdateUserProfileMutation(); - const updateUserProfile = () => {}; - const isUpdatingProfile = false; - const isErrorUpdatingProfile = false; + const [updateUserProfile, {isLoading: isUpdatingProfile}] = useUpdateUserMutation(); - // const [changePassword, { isLoading: isChangingPassword }] = useChangePasswordMutation(); - const changePassword = () => {}; - const isChangingPassword = false; + const [changePassword, {isLoading: isChangingPassword}] = useChangePasswordMutation(); const handleEditProfile = () => { dispatch(openEditProfileModal()); @@ -37,8 +36,9 @@ const useProfilePage = () => { first_name: values.first_name, last_name: values.last_name, patronymic: values.patronymic || null, + login: userData.login, }; - await updateUserProfile(profileData).unwrap(); + await updateUserProfile({userId: userData.id, ...profileData}).unwrap(); notification.success({ message: "Успех", description: "Профиль успешно обновлен.", @@ -60,6 +60,7 @@ const useProfilePage = () => { current_password: values.current_password, new_password: values.new_password, confirm_password: values.confirm_password, + user_id: userData.id, }; await changePassword(passwordData).unwrap(); notification.success({ diff --git a/web-app/src/Components/Pages/ProfilePage/useProfilePageUI.js b/web-app/src/Components/Pages/ProfilePage/useProfilePageUI.js index 61c5eb3..0421ed0 100644 --- a/web-app/src/Components/Pages/ProfilePage/useProfilePageUI.js +++ b/web-app/src/Components/Pages/ProfilePage/useProfilePageUI.js @@ -1,4 +1,4 @@ -import { Grid } from "antd"; +import {Form, Grid} from "antd"; const { useBreakpoint } = Grid; @@ -10,6 +10,9 @@ const useProfilePageUI = () => { const buttonStyle = { width: screens.xs ? "100%" : "auto" }; const formStyle = { maxWidth: 600 }; + const [profileForm] = Form.useForm(); + const [passwordForm] = Form.useForm(); + const profileFormRules = { first_name: [ { required: true, message: "Пожалуйста, введите имя" }, @@ -56,6 +59,8 @@ const useProfilePageUI = () => { formStyle, profileFormRules, passwordFormRules, + profileForm, + passwordForm, isMobile: screens.xs, }; };