refactor: Перенос виджетов и форм в отдельные папки

Перемещены компоненты и хуки модальных окон и форм для лучшей организации и переиспользования. Обновлена домашняя страница для использования новых виджетов.
This commit is contained in:
Андрей Дувакин 2025-06-02 16:08:26 +05:00
parent f7678962fc
commit 4648f638a3
19 changed files with 239 additions and 240 deletions

View File

@ -1,5 +1,4 @@
import JoditEditor from "jodit-react"; import JoditEditor from "jodit-react";
import { useRef } from "react";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { import {
Button, Button,
@ -19,22 +18,18 @@ import {
} 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";
const AppointmentFormModal = ({ onCancel }) => { const AppointmentFormModal = () => {
const appointmentFormModalData = useAppointmentFormModal(); const appointmentFormModalData = useAppointmentFormModal();
const appointmentFormModalUI = useAppointmentFormModalUI( const appointmentFormModalUI = useAppointmentFormModalUI(
onCancel,
appointmentFormModalData.createAppointment, appointmentFormModalData.createAppointment,
appointmentFormModalData.patients, appointmentFormModalData.patients,
appointmentFormModalData.cancelAppointment, appointmentFormModalData.cancelAppointment,
appointmentFormModalData.useGetByPatientIdQuery appointmentFormModalData.useGetByPatientIdQuery
); );
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)})`,
@ -91,7 +86,7 @@ const AppointmentFormModal = ({ onCancel }) => {
allowClear allowClear
/> />
<div style={appointmentFormModalUI.chooseContainerStyle}> <div style={appointmentFormModalUI.chooseContainerStyle}>
<Collapse items={patientsItems} /> <Collapse items={patientsItems}/>
</div> </div>
</> </>
); );
@ -103,7 +98,7 @@ const AppointmentFormModal = ({ onCancel }) => {
<Button <Button
type="primary" type="primary"
onClick={appointmentFormModalUI.showDrawer} onClick={appointmentFormModalUI.showDrawer}
style={{ marginBottom: 16 }} style={{marginBottom: 16}}
disabled={!appointmentFormModalUI.selectedPatient} disabled={!appointmentFormModalUI.selectedPatient}
> >
Показать прошлые приемы Показать прошлые приемы
@ -116,7 +111,8 @@ const AppointmentFormModal = ({ onCancel }) => {
}} }}
layout="vertical" layout="vertical"
> >
<Form.Item name="type_id" label="Тип приема" rules={[{ required: true, message: "Выберите тип приема" }]}> <Form.Item name="type_id" label="Тип приема"
rules={[{required: true, message: "Выберите тип приема"}]}>
<Select placeholder="Выберите тип приема"> <Select placeholder="Выберите тип приема">
{appointmentFormModalData.appointmentTypes.map((type) => ( {appointmentFormModalData.appointmentTypes.map((type) => (
<Select.Option key={type.id} value={type.id}> <Select.Option key={type.id} value={type.id}>
@ -128,25 +124,25 @@ const AppointmentFormModal = ({ onCancel }) => {
<Form.Item <Form.Item
name="appointment_datetime" name="appointment_datetime"
label="Время приема" label="Время приема"
rules={[{ required: true, message: "Выберите время" }]} rules={[{required: true, message: "Выберите время"}]}
> >
<DatePicker <DatePicker
maxDate={dayjs(new Date()).add(1, "day")} maxDate={dayjs(new Date()).add(1, "day")}
showTime showTime
format="DD.MM.YYYY HH:mm" format="DD.MM.YYYY HH:mm"
style={{ width: "100%" }} style={{width: "100%"}}
/> />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
name="days_until_the_next_appointment" name="days_until_the_next_appointment"
label="Дней до следующего приема" 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>
<Form.Item name="results" label="Результаты приема"> <Form.Item name="results" label="Результаты приема">
<JoditEditor <JoditEditor
ref={editor} ref={appointmentFormModalUI.editor}
value={appointmentFormModalUI.results} value={appointmentFormModalUI.results}
config={{ config={{
readonly: false, readonly: false,
@ -184,7 +180,7 @@ const AppointmentFormModal = ({ onCancel }) => {
<p> <p>
<b>Результаты приема:</b> <b>Результаты приема:</b>
</p> </p>
<div dangerouslySetInnerHTML={{ __html: values.results || "Не указаны" }} /> <div dangerouslySetInnerHTML={{__html: values.results || "Не указаны"}}/>
</div> </div>
); );
}, [appointmentFormModalUI, appointmentFormModalData]); }, [appointmentFormModalUI, appointmentFormModalData]);
@ -217,7 +213,7 @@ const AppointmentFormModal = ({ onCancel }) => {
return ( return (
<> <>
{appointmentFormModalData.isLoading ? ( {appointmentFormModalData.isLoading ? (
<LoadingIndicator /> <LoadingIndicator/>
) : ( ) : (
<> <>
<Modal <Modal
@ -229,10 +225,11 @@ const AppointmentFormModal = ({ onCancel }) => {
> >
{appointmentFormModalData.isLoading ? ( {appointmentFormModalData.isLoading ? (
<div style={appointmentFormModalUI.loadingContainerStyle}> <div style={appointmentFormModalUI.loadingContainerStyle}>
<Spin size="large" /> <Spin size="large"/>
</div> </div>
) : ( ) : (
<div style={appointmentFormModalUI.stepsContentStyle}>{steps[appointmentFormModalUI.currentStep].content}</div> <div
style={appointmentFormModalUI.stepsContentStyle}>{steps[appointmentFormModalUI.currentStep].content}</div>
)} )}
{!appointmentFormModalUI.screenXS && ( {!appointmentFormModalUI.screenXS && (
@ -272,14 +269,15 @@ const AppointmentFormModal = ({ onCancel }) => {
placeholder="Поиск по результатам приема" placeholder="Поиск по результатам приема"
value={appointmentFormModalUI.searchPreviousAppointments} value={appointmentFormModalUI.searchPreviousAppointments}
onChange={appointmentFormModalUI.handleSetSearchPreviousAppointments} onChange={appointmentFormModalUI.handleSetSearchPreviousAppointments}
style={{ marginBottom: 16 }} style={{marginBottom: 16}}
allowClear allowClear
/> />
<Collapse <Collapse
items={appointmentFormModalUI.filteredPreviousAppointments.map((appointment) => ({ items={appointmentFormModalUI.filteredPreviousAppointments.map((appointment) => ({
key: appointment.id, key: appointment.id,
label: `Прием ${dayjs(appointment.appointment_datetime).format("DD.MM.YYYY HH:mm")}`, label: `Прием ${dayjs(appointment.appointment_datetime).format("DD.MM.YYYY HH:mm")}`,
children: <div dangerouslySetInnerHTML={{ __html: appointment.results || "Результаты не указаны" }} />, children: <div
dangerouslySetInnerHTML={{__html: appointment.results || "Результаты не указаны"}}/>,
}))} }))}
/> />
</Drawer> </Drawer>
@ -289,8 +287,5 @@ const AppointmentFormModal = ({ onCancel }) => {
); );
}; };
AppointmentFormModal.propTypes = {
onCancel: PropTypes.func.isRequired,
};
export default AppointmentFormModal; export default AppointmentFormModal;

View File

@ -1,10 +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 { import {
useCreateAppointmentMutation, useCreateAppointmentMutation,
useGetByPatientIdQuery, useGetByPatientIdQuery,
} from "../../../../../Api/appointmentsApi.js"; } from "../../../Api/appointmentsApi.js";
import { useCancelScheduledAppointmentMutation } from "../../../../../Api/scheduledAppointmentsApi.js"; import { useCancelScheduledAppointmentMutation } from "../../../Api/scheduledAppointmentsApi.js";
const useAppointmentFormModal = () => { const useAppointmentFormModal = () => {
const { const {

View File

@ -1,14 +1,14 @@
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, useRef, 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, useGetByPatientIdQuery) => { const useAppointmentFormModalUI = (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();
@ -22,6 +22,7 @@ const useAppointmentFormModalUI = (onCancel, createAppointment, patients, cancel
const [results, setResults] = useState(""); const [results, setResults] = useState("");
const [isDrawerVisible, setIsDrawerVisible] = useState(false); const [isDrawerVisible, setIsDrawerVisible] = useState(false);
const [searchPreviousAppointments, setSearchPreviousAppointments] = useState(""); const [searchPreviousAppointments, setSearchPreviousAppointments] = useState("");
const editor = useRef(null);
const { data: appointments = [] } = useGetAppointmentsQuery(undefined, { const { data: appointments = [] } = useGetAppointmentsQuery(undefined, {
pollingInterval: 20000, pollingInterval: 20000,
@ -241,7 +242,7 @@ const useAppointmentFormModalUI = (onCancel, createAppointment, patients, cancel
setSearchPatientString(""); setSearchPatientString("");
setFormValues({}); setFormValues({});
setIsDrawerVisible(false); setIsDrawerVisible(false);
onCancel(); dispatch(closeModal());
}; };
const disableBackButton = currentStep === 0; const disableBackButton = currentStep === 0;
@ -258,6 +259,7 @@ const useAppointmentFormModalUI = (onCancel, createAppointment, patients, cancel
appointmentDate, appointmentDate,
results, results,
setResults, setResults,
editor,
handleSetSearchPatientString, handleSetSearchPatientString,
filteredPatients, filteredPatients,
filteredPreviousAppointments, filteredPreviousAppointments,

View File

@ -1,20 +1,20 @@
import {Modal, Form, Input, DatePicker} from "antd"; import {Modal, Form, Input, DatePicker} from "antd";
import PropTypes from "prop-types";
import locale from "antd/es/date-picker/locale/ru_RU"; import locale from "antd/es/date-picker/locale/ru_RU";
import {MaskedInput} from "antd-mask-input"; import {MaskedInput} from "antd-mask-input";
import dayjs from "dayjs"; import dayjs from "dayjs";
import {PatientPropType} from "../../../../../Types/patientPropType.js"; import usePatientFormModalUI from "./usePatientFormModalUI.js";
import usePatientFormUI from "./usePatientFormUI.js"; import usePatientFormModal from "./usePatientFormModal.js";
const {TextArea} = Input; const {TextArea} = Input;
const PatientFormModal = ({visible, onCancel, onSubmit}) => { const PatientFormModal = () => {
const patientFormModalUI = usePatientFormUI(visible, onCancel, onSubmit); const patientFormModalData = usePatientFormModal();
const patientFormModalUI = usePatientFormModalUI(patientFormModalData.handleModalSubmit);
return ( return (
<Modal <Modal
title={patientFormModalUI.modalTitle} title={patientFormModalUI.modalTitle}
open={visible} open={patientFormModalUI.isModalVisible}
onCancel={patientFormModalUI.handleCancel} onCancel={patientFormModalUI.handleCancel}
onOk={patientFormModalUI.handleOk} onOk={patientFormModalUI.handleOk}
okText={"Сохранить"} 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; export default PatientFormModal;

View File

@ -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;

View File

@ -2,16 +2,17 @@ import {Form, notification} from "antd";
import {useEffect} from "react"; import {useEffect} from "react";
import dayjs from "dayjs"; import dayjs from "dayjs";
import validator from "validator"; 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 usePatientFormModalUI = (onSubmit) => {
const {selectedPatient} = useSelector(state => state.patientsUI); const dispatch = useDispatch();
const {selectedPatient, isModalVisible} = useSelector(state => state.patientsUI);
const [form] = Form.useForm(); const [form] = Form.useForm();
useEffect(() => { useEffect(() => {
if (visible) { if (isModalVisible) {
form.resetFields(); form.resetFields();
if (selectedPatient) { if (selectedPatient) {
form.setFieldsValue({ form.setFieldsValue({
@ -20,7 +21,7 @@ const usePatientFormUI = (visible, onCancel, onSubmit) => {
}); });
} }
} }
}, [visible, selectedPatient, form]); }, [isModalVisible, selectedPatient, form]);
const modalStyle = { const modalStyle = {
marginTop: 20, marginTop: 20,
@ -55,17 +56,18 @@ const usePatientFormUI = (visible, onCancel, onSubmit) => {
const handleCancel = () => { const handleCancel = () => {
form.resetFields(); form.resetFields();
onCancel(); dispatch(closeModal());
}; };
return { return {
form, form,
modalStyle, modalStyle,
modalTitle, modalTitle,
isModalVisible,
handleOk, handleOk,
handleCancel, handleCancel,
emailValidator, emailValidator,
} }
}; };
export default usePatientFormUI; export default usePatientFormModalUI;

View File

@ -1,6 +1,6 @@
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 {useCreateScheduledAppointmentMutation} from "../../../../../Api/scheduledAppointmentsApi.js"; import {useCreateScheduledAppointmentMutation} from "../../../Api/scheduledAppointmentsApi.js";
const useScheduledAppointmentFormModal = () => { const useScheduledAppointmentFormModal = () => {

View File

@ -1,5 +1,5 @@
import {useDispatch, useSelector} from "react-redux"; 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 {useMemo, useState} from "react";
import dayjs from "dayjs"; import dayjs from "dayjs";
import {notification} from "antd"; import {notification} from "antd";

View File

@ -12,7 +12,7 @@ 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 "../../Dummies/AppointmentFormModal/AppointmentFormModal.jsx";
import {useDispatch} from "react-redux"; import {useDispatch} from "react-redux";
import { import {
closeModal, closeModal,
@ -20,10 +20,10 @@ import {
setSelectedAppointment, setSelectedAppointment,
setSelectedScheduledAppointment setSelectedScheduledAppointment
} from "../../../Redux/Slices/appointmentsSlice.js"; } from "../../../Redux/Slices/appointmentsSlice.js";
import AppointmentViewModal from "./Components/AppointmentViewModal/AppointmentViewModal.jsx"; import AppointmentViewModal from "../../Widgets/AppointmentViewModal/AppointmentViewModal.jsx";
import ScheduledAppointmentFormModal from "./Components/ScheduledAppintmentFormModal/ScheduledAppointmentFormModal.jsx"; import ScheduledAppointmentFormModal from "../../Dummies/ScheduledAppintmentFormModal/ScheduledAppointmentFormModal.jsx";
import ScheduledAppointmentsViewModal import ScheduledAppointmentsViewModal
from "./Components/ScheduledAppointmentsViewModal/ScheduledAppointmentsViewModal.jsx"; from "../../Widgets/ScheduledAppointmentsViewModal/ScheduledAppointmentsViewModal.jsx";
import AppointmentsListModal from "./Components/AppointmentsListModal/AppointmentsListModal.jsx"; import AppointmentsListModal from "./Components/AppointmentsListModal/AppointmentsListModal.jsx";
const AppointmentsPage = () => { const AppointmentsPage = () => {
@ -31,10 +31,6 @@ const AppointmentsPage = () => {
const appointmentsPageUI = useAppointmentsUI(appointmentsData.appointments, appointmentsData.scheduledAppointments); const appointmentsPageUI = useAppointmentsUI(appointmentsData.appointments, appointmentsData.scheduledAppointments);
const dispatch = useDispatch(); const dispatch = useDispatch();
const handleCancelModal = () => {
dispatch(closeModal());
};
const handleEventClick = (event) => { const handleEventClick = (event) => {
if (event.appointment_datetime) { if (event.appointment_datetime) {
dispatch(setSelectedAppointment(event)); dispatch(setSelectedAppointment(event));
@ -169,11 +165,8 @@ const AppointmentsPage = () => {
/> />
</FloatButton.Group> </FloatButton.Group>
<AppointmentFormModal onCancel={handleCancelModal}/> <AppointmentFormModal/>
<AppointmentViewModal <AppointmentViewModal/>
visible={appointmentsPageUI.selectedAppointment !== null}
onCancel={appointmentsPageUI.handleCancelViewModal}
/>
<ScheduledAppointmentFormModal/> <ScheduledAppointmentFormModal/>
<ScheduledAppointmentsViewModal/> <ScheduledAppointmentsViewModal/>
<AppointmentsListModal/> <AppointmentsListModal/>

View File

@ -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;

View File

@ -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 { import {
HomeOutlined, HomeOutlined,
PlusOutlined, PlusOutlined,
@ -9,7 +9,7 @@ import useHomePage from "./useHomePage.js";
import useHomePageUI from "./useHomePageUI.js"; import useHomePageUI from "./useHomePageUI.js";
import dayjs from "dayjs"; import dayjs from "dayjs";
import LoadingIndicator from "../../Widgets/LoadingIndicator.jsx"; import LoadingIndicator from "../../Widgets/LoadingIndicator.jsx";
import { Bar } from "react-chartjs-2"; import {Bar} from "react-chartjs-2";
import { import {
Chart as ChartJS, Chart as ChartJS,
CategoryScale, CategoryScale,
@ -19,58 +19,22 @@ import {
Tooltip, Tooltip,
Legend, Legend,
} from "chart.js"; } from "chart.js";
import AppointmentFormModal from "../AppointmentsPage/Components/AppointmentFormModal/AppointmentFormModal.jsx"; import AppointmentFormModal from "../../Dummies/AppointmentFormModal/AppointmentFormModal.jsx";
import ScheduledAppointmentFormModal from "../AppointmentsPage/Components/ScheduledAppintmentFormModal/ScheduledAppointmentFormModal.jsx"; import ScheduledAppointmentFormModal
import AppointmentViewModal from "../AppointmentsPage/Components/AppointmentViewModal/AppointmentViewModal.jsx"; from "../../Dummies/ScheduledAppintmentFormModal/ScheduledAppointmentFormModal.jsx";
import ScheduledAppointmentsViewModal from "../AppointmentsPage/Components/ScheduledAppointmentsViewModal/ScheduledAppointmentsViewModal.jsx"; import AppointmentViewModal from "../../Widgets/AppointmentViewModal/AppointmentViewModal.jsx";
import { useSelector } from "react-redux"; import ScheduledAppointmentsViewModal
from "../../Widgets/ScheduledAppointmentsViewModal/ScheduledAppointmentsViewModal.jsx";
import PatientFormModal from "../../Dummies/PatientFormModal/PatientFormModal.jsx";
ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend); ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend);
const HomePage = () => { const HomePage = () => {
const { const homePageData = useHomePage();
patients, const homePageUI = useHomePageUI(homePageData.appointments, homePageData.scheduledAppointments, homePageData.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 selectedScheduledAppointment = useSelector((state) => state.appointmentsUI.selectedScheduledAppointment); // const selectedScheduledAppointment = useSelector((state) => state.appointmentsUI.selectedScheduledAppointment);
const chartData = { if (homePageData.isError) {
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) {
return ( return (
<Result <Result
status="error" status="error"
@ -81,80 +45,78 @@ const HomePage = () => {
} }
return ( return (
<div style={containerStyle}> <div style={homePageUI.containerStyle}>
{isLoading ? ( {homePageData.isLoading ? (
<LoadingIndicator /> <LoadingIndicator/>
) : ( ) : (
<> <>
<Typography.Title level={1}> <Typography.Title level={1}>
<HomeOutlined /> Главная страница <HomeOutlined/> Главная страница
</Typography.Title> </Typography.Title>
{/* Быстрые действия */} <div style={homePageUI.sectionStyle}>
<div style={sectionStyle}> <Space direction={homePageUI.isMobile ? "vertical" : "horizontal"} size="middle"
<Space direction={isMobile ? "vertical" : "horizontal"} size="middle" style={{ width: "100%" }}> style={{width: "100%"}}>
<Button <Button
type="primary" type="primary"
icon={<PlusOutlined />} icon={<PlusOutlined/>}
onClick={handleCreateAppointment} onClick={homePageData.handleCreateAppointment}
style={buttonStyle} style={homePageUI.buttonStyle}
> >
Новый прием Новый прием
</Button> </Button>
<Button <Button
type="primary" type="primary"
icon={<CalendarOutlined />} icon={<CalendarOutlined/>}
onClick={handleCreateScheduledAppointment} onClick={homePageData.handleCreateScheduledAppointment}
style={buttonStyle} style={homePageUI.buttonStyle}
> >
Запланировать Запланировать
</Button> </Button>
<Button <Button
type="primary" type="primary"
icon={<UserAddOutlined />} icon={<UserAddOutlined/>}
onClick={handleCreatePatient} onClick={homePageData.handleCreatePatient}
style={buttonStyle} style={homePageUI.buttonStyle}
> >
Добавить пациента Добавить пациента
</Button> </Button>
</Space> </Space>
</div> </div>
{/* Статистика */} <Row gutter={[16, 16]} style={homePageUI.sectionStyle}>
<Row gutter={[16, 16]} style={sectionStyle}> <Col span={homePageUI.isMobile ? 24 : 8}>
<Col span={isMobile ? 24 : 8}> <Card style={homePageUI.cardStyle}>
<Card style={cardStyle}> <Statistic title="Пациенты" value={homePageData.patients.length}/>
<Statistic title="Пациенты" value={patients.length} />
</Card> </Card>
</Col> </Col>
<Col span={isMobile ? 24 : 8}> <Col span={homePageUI.isMobile ? 24 : 8}>
<Card style={cardStyle}> <Card style={homePageUI.cardStyle}>
<Statistic <Statistic
title="Приемы за месяц" title="Приемы за месяц"
value={ 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> </Card>
</Col> </Col>
<Col span={isMobile ? 24 : 8}> <Col span={homePageUI.isMobile ? 24 : 8}>
<Card style={cardStyle}> <Card style={homePageUI.cardStyle}>
<Statistic title="Запланировано" value={scheduledAppointments.length} /> <Statistic title="Запланировано" value={homePageData.scheduledAppointments.length}/>
</Card> </Card>
</Col> </Col>
</Row> </Row>
{/* События на сегодня */}
<Card <Card
title={`События на сегодня (${dayjs().format("DD.MM.YYYY")})`} title={`События на сегодня (${dayjs().format("DD.MM.YYYY")})`}
style={sectionStyle} style={homePageUI.sectionStyle}
> >
<List <List
dataSource={todayEvents} dataSource={homePageUI.todayEvents}
renderItem={(item) => ( renderItem={(item) => (
<List.Item <List.Item
onClick={() => handleEventClick(item)} onClick={() => homePageData.handleEventClick(item)}
style={listItemStyle} style={homePageUI.listItemStyle}
> >
<Space> <Space>
<Typography.Text strong> <Typography.Text strong>
@ -168,43 +130,22 @@ const HomePage = () => {
</List.Item> </List.Item>
)} )}
/> />
{todayEvents.length === 0 && ( {homePageUI.todayEvents.length === 0 && (
<Typography.Text type="secondary">Нет событий на сегодня</Typography.Text> <Typography.Text type="secondary">Нет событий на сегодня</Typography.Text>
)} )}
</Card> </Card>
{/* Уведомления */} <Card title="Статистика приемов" style={homePageUI.sectionStyle}>
<Card title="Уведомления" style={sectionStyle}> <div style={{...homePageUI.chartContainerStyle, height: 300}}>
<Space direction="vertical" style={{ width: "100%" }}> <Bar data={homePageUI.chartData} options={homePageUI.chartOptions}/>
{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} />
</div> </div>
</Card> </Card>
{/* Модальные окна */} <PatientFormModal/>
<AppointmentFormModal onCancel={handleCancelModal} /> <AppointmentFormModal/>
<ScheduledAppointmentFormModal /> <AppointmentViewModal/>
<AppointmentViewModal <ScheduledAppointmentFormModal/>
visible={!!selectedAppointment} <ScheduledAppointmentsViewModal/>
onCancel={handleCancelModal}
/>
<ScheduledAppointmentsViewModal />
</> </>
)} )}
</div> </div>

View File

@ -1,16 +1,18 @@
import { useGetAppointmentsQuery } from "../../../Api/appointmentsApi.js"; import {useGetAppointmentsQuery} from "../../../Api/appointmentsApi.js";
import { useGetScheduledAppointmentsQuery } from "../../../Api/scheduledAppointmentsApi.js"; import {useGetScheduledAppointmentsQuery} from "../../../Api/scheduledAppointmentsApi.js";
import { useGetPatientsQuery } from "../../../Api/patientsApi.js"; import {useGetPatientsQuery} from "../../../Api/patientsApi.js";
import { notification } from "antd"; import {notification} from "antd";
import { useEffect } from "react"; import {useEffect} from "react";
import { useDispatch } from "react-redux"; import {useDispatch} from "react-redux";
import { import {
setSelectedAppointment, setSelectedAppointment,
setSelectedScheduledAppointment, setSelectedScheduledAppointment,
openModal, openModal as openAppointmentsListModal,
openScheduledModal, openScheduledModal,
closeModal,
} from "../../../Redux/Slices/appointmentsSlice.js"; } from "../../../Redux/Slices/appointmentsSlice.js";
import {
openModal as openPatientModal,
} from "../../../Redux/Slices/patientsSlice.js";
import dayjs from "dayjs"; import dayjs from "dayjs";
import isBetween from "dayjs/plugin/isBetween"; import isBetween from "dayjs/plugin/isBetween";
import {useGetAppointmentTypesQuery} from "../../../Api/appointmentTypesApi.js"; // Import isBetween plugin import {useGetAppointmentTypesQuery} from "../../../Api/appointmentTypesApi.js"; // Import isBetween plugin
@ -92,7 +94,7 @@ const useHomePage = () => {
}; };
const handleCreateAppointment = () => { const handleCreateAppointment = () => {
dispatch(openModal()); dispatch(openAppointmentsListModal());
}; };
const handleCreateScheduledAppointment = () => { const handleCreateScheduledAppointment = () => {
@ -100,15 +102,7 @@ const useHomePage = () => {
}; };
const handleCreatePatient = () => { const handleCreatePatient = () => {
notification.info({ dispatch(openPatientModal());
message: "В разработке",
description: "Функция добавления пациента будет доступна в будущем.",
placement: "topRight",
});
};
const handleCancelModal = () => {
dispatch(closeModal());
}; };
return { return {
@ -130,7 +124,6 @@ const useHomePage = () => {
handleCreateAppointment, handleCreateAppointment,
handleCreateScheduledAppointment, handleCreateScheduledAppointment,
handleCreatePatient, handleCreatePatient,
handleCancelModal,
}; };
}; };

View File

@ -1,7 +1,8 @@
import { Grid } from "antd"; import { Grid } from "antd";
import { useMemo } from "react"; import { useMemo } from "react";
import dayjs from "dayjs"; 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 dayjs.extend(isBetween); // Extend dayjs with isBetween
@ -42,6 +43,52 @@ const useHomePageUI = (appointments, scheduledAppointments, patients) => {
return data; return data;
}, [appointments]); }, [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 { return {
containerStyle, containerStyle,
sectionStyle, sectionStyle,
@ -53,6 +100,8 @@ const useHomePageUI = (appointments, scheduledAppointments, patients) => {
todayEvents, todayEvents,
upcomingBirthdays, upcomingBirthdays,
appointmentsByDay, appointmentsByDay,
chartData,
chartOptions,
}; };
}; };

View File

@ -1,11 +1,10 @@
import {Button, Modal, Row, Typography} from "antd"; import {Button, Modal, Row, Typography} from "antd";
import useAppointmentViewUI from "./useAppointmentViewUI.js"; import useAppointmentViewUI from "./useAppointmentViewUI.js";
import PropTypes from "prop-types";
import dayjs from "dayjs"; import dayjs from "dayjs";
const AppointmentViewModal = ({visible, onCancel}) => { const AppointmentViewModal = () => {
const appointmentViewModalUI = useAppointmentViewUI(visible, onCancel); const appointmentViewModalUI = useAppointmentViewUI();
if (!appointmentViewModalUI.selectedAppointment) { if (!appointmentViewModalUI.selectedAppointment) {
return null; return null;
@ -15,8 +14,8 @@ const AppointmentViewModal = ({visible, onCancel}) => {
<> <>
<Modal <Modal
title="Просмотр приема" title="Просмотр приема"
open={visible} open={appointmentViewModalUI.visible}
onCancel={onCancel} onCancel={appointmentViewModalUI.onCancel}
footer={null} footer={null}
width={appointmentViewModalUI.modalWidth} width={appointmentViewModalUI.modalWidth}
> >
@ -52,10 +51,11 @@ const AppointmentViewModal = ({visible, onCancel}) => {
<p> <p>
<b>Результаты приема:</b> <b>Результаты приема:</b>
</p> </p>
<div dangerouslySetInnerHTML={{__html: appointmentViewModalUI.selectedAppointment.results || "Не указаны"}}/> <div
dangerouslySetInnerHTML={{__html: appointmentViewModalUI.selectedAppointment.results || "Не указаны"}}/>
</div> </div>
<Row justify="end" style={appointmentViewModalUI.footerRowStyle}> <Row justify="end" style={appointmentViewModalUI.footerRowStyle}>
<Button style={appointmentViewModalUI.footerButtonStyle} onClick={onCancel}> <Button style={appointmentViewModalUI.footerButtonStyle} onClick={appointmentViewModalUI.onCancel}>
Закрыть Закрыть
</Button> </Button>
</Row> </Row>
@ -64,9 +64,5 @@ const AppointmentViewModal = ({visible, onCancel}) => {
); );
}; };
AppointmentViewModal.propTypes = {
visible: PropTypes.bool.isRequired,
onCancel: PropTypes.func.isRequired,
};
export default AppointmentViewModal; export default AppointmentViewModal;

View File

@ -1,4 +1,7 @@
import {useDispatch, useSelector} from "react-redux"; import {useDispatch, useSelector} from "react-redux";
import {
setSelectedAppointment
} from "../../../Redux/Slices/appointmentsSlice.js";
const useAppointmentViewUI = () => { const useAppointmentViewUI = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -10,7 +13,11 @@ const useAppointmentViewUI = () => {
const blockStyle = {marginBottom: 16}; const blockStyle = {marginBottom: 16};
const footerRowStyle = {marginTop: 16}; const footerRowStyle = {marginTop: 16};
const footerButtonStyle = {marginRight: 8}; const footerButtonStyle = {marginRight: 8};
const visible = !!selectedAppointment;
const onCancel = () => {
dispatch(setSelectedAppointment(null));
};
const getDateString = (date) => { const getDateString = (date) => {
return new Date(date).toLocaleDateString('ru-RU'); return new Date(date).toLocaleDateString('ru-RU');
@ -22,7 +29,9 @@ const useAppointmentViewUI = () => {
footerRowStyle, footerRowStyle,
footerButtonStyle, footerButtonStyle,
selectedAppointment, selectedAppointment,
visible,
getDateString, getDateString,
onCancel,
}; };
}; };

View File

@ -3,7 +3,7 @@ import dayjs from "dayjs";
import useScheduledAppointmentsViewModal from "./useScheduledAppointmentsViewModal.js"; import useScheduledAppointmentsViewModal from "./useScheduledAppointmentsViewModal.js";
import useScheduledAppointmentsViewModalUI from "./useScheduledAppointmentsViewModalUI.js"; import useScheduledAppointmentsViewModalUI from "./useScheduledAppointmentsViewModalUI.js";
import { useDispatch } from "react-redux"; import { useDispatch } from "react-redux";
import {openModalWithScheduledData} from "../../../../../Redux/Slices/appointmentsSlice.js"; import {openModalWithScheduledData} from "../../../Redux/Slices/appointmentsSlice.js";
const ScheduledAppointmentsViewModal = () => { const ScheduledAppointmentsViewModal = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();

View File

@ -1,4 +1,4 @@
import {useCancelScheduledAppointmentMutation} from "../../../../../Api/scheduledAppointmentsApi.js"; import {useCancelScheduledAppointmentMutation} from "../../../Api/scheduledAppointmentsApi.js";
const useScheduledAppointmentsViewModal = () => { const useScheduledAppointmentsViewModal = () => {

View File

@ -1,6 +1,6 @@
import {useDispatch, useSelector} from "react-redux"; import {useDispatch, useSelector} from "react-redux";
import dayjs from "dayjs"; import dayjs from "dayjs";
import {setSelectedScheduledAppointment} from "../../../../../Redux/Slices/appointmentsSlice.js"; import {setSelectedScheduledAppointment} from "../../../Redux/Slices/appointmentsSlice.js";
import {notification} from "antd"; import {notification} from "antd";