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

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),
):
appointment_service = ScheduledAppointmentsService(db)
return await appointment_service.create_scheduled_appointment(appointment)
return await appointment_service.create_scheduled_appointment(appointment, user.id)
@router.put(

View File

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

View File

@ -63,7 +63,8 @@ class ScheduledAppointmentsService:
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
]:
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',
)
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:
raise HTTPException(
@ -82,6 +83,8 @@ class ScheduledAppointmentsService:
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)
if not appointment_type:

View File

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

View File

@ -1,16 +1,9 @@
import {Calendar} from "antd";
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 useAppointments from "../../useAppointments.js";
import useAppointmentCalendarUI from "./useAppointmentCalendarUI.js";
dayjs.extend(utc);
dayjs.extend(timezone);
dayjs.tz.setDefault('Asia/Almaty');
const AppointmentsCalendarTab = () => {
const appointmentsData = useAppointments();
const appointmentsCalendarUI = useAppointmentCalendarUI(

View File

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

View File

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

View File

@ -8,7 +8,7 @@ import {Grid} from "antd";
const {useBreakpoint} = Grid;
const useAppointmentFormModalUI = (onCancel, createAppointment, updateAppointment, patients) => {
const useAppointmentFormModalUI = (onCancel, createAppointment, patients) => {
const dispatch = useDispatch();
const {modalVisible} = useSelector(state => state.appointmentsUI);
const [form] = Form.useForm();
@ -57,7 +57,7 @@ const useAppointmentFormModalUI = (onCancel, createAppointment, updateAppointmen
appointment_datetime: dayjs(new Date()),
})
}
}, [modalVisible, form, appointments]);
}, [modalVisible, form]);
const handleResultsChange = (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,
setSelectedAppointment,
setSelectedScheduledAppointment,
toggleSider
toggleSider,
openScheduledModal,
} from "../../../Redux/Slices/appointmentsSlice.js";
import { useEffect, useMemo } from "react";
import dayjs from "dayjs";
@ -60,8 +61,7 @@ const useAppointmentsUI = (appointments, scheduledAppointments) => {
};
const openCreateScheduledAppointmentModal = () => {
// Логика для запланированных приемов будет добавлена позже
console.log('Открыть модальное окно для запланированного приема');
dispatch(openScheduledModal());
};
const siderButtonText = useMemo(() => hovered ? (collapsed ? "Показать предстоящие события" : "Скрыть предстоящие события") : "", [collapsed, hovered]);

View File

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