сделал создание запланированного приема

This commit is contained in:
Андрей Дувакин 2025-06-01 19:14:47 +05:00
parent c2a846f433
commit 264ac5063a
13 changed files with 284 additions and 22 deletions

View File

@ -65,7 +65,7 @@ async def create_appointment(
user=Depends(get_current_user), user=Depends(get_current_user),
): ):
appointment_service = ScheduledAppointmentsService(db) appointment_service = ScheduledAppointmentsService(db)
return await appointment_service.create_scheduled_appointment(appointment) return await appointment_service.create_scheduled_appointment(appointment, user.id)
@router.put( @router.put(

View File

@ -13,7 +13,7 @@ class ScheduledAppointmentEntity(BaseModel):
scheduled_datetime: datetime.datetime scheduled_datetime: datetime.datetime
patient_id: int patient_id: int
doctor_id: int doctor_id: Optional[int] = None
type_id: int type_id: int
patient: Optional[PatientEntity] = None patient: Optional[PatientEntity] = None

View File

@ -63,7 +63,8 @@ class ScheduledAppointmentsService:
for scheduled_appointment in scheduled_appointments for scheduled_appointment in scheduled_appointments
] ]
async def create_scheduled_appointment(self, scheduled_appointment: ScheduledAppointmentEntity) -> Optional[ async def create_scheduled_appointment(self, scheduled_appointment: ScheduledAppointmentEntity, doctor_id: int) -> \
Optional[
ScheduledAppointmentEntity ScheduledAppointmentEntity
]: ]:
patient = await self.patients_repository.get_by_id(scheduled_appointment.patient_id) patient = await self.patients_repository.get_by_id(scheduled_appointment.patient_id)
@ -74,7 +75,7 @@ class ScheduledAppointmentsService:
detail='The patient with this ID was not found', detail='The patient with this ID was not found',
) )
doctor = await self.users_repository.get_by_id(scheduled_appointment.doctor_id) doctor = await self.users_repository.get_by_id(doctor_id)
if not doctor: if not doctor:
raise HTTPException( raise HTTPException(
@ -82,6 +83,8 @@ class ScheduledAppointmentsService:
detail='The doctor/user with this ID was not found', detail='The doctor/user with this ID was not found',
) )
scheduled_appointment.doctor_id = doctor_id
appointment_type = await self.appointment_types_repository.get_by_id(scheduled_appointment.type_id) appointment_type = await self.appointment_types_repository.get_by_id(scheduled_appointment.type_id)
if not appointment_type: if not appointment_type:

View File

@ -13,6 +13,7 @@ import {useDispatch} from "react-redux";
import {closeModal, openModal} from "../../../Redux/Slices/appointmentsSlice.js"; import {closeModal, openModal} from "../../../Redux/Slices/appointmentsSlice.js";
import AppointmentViewModal import AppointmentViewModal
from "./Components/AppointmentViewModal/AppointmentViewModal.jsx"; from "./Components/AppointmentViewModal/AppointmentViewModal.jsx";
import ScheduledAppointmentFormModal from "./Components/ScheduledAppintmentFormModal/ScheduledAppointmentFormModal.jsx";
const AppointmentsPage = () => { const AppointmentsPage = () => {
const appointmentsData = useAppointments(); const appointmentsData = useAppointments();
@ -110,6 +111,7 @@ const AppointmentsPage = () => {
</Button> </Button>
</div> </div>
<FloatButton.Group <FloatButton.Group
placement={"left"}
trigger="hover" trigger="hover"
type="primary" type="primary"
icon={<PlusOutlined/>} icon={<PlusOutlined/>}
@ -136,6 +138,8 @@ const AppointmentsPage = () => {
visible={appointmentsPageUI.selectedAppointment !== null} visible={appointmentsPageUI.selectedAppointment !== null}
onCancel={appointmentsPageUI.handleCancelViewModal} onCancel={appointmentsPageUI.handleCancelViewModal}
/> />
<ScheduledAppointmentFormModal/>
</> </>
)} )}
</> </>

View File

