сделал просмотр информации о приеме

This commit is contained in:
Андрей Дувакин 2025-06-01 17:45:45 +05:00
parent 2a1f6a9989
commit c2a846f433
10 changed files with 156 additions and 125 deletions

View File

@ -1,15 +1,18 @@
import { Button, FloatButton, Result, Tabs, Typography } from "antd";
import { Splitter } from "antd";
import { CalendarOutlined, TableOutlined, MenuFoldOutlined, MenuUnfoldOutlined, PlusOutlined } from "@ant-design/icons";
import {Button, FloatButton, Result, Tabs, Typography} from "antd";
import {Splitter} from "antd";
import {CalendarOutlined, TableOutlined, MenuFoldOutlined, MenuUnfoldOutlined, PlusOutlined} from "@ant-design/icons";
import AppointmentsCalendarTab from "./Components/AppointmentCalendarTab/AppointmentsCalendarTab.jsx";
import AppointmentsTableTab from "./Components/AppointmentTableTab/AppointmentsTableTab.jsx";
import useAppointmentsUI from "./useAppointmentsUI.js";
import useAppointments from "./useAppointments.js";
import dayjs from 'dayjs';
import LoadingIndicator from "../../Widgets/LoadingIndicator.jsx";
import AppointmentFormModal from "./Components/AppointmentCalendarTab/Components/AppointmentFormModal/AppointmentFormModal.jsx";
import { useDispatch } from "react-redux";
import { closeModal, openModal } from "../../../Redux/Slices/appointmentsSlice.js";
import AppointmentFormModal
from "./Components/AppointmentFormModal/AppointmentFormModal.jsx";
import {useDispatch} from "react-redux";
import {closeModal, openModal} from "../../../Redux/Slices/appointmentsSlice.js";
import AppointmentViewModal
from "./Components/AppointmentViewModal/AppointmentViewModal.jsx";
const AppointmentsPage = () => {
const appointmentsData = useAppointments();
@ -24,14 +27,14 @@ const AppointmentsPage = () => {
{
key: "1",
label: "Календарь приемов",
children: <AppointmentsCalendarTab />,
icon: <CalendarOutlined />,
children: <AppointmentsCalendarTab/>,
icon: <CalendarOutlined/>,
},
{
key: "2",
label: "Таблица приемов",
children: <AppointmentsTableTab />,
icon: <TableOutlined />,
children: <AppointmentsTableTab/>,
icon: <TableOutlined/>,
},
];
@ -46,7 +49,7 @@ const AppointmentsPage = () => {
return (
<>
{appointmentsData.isLoading ? (
<LoadingIndicator />
<LoadingIndicator/>
) : (
<>
<Splitter
@ -62,7 +65,7 @@ const AppointmentsPage = () => {
min="25%"
max="90%"
>
<Tabs defaultActiveKey="1" items={items} />
<Tabs defaultActiveKey="1" items={items}/>
</Splitter.Panel>
{appointmentsPageUI.showSplitterPanel && (
@ -100,7 +103,7 @@ const AppointmentsPage = () => {
<Button
type="primary"
onClick={appointmentsPageUI.handleToggleSider}
icon={appointmentsPageUI.collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
icon={appointmentsPageUI.collapsed ? <MenuUnfoldOutlined/> : <MenuFoldOutlined/>}
style={appointmentsPageUI.siderButtonStyle}
>
{appointmentsPageUI.siderButtonText}
@ -109,16 +112,16 @@ const AppointmentsPage = () => {
<FloatButton.Group
trigger="hover"
type="primary"
icon={<PlusOutlined />}
icon={<PlusOutlined/>}
tooltip="Создать"
>
<FloatButton
icon={<PlusOutlined />}
icon={<PlusOutlined/>}
onClick={() => dispatch(openModal())}
tooltip="Прием"
/>
<FloatButton
icon={<CalendarOutlined />}
icon={<CalendarOutlined/>}
onClick={appointmentsPageUI.openCreateScheduledAppointmentModal}
tooltip="Запланированный прием"
/>
@ -128,6 +131,11 @@ const AppointmentsPage = () => {
visible={appointmentsPageUI.modalVisible}
onCancel={handleCancelModal}
/>
<AppointmentViewModal
visible={appointmentsPageUI.selectedAppointment !== null}
onCancel={appointmentsPageUI.handleCancelViewModal}
/>
</>
)}
</>

View File

@ -1,4 +1,4 @@
import {Calendar, Modal, Form, Input, DatePicker, Button} from "antd";
import {Calendar} from "antd";
import 'dayjs/locale/ru';
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
@ -6,12 +6,6 @@ import timezone from 'dayjs/plugin/timezone';
import CalendarCell from "../../../../Widgets/CalendarCell.jsx";
import useAppointments from "../../useAppointments.js";
import useAppointmentCalendarUI from "./useAppointmentCalendarUI.js";
import {
closeModal,
openModal,
setSelectedAppointment,
setSelectedScheduledAppointment,
} from "../../../../../Redux/Slices/appointmentsSlice.js"
dayjs.extend(utc);
dayjs.extend(timezone);
@ -40,14 +34,7 @@ const AppointmentsCalendarTab = () => {
appointments={appointmentsForDate}
scheduledAppointments={scheduledForDate}
onCellClick={() => appointmentsCalendarUI.onSelect(value)}
onItemClick={(appointment) => {
if (appointment.appointment_datetime) {
dispatch(setSelectedAppointment(appointment));
} else {
dispatch(setSelectedScheduledAppointment(appointment));
}
dispatch(openModal());
}}
onItemClick={appointmentsCalendarUI.onOpenAppointmentModal}
/>
);
};

View File

@ -53,91 +53,14 @@ const useAppointmentCalendarUI = (appointments, scheduledAppointments) => {
dispatch(openModal());
};
const onFinish = async (values) => {
try {
const appointmentTime = values.appointmentTime;
const isScheduled = !(selectedAppointment?.appointment_datetime);
const conflictingAppointments = isScheduled
? scheduledAppointments
: appointments;
const hasConflict = conflictingAppointments.some(app =>
dayjs(app.appointment_datetime || app.scheduled_datetime)
.tz('Europe/Moscow')
.isSame(appointmentTime, 'minute')
);
if (hasConflict) {
notification.error({
message: "Выбранное время уже занято",
description: "Выбранное время уже занято",
placement: "topRight",
});
return;
}
const data = {
patient: {
last_name: values.patientName.split(' ')[0] || '',
first_name: values.patientName.split(' ')[1] || '',
},
...(isScheduled
? { scheduled_datetime: appointmentTime.toISOString() }
: { appointment_datetime: appointmentTime.toISOString() }),
reason: values.reason,
};
if (selectedAppointment || selectedScheduledAppointment) {
if (selectedAppointment?.appointment_datetime) {
await updateAppointment({ id: selectedAppointment.id, data }).unwrap();
notification.success({
message: "Прием успешно обновлен",
description: "Прием успешно обновлен",
placement: "topRight",
});
} else {
await updateScheduledAppointment({ id: selectedScheduledAppointment.id, data }).unwrap();
notification.success({
message: "Запланированный прием успешно обновлен",
description: "Запланированный прием успешно обновлен",
placement: "topRight",
});
}
} else {
if (isScheduled) {
await createScheduledAppointment(data).unwrap();
notification.success({
message: "Запланированный прием успешно создан",
description: "Запланированный прием успешно создан",
placement: "topRight",
});
} else {
await createAppointment(data).unwrap();
notification.success({
message: "Прием успешно создан",
description: "Прием успешно создан",
placement: "topRight",
});
}
}
dispatch(closeModal());
form.resetFields();
} catch (error) {
notification.error({
message: "Ошибка при сохранении приема",
description: error.data?.message || "Не удалось сохранить прием",
placement: "topRight",
});
const onOpenAppointmentModal = (appointment) => {
if (appointment.appointment_datetime) {
dispatch(setSelectedAppointment(appointment));
} else {
dispatch(setSelectedScheduledAppointment(appointment));
}
};
const handleCreateAppointment = () => {
dispatch(setSelectedAppointment(null));
dispatch(setSelectedScheduledAppointment(null));
form.resetFields();
dispatch(openModal());
};
const getAppointmentsByListAndDate = (list, value, isScheduled = false) => {
const date = value.tz('Europe/Moscow').format('YYYY-MM-DD');
return list.filter(app =>
@ -153,6 +76,7 @@ const useAppointmentCalendarUI = (appointments, scheduledAppointments) => {
calendarContainerStyle,
onSelect,
getAppointmentsByListAndDate,
onOpenAppointmentModal,
};
};

View File

@ -18,7 +18,7 @@ import {
} from "antd";
import useAppointmentFormModal from "./useAppointmentFormModal.js";
import useAppointmentFormModalUI from "./useAppointmentFormModalUI.js";
import LoadingIndicator from "../../../../../../Widgets/LoadingIndicator.jsx";
import LoadingIndicator from "../../../../Widgets/LoadingIndicator.jsx";
import {useMemo} from "react";
import PropTypes from "prop-types";

View File

@ -1,6 +1,6 @@
import {useGetPatientsQuery} from "../../../../../../../Api/patientsApi.js";
import {useGetAppointmentTypesQuery} from "../../../../../../../Api/appointmentTypesApi.js";
import {useCreateAppointmentMutation, useUpdateAppointmentMutation} from "../../../../../../../Api/appointmentsApi.js";
import {useGetPatientsQuery} from "../../../../../Api/patientsApi.js";
import {useGetAppointmentTypesQuery} from "../../../../../Api/appointmentTypesApi.js";
import {useCreateAppointmentMutation, useUpdateAppointmentMutation} from "../../../../../Api/appointmentsApi.js";
const useAppointmentFormModal = () => {
const {

View File

@ -1,9 +1,9 @@
import {Form, notification} from "antd";
import {useDispatch, useSelector} from "react-redux";
import {closeModal} from "../../../../../../../Redux/Slices/appointmentsSlice.js";
import {closeModal} from "../../../../../Redux/Slices/appointmentsSlice.js";
import {useEffect, useMemo, useState} from "react";
import dayjs from "dayjs";
import {useGetAppointmentsQuery} from "../../../../../../../Api/appointmentsApi.js";
import {useGetAppointmentsQuery} from "../../../../../Api/appointmentsApi.js";
import {Grid} from "antd";
const {useBreakpoint} = Grid;

View File

@ -0,0 +1,72 @@
import {Button, Modal, Row, Typography} from "antd";
import useAppointmentViewUI from "./useAppointmentViewUI.js";
import PropTypes from "prop-types";
import dayjs from "dayjs";
const AppointmentViewModal = ({visible, onCancel}) => {
const appointmentViewModalUI = useAppointmentViewUI(visible, onCancel);
if (!appointmentViewModalUI.selectedAppointment) {
return null;
}
return (
<>
<Modal
title="Просмотр приема"
open={visible}
onCancel={onCancel}
footer={null}
width={appointmentViewModalUI.modalWidth}
>
<div style={appointmentViewModalUI.blockStyle}>
<Typography.Title level={4}>Информация о приеме</Typography.Title>
<p>
<b>Пациент:</b>{" "}
{appointmentViewModalUI.selectedAppointment.patient ? `${appointmentViewModalUI.selectedAppointment.patient.last_name} ${appointmentViewModalUI.selectedAppointment.patient.first_name}` : "Не указан"}
</p>
<p>
<b>Дата рождения:</b>{" "}
{appointmentViewModalUI.selectedAppointment.patient ? appointmentViewModalUI.getDateString(appointmentViewModalUI.selectedAppointment.patient.birthday) : "Не указан"}
</p>
<p>
<b>Email:</b> {appointmentViewModalUI.selectedAppointment.patient?.email || "Не указан"}
</p>
<p>
<b>Телефон:</b> {appointmentViewModalUI.selectedAppointment.patient?.phone || "Не указан"}
</p>
<p>
<b>Тип приема:</b> {appointmentViewModalUI.selectedAppointment.type?.title || "Не указан"}
</p>
<p>
<b>Время приема:</b>{" "}
{appointmentViewModalUI.selectedAppointment.appointment_datetime
? dayjs(appointmentViewModalUI.selectedAppointment.appointment_datetime).format("DD.MM.YYYY HH:mm")
: "Не указано"}
</p>
<p>
<b>Дней до следующего приема:</b>{" "}
{appointmentViewModalUI.selectedAppointment.days_until_the_next_appointment || "Не указано"}
</p>
<p>
<b>Результаты приема:</b>
</p>
<div dangerouslySetInnerHTML={{__html: appointmentViewModalUI.selectedAppointment.results || "Не указаны"}}/>
</div>
<Row justify="end" style={appointmentViewModalUI.footerRowStyle}>
<Button style={appointmentViewModalUI.footerButtonStyle} onClick={onCancel}>
Закрыть
</Button>
</Row>
</Modal>
</>
);
};
AppointmentViewModal.propTypes = {
visible: PropTypes.bool.isRequired,
onCancel: PropTypes.func.isRequired,
};
export default AppointmentViewModal;

View File

@ -0,0 +1,29 @@
import {useDispatch, useSelector} from "react-redux";
const useAppointmentViewUI = () => {
const dispatch = useDispatch();
const {
selectedAppointment,
} = useSelector(state => state.appointmentsUI);
const modalWidth = 700;
const blockStyle = {marginBottom: 16};
const footerRowStyle = {marginTop: 16};
const footerButtonStyle = {marginRight: 8};
const getDateString = (date) => {
return new Date(date).toLocaleDateString('ru-RU');
};
return {
modalWidth,
blockStyle,
footerRowStyle,
footerButtonStyle,
selectedAppointment,
getDateString,
};
};
export default useAppointmentViewUI;

View File

@ -1,6 +1,11 @@
import { useDispatch, useSelector } from "react-redux";
import { Grid } from "antd";
import { setHovered, toggleSider } from "../../../Redux/Slices/appointmentsSlice.js";
import {
setHovered,
setSelectedAppointment,
setSelectedScheduledAppointment,
toggleSider
} from "../../../Redux/Slices/appointmentsSlice.js";
import { useEffect, useMemo } from "react";
import dayjs from "dayjs";
@ -12,6 +17,7 @@ const useAppointmentsUI = (appointments, scheduledAppointments) => {
collapsed,
siderWidth,
hovered,
selectedAppointment,
} = useSelector(state => state.appointmentsUI);
const screens = useBreakpoint();
@ -45,6 +51,14 @@ const useAppointmentsUI = (appointments, scheduledAppointments) => {
const handleHoverSider = () => dispatch(setHovered(true));
const handleLeaveSider = () => dispatch(setHovered(false));
const handleCancelViewModal = () => {
if (selectedAppointment) {
dispatch(setSelectedAppointment(null));
} else {
dispatch(setSelectedScheduledAppointment(null));
}
};
const openCreateScheduledAppointmentModal = () => {
// Логика для запланированных приемов будет добавлена позже
console.log('Открыть модальное окно для запланированного приема');
@ -71,6 +85,8 @@ const useAppointmentsUI = (appointments, scheduledAppointments) => {
siderButtonContainerStyle,
siderButtonStyle,
upcomingEvents,
selectedAppointment,
handleCancelViewModal,
handleToggleSider,
handleHoverSider,
handleLeaveSider,

View File

@ -1,12 +1,10 @@
import { createSlice } from "@reduxjs/toolkit";
import dayjs from "dayjs";
const initialState = {
collapsed: true,
siderWidth: 250,
hovered: false,
modalVisible: false,
selectedAppointments: [],
selectedAppointment: null,
scheduledAppointments: [],
selectedScheduledAppointment: null,
@ -34,9 +32,6 @@ const appointmentsSlice = createSlice({
closeModal: (state) => {
state.modalVisible = false;
},
setSelectedAppointments: (state, action) => {
state.selectedAppointments = action.payload;
},
setSelectedAppointment: (state, action) => {
state.selectedAppointment = action.payload;
},