diff --git a/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/Components/AppointmentFormModal/AppointmentFormModal.jsx b/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/Components/AppointmentFormModal/AppointmentFormModal.jsx index fb86249..794ebb4 100644 --- a/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/Components/AppointmentFormModal/AppointmentFormModal.jsx +++ b/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/Components/AppointmentFormModal/AppointmentFormModal.jsx @@ -11,7 +11,8 @@ import { Input, InputNumber, Modal, - Result, Row, + Result, + Row, Select, Spin, Steps, @@ -20,20 +21,20 @@ import { import useAppointmentFormModal from "./useAppointmentFormModal.js"; import useAppointmentFormModalUI from "./useAppointmentFormModalUI.js"; import LoadingIndicator from "../../../../../../Widgets/LoadingIndicator.jsx"; -import {DefaultModalPropType} from "../../../../../../../Types/defaultModalPropType.js"; import {useMemo} from "react"; +import PropTypes from "prop-types"; dayjs.extend(utc); dayjs.extend(timezone); dayjs.tz.setDefault('Europe/Moscow'); -const AppointmentFormModal = ({visible, onCancel}) => { +const AppointmentFormModal = ({onCancel}) => { const appointmentFormModalData = useAppointmentFormModal(); const appointmentFormModalUI = useAppointmentFormModalUI( - visible, onCancel, appointmentFormModalData.createAppointment, - appointmentFormModalData.updateAppointment + appointmentFormModalData.updateAppointment, + appointmentFormModalData.patients, ); if (appointmentFormModalData.isError) { @@ -46,17 +47,21 @@ const AppointmentFormModal = ({visible, onCancel}) => { ); } - const patientsItems = appointmentFormModalData.filteredPatients.map((patient) => ({ + const patientsItems = appointmentFormModalUI.filteredPatients.map((patient) => ({ key: patient.id, label: `${patient.last_name} ${patient.first_name} (${appointmentFormModalUI.getDateString(patient.birthday)})`, - children:
-

Пациент: {patient.last_name} {patient.first_name}

-

Дата рождения: {appointmentFormModalUI.getDateString(patient.birthday)}

-

Диагноз: {patient.diagnosis}

-

Email: {patient.email}

-

Телефон: {patient.phone}

- -
, + children: ( +
+

Пациент: {patient.last_name} {patient.first_name}

+

Дата рождения: {appointmentFormModalUI.getDateString(patient.birthday)}

+

Диагноз: {patient.diagnosis || 'Не указан'}

+

Email: {patient.email || 'Не указан'}

+

Телефон: {patient.phone || 'Не указан'}

+ +
+ ), })); const SelectPatientStep = useMemo(() => { @@ -65,11 +70,9 @@ const AppointmentFormModal = ({visible, onCancel}) => { {appointmentFormModalUI.selectedPatient.last_name} {appointmentFormModalUI.selectedPatient.first_name} -

Дата - рождения: {appointmentFormModalUI.getSelectedPatientBirthdayString()} -

-

Email: {appointmentFormModalUI.selectedPatient.email}

-

Телефон: {appointmentFormModalUI.selectedPatient.phone}

+

Дата рождения: {appointmentFormModalUI.getSelectedPatientBirthdayString()}

+

Email: {appointmentFormModalUI.selectedPatient.email || 'Не указан'}

+

Телефон: {appointmentFormModalUI.selectedPatient.phone || 'Не указан'}

- ); - }, [appointmentFormModalData.appointmentTypes, appointmentFormModalData.patients, appointmentFormModalUI.form, appointmentFormModalUI.handleOk, appointmentFormModalUI.selectedAppointment]); + }, [ + appointmentFormModalData, + appointmentFormModalUI, + ]); - const steps = [{ - title: 'Выбор пациента', content: SelectPatientStep, - }, { - title: 'Заполнение информации о приеме', content: AppointmentStep, - }, { - title: 'Подтверждение', content: ConfirmStep, - }]; + const ConfirmStep = useMemo(() => { + const values = appointmentFormModalUI.form.getFieldsValue(); + const patient = appointmentFormModalData.patients.find(p => p.id === values.patient_id); + const appointmentType = appointmentFormModalData.appointmentTypes.find(t => t.id === values.type_id); + + return ( +
+ Подтверждение +

Пациент: {patient ? `${patient.last_name} ${patient.first_name}` : 'Не выбран'}

+

Тип приема: {appointmentType ? appointmentType.name : 'Не выбран'}

+

Время + приема: {values.appointment_datetime ? dayjs(values.appointment_datetime).tz('Europe/Moscow').format('DD.MM.YYYY HH:mm') : 'Не указано'} +

+

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

+

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

+
+
+ ); + }, [appointmentFormModalUI, appointmentFormModalData]); + + const steps = [ + { + title: 'Выбор пациента', + content: SelectPatientStep, + }, + { + title: 'Заполнение информации о приеме', + content: AppointmentStep, + }, + { + title: 'Подтверждение', + content: ConfirmStep, + }, + ]; return ( <> @@ -196,8 +202,9 @@ const AppointmentFormModal = ({visible, onCancel}) => { open={appointmentFormModalUI.modalVisible} onCancel={appointmentFormModalUI.handleCancel} footer={null} + width={appointmentFormModalUI.modalWidth} > - {appointmentFormModalData.loading ? ( + {appointmentFormModalData.isLoading ? (
@@ -207,7 +214,6 @@ const AppointmentFormModal = ({visible, onCancel}) => {
)} - {!appointmentFormModalUI.screenXS && ( { ); }; -AppointmentFormModal.propTypes = DefaultModalPropType; +AppointmentFormModal.propTypes = { + onCancel: PropTypes.func.isRequired, +}; export default AppointmentFormModal; \ No newline at end of file diff --git a/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/Components/AppointmentFormModal/useAppointmentFormModalUI.js b/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/Components/AppointmentFormModal/useAppointmentFormModalUI.js index 9b7cdba..e19e5c3 100644 --- a/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/Components/AppointmentFormModal/useAppointmentFormModalUI.js +++ b/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/Components/AppointmentFormModal/useAppointmentFormModalUI.js @@ -1,32 +1,59 @@ -import { Form, notification } from "antd"; -import { useDispatch, useSelector } from "react-redux"; -import { closeModal } from "../../../../../../../Redux/Slices/appointmentsSlice.js"; -import {useEffect, useState} from "react"; +import {Form, notification} from "antd"; +import {useDispatch, useSelector} from "react-redux"; +import {closeModal} from "../../../../../../../Redux/Slices/appointmentsSlice.js"; +import {useEffect, useMemo, useState} from "react"; import dayjs from "dayjs"; -import { useGetAppointmentsQuery } from "../../../../../../../Api/appointmentsApi.js"; +import {useGetAppointmentsQuery} from "../../../../../../../Api/appointmentsApi.js"; +import {Grid} from "antd"; -const useAppointmentFormModalUI = (visible, onCancel, createAppointment, updateAppointment) => { +const {useBreakpoint} = Grid; + +const useAppointmentFormModalUI = (onCancel, createAppointment, updateAppointment, patients) => { const dispatch = useDispatch(); - const { modalVisible, selectedAppointment } = useSelector(state => state.appointmentsUI); + const {modalVisible, selectedAppointment} = useSelector(state => state.appointmentsUI); const [form] = Form.useForm(); + const screens = useBreakpoint(); const [selectedPatient, setSelectedPatient] = useState(null); const [currentStep, setCurrentStep] = useState(0); + const [searchPatientString, setSearchPatientString] = useState(""); + const [formValues, setFormValues] = useState({}); - const resetPatient = () => setSelectedPatient(null); - - const getDateString = (date) => { - return new Date(date).toLocaleDateString('ru-RU'); - }; - - const { data: appointments = [] } = useGetAppointmentsQuery(undefined, { + const {data: appointments = []} = useGetAppointmentsQuery(undefined, { pollingInterval: 20000, }); + const blockStepStyle = {marginBottom: 16}; + const searchInputStyle = {marginBottom: 16}; + const chooseContainerStyle = {maxHeight: 400, overflowY: "auto"}; + const loadingContainerStyle = {display: "flex", justifyContent: "center", alignItems: "center", height: 200}; + const stepsContentStyle = {marginBottom: 16}; + const stepsIndicatorStyle = {marginBottom: 16}; + const footerRowStyle = {marginTop: 16}; + const footerButtonStyle = {marginLeft: 8}; + + const screenXS = !screens.sm; + const direction = screenXS ? "vertical" : "horizontal"; + + const filteredPatients = useMemo(() => patients.filter((patient) => { + const searchLower = searchPatientString.toLowerCase(); + + return Object.values(patient) + .filter(value => typeof value === "string") + .some(value => value.toLowerCase().includes(searchLower)); + }), [patients, searchPatientString]); + useEffect(() => { - if (visible) { + if (modalVisible) { form.resetFields(); + setSelectedPatient(null); + setCurrentStep(0); + setSearchPatientString(""); + setFormValues({}); if (selectedAppointment) { + const patient = appointments.find(p => p.id === selectedAppointment.patient_id); + setSelectedPatient(patient); + setCurrentStep(1); // При редактировании начинаем со второго шага form.setFieldsValue({ patient_id: selectedAppointment.patient_id, type_id: selectedAppointment.type_id, @@ -38,16 +65,70 @@ const useAppointmentFormModalUI = (visible, onCancel, createAppointment, updateA }); } } - }, [visible, selectedAppointment, form]); + }, [modalVisible, selectedAppointment, form, appointments]); + + const handleSetSearchPatientString = (e) => { + setSearchPatientString(e.target.value); + }; + + const getDateString = (date) => { + return date ? dayjs(date).format('DD.MM.YYYY') : 'Не указано'; + }; + + const getSelectedPatientBirthdayString = () => { + return selectedPatient ? getDateString(selectedPatient.birthday) : 'Не выбран'; + }; + + const resetPatient = () => { + setSelectedPatient(null); + form.setFieldsValue({patient_id: undefined}); + }; + + const modalWidth = useMemo(() => screenXS ? 700 : "90%", [screenXS]); + + const handleClickNextButton = async () => { + if (currentStep === 0) { + if (!selectedPatient) { + notification.error({ + message: 'Ошибка', + description: 'Пожалуйста, выберите пациента.', + placement: 'topRight', + }); + return; + } + form.setFieldsValue({patient_id: selectedPatient.id}); + setCurrentStep(1); + } else if (currentStep === 1) { + try { + const values = await form.validateFields(); + setFormValues(values); + setCurrentStep(2); + } catch (error) { + notification.error({ + message: 'Ошибка валидации', + description: 'Пожалуйста, заполните все обязательные поля.', + placement: 'topRight', + }); + } + } else if (currentStep === 2) { + await handleOk(); + } + }; + + const handleClickBackButton = () => { + if (currentStep > 0) { + setCurrentStep(currentStep - 1); + } + }; const handleOk = async () => { try { - const values = await form.validateFields(); + const values = formValues; // Проверка пересечения времени const appointmentTime = values.appointment_datetime; const hasConflict = appointments.some(app => - app.id !== selectedAppointment?.id && // Исключаем текущий прием при редактировании + app.id !== selectedAppointment?.id && dayjs(app.appointment_datetime).tz('Europe/Moscow').isSame(appointmentTime, 'minute') ); @@ -79,7 +160,7 @@ const useAppointmentFormModalUI = (visible, onCancel, createAppointment, updateA } if (selectedAppointment) { - await updateAppointment({ id: selectedAppointment.id, data }).unwrap(); + await updateAppointment({id: selectedAppointment.id, data}).unwrap(); notification.success({ message: 'Прием обновлен', description: 'Прием успешно обновлен.', @@ -96,6 +177,9 @@ const useAppointmentFormModalUI = (visible, onCancel, createAppointment, updateA dispatch(closeModal()); form.resetFields(); + setSelectedPatient(null); + setCurrentStep(0); + setFormValues({}); } catch (error) { notification.error({ message: 'Ошибка', @@ -107,19 +191,47 @@ const useAppointmentFormModalUI = (visible, onCancel, createAppointment, updateA const handleCancel = () => { form.resetFields(); + setSelectedPatient(null); + setCurrentStep(0); + setSearchPatientString(""); + setFormValues({}); onCancel(); }; + const disableBackButton = currentStep === 0; + const disableNextButton = currentStep === 0 && !selectedPatient; + const nextButtonText = currentStep === 2 ? (selectedAppointment ? 'Сохранить' : 'Создать') : 'Далее'; + return { form, modalVisible, - selectedAppointment, selectedPatient, setSelectedPatient, + currentStep, + searchPatientString, + handleSetSearchPatientString, + filteredPatients, handleOk, handleCancel, resetPatient, getDateString, + getSelectedPatientBirthdayString, + handleClickNextButton, + handleClickBackButton, + modalWidth, + disableBackButton, + disableNextButton, + nextButtonText, + blockStepStyle, + searchInputStyle, + chooseContainerStyle, + loadingContainerStyle, + stepsContentStyle, + stepsIndicatorStyle, + footerRowStyle, + footerButtonStyle, + screenXS, + direction, }; };