сделал просмотр результатов прошлых приемов при создании нового
This commit is contained in:
parent
b541bef7aa
commit
a9c5f87104
@ -40,7 +40,7 @@ async def get_all_appointments_by_doctor_id(
|
|||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
"/patient/{patient_id}/",
|
"/patient/{patient_id}/",
|
||||||
response_model=AppointmentEntity,
|
response_model=list[AppointmentEntity],
|
||||||
summary="Get all appointments for patient",
|
summary="Get all appointments for patient",
|
||||||
description="Returns a list of appointments for patient",
|
description="Returns a list of appointments for patient",
|
||||||
)
|
)
|
||||||
|
|||||||
@ -18,6 +18,11 @@ export const appointmentsApi = createApi({
|
|||||||
providesTags: ['Appointment'],
|
providesTags: ['Appointment'],
|
||||||
refetchOnMountOrArgChange: 5,
|
refetchOnMountOrArgChange: 5,
|
||||||
}),
|
}),
|
||||||
|
getByPatientId: builder.query({
|
||||||
|
query: (id) => `/appointments/patient/${id}/`,
|
||||||
|
providesTags: ['Appointment'],
|
||||||
|
refetchOnMountOrArgChange: 5,
|
||||||
|
}),
|
||||||
createAppointment: builder.mutation({
|
createAppointment: builder.mutation({
|
||||||
query: (data) => ({
|
query: (data) => ({
|
||||||
url: '/appointments/',
|
url: '/appointments/',
|
||||||
@ -39,6 +44,7 @@ export const appointmentsApi = createApi({
|
|||||||
|
|
||||||
export const {
|
export const {
|
||||||
useGetAppointmentsQuery,
|
useGetAppointmentsQuery,
|
||||||
|
useGetByPatientIdQuery,
|
||||||
useCreateAppointmentMutation,
|
useCreateAppointmentMutation,
|
||||||
useUpdateAppointmentMutation,
|
useUpdateAppointmentMutation,
|
||||||
} = appointmentsApi;
|
} = appointmentsApi;
|
||||||
@ -1,18 +1,29 @@
|
|||||||
import { Button, FloatButton, Result, Tabs, Typography } from "antd";
|
import {Button, FloatButton, List, Result, Space, Typography} from "antd";
|
||||||
import { Splitter } from "antd";
|
import {Splitter} from "antd";
|
||||||
import { CalendarOutlined, TableOutlined, MenuFoldOutlined, MenuUnfoldOutlined, PlusOutlined } from "@ant-design/icons";
|
import {
|
||||||
|
CalendarOutlined,
|
||||||
|
MenuFoldOutlined,
|
||||||
|
MenuUnfoldOutlined,
|
||||||
|
PlusOutlined,
|
||||||
|
ClockCircleOutlined
|
||||||
|
} from "@ant-design/icons";
|
||||||
import AppointmentsCalendarTab from "./Components/AppointmentCalendarTab/AppointmentsCalendarTab.jsx";
|
import AppointmentsCalendarTab from "./Components/AppointmentCalendarTab/AppointmentsCalendarTab.jsx";
|
||||||
import AppointmentsTableTab from "./Components/AppointmentTableTab/AppointmentsTableTab.jsx";
|
|
||||||
import useAppointmentsUI from "./useAppointmentsUI.js";
|
import useAppointmentsUI from "./useAppointmentsUI.js";
|
||||||
import useAppointments from "./useAppointments.js";
|
import useAppointments from "./useAppointments.js";
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import LoadingIndicator from "../../Widgets/LoadingIndicator.jsx";
|
import LoadingIndicator from "../../Widgets/LoadingIndicator.jsx";
|
||||||
import AppointmentFormModal from "./Components/AppointmentFormModal/AppointmentFormModal.jsx";
|
import AppointmentFormModal from "./Components/AppointmentFormModal/AppointmentFormModal.jsx";
|
||||||
import { useDispatch } from "react-redux";
|
import {useDispatch} from "react-redux";
|
||||||
import { closeModal, openModal } from "../../../Redux/Slices/appointmentsSlice.js";
|
import {
|
||||||
|
closeModal,
|
||||||
|
openModal,
|
||||||
|
setSelectedAppointment,
|
||||||
|
setSelectedScheduledAppointment
|
||||||
|
} from "../../../Redux/Slices/appointmentsSlice.js";
|
||||||
import AppointmentViewModal from "./Components/AppointmentViewModal/AppointmentViewModal.jsx";
|
import AppointmentViewModal from "./Components/AppointmentViewModal/AppointmentViewModal.jsx";
|
||||||
import ScheduledAppointmentFormModal from "./Components/ScheduledAppintmentFormModal/ScheduledAppointmentFormModal.jsx";
|
import ScheduledAppointmentFormModal from "./Components/ScheduledAppintmentFormModal/ScheduledAppointmentFormModal.jsx";
|
||||||
import ScheduledAppointmentsViewModal from "./Components/ScheduledAppointmentsViewModal/ScheduledAppointmentsViewModal.jsx";
|
import ScheduledAppointmentsViewModal
|
||||||
|
from "./Components/ScheduledAppointmentsViewModal/ScheduledAppointmentsViewModal.jsx";
|
||||||
import AppointmentsListModal from "./Components/AppointmentsListModal/AppointmentsListModal.jsx";
|
import AppointmentsListModal from "./Components/AppointmentsListModal/AppointmentsListModal.jsx";
|
||||||
|
|
||||||
const AppointmentsPage = () => {
|
const AppointmentsPage = () => {
|
||||||
@ -24,20 +35,13 @@ const AppointmentsPage = () => {
|
|||||||
dispatch(closeModal());
|
dispatch(closeModal());
|
||||||
};
|
};
|
||||||
|
|
||||||
const items = [
|
const handleEventClick = (event) => {
|
||||||
{
|
if (event.appointment_datetime) {
|
||||||
key: "1",
|
dispatch(setSelectedAppointment(event));
|
||||||
label: "Календарь приемов",
|
} else {
|
||||||
children: <AppointmentsCalendarTab />,
|
dispatch(setSelectedScheduledAppointment(event));
|
||||||
icon: <CalendarOutlined />,
|
}
|
||||||
},
|
};
|
||||||
{
|
|
||||||
key: "2",
|
|
||||||
label: "Таблица приемов",
|
|
||||||
children: <AppointmentsTableTab />,
|
|
||||||
icon: <TableOutlined />,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
if (appointmentsData.isError) return (
|
if (appointmentsData.isError) return (
|
||||||
<Result
|
<Result
|
||||||
@ -50,7 +54,7 @@ const AppointmentsPage = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{appointmentsData.isLoading ? (
|
{appointmentsData.isLoading ? (
|
||||||
<LoadingIndicator />
|
<LoadingIndicator/>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Splitter
|
<Splitter
|
||||||
@ -66,7 +70,7 @@ const AppointmentsPage = () => {
|
|||||||
min="25%"
|
min="25%"
|
||||||
max="90%"
|
max="90%"
|
||||||
>
|
>
|
||||||
<Tabs defaultActiveKey="1" items={items} />
|
<AppointmentsCalendarTab/>
|
||||||
</Splitter.Panel>
|
</Splitter.Panel>
|
||||||
|
|
||||||
{appointmentsPageUI.showSplitterPanel && (
|
{appointmentsPageUI.showSplitterPanel && (
|
||||||
@ -80,17 +84,53 @@ const AppointmentsPage = () => {
|
|||||||
Предстоящие события
|
Предстоящие события
|
||||||
</Typography.Title>
|
</Typography.Title>
|
||||||
{appointmentsPageUI.upcomingEvents.length ? (
|
{appointmentsPageUI.upcomingEvents.length ? (
|
||||||
<ul>
|
<List
|
||||||
{appointmentsPageUI.upcomingEvents.map((app) => (
|
dataSource={appointmentsPageUI.upcomingEvents.sort((a, b) =>
|
||||||
<li key={app.id}>
|
dayjs(a.appointment_datetime || a.scheduled_datetime).diff(
|
||||||
{dayjs(app.appointment_datetime || app.scheduled_datetime)
|
dayjs(b.appointment_datetime || b.scheduled_datetime)
|
||||||
.format('DD.MM.YYYY HH:mm')} -
|
)
|
||||||
{app.appointment_datetime ? 'Прием' : 'Запланировано'}
|
)}
|
||||||
</li>
|
renderItem={(item) => (
|
||||||
))}
|
<List.Item
|
||||||
</ul>
|
onClick={() => handleEventClick(item)}
|
||||||
|
style={{
|
||||||
|
padding: "12px",
|
||||||
|
marginBottom: "8px",
|
||||||
|
borderRadius: "4px",
|
||||||
|
background: item.appointment_datetime ? "#e6f7ff" : "#f6ffed",
|
||||||
|
cursor: "pointer",
|
||||||
|
transition: "background 0.3s",
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => (e.currentTarget.style.background = item.appointment_datetime ? "#d9efff" : "#efffdb")}
|
||||||
|
onMouseLeave={(e) => (e.currentTarget.style.background = item.appointment_datetime ? "#e6f7ff" : "#f6ffed")}
|
||||||
|
>
|
||||||
|
<Space direction="vertical" size={2}>
|
||||||
|
<Space>
|
||||||
|
{item.appointment_datetime ? (
|
||||||
|
<ClockCircleOutlined style={{color: "#1890ff"}}/>
|
||||||
|
) : (
|
||||||
|
<CalendarOutlined style={{color: "#52c41a"}}/>
|
||||||
|
)}
|
||||||
|
<Typography.Text strong>
|
||||||
|
{dayjs(item.appointment_datetime || item.scheduled_datetime).format('DD.MM.YYYY HH:mm')}
|
||||||
|
</Typography.Text>
|
||||||
|
</Space>
|
||||||
|
<Typography.Text>
|
||||||
|
{item.appointment_datetime ? 'Прием' : 'Запланировано'}
|
||||||
|
{item.patient ? ` - ${item.patient.last_name} ${item.patient.first_name}` : ''}
|
||||||
|
</Typography.Text>
|
||||||
|
<Typography.Text type="secondary">
|
||||||
|
Тип: {item.type?.title || 'Не указан'}
|
||||||
|
</Typography.Text>
|
||||||
|
{dayjs(item.appointment_datetime || item.scheduled_datetime).isSame(dayjs(), 'day') && (
|
||||||
|
<Typography.Text type="warning">Сегодня</Typography.Text>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
</List.Item>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<p>Нет предстоящих событий</p>
|
<Typography.Text type="secondary">Нет предстоящих событий</Typography.Text>
|
||||||
)}
|
)}
|
||||||
</Splitter.Panel>
|
</Splitter.Panel>
|
||||||
)}
|
)}
|
||||||
@ -103,7 +143,7 @@ const AppointmentsPage = () => {
|
|||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={appointmentsPageUI.handleToggleSider}
|
onClick={appointmentsPageUI.handleToggleSider}
|
||||||
icon={appointmentsPageUI.collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
icon={appointmentsPageUI.collapsed ? <MenuUnfoldOutlined/> : <MenuFoldOutlined/>}
|
||||||
style={appointmentsPageUI.siderButtonStyle}
|
style={appointmentsPageUI.siderButtonStyle}
|
||||||
>
|
>
|
||||||
{appointmentsPageUI.siderButtonText}
|
{appointmentsPageUI.siderButtonText}
|
||||||
@ -113,29 +153,29 @@ const AppointmentsPage = () => {
|
|||||||
placement={"left"}
|
placement={"left"}
|
||||||
trigger="hover"
|
trigger="hover"
|
||||||
type="primary"
|
type="primary"
|
||||||
icon={<PlusOutlined />}
|
icon={<PlusOutlined/>}
|
||||||
tooltip="Создать"
|
tooltip="Создать"
|
||||||
>
|
>
|
||||||
<FloatButton
|
<FloatButton
|
||||||
icon={<PlusOutlined />}
|
icon={<PlusOutlined/>}
|
||||||
onClick={() => dispatch(openModal())}
|
onClick={() => dispatch(openModal())}
|
||||||
tooltip="Прием"
|
tooltip="Прием"
|
||||||
/>
|
/>
|
||||||
<FloatButton
|
<FloatButton
|
||||||
icon={<CalendarOutlined />}
|
icon={<CalendarOutlined/>}
|
||||||
onClick={appointmentsPageUI.openCreateScheduledAppointmentModal}
|
onClick={appointmentsPageUI.openCreateScheduledAppointmentModal}
|
||||||
tooltip="Запланированный прием"
|
tooltip="Запланированный прием"
|
||||||
/>
|
/>
|
||||||
</FloatButton.Group>
|
</FloatButton.Group>
|
||||||
|
|
||||||
<AppointmentFormModal onCancel={handleCancelModal} />
|
<AppointmentFormModal onCancel={handleCancelModal}/>
|
||||||
<AppointmentViewModal
|
<AppointmentViewModal
|
||||||
visible={appointmentsPageUI.selectedAppointment !== null}
|
visible={appointmentsPageUI.selectedAppointment !== null}
|
||||||
onCancel={appointmentsPageUI.handleCancelViewModal}
|
onCancel={appointmentsPageUI.handleCancelViewModal}
|
||||||
/>
|
/>
|
||||||
<ScheduledAppointmentFormModal />
|
<ScheduledAppointmentFormModal/>
|
||||||
<ScheduledAppointmentsViewModal />
|
<ScheduledAppointmentsViewModal/>
|
||||||
<AppointmentsListModal />
|
<AppointmentsListModal/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -4,6 +4,7 @@ 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";
|
||||||
import AppointmentsListModal from "../AppointmentsListModal/AppointmentsListModal.jsx";
|
import AppointmentsListModal from "../AppointmentsListModal/AppointmentsListModal.jsx";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
const AppointmentsCalendarTab = () => {
|
const AppointmentsCalendarTab = () => {
|
||||||
const appointmentsData = useAppointments();
|
const appointmentsData = useAppointments();
|
||||||
@ -23,10 +24,15 @@ const AppointmentsCalendarTab = () => {
|
|||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const allAppointments = [...appointmentsForDate, ...scheduledForDate].sort((a, b) => {
|
||||||
|
const timeA = a.appointment_datetime || a.scheduled_datetime;
|
||||||
|
const timeB = b.appointment_datetime || b.scheduled_datetime;
|
||||||
|
return dayjs(timeA).diff(dayjs(timeB));
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CalendarCell
|
<CalendarCell
|
||||||
appointments={appointmentsForDate}
|
allAppointments={allAppointments}
|
||||||
scheduledAppointments={scheduledForDate}
|
|
||||||
onCellClick={() => appointmentsCalendarUI.onSelect(value)}
|
onCellClick={() => appointmentsCalendarUI.onSelect(value)}
|
||||||
onItemClick={appointmentsCalendarUI.onOpenAppointmentModal}
|
onItemClick={appointmentsCalendarUI.onOpenAppointmentModal}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import JoditEditor from 'jodit-react';
|
import JoditEditor from "jodit-react";
|
||||||
import {useRef} from 'react';
|
import { useRef } from "react";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@ -14,36 +14,47 @@ import {
|
|||||||
Select,
|
Select,
|
||||||
Spin,
|
Spin,
|
||||||
Steps,
|
Steps,
|
||||||
Typography
|
Typography,
|
||||||
|
Drawer,
|
||||||
} from "antd";
|
} from "antd";
|
||||||
import useAppointmentFormModal from "./useAppointmentFormModal.js";
|
import useAppointmentFormModal from "./useAppointmentFormModal.js";
|
||||||
import useAppointmentFormModalUI from "./useAppointmentFormModalUI.js";
|
import useAppointmentFormModalUI from "./useAppointmentFormModalUI.js";
|
||||||
import LoadingIndicator from "../../../../Widgets/LoadingIndicator.jsx";
|
import LoadingIndicator from "../../../../Widgets/LoadingIndicator.jsx";
|
||||||
import {useMemo} from "react";
|
import { useMemo } from "react";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
const AppointmentFormModal = ({onCancel}) => {
|
const AppointmentFormModal = ({ onCancel }) => {
|
||||||
const appointmentFormModalData = useAppointmentFormModal();
|
const appointmentFormModalData = useAppointmentFormModal();
|
||||||
const appointmentFormModalUI = useAppointmentFormModalUI(
|
const appointmentFormModalUI = useAppointmentFormModalUI(
|
||||||
onCancel,
|
onCancel,
|
||||||
appointmentFormModalData.createAppointment,
|
appointmentFormModalData.createAppointment,
|
||||||
appointmentFormModalData.patients,
|
appointmentFormModalData.patients,
|
||||||
appointmentFormModalData.cancelAppointment,
|
appointmentFormModalData.cancelAppointment,
|
||||||
|
appointmentFormModalData.useGetByPatientIdQuery
|
||||||
);
|
);
|
||||||
|
|
||||||
const editor = useRef(null);
|
const editor = useRef(null);
|
||||||
|
|
||||||
|
|
||||||
const patientsItems = appointmentFormModalUI.filteredPatients.map((patient) => ({
|
const patientsItems = appointmentFormModalUI.filteredPatients.map((patient) => ({
|
||||||
key: patient.id,
|
key: patient.id,
|
||||||
label: `${patient.last_name} ${patient.first_name} (${appointmentFormModalUI.getDateString(patient.birthday)})`,
|
label: `${patient.last_name} ${patient.first_name} (${appointmentFormModalUI.getDateString(patient.birthday)})`,
|
||||||
children: (
|
children: (
|
||||||
<div>
|
<div>
|
||||||
<p><b>Пациент:</b> {patient.last_name} {patient.first_name}</p>
|
<p>
|
||||||
<p><b>Дата рождения:</b> {appointmentFormModalUI.getDateString(patient.birthday)}</p>
|
<b>Пациент:</b> {patient.last_name} {patient.first_name}
|
||||||
<p><b>Диагноз:</b> {patient.diagnosis || 'Не указан'}</p>
|
</p>
|
||||||
<p><b>Email:</b> {patient.email || 'Не указан'}</p>
|
<p>
|
||||||
<p><b>Телефон:</b> {patient.phone || 'Не указан'}</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 type="primary" onClick={() => appointmentFormModalUI.setSelectedPatient(patient)}>
|
||||||
Выбрать
|
Выбрать
|
||||||
</Button>
|
</Button>
|
||||||
@ -57,14 +68,16 @@ const AppointmentFormModal = ({onCancel}) => {
|
|||||||
<Typography.Text strong>
|
<Typography.Text strong>
|
||||||
{appointmentFormModalUI.selectedPatient.last_name} {appointmentFormModalUI.selectedPatient.first_name}
|
{appointmentFormModalUI.selectedPatient.last_name} {appointmentFormModalUI.selectedPatient.first_name}
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
<p><b>Дата рождения:</b> {appointmentFormModalUI.getSelectedPatientBirthdayString()}</p>
|
<p>
|
||||||
<p><b>Email:</b> {appointmentFormModalUI.selectedPatient.email || 'Не указан'}</p>
|
<b>Дата рождения:</b> {appointmentFormModalUI.getSelectedPatientBirthdayString()}
|
||||||
<p><b>Телефон:</b> {appointmentFormModalUI.selectedPatient.phone || 'Не указан'}</p>
|
</p>
|
||||||
<Button
|
<p>
|
||||||
type="primary"
|
<b>Email:</b> {appointmentFormModalUI.selectedPatient.email || "Не указан"}
|
||||||
onClick={appointmentFormModalUI.resetPatient}
|
</p>
|
||||||
danger
|
<p>
|
||||||
>
|
<b>Телефон:</b> {appointmentFormModalUI.selectedPatient.phone || "Не указан"}
|
||||||
|
</p>
|
||||||
|
<Button type="primary" onClick={appointmentFormModalUI.resetPatient} danger>
|
||||||
Выбрать другого пациента
|
Выбрать другого пациента
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@ -78,7 +91,7 @@ const AppointmentFormModal = ({onCancel}) => {
|
|||||||
allowClear
|
allowClear
|
||||||
/>
|
/>
|
||||||
<div style={appointmentFormModalUI.chooseContainerStyle}>
|
<div style={appointmentFormModalUI.chooseContainerStyle}>
|
||||||
<Collapse items={patientsItems}/>
|
<Collapse items={patientsItems} />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@ -86,95 +99,107 @@ const AppointmentFormModal = ({onCancel}) => {
|
|||||||
|
|
||||||
const AppointmentStep = useMemo(() => {
|
const AppointmentStep = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
<Form
|
<div>
|
||||||
form={appointmentFormModalUI.form}
|
<Button
|
||||||
onFinish={appointmentFormModalUI.handleOk}
|
type="primary"
|
||||||
initialValues={{
|
onClick={appointmentFormModalUI.showDrawer}
|
||||||
patient_id: appointmentFormModalUI.selectedPatient?.id,
|
style={{ marginBottom: 16 }}
|
||||||
}}
|
disabled={!appointmentFormModalUI.selectedPatient}
|
||||||
layout="vertical"
|
|
||||||
>
|
|
||||||
<Form.Item
|
|
||||||
name="type_id"
|
|
||||||
label="Тип приема"
|
|
||||||
rules={[{required: true, message: 'Выберите тип приема'}]}
|
|
||||||
>
|
>
|
||||||
<Select placeholder="Выберите тип приема">
|
Показать прошлые приемы
|
||||||
{appointmentFormModalData.appointmentTypes.map(type => (
|
</Button>
|
||||||
<Select.Option key={type.id} value={type.id}>
|
<Form
|
||||||
{type.title}
|
form={appointmentFormModalUI.form}
|
||||||
</Select.Option>
|
onFinish={appointmentFormModalUI.handleOk}
|
||||||
))}
|
initialValues={{
|
||||||
</Select>
|
patient_id: appointmentFormModalUI.selectedPatient?.id,
|
||||||
</Form.Item>
|
}}
|
||||||
<Form.Item
|
layout="vertical"
|
||||||
name="appointment_datetime"
|
|
||||||
label="Время приема"
|
|
||||||
rules={[{required: true, message: 'Выберите время'}]}
|
|
||||||
>
|
>
|
||||||
<DatePicker maxDate={dayjs(new Date()).add(1, 'day')} showTime format="DD.MM.YYYY HH:mm"
|
<Form.Item name="type_id" label="Тип приема" rules={[{ required: true, message: "Выберите тип приема" }]}>
|
||||||
style={{width: '100%'}}/>
|
<Select placeholder="Выберите тип приема">
|
||||||
</Form.Item>
|
{appointmentFormModalData.appointmentTypes.map((type) => (
|
||||||
<Form.Item
|
<Select.Option key={type.id} value={type.id}>
|
||||||
name="days_until_the_next_appointment"
|
{type.title}
|
||||||
label="Дней до следующего приема"
|
</Select.Option>
|
||||||
rules={[{type: 'number', min: 0, message: 'Введите неотрицательное число'}]}
|
))}
|
||||||
>
|
</Select>
|
||||||
<InputNumber min={0} style={{width: '100%'}}/>
|
</Form.Item>
|
||||||
</Form.Item>
|
<Form.Item
|
||||||
<Form.Item
|
name="appointment_datetime"
|
||||||
name="results"
|
label="Время приема"
|
||||||
label="Результаты приема"
|
rules={[{ required: true, message: "Выберите время" }]}
|
||||||
>
|
>
|
||||||
<JoditEditor
|
<DatePicker
|
||||||
ref={editor}
|
maxDate={dayjs(new Date()).add(1, "day")}
|
||||||
value={appointmentFormModalUI.results}
|
showTime
|
||||||
config={{
|
format="DD.MM.YYYY HH:mm"
|
||||||
readonly: false,
|
style={{ width: "100%" }}
|
||||||
height: 150,
|
/>
|
||||||
}}
|
</Form.Item>
|
||||||
onBlur={appointmentFormModalUI.handleResultsChange}
|
<Form.Item
|
||||||
/>
|
name="days_until_the_next_appointment"
|
||||||
</Form.Item>
|
label="Дней до следующего приема"
|
||||||
|
rules={[{ type: "number", min: 0, message: "Введите неотрицательное число" }]}
|
||||||
</Form>
|
>
|
||||||
|
<InputNumber min={0} style={{ width: "100%" }} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name="results" label="Результаты приема">
|
||||||
|
<JoditEditor
|
||||||
|
ref={editor}
|
||||||
|
value={appointmentFormModalUI.results}
|
||||||
|
config={{
|
||||||
|
readonly: false,
|
||||||
|
height: 150,
|
||||||
|
}}
|
||||||
|
onBlur={appointmentFormModalUI.handleResultsChange}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}, [
|
}, [appointmentFormModalData, appointmentFormModalUI]);
|
||||||
appointmentFormModalData,
|
|
||||||
appointmentFormModalUI,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const ConfirmStep = useMemo(() => {
|
const ConfirmStep = useMemo(() => {
|
||||||
const values = appointmentFormModalUI.form.getFieldsValue();
|
const values = appointmentFormModalUI.form.getFieldsValue();
|
||||||
const patient = appointmentFormModalData.patients.find(p => p.id === values.patient_id);
|
const patient = appointmentFormModalData.patients.find((p) => p.id === values.patient_id);
|
||||||
const appointmentType = appointmentFormModalData.appointmentTypes.find(t => t.id === values.type_id);
|
const appointmentType = appointmentFormModalData.appointmentTypes.find((t) => t.id === values.type_id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={appointmentFormModalUI.blockStepStyle}>
|
<div style={appointmentFormModalUI.blockStepStyle}>
|
||||||
<Typography.Title level={4}>Подтверждение</Typography.Title>
|
<Typography.Title level={4}>Подтверждение</Typography.Title>
|
||||||
<p><b>Пациент:</b> {patient ? `${patient.last_name} ${patient.first_name}` : 'Не выбран'}</p>
|
<p>
|
||||||
<p><b>Тип приема:</b> {appointmentType ? appointmentType.name : 'Не выбран'}</p>
|
<b>Пациент:</b> {patient ? `${patient.last_name} ${patient.first_name}` : "Не выбран"}
|
||||||
<p><b>Время
|
|
||||||
приема:</b> {values.appointment_datetime ? dayjs(values.appointment_datetime).format('DD.MM.YYYY HH:mm') : 'Не указано'}
|
|
||||||
</p>
|
</p>
|
||||||
<p><b>Дней до следующего приема:</b> {values.days_until_the_next_appointment || 'Не указано'}</p>
|
<p>
|
||||||
<p><b>Результаты приема:</b></p>
|
<b>Тип приема:</b> {appointmentType ? appointmentType.title : "Не выбран"}
|
||||||
<div dangerouslySetInnerHTML={{__html: values.results || 'Не указаны'}}/>
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>Время приема:</b>{" "}
|
||||||
|
{values.appointment_datetime ? dayjs(values.appointment_datetime).format("DD.MM.YYYY HH:mm") : "Не указано"}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>Дней до следующего приема:</b> {values.days_until_the_next_appointment || "Не указано"}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>Результаты приема:</b>
|
||||||
|
</p>
|
||||||
|
<div dangerouslySetInnerHTML={{ __html: values.results || "Не указаны" }} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}, [appointmentFormModalUI, appointmentFormModalData]);
|
}, [appointmentFormModalUI, appointmentFormModalData]);
|
||||||
|
|
||||||
const steps = [
|
const steps = [
|
||||||
{
|
{
|
||||||
title: 'Выбор пациента',
|
title: "Выбор пациента",
|
||||||
content: SelectPatientStep,
|
content: SelectPatientStep,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Заполнение информации о приеме',
|
title: "Заполнение информации о приеме",
|
||||||
content: AppointmentStep,
|
content: AppointmentStep,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Подтверждение',
|
title: "Подтверждение",
|
||||||
content: ConfirmStep,
|
content: ConfirmStep,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -192,55 +217,73 @@ const AppointmentFormModal = ({onCancel}) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{appointmentFormModalData.isLoading ? (
|
{appointmentFormModalData.isLoading ? (
|
||||||
<LoadingIndicator/>
|
<LoadingIndicator />
|
||||||
) : (
|
) : (
|
||||||
<Modal
|
<>
|
||||||
title={"Создать прием"}
|
<Modal
|
||||||
open={appointmentFormModalUI.modalVisible}
|
title={"Создать прием"}
|
||||||
onCancel={appointmentFormModalUI.handleCancel}
|
open={appointmentFormModalUI.modalVisible}
|
||||||
footer={null}
|
onCancel={appointmentFormModalUI.handleCancel}
|
||||||
width={appointmentFormModalUI.modalWidth}
|
footer={null}
|
||||||
>
|
width={appointmentFormModalUI.modalWidth}
|
||||||
{appointmentFormModalData.isLoading ? (
|
|
||||||
<div style={appointmentFormModalUI.loadingContainerStyle}>
|
|
||||||
<Spin size="large"/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div style={appointmentFormModalUI.stepsContentStyle}>
|
|
||||||
{steps[appointmentFormModalUI.currentStep].content}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!appointmentFormModalUI.screenXS && (
|
|
||||||
<Steps
|
|
||||||
current={appointmentFormModalUI.currentStep}
|
|
||||||
items={steps}
|
|
||||||
style={appointmentFormModalUI.stepsIndicatorStyle}
|
|
||||||
direction={appointmentFormModalUI.direction}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Row
|
|
||||||
justify="end"
|
|
||||||
style={appointmentFormModalUI.footerRowStyle}
|
|
||||||
gutter={[8, 8]}
|
|
||||||
>
|
>
|
||||||
<Button
|
{appointmentFormModalData.isLoading ? (
|
||||||
style={appointmentFormModalUI.footerButtonStyle}
|
<div style={appointmentFormModalUI.loadingContainerStyle}>
|
||||||
onClick={appointmentFormModalUI.handleClickBackButton}
|
<Spin size="large" />
|
||||||
disabled={appointmentFormModalUI.disableBackButton}
|
</div>
|
||||||
>
|
) : (
|
||||||
Назад
|
<div style={appointmentFormModalUI.stepsContentStyle}>{steps[appointmentFormModalUI.currentStep].content}</div>
|
||||||
</Button>
|
)}
|
||||||
<Button
|
|
||||||
type="primary"
|
{!appointmentFormModalUI.screenXS && (
|
||||||
onClick={appointmentFormModalUI.handleClickNextButton}
|
<Steps
|
||||||
disabled={appointmentFormModalUI.disableNextButton}
|
current={appointmentFormModalUI.currentStep}
|
||||||
>
|
items={steps}
|
||||||
{appointmentFormModalUI.nextButtonText}
|
style={appointmentFormModalUI.stepsIndicatorStyle}
|
||||||
</Button>
|
direction={appointmentFormModalUI.direction}
|
||||||
</Row>
|
/>
|
||||||
</Modal>
|
)}
|
||||||
|
|
||||||
|
<Row justify="end" style={appointmentFormModalUI.footerRowStyle} gutter={[8, 8]}>
|
||||||
|
<Button
|
||||||
|
style={appointmentFormModalUI.footerButtonStyle}
|
||||||
|
onClick={appointmentFormModalUI.handleClickBackButton}
|
||||||
|
disabled={appointmentFormModalUI.disableBackButton}
|
||||||
|
>
|
||||||
|
Назад
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={appointmentFormModalUI.handleClickNextButton}
|
||||||
|
disabled={appointmentFormModalUI.disableNextButton}
|
||||||
|
>
|
||||||
|
{appointmentFormModalUI.nextButtonText}
|
||||||
|
</Button>
|
||||||
|
</Row>
|
||||||
|
</Modal>
|
||||||
|
<Drawer
|
||||||
|
title="Прошлые приемы"
|
||||||
|
placement="right"
|
||||||
|
onClose={appointmentFormModalUI.closeDrawer}
|
||||||
|
open={appointmentFormModalUI.isDrawerVisible}
|
||||||
|
width={400}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
placeholder="Поиск по результатам приема"
|
||||||
|
value={appointmentFormModalUI.searchPreviousAppointments}
|
||||||
|
onChange={appointmentFormModalUI.handleSetSearchPreviousAppointments}
|
||||||
|
style={{ marginBottom: 16 }}
|
||||||
|
allowClear
|
||||||
|
/>
|
||||||
|
<Collapse
|
||||||
|
items={appointmentFormModalUI.filteredPreviousAppointments.map((appointment) => ({
|
||||||
|
key: appointment.id,
|
||||||
|
label: `Прием ${dayjs(appointment.appointment_datetime).format("DD.MM.YYYY HH:mm")}`,
|
||||||
|
children: <div dangerouslySetInnerHTML={{ __html: appointment.results || "Результаты не указаны" }} />,
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
</Drawer>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,7 +1,10 @@
|
|||||||
import {useGetPatientsQuery} from "../../../../../Api/patientsApi.js";
|
import { useGetPatientsQuery } from "../../../../../Api/patientsApi.js";
|
||||||
import {useGetAppointmentTypesQuery} from "../../../../../Api/appointmentTypesApi.js";
|
import { useGetAppointmentTypesQuery } from "../../../../../Api/appointmentTypesApi.js";
|
||||||
import {useCreateAppointmentMutation, useUpdateAppointmentMutation} from "../../../../../Api/appointmentsApi.js";
|
import {
|
||||||
import {useCancelScheduledAppointmentMutation} from "../../../../../Api/scheduledAppointmentsApi.js";
|
useCreateAppointmentMutation,
|
||||||
|
useGetByPatientIdQuery,
|
||||||
|
} from "../../../../../Api/appointmentsApi.js";
|
||||||
|
import { useCancelScheduledAppointmentMutation } from "../../../../../Api/scheduledAppointmentsApi.js";
|
||||||
|
|
||||||
const useAppointmentFormModal = () => {
|
const useAppointmentFormModal = () => {
|
||||||
const {
|
const {
|
||||||
@ -19,7 +22,7 @@ const useAppointmentFormModal = () => {
|
|||||||
pollingInterval: 20000,
|
pollingInterval: 20000,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [createAppointment, {isLoading: isCreating, isError: isErrorCreating}] = useCreateAppointmentMutation();
|
const [createAppointment, { isLoading: isCreating, isError: isErrorCreating }] = useCreateAppointmentMutation();
|
||||||
const [cancelAppointment] = useCancelScheduledAppointmentMutation();
|
const [cancelAppointment] = useCancelScheduledAppointmentMutation();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -27,6 +30,7 @@ const useAppointmentFormModal = () => {
|
|||||||
appointmentTypes,
|
appointmentTypes,
|
||||||
createAppointment,
|
createAppointment,
|
||||||
cancelAppointment,
|
cancelAppointment,
|
||||||
|
useGetByPatientIdQuery,
|
||||||
isLoading: isLoadingPatients || isLoadingAppointmentTypes || isCreating,
|
isLoading: isLoadingPatients || isLoadingAppointmentTypes || isCreating,
|
||||||
isError: isErrorPatients || isErrorAppointmentTypes || isErrorCreating,
|
isError: isErrorPatients || isErrorAppointmentTypes || isErrorCreating,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,16 +1,16 @@
|
|||||||
import {Form, notification} from "antd";
|
import { Form, notification } from "antd";
|
||||||
import {useDispatch, useSelector} from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import {closeModal, setSelectedScheduledAppointment} from "../../../../../Redux/Slices/appointmentsSlice.js";
|
import { closeModal, setSelectedScheduledAppointment } from "../../../../../Redux/Slices/appointmentsSlice.js";
|
||||||
import {useEffect, useMemo, useState} from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import {useGetAppointmentsQuery} from "../../../../../Api/appointmentsApi.js";
|
import { useGetAppointmentsQuery } from "../../../../../Api/appointmentsApi.js";
|
||||||
import {Grid} from "antd";
|
import { Grid } from "antd";
|
||||||
|
|
||||||
const {useBreakpoint} = Grid;
|
const { useBreakpoint } = Grid;
|
||||||
|
|
||||||
const useAppointmentFormModalUI = (onCancel, createAppointment, patients, cancelAppointment) => {
|
const useAppointmentFormModalUI = (onCancel, createAppointment, patients, cancelAppointment, useGetByPatientIdQuery) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const {modalVisible, scheduledData} = useSelector(state => state.appointmentsUI);
|
const { modalVisible, scheduledData } = useSelector((state) => state.appointmentsUI);
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const screens = useBreakpoint();
|
const screens = useBreakpoint();
|
||||||
|
|
||||||
@ -19,31 +19,54 @@ const useAppointmentFormModalUI = (onCancel, createAppointment, patients, cancel
|
|||||||
const [appointmentDate, setAppointmentDate] = useState(dayjs(new Date()));
|
const [appointmentDate, setAppointmentDate] = useState(dayjs(new Date()));
|
||||||
const [searchPatientString, setSearchPatientString] = useState("");
|
const [searchPatientString, setSearchPatientString] = useState("");
|
||||||
const [formValues, setFormValues] = useState({});
|
const [formValues, setFormValues] = useState({});
|
||||||
const [results, setResults] = useState('');
|
const [results, setResults] = useState("");
|
||||||
|
const [isDrawerVisible, setIsDrawerVisible] = useState(false);
|
||||||
|
const [searchPreviousAppointments, setSearchPreviousAppointments] = useState("");
|
||||||
|
|
||||||
const {data: appointments = []} = useGetAppointmentsQuery(undefined, {
|
const { data: appointments = [] } = useGetAppointmentsQuery(undefined, {
|
||||||
pollingInterval: 20000,
|
pollingInterval: 20000,
|
||||||
});
|
});
|
||||||
|
|
||||||
const blockStepStyle = {marginBottom: 16};
|
const {
|
||||||
const searchInputStyle = {marginBottom: 16};
|
data: previousAppointments = [],
|
||||||
const chooseContainerStyle = {maxHeight: 400, overflowY: "auto"};
|
isLoading: isLoadingPreviousAppointments,
|
||||||
const loadingContainerStyle = {display: "flex", justifyContent: "center", alignItems: "center", height: 200};
|
isError: isErrorPreviousAppointments,
|
||||||
const stepsContentStyle = {marginBottom: 16};
|
} = useGetByPatientIdQuery(selectedPatient?.id, {
|
||||||
const stepsIndicatorStyle = {marginBottom: 16};
|
pollingInterval: 20000,
|
||||||
const footerRowStyle = {marginTop: 16};
|
skip: !selectedPatient,
|
||||||
const footerButtonStyle = {marginRight: 8};
|
});
|
||||||
|
|
||||||
|
const blockStepStyle = { marginBottom: 16 };
|
||||||
|
const searchInputStyle = { marginBottom: 16 };
|
||||||
|
const chooseContainerStyle = { maxHeight: 400, overflowY: "auto" };
|
||||||
|
const loadingContainerStyle = { display: "flex", justifyContent: "center", alignItems: "center", height: 200 };
|
||||||
|
const stepsContentStyle = { marginBottom: 16 };
|
||||||
|
const stepsIndicatorStyle = { marginBottom: 16 };
|
||||||
|
const footerRowStyle = { marginTop: 16 };
|
||||||
|
const footerButtonStyle = { marginRight: 8 };
|
||||||
|
|
||||||
const screenXS = !screens.sm;
|
const screenXS = !screens.sm;
|
||||||
const direction = screenXS ? "vertical" : "horizontal";
|
const direction = screenXS ? "vertical" : "horizontal";
|
||||||
|
|
||||||
const filteredPatients = useMemo(() => patients.filter((patient) => {
|
const filteredPatients = useMemo(
|
||||||
const searchLower = searchPatientString.toLowerCase();
|
() =>
|
||||||
|
patients.filter((patient) => {
|
||||||
|
const searchLower = searchPatientString.toLowerCase();
|
||||||
|
return Object.values(patient)
|
||||||
|
.filter((value) => typeof value === "string")
|
||||||
|
.some((value) => value.toLowerCase().includes(searchLower));
|
||||||
|
}),
|
||||||
|
[patients, searchPatientString]
|
||||||
|
);
|
||||||
|
|
||||||
return Object.values(patient)
|
const filteredPreviousAppointments = useMemo(
|
||||||
.filter(value => typeof value === "string")
|
() =>
|
||||||
.some(value => value.toLowerCase().includes(searchLower));
|
previousAppointments.filter((appointment) => {
|
||||||
}), [patients, searchPatientString]);
|
const searchLower = searchPreviousAppointments.toLowerCase();
|
||||||
|
return appointment.results?.toLowerCase().includes(searchLower);
|
||||||
|
}),
|
||||||
|
[previousAppointments, searchPreviousAppointments]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (modalVisible) {
|
if (modalVisible) {
|
||||||
@ -52,9 +75,11 @@ const useAppointmentFormModalUI = (onCancel, createAppointment, patients, cancel
|
|||||||
setCurrentStep(0);
|
setCurrentStep(0);
|
||||||
setSearchPatientString("");
|
setSearchPatientString("");
|
||||||
setFormValues({});
|
setFormValues({});
|
||||||
|
setIsDrawerVisible(false);
|
||||||
|
setSearchPreviousAppointments("");
|
||||||
|
|
||||||
if (scheduledData) {
|
if (scheduledData) {
|
||||||
const patient = patients.find(p => p.id === scheduledData.patient_id);
|
const patient = patients.find((p) => p.id === scheduledData.patient_id);
|
||||||
if (patient) {
|
if (patient) {
|
||||||
setSelectedPatient(patient);
|
setSelectedPatient(patient);
|
||||||
setCurrentStep(1); // Skip to appointment details step
|
setCurrentStep(1); // Skip to appointment details step
|
||||||
@ -80,33 +105,46 @@ const useAppointmentFormModalUI = (onCancel, createAppointment, patients, cancel
|
|||||||
setSearchPatientString(e.target.value);
|
setSearchPatientString(e.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSetSearchPreviousAppointments = (e) => {
|
||||||
|
setSearchPreviousAppointments(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
const getDateString = (date) => {
|
const getDateString = (date) => {
|
||||||
return date ? dayjs(date).format('DD.MM.YYYY') : 'Не указано';
|
return date ? dayjs(date).format("DD.MM.YYYY") : "Не указано";
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSelectedPatientBirthdayString = () => {
|
const getSelectedPatientBirthdayString = () => {
|
||||||
return selectedPatient ? getDateString(selectedPatient.birthday) : 'Не выбран';
|
return selectedPatient ? getDateString(selectedPatient.birthday) : "Не выбран";
|
||||||
};
|
};
|
||||||
|
|
||||||
const resetPatient = () => {
|
const resetPatient = () => {
|
||||||
setSelectedPatient(null);
|
setSelectedPatient(null);
|
||||||
form.setFieldsValue({patient_id: undefined});
|
form.setFieldsValue({ patient_id: undefined });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSetAppointmentDate = (date) => setAppointmentDate(date);
|
const handleSetAppointmentDate = (date) => setAppointmentDate(date);
|
||||||
const modalWidth = useMemo(() => screenXS ? 700 : "90%", [screenXS]);
|
const modalWidth = useMemo(() => (screenXS ? 700 : "90%"), [screenXS]);
|
||||||
|
|
||||||
|
const showDrawer = () => {
|
||||||
|
setIsDrawerVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeDrawer = () => {
|
||||||
|
setIsDrawerVisible(false);
|
||||||
|
setSearchPreviousAppointments(""); // Reset search on close
|
||||||
|
};
|
||||||
|
|
||||||
const handleClickNextButton = async () => {
|
const handleClickNextButton = async () => {
|
||||||
if (currentStep === 0) {
|
if (currentStep === 0) {
|
||||||
if (!selectedPatient) {
|
if (!selectedPatient) {
|
||||||
notification.error({
|
notification.error({
|
||||||
message: 'Ошибка',
|
message: "Ошибка",
|
||||||
description: 'Пожалуйста, выберите пациента.',
|
description: "Пожалуйста, выберите пациента.",
|
||||||
placement: 'topRight',
|
placement: "topRight",
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
form.setFieldsValue({patient_id: selectedPatient.id});
|
form.setFieldsValue({ patient_id: selectedPatient.id });
|
||||||
setCurrentStep(1);
|
setCurrentStep(1);
|
||||||
} else if (currentStep === 1) {
|
} else if (currentStep === 1) {
|
||||||
try {
|
try {
|
||||||
@ -115,9 +153,9 @@ const useAppointmentFormModalUI = (onCancel, createAppointment, patients, cancel
|
|||||||
setCurrentStep(2);
|
setCurrentStep(2);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notification.error({
|
notification.error({
|
||||||
message: 'Ошибка валидации',
|
message: "Ошибка валидации",
|
||||||
description: error.message || 'Пожалуйста, заполните все обязательные поля.',
|
description: error.message || "Пожалуйста, заполните все обязательные поля.",
|
||||||
placement: 'topRight',
|
placement: "topRight",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (currentStep === 2) {
|
} else if (currentStep === 2) {
|
||||||
@ -136,15 +174,15 @@ const useAppointmentFormModalUI = (onCancel, createAppointment, patients, cancel
|
|||||||
const values = formValues;
|
const values = formValues;
|
||||||
|
|
||||||
const appointmentTime = values.appointment_datetime;
|
const appointmentTime = values.appointment_datetime;
|
||||||
const hasConflict = appointments.some(app =>
|
const hasConflict = appointments.some((app) =>
|
||||||
dayjs(app.appointment_datetime).isSame(appointmentTime, 'minute')
|
dayjs(app.appointment_datetime).isSame(appointmentTime, "minute")
|
||||||
);
|
);
|
||||||
|
|
||||||
if (hasConflict) {
|
if (hasConflict) {
|
||||||
notification.error({
|
notification.error({
|
||||||
message: 'Конфликт времени',
|
message: "Конфликт времени",
|
||||||
description: 'Выбранное время уже занято другим приемом.',
|
description: "Выбранное время уже занято другим приемом.",
|
||||||
placement: 'topRight',
|
placement: "topRight",
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -165,9 +203,9 @@ const useAppointmentFormModalUI = (onCancel, createAppointment, patients, cancel
|
|||||||
}
|
}
|
||||||
|
|
||||||
notification.success({
|
notification.success({
|
||||||
message: 'Прием создан',
|
message: "Прием создан",
|
||||||
description: 'Прием успешно создан.',
|
description: "Прием успешно создан.",
|
||||||
placement: 'topRight',
|
placement: "topRight",
|
||||||
});
|
});
|
||||||
|
|
||||||
dispatch(closeModal());
|
dispatch(closeModal());
|
||||||
@ -177,9 +215,9 @@ const useAppointmentFormModalUI = (onCancel, createAppointment, patients, cancel
|
|||||||
setFormValues({});
|
setFormValues({});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notification.error({
|
notification.error({
|
||||||
message: 'Ошибка',
|
message: "Ошибка",
|
||||||
description: error.data?.message || 'Не удалось сохранить прием.',
|
description: error.data?.message || "Не удалось сохранить прием.",
|
||||||
placement: 'topRight',
|
placement: "topRight",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -189,10 +227,10 @@ const useAppointmentFormModalUI = (onCancel, createAppointment, patients, cancel
|
|||||||
await cancelAppointment(selectedScheduledAppointmentId);
|
await cancelAppointment(selectedScheduledAppointmentId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notification.error({
|
notification.error({
|
||||||
message: 'Ошибка',
|
message: "Ошибка",
|
||||||
description: error.data?.message || 'Не удалось отменить прием.',
|
description: error.data?.message || "Не удалось отменить прием.",
|
||||||
placement: 'topRight',
|
placement: "topRight",
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -202,12 +240,13 @@ const useAppointmentFormModalUI = (onCancel, createAppointment, patients, cancel
|
|||||||
setCurrentStep(0);
|
setCurrentStep(0);
|
||||||
setSearchPatientString("");
|
setSearchPatientString("");
|
||||||
setFormValues({});
|
setFormValues({});
|
||||||
|
setIsDrawerVisible(false);
|
||||||
onCancel();
|
onCancel();
|
||||||
};
|
};
|
||||||
|
|
||||||
const disableBackButton = currentStep === 0;
|
const disableBackButton = currentStep === 0;
|
||||||
const disableNextButton = currentStep === 0 && !selectedPatient;
|
const disableNextButton = currentStep === 0 && !selectedPatient;
|
||||||
const nextButtonText = currentStep === 2 ? 'Создать' : 'Далее';
|
const nextButtonText = currentStep === 2 ? "Создать" : "Далее";
|
||||||
|
|
||||||
return {
|
return {
|
||||||
form,
|
form,
|
||||||
@ -221,6 +260,8 @@ const useAppointmentFormModalUI = (onCancel, createAppointment, patients, cancel
|
|||||||
setResults,
|
setResults,
|
||||||
handleSetSearchPatientString,
|
handleSetSearchPatientString,
|
||||||
filteredPatients,
|
filteredPatients,
|
||||||
|
filteredPreviousAppointments,
|
||||||
|
handleSetSearchPreviousAppointments,
|
||||||
handleOk,
|
handleOk,
|
||||||
handleCancel,
|
handleCancel,
|
||||||
resetPatient,
|
resetPatient,
|
||||||
@ -244,6 +285,11 @@ const useAppointmentFormModalUI = (onCancel, createAppointment, patients, cancel
|
|||||||
footerButtonStyle,
|
footerButtonStyle,
|
||||||
screenXS,
|
screenXS,
|
||||||
direction,
|
direction,
|
||||||
|
isDrawerVisible,
|
||||||
|
showDrawer,
|
||||||
|
closeDrawer,
|
||||||
|
isLoadingPreviousAppointments,
|
||||||
|
isErrorPreviousAppointments,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Button, List, Modal, Typography } from "antd";
|
import {Button, Card, List, Modal, Typography} from "antd";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import {useDispatch, useSelector} from "react-redux";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import {
|
import {
|
||||||
closeAppointmentsListModal,
|
closeAppointmentsListModal,
|
||||||
@ -9,7 +9,7 @@ import {
|
|||||||
|
|
||||||
const AppointmentsListModal = () => {
|
const AppointmentsListModal = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { appointmentsListModalVisible, selectedDateAppointments } = useSelector(
|
const {appointmentsListModalVisible, selectedDateAppointments} = useSelector(
|
||||||
(state) => state.appointmentsUI
|
(state) => state.appointmentsUI
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -39,10 +39,18 @@ const AppointmentsListModal = () => {
|
|||||||
dataSource={selectedDateAppointments}
|
dataSource={selectedDateAppointments}
|
||||||
renderItem={(item) => (
|
renderItem={(item) => (
|
||||||
<List.Item
|
<List.Item
|
||||||
onClick={() => handleItemClick(item)}
|
style={{cursor: "pointer", padding: "8px 0"}}
|
||||||
style={{ cursor: "pointer", padding: "8px 0" }}
|
|
||||||
>
|
>
|
||||||
<div>
|
<Card
|
||||||
|
hoverable
|
||||||
|
style={{width: "100%"}}
|
||||||
|
size="small"
|
||||||
|
actions={[
|
||||||
|
<Button type={"link"} key={"view"} onClick={() => handleItemClick(item)}>
|
||||||
|
Просмотр приема
|
||||||
|
</Button>
|
||||||
|
]}
|
||||||
|
>
|
||||||
<p>
|
<p>
|
||||||
<b>Время:</b>{" "}
|
<b>Время:</b>{" "}
|
||||||
{item.appointment_datetime
|
{item.appointment_datetime
|
||||||
@ -61,14 +69,14 @@ const AppointmentsListModal = () => {
|
|||||||
? `${item.patient.last_name} ${item.patient.first_name}`
|
? `${item.patient.last_name} ${item.patient.first_name}`
|
||||||
: "Не указан"}
|
: "Не указан"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</Card>
|
||||||
</List.Item>
|
</List.Item>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<p>Нет приемов на эту дату</p>
|
<p>Нет приемов на эту дату</p>
|
||||||
)}
|
)}
|
||||||
<Button onClick={handleCancel} style={{ marginTop: 16 }}>
|
<Button onClick={handleCancel} style={{marginTop: 16}}>
|
||||||
Закрыть
|
Закрыть
|
||||||
</Button>
|
</Button>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@ -44,7 +44,7 @@ const useAppointmentsUI = (appointments, scheduledAppointments) => {
|
|||||||
padding: hovered ? "0 20px" : "0",
|
padding: hovered ? "0 20px" : "0",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
textAlign: "left",
|
textAlign: "left",
|
||||||
transition: "width 0.8s ease, padding 0.8s ease",
|
transition: "width 0.3s ease, padding 0.3s ease",
|
||||||
borderRadius: "4px 0 0 4px",
|
borderRadius: "4px 0 0 4px",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,11 @@
|
|||||||
import {useEffect, useRef, useState} from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import {Badge, Col, Tag, Tooltip} from "antd";
|
import { Badge, Col, Tag, Tooltip } from "antd";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import {AppointmentPropType} from "../../Types/appointmentPropType.js";
|
import { AppointmentPropType } from "../../Types/appointmentPropType.js";
|
||||||
import {ScheduledAppointmentPropType} from "../../Types/scheduledAppointmentPropType.js";
|
import { ScheduledAppointmentPropType } from "../../Types/scheduledAppointmentPropType.js";
|
||||||
|
|
||||||
|
const CalendarCell = ({ allAppointments, onCellClick, onItemClick }) => {
|
||||||
const CalendarCell = ({appointments, scheduledAppointments, onCellClick, onItemClick}) => {
|
|
||||||
const containerRef = useRef(null);
|
const containerRef = useRef(null);
|
||||||
const [isCompressed, setIsCompressed] = useState(false);
|
const [isCompressed, setIsCompressed] = useState(false);
|
||||||
const COMPRESSION_THRESHOLD = 70;
|
const COMPRESSION_THRESHOLD = 70;
|
||||||
@ -28,56 +27,34 @@ const CalendarCell = ({appointments, scheduledAppointments, onCellClick, onItemC
|
|||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
onClick={isCompressed ? onCellClick : undefined}
|
onClick={isCompressed ? onCellClick : undefined}
|
||||||
style={{
|
style={{
|
||||||
height: '100%',
|
height: "100%",
|
||||||
cursor: isCompressed ? 'pointer' : 'default',
|
cursor: isCompressed ? "pointer" : "default",
|
||||||
position: 'relative',
|
position: "relative",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!isCompressed && (
|
{!isCompressed && (
|
||||||
<ul style={{padding: 0, margin: 0}}>
|
<ul style={{ padding: 0, margin: 0 }}>
|
||||||
{appointments.map(app => (
|
{allAppointments.map((app) => (
|
||||||
<Col
|
<Col key={app.id} style={{ overflowX: "hidden" }}>
|
||||||
key={app.id}
|
|
||||||
style={{overflowX: 'hidden'}}
|
|
||||||
>
|
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={`Прошедший прием: ${dayjs(app.appointment_datetime).format('HH:mm')}`}
|
title={`${
|
||||||
|
app.appointment_datetime ? "Прошедший прием" : "Запланированный прием"
|
||||||
|
}: ${dayjs(app.appointment_datetime || app.scheduled_datetime).format("HH:mm")}`}
|
||||||
>
|
>
|
||||||
<Tag
|
<Tag
|
||||||
color="green"
|
color={app.appointment_datetime ? "green" : "blue"}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onItemClick(app);
|
onItemClick(app);
|
||||||
}}
|
}}
|
||||||
style={{margin: '2px 2px 0 0', cursor: 'pointer', width: "95%", minHeight: 30}}
|
style={{ margin: "2px 2px 0 0", cursor: "pointer", width: "95%", minHeight: 30 }}
|
||||||
>
|
>
|
||||||
<Badge
|
<Badge
|
||||||
status="success"
|
status={app.appointment_datetime ? "success" : "processing"}
|
||||||
text={dayjs(app.appointment_datetime).format('HH:mm') + ` ${app.patient.last_name} ${app.patient.first_name} `}
|
text={
|
||||||
/>
|
dayjs(app.appointment_datetime || app.scheduled_datetime).format("HH:mm") +
|
||||||
</Tag>
|
` ${app.patient?.last_name || ""} ${app.patient?.first_name || ""}`
|
||||||
</Tooltip>
|
}
|
||||||
</Col>
|
|
||||||
))}
|
|
||||||
{scheduledAppointments.map(app => (
|
|
||||||
<Col
|
|
||||||
key={app.id}
|
|
||||||
style={{overflowX: 'hidden'}}
|
|
||||||
>
|
|
||||||
<Tooltip
|
|
||||||
title={`Запланированный прием: ${dayjs(app.scheduled_datetime).format('HH:mm')}`}
|
|
||||||
>
|
|
||||||
<Tag
|
|
||||||
color="blue"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
onItemClick(app);
|
|
||||||
}}
|
|
||||||
style={{margin: '2px 2px 0 0', cursor: 'pointer', width: "95%", minHeight: 30}}
|
|
||||||
>
|
|
||||||
<Badge
|
|
||||||
status="processing"
|
|
||||||
text={dayjs(app.scheduled_datetime).format('HH:mm') + ` ${app.patient.last_name} ${app.patient.first_name}`}
|
|
||||||
/>
|
/>
|
||||||
</Tag>
|
</Tag>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@ -86,15 +63,17 @@ const CalendarCell = ({appointments, scheduledAppointments, onCellClick, onItemC
|
|||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
{isCompressed && (
|
{isCompressed && (
|
||||||
<div style={{
|
<div
|
||||||
position: 'absolute',
|
style={{
|
||||||
top: 2,
|
position: "absolute",
|
||||||
right: 2,
|
top: 2,
|
||||||
fontSize: 10,
|
right: 2,
|
||||||
fontWeight: 'bold',
|
fontSize: 10,
|
||||||
color: '#1890ff'
|
fontWeight: "bold",
|
||||||
}}>
|
color: "#1890ff",
|
||||||
{appointments.length + scheduledAppointments.length > 0 && `+${appointments.length + scheduledAppointments.length}`}
|
}}
|
||||||
|
>
|
||||||
|
{allAppointments.length > 0 && `+${allAppointments.length}`}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -102,8 +81,9 @@ const CalendarCell = ({appointments, scheduledAppointments, onCellClick, onItemC
|
|||||||
};
|
};
|
||||||
|
|
||||||
CalendarCell.propTypes = {
|
CalendarCell.propTypes = {
|
||||||
appointments: PropTypes.arrayOf(AppointmentPropType).isRequired,
|
allAppointments: PropTypes.arrayOf(
|
||||||
scheduledAppointments: PropTypes.arrayOf(ScheduledAppointmentPropType).isRequired,
|
PropTypes.oneOfType([AppointmentPropType, ScheduledAppointmentPropType])
|
||||||
|
).isRequired,
|
||||||
onCellClick: PropTypes.func.isRequired,
|
onCellClick: PropTypes.func.isRequired,
|
||||||
onItemClick: PropTypes.func.isRequired,
|
onItemClick: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { createSlice } from '@reduxjs/toolkit';
|
|||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
modalVisible: false,
|
modalVisible: false,
|
||||||
collapsed: false,
|
collapsed: true,
|
||||||
siderWidth: 300,
|
siderWidth: 300,
|
||||||
hovered: false,
|
hovered: false,
|
||||||
selectedAppointment: null,
|
selectedAppointment: null,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user