feat(AppointmentFormModal): Обновление формы записи на прием
Добавлены lodash, JoditEditor, улучшена обработка фокуса редактора.
This commit is contained in:
parent
827cfb413a
commit
c9c2919577
7
web-app/package-lock.json
generated
7
web-app/package-lock.json
generated
@ -19,6 +19,7 @@
|
||||
"chart.js": "^4.4.9",
|
||||
"dayjs": "^1.11.13",
|
||||
"jodit-react": "^5.2.19",
|
||||
"lodash": "^4.17.21",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "^18.3.1",
|
||||
"react-chartjs-2": "^5.3.0",
|
||||
@ -3729,6 +3730,12 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.merge": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||
|
||||
@ -21,6 +21,7 @@
|
||||
"chart.js": "^4.4.9",
|
||||
"dayjs": "^1.11.13",
|
||||
"jodit-react": "^5.2.19",
|
||||
"lodash": "^4.17.21",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "^18.3.1",
|
||||
"react-chartjs-2": "^5.3.0",
|
||||
|
||||
@ -19,7 +19,7 @@ import {
|
||||
import useAppointmentFormModal from "./useAppointmentFormModal.js";
|
||||
import useAppointmentFormModalUI from "./useAppointmentFormModalUI.js";
|
||||
import LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.jsx";
|
||||
import {useMemo} from "react";
|
||||
import {useMemo, useCallback, useRef} from "react";
|
||||
|
||||
const AppointmentFormModal = () => {
|
||||
const appointmentFormModalData = useAppointmentFormModal();
|
||||
@ -30,32 +30,66 @@ const AppointmentFormModal = () => {
|
||||
appointmentFormModalData.useGetByPatientIdQuery
|
||||
);
|
||||
|
||||
const patientsItems = appointmentFormModalUI.filteredPatients.map((patient) => ({
|
||||
key: patient.id,
|
||||
label: `${patient.last_name} ${patient.first_name} (${appointmentFormModalUI.getDateString(patient.birthday)})`,
|
||||
children: (
|
||||
<div>
|
||||
<p>
|
||||
<b>Пациент:</b> {patient.last_name} {patient.first_name}
|
||||
</p>
|
||||
<p>
|
||||
<b>Дата рождения:</b> {appointmentFormModalUI.getDateString(patient.birthday)}
|
||||
</p>
|
||||
<p>
|
||||
<b>Диагноз:</b> {patient.diagnosis || "Не указан"}
|
||||
</p>
|
||||
<p>
|
||||
<b>Email:</b> {patient.email || "Не указан"}
|
||||
</p>
|
||||
<p>
|
||||
<b>Телефон:</b> {patient.phone || "Не указан"}
|
||||
</p>
|
||||
<Button type="primary" onClick={() => appointmentFormModalUI.setSelectedPatient(patient)}>
|
||||
Выбрать
|
||||
</Button>
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
const cursorPositionRef = useRef(null);
|
||||
|
||||
const saveCursorPosition = useCallback(() => {
|
||||
if (appointmentFormModalUI.editor.current) {
|
||||
const editor = appointmentFormModalUI.editor.current.editor;
|
||||
const selection = editor.selection;
|
||||
if (selection) {
|
||||
cursorPositionRef.current = selection.getBookmark();
|
||||
}
|
||||
}
|
||||
}, [appointmentFormModalUI.editor]);
|
||||
|
||||
const restoreCursorPosition = useCallback(() => {
|
||||
if (appointmentFormModalUI.editor.current && cursorPositionRef.current) {
|
||||
const editor = appointmentFormModalUI.editor.current.editor;
|
||||
const selection = editor.selection;
|
||||
if (selection && cursorPositionRef.current) {
|
||||
selection.moveToBookmark(cursorPositionRef.current);
|
||||
}
|
||||
}
|
||||
}, [appointmentFormModalUI.editor]);
|
||||
|
||||
const handleEditorBlur = useCallback(
|
||||
(newContent) => {
|
||||
saveCursorPosition();
|
||||
appointmentFormModalUI.form.setFieldsValue({results: newContent});
|
||||
setTimeout(restoreCursorPosition, 0);
|
||||
},
|
||||
[appointmentFormModalUI.form, saveCursorPosition, restoreCursorPosition]
|
||||
);
|
||||
|
||||
const patientsItems = useMemo(() =>
|
||||
appointmentFormModalUI.filteredPatients.map((patient) => ({
|
||||
key: patient.id,
|
||||
label: `${patient.last_name} ${patient.first_name} (${appointmentFormModalUI.getDateString(patient.birthday)})`,
|
||||
children: (
|
||||
<div>
|
||||
<p>
|
||||
<b>Пациент:</b> {patient.last_name} {patient.first_name}
|
||||
</p>
|
||||
<p>
|
||||
<b>Дата рождения:</b> {appointmentFormModalUI.getDateString(patient.birthday)}
|
||||
</p>
|
||||
<p>
|
||||
<b>Диагноз:</b> {patient.diagnosis || "Не указан"}
|
||||
</p>
|
||||
<p>
|
||||
<b>Email:</b> {patient.email || "Не указан"}
|
||||
</p>
|
||||
<p>
|
||||
<b>Телефон:</b> {patient.phone || ""}
|
||||
</p>
|
||||
<Button type="primary" onClick={() => appointmentFormModalUI.setSelectedPatient(patient)}>
|
||||
Выбрать
|
||||
</Button>
|
||||
</div>
|
||||
),
|
||||
})),
|
||||
[appointmentFormModalUI.filteredPatients, appointmentFormModalUI.getDateString, appointmentFormModalUI.setSelectedPatient]
|
||||
);
|
||||
|
||||
const SelectPatientStep = useMemo(() => {
|
||||
return appointmentFormModalUI.selectedPatient ? (
|
||||
@ -70,7 +104,7 @@ const AppointmentFormModal = () => {
|
||||
<b>Email:</b> {appointmentFormModalUI.selectedPatient.email || "Не указан"}
|
||||
</p>
|
||||
<p>
|
||||
<b>Телефон:</b> {appointmentFormModalUI.selectedPatient.phone || "Не указан"}
|
||||
<b>Телефон:</b> {appointmentFormModalUI.selectedPatient.phone || ""}
|
||||
</p>
|
||||
<Button type="primary" onClick={appointmentFormModalUI.resetPatient} danger>
|
||||
Выбрать другого пациента
|
||||
@ -90,7 +124,17 @@ const AppointmentFormModal = () => {
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}, [appointmentFormModalUI, patientsItems]);
|
||||
}, [
|
||||
appointmentFormModalUI.selectedPatient,
|
||||
appointmentFormModalUI.searchPatientString,
|
||||
appointmentFormModalUI.blockStepStyle,
|
||||
appointmentFormModalUI.chooseContainerStyle,
|
||||
appointmentFormModalUI.searchInputStyle,
|
||||
appointmentFormModalUI.getSelectedPatientBirthdayString,
|
||||
appointmentFormModalUI.resetPatient,
|
||||
appointmentFormModalUI.handleSetSearchPatientString,
|
||||
patientsItems,
|
||||
]);
|
||||
|
||||
const AppointmentStep = useMemo(() => {
|
||||
return (
|
||||
@ -141,20 +185,27 @@ const AppointmentFormModal = () => {
|
||||
<InputNumber min={0} style={{width: "100%"}}/>
|
||||
</Form.Item>
|
||||
<Form.Item name="results" label="Результаты приема">
|
||||
<JoditEditor
|
||||
ref={appointmentFormModalUI.editor}
|
||||
value={appointmentFormModalUI.results}
|
||||
config={{
|
||||
readonly: false,
|
||||
height: 150,
|
||||
}}
|
||||
onBlur={appointmentFormModalUI.handleResultsChange}
|
||||
/>
|
||||
<div className="jodit-container">
|
||||
<JoditEditor
|
||||
ref={appointmentFormModalUI.editor}
|
||||
value={appointmentFormModalUI.form.getFieldValue("results") || ""}
|
||||
config={appointmentFormModalUI.joditConfig}
|
||||
onBlur={handleEditorBlur}
|
||||
/>
|
||||
</div>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
}, [appointmentFormModalData, appointmentFormModalUI]);
|
||||
}, [
|
||||
appointmentFormModalUI.form,
|
||||
appointmentFormModalUI.selectedPatient,
|
||||
appointmentFormModalUI.showDrawer,
|
||||
appointmentFormModalUI.editor,
|
||||
appointmentFormModalData.appointmentTypes,
|
||||
appointmentFormModalUI.joditConfig,
|
||||
handleEditorBlur,
|
||||
]);
|
||||
|
||||
const ConfirmStep = useMemo(() => {
|
||||
const values = appointmentFormModalUI.form.getFieldsValue();
|
||||
@ -165,10 +216,10 @@ const AppointmentFormModal = () => {
|
||||
<div style={appointmentFormModalUI.blockStepStyle}>
|
||||
<Typography.Title level={4}>Подтверждение</Typography.Title>
|
||||
<p>
|
||||
<b>Пациент:</b> {patient ? `${patient.last_name} ${patient.first_name}` : "Не выбран"}
|
||||
<b>Пациент:</b> {patient ? `${patient.last_name} ${patient.first_name}` : "Не указан"}
|
||||
</p>
|
||||
<p>
|
||||
<b>Тип приема:</b> {appointmentType ? appointmentType.title : "Не выбран"}
|
||||
<b>Тип приема:</b> {appointmentType ? appointmentType.title : "Не указан"}
|
||||
</p>
|
||||
<p>
|
||||
<b>Время приема:</b>{" "}
|
||||
@ -183,9 +234,9 @@ const AppointmentFormModal = () => {
|
||||
<div dangerouslySetInnerHTML={{__html: values.results || "Не указаны"}}/>
|
||||
</div>
|
||||
);
|
||||
}, [appointmentFormModalUI, appointmentFormModalData]);
|
||||
}, [appointmentFormModalUI.form, appointmentFormModalData.patients, appointmentFormModalData.appointmentTypes, appointmentFormModalUI.blockStepStyle]);
|
||||
|
||||
const steps = [
|
||||
const steps = useMemo(() => [
|
||||
{
|
||||
title: "Выбор пациента",
|
||||
content: SelectPatientStep,
|
||||
@ -198,7 +249,7 @@ const AppointmentFormModal = () => {
|
||||
title: "Подтверждение",
|
||||
content: ConfirmStep,
|
||||
},
|
||||
];
|
||||
], [SelectPatientStep, AppointmentStep, ConfirmStep]);
|
||||
|
||||
if (appointmentFormModalData.isError) {
|
||||
return (
|
||||
@ -268,7 +319,7 @@ const AppointmentFormModal = () => {
|
||||
<Input
|
||||
placeholder="Поиск по результатам приема"
|
||||
value={appointmentFormModalUI.searchPreviousAppointments}
|
||||
onChange={appointmentFormModalUI.handleSetSearchPreviousAppointments}
|
||||
onChange={appointmentFormModalUI.handleSetSearchPatientString}
|
||||
style={{marginBottom: 16}}
|
||||
allowClear
|
||||
/>
|
||||
@ -287,5 +338,4 @@ const AppointmentFormModal = () => {
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export default AppointmentFormModal;
|
||||
@ -11,16 +11,12 @@ const useAppointmentFormModal = () => {
|
||||
data: patients = [],
|
||||
isLoading: isLoadingPatients,
|
||||
isError: isErrorPatients,
|
||||
} = useGetPatientsQuery(undefined, {
|
||||
pollingInterval: 20000,
|
||||
});
|
||||
} = useGetPatientsQuery(undefined);
|
||||
const {
|
||||
data: appointmentTypes = [],
|
||||
isLoading: isLoadingAppointmentTypes,
|
||||
isError: isErrorAppointmentTypes,
|
||||
} = useGetAppointmentTypesQuery(undefined, {
|
||||
pollingInterval: 20000,
|
||||
});
|
||||
} = useGetAppointmentTypesQuery(undefined);
|
||||
|
||||
const [createAppointment, { isLoading: isCreating, isError: isErrorCreating }] = useCreateAppointmentMutation();
|
||||
const [cancelAppointment] = useCancelScheduledAppointmentMutation();
|
||||
|
||||
@ -9,12 +9,9 @@ import { Grid } from "antd";
|
||||
const { useBreakpoint } = Grid;
|
||||
|
||||
const useAppointmentFormModalUI = (createAppointment, patients, cancelAppointment, useGetByPatientIdQuery) => {
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const {
|
||||
userData
|
||||
} = useSelector((state) => state.auth);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const { userData } = useSelector((state) => state.auth);
|
||||
const { modalVisible, scheduledData } = useSelector((state) => state.appointmentsUI);
|
||||
const [form] = Form.useForm();
|
||||
const screens = useBreakpoint();
|
||||
@ -24,12 +21,11 @@ const useAppointmentFormModalUI = (createAppointment, patients, cancelAppointmen
|
||||
const [appointmentDate, setAppointmentDate] = useState(dayjs(new Date()));
|
||||
const [searchPatientString, setSearchPatientString] = useState("");
|
||||
const [formValues, setFormValues] = useState({});
|
||||
const [results, setResults] = useState("");
|
||||
const [isDrawerVisible, setIsDrawerVisible] = useState(false);
|
||||
const [searchPreviousAppointments, setSearchPreviousAppointments] = useState("");
|
||||
const editor = useRef(null);
|
||||
const editorRef = useRef(null);
|
||||
|
||||
const { data: appointments = [] } = useGetAppointmentsQuery((userData.id), {
|
||||
const { data: appointments = [] } = useGetAppointmentsQuery(userData.id, {
|
||||
pollingInterval: 20000,
|
||||
});
|
||||
|
||||
@ -54,6 +50,88 @@ const useAppointmentFormModalUI = (createAppointment, patients, cancelAppointmen
|
||||
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"],
|
||||
},
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
const filteredPatients = useMemo(
|
||||
() =>
|
||||
patients.filter((patient) => {
|
||||
@ -88,25 +166,23 @@ const useAppointmentFormModalUI = (createAppointment, patients, cancelAppointmen
|
||||
const patient = patients.find((p) => p.id === scheduledData.patient_id);
|
||||
if (patient) {
|
||||
setSelectedPatient(patient);
|
||||
setCurrentStep(1); // Skip to appointment details step
|
||||
setCurrentStep(1);
|
||||
form.setFieldsValue({
|
||||
patient_id: scheduledData.patient_id,
|
||||
type_id: scheduledData.type_id,
|
||||
appointment_datetime: dayjs(scheduledData.appointment_datetime),
|
||||
results: scheduledData.results || "",
|
||||
});
|
||||
}
|
||||
} else {
|
||||
form.setFieldsValue({
|
||||
appointment_datetime: dayjs(new Date()),
|
||||
results: "",
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [modalVisible, form, scheduledData, patients]);
|
||||
|
||||
const handleResultsChange = (newContent) => {
|
||||
setResults(newContent);
|
||||
};
|
||||
|
||||
const handleSetSearchPatientString = (e) => {
|
||||
setSearchPatientString(e.target.value);
|
||||
};
|
||||
@ -137,7 +213,7 @@ const useAppointmentFormModalUI = (createAppointment, patients, cancelAppointmen
|
||||
|
||||
const closeDrawer = () => {
|
||||
setIsDrawerVisible(false);
|
||||
setSearchPreviousAppointments(""); // Reset search on close
|
||||
setSearchPreviousAppointments("");
|
||||
};
|
||||
|
||||
const handleClickNextButton = async () => {
|
||||
@ -178,7 +254,6 @@ const useAppointmentFormModalUI = (createAppointment, patients, cancelAppointmen
|
||||
const handleOk = async () => {
|
||||
try {
|
||||
const values = formValues;
|
||||
|
||||
const appointmentTime = values.appointment_datetime;
|
||||
const hasConflict = appointments.some((app) =>
|
||||
dayjs(app.appointment_datetime).isSame(appointmentTime, "minute")
|
||||
@ -198,7 +273,7 @@ const useAppointmentFormModalUI = (createAppointment, patients, cancelAppointmen
|
||||
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: results,
|
||||
results: values.results || "",
|
||||
};
|
||||
|
||||
await createAppointment(data).unwrap();
|
||||
@ -262,9 +337,7 @@ const useAppointmentFormModalUI = (createAppointment, patients, cancelAppointmen
|
||||
currentStep,
|
||||
searchPatientString,
|
||||
appointmentDate,
|
||||
results,
|
||||
setResults,
|
||||
editor,
|
||||
editor: editorRef,
|
||||
handleSetSearchPatientString,
|
||||
filteredPatients,
|
||||
filteredPreviousAppointments,
|
||||
@ -277,7 +350,6 @@ const useAppointmentFormModalUI = (createAppointment, patients, cancelAppointmen
|
||||
handleClickNextButton,
|
||||
handleClickBackButton,
|
||||
handleSetAppointmentDate,
|
||||
handleResultsChange,
|
||||
modalWidth,
|
||||
disableBackButton,
|
||||
disableNextButton,
|
||||
@ -297,6 +369,7 @@ const useAppointmentFormModalUI = (createAppointment, patients, cancelAppointmen
|
||||
closeDrawer,
|
||||
isLoadingPreviousAppointments,
|
||||
isErrorPreviousAppointments,
|
||||
joditConfig,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user