@ -1,16 +1,9 @@
import {Calendar} from "antd"; import {Calendar} from "antd";
import 'dayjs/locale/ru'; import 'dayjs/locale/ru';
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
import CalendarCell from "../../../../Widgets/CalendarCell.jsx"; import CalendarCell from "../../../../Widgets/CalendarCell.jsx";
import useAppointments from "../../useAppointments.js"; import useAppointments from "../../useAppointments.js";
import useAppointmentCalendarUI from "./useAppointmentCalendarUI.js"; import useAppointmentCalendarUI from "./useAppointmentCalendarUI.js";
dayjs.extend(utc);
dayjs.extend(timezone);
dayjs.tz.setDefault('Asia/Almaty');
const AppointmentsCalendarTab = () => { const AppointmentsCalendarTab = () => {
const appointmentsData = useAppointments(); const appointmentsData = useAppointments();
const appointmentsCalendarUI = useAppointmentCalendarUI( const appointmentsCalendarUI = useAppointmentCalendarUI(

View File

@ -27,7 +27,6 @@ const AppointmentFormModal = ({onCancel}) => {
const appointmentFormModalUI = useAppointmentFormModalUI( const appointmentFormModalUI = useAppointmentFormModalUI(
onCancel, onCancel,
appointmentFormModalData.createAppointment, appointmentFormModalData.createAppointment,
appointmentFormModalData.updateAppointment,
appointmentFormModalData.patients, appointmentFormModalData.patients,
); );

View File

@ -19,15 +19,13 @@ const useAppointmentFormModal = () => {
}); });
const [createAppointment, {isLoading: isCreating, isError: isErrorCreating}] = useCreateAppointmentMutation(); const [createAppointment, {isLoading: isCreating, isError: isErrorCreating}] = useCreateAppointmentMutation();
const [updateAppointment, {isLoading: isUpdating, isError: isErrorUpdating}] = useUpdateAppointmentMutation();
return { return {
patients, patients,
appointmentTypes, appointmentTypes,
createAppointment, createAppointment,
updateAppointment, isLoading: isLoadingPatients || isLoadingAppointmentTypes || isCreating,
isLoading: isLoadingPatients || isLoadingAppointmentTypes || isCreating || isUpdating, isError: isErrorPatients || isErrorAppointmentTypes || isErrorCreating,
isError: isErrorPatients || isErrorAppointmentTypes || isErrorCreating || isErrorUpdating,
}; };
}; };

View File

