refactor: Перенос виджетов и форм в отдельные папки
Перемещены компоненты и хуки модальных окон и форм для лучшей организации и переиспользования. Обновлена домашняя страница для использования новых виджетов.
This commit is contained in:
parent
f7678962fc
commit
4648f638a3
@ -1,5 +1,4 @@
|
||||
import JoditEditor from "jodit-react";
|
||||
import { useRef } from "react";
|
||||
import dayjs from "dayjs";
|
||||
import {
|
||||
Button,
|
||||
@ -19,22 +18,18 @@ import {
|
||||
} from "antd";
|
||||
import useAppointmentFormModal from "./useAppointmentFormModal.js";
|
||||
import useAppointmentFormModalUI from "./useAppointmentFormModalUI.js";
|
||||
import LoadingIndicator from "../../../../Widgets/LoadingIndicator.jsx";
|
||||
import { useMemo } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import LoadingIndicator from "../../Widgets/LoadingIndicator.jsx";
|
||||
import {useMemo} from "react";
|
||||
|
||||
const AppointmentFormModal = ({ onCancel }) => {
|
||||
const AppointmentFormModal = () => {
|
||||
const appointmentFormModalData = useAppointmentFormModal();
|
||||
const appointmentFormModalUI = useAppointmentFormModalUI(
|
||||
onCancel,
|
||||
appointmentFormModalData.createAppointment,
|
||||
appointmentFormModalData.patients,
|
||||
appointmentFormModalData.cancelAppointment,
|
||||
appointmentFormModalData.useGetByPatientIdQuery
|
||||
);
|
||||
|
||||
const editor = useRef(null);
|
||||
|
||||
const patientsItems = appointmentFormModalUI.filteredPatients.map((patient) => ({
|
||||
key: patient.id,
|
||||
label: `${patient.last_name} ${patient.first_name} (${appointmentFormModalUI.getDateString(patient.birthday)})`,
|
||||
@ -91,7 +86,7 @@ const AppointmentFormModal = ({ onCancel }) => {
|
||||
allowClear
|
||||
/>
|
||||
<div style={appointmentFormModalUI.chooseContainerStyle}>
|
||||
<Collapse items={patientsItems} />
|
||||
<Collapse items={patientsItems}/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
@ -103,7 +98,7 @@ const AppointmentFormModal = ({ onCancel }) => {
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={appointmentFormModalUI.showDrawer}
|
||||
style={{ marginBottom: 16 }}
|
||||
style={{marginBottom: 16}}
|
||||
disabled={!appointmentFormModalUI.selectedPatient}
|
||||
>
|
||||
Показать прошлые приемы
|
||||
@ -116,7 +111,8 @@ const AppointmentFormModal = ({ onCancel }) => {
|
||||
}}
|
||||
layout="vertical"
|
||||
>
|
||||
<Form.Item name="type_id" label="Тип приема" rules={[{ required: true, message: "Выберите тип приема" }]}>
|
||||
<Form.Item name="type_id" label="Тип приема"
|
||||
rules={[{required: true, message: "Выберите тип приема"}]}>
|
||||
<Select placeholder="Выберите тип приема">
|
||||
{appointmentFormModalData.appointmentTypes.map((type) => (
|
||||
<Select.Option key={type.id} value={type.id}>
|
||||
@ -128,25 +124,25 @@ const AppointmentFormModal = ({ onCancel }) => {
|
||||
<Form.Item
|
||||
name="appointment_datetime"
|
||||
label="Время приема"
|
||||
rules={[{ required: true, message: "Выберите время" }]}
|
||||
rules={[{required: true, message: "Выберите время"}]}
|
||||
>
|
||||
<DatePicker
|
||||
maxDate={dayjs(new Date()).add(1, "day")}
|
||||
showTime
|
||||
format="DD.MM.YYYY HH:mm"
|
||||
style={{ width: "100%" }}
|
||||
style={{width: "100%"}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="days_until_the_next_appointment"
|
||||
label="Дней до следующего приема"
|
||||
rules={[{ type: "number", min: 0, message: "Введите неотрицательное число" }]}
|
||||
rules={[{type: "number", min: 0, message: "Введите неотрицательное число"}]}
|
||||
>
|
||||
<InputNumber min={0} style={{ width: "100%" }} />
|
||||
<InputNumber min={0} style={{width: "100%"}}/>
|
||||
</Form.Item>
|
||||
<Form.Item name="results" label="Результаты приема">
|
||||
<JoditEditor
|
||||
ref={editor}
|
||||
ref={appointmentFormModalUI.editor}
|
||||
value={appointmentFormModalUI.results}
|
||||
config={{
|
||||
readonly: false,
|
||||
@ -184,7 +180,7 @@ const AppointmentFormModal = ({ onCancel }) => {
|
||||
<p>
|
||||
<b>Результаты приема:</b>
|
||||
</p>
|
||||
<div dangerouslySetInnerHTML={{ __html: values.results || "Не указаны" }} />
|
||||
<div dangerouslySetInnerHTML={{__html: values.results || "Не указаны"}}/>
|
||||
</div>
|
||||
);
|
||||
}, [appointmentFormModalUI, appointmentFormModalData]);
|
||||
@ -217,7 +213,7 @@ const AppointmentFormModal = ({ onCancel }) => {
|
||||
return (
|
||||
<>
|
||||
{appointmentFormModalData.isLoading ? (
|
||||
<LoadingIndicator />
|
||||
<LoadingIndicator/>
|
||||
) : (
|
||||
<>
|
||||
<Modal
|
||||
@ -229,10 +225,11 @@ const AppointmentFormModal = ({ onCancel }) => {
|
||||
>
|
||||
{appointmentFormModalData.isLoading ? (
|
||||
<div style={appointmentFormModalUI.loadingContainerStyle}>
|
||||
<Spin size="large" />
|
||||
<Spin size="large"/>
|
||||
</div>
|
||||
) : (
|
||||
<div style={appointmentFormModalUI.stepsContentStyle}>{steps[appointmentFormModalUI.currentStep].content}</div>
|
||||
<div
|
||||
style={appointmentFormModalUI.stepsContentStyle}>{steps[appointmentFormModalUI.currentStep].content}</div>
|
||||
)}
|
||||
|
||||
{!appointmentFormModalUI.screenXS && (
|
||||
@ -272,14 +269,15 @@ const AppointmentFormModal = ({ onCancel }) => {
|
||||
placeholder="Поиск по результатам приема"
|
||||
value={appointmentFormModalUI.searchPreviousAppointments}
|
||||
onChange={appointmentFormModalUI.handleSetSearchPreviousAppointments}
|
||||
style={{ marginBottom: 16 }}
|
||||
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 || "Результаты не указаны" }} />,
|
||||
children: <div
|
||||
dangerouslySetInnerHTML={{__html: appointment.results || "Результаты не указаны"}}/>,
|
||||
}))}
|
||||
/>
|
||||
</Drawer>
|
||||
@ -289,8 +287,5 @@ const AppointmentFormModal = ({ onCancel }) => {
|
||||
);
|
||||
};
|
||||
|
||||
AppointmentFormModal.propTypes = {
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default AppointmentFormModal;
|
||||
@ -1,10 +1,10 @@
|
||||
import { useGetPatientsQuery } from "../../../../../Api/patientsApi.js";
|
||||
import { useGetAppointmentTypesQuery } from "../../../../../Api/appointmentTypesApi.js";
|
||||
import { useGetPatientsQuery } from "../../../Api/patientsApi.js";
|
||||
import { useGetAppointmentTypesQuery } from "../../../Api/appointmentTypesApi.js";
|
||||
import {
|
||||
useCreateAppointmentMutation,
|
||||
useGetByPatientIdQuery,
|
||||
} from "../../../../../Api/appointmentsApi.js";
|
||||
import { useCancelScheduledAppointmentMutation } from "../../../../../Api/scheduledAppointmentsApi.js";
|
||||
} from "../../../Api/appointmentsApi.js";
|
||||
import { useCancelScheduledAppointmentMutation } from "../../../Api/scheduledAppointmentsApi.js";
|
||||
|
||||
const useAppointmentFormModal = () => {
|
||||
const {
|
||||
@ -1,14 +1,14 @@
|
||||
import { Form, notification } from "antd";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { closeModal, setSelectedScheduledAppointment } from "../../../../../Redux/Slices/appointmentsSlice.js";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { closeModal, setSelectedScheduledAppointment } from "../../../Redux/Slices/appointmentsSlice.js";
|
||||
import {useEffect, useMemo, useRef, 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;
|
||||
|
||||
const useAppointmentFormModalUI = (onCancel, createAppointment, patients, cancelAppointment, useGetByPatientIdQuery) => {
|
||||
const useAppointmentFormModalUI = (createAppointment, patients, cancelAppointment, useGetByPatientIdQuery) => {
|
||||
const dispatch = useDispatch();
|
||||
const { modalVisible, scheduledData } = useSelector((state) => state.appointmentsUI);
|
||||
const [form] = Form.useForm();
|
||||
@ -22,6 +22,7 @@ const useAppointmentFormModalUI = (onCancel, createAppointment, patients, cancel
|
||||
const [results, setResults] = useState("");
|
||||
const [isDrawerVisible, setIsDrawerVisible] = useState(false);
|
||||
const [searchPreviousAppointments, setSearchPreviousAppointments] = useState("");
|
||||
const editor = useRef(null);
|
||||
|
||||
const { data: appointments = [] } = useGetAppointmentsQuery(undefined, {
|
||||
pollingInterval: 20000,
|
||||
@ -241,7 +242,7 @@ const useAppointmentFormModalUI = (onCancel, createAppointment, patients, cancel
|
||||
setSearchPatientString("");
|
||||
setFormValues({});
|
||||
setIsDrawerVisible(false);
|
||||
onCancel();
|
||||
dispatch(closeModal());
|
||||
};
|
||||
|
||||
const disableBackButton = currentStep === 0;
|
||||
@ -258,6 +259,7 @@ const useAppointmentFormModalUI = (onCancel, createAppointment, patients, cancel
|
||||
appointmentDate,
|
||||
results,
|
||||
setResults,
|
||||
editor,
|
||||
handleSetSearchPatientString,
|
||||
filteredPatients,
|
||||
filteredPreviousAppointments,
|
||||
@ -1,20 +1,20 @@
|
||||
import {Modal, Form, Input, DatePicker} from "antd";
|
||||
import PropTypes from "prop-types";
|
||||
import locale from "antd/es/date-picker/locale/ru_RU";
|
||||
import {MaskedInput} from "antd-mask-input";
|
||||
import dayjs from "dayjs";
|
||||
import {PatientPropType} from "../../../../../Types/patientPropType.js";
|
||||
import usePatientFormUI from "./usePatientFormUI.js";
|
||||
import usePatientFormModalUI from "./usePatientFormModalUI.js";
|
||||
import usePatientFormModal from "./usePatientFormModal.js";
|
||||
|
||||
const {TextArea} = Input;
|
||||
|
||||
const PatientFormModal = ({visible, onCancel, onSubmit}) => {
|
||||
const patientFormModalUI = usePatientFormUI(visible, onCancel, onSubmit);
|
||||
const PatientFormModal = () => {
|
||||
const patientFormModalData = usePatientFormModal();
|
||||
const patientFormModalUI = usePatientFormModalUI(patientFormModalData.handleModalSubmit);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={patientFormModalUI.modalTitle}
|
||||
open={visible}
|
||||
open={patientFormModalUI.isModalVisible}
|
||||
onCancel={patientFormModalUI.handleCancel}
|
||||
onOk={patientFormModalUI.handleOk}
|
||||
okText={"Сохранить"}
|
||||
@ -99,11 +99,4 @@ const PatientFormModal = ({visible, onCancel, onSubmit}) => {
|
||||
);
|
||||
};
|
||||
|
||||
PatientFormModal.propTypes = {
|
||||
visible: PropTypes.bool.isRequired,
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
onSubmit: PropTypes.func.isRequired,
|
||||
patient: PatientPropType,
|
||||
};
|
||||
|
||||
export default PatientFormModal;
|
||||
@ -0,0 +1,50 @@
|
||||
import {closeModal} from "../../../Redux/Slices/patientsSlice.js";
|
||||
import {notification} from "antd";
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
import {useAddPatientMutation, useUpdatePatientMutation} from "../../../Api/patientsApi.js";
|
||||
|
||||
|
||||
const usePatientFormModal = () => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [addPatient] = useAddPatientMutation();
|
||||
const [updatePatient] = useUpdatePatientMutation();
|
||||
|
||||
const {
|
||||
selectedPatient,
|
||||
} = useSelector(state => state.patientsUI);
|
||||
|
||||
const handleModalSubmit = async (patientData) => {
|
||||
dispatch(closeModal());
|
||||
|
||||
try {
|
||||
if (selectedPatient) {
|
||||
await updatePatient({ id: selectedPatient.id, ...patientData }).unwrap();
|
||||
notification.success({
|
||||
message: "Пациент обновлён",
|
||||
description: `Данные пациента ${patientData.first_name} ${patientData.last_name} успешно обновлены.`,
|
||||
placement: "topRight",
|
||||
});
|
||||
} else {
|
||||
await addPatient(patientData).unwrap();
|
||||
notification.success({
|
||||
message: "Пациент добавлен",
|
||||
description: `Пациент ${patientData.first_name} ${patientData.last_name} успешно добавлен.`,
|
||||
placement: "topRight",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
notification.error({
|
||||
message: "Ошибка",
|
||||
description: error.data?.message || "Произошла ошибка при сохранении",
|
||||
placement: "topRight",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
handleModalSubmit,
|
||||
};
|
||||
};
|
||||
|
||||
export default usePatientFormModal;
|
||||
@ -2,16 +2,17 @@ import {Form, notification} from "antd";
|
||||
import {useEffect} from "react";
|
||||
import dayjs from "dayjs";
|
||||
import validator from "validator";
|
||||
import {useSelector} from "react-redux";
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
import {closeModal} from "../../../Redux/Slices/patientsSlice.js";
|
||||
|
||||
|
||||
const usePatientFormUI = (visible, onCancel, onSubmit) => {
|
||||
const {selectedPatient} = useSelector(state => state.patientsUI);
|
||||
|
||||
const usePatientFormModalUI = (onSubmit) => {
|
||||
const dispatch = useDispatch();
|
||||
const {selectedPatient, isModalVisible} = useSelector(state => state.patientsUI);
|
||||
const [form] = Form.useForm();
|
||||
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
if (isModalVisible) {
|
||||
form.resetFields();
|
||||
if (selectedPatient) {
|
||||
form.setFieldsValue({
|
||||
@ -20,7 +21,7 @@ const usePatientFormUI = (visible, onCancel, onSubmit) => {
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [visible, selectedPatient, form]);
|
||||
}, [isModalVisible, selectedPatient, form]);
|
||||
|
||||
const modalStyle = {
|
||||
marginTop: 20,
|
||||
@ -55,17 +56,18 @@ const usePatientFormUI = (visible, onCancel, onSubmit) => {
|
||||
|
||||
const handleCancel = () => {
|
||||
form.resetFields();
|
||||
onCancel();
|
||||
dispatch(closeModal());
|
||||
};
|
||||
|
||||
return {
|
||||
form,
|
||||
modalStyle,
|
||||
modalTitle,
|
||||
isModalVisible,
|
||||
handleOk,
|
||||
handleCancel,
|
||||
emailValidator,
|
||||
}
|
||||
};
|
||||
|
||||
export default usePatientFormUI;
|
||||
export default usePatientFormModalUI;
|
||||
@ -1,6 +1,6 @@
|
||||
import {useGetPatientsQuery} from "../../../../../Api/patientsApi.js";
|
||||
import {useGetAppointmentTypesQuery} from "../../../../../Api/appointmentTypesApi.js";
|
||||
import {useCreateScheduledAppointmentMutation} from "../../../../../Api/scheduledAppointmentsApi.js";
|
||||
import {useGetPatientsQuery} from "../../../Api/patientsApi.js";
|
||||
import {useGetAppointmentTypesQuery} from "../../../Api/appointmentTypesApi.js";
|
||||
import {useCreateScheduledAppointmentMutation} from "../../../Api/scheduledAppointmentsApi.js";
|
||||
|
||||
|
||||
const useScheduledAppointmentFormModal = () => {
|
||||
@ -1,5 +1,5 @@
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
import {closeScheduledModal} from "../../../../../Redux/Slices/appointmentsSlice.js";
|
||||
import {closeScheduledModal} from "../../../Redux/Slices/appointmentsSlice.js";
|
||||
import {useMemo, useState} from "react";
|
||||
import dayjs from "dayjs";
|
||||
import {notification} from "antd";
|
||||
@ -12,7 +12,7 @@ import useAppointmentsUI from "./useAppointmentsUI.js";
|
||||
import useAppointments from "./useAppointments.js";
|
||||
import dayjs from 'dayjs';
|
||||
import LoadingIndicator from "../../Widgets/LoadingIndicator.jsx";
|
||||
import AppointmentFormModal from "./Components/AppointmentFormModal/AppointmentFormModal.jsx";
|
||||
import AppointmentFormModal from "../../Dummies/AppointmentFormModal/AppointmentFormModal.jsx";
|
||||
import {useDispatch} from "react-redux";
|
||||
import {
|
||||
closeModal,
|
||||
@ -20,10 +20,10 @@ import {
|
||||
setSelectedAppointment,
|
||||
setSelectedScheduledAppointment
|
||||
} from "../../../Redux/Slices/appointmentsSlice.js";
|
||||
import AppointmentViewModal from "./Components/AppointmentViewModal/AppointmentViewModal.jsx";
|
||||
import ScheduledAppointmentFormModal from "./Components/ScheduledAppintmentFormModal/ScheduledAppointmentFormModal.jsx";
|
||||
import AppointmentViewModal from "../../Widgets/AppointmentViewModal/AppointmentViewModal.jsx";
|
||||
import ScheduledAppointmentFormModal from "../../Dummies/ScheduledAppintmentFormModal/ScheduledAppointmentFormModal.jsx";
|
||||
import ScheduledAppointmentsViewModal
|
||||
from "./Components/ScheduledAppointmentsViewModal/ScheduledAppointmentsViewModal.jsx";
|
||||
from "../../Widgets/ScheduledAppointmentsViewModal/ScheduledAppointmentsViewModal.jsx";
|
||||
import AppointmentsListModal from "./Components/AppointmentsListModal/AppointmentsListModal.jsx";
|
||||
|
||||
const AppointmentsPage = () => {
|
||||
@ -31,10 +31,6 @@ const AppointmentsPage = () => {
|
||||
const appointmentsPageUI = useAppointmentsUI(appointmentsData.appointments, appointmentsData.scheduledAppointments);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleCancelModal = () => {
|
||||
dispatch(closeModal());
|
||||
};
|
||||
|
||||
const handleEventClick = (event) => {
|
||||
if (event.appointment_datetime) {
|
||||
dispatch(setSelectedAppointment(event));
|
||||
@ -169,11 +165,8 @@ const AppointmentsPage = () => {
|
||||
/>
|
||||
</FloatButton.Group>
|
||||
|
||||
<AppointmentFormModal onCancel={handleCancelModal}/>
|
||||
<AppointmentViewModal
|
||||
visible={appointmentsPageUI.selectedAppointment !== null}
|
||||
onCancel={appointmentsPageUI.handleCancelViewModal}
|
||||
/>
|
||||
<AppointmentFormModal/>
|
||||
<AppointmentViewModal/>
|
||||
<ScheduledAppointmentFormModal/>
|
||||
<ScheduledAppointmentsViewModal/>
|
||||
<AppointmentsListModal/>
|
||||
|
||||
@ -1,24 +0,0 @@
|
||||
import {useAuth} from "../../../../../Hooks/AuthContext.jsx";
|
||||
import {useEffect, useState} from "react";
|
||||
|
||||
|
||||
const AppointmentsTableTab = () => {
|
||||
const {api} = useAuth();
|
||||
|
||||
const [appointments, setAppointments] = useState([]);
|
||||
|
||||
const [current, setCurrent] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
const [selectedAppointment, setSelectedAppointment] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
return (
|
||||
<div style={{padding: 20}}>
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AppointmentsTableTab;
|
||||
@ -1,4 +1,4 @@
|
||||
import { Button, Card, Col, List, Row, Space, Statistic, Typography, Alert, Result } from "antd";
|
||||
import {Button, Card, Col, List, Row, Space, Statistic, Typography, Result} from "antd";
|
||||
import {
|
||||
HomeOutlined,
|
||||
PlusOutlined,
|
||||
@ -9,7 +9,7 @@ import useHomePage from "./useHomePage.js";
|
||||
import useHomePageUI from "./useHomePageUI.js";
|
||||
import dayjs from "dayjs";
|
||||
import LoadingIndicator from "../../Widgets/LoadingIndicator.jsx";
|
||||
import { Bar } from "react-chartjs-2";
|
||||
import {Bar} from "react-chartjs-2";
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
CategoryScale,
|
||||
@ -19,58 +19,22 @@ import {
|
||||
Tooltip,
|
||||
Legend,
|
||||
} from "chart.js";
|
||||
import AppointmentFormModal from "../AppointmentsPage/Components/AppointmentFormModal/AppointmentFormModal.jsx";
|
||||
import ScheduledAppointmentFormModal from "../AppointmentsPage/Components/ScheduledAppintmentFormModal/ScheduledAppointmentFormModal.jsx";
|
||||
import AppointmentViewModal from "../AppointmentsPage/Components/AppointmentViewModal/AppointmentViewModal.jsx";
|
||||
import ScheduledAppointmentsViewModal from "../AppointmentsPage/Components/ScheduledAppointmentsViewModal/ScheduledAppointmentsViewModal.jsx";
|
||||
import { useSelector } from "react-redux";
|
||||
import AppointmentFormModal from "../../Dummies/AppointmentFormModal/AppointmentFormModal.jsx";
|
||||
import ScheduledAppointmentFormModal
|
||||
from "../../Dummies/ScheduledAppintmentFormModal/ScheduledAppointmentFormModal.jsx";
|
||||
import AppointmentViewModal from "../../Widgets/AppointmentViewModal/AppointmentViewModal.jsx";
|
||||
import ScheduledAppointmentsViewModal
|
||||
from "../../Widgets/ScheduledAppointmentsViewModal/ScheduledAppointmentsViewModal.jsx";
|
||||
import PatientFormModal from "../../Dummies/PatientFormModal/PatientFormModal.jsx";
|
||||
|
||||
ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend);
|
||||
|
||||
const HomePage = () => {
|
||||
const {
|
||||
patients,
|
||||
appointments,
|
||||
scheduledAppointments,
|
||||
isLoading,
|
||||
isError,
|
||||
handleEventClick,
|
||||
handleCreateAppointment,
|
||||
handleCreateScheduledAppointment,
|
||||
handleCreatePatient,
|
||||
handleCancelModal,
|
||||
} = useHomePage();
|
||||
const { containerStyle, sectionStyle, cardStyle, listItemStyle, buttonStyle, chartContainerStyle, isMobile, todayEvents, upcomingBirthdays, appointmentsByDay } = useHomePageUI(appointments, scheduledAppointments, patients);
|
||||
const selectedAppointment = useSelector((state) => state.appointmentsUI.selectedAppointment);
|
||||
const homePageData = useHomePage();
|
||||
const homePageUI = useHomePageUI(homePageData.appointments, homePageData.scheduledAppointments, homePageData.patients);
|
||||
// const selectedScheduledAppointment = useSelector((state) => state.appointmentsUI.selectedScheduledAppointment);
|
||||
|
||||
const chartData = {
|
||||
labels: ["Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс"],
|
||||
datasets: [
|
||||
{
|
||||
label: "Приемы",
|
||||
data: appointmentsByDay,
|
||||
backgroundColor: "#1890ff",
|
||||
borderColor: "#096dd9",
|
||||
borderWidth: 1,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const chartOptions = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
y: { beginAtZero: true, title: { display: true, text: "Количество приемов" } },
|
||||
x: { title: { display: true, text: "День недели" } },
|
||||
},
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
title: { display: true, text: "Приемы за неделю" },
|
||||
},
|
||||
};
|
||||
|
||||
if (isError) {
|
||||
if (homePageData.isError) {
|
||||
return (
|
||||
<Result
|
||||
status="error"
|
||||
@ -81,80 +45,78 @@ const HomePage = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={containerStyle}>
|
||||
{isLoading ? (
|
||||
<LoadingIndicator />
|
||||
<div style={homePageUI.containerStyle}>
|
||||
{homePageData.isLoading ? (
|
||||
<LoadingIndicator/>
|
||||
) : (
|
||||
<>
|
||||
<Typography.Title level={1}>
|
||||
<HomeOutlined /> Главная страница
|
||||
<HomeOutlined/> Главная страница
|
||||
</Typography.Title>
|
||||
|
||||
{/* Быстрые действия */}
|
||||
<div style={sectionStyle}>
|
||||
<Space direction={isMobile ? "vertical" : "horizontal"} size="middle" style={{ width: "100%" }}>
|
||||
<div style={homePageUI.sectionStyle}>
|
||||
<Space direction={homePageUI.isMobile ? "vertical" : "horizontal"} size="middle"
|
||||
style={{width: "100%"}}>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={handleCreateAppointment}
|
||||
style={buttonStyle}
|
||||
icon={<PlusOutlined/>}
|
||||
onClick={homePageData.handleCreateAppointment}
|
||||
style={homePageUI.buttonStyle}
|
||||
>
|
||||
Новый прием
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<CalendarOutlined />}
|
||||
onClick={handleCreateScheduledAppointment}
|
||||
style={buttonStyle}
|
||||
icon={<CalendarOutlined/>}
|
||||
onClick={homePageData.handleCreateScheduledAppointment}
|
||||
style={homePageUI.buttonStyle}
|
||||
>
|
||||
Запланировать
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<UserAddOutlined />}
|
||||
onClick={handleCreatePatient}
|
||||
style={buttonStyle}
|
||||
icon={<UserAddOutlined/>}
|
||||
onClick={homePageData.handleCreatePatient}
|
||||
style={homePageUI.buttonStyle}
|
||||
>
|
||||
Добавить пациента
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
{/* Статистика */}
|
||||
<Row gutter={[16, 16]} style={sectionStyle}>
|
||||
<Col span={isMobile ? 24 : 8}>
|
||||
<Card style={cardStyle}>
|
||||
<Statistic title="Пациенты" value={patients.length} />
|
||||
<Row gutter={[16, 16]} style={homePageUI.sectionStyle}>
|
||||
<Col span={homePageUI.isMobile ? 24 : 8}>
|
||||
<Card style={homePageUI.cardStyle}>
|
||||
<Statistic title="Пациенты" value={homePageData.patients.length}/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={isMobile ? 24 : 8}>
|
||||
<Card style={cardStyle}>
|
||||
<Col span={homePageUI.isMobile ? 24 : 8}>
|
||||
<Card style={homePageUI.cardStyle}>
|
||||
<Statistic
|
||||
title="Приемы за месяц"
|
||||
value={
|
||||
appointments.filter((a) => dayjs(a.appointment_datetime).isSame(dayjs(), "month")).length
|
||||
homePageData.appointments.filter((a) => dayjs(a.appointment_datetime).isSame(dayjs(), "month")).length
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={isMobile ? 24 : 8}>
|
||||
<Card style={cardStyle}>
|
||||
<Statistic title="Запланировано" value={scheduledAppointments.length} />
|
||||
<Col span={homePageUI.isMobile ? 24 : 8}>
|
||||
<Card style={homePageUI.cardStyle}>
|
||||
<Statistic title="Запланировано" value={homePageData.scheduledAppointments.length}/>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{/* События на сегодня */}
|
||||
<Card
|
||||
title={`События на сегодня (${dayjs().format("DD.MM.YYYY")})`}
|
||||
style={sectionStyle}
|
||||
style={homePageUI.sectionStyle}
|
||||
>
|
||||
<List
|
||||
dataSource={todayEvents}
|
||||
dataSource={homePageUI.todayEvents}
|
||||
renderItem={(item) => (
|
||||
<List.Item
|
||||
onClick={() => handleEventClick(item)}
|
||||
style={listItemStyle}
|
||||
onClick={() => homePageData.handleEventClick(item)}
|
||||
style={homePageUI.listItemStyle}
|
||||
>
|
||||
<Space>
|
||||
<Typography.Text strong>
|
||||
@ -168,43 +130,22 @@ const HomePage = () => {
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
{todayEvents.length === 0 && (
|
||||
{homePageUI.todayEvents.length === 0 && (
|
||||
<Typography.Text type="secondary">Нет событий на сегодня</Typography.Text>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
{/* Уведомления */}
|
||||
<Card title="Уведомления" style={sectionStyle}>
|
||||
<Space direction="vertical" style={{ width: "100%" }}>
|
||||
{upcomingBirthdays.map((p) => (
|
||||
<Alert
|
||||
key={p.id}
|
||||
message={`День рождения: ${p.last_name} ${p.first_name} - ${dayjs(p.birthday).format("DD.MM")}`}
|
||||
type="info"
|
||||
showIcon
|
||||
/>
|
||||
))}
|
||||
{upcomingBirthdays.length === 0 && (
|
||||
<Typography.Text type="secondary">Нет уведомлений</Typography.Text>
|
||||
)}
|
||||
</Space>
|
||||
</Card>
|
||||
|
||||
{/* График */}
|
||||
<Card title="Статистика приемов" style={sectionStyle}>
|
||||
<div style={{ ...chartContainerStyle, height: 300 }}>
|
||||
<Bar data={chartData} options={chartOptions} />
|
||||
<Card title="Статистика приемов" style={homePageUI.sectionStyle}>
|
||||
<div style={{...homePageUI.chartContainerStyle, height: 300}}>
|
||||
<Bar data={homePageUI.chartData} options={homePageUI.chartOptions}/>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Модальные окна */}
|
||||
<AppointmentFormModal onCancel={handleCancelModal} />
|
||||
<ScheduledAppointmentFormModal />
|
||||
<AppointmentViewModal
|
||||
visible={!!selectedAppointment}
|
||||
onCancel={handleCancelModal}
|
||||
/>
|
||||
<ScheduledAppointmentsViewModal />
|
||||
<PatientFormModal/>
|
||||
<AppointmentFormModal/>
|
||||
<AppointmentViewModal/>
|
||||
<ScheduledAppointmentFormModal/>
|
||||
<ScheduledAppointmentsViewModal/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -1,16 +1,18 @@
|
||||
import { useGetAppointmentsQuery } from "../../../Api/appointmentsApi.js";
|
||||
import { useGetScheduledAppointmentsQuery } from "../../../Api/scheduledAppointmentsApi.js";
|
||||
import { useGetPatientsQuery } from "../../../Api/patientsApi.js";
|
||||
import { notification } from "antd";
|
||||
import { useEffect } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import {useGetAppointmentsQuery} from "../../../Api/appointmentsApi.js";
|
||||
import {useGetScheduledAppointmentsQuery} from "../../../Api/scheduledAppointmentsApi.js";
|
||||
import {useGetPatientsQuery} from "../../../Api/patientsApi.js";
|
||||
import {notification} from "antd";
|
||||
import {useEffect} from "react";
|
||||
import {useDispatch} from "react-redux";
|
||||
import {
|
||||
setSelectedAppointment,
|
||||
setSelectedScheduledAppointment,
|
||||
openModal,
|
||||
openModal as openAppointmentsListModal,
|
||||
openScheduledModal,
|
||||
closeModal,
|
||||
} from "../../../Redux/Slices/appointmentsSlice.js";
|
||||
import {
|
||||
openModal as openPatientModal,
|
||||
} from "../../../Redux/Slices/patientsSlice.js";
|
||||
import dayjs from "dayjs";
|
||||
import isBetween from "dayjs/plugin/isBetween";
|
||||
import {useGetAppointmentTypesQuery} from "../../../Api/appointmentTypesApi.js"; // Import isBetween plugin
|
||||
@ -92,7 +94,7 @@ const useHomePage = () => {
|
||||
};
|
||||
|
||||
const handleCreateAppointment = () => {
|
||||
dispatch(openModal());
|
||||
dispatch(openAppointmentsListModal());
|
||||
};
|
||||
|
||||
const handleCreateScheduledAppointment = () => {
|
||||
@ -100,15 +102,7 @@ const useHomePage = () => {
|
||||
};
|
||||
|
||||
const handleCreatePatient = () => {
|
||||
notification.info({
|
||||
message: "В разработке",
|
||||
description: "Функция добавления пациента будет доступна в будущем.",
|
||||
placement: "topRight",
|
||||
});
|
||||
};
|
||||
|
||||
const handleCancelModal = () => {
|
||||
dispatch(closeModal());
|
||||
dispatch(openPatientModal());
|
||||
};
|
||||
|
||||
return {
|
||||
@ -130,7 +124,6 @@ const useHomePage = () => {
|
||||
handleCreateAppointment,
|
||||
handleCreateScheduledAppointment,
|
||||
handleCreatePatient,
|
||||
handleCancelModal,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { Grid } from "antd";
|
||||
import { useMemo } from "react";
|
||||
import dayjs from "dayjs";
|
||||
import isBetween from "dayjs/plugin/isBetween"; // Import isBetween plugin
|
||||
import isBetween from "dayjs/plugin/isBetween";
|
||||
import {useSelector} from "react-redux"; // Import isBetween plugin
|
||||
|
||||
dayjs.extend(isBetween); // Extend dayjs with isBetween
|
||||
|
||||
@ -42,6 +43,52 @@ const useHomePageUI = (appointments, scheduledAppointments, patients) => {
|
||||
return data;
|
||||
}, [appointments]);
|
||||
|
||||
const scheduledAppointmentsByDay = useMemo(() => {
|
||||
const data = Array(7).fill(0);
|
||||
scheduledAppointments
|
||||
.filter((app) =>
|
||||
dayjs(app.scheduled_datetime).isBetween(dayjs().startOf("week"), dayjs().endOf("week"), "day", "[]")
|
||||
)
|
||||
.forEach((app) => {
|
||||
const dayIndex = dayjs(app.scheduled_datetime).day();
|
||||
data[dayIndex === 0 ? 6 : dayIndex - 1]++;
|
||||
});
|
||||
return data;
|
||||
}, [scheduledAppointments]);
|
||||
|
||||
const chartOptions = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
y: { beginAtZero: true, title: { display: true, text: "Количество приемов" } },
|
||||
x: { title: { display: true, text: "День недели" } },
|
||||
},
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
title: { display: true, text: "Приемы за неделю" },
|
||||
},
|
||||
};
|
||||
|
||||
const chartData = {
|
||||
labels: ["Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс"],
|
||||
datasets: [
|
||||
{
|
||||
label: "Приемы",
|
||||
data: appointmentsByDay,
|
||||
backgroundColor: "#1890ff",
|
||||
borderColor: "#096dd9",
|
||||
borderWidth: 1,
|
||||
},
|
||||
{
|
||||
label: "Запланированные приемы",
|
||||
data: scheduledAppointmentsByDay,
|
||||
backgroundColor: "#ff4d4f",
|
||||
borderColor: "#ff4d4f",
|
||||
borderWidth: 1,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return {
|
||||
containerStyle,
|
||||
sectionStyle,
|
||||
@ -53,6 +100,8 @@ const useHomePageUI = (appointments, scheduledAppointments, patients) => {
|
||||
todayEvents,
|
||||
upcomingBirthdays,
|
||||
appointmentsByDay,
|
||||
chartData,
|
||||
chartOptions,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
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);
|
||||
const AppointmentViewModal = () => {
|
||||
const appointmentViewModalUI = useAppointmentViewUI();
|
||||
|
||||
if (!appointmentViewModalUI.selectedAppointment) {
|
||||
return null;
|
||||
@ -15,8 +14,8 @@ const AppointmentViewModal = ({visible, onCancel}) => {
|
||||
<>
|
||||
<Modal
|
||||
title="Просмотр приема"
|
||||
open={visible}
|
||||
onCancel={onCancel}
|
||||
open={appointmentViewModalUI.visible}
|
||||
onCancel={appointmentViewModalUI.onCancel}
|
||||
footer={null}
|
||||
width={appointmentViewModalUI.modalWidth}
|
||||
>
|
||||
@ -52,10 +51,11 @@ const AppointmentViewModal = ({visible, onCancel}) => {
|
||||
<p>
|
||||
<b>Результаты приема:</b>
|
||||
</p>
|
||||
<div dangerouslySetInnerHTML={{__html: appointmentViewModalUI.selectedAppointment.results || "Не указаны"}}/>
|
||||
<div
|
||||
dangerouslySetInnerHTML={{__html: appointmentViewModalUI.selectedAppointment.results || "Не указаны"}}/>
|
||||
</div>
|
||||
<Row justify="end" style={appointmentViewModalUI.footerRowStyle}>
|
||||
<Button style={appointmentViewModalUI.footerButtonStyle} onClick={onCancel}>
|
||||
<Button style={appointmentViewModalUI.footerButtonStyle} onClick={appointmentViewModalUI.onCancel}>
|
||||
Закрыть
|
||||
</Button>
|
||||
</Row>
|
||||
@ -64,9 +64,5 @@ const AppointmentViewModal = ({visible, onCancel}) => {
|
||||
);
|
||||
};
|
||||
|
||||
AppointmentViewModal.propTypes = {
|
||||
visible: PropTypes.bool.isRequired,
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default AppointmentViewModal;
|
||||
@ -1,4 +1,7 @@
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
import {
|
||||
setSelectedAppointment
|
||||
} from "../../../Redux/Slices/appointmentsSlice.js";
|
||||
|
||||
const useAppointmentViewUI = () => {
|
||||
const dispatch = useDispatch();
|
||||
@ -10,7 +13,11 @@ const useAppointmentViewUI = () => {
|
||||
const blockStyle = {marginBottom: 16};
|
||||
const footerRowStyle = {marginTop: 16};
|
||||
const footerButtonStyle = {marginRight: 8};
|
||||
const visible = !!selectedAppointment;
|
||||
|
||||
const onCancel = () => {
|
||||
dispatch(setSelectedAppointment(null));
|
||||
};
|
||||
|
||||
const getDateString = (date) => {
|
||||
return new Date(date).toLocaleDateString('ru-RU');
|
||||
@ -22,7 +29,9 @@ const useAppointmentViewUI = () => {
|
||||
footerRowStyle,
|
||||
footerButtonStyle,
|
||||
selectedAppointment,
|
||||
visible,
|
||||
getDateString,
|
||||
onCancel,
|
||||
};
|
||||
};
|
||||
|
||||
@ -3,7 +3,7 @@ import dayjs from "dayjs";
|
||||
import useScheduledAppointmentsViewModal from "./useScheduledAppointmentsViewModal.js";
|
||||
import useScheduledAppointmentsViewModalUI from "./useScheduledAppointmentsViewModalUI.js";
|
||||
import { useDispatch } from "react-redux";
|
||||
import {openModalWithScheduledData} from "../../../../../Redux/Slices/appointmentsSlice.js";
|
||||
import {openModalWithScheduledData} from "../../../Redux/Slices/appointmentsSlice.js";
|
||||
|
||||
const ScheduledAppointmentsViewModal = () => {
|
||||
const dispatch = useDispatch();
|
||||
@ -1,4 +1,4 @@
|
||||
import {useCancelScheduledAppointmentMutation} from "../../../../../Api/scheduledAppointmentsApi.js";
|
||||
import {useCancelScheduledAppointmentMutation} from "../../../Api/scheduledAppointmentsApi.js";
|
||||
|
||||
|
||||
const useScheduledAppointmentsViewModal = () => {
|
||||
@ -1,6 +1,6 @@
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
import dayjs from "dayjs";
|
||||
import {setSelectedScheduledAppointment} from "../../../../../Redux/Slices/appointmentsSlice.js";
|
||||
import {setSelectedScheduledAppointment} from "../../../Redux/Slices/appointmentsSlice.js";
|
||||
import {notification} from "antd";
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user