diff --git a/web-app/src/Components/Dummies/AppointmentFormModal/AppointmentFormModal.jsx b/web-app/src/Components/Dummies/AppointmentFormModal/AppointmentFormModal.jsx index 00c7970..67f952a 100644 --- a/web-app/src/Components/Dummies/AppointmentFormModal/AppointmentFormModal.jsx +++ b/web-app/src/Components/Dummies/AppointmentFormModal/AppointmentFormModal.jsx @@ -18,7 +18,7 @@ import { } from "antd"; import useAppointmentFormModal from "./useAppointmentFormModal.js"; import useAppointmentFormModalUI from "./useAppointmentFormModalUI.js"; -import LoadingIndicator from "../../Widgets/LoadingIndicator.jsx"; +import LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.jsx"; import {useMemo} from "react"; const AppointmentFormModal = () => { diff --git a/web-app/src/Components/Pages/AppointmentsPage/AppointmentsPage.jsx b/web-app/src/Components/Pages/AppointmentsPage/AppointmentsPage.jsx index 6a0f884..22abf85 100644 --- a/web-app/src/Components/Pages/AppointmentsPage/AppointmentsPage.jsx +++ b/web-app/src/Components/Pages/AppointmentsPage/AppointmentsPage.jsx @@ -11,7 +11,7 @@ import AppointmentsCalendarTab from "./Components/AppointmentCalendarTab/Appoint import useAppointmentsUI from "./useAppointmentsUI.js"; import useAppointments from "./useAppointments.js"; import dayjs from 'dayjs'; -import LoadingIndicator from "../../Widgets/LoadingIndicator.jsx"; +import LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.jsx"; import AppointmentFormModal from "../../Dummies/AppointmentFormModal/AppointmentFormModal.jsx"; import {useDispatch} from "react-redux"; import { diff --git a/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/AppointmentsCalendarTab.jsx b/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/AppointmentsCalendarTab.jsx index f675b35..f423a4d 100644 --- a/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/AppointmentsCalendarTab.jsx +++ b/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/AppointmentsCalendarTab.jsx @@ -1,6 +1,6 @@ import { Calendar } from "antd"; import "dayjs/locale/ru"; -import CalendarCell from "../../../../Widgets/CalendarCell.jsx"; +import CalendarCell from "../CalendarCell/CalendarCell.jsx"; import useAppointments from "../../useAppointments.js"; import useAppointmentCalendarUI from "./useAppointmentCalendarUI.js"; import AppointmentsListModal from "../AppointmentsListModal/AppointmentsListModal.jsx"; diff --git a/web-app/src/Components/Pages/AppointmentsPage/Components/CalendarCell/CalendarCell.jsx b/web-app/src/Components/Pages/AppointmentsPage/Components/CalendarCell/CalendarCell.jsx new file mode 100644 index 0000000..c42b6e8 --- /dev/null +++ b/web-app/src/Components/Pages/AppointmentsPage/Components/CalendarCell/CalendarCell.jsx @@ -0,0 +1,73 @@ +import { Badge, Col, Tag, Tooltip } from "antd"; +import PropTypes from "prop-types"; +import { AppointmentPropType } from "../../../../../Types/appointmentPropType.js"; +import { ScheduledAppointmentPropType } from "../../../../../Types/scheduledAppointmentPropType.js"; +import useCalendarCellUI from "./useCalendarCellUI.js"; + +const CalendarCell = ({ allAppointments, onCellClick, onItemClick }) => { + const { + containerRef, + isCompressed, + containerStyle, + listStyle, + columnStyle, + tagStyle, + badgeTextStyle, + compressedCountStyle, + getTooltipTitle, + getBadgeText, + getTagColor, + getBadgeStatus, + } = useCalendarCellUI(); + + return ( +
+ {!isCompressed && ( + + )} + {isCompressed && ( +
+ {allAppointments.length > 0 && `+${allAppointments.length}`} +
+ )} +
+ ); +}; + +CalendarCell.propTypes = { + allAppointments: PropTypes.arrayOf( + PropTypes.oneOfType([AppointmentPropType, ScheduledAppointmentPropType]) + ).isRequired, + onCellClick: PropTypes.func.isRequired, + onItemClick: PropTypes.func.isRequired, +}; + +export default CalendarCell; \ No newline at end of file diff --git a/web-app/src/Components/Pages/AppointmentsPage/Components/CalendarCell/useCalendarCellUI.js b/web-app/src/Components/Pages/AppointmentsPage/Components/CalendarCell/useCalendarCellUI.js new file mode 100644 index 0000000..7557da1 --- /dev/null +++ b/web-app/src/Components/Pages/AppointmentsPage/Components/CalendarCell/useCalendarCellUI.js @@ -0,0 +1,114 @@ +import { useEffect, useRef, useState } from "react"; +import dayjs from "dayjs"; + +const useCalendarCellUI = () => { + const containerRef = useRef(null); + const [isCompressed, setIsCompressed] = useState(false); + const COMPRESSION_THRESHOLD = 70; + + useEffect(() => { + if (!containerRef.current) return; + + const observer = new ResizeObserver((entries) => { + const width = entries[0].contentRect.width; + setIsCompressed(width < COMPRESSION_THRESHOLD); + }); + + observer.observe(containerRef.current); + return () => observer.disconnect(); + }, []); + + // Styles + const containerStyle = { + height: "100%", + cursor: isCompressed ? "pointer" : "default", + position: "relative", + }; + + const listStyle = { + padding: 0, + margin: 0, + }; + + const columnStyle = { + overflowX: "hidden", + }; + + const tagStyle = { + margin: "2px 2px 0 0", + cursor: "pointer", + width: "95%", + minHeight: 30, + display: "flex", + alignItems: "center", + overflow: "hidden", + }; + + const badgeTextStyle = { + whiteSpace: "nowrap", + overflow: "hidden", + textOverflow: "ellipsis", + display: "inline-block", + width: "100%", + }; + + const compressedCountStyle = { + position: "absolute", + top: 2, + right: 2, + fontSize: 10, + fontWeight: "bold", + color: "#1890ff", + }; + + // Static configuration + const labels = { + pastAppointment: "Прошедший прием", + scheduledAppointment: "Запланированный прием", + notSpecified: "Не указан", + }; + + // Formatting functions + const getAppointmentTime = (datetime) => { + return datetime ? dayjs(datetime).format("HH:mm") : labels.notSpecified; + }; + + const getTooltipTitle = (app) => { + const type = app.appointment_datetime ? labels.pastAppointment : labels.scheduledAppointment; + return `${type}: ${getAppointmentTime(app.appointment_datetime || app.scheduled_datetime)}`; + }; + + const getBadgeText = (app) => { + const time = getAppointmentTime(app.appointment_datetime || app.scheduled_datetime); + const patientName = app.patient + ? `${app.patient.last_name || ""} ${app.patient.first_name || ""}`.trim() + : labels.notSpecified; + return `${time} ${patientName}`; + }; + + const getTagColor = (isPast) => { + return isPast ? "green" : "blue"; + }; + + const getBadgeStatus = (isPast) => { + return isPast ? "success" : "processing"; + }; + + return { + containerRef, + isCompressed, + containerStyle, + listStyle, + columnStyle, + tagStyle, + badgeTextStyle, + compressedCountStyle, + labels, + getTooltipTitle, + getBadgeText, + getTagColor, + getBadgeStatus, + }; +}; + +export default useCalendarCellUI; \ No newline at end of file diff --git a/web-app/src/Components/Pages/HomePage/HomePage.jsx b/web-app/src/Components/Pages/HomePage/HomePage.jsx index e1047c1..84818d3 100644 --- a/web-app/src/Components/Pages/HomePage/HomePage.jsx +++ b/web-app/src/Components/Pages/HomePage/HomePage.jsx @@ -8,7 +8,7 @@ import { import useHomePage from "./useHomePage.js"; import useHomePageUI from "./useHomePageUI.js"; import dayjs from "dayjs"; -import LoadingIndicator from "../../Widgets/LoadingIndicator.jsx"; +import LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.jsx"; import {Bar} from "react-chartjs-2"; import { Chart as ChartJS, diff --git a/web-app/src/Components/Pages/IssuesPage/IssuesPage.jsx b/web-app/src/Components/Pages/IssuesPage/IssuesPage.jsx index 4699408..139a929 100644 --- a/web-app/src/Components/Pages/IssuesPage/IssuesPage.jsx +++ b/web-app/src/Components/Pages/IssuesPage/IssuesPage.jsx @@ -16,8 +16,8 @@ import {DatabaseOutlined, PlusOutlined, UnorderedListOutlined} from "@ant-design import LensIssueViewModal from "./Components/LensIssueViewModal/LensIssueViewModal.jsx"; import dayjs from "dayjs"; import LensIssueFormModal from "./Components/LensIssueFormModal/LensIssueFormModal.jsx"; -import SelectViewMode from "../../Widgets/SelectViewMode.jsx"; -import LoadingIndicator from "../../Widgets/LoadingIndicator.jsx"; +import SelectViewMode from "../../Widgets/SelectViewMode/SelectViewMode.jsx"; +import LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.jsx"; import useIssues from "./useIssues.js"; import useIssuesUI from "./useIssuesUI.js"; diff --git a/web-app/src/Components/Pages/LensesSetsPage/Components/LensesTab/LensesTab.jsx b/web-app/src/Components/Pages/LensesSetsPage/Components/LensesTab/LensesTab.jsx index 35b80ce..86b2b3e 100644 --- a/web-app/src/Components/Pages/LensesSetsPage/Components/LensesTab/LensesTab.jsx +++ b/web-app/src/Components/Pages/LensesSetsPage/Components/LensesTab/LensesTab.jsx @@ -24,8 +24,8 @@ import { } from "@ant-design/icons"; import LensCard from "../../../../Dummies/LensListCard.jsx"; import LensFormModal from "./Components/LensFormModal/LensFormModal.jsx"; -import SelectViewMode from "../../../../Widgets/SelectViewMode.jsx"; -import LoadingIndicator from "../../../../Widgets/LoadingIndicator.jsx"; +import SelectViewMode from "../../../../Widgets/SelectViewMode/SelectViewMode.jsx"; +import LoadingIndicator from "../../../../Widgets/LoadingIndicator/LoadingIndicator.jsx"; import useLenses from "./useLenses.js"; import useLensesUI from "./useLensesUI.js"; diff --git a/web-app/src/Components/Pages/LensesSetsPage/Components/SetsTab/SetsTab.jsx b/web-app/src/Components/Pages/LensesSetsPage/Components/SetsTab/SetsTab.jsx index 523211c..2d99915 100644 --- a/web-app/src/Components/Pages/LensesSetsPage/Components/SetsTab/SetsTab.jsx +++ b/web-app/src/Components/Pages/LensesSetsPage/Components/SetsTab/SetsTab.jsx @@ -2,7 +2,7 @@ import {FloatButton, Input, List, Result, Row, Typography} from "antd"; import {PlusOutlined, SwitcherOutlined} from "@ant-design/icons"; import SetListCard from "../../../../Dummies/SetListCard.jsx"; import SetFormModal from "./Components/SetFormModal/SetFormModal.jsx"; -import LoadingIndicator from "../../../../Widgets/LoadingIndicator.jsx"; +import LoadingIndicator from "../../../../Widgets/LoadingIndicator/LoadingIndicator.jsx"; import useSets from "./useSets.js"; import useSetsUI from "./useSetsUI.js"; diff --git a/web-app/src/Components/Pages/PatientsPage/PatientsPage.jsx b/web-app/src/Components/Pages/PatientsPage/PatientsPage.jsx index f26922f..153de88 100644 --- a/web-app/src/Components/Pages/PatientsPage/PatientsPage.jsx +++ b/web-app/src/Components/Pages/PatientsPage/PatientsPage.jsx @@ -19,9 +19,9 @@ import { TeamOutlined } from "@ant-design/icons"; import PatientListCard from "../../Dummies/PatientListCard.jsx"; -import PatientFormModal from "./Components/PatientFormModal/PatientFormModal.jsx"; -import SelectViewMode from "../../Widgets/SelectViewMode.jsx"; -import LoadingIndicator from "../../Widgets/LoadingIndicator.jsx"; +import PatientFormModal from "../../Dummies/PatientFormModal/PatientFormModal.jsx"; +import SelectViewMode from "../../Widgets/SelectViewMode/SelectViewMode.jsx"; +import LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.jsx"; import usePatients from "./usePatients.js"; import usePatientsUI from "./usePatientsUI.js"; @@ -186,11 +186,7 @@ const PatientsPage = () => { tooltip="Добавить пациента" /> - + ); }; diff --git a/web-app/src/Components/Pages/PatientsPage/usePatients.js b/web-app/src/Components/Pages/PatientsPage/usePatients.js index 78fe3e2..515f45f 100644 --- a/web-app/src/Components/Pages/PatientsPage/usePatients.js +++ b/web-app/src/Components/Pages/PatientsPage/usePatients.js @@ -1,24 +1,14 @@ -import { useDispatch, useSelector } from "react-redux"; -import { notification } from "antd"; +import {notification} from "antd"; import { - useAddPatientMutation, useDeletePatientMutation, useGetPatientsQuery, - useUpdatePatientMutation } from "../../../Api/patientsApi.js"; -import {closeModal} from "../../../Redux/Slices/patientsSlice.js"; const usePatients = () => { - const dispatch = useDispatch(); - const { - selectedPatient, - } = useSelector(state => state.patientsUI); - - const { data: patients = [], isLoading, isError } = useGetPatientsQuery(undefined, { + const {data: patients = [], isLoading, isError} = useGetPatientsQuery(undefined, { pollingInterval: 20000, }); - const [addPatient] = useAddPatientMutation(); - const [updatePatient] = useUpdatePatientMutation(); + const [deletePatient] = useDeletePatientMutation(); const handleDeletePatient = async (patientId) => { @@ -38,40 +28,11 @@ const usePatients = () => { } }; - const handleModalSubmit = async (patientData) => { - dispatch(closeModal()); - - try { - if (selectedPatient) { - await updatePatient({ id: selectedPatient.id, ...patientData }).unwrap(); - notification.success({ - message: "Пациент обновлён", - description: `Данные пациента ${patientData.first_name} ${patientData.last_name} успешно обновлены.`, - placement: "topRight", - }); - } else { - await addPatient(patientData).unwrap(); - notification.success({ - message: "Пациент добавлен", - description: `Пациент ${patientData.first_name} ${patientData.last_name} успешно добавлен.`, - placement: "topRight", - }); - } - } catch (error) { - notification.error({ - message: "Ошибка", - description: error.data?.message || "Произошла ошибка при сохранении", - placement: "topRight", - }); - } - }; - return { patients, isLoading, isError, handleDeletePatient, - handleModalSubmit, }; }; diff --git a/web-app/src/Components/Pages/ProfilePage/ProfilePage.jsx b/web-app/src/Components/Pages/ProfilePage/ProfilePage.jsx index ef19099..d7f5e01 100644 --- a/web-app/src/Components/Pages/ProfilePage/ProfilePage.jsx +++ b/web-app/src/Components/Pages/ProfilePage/ProfilePage.jsx @@ -1,6 +1,6 @@ import { Button, Card, Col, Form, Input, Modal, Row, Space, Typography, Result } from "antd"; import { EditOutlined } from "@ant-design/icons"; -import LoadingIndicator from "../../Widgets/LoadingIndicator.jsx"; +import LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.jsx"; import useProfilePage from "./useProfilePage.js"; import useProfilePageUI from "./useProfilePageUI.js"; import { useSelector } from "react-redux"; diff --git a/web-app/src/Components/Widgets/AppointmentViewModal/AppointmentViewModal.jsx b/web-app/src/Components/Widgets/AppointmentViewModal/AppointmentViewModal.jsx index 99f1130..e54fb91 100644 --- a/web-app/src/Components/Widgets/AppointmentViewModal/AppointmentViewModal.jsx +++ b/web-app/src/Components/Widgets/AppointmentViewModal/AppointmentViewModal.jsx @@ -1,68 +1,78 @@ import {Button, Modal, Row, Typography} from "antd"; import useAppointmentViewUI from "./useAppointmentViewUI.js"; -import dayjs from "dayjs"; - const AppointmentViewModal = () => { - const appointmentViewModalUI = useAppointmentViewUI(); + const { + modalWidth, + blockStyle, + footerRowStyle, + footerButtonStyle, + labels, + selectedAppointment, + visible, + getDateString, + getAppointmentTime, + getPatientName, + getPatientField, + getResults, + onCancel, + } = useAppointmentViewUI(); - if (!appointmentViewModalUI.selectedAppointment) { + if (!selectedAppointment) { return null; } return ( - <> - -
- Информация о приеме -

- Пациент:{" "} - {appointmentViewModalUI.selectedAppointment.patient ? `${appointmentViewModalUI.selectedAppointment.patient.last_name} ${appointmentViewModalUI.selectedAppointment.patient.first_name}` : "Не указан"} -

-

- Дата рождения:{" "} - {appointmentViewModalUI.selectedAppointment.patient ? appointmentViewModalUI.getDateString(appointmentViewModalUI.selectedAppointment.patient.birthday) : "Не указан"} -

-

- Email: {appointmentViewModalUI.selectedAppointment.patient?.email || "Не указан"} -

-

- Телефон: {appointmentViewModalUI.selectedAppointment.patient?.phone || "Не указан"} -

-

- Тип приема: {appointmentViewModalUI.selectedAppointment.type?.title || "Не указан"} -

-

- Время приема:{" "} - {appointmentViewModalUI.selectedAppointment.appointment_datetime - ? dayjs(appointmentViewModalUI.selectedAppointment.appointment_datetime).format("DD.MM.YYYY HH:mm") - : "Не указано"} -

-

- Дней до следующего приема:{" "} - {appointmentViewModalUI.selectedAppointment.days_until_the_next_appointment || "Не указано"} -

-

- Результаты приема: -

-
-
- - - - - + +
+ Информация о приеме +

+ {labels.patient} {getPatientName(selectedAppointment.patient)} +

+

+ {labels.birthday}{" "} + {getDateString(selectedAppointment.patient?.birthday)} +

+

+ {labels.email}{" "} + {getPatientField(selectedAppointment.patient?.email)} +

+

+ {labels.phone}{" "} + {getPatientField(selectedAppointment.patient?.phone)} +

+

+ {labels.type}{" "} + {getPatientField(selectedAppointment.type?.title)} +

+

+ {labels.appointmentTime}{" "} + {getAppointmentTime(selectedAppointment.appointment_datetime)} +

+

+ {labels.daysUntilNext}{" "} + {getPatientField(selectedAppointment.days_until_the_next_appointment)} +

+

+ {labels.results} +

+
+
+ + + + ); }; - export default AppointmentViewModal; \ No newline at end of file diff --git a/web-app/src/Components/Widgets/AppointmentViewModal/useAppointmentViewUI.js b/web-app/src/Components/Widgets/AppointmentViewModal/useAppointmentViewUI.js index c461e22..bb9d05f 100644 --- a/web-app/src/Components/Widgets/AppointmentViewModal/useAppointmentViewUI.js +++ b/web-app/src/Components/Widgets/AppointmentViewModal/useAppointmentViewUI.js @@ -1,36 +1,74 @@ -import {useDispatch, useSelector} from "react-redux"; -import { - setSelectedAppointment -} from "../../../Redux/Slices/appointmentsSlice.js"; +import { useDispatch, useSelector } from "react-redux"; +import { setSelectedAppointment } from "../../../Redux/Slices/appointmentsSlice.js"; +import dayjs from "dayjs"; const useAppointmentViewUI = () => { const dispatch = useDispatch(); - const { - selectedAppointment, - } = useSelector(state => state.appointmentsUI); + const { selectedAppointment } = useSelector((state) => state.appointmentsUI); const modalWidth = 700; - const blockStyle = {marginBottom: 16}; - const footerRowStyle = {marginTop: 16}; - const footerButtonStyle = {marginRight: 8}; + const blockStyle = { marginBottom: 16 }; + const footerRowStyle = { marginTop: 16 }; + const footerButtonStyle = { marginRight: 8 }; + + const labels = { + title: "Просмотр приема", + patient: "Пациент:", + birthday: "Дата рождения:", + email: "Email:", + phone: "Телефон:", + type: "Тип приема:", + appointmentTime: "Время приема:", + daysUntilNext: "Дней до следующего приема:", + results: "Результаты приема:", + closeButton: "Закрыть", + notSpecified: "Не указан", + resultsNotSpecified: "Не указаны", + }; + const visible = !!selectedAppointment; + const getDateString = (date) => { + return date ? new Date(date).toLocaleDateString("ru-RU") : 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 getResults = (results) => { + return results || labels.resultsNotSpecified; + }; + const onCancel = () => { dispatch(setSelectedAppointment(null)); }; - const getDateString = (date) => { - return new Date(date).toLocaleDateString('ru-RU'); - }; - return { modalWidth, blockStyle, footerRowStyle, footerButtonStyle, + labels, selectedAppointment, visible, getDateString, + getAppointmentTime, + getPatientName, + getPatientField, + getResults, onCancel, }; }; diff --git a/web-app/src/Components/Widgets/CalendarCell.jsx b/web-app/src/Components/Widgets/CalendarCell.jsx deleted file mode 100644 index 8bc3497..0000000 --- a/web-app/src/Components/Widgets/CalendarCell.jsx +++ /dev/null @@ -1,109 +0,0 @@ -import {useEffect, useRef, useState} from "react"; -import {Badge, Col, Tag, Tooltip} from "antd"; -import dayjs from "dayjs"; -import PropTypes from "prop-types"; -import {AppointmentPropType} from "../../Types/appointmentPropType.js"; -import {ScheduledAppointmentPropType} from "../../Types/scheduledAppointmentPropType.js"; - -const CalendarCell = ({allAppointments, onCellClick, onItemClick}) => { - const containerRef = useRef(null); - const [isCompressed, setIsCompressed] = useState(false); - const COMPRESSION_THRESHOLD = 70; - - useEffect(() => { - if (!containerRef.current) return; - - const observer = new ResizeObserver((entries) => { - const width = entries[0].contentRect.width; - setIsCompressed(width < COMPRESSION_THRESHOLD); - }); - - observer.observe(containerRef.current); - return () => observer.disconnect(); - }, []); - - return ( -
- {!isCompressed && ( -
    - {allAppointments.map((app) => ( - - - { - e.stopPropagation(); - onItemClick(app); - }} - style={{ - margin: "2px 2px 0 0", - cursor: "pointer", - width: "95%", - minHeight: 30, - display: "flex", - alignItems: "center", - overflow: "hidden", - }} - > - - {dayjs(app.appointment_datetime || app.scheduled_datetime).format("HH:mm") + - ` ${app.patient?.last_name || ""} ${app.patient?.first_name || ""}`} - - } - /> - - - - ))} -
- )} - {isCompressed && ( -
- {allAppointments.length > 0 && `+${allAppointments.length}`} -
- )} -
- ); -}; - -CalendarCell.propTypes = { - allAppointments: PropTypes.arrayOf( - PropTypes.oneOfType([AppointmentPropType, ScheduledAppointmentPropType]) - ).isRequired, - onCellClick: PropTypes.func.isRequired, - onItemClick: PropTypes.func.isRequired, -}; - -export default CalendarCell; \ No newline at end of file diff --git a/web-app/src/Components/Widgets/LoadingIndicator.jsx b/web-app/src/Components/Widgets/LoadingIndicator.jsx deleted file mode 100644 index ff51c1b..0000000 --- a/web-app/src/Components/Widgets/LoadingIndicator.jsx +++ /dev/null @@ -1,18 +0,0 @@ -import {Spin} from "antd"; -import {LoadingOutlined} from "@ant-design/icons"; - - -const LoadingIndicator = () => { - return ( -
- }/> -
- ) -}; - -export default LoadingIndicator; \ No newline at end of file diff --git a/web-app/src/Components/Widgets/LoadingIndicator/LoadingIndicator.jsx b/web-app/src/Components/Widgets/LoadingIndicator/LoadingIndicator.jsx new file mode 100644 index 0000000..0e850a3 --- /dev/null +++ b/web-app/src/Components/Widgets/LoadingIndicator/LoadingIndicator.jsx @@ -0,0 +1,15 @@ +import {Spin} from "antd"; +import {LoadingOutlined} from "@ant-design/icons"; +import useLoadingIndicatorUI from "./useLoadingIndicator.js"; + +const LoadingIndicator = () => { + const {containerStyle, iconStyle} = useLoadingIndicatorUI(); + + return ( +
+ }/> +
+ ); +}; + +export default LoadingIndicator; \ No newline at end of file diff --git a/web-app/src/Components/Widgets/LoadingIndicator/useLoadingIndicator.js b/web-app/src/Components/Widgets/LoadingIndicator/useLoadingIndicator.js new file mode 100644 index 0000000..8ff8713 --- /dev/null +++ b/web-app/src/Components/Widgets/LoadingIndicator/useLoadingIndicator.js @@ -0,0 +1,20 @@ +const useLoadingIndicatorUI = () => { + const containerStyle = { + display: "flex", + justifyContent: "center", + alignItems: "center", + height: "100vh", + }; + + const iconStyle = { + fontSize: 64, + color: "#1890ff", + }; + + return { + containerStyle, + iconStyle, + }; +}; + +export default useLoadingIndicatorUI; \ 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 6fa6f74..837437a 100644 --- a/web-app/src/Components/Widgets/ScheduledAppointmentsViewModal/ScheduledAppointmentsViewModal.jsx +++ b/web-app/src/Components/Widgets/ScheduledAppointmentsViewModal/ScheduledAppointmentsViewModal.jsx @@ -1,79 +1,79 @@ import {Button, Modal, Popconfirm, Row, Typography} from "antd"; -import dayjs from "dayjs"; import useScheduledAppointmentsViewModal from "./useScheduledAppointmentsViewModal.js"; import useScheduledAppointmentsViewModalUI from "./useScheduledAppointmentsViewModalUI.js"; -import { useDispatch } from "react-redux"; -import {openModalWithScheduledData} from "../../../Redux/Slices/appointmentsSlice.js"; const ScheduledAppointmentsViewModal = () => { - const dispatch = useDispatch(); const scheduledAppointmentsViewModalData = useScheduledAppointmentsViewModal(); - const scheduledAppointmentsViewModalUI = useScheduledAppointmentsViewModalUI(scheduledAppointmentsViewModalData.cancelAppointment); + const { + selectedScheduledAppointment, + modalWidth, + blockStyle, + footerRowStyle, + footerButtonStyle, + labels, + visible, + getDateString, + getAppointmentTime, + getPatientName, + getPatientField, + onCancel, + cancelScheduledAppointment, + handleConvertToAppointment, + } = useScheduledAppointmentsViewModalUI(scheduledAppointmentsViewModalData.cancelAppointment); - if (!scheduledAppointmentsViewModalUI.selectedScheduledAppointment) { + if (!selectedScheduledAppointment) { return null; } - const handleConvertToAppointment = () => { - dispatch(openModalWithScheduledData({ - id: scheduledAppointmentsViewModalUI.selectedScheduledAppointment.id, - patient_id: scheduledAppointmentsViewModalUI.selectedScheduledAppointment.patient?.id, - type_id: scheduledAppointmentsViewModalUI.selectedScheduledAppointment.type?.id, - appointment_datetime: scheduledAppointmentsViewModalUI.selectedScheduledAppointment.scheduled_datetime, - })); - }; - return ( -
+
Информация о приеме

- Пациент:{" "} - {scheduledAppointmentsViewModalUI.selectedScheduledAppointment.patient ? `${scheduledAppointmentsViewModalUI.selectedScheduledAppointment.patient.last_name} ${scheduledAppointmentsViewModalUI.selectedScheduledAppointment.patient.first_name}` : "Не указан"} + {labels.patient} {getPatientName(selectedScheduledAppointment.patient)}

- Дата рождения:{" "} - {scheduledAppointmentsViewModalUI.selectedScheduledAppointment.patient ? scheduledAppointmentsViewModalUI.getDateString(scheduledAppointmentsViewModalUI.selectedScheduledAppointment.patient.birthday) : "Не указан"} + {labels.birthday}{" "} + {getDateString(selectedScheduledAppointment.patient?.birthday)}

- Email: {scheduledAppointmentsViewModalUI.selectedScheduledAppointment.patient?.email || "Не указан"} + {labels.email}{" "} + {getPatientField(selectedScheduledAppointment.patient?.email)}

- Телефон: {scheduledAppointmentsViewModalUI.selectedScheduledAppointment.patient?.phone || "Не указан"} + {labels.phone}{" "} + {getPatientField(selectedScheduledAppointment.patient?.phone)}

- Тип - приема: {scheduledAppointmentsViewModalUI.selectedScheduledAppointment.type?.title || "Не указан"} + {labels.type}{" "} + {getPatientField(selectedScheduledAppointment.type?.title)}

- Время приема:{" "} - {scheduledAppointmentsViewModalUI.selectedScheduledAppointment.scheduled_datetime - ? dayjs(scheduledAppointmentsViewModalUI.selectedScheduledAppointment.scheduled_datetime).format("DD.MM.YYYY HH:mm") - : "Не указано"} + {labels.appointmentTime}{" "} + {getAppointmentTime(selectedScheduledAppointment.scheduled_datetime)}

- - - diff --git a/web-app/src/Components/Widgets/ScheduledAppointmentsViewModal/useScheduledAppointmentsViewModalUI.js b/web-app/src/Components/Widgets/ScheduledAppointmentsViewModal/useScheduledAppointmentsViewModalUI.js index d2a5730..df2100f 100644 --- a/web-app/src/Components/Widgets/ScheduledAppointmentsViewModal/useScheduledAppointmentsViewModalUI.js +++ b/web-app/src/Components/Widgets/ScheduledAppointmentsViewModal/useScheduledAppointmentsViewModalUI.js @@ -1,22 +1,55 @@ -import {useDispatch, useSelector} from "react-redux"; +import { useDispatch, useSelector } from "react-redux"; +import { setSelectedScheduledAppointment, openModalWithScheduledData } from "../../../Redux/Slices/appointmentsSlice.js"; +import { notification } from "antd"; import dayjs from "dayjs"; -import {setSelectedScheduledAppointment} from "../../../Redux/Slices/appointmentsSlice.js"; -import {notification} from "antd"; - 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 { - selectedScheduledAppointment, - } = useSelector(state => state.appointmentsUI); + const labels = { + title: "Просмотр запланированного приема", + patient: "Пациент:", + birthday: "Дата рождения:", + email: "Email:", + phone: "Телефон:", + type: "Тип приема:", + appointmentTime: "Время приема:", + closeButton: "Закрыть", + convertButton: "Конвертировать в прием", + cancelButton: "Отмена приема", + popconfirmTitle: "Вы уверены, что хотите отменить прием?", + popconfirmOk: "Да, отменить", + popconfirmCancel: "Отмена", + notSpecified: "Не указан", + }; - const blockStyle = {marginBottom: 16}; + const visible = !!selectedScheduledAppointment; const getDateString = (date) => { - return date ? dayjs(date).format('DD.MM.YYYY') : 'Не указано'; - } + 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)); @@ -26,26 +59,48 @@ const useScheduledAppointmentsViewModalUI = (cancelAppointment) => { try { await cancelAppointment(selectedScheduledAppointment.id); notification.success({ - message: 'Прием отменен', - placement: 'topRight', - description: 'Прием успешно отменен.', - }) + message: "Прием отменен", + placement: "topRight", + description: "Прием успешно отменен.", + }); onCancel(); } catch (error) { notification.error({ - message: 'Ошибка', - description: error.data?.message || 'Не удалось отменить прием.', - placement: 'topRight', - }) + message: "Ошибка", + description: error.data?.message || "Не удалось отменить прием.", + 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, }; }; diff --git a/web-app/src/Components/Widgets/SelectViewMode.jsx b/web-app/src/Components/Widgets/SelectViewMode.jsx deleted file mode 100644 index 52d99da..0000000 --- a/web-app/src/Components/Widgets/SelectViewMode.jsx +++ /dev/null @@ -1,42 +0,0 @@ -import {BuildOutlined, TableOutlined} from "@ant-design/icons"; -import {Select, Tooltip} from "antd"; -import PropTypes from "prop-types"; -import {cacheInfo} from "../../Utils/cachedInfoUtils.js"; -import {ViewModPropType} from "../../Types/viewModPropType.js"; - -const {Option} = Select; - -const SelectViewMode = ({viewMode, setViewMode, localStorageKey, toolTipText, viewModes}) => { - return ( - - - - - ) -}; - -SelectViewMode.propTypes = { - viewMode: PropTypes.string.isRequired, - setViewMode: PropTypes.func.isRequired, - localStorageKey: PropTypes.string.isRequired, - toolTipText: PropTypes.string.isRequired, - viewModes: PropTypes.arrayOf(ViewModPropType).isRequired, -}; - -export default SelectViewMode; \ 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 new file mode 100644 index 0000000..c632d3b --- /dev/null +++ b/web-app/src/Components/Widgets/SelectViewMode/SelectViewMode.jsx @@ -0,0 +1,33 @@ +import { Select, Tooltip } from "antd"; +import PropTypes from "prop-types"; +import { ViewModPropType } from "../../../Types/viewModPropType.js"; +import useSelectViewModeUI from "./useSelectViewModeUI.js"; + +const { Option } = Select; + +const SelectViewMode = ({ viewMode, setViewMode, localStorageKey, toolTipText, viewModes }) => { + const { selectStyle, handleChange } = useSelectViewModeUI({ setViewMode, localStorageKey }); + + return ( + + + + ); +}; + +SelectViewMode.propTypes = { + viewMode: PropTypes.string.isRequired, + setViewMode: PropTypes.func.isRequired, + localStorageKey: PropTypes.string.isRequired, + toolTipText: PropTypes.string.isRequired, + viewModes: PropTypes.arrayOf(ViewModPropType).isRequired, +}; + +export default SelectViewMode; \ No newline at end of file diff --git a/web-app/src/Components/Widgets/SelectViewMode/useSelectViewModeUI.js b/web-app/src/Components/Widgets/SelectViewMode/useSelectViewModeUI.js new file mode 100644 index 0000000..1c0d283 --- /dev/null +++ b/web-app/src/Components/Widgets/SelectViewMode/useSelectViewModeUI.js @@ -0,0 +1,19 @@ +import { cacheInfo } from "../../../Utils/cachedInfoUtils.js"; + +const useSelectViewModeUI = ({ setViewMode, localStorageKey }) => { + const selectStyle = { + width: "100%", + }; + + const handleChange = (value) => { + setViewMode(value); + cacheInfo(localStorageKey, value); + }; + + return { + selectStyle, + handleChange, + }; +}; + +export default useSelectViewModeUI; \ No newline at end of file