From ceee769100d42141a092929d9842ac282126c462 Mon Sep 17 00:00:00 2001 From: andrei Date: Sat, 28 Jun 2025 18:05:21 +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=B1=D0=BB=D0=BE=D0=BA=D0=B8=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=BA=D0=B0=20=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2?= =?UTF-8?q?=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 --- ...64_0005_добавил_блокировку_пользователя.py | 30 +++++ api/app/domain/entities/user.py | 1 + api/app/domain/models/users.py | 3 +- api/app/infrastructure/users_service.py | 2 + api/k8s/helm/visus-api/values.yaml | 4 +- web-app/k8s/helm/visus-web/values.yaml | 4 +- .../UpdateUserModalForm.jsx | 2 +- .../Components/Pages/AdminPage/AdminPage.jsx | 14 ++- .../Components/Pages/LoginPage/LoginPage.jsx | 22 ++-- .../Pages/LoginPage/useLoginPage.js | 72 +++++++++++- .../Pages/LoginPage/useLoginPageUI.js | 78 ------------- .../Pages/ProfilePage/ProfilePage.jsx | 11 +- .../Pages/ProfilePage/useProfilePage.js | 27 ++++- .../Pages/ProfilePage/useProfilePageUI.js | 29 ----- .../ScheduledAppointmentsViewModal.jsx | 4 +- .../useScheduledAppointmentsViewModal.js | 104 ++++++++++++++++- .../useScheduledAppointmentsViewModalUI.js | 107 ------------------ .../Widgets/SelectViewMode/SelectViewMode.jsx | 4 +- ...lectViewModeUI.js => useSelectViewMode.js} | 4 +- 19 files changed, 268 insertions(+), 254 deletions(-) create mode 100644 api/app/database/migrations/versions/9e7ab1a46b64_0005_добавил_блокировку_пользователя.py delete mode 100644 web-app/src/Components/Pages/LoginPage/useLoginPageUI.js delete mode 100644 web-app/src/Components/Pages/ProfilePage/useProfilePageUI.js delete mode 100644 web-app/src/Components/Widgets/ScheduledAppointmentsViewModal/useScheduledAppointmentsViewModalUI.js rename web-app/src/Components/Widgets/SelectViewMode/{useSelectViewModeUI.js => useSelectViewMode.js} (74%) diff --git a/api/app/database/migrations/versions/9e7ab1a46b64_0005_добавил_блокировку_пользователя.py b/api/app/database/migrations/versions/9e7ab1a46b64_0005_добавил_блокировку_пользователя.py new file mode 100644 index 0000000..6212d23 --- /dev/null +++ b/api/app/database/migrations/versions/9e7ab1a46b64_0005_добавил_блокировку_пользователя.py @@ -0,0 +1,30 @@ +"""0005_добавил блокировку пользователя + +Revision ID: 9e7ab1a46b64 +Revises: 69fee5fc14c8 +Create Date: 2025-06-15 13:41:20.591874 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '9e7ab1a46b64' +down_revision: Union[str, None] = '69fee5fc14c8' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('users', sa.Column('is_blocked', sa.Boolean(), server_default='false', nullable=False)) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('users', 'is_blocked') + # ### end Alembic commands ### diff --git a/api/app/domain/entities/user.py b/api/app/domain/entities/user.py index bdabc64..d8f902c 100644 --- a/api/app/domain/entities/user.py +++ b/api/app/domain/entities/user.py @@ -11,6 +11,7 @@ class UserEntity(BaseModel): last_name: str patronymic: Optional[str] = None login: str + is_blocked: bool role_id: Optional[int] = None diff --git a/api/app/domain/models/users.py b/api/app/domain/models/users.py index 16dd08c..a6758b8 100644 --- a/api/app/domain/models/users.py +++ b/api/app/domain/models/users.py @@ -1,4 +1,4 @@ -from sqlalchemy import Column, Integer, VARCHAR, ForeignKey, String +from sqlalchemy import Column, Integer, VARCHAR, ForeignKey, String, Boolean from sqlalchemy.orm import relationship from werkzeug.security import check_password_hash, generate_password_hash @@ -13,6 +13,7 @@ class User(BaseModel): patronymic = Column(VARCHAR(200)) login = Column(String, nullable=False, unique=True) password = Column(String, nullable=False) + is_blocked = Column(Boolean, nullable=False, default=False, server_default='false') role_id = Column(Integer, ForeignKey('roles.id'), nullable=False) diff --git a/api/app/infrastructure/users_service.py b/api/app/infrastructure/users_service.py index dd039f1..1719170 100644 --- a/api/app/infrastructure/users_service.py +++ b/api/app/infrastructure/users_service.py @@ -180,6 +180,7 @@ class UsersService: last_name=user.last_name, patronymic=user.patronymic, login=user.login, + is_blocked=user.is_blocked, ) if user.id is not None: @@ -196,4 +197,5 @@ class UsersService: patronymic=user.patronymic, login=user.login, role_id=user.role_id, + is_blocked=user.is_blocked, ) diff --git a/api/k8s/helm/visus-api/values.yaml b/api/k8s/helm/visus-api/values.yaml index c00b8a9..0683a5e 100644 --- a/api/k8s/helm/visus-api/values.yaml +++ b/api/k8s/helm/visus-api/values.yaml @@ -11,8 +11,8 @@ service: resources: limits: - memory: 512Mi - cpu: 500m + memory: 128Mi + cpu: 200m persistence: path: /mnt/k8s_storage/visus-api diff --git a/web-app/k8s/helm/visus-web/values.yaml b/web-app/k8s/helm/visus-web/values.yaml index adc3d68..78384e4 100644 --- a/web-app/k8s/helm/visus-web/values.yaml +++ b/web-app/k8s/helm/visus-web/values.yaml @@ -11,8 +11,8 @@ service: resources: limits: - memory: 512Mi - cpu: 500m + memory: 128Mi + cpu: 200m ingress: secretTLSName: visus-web-tls-secret diff --git a/web-app/src/Components/Dummies/UpdateUserModalForm/UpdateUserModalForm.jsx b/web-app/src/Components/Dummies/UpdateUserModalForm/UpdateUserModalForm.jsx index f366d66..8cc17b2 100644 --- a/web-app/src/Components/Dummies/UpdateUserModalForm/UpdateUserModalForm.jsx +++ b/web-app/src/Components/Dummies/UpdateUserModalForm/UpdateUserModalForm.jsx @@ -1,4 +1,4 @@ -import {Button, Form, Input, Modal, Select, Space, Typography} from "antd"; +import {Button, Form, Input, Modal, Select, Space, Switch, Typography} from "antd"; import useUpdateUserModalFormUI from "./useUpdateUserModalFormUI.js"; import useUpdateUserModalForm from "./useUpdateUserModalForm.js"; diff --git a/web-app/src/Components/Pages/AdminPage/AdminPage.jsx b/web-app/src/Components/Pages/AdminPage/AdminPage.jsx index 48d3c33..8099656 100644 --- a/web-app/src/Components/Pages/AdminPage/AdminPage.jsx +++ b/web-app/src/Components/Pages/AdminPage/AdminPage.jsx @@ -1,4 +1,4 @@ -import {Table, Button, Result, Typography} from "antd"; +import {Table, Button, Result, Typography, Switch} from "antd"; import {ControlOutlined} from "@ant-design/icons"; import LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.jsx"; import useAdminPage from "./useAdminPage.js"; @@ -41,6 +41,18 @@ const AdminPage = () => { filters: adminPageData.roles.map(role => ({text: role.title, value: role.title})), onFilter: (value, record) => record.role.title === value, }, + { + title: "Заблокирован", + dataIndex: ["is_blocked"], + key: "is_blocked", + render: (value, record) => ( + + ) + }, { title: "Действия", key: "actions", diff --git a/web-app/src/Components/Pages/LoginPage/LoginPage.jsx b/web-app/src/Components/Pages/LoginPage/LoginPage.jsx index 343a05c..bf44b97 100644 --- a/web-app/src/Components/Pages/LoginPage/LoginPage.jsx +++ b/web-app/src/Components/Pages/LoginPage/LoginPage.jsx @@ -1,11 +1,9 @@ -import {Form, Input, Button, Row, Col, Typography, Image, Space} from "antd"; +import { Form, Input, Button, Row, Col, Typography, Image, Space } from "antd"; import useLoginPage from "./useLoginPage.js"; -import useLoginPageUI from "./useLoginPageUI.js"; -const {Title} = Typography; +const { Title } = Typography; const LoginPage = () => { - const {onFinish, isLoading} = useLoginPage(); const { containerStyle, formContainerStyle, @@ -13,8 +11,10 @@ const LoginPage = () => { logoBlockStyle, logoStyle, appNameStyle, - labels - } = useLoginPageUI(); + labels, + isLoading, + onFinish, + } = useLoginPage(); return ( @@ -35,19 +35,19 @@ const LoginPage = () => { {labels.title} -
+ - + - + diff --git a/web-app/src/Components/Pages/LoginPage/useLoginPage.js b/web-app/src/Components/Pages/LoginPage/useLoginPage.js index 1f2d670..cad7cc7 100644 --- a/web-app/src/Components/Pages/LoginPage/useLoginPage.js +++ b/web-app/src/Components/Pages/LoginPage/useLoginPage.js @@ -1,12 +1,73 @@ -import { useDispatch } from "react-redux"; -import { notification } from "antd"; +import { useEffect, useRef } from "react"; +import { useNavigate } from "react-router-dom"; +import { useDispatch, useSelector } from "react-redux"; +import { Grid, notification } from "antd"; import { setError, setUser } from "../../../Redux/Slices/authSlice.js"; import { useLoginMutation } from "../../../Api/authApi.js"; import { checkAuth } from "../../../Redux/Slices/authSlice.js"; +const { useBreakpoint } = Grid; + const useLoginPage = () => { + const navigate = useNavigate(); const dispatch = useDispatch(); const [loginUser, { isLoading }] = useLoginMutation(); + const { user, userData } = useSelector((state) => state.auth); + const screens = useBreakpoint(); + const hasRedirected = useRef(false); + + const containerStyle = { + minHeight: "100vh", + }; + + const formContainerStyle = { + padding: screens.xs ? 10 : 20, + border: "1px solid #ddd", + borderRadius: 8, + textAlign: "center", + }; + + const titleStyle = { + textAlign: "center", + marginBottom: 20, + }; + + const logoStyle = { + width: 80, + marginBottom: 10, + borderRadius: 20, + border: "1px solid #ddd", + }; + + const appNameStyle = { + textAlign: "center", + color: "#1890ff", + marginBottom: 40, + }; + + const logoBlockStyle = { + display: "flex", + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + }; + + const labels = { + title: "Авторизация", + loginPlaceholder: "Логин", + passwordPlaceholder: "Пароль", + submitButton: "Войти", + loginRequired: "Пожалуйста, введите логин", + passwordRequired: "Пожалуйста, введите пароль", + }; + + useEffect(() => { + if (user && userData && !isLoading && !hasRedirected.current) { + hasRedirected.current = true; + navigate("/"); + } + document.title = labels.title; + }, [user, userData, isLoading, navigate]); const onFinish = async (loginData) => { try { @@ -32,6 +93,13 @@ const useLoginPage = () => { }; return { + containerStyle, + formContainerStyle, + titleStyle, + logoStyle, + appNameStyle, + labels, + logoBlockStyle, onFinish, isLoading, }; diff --git a/web-app/src/Components/Pages/LoginPage/useLoginPageUI.js b/web-app/src/Components/Pages/LoginPage/useLoginPageUI.js deleted file mode 100644 index 91af0ef..0000000 --- a/web-app/src/Components/Pages/LoginPage/useLoginPageUI.js +++ /dev/null @@ -1,78 +0,0 @@ -import { useEffect, useRef } from "react"; -import { useNavigate } from "react-router-dom"; -import { useSelector } from "react-redux"; -import { Grid } from "antd"; - -const { useBreakpoint } = Grid; - -const useLoginPageUI = () => { - const navigate = useNavigate(); - const { user, userData, isLoading } = useSelector((state) => state.auth); - const screens = useBreakpoint(); - const hasRedirected = useRef(false); - - const containerStyle = { - minHeight: "100vh", - }; - - const formContainerStyle = { - padding: screens.xs ? 10 : 20, - border: "1px solid #ddd", - borderRadius: 8, - textAlign: "center", - }; - - const titleStyle = { - textAlign: "center", - marginBottom: 20, - }; - - const logoStyle = { - width: 80, - marginBottom: 10, - borderRadius: 20, - border: "1px solid #ddd", - }; - - const appNameStyle = { - textAlign: "center", - color: "#1890ff", - marginBottom: 40, - }; - - const logoBlockStyle = { - display: "flex", - flexDirection: "row", - alignItems: "center", - justifyContent: "center", - }; - - const labels = { - title: "Авторизация", - loginPlaceholder: "Логин", - passwordPlaceholder: "Пароль", - submitButton: "Войти", - loginRequired: "Пожалуйста, введите логин", - passwordRequired: "Пожалуйста, введите пароль", - }; - - useEffect(() => { - if (user && userData && !isLoading && !hasRedirected.current) { - hasRedirected.current = true; - navigate("/"); - } - document.title = labels.title; - }, [user, userData, isLoading, navigate]); - - return { - containerStyle, - formContainerStyle, - titleStyle, - logoStyle, - appNameStyle, - labels, - logoBlockStyle, - }; -}; - -export default useLoginPageUI; \ No newline at end of file diff --git a/web-app/src/Components/Pages/ProfilePage/ProfilePage.jsx b/web-app/src/Components/Pages/ProfilePage/ProfilePage.jsx index 357c192..e61f1f1 100644 --- a/web-app/src/Components/Pages/ProfilePage/ProfilePage.jsx +++ b/web-app/src/Components/Pages/ProfilePage/ProfilePage.jsx @@ -2,22 +2,19 @@ import {Button, Card, Col, Row, Typography, Result} from "antd"; import {EditOutlined, UserOutlined} from "@ant-design/icons"; import LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.jsx"; import useProfilePage from "./useProfilePage.js"; -import useProfilePageUI from "./useProfilePageUI.js"; import UpdateUserModalForm from "../../Dummies/UpdateUserModalForm/UpdateUserModalForm.jsx"; const ProfilePage = () => { - const { - userData, - isLoading, - isError, - } = useProfilePage(); const { containerStyle, cardStyle, buttonStyle, + isLoading, + isError, + userData, handleEditUser, - } = useProfilePageUI(userData); + } = useProfilePage(); if (isError) { return ( diff --git a/web-app/src/Components/Pages/ProfilePage/useProfilePage.js b/web-app/src/Components/Pages/ProfilePage/useProfilePage.js index fe3928c..430126c 100644 --- a/web-app/src/Components/Pages/ProfilePage/useProfilePage.js +++ b/web-app/src/Components/Pages/ProfilePage/useProfilePage.js @@ -1,8 +1,14 @@ -import { - useGetAuthenticatedUserDataQuery, -} from "../../../Api/usersApi.js"; +import {Grid} from "antd"; +import {useDispatch} from "react-redux"; +import {setSelectedUser} from "../../../Redux/Slices/usersSlice.js"; +import {useGetAuthenticatedUserDataQuery} from "../../../Api/usersApi.js"; + +const {useBreakpoint} = Grid; const useProfilePage = () => { + const dispatch = useDispatch(); + const screens = useBreakpoint(); + const { data: userData = {}, isLoading: isLoadingUserData, @@ -11,10 +17,23 @@ const useProfilePage = () => { pollingInterval: 20000, }); + const containerStyle = {padding: screens.xs ? 16 : 24}; + const cardStyle = {marginBottom: 24}; + const buttonStyle = {width: screens.xs ? "100%" : "auto"}; + + + const handleEditUser = () => { + dispatch(setSelectedUser(userData)) + }; + return { - userData, + containerStyle, + cardStyle, + buttonStyle, + isMobile: screens.xs, isLoading: isLoadingUserData, isError: isErrorUserData, + handleEditUser, }; }; diff --git a/web-app/src/Components/Pages/ProfilePage/useProfilePageUI.js b/web-app/src/Components/Pages/ProfilePage/useProfilePageUI.js deleted file mode 100644 index ce29395..0000000 --- a/web-app/src/Components/Pages/ProfilePage/useProfilePageUI.js +++ /dev/null @@ -1,29 +0,0 @@ -import {Grid} from "antd"; -import {useDispatch} from "react-redux"; -import {setSelectedUser} from "../../../Redux/Slices/usersSlice.js"; - -const { useBreakpoint } = Grid; - -const useProfilePageUI = (userData) => { - const dispatch = useDispatch(); - const screens = useBreakpoint(); - - const containerStyle = { padding: screens.xs ? 16 : 24 }; - const cardStyle = { marginBottom: 24 }; - const buttonStyle = { width: screens.xs ? "100%" : "auto" }; - - - const handleEditUser = () => { - dispatch(setSelectedUser(userData)) - }; - - return { - containerStyle, - cardStyle, - buttonStyle, - isMobile: screens.xs, - handleEditUser, - }; -}; - -export default useProfilePageUI; \ No newline at end of file diff --git a/web-app/src/Components/Widgets/ScheduledAppointmentsViewModal/ScheduledAppointmentsViewModal.jsx b/web-app/src/Components/Widgets/ScheduledAppointmentsViewModal/ScheduledAppointmentsViewModal.jsx index 837437a..8419ac5 100644 --- a/web-app/src/Components/Widgets/ScheduledAppointmentsViewModal/ScheduledAppointmentsViewModal.jsx +++ b/web-app/src/Components/Widgets/ScheduledAppointmentsViewModal/ScheduledAppointmentsViewModal.jsx @@ -1,9 +1,7 @@ import {Button, Modal, Popconfirm, Row, Typography} from "antd"; import useScheduledAppointmentsViewModal from "./useScheduledAppointmentsViewModal.js"; -import useScheduledAppointmentsViewModalUI from "./useScheduledAppointmentsViewModalUI.js"; const ScheduledAppointmentsViewModal = () => { - const scheduledAppointmentsViewModalData = useScheduledAppointmentsViewModal(); const { selectedScheduledAppointment, modalWidth, @@ -19,7 +17,7 @@ const ScheduledAppointmentsViewModal = () => { onCancel, cancelScheduledAppointment, handleConvertToAppointment, - } = useScheduledAppointmentsViewModalUI(scheduledAppointmentsViewModalData.cancelAppointment); + } = useScheduledAppointmentsViewModal(); if (!selectedScheduledAppointment) { return null; diff --git a/web-app/src/Components/Widgets/ScheduledAppointmentsViewModal/useScheduledAppointmentsViewModal.js b/web-app/src/Components/Widgets/ScheduledAppointmentsViewModal/useScheduledAppointmentsViewModal.js index b0ab3e1..a80661e 100644 --- a/web-app/src/Components/Widgets/ScheduledAppointmentsViewModal/useScheduledAppointmentsViewModal.js +++ b/web-app/src/Components/Widgets/ScheduledAppointmentsViewModal/useScheduledAppointmentsViewModal.js @@ -1,10 +1,110 @@ +import { useDispatch, useSelector } from "react-redux"; +import { setSelectedScheduledAppointment, openModalWithScheduledData } from "../../../Redux/Slices/appointmentsSlice.js"; +import { notification } from "antd"; +import dayjs from "dayjs"; import {useCancelScheduledAppointmentMutation} from "../../../Api/scheduledAppointmentsApi.js"; - const useScheduledAppointmentsViewModal = () => { + const dispatch = useDispatch(); + const { selectedScheduledAppointment } = useSelector((state) => state.appointmentsUI); const [cancelAppointment] = useCancelScheduledAppointmentMutation(); - return {cancelAppointment}; + + const modalWidth = 700; + const blockStyle = { marginBottom: 16 }; + const footerRowStyle = { marginTop: 16, gap: 8 }; + const footerButtonStyle = { marginRight: 8 }; + + const labels = { + title: "Просмотр запланированного приема", + patient: "Пациент:", + birthday: "Дата рождения:", + email: "Email:", + phone: "Телефон:", + type: "Тип приема:", + appointmentTime: "Время приема:", + closeButton: "Закрыть", + convertButton: "Конвертировать в прием", + cancelButton: "Отмена приема", + popconfirmTitle: "Вы уверены, что хотите отменить прием?", + popconfirmOk: "Да, отменить", + popconfirmCancel: "Отмена", + notSpecified: "Не указан", + }; + + const visible = !!selectedScheduledAppointment; + + const getDateString = (date) => { + return date ? dayjs(date).format("DD.MM.YYYY") : labels.notSpecified; + }; + + const getAppointmentTime = (datetime) => { + return datetime + ? dayjs(datetime).format("DD.MM.YYYY HH:mm") + : labels.notSpecified; + }; + + const getPatientName = (patient) => { + return patient + ? `${patient.last_name} ${patient.first_name}` + : labels.notSpecified; + }; + + const getPatientField = (field) => { + return field || labels.notSpecified; + }; + + const onCancel = () => { + dispatch(setSelectedScheduledAppointment(null)); + }; + + const cancelScheduledAppointment = async () => { + try { + await cancelAppointment(selectedScheduledAppointment.id); + notification.success({ + message: "Прием отменен", + placement: "topRight", + description: "Прием успешно отменен.", + }); + onCancel(); + } catch (error) { + notification.error({ + message: "Ошибка", + description: error?.data?.detail || "Не удалось отменить прием.", + placement: "topRight", + }); + } + }; + + const handleConvertToAppointment = () => { + if (selectedScheduledAppointment) { + dispatch( + openModalWithScheduledData({ + id: selectedScheduledAppointment.id, + patient_id: selectedScheduledAppointment.patient?.id, + type_id: selectedScheduledAppointment.type?.id, + appointment_datetime: selectedScheduledAppointment.scheduled_datetime, + }) + ); + } + }; + + return { + selectedScheduledAppointment, + modalWidth, + blockStyle, + footerRowStyle, + footerButtonStyle, + labels, + visible, + getDateString, + getAppointmentTime, + getPatientName, + getPatientField, + onCancel, + cancelScheduledAppointment, + handleConvertToAppointment, + }; }; export default useScheduledAppointmentsViewModal; \ No newline at end of file diff --git a/web-app/src/Components/Widgets/ScheduledAppointmentsViewModal/useScheduledAppointmentsViewModalUI.js b/web-app/src/Components/Widgets/ScheduledAppointmentsViewModal/useScheduledAppointmentsViewModalUI.js deleted file mode 100644 index 3c856e6..0000000 --- a/web-app/src/Components/Widgets/ScheduledAppointmentsViewModal/useScheduledAppointmentsViewModalUI.js +++ /dev/null @@ -1,107 +0,0 @@ -import { useDispatch, useSelector } from "react-redux"; -import { setSelectedScheduledAppointment, openModalWithScheduledData } from "../../../Redux/Slices/appointmentsSlice.js"; -import { notification } from "antd"; -import dayjs from "dayjs"; - -const useScheduledAppointmentsViewModalUI = (cancelAppointment) => { - const dispatch = useDispatch(); - const { selectedScheduledAppointment } = useSelector((state) => state.appointmentsUI); - - const modalWidth = 700; - const blockStyle = { marginBottom: 16 }; - const footerRowStyle = { marginTop: 16, gap: 8 }; - const footerButtonStyle = { marginRight: 8 }; - - const labels = { - title: "Просмотр запланированного приема", - patient: "Пациент:", - birthday: "Дата рождения:", - email: "Email:", - phone: "Телефон:", - type: "Тип приема:", - appointmentTime: "Время приема:", - closeButton: "Закрыть", - convertButton: "Конвертировать в прием", - cancelButton: "Отмена приема", - popconfirmTitle: "Вы уверены, что хотите отменить прием?", - popconfirmOk: "Да, отменить", - popconfirmCancel: "Отмена", - notSpecified: "Не указан", - }; - - const visible = !!selectedScheduledAppointment; - - const getDateString = (date) => { - return date ? dayjs(date).format("DD.MM.YYYY") : labels.notSpecified; - }; - - const getAppointmentTime = (datetime) => { - return datetime - ? dayjs(datetime).format("DD.MM.YYYY HH:mm") - : labels.notSpecified; - }; - - const getPatientName = (patient) => { - return patient - ? `${patient.last_name} ${patient.first_name}` - : labels.notSpecified; - }; - - const getPatientField = (field) => { - return field || labels.notSpecified; - }; - - const onCancel = () => { - dispatch(setSelectedScheduledAppointment(null)); - }; - - const cancelScheduledAppointment = async () => { - try { - await cancelAppointment(selectedScheduledAppointment.id); - notification.success({ - message: "Прием отменен", - placement: "topRight", - description: "Прием успешно отменен.", - }); - onCancel(); - } catch (error) { - notification.error({ - message: "Ошибка", - description: error?.data?.detail || "Не удалось отменить прием.", - placement: "topRight", - }); - } - }; - - const handleConvertToAppointment = () => { - if (selectedScheduledAppointment) { - dispatch( - openModalWithScheduledData({ - id: selectedScheduledAppointment.id, - patient_id: selectedScheduledAppointment.patient?.id, - type_id: selectedScheduledAppointment.type?.id, - appointment_datetime: selectedScheduledAppointment.scheduled_datetime, - }) - ); - } - }; - - return { - selectedScheduledAppointment, - modalWidth, - blockStyle, - footerRowStyle, - footerButtonStyle, - labels, - visible, - getDateString, - getAppointmentTime, - getPatientName, - getPatientField, - onCancel, - cancelScheduledAppointment, - handleConvertToAppointment, - }; -}; - -export default useScheduledAppointmentsViewModalUI; \ No newline at end of file diff --git a/web-app/src/Components/Widgets/SelectViewMode/SelectViewMode.jsx b/web-app/src/Components/Widgets/SelectViewMode/SelectViewMode.jsx index c632d3b..4792855 100644 --- a/web-app/src/Components/Widgets/SelectViewMode/SelectViewMode.jsx +++ b/web-app/src/Components/Widgets/SelectViewMode/SelectViewMode.jsx @@ -1,12 +1,12 @@ import { Select, Tooltip } from "antd"; import PropTypes from "prop-types"; import { ViewModPropType } from "../../../Types/viewModPropType.js"; -import useSelectViewModeUI from "./useSelectViewModeUI.js"; +import useSelectViewMode from "./useSelectViewMode.js"; const { Option } = Select; const SelectViewMode = ({ viewMode, setViewMode, localStorageKey, toolTipText, viewModes }) => { - const { selectStyle, handleChange } = useSelectViewModeUI({ setViewMode, localStorageKey }); + const { selectStyle, handleChange } = useSelectViewMode({ setViewMode, localStorageKey }); return ( diff --git a/web-app/src/Components/Widgets/SelectViewMode/useSelectViewModeUI.js b/web-app/src/Components/Widgets/SelectViewMode/useSelectViewMode.js similarity index 74% rename from web-app/src/Components/Widgets/SelectViewMode/useSelectViewModeUI.js rename to web-app/src/Components/Widgets/SelectViewMode/useSelectViewMode.js index 1c0d283..42c5a3d 100644 --- a/web-app/src/Components/Widgets/SelectViewMode/useSelectViewModeUI.js +++ b/web-app/src/Components/Widgets/SelectViewMode/useSelectViewMode.js @@ -1,6 +1,6 @@ import { cacheInfo } from "../../../Utils/cachedInfoUtils.js"; -const useSelectViewModeUI = ({ setViewMode, localStorageKey }) => { +const useSelectViewMode = ({ setViewMode, localStorageKey }) => { const selectStyle = { width: "100%", }; @@ -16,4 +16,4 @@ const useSelectViewModeUI = ({ setViewMode, localStorageKey }) => { }; }; -export default useSelectViewModeUI; \ No newline at end of file +export default useSelectViewMode; \ No newline at end of file