@ -8,7 +8,7 @@ import {Grid} from "antd";
const {useBreakpoint} = Grid; const {useBreakpoint} = Grid;
const useAppointmentFormModalUI = (onCancel, createAppointment, updateAppointment, patients) => { const useAppointmentFormModalUI = (onCancel, createAppointment, patients) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const {modalVisible} = useSelector(state => state.appointmentsUI); const {modalVisible} = useSelector(state => state.appointmentsUI);
const [form] = Form.useForm(); const [form] = Form.useForm();
@ -57,7 +57,7 @@ const useAppointmentFormModalUI = (onCancel, createAppointment, updateAppointmen
appointment_datetime: dayjs(new Date()), appointment_datetime: dayjs(new Date()),
}) })
} }
}, [modalVisible, form, appointments]); }, [modalVisible, form]);
const handleResultsChange = (newContent) => { const handleResultsChange = (newContent) => {
setResults(newContent); setResults(newContent);

View File

@ -0,0 +1,107 @@
import useScheduledAppointmentFormModal from "./useScheduledAppointmentFormModal.js";
import useScheduledAppointmentFormModalUI from "./useScheduledAppointmentFormModalUI.js";
import {Button, Collapse, DatePicker, Input, Modal, Select, Space, Typography} from "antd";
import dayjs from "dayjs";
const ScheduledAppointmentFormModal = () => {
const scheduledAppointmentFormModalData = useScheduledAppointmentFormModal();
const scheduledAppointmentFormModalUI = useScheduledAppointmentFormModalUI(scheduledAppointmentFormModalData.patients, scheduledAppointmentFormModalData.createScheduledAppointment);
const patientsItems = scheduledAppointmentFormModalUI.filteredPatients.map((patient) => ({
key: patient.id,
label: `${patient.last_name} ${patient.first_name} (${scheduledAppointmentFormModalUI.getDateString(patient.birthday)})`,
children: (
<div>
<p><b>Пациент:</b> {patient.last_name} {patient.first_name}</p>
<p><b>Дата рождения:</b> {scheduledAppointmentFormModalUI.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={() => scheduledAppointmentFormModalUI.setSelectedPatient(patient)}>
Выбрать
</Button>
</div>
),
}));
return (
<>
<Modal
title="Планирование приема"
open={scheduledAppointmentFormModalUI.scheduledModalVisible}
onCancel={scheduledAppointmentFormModalUI.handleCancelModal}
footer={null}
width={scheduledAppointmentFormModalUI.modalWidth}
>
<Space direction="vertical">
<Typography.Text>Тип приема</Typography.Text>
<Select
placeholder="Тип приема"
value={scheduledAppointmentFormModalUI.selectedAppointmentType}
onChange={scheduledAppointmentFormModalUI.handleSetSelectedAppointmentType}
>
{scheduledAppointmentFormModalData.appointmentTypes.map((appointmentType) => (
<Select.Option key={appointmentType.id} value={appointmentType.id}>
{appointmentType.title}
</Select.Option>
))}
</Select>
<Typography.Text>Планируемая дата приема</Typography.Text>
<DatePicker
showTime
format="DD.MM.YYYY HH:mm"
placeholder="Дата приема"
style={scheduledAppointmentFormModalUI.datePickerStyle}
value={scheduledAppointmentFormModalUI.selectedDateTime}
onChange={scheduledAppointmentFormModalUI.handleSetDateTime}
minDate={dayjs(new Date()).add(1, 'day')}
/>
</Space>
{scheduledAppointmentFormModalUI.selectedPatient ? (
<div style={scheduledAppointmentFormModalUI.blockStepStyle}>
<Typography.Text strong>
{scheduledAppointmentFormModalUI.selectedPatient.last_name} {scheduledAppointmentFormModalUI.selectedPatient.first_name}
</Typography.Text>
<p><b>Дата
рождения:</b> {scheduledAppointmentFormModalUI.getSelectedPatientBirthdayString()}
</p>
<p><b>Email:</b> {scheduledAppointmentFormModalUI.selectedPatient.email || 'Не указан'}</p>
<p><b>Телефон:</b> {scheduledAppointmentFormModalUI.selectedPatient.phone || 'Не указан'}
</p>
<Button
type="primary"
onClick={scheduledAppointmentFormModalUI.resetPatient}
danger
>
Выбрать другого пациента
</Button>
</div>
) : (
<>
<Input
placeholder="Поиск пациента"
value={scheduledAppointmentFormModalUI.searchPatientString}
onChange={scheduledAppointmentFormModalUI.handleSetSearchPatientString}
style={scheduledAppointmentFormModalUI.searchInputStyle}
allowClear
/>
<div style={scheduledAppointmentFormModalUI.chooseContainerStyle}>
<Collapse items={patientsItems}/>
</div>
</>
)}
<Button
type="primary"
disabled={scheduledAppointmentFormModalUI.selectedPatient === null || scheduledAppointmentFormModalUI.selectedDateTime === null || scheduledAppointmentFormModalUI.selectedAppointmentType === null}
onClick={scheduledAppointmentFormModalUI.handleCreateScheduledAppointment}
style={scheduledAppointmentFormModalUI.buttonStyle}
>
Запланировать
</Button>
</Modal>
</>
);
};
export default ScheduledAppointmentFormModal;

View File

@ -0,0 +1,34 @@
import {useGetPatientsQuery} from "../../../../../Api/patientsApi.js";
import {useGetAppointmentTypesQuery} from "../../../../../Api/appointmentTypesApi.js";
import {useCreateScheduledAppointmentMutation} from "../../../../../Api/scheduledAppointmentsApi.js";
const useScheduledAppointmentFormModal = () => {
const {
data: patients = [],
isLoading: isLoadingPatients,
isError: isErrorPatients,
} = useGetPatientsQuery(undefined, {
pollingInterval: 20000,
});
const {
data: appointmentTypes = [],
isLoading: isLoadingAppointmentTypes,
isError: isErrorAppointmentTypes,
} = useGetAppointmentTypesQuery(undefined, {
pollingInterval: 20000,
});
const [createScheduledAppointment, {isLoading: isCreating, isError: isErrorCreating}] = useCreateScheduledAppointmentMutation();
return {
patients,
appointmentTypes,
createScheduledAppointment,
isLoading: isLoadingPatients || isLoadingAppointmentTypes || isCreating,
isError: isErrorPatients || isErrorAppointmentTypes || isErrorCreating,
};
};
export default useScheduledAppointmentFormModal;

View File

@ -0,0 +1,112 @@
import {useDispatch, useSelector} from "react-redux";
import {closeScheduledModal} from "../../../../../Redux/Slices/appointmentsSlice.js";
import {useMemo, useState} from "react";
import dayjs from "dayjs";
import {notification} from "antd";
const useScheduledAppointmentFormModalUI = (patients, createScheduledAppointment) => {
const dispatch = useDispatch();
const {
scheduledModalVisible,
} = useSelector(state => state.appointmentsUI);
const [searchPatientString, setSearchPatientString] = useState('');
const [selectedPatient, setSelectedPatient] = useState(null);
const [selectedDateTime, setSelectedDateTime] = useState(dayjs(new Date()).add(1, 'day'));
const [selectedAppointmentType, setSelectedAppointmentType] = useState(null);
const handleCreateScheduledAppointment = async () => {
try {
const data = {
scheduled_datetime: selectedDateTime.format('YYYY-MM-DD HH:mm:ss'),
patient_id: selectedPatient.id,
type_id: selectedAppointmentType
};
console.log(data)
await createScheduledAppointment(data).unwrap();
notification.success({
message: 'Прием создан',
placement: 'topRight',
description: 'Прием успешно запланирован.',
})
dispatch(closeScheduledModal());
} catch (error) {
notification.error({
message: 'Ошибка',
description: error.data?.message || 'Не удалось сохранить прием.',
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 handleSetDateTime = (date) => {
setSelectedDateTime(date);
};
const handleSetSearchPatientString = (e) => {
setSearchPatientString(e.target.value);
};
const handleSetSelectedAppointmentType = (type) => {
setSelectedAppointmentType(type);
};
const getDateString = (date) => {
return date ? dayjs(date).format('DD.MM.YYYY') : 'Не указано';
};
const getSelectedPatientBirthdayString = () => {
return selectedPatient ? getDateString(selectedPatient.birthday) : 'Не выбран';
};
const resetPatient = () => {
setSelectedPatient(null);
};
const searchInputStyle = {marginBottom: 16};
const modalWidth = 700;
const handleCancelModal = () => {
dispatch(closeScheduledModal());
};
const datePickerStyle = {marginBottom: 16};
const buttonStyle = {marginTop: 8};
return {
scheduledModalVisible,
filteredPatients,
selectedPatient,
setSelectedPatient,
modalWidth,
selectedDateTime,
searchInputStyle,
datePickerStyle,
buttonStyle,
selectedAppointmentType,
handleCancelModal,
handleSetSearchPatientString,
resetPatient,
getSelectedPatientBirthdayString,
getDateString,
handleSetDateTime,
handleSetSelectedAppointmentType,
handleCreateScheduledAppointment,
}
};
export default useScheduledAppointmentFormModalUI

View File

@ -4,7 +4,8 @@ import {
setHovered, setHovered,
setSelectedAppointment, setSelectedAppointment,
setSelectedScheduledAppointment, setSelectedScheduledAppointment,
toggleSider toggleSider,
openScheduledModal,
} from "../../../Redux/Slices/appointmentsSlice.js"; } from "../../../Redux/Slices/appointmentsSlice.js";
import { useEffect, useMemo } from "react"; import { useEffect, useMemo } from "react";
import dayjs from "dayjs"; import dayjs from "dayjs";
@ -60,8 +61,7 @@ const useAppointmentsUI = (appointments, scheduledAppointments) => {
}; };
const openCreateScheduledAppointmentModal = () => { const openCreateScheduledAppointmentModal = () => {
// Логика для запланированных приемов будет добавлена позже dispatch(openScheduledModal());
console.log('Открыть модальное окно для запланированного приема');
}; };
const siderButtonText = useMemo(() => hovered ? (collapsed ? "Показать предстоящие события" : "Скрыть предстоящие события") : "", [collapsed, hovered]); const siderButtonText = useMemo(() => hovered ? (collapsed ? "Показать предстоящие события" : "Скрыть предстоящие события") : "", [collapsed, hovered]);
@ -94,4 +94,4 @@ const useAppointmentsUI = (appointments, scheduledAppointments) => {
}; };
}; };
export default useAppointmentsUI; export default useAppointmentsUI;

View File

@ -5,6 +5,7 @@ const initialState = {
siderWidth: 250, siderWidth: 250,
hovered: false, hovered: false,
modalVisible: false, modalVisible: false,
scheduledModalVisible: false,
selectedAppointment: null, selectedAppointment: null,
scheduledAppointments: [], scheduledAppointments: [],
selectedScheduledAppointment: null, selectedScheduledAppointment: null,
@ -32,6 +33,15 @@ const appointmentsSlice = createSlice({
closeModal: (state) => { closeModal: (state) => {
state.modalVisible = false; state.modalVisible = false;
}, },
openScheduledModal: (state) => {
state.scheduledModalVisible = true;
},
closeScheduledModal: (state) => {
state.scheduledModalVisible = false;
},
setSelectedAppointments: (state, action) => {
state.selectedAppointments = action.payload;
},
setSelectedAppointment: (state, action) => { setSelectedAppointment: (state, action) => {
state.selectedAppointment = action.payload; state.selectedAppointment = action.payload;
}, },
@ -55,6 +65,8 @@ export const {
setSelectedAppointment, setSelectedAppointment,
setSelectedScheduledAppointment, setSelectedScheduledAppointment,
setScheduledAppointments, setScheduledAppointments,
openScheduledModal,
closeScheduledModal,
} = appointmentsSlice.actions; } = appointmentsSlice.actions;
export default appointmentsSlice.reducer; export default appointmentsSlice.reducer;