import {Form, notification} from "antd"; import {useDispatch, useSelector} from "react-redux"; import {closeModal, setSelectedScheduledAppointment} from "../../../Redux/Slices/appointmentsSlice.js"; import {useEffect, useMemo, useRef, useState} from "react"; import dayjs from "dayjs"; import {useGetAppointmentsQuery} from "../../../Api/appointmentsApi.js"; import {Grid} from "antd"; import {useUploadAppointmentFileMutation} from "../../../Api/appointmentFilesApi.js"; const {useBreakpoint} = Grid; const useAppointmentFormModalUI = (createAppointment, patients, cancelAppointment, useGetByPatientIdQuery) => { const dispatch = useDispatch(); const {userData} = useSelector((state) => state.auth); const {modalVisible, scheduledData} = useSelector((state) => state.appointmentsUI); const [form] = Form.useForm(); const screens = useBreakpoint(); const [selectedPatient, setSelectedPatient] = useState(null); const [currentStep, setCurrentStep] = useState(0); const [appointmentDate, setAppointmentDate] = useState(dayjs(new Date())); const [searchPatientString, setSearchPatientString] = useState(""); const [formValues, setFormValues] = useState({}); const [isDrawerVisible, setIsDrawerVisible] = useState(false); const [searchPreviousAppointments, setSearchPreviousAppointments] = useState(""); const [draftFiles, setDraftFiles] = useState([]); const editorRef = useRef(null); const [uploadAppointmentFile, {isLoading: isUploadingFile}] = useUploadAppointmentFileMutation(); const startDate = appointmentDate.startOf('month').utc().format('YYYY-MM-DD'); const endDate = appointmentDate.endOf('month').utc().format('YYYY-MM-DD'); const { data: appointments = [], isLoading: isLoadingAppointments, isError: isErrorAppointments, } = useGetAppointmentsQuery( {doctor_id: userData.id, start_date: startDate, end_date: endDate}, { pollingInterval: 60000, skip: !userData.id, } ); const { data: previousAppointments = [], isLoading: isLoadingPreviousAppointments, isError: isErrorPreviousAppointments, } = useGetByPatientIdQuery(selectedPatient?.id, { pollingInterval: 20000, skip: !selectedPatient, }); useEffect(() => { if (isErrorAppointments) { notification.error({ message: 'Ошибка', description: 'Ошибка загрузки приемов.', placement: 'topRight', }); } }, [isErrorAppointments]); 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 = {marginRight: 8}; const screenXS = !screens.sm; const direction = screenXS ? "vertical" : "horizontal"; const joditConfig = useMemo( () => ({ readonly: false, height: 150, toolbarAdaptive: false, buttons: [ "bold", "italic", "underline", "strikethrough", "|", "superscript", "subscript", "|", "ul", "ol", "outdent", "indent", "|", "font", "fontsize", "brush", "paragraph", "|", "align", "hr", "|", "table", "link", "image", "video", "symbols", "|", "undo", "redo", "cut", "copy", "paste", "selectall", "eraser", "|", "find", "source", "fullsize", "print", "preview", ], autofocus: false, preserveSelection: true, askBeforePasteHTML: false, askBeforePasteFromWord: false, defaultActionOnPaste: "insert_clear_html", spellcheck: true, placeholder: "Введите результаты приёма...", showCharsCounter: true, showWordsCounter: true, showXPathInStatusbar: false, toolbarSticky: true, toolbarButtonSize: "middle", cleanHTML: { removeEmptyElements: true, replaceNBSP: false, }, hotkeys: { "ctrl + shift + f": "find", "ctrl + b": "bold", "ctrl + i": "italic", "ctrl + u": "underline", }, image: { editSrc: true, editTitle: true, editAlt: true, openOnDblClick: false, }, video: { allowedSources: ["youtube", "vimeo"], }, uploader: { insertImageAsBase64URI: true, }, paste: { insertAsBase64: true, mimeTypes: ["image/png", "image/jpeg", "image/gif"], maxFileSize: 5 * 1024 * 1024, // 5MB error: () => { notification.error({ message: "Ошибка вставки", description: "Файл слишком большой или неподдерживаемый формат.", placement: "topRight", }); }, }, }), [] ); 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] ); const filteredPreviousAppointments = useMemo( () => previousAppointments.filter((appointment) => { const searchLower = searchPreviousAppointments.toLowerCase(); return appointment.results?.toLowerCase().includes(searchLower); }), [previousAppointments, searchPreviousAppointments] ); const handleAddFile = (file) => { const maxSize = 10 * 1024 * 1024; // 10MB if (file.size > maxSize) { notification.error({ message: "Ошибка вставки", description: "Файл слишком большой.", placement: "topRight", }); return false; } setDraftFiles((prev) => [...prev, file]); return false; }; const handleRemoveFile = (file) => { setDraftFiles((prev) => prev.filter((f) => f.uid !== file.uid)); }; useEffect(() => { if (modalVisible) { form.resetFields(); setSelectedPatient(null); setCurrentStep(0); setSearchPatientString(""); setFormValues({}); setIsDrawerVisible(false); setSearchPreviousAppointments(""); setDraftFiles([]); if (scheduledData) { const patient = patients.find((p) => p.id === scheduledData.patient_id); if (patient) { setSelectedPatient(patient); setCurrentStep(1); form.setFieldsValue({ patient_id: scheduledData.patient_id, type_id: scheduledData.type_id, appointment_datetime: dayjs(scheduledData.appointment_datetime), results: scheduledData.results || "", }); setAppointmentDate(dayjs(scheduledData.appointment_datetime)); } } else { form.setFieldsValue({ appointment_datetime: dayjs(new Date()), results: "", }); } } }, [modalVisible, form, scheduledData, patients]); const handleSetSearchPatientString = (e) => { setSearchPatientString(e.target.value); }; const handleSetSearchPreviousAppointments = (e) => { setSearchPreviousAppointments(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 resetForm = () => { form.resetFields(); setSelectedPatient(null); setCurrentStep(0); setSearchPatientString(""); setFormValues({}); setIsDrawerVisible(false); setDraftFiles([]); setAppointmentDate(dayjs(new Date())); // Сбрасываем дату }; const handleSetAppointmentDate = (date) => { setAppointmentDate(date); form.setFieldsValue({appointment_datetime: date}); // Синхронизируем форму }; const modalWidth = useMemo(() => (screenXS ? 700 : "90%"), [screenXS]); const showDrawer = () => { setIsDrawerVisible(true); }; const closeDrawer = () => { setIsDrawerVisible(false); setSearchPreviousAppointments(""); }; 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, patient_id: selectedPatient.id}); setCurrentStep(2); } catch (error) { notification.error({ message: "Ошибка валидации", description: error.message || "Пожалуйста, заполните все обязательные поля.", placement: "topRight", }); } } else if (currentStep === 2) { await handleOk(); } }; const handleClickBackButton = () => { if (currentStep > 0) { setCurrentStep(currentStep - 1); } }; const handleOk = async () => { try { const values = formValues; const appointmentTime = values.appointment_datetime; const hasConflict = appointments.some((app) => dayjs(app.appointment_datetime).isSame(appointmentTime, "minute") ); if (hasConflict) { notification.error({ message: "Конфликт времени", description: "Выбранное время уже занято другим приемом.", placement: "topRight", }); return; } const data = { patient_id: selectedPatient.id, type_id: values.type_id, appointment_datetime: appointmentTime.format("YYYY-MM-DD HH:mm:ss"), days_until_the_next_appointment: values.days_until_the_next_appointment, results: values.results || "", }; const response = await createAppointment(data).unwrap(); for (const file of draftFiles) { try { await uploadAppointmentFile({ appointmentId: response.id, file: file, }).unwrap(); } catch (error) { console.error(`Error uploading file ${file.name}:`, error); const errorMessage = error.data?.detail ? JSON.stringify(error.data.detail, null, 2) : JSON.stringify(error.data || error.message || "Неизвестная ошибка", null, 2); notification.error({ message: "Ошибка загрузки файла", description: `Не удалось загрузить файл ${file.name}: ${errorMessage}`, placement: "topRight", }); } } if (scheduledData) { await cancelScheduledAppointment(scheduledData.id); dispatch(setSelectedScheduledAppointment(null)); } notification.success({ message: "Прием создан", description: "Прием успешно создан.", placement: "topRight", }); resetForm(); dispatch(closeModal()); } catch (error) { console.error("Error creating appointment:", error); notification.error({ message: "Ошибка создания приема", description: `Не удалось создать прием: ${JSON.stringify(error.data?.detail || error.data || error.message || "Неизвестная ошибка", null, 2)}`, placement: "topRight", }); } }; const cancelScheduledAppointment = async (selectedScheduledAppointmentId) => { try { await cancelAppointment(selectedScheduledAppointmentId); } catch (error) { console.error("Error cancelling appointment:", error); notification.error({ message: "Ошибка", description: error.data?.message || "Не удалось отменить прием.", placement: "topRight", }); } }; const handleCancel = () => { resetForm(); dispatch(closeModal()); }; const disableBackButton = currentStep === 0; const disableNextButton = currentStep === 0 && !selectedPatient; const nextButtonText = currentStep === 2 ? "Создать" : "Далее"; return { form, modalVisible, selectedPatient, setSelectedPatient, currentStep, searchPatientString, appointmentDate, editor: editorRef, handleSetSearchPatientString, filteredPatients, filteredPreviousAppointments, searchPreviousAppointments, handleSetSearchPreviousAppointments, handleOk, handleCancel, resetPatient, getDateString, getSelectedPatientBirthdayString, handleClickNextButton, handleClickBackButton, handleSetAppointmentDate, modalWidth, disableBackButton, disableNextButton, nextButtonText, blockStepStyle, searchInputStyle, chooseContainerStyle, loadingContainerStyle, stepsContentStyle, stepsIndicatorStyle, footerRowStyle, footerButtonStyle, screenXS, direction, isDrawerVisible, formValues, showDrawer, closeDrawer, isLoadingPreviousAppointments, isErrorPreviousAppointments, joditConfig, draftFiles, handleAddFile, handleRemoveFile, isUploadingFile, isLoadingAppointments, isErrorAppointments, }; }; export default useAppointmentFormModalUI;