From 49e4e2f3f125b6e0e376bf2fb38bc505dfac5066 Mon Sep 17 00:00:00 2001 From: andrei Date: Mon, 2 Jun 2025 21:18:56 +0500 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D0=B0=D0=B4=D0=BC=D0=B8=D0=BD-=D0=BF?= =?UTF-8?q?=D0=B0=D0=BD=D0=B5=D0=BB=D1=8C=20=D0=B8=20=D1=83=D0=BB=D1=83?= =?UTF-8?q?=D1=87=D1=88=D0=B5=D0=BD=D0=B0=20=D0=BD=D0=B0=D0=B2=D0=B8=D0=B3?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D1=8F.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web-app/src/App/AdminRoute.jsx | 14 ++++ web-app/src/App/AppRouter.jsx | 8 ++ web-app/src/App/ErrorBoundary.jsx | 3 +- web-app/src/Components/Layouts/MainLayout.jsx | 81 ++++++++++--------- .../src/Components/Layouts/useMainLayout.js | 20 +++++ .../src/Components/Layouts/useMainLayoutUI.js | 30 +++++++ .../Components/Pages/AdminPage/AdminPage.jsx | 0 .../useAppointmentCalendarUI.js | 10 ++- 8 files changed, 124 insertions(+), 42 deletions(-) create mode 100644 web-app/src/App/AdminRoute.jsx create mode 100644 web-app/src/Components/Layouts/useMainLayout.js create mode 100644 web-app/src/Components/Layouts/useMainLayoutUI.js create mode 100644 web-app/src/Components/Pages/AdminPage/AdminPage.jsx diff --git a/web-app/src/App/AdminRoute.jsx b/web-app/src/App/AdminRoute.jsx new file mode 100644 index 0000000..5765f7d --- /dev/null +++ b/web-app/src/App/AdminRoute.jsx @@ -0,0 +1,14 @@ +import { Navigate, Outlet } from "react-router-dom"; +import { useSelector } from "react-redux"; + +const AdminRoute = () => { + const { user } = useSelector((state) => state.auth); + + if (!user || user.role.title !== "Администратор") { + return ; + } + + return ; +}; + +export default AdminRoute; \ No newline at end of file diff --git a/web-app/src/App/AppRouter.jsx b/web-app/src/App/AppRouter.jsx index 5572421..602b013 100644 --- a/web-app/src/App/AppRouter.jsx +++ b/web-app/src/App/AppRouter.jsx @@ -8,6 +8,7 @@ import LensesSetsPage from "../Components/Pages/LensesSetsPage/LensesSetsPage.js import IssuesPage from "../Components/Pages/IssuesPage/IssuesPage.jsx"; import AppointmentsPage from "../Components/Pages/AppointmentsPage/AppointmentsPage.jsx"; import ProfilePage from "../Components/Pages/ProfilePage/ProfilePage.jsx"; +import AdminRoute from "./AdminRoute.jsx"; const AppRouter = () => ( @@ -24,6 +25,13 @@ const AppRouter = () => ( }/> + + }> + }> + } /> + + + }/> ); diff --git a/web-app/src/App/ErrorBoundary.jsx b/web-app/src/App/ErrorBoundary.jsx index 42b463c..51ff4fe 100644 --- a/web-app/src/App/ErrorBoundary.jsx +++ b/web-app/src/App/ErrorBoundary.jsx @@ -1,4 +1,5 @@ import { Component } from "react"; +import {Result} from "antd"; class ErrorBoundary extends Component { state = { hasError: false }; @@ -13,7 +14,7 @@ class ErrorBoundary extends Component { render() { if (this.state.hasError) { - return
Произошла ошибка
; + return ; } return this.props.children; } diff --git a/web-app/src/Components/Layouts/MainLayout.jsx b/web-app/src/Components/Layouts/MainLayout.jsx index 64bfaa6..323f772 100644 --- a/web-app/src/Components/Layouts/MainLayout.jsx +++ b/web-app/src/Components/Layouts/MainLayout.jsx @@ -1,6 +1,5 @@ -import {useState} from "react"; -import {Grid, Layout, Menu} from "antd"; -import {Outlet, useLocation, useNavigate} from "react-router-dom"; +import {Alert, Layout, Menu} from "antd"; +import {Outlet} from "react-router-dom"; import { HomeOutlined, CalendarOutlined, @@ -9,58 +8,56 @@ import { UserOutlined, TeamOutlined, LogoutOutlined, - MessageOutlined + MessageOutlined, ControlOutlined } from "@ant-design/icons"; -import useAuthUtils from "../../Hooks/useAuthUtils.js"; +import useMainLayout from "./useMainLayout.js"; +import useMainLayoutUI from "./useMainLayoutUI.js"; +import LoadingIndicator from "../Widgets/LoadingIndicator/LoadingIndicator.jsx"; const {Content, Footer, Sider} = Layout; -const getItem = (label, key, icon, children) => ({key, icon, children, label}); -const {useBreakpoint} = Grid; - const MainLayout = () => { - const screens = useBreakpoint(); - - const [collapsed, setCollapsed] = useState(true); - const navigate = useNavigate(); - const location = useLocation(); - const {logoutAndRedirect} = useAuthUtils(); + const mainLayoutData = useMainLayout(); + const mainLayoutUI = useMainLayoutUI(mainLayoutData.user); const menuItems = [ - getItem("Главная", "/", ), - getItem("Приёмы", "/appointments", ), - getItem("Выдачи линз", "/issues", ), - getItem("Линзы и наборы", "/Lenses", ), - getItem("Пациенты", "/Patients", ), - getItem("Рассылки", "/mailing", ), - {type: "divider"}, - getItem("Мой профиль", "profile", , [ - getItem("Перейти в профиль", "/profile", ), - getItem("Выйти", "logout", ) - ]) + mainLayoutUI.getItem("Главная", "/", ), + mainLayoutUI.getItem("Приёмы", "/appointments", ), + mainLayoutUI.getItem("Выдачи линз", "/issues", ), + mainLayoutUI.getItem("Линзы и наборы", "/Lenses", ), + mainLayoutUI.getItem("Пациенты", "/Patients", ), + mainLayoutUI.getItem("Рассылки", "/mailing", ), + { type: "divider" } ]; - const handleMenuClick = ({key}) => { - if (key === "logout") { - logoutAndRedirect(); - return; - } - navigate(key); - }; + if (mainLayoutData.user?.role.title === "Администратор") { + menuItems.push(mainLayoutUI.getItem("Панель администратора", "/admin", )); + } + + menuItems.push( + mainLayoutUI.getItem("Мой профиль", "profile", , [ + mainLayoutUI.getItem("Перейти в профиль", "/profile", ), + mainLayoutUI.getItem("Выйти", "logout", ) + ]) + ); + + if (mainLayoutData.isUserError) { + return + } return (
Логотип
{ selectedKeys={[location.pathname]} mode="inline" items={menuItems} - onClick={handleMenuClick} + onClick={mainLayoutUI.handleMenuClick} /> { borderRadius: 8, marginTop: "15px" }}> - + {mainLayoutData.isUserLoading ? ( + + ) : ( + + )} -
Линза+ © {new Date().getFullYear()}
+
); diff --git a/web-app/src/Components/Layouts/useMainLayout.js b/web-app/src/Components/Layouts/useMainLayout.js new file mode 100644 index 0000000..2a3b287 --- /dev/null +++ b/web-app/src/Components/Layouts/useMainLayout.js @@ -0,0 +1,20 @@ +import {useGetAuthenticatedUserDataQuery} from "../../Api/usersApi.js"; + +const useMainLayout = () => { + + const { + data: user, + isLoading: isUserLoading, + isError: isUserError, + } = useGetAuthenticatedUserDataQuery(undefined, { + pollingInterval: 20000, + }); + + return { + user, + isUserLoading, + isUserError, + }; +}; + +export default useMainLayout; \ No newline at end of file diff --git a/web-app/src/Components/Layouts/useMainLayoutUI.js b/web-app/src/Components/Layouts/useMainLayoutUI.js new file mode 100644 index 0000000..7d576aa --- /dev/null +++ b/web-app/src/Components/Layouts/useMainLayoutUI.js @@ -0,0 +1,30 @@ +import {useState} from "react"; +import {Grid} from "antd"; +import {useLocation, useNavigate} from "react-router-dom"; +import useAuthUtils from "../../Hooks/useAuthUtils.js"; + + +const {useBreakpoint} = Grid; + + +const useMainLayoutUI = () => { + const screens = useBreakpoint(); + + const [collapsed, setCollapsed] = useState(true); + const navigate = useNavigate(); + const location = useLocation(); + const {logoutAndRedirect} = useAuthUtils(); + + const handleMenuClick = ({key}) => { + if (key === "logout") { + logoutAndRedirect(); + return; + } + navigate(key); + }; + const getItem = (label, key, icon, children) => ({key, icon, children, label}); + + return {screens, collapsed, setCollapsed, location, handleMenuClick, getItem}; +}; + +export default useMainLayoutUI; \ No newline at end of file diff --git a/web-app/src/Components/Pages/AdminPage/AdminPage.jsx b/web-app/src/Components/Pages/AdminPage/AdminPage.jsx new file mode 100644 index 0000000..e69de29 diff --git a/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/useAppointmentCalendarUI.js b/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/useAppointmentCalendarUI.js index 35ce208..91882a3 100644 --- a/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/useAppointmentCalendarUI.js +++ b/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/useAppointmentCalendarUI.js @@ -9,6 +9,7 @@ import { setSelectedScheduledAppointment, setSelectedDate, } from "../../../../../Redux/Slices/appointmentsSlice.js"; +import {useEffect, useState} from "react"; dayjs.extend(utc); dayjs.extend(timezone); @@ -21,7 +22,14 @@ const useAppointmentCalendarUI = (appointments, scheduledAppointments) => { const selectedDate = dayjs.tz(useSelector((state) => state.appointmentsUI.selectedDate), "Europe/Moscow"); const screens = useBreakpoint(); - const fullScreenCalendar = !screens.xs; + const [fullScreenCalendar, setFullScreenCalendar] = useState(true); + + useEffect(() => { + const isMobile = window.innerWidth < 576; + setFullScreenCalendar(!isMobile); + + setFullScreenCalendar(!screens.xs); + }, [screens.xs]); const calendarContainerStyle = { padding: 20 };