diff --git a/api/app/controllers/scheduled_appointments_router.py b/api/app/controllers/scheduled_appointments_router.py index fb57b5c..31dcd33 100644 --- a/api/app/controllers/scheduled_appointments_router.py +++ b/api/app/controllers/scheduled_appointments_router.py @@ -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( diff --git a/api/app/domain/entities/scheduled_appointment.py b/api/app/domain/entities/scheduled_appointment.py index 1e070ae..390a7f3 100644 --- a/api/app/domain/entities/scheduled_appointment.py +++ b/api/app/domain/entities/scheduled_appointment.py @@ -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 diff --git a/api/app/infrastructure/scheduled_appointments_service.py b/api/app/infrastructure/scheduled_appointments_service.py index 039ef7d..369fc6d 100644 --- a/api/app/infrastructure/scheduled_appointments_service.py +++ b/api/app/infrastructure/scheduled_appointments_service.py @@ -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: diff --git a/web-app/src/Components/Pages/AppointmentsPage/AppointmentsPage.jsx b/web-app/src/Components/Pages/AppointmentsPage/AppointmentsPage.jsx index 5a07f50..640b3dd 100644 --- a/web-app/src/Components/Pages/AppointmentsPage/AppointmentsPage.jsx +++ b/web-app/src/Components/Pages/AppointmentsPage/AppointmentsPage.jsx @@ -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 = () => { } @@ -136,6 +138,8 @@ const AppointmentsPage = () => { visible={appointmentsPageUI.selectedAppointment !== null} onCancel={appointmentsPageUI.handleCancelViewModal} /> + + )} diff --git a/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/AppointmentsCalendarTab.jsx b/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/AppointmentsCalendarTab.jsx index 4b1c66d..482196d 100644 --- a/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/AppointmentsCalendarTab.jsx +++ b/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentCalendarTab/AppointmentsCalendarTab.jsx @@ -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( diff --git a/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentFormModal/AppointmentFormModal.jsx b/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentFormModal/AppointmentFormModal.jsx index 65ac2bc..b7d1de5 100644 --- a/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentFormModal/AppointmentFormModal.jsx +++ b/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentFormModal/AppointmentFormModal.jsx @@ -27,7 +27,6 @@ const AppointmentFormModal = ({onCancel}) => { const appointmentFormModalUI = useAppointmentFormModalUI( onCancel, appointmentFormModalData.createAppointment, - appointmentFormModalData.updateAppointment, appointmentFormModalData.patients, ); diff --git a/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentFormModal/useAppointmentFormModal.js b/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentFormModal/useAppointmentFormModal.js index f0f4664..ab197b2 100644 --- a/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentFormModal/useAppointmentFormModal.js +++ b/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentFormModal/useAppointmentFormModal.js @@ -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, }; }; diff --git a/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentFormModal/useAppointmentFormModalUI.js b/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentFormModal/useAppointmentFormModalUI.js index bc0fd89..c1a860a 100644 --- a/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentFormModal/useAppointmentFormModalUI.js +++ b/web-app/src/Components/Pages/AppointmentsPage/Components/AppointmentFormModal/useAppointmentFormModalUI.js @@ -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); diff --git a/web-app/src/Components/Pages/AppointmentsPage/Components/ScheduledAppintmentFormModal/ScheduledAppointmentFormModal.jsx b/web-app/src/Components/Pages/AppointmentsPage/Components/ScheduledAppintmentFormModal/ScheduledAppointmentFormModal.jsx new file mode 100644 index 0000000..41354ec --- /dev/null +++ b/web-app/src/Components/Pages/AppointmentsPage/Components/ScheduledAppintmentFormModal/ScheduledAppointmentFormModal.jsx @@ -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: ( +
+

Пациент: {patient.last_name} {patient.first_name}

+

Дата рождения: {scheduledAppointmentFormModalUI.getDateString(patient.birthday)}

+

Диагноз: {patient.diagnosis || 'Не указан'}

+

Email: {patient.email || 'Не указан'}

+

Телефон: {patient.phone || 'Не указан'}

+ +
+ ), + })); + + return ( + <> + + + Тип приема + + Планируемая дата приема + + + {scheduledAppointmentFormModalUI.selectedPatient ? ( +
+ + {scheduledAppointmentFormModalUI.selectedPatient.last_name} {scheduledAppointmentFormModalUI.selectedPatient.first_name} + +

Дата + рождения: {scheduledAppointmentFormModalUI.getSelectedPatientBirthdayString()} +

+

Email: {scheduledAppointmentFormModalUI.selectedPatient.email || 'Не указан'}

+

Телефон: {scheduledAppointmentFormModalUI.selectedPatient.phone || 'Не указан'} +

+ +
+ ) : ( + <> + +
+ +
+ + )} + +
+ + ); +}; + +export default ScheduledAppointmentFormModal; \ No newline at end of file diff --git a/web-app/src/Components/Pages/AppointmentsPage/Components/ScheduledAppintmentFormModal/useScheduledAppointmentFormModal.js b/web-app/src/Components/Pages/AppointmentsPage/Components/ScheduledAppintmentFormModal/useScheduledAppointmentFormModal.js new file mode 100644 index 0000000..4fd3161 --- /dev/null +++ b/web-app/src/Components/Pages/AppointmentsPage/Components/ScheduledAppintmentFormModal/useScheduledAppointmentFormModal.js @@ -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; \ No newline at end of file diff --git a/web-app/src/Components/Pages/AppointmentsPage/Components/ScheduledAppintmentFormModal/useScheduledAppointmentFormModalUI.js b/web-app/src/Components/Pages/AppointmentsPage/Components/ScheduledAppintmentFormModal/useScheduledAppointmentFormModalUI.js new file mode 100644 index 0000000..e7534e9 --- /dev/null +++ b/web-app/src/Components/Pages/AppointmentsPage/Components/ScheduledAppintmentFormModal/useScheduledAppointmentFormModalUI.js @@ -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 \ No newline at end of file diff --git a/web-app/src/Components/Pages/AppointmentsPage/useAppointmentsUI.js b/web-app/src/Components/Pages/AppointmentsPage/useAppointmentsUI.js index 6f47bf8..71abf7d 100644 --- a/web-app/src/Components/Pages/AppointmentsPage/useAppointmentsUI.js +++ b/web-app/src/Components/Pages/AppointmentsPage/useAppointmentsUI.js @@ -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]); @@ -94,4 +94,4 @@ const useAppointmentsUI = (appointments, scheduledAppointments) => { }; }; -export default useAppointmentsUI; \ No newline at end of file +export default useAppointmentsUI; diff --git a/web-app/src/Redux/Slices/appointmentsSlice.js b/web-app/src/Redux/Slices/appointmentsSlice.js index 85c556a..6a772b4 100644 --- a/web-app/src/Redux/Slices/appointmentsSlice.js +++ b/web-app/src/Redux/Slices/appointmentsSlice.js @@ -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; \ No newline at end of file