440 lines
16 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;