вынесс проп-типы в отдельный слой приложения и убрал повторения кода

This commit is contained in:
Андрей Дувакин 2025-03-17 20:05:26 +05:00
parent 2372083afe
commit 396aee3ab7
33 changed files with 399 additions and 507 deletions

View File

@ -17,7 +17,7 @@ class ScheduledAppointmentsRepository:
.options(joinedload(ScheduledAppointment.type)) .options(joinedload(ScheduledAppointment.type))
.options(joinedload(ScheduledAppointment.patient)) .options(joinedload(ScheduledAppointment.patient))
.options(joinedload(ScheduledAppointment.doctor)) .options(joinedload(ScheduledAppointment.doctor))
.order_by(desc(ScheduledAppointment.appointment_datetime)) .order_by(desc(ScheduledAppointment.scheduled_datetime))
) )
result = await self.db.execute(stmt) result = await self.db.execute(stmt)
return result.scalars().all() return result.scalars().all()
@ -29,7 +29,7 @@ class ScheduledAppointmentsRepository:
.options(joinedload(ScheduledAppointment.patient)) .options(joinedload(ScheduledAppointment.patient))
.options(joinedload(ScheduledAppointment.doctor)) .options(joinedload(ScheduledAppointment.doctor))
.filter(ScheduledAppointment.doctor_id == doctor_id) .filter(ScheduledAppointment.doctor_id == doctor_id)
.order_by(desc(ScheduledAppointment.appointment_datetime)) .order_by(desc(ScheduledAppointment.scheduled_datetime))
) )
result = await self.db.execute(stmt) result = await self.db.execute(stmt)
return result.scalars().all() return result.scalars().all()
@ -41,7 +41,7 @@ class ScheduledAppointmentsRepository:
.options(joinedload(ScheduledAppointment.patient)) .options(joinedload(ScheduledAppointment.patient))
.options(joinedload(ScheduledAppointment.doctor)) .options(joinedload(ScheduledAppointment.doctor))
.filter(ScheduledAppointment.patient_id == patient_id) .filter(ScheduledAppointment.patient_id == patient_id)
.order_by(desc(ScheduledAppointment.appointment_datetime)) .order_by(desc(ScheduledAppointment.scheduled_datetime))
) )
result = await self.db.execute(stmt) result = await self.db.execute(stmt)
return result.scalars().all() return result.scalars().all()

View File

@ -2,6 +2,7 @@ import {BuildOutlined, TableOutlined} from "@ant-design/icons";
import {Select, Tooltip} from "antd"; import {Select, Tooltip} from "antd";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import {cacheInfo} from "../utils/cachedInfoUtils.jsx"; import {cacheInfo} from "../utils/cachedInfoUtils.jsx";
import {ViewModPropType} from "../types/viewModPropType.jsx";
const {Option} = Select; const {Option} = Select;
@ -35,11 +36,7 @@ SelectViewMode.propTypes = {
setViewMode: PropTypes.func.isRequired, setViewMode: PropTypes.func.isRequired,
localStorageKey: PropTypes.string.isRequired, localStorageKey: PropTypes.string.isRequired,
toolTipText: PropTypes.string.isRequired, toolTipText: PropTypes.string.isRequired,
viewModes: PropTypes.arrayOf(PropTypes.shape({ viewModes: PropTypes.arrayOf(ViewModPropType).isRequired,
value: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
icon: PropTypes.element,
})).isRequired,
}; };
export default SelectViewMode; export default SelectViewMode;

View File

@ -0,0 +1,7 @@
const AppointmentCellViewModal = ({appointment}) => {
};

View File

@ -0,0 +1,109 @@
import {useEffect, useRef, useState} from "react";
import {Badge, Col, Tag, Tooltip, Typography} from "antd";
import dayjs from "dayjs";
import PropTypes from "prop-types";
import {AppointmentPropType} from "../../types/appointmentPropType.jsx";
import {ScheduledAppointmentPropType} from "../../types/scheduledAppointmentPropType.jsx";
const CalendarCell = ({appointments, scheduledAppointments, onCellClick, onItemClick}) => {
const containerRef = useRef(null);
const [isCompressed, setIsCompressed] = useState(false);
const COMPRESSION_THRESHOLD = 70;
useEffect(() => {
if (!containerRef.current) return;
const observer = new ResizeObserver((entries) => {
const width = entries[0].contentRect.width;
setIsCompressed(width < COMPRESSION_THRESHOLD);
});
observer.observe(containerRef.current);
return () => observer.disconnect();
}, []);
return (
<div
ref={containerRef}
onClick={isCompressed ? onCellClick : undefined}
style={{
height: '100%',
cursor: isCompressed ? 'pointer' : 'default',
position: 'relative',
}}
>
{!isCompressed && (
<ul style={{padding: 0, margin: 0}}>
{appointments.map(app => (
<Col
key={app.id}
>
<Tooltip
title={`Прошедший прием: ${dayjs(app.appointment_datetime).format('HH:mm')}`}
>
<Tag
color="green"
onClick={(e) => {
e.stopPropagation();
onItemClick(app);
}}
style={{margin: '2px 0', cursor: 'pointer'}}
>
<Badge
status="success"
text={dayjs(app.appointment_datetime).format('HH:mm')}
/>
</Tag>
</Tooltip>
</Col>
))}
{scheduledAppointments.map(app => (
<Col
key={app.id}
>
<Tooltip
title={`Запланированный прием: ${dayjs(app.scheduled_datetime).format('HH:mm')}`}
>
<Tag
color="blue"
onClick={(e) => {
e.stopPropagation();
onItemClick(app);
}}
style={{margin: '2px 0', cursor: 'pointer'}}
>
<Badge
status="processing"
text={dayjs(app.scheduled_datetime).format('HH:mm')}
/>
</Tag>
</Tooltip>
</Col>
))}
</ul>
)}
{isCompressed && (
<div style={{
position: 'absolute',
top: 2,
right: 2,
fontSize: 10,
fontWeight: 'bold',
color: '#1890ff'
}}>
{appointments.length + scheduledAppointments.length > 0 && `+${appointments.length + scheduledAppointments.length}`}
</div>
)}
</div>
);
};
CalendarCell.propTypes = {
appointments: PropTypes.arrayOf(AppointmentPropType).isRequired,
scheduledAppointments: PropTypes.arrayOf(ScheduledAppointmentPropType).isRequired,
onCellClick: PropTypes.func.isRequired,
onItemClick: PropTypes.func.isRequired,
};
export default CalendarCell;

View File

@ -36,27 +36,13 @@ const LensIssueFormModal = ({visible, onCancel, onSubmit}) => {
}, [visible]); }, [visible]);
const fetchPatients = async () => { const fetchPatients = async () => {
try {
const data = await getAllPatients(api); const data = await getAllPatients(api);
setPatients(data); setPatients(data);
} catch (error) {
console.error(error);
notification.error({
message: "Ошибка загрузки пациентов", description: "Проверьте подключение к сети.",
});
}
}; };
const fetchLenses = async () => { const fetchLenses = async () => {
try {
const data = await getNotIssuedLenses(api); const data = await getNotIssuedLenses(api);
setLenses(data); setLenses(data);
} catch (error) {
console.error(error);
notification.error({
message: "Ошибка загрузки линз", description: "Проверьте подключение к сети.",
});
}
}; };
const handleOk = async () => { const handleOk = async () => {
@ -324,7 +310,9 @@ const LensIssueFormModal = ({visible, onCancel, onSubmit}) => {
}; };
LensIssueFormModal.propTypes = { LensIssueFormModal.propTypes = {
visible: PropTypes.bool.isRequired, onCancel: PropTypes.func.isRequired, onSubmit: PropTypes.func.isRequired, visible: PropTypes.bool.isRequired,
onCancel: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
}; };
export default LensIssueFormModal; export default LensIssueFormModal;

View File

@ -1,5 +1,6 @@
import {Collapse, Modal} from "antd"; import {Collapse, Modal} from "antd";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import {LensIssuePropType} from "../../types/lensIssuePropType.jsx";
const LensIssueViewModal = ({visible, onCancel, lensIssue}) => { const LensIssueViewModal = ({visible, onCancel, lensIssue}) => {
@ -69,39 +70,9 @@ const LensIssueViewModal = ({visible, onCancel, lensIssue}) => {
}; };
LensIssueViewModal.propTypes = { LensIssueViewModal.propTypes = {
visible: PropTypes.bool, visible: PropTypes.bool.isRequired,
onCancel: PropTypes.func, onCancel: PropTypes.func.isRequired,
lensIssue: PropTypes.shape({ lensIssue: LensIssuePropType.isRequired,
issue_date: PropTypes.string,
patient: PropTypes.shape({
first_name: PropTypes.string,
last_name: PropTypes.string,
patronymic: PropTypes.string,
birthday: PropTypes.string,
address: PropTypes.string,
email: PropTypes.string,
phone: PropTypes.string,
diagnosis: PropTypes.string,
correction: PropTypes.string,
}),
doctor: PropTypes.shape({
last_name: PropTypes.string,
first_name: PropTypes.string,
login: PropTypes.string,
}),
lens: PropTypes.shape({
id: PropTypes.number.isRequired,
tor: PropTypes.number.isRequired,
diameter: PropTypes.number.isRequired,
esa: PropTypes.number.isRequired,
fvc: PropTypes.number.isRequired,
preset_refraction: PropTypes.number.isRequired,
periphery_toricity: PropTypes.number.isRequired,
side: PropTypes.string.isRequired,
issued: PropTypes.bool.isRequired,
trial: PropTypes.number.isRequired,
}),
}),
}; };
export default LensIssueViewModal; export default LensIssueViewModal;

View File

@ -3,6 +3,7 @@ import {useEffect, useState} from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import getAllLensTypes from "../../api/lens_types/getAllLensTypes.jsx"; import getAllLensTypes from "../../api/lens_types/getAllLensTypes.jsx";
import {useAuth} from "../../AuthContext.jsx"; import {useAuth} from "../../AuthContext.jsx";
import {LensPropType} from "../../types/lensPropType.jsx";
const LensFormModal = ({visible, onCancel, onSubmit, lens}) => { const LensFormModal = ({visible, onCancel, onSubmit, lens}) => {
@ -26,17 +27,8 @@ const LensFormModal = ({visible, onCancel, onSubmit, lens}) => {
}, [visible, lens]); }, [visible, lens]);
const fetchLensTypes = async () => { const fetchLensTypes = async () => {
try {
const data = await getAllLensTypes(api); const data = await getAllLensTypes(api);
setLensTypes(data); setLensTypes(data);
} catch (error) {
console.log(error);
notification.error({
message: "Ошибка загрузки типов линз",
description: "Проверьте подключение к сети.",
placement: "topRight",
});
}
}; };
const handleOk = async () => { const handleOk = async () => {
@ -199,18 +191,7 @@ LensFormModal.propTypes = {
visible: PropTypes.bool.isRequired, visible: PropTypes.bool.isRequired,
onCancel: PropTypes.func.isRequired, onCancel: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired, onSubmit: PropTypes.func.isRequired,
lens: PropTypes.shape({ lens: LensPropType.isRequired,
tor: PropTypes.number.isRequired,
trial: PropTypes.number.isRequired,
esa: PropTypes.number.isRequired,
fvc: PropTypes.number.isRequired,
preset_refraction: PropTypes.number.isRequired,
diameter: PropTypes.number.isRequired,
periphery_toricity: PropTypes.number.isRequired,
side: PropTypes.string.isRequired,
issued: PropTypes.bool.isRequired,
type_id: PropTypes.number.isRequired,
}),
} }
export default LensFormModal; export default LensFormModal;

View File

@ -3,6 +3,7 @@ import PropTypes from "prop-types";
import {DeleteOutlined, EditOutlined, EyeOutlined} from "@ant-design/icons"; import {DeleteOutlined, EditOutlined, EyeOutlined} from "@ant-design/icons";
import {useState} from "react"; import {useState} from "react";
import LensViewModal from "./LensViewModal.jsx"; import LensViewModal from "./LensViewModal.jsx";
import {LensPropType} from "../../types/lensPropType.jsx";
const LensListCard = ({lens, handleEditLens, handleDeleteLens}) => { const LensListCard = ({lens, handleEditLens, handleDeleteLens}) => {
const [showModalInfo, setShowModalInfo] = useState(false); const [showModalInfo, setShowModalInfo] = useState(false);
@ -61,18 +62,7 @@ const LensListCard = ({lens, handleEditLens, handleDeleteLens}) => {
}; };
LensListCard.propTypes = { LensListCard.propTypes = {
lens: PropTypes.shape({ lens: LensPropType.isRequired,
id: PropTypes.number.isRequired,
tor: PropTypes.number.isRequired,
trial: PropTypes.number,
esa: PropTypes.number,
fvc: PropTypes.number,
preset_refraction: PropTypes.number.isRequired,
diameter: PropTypes.number.isRequired,
periphery_toricity: PropTypes.number.isRequired,
side: PropTypes.string.isRequired,
issued: PropTypes.bool.isRequired,
}).isRequired,
handleEditLens: PropTypes.func.isRequired, handleEditLens: PropTypes.func.isRequired,
handleDeleteLens: PropTypes.func.isRequired, handleDeleteLens: PropTypes.func.isRequired,
}; };

View File

@ -1,5 +1,6 @@
import {Button, Col, Modal, Row, Typography} from "antd"; import {Button, Col, Modal, Row, Typography} from "antd";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import {LensPropType} from "../../types/lensPropType.jsx";
const {Text, Title} = Typography; const {Text, Title} = Typography;
@ -72,17 +73,7 @@ const LensViewModal = ({visible, onCancel, lens}) => {
LensViewModal.propTypes = { LensViewModal.propTypes = {
visible: PropTypes.bool.isRequired, visible: PropTypes.bool.isRequired,
onCancel: PropTypes.func.isRequired, onCancel: PropTypes.func.isRequired,
lens: PropTypes.shape({ lens: LensPropType.isRequired,
tor: PropTypes.number.isRequired,
diameter: PropTypes.number.isRequired,
esa: PropTypes.number.isRequired,
fvc: PropTypes.number.isRequired,
preset_refraction: PropTypes.number.isRequired,
periphery_toricity: PropTypes.number.isRequired,
side: PropTypes.string.isRequired,
issued: PropTypes.bool.isRequired,
trial: PropTypes.number.isRequired,
}),
}; };
export default LensViewModal; export default LensViewModal;

View File

@ -5,6 +5,7 @@ import locale from "antd/es/date-picker/locale/ru_RU";
import validator from "validator"; import validator from "validator";
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.jsx";
const {TextArea} = Input; const {TextArea} = Input;
@ -141,17 +142,7 @@ PatientFormModal.propTypes = {
visible: PropTypes.bool.isRequired, visible: PropTypes.bool.isRequired,
onCancel: PropTypes.func.isRequired, onCancel: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired, onSubmit: PropTypes.func.isRequired,
patient: PropTypes.shape({ patient: PatientPropType.isRequired,
first_name: PropTypes.string,
last_name: PropTypes.string,
patronymic: PropTypes.string,
birthday: PropTypes.string,
address: PropTypes.string,
email: PropTypes.string,
phone: PropTypes.string,
diagnosis: PropTypes.string,
correction: PropTypes.string,
}),
}; };
export default PatientFormModal; export default PatientFormModal;

View File

@ -3,6 +3,7 @@ import PropTypes from "prop-types";
import {DeleteOutlined, EditOutlined, EyeOutlined} from "@ant-design/icons"; import {DeleteOutlined, EditOutlined, EyeOutlined} from "@ant-design/icons";
import {useState} from "react"; import {useState} from "react";
import PatientViewModal from "./PatientViewModal.jsx"; import PatientViewModal from "./PatientViewModal.jsx";
import {PatientPropType} from "../../types/patientPropType.jsx";
const PatientListCard = ({patient, handleEditPatient, handleDeletePatient}) => { const PatientListCard = ({patient, handleEditPatient, handleDeletePatient}) => {
const [showModalInfo, setShowModalInfo] = useState(false); const [showModalInfo, setShowModalInfo] = useState(false);
@ -73,18 +74,7 @@ const PatientListCard = ({patient, handleEditPatient, handleDeletePatient}) => {
}; };
PatientListCard.propTypes = { PatientListCard.propTypes = {
patient: PropTypes.shape({ patient: PatientPropType.isRequired,
id: PropTypes.number.isRequired,
last_name: PropTypes.string.isRequired,
first_name: PropTypes.string.isRequired,
patronymic: PropTypes.string,
birthday: PropTypes.string.isRequired,
address: PropTypes.string,
email: PropTypes.string,
phone: PropTypes.string,
diagnosis: PropTypes.string,
correction: PropTypes.string,
}).isRequired,
handleEditPatient: PropTypes.func.isRequired, handleEditPatient: PropTypes.func.isRequired,
handleDeletePatient: PropTypes.func.isRequired, handleDeletePatient: PropTypes.func.isRequired,
}; };

View File

@ -1,5 +1,6 @@
import {Button, Col, Modal, Row, Typography, Divider} from "antd"; import {Button, Col, Modal, Row, Typography, Divider} from "antd";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import {PatientPropType} from "../../types/patientPropType.jsx";
const { Text, Title } = Typography; const { Text, Title } = Typography;
@ -72,17 +73,7 @@ const PatientViewModal = ({ visible, onCancel, patient }) => {
PatientViewModal.propTypes = { PatientViewModal.propTypes = {
visible: PropTypes.bool.isRequired, visible: PropTypes.bool.isRequired,
onCancel: PropTypes.func.isRequired, onCancel: PropTypes.func.isRequired,
patient: PropTypes.shape({ patient: PatientPropType.isRequired,
first_name: PropTypes.string,
last_name: PropTypes.string,
patronymic: PropTypes.string,
birthday: PropTypes.string,
address: PropTypes.string,
email: PropTypes.string,
phone: PropTypes.string,
diagnosis: PropTypes.string,
correction: PropTypes.string,
}),
}; };
export default PatientViewModal; export default PatientViewModal;

View File

@ -6,6 +6,7 @@ import getAllLensTypes from "../../api/lens_types/getAllLensTypes.jsx";
import {useAuth} from "../../AuthContext.jsx"; import {useAuth} from "../../AuthContext.jsx";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import getSetContentBySetId from "../../api/set_content/getSetContentBySetId.jsx"; import getSetContentBySetId from "../../api/set_content/getSetContentBySetId.jsx";
import {SetPropType} from "../../types/setPropType.jsx";
const {Option} = Select; const {Option} = Select;
@ -32,31 +33,13 @@ const SetFormModal = ({visible, onCancel, setData, onSubmit}) => {
const fetchSetContents = async () => { const fetchSetContents = async () => {
if (!setData) return; if (!setData) return;
try {
const data = await getSetContentBySetId(api, setData.id); const data = await getSetContentBySetId(api, setData.id);
setContent(data); setContent(data);
} catch (error) {
console.log(error);
notification.error({
message: "Ошибка загрузки контента",
description: "Проверьте подключение к сети.",
placement: "topRight",
});
}
}; };
const fetchLensTypes = async () => { const fetchLensTypes = async () => {
try {
const data = await getAllLensTypes(api); const data = await getAllLensTypes(api);
setLensTypes(data); setLensTypes(data);
} catch (error) {
console.log(error);
notification.error({
message: "Ошибка загрузки типов линз",
description: "Проверьте подключение к сети.",
placement: "topRight",
});
}
}; };
const addContentItem = () => { const addContentItem = () => {
@ -272,10 +255,7 @@ SetFormModal.propTypes = {
visible: PropTypes.bool.isRequired, visible: PropTypes.bool.isRequired,
onCancel: PropTypes.func.isRequired, onCancel: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired, onSubmit: PropTypes.func.isRequired,
setData: PropTypes.shape({ setData: SetPropType.isRequired,
id: PropTypes.number,
title: PropTypes.string,
}),
} }
export default SetFormModal; export default SetFormModal;

View File

@ -1,6 +1,7 @@
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import {Card, Modal, Popconfirm, Tooltip} from "antd"; import {Card, Modal, Popconfirm, Tooltip} from "antd";
import {DeleteOutlined, EditOutlined, PlusOutlined} from "@ant-design/icons"; import {DeleteOutlined, EditOutlined, PlusOutlined} from "@ant-design/icons";
import {SetPropType} from "../../types/setPropType.jsx";
const SetListCard = ({set, handleEditSet, handleDeleteSet, handleAppendSet}) => { const SetListCard = ({set, handleEditSet, handleDeleteSet, handleAppendSet}) => {
const deleteSet = () => { const deleteSet = () => {
@ -59,10 +60,7 @@ const SetListCard = ({set, handleEditSet, handleDeleteSet, handleAppendSet}) =>
}; };
SetListCard.propTypes = { SetListCard.propTypes = {
set: PropTypes.shape({ set: SetPropType.isRequired,
id: PropTypes.number.isRequired,
title: PropTypes.string.isRequired,
}).isRequired,
handleEditSet: PropTypes.func.isRequired, handleEditSet: PropTypes.func.isRequired,
handleAppendSet: PropTypes.func.isRequired, handleAppendSet: PropTypes.func.isRequired,
handleDeleteSet: PropTypes.func.isRequired, handleDeleteSet: PropTypes.func.isRequired,

View File

@ -1,5 +1,5 @@
import {useEffect, useState} from "react"; import {useEffect, useState} from "react";
import {Button, Grid, notification, Tabs, Typography} from "antd"; import {Button, Grid, Tabs, Typography} from "antd";
import {Splitter} from "antd"; import {Splitter} from "antd";
import { import {
CalendarOutlined, TableOutlined, MenuFoldOutlined, MenuUnfoldOutlined, CalendarOutlined, TableOutlined, MenuFoldOutlined, MenuUnfoldOutlined,
@ -62,17 +62,9 @@ const AppointmentsLayout = () => {
}; };
const fetchAppointments = async () => { const fetchAppointments = async () => {
try {
const data = await getAllAppointments(api); const data = await getAllAppointments(api);
setAppointments(data); setAppointments(data);
cacheInfo("appointmentsData", data); cacheInfo("appointmentsData", data);
} catch (error) {
console.log(error);
notification.error({
message: "Ошибка загрузки данных", description: "Проверьте подключение к сети.", placement: "topRight",
});
}
}; };
const fetchScheduledAppointmentsWithCache = async () => { const fetchScheduledAppointmentsWithCache = async () => {
@ -88,17 +80,9 @@ const AppointmentsLayout = () => {
}; };
const fetchScheduledAppointments = async () => { const fetchScheduledAppointments = async () => {
try {
const data = await getAllScheduledAppointments(api); const data = await getAllScheduledAppointments(api);
setScheduledAppointments(data); setScheduledAppointments(data);
cacheInfo("scheduledAppointmentsData", data); cacheInfo("scheduledAppointmentsData", data);
} catch (error) {
console.log(error);
notification.error({
message: "Ошибка загрузки данных", description: "Проверьте подключение к сети.", placement: "topRight",
});
}
}; };
const items = [{ const items = [{

View File

@ -92,39 +92,20 @@ const IssuesPage = () => {
}; };
const handleSubmitFormModal = async (issue_date, patient_id, lens_id) => { const handleSubmitFormModal = async (issue_date, patient_id, lens_id) => {
try {
await addLensIssue(api, {issue_date, patient_id, lens_id});
setIsModalVisible(false); setIsModalVisible(false);
await addLensIssue(api, {issue_date, patient_id, lens_id});
notification.success({ notification.success({
message: "Линза выдана", message: "Линза выдана",
description: "Линза успешно выдана пациенту.", description: "Линза успешно выдана пациенту.",
placement: "topRight", placement: "topRight",
}); });
await fetchLensIssues(); await fetchLensIssues();
} catch (error) {
console.log(error);
notification.error({
message: "Ошибка добавления",
description: "Не удалось выдать линзу пациенту.",
placement: "topRight",
});
}
}; };
const fetchLensIssues = async () => { const fetchLensIssues = async () => {
try {
const data = await getAllLensIssues(api); const data = await getAllLensIssues(api);
setLensIssues(data); setLensIssues(data);
setLoading(false); setLoading(false);
} catch (error) {
console.log(error);
notification.error({
message: "Ошибка загрузки данных",
description: "Проверьте подключение к сети.",
placement: "topRight",
});
setLoading(false);
}
}; };
const handleSearch = (e) => { const handleSearch = (e) => {

View File

@ -75,20 +75,10 @@ const PatientsPage = () => {
}; };
const fetchPatients = async () => { const fetchPatients = async () => {
try {
const data = await getAllPatients(api); const data = await getAllPatients(api);
setPatients(data); setPatients(data);
cacheInfo("patientsData", data); cacheInfo("patientsData", data);
} catch (error) {
console.log(error);
notification.error({
message: "Ошибка загрузки данных",
description: "Проверьте подключение к сети.",
placement: "topRight",
})
}
setLoading(false); setLoading(false);
}; };
@ -124,7 +114,6 @@ const PatientsPage = () => {
}; };
const handleDeletePatient = async (patient_id) => { const handleDeletePatient = async (patient_id) => {
try {
await deletePatient(api, patient_id); await deletePatient(api, patient_id);
await fetchPatients(); await fetchPatients();
notification.success({ notification.success({
@ -132,14 +121,6 @@ const PatientsPage = () => {
description: "Пациент успешно удалён из базы.", description: "Пациент успешно удалён из базы.",
placement: "topRight", placement: "topRight",
}); });
} catch (error) {
console.log(error);
notification.error({
message: "Ошибка удаления",
description: "Не удалось удалить пациента.",
placement: "topRight",
});
}
}; };
const handleCancel = () => { const handleCancel = () => {
@ -147,24 +128,14 @@ const PatientsPage = () => {
}; };
const handleModalPatientSubmit = async (newPatient) => { const handleModalPatientSubmit = async (newPatient) => {
try { setIsModalVisible(false);
if (selectedPatient) { if (selectedPatient) {
await editPatient(newPatient); await editPatient(newPatient);
} else { } else {
await addNewPatient(newPatient); await addNewPatient(newPatient);
} }
setIsModalVisible(false);
await fetchPatients(); await fetchPatients();
} catch (error) {
console.log(error);
notification.error({
message: "Ошибка",
description: error.response?.status === 401
? "Ошибка авторизации: пользователь не найден или токен недействителен"
: "Не удалось сохранить данные пациента.",
placement: "topRight",
});
}
}; };
const editPatient = async (patient) => { const editPatient = async (patient) => {
@ -330,6 +301,7 @@ const PatientsPage = () => {
allowClear allowClear
/> />
</Col> </Col>
{viewMode === "tile" && ( {viewMode === "tile" && (
<Col xs={24} md={5} sm={6} xl={3} xxl={2}> <Col xs={24} md={5} sm={6} xl={3} xxl={2}>
<Tooltip <Tooltip

View File

@ -1,10 +1,13 @@
import {Calendar, Grid, ConfigProvider, Badge, Modal} from "antd"; import {Calendar, Grid, ConfigProvider, Badge, Modal, Tag, Tooltip} from "antd";
import {useState} from "react"; import {useState} from "react";
import dayjs from "dayjs"; import dayjs from "dayjs";
import 'dayjs/locale/ru'; import 'dayjs/locale/ru';
import locale from 'antd/es/locale/ru_RU'; import locale from 'antd/es/locale/ru_RU';
import updateLocale from 'dayjs/plugin/updateLocale'; import updateLocale from 'dayjs/plugin/updateLocale';
import PropTypes from "prop-types"; import PropTypes, {arrayOf} from "prop-types";
import CalendarCell from "../../components/appointments/CalendarCell.jsx";
import {AppointmentPropType} from "../../types/appointmentPropType.jsx";
import {ScheduledAppointmentPropType} from "../../types/scheduledAppointmentPropType.jsx";
const {useBreakpoint} = Grid; const {useBreakpoint} = Grid;
@ -18,6 +21,7 @@ const AppointmentsCalendarPage = ({appointments, scheduledAppointments}) => {
const [selectedDate, setSelectedDate] = useState(dayjs(new Date())); const [selectedDate, setSelectedDate] = useState(dayjs(new Date()));
const [modalVisible, setModalVisible] = useState(false); const [modalVisible, setModalVisible] = useState(false);
const [selectedAppointments, setSelectedAppointments] = useState([]); const [selectedAppointments, setSelectedAppointments] = useState([]);
const [selectedAppointment, setSelectedAppointment] = useState(null);
const dateCellRender = (value) => { const dateCellRender = (value) => {
const date = value.format('YYYY-MM-DD'); const date = value.format('YYYY-MM-DD');
@ -29,19 +33,14 @@ const AppointmentsCalendarPage = ({appointments, scheduledAppointments}) => {
); );
return ( return (
<ul style={{listStyle: 'none', padding: 0}}> <CalendarCell
{appointmentsForDate.map(app => ( appointments={appointmentsForDate}
<li key={app.id}> scheduledAppointments={scheduledForDate}
<Badge status="success" text={`Прием ${dayjs(app.appointment_datetime).format('HH:mm')}`}/> onCellClick={() => {
</li> }}
))} onItemClick={() => {
{scheduledForDate.map(app => ( }}
<li key={app.id}> />
<Badge status="processing"
text={`Запланировано ${dayjs(app.scheduled_datetime).format('HH:mm')}`}/>
</li>
))}
</ul>
); );
}; };
@ -67,83 +66,15 @@ const AppointmentsCalendarPage = ({appointments, scheduledAppointments}) => {
onSelect={onSelect} onSelect={onSelect}
cellRender={dateCellRender} cellRender={dateCellRender}
/> />
<Modal
title={`Приемы на ${selectedDate.format('DD.MM.YYYY')}`}
open={modalVisible}
onCancel={() => setModalVisible(false)}
footer={null}
>
{selectedAppointments.map(app => (
<div key={app.id}>
<p>{app.appointment_datetime ? 'Прием' : 'Запланировано'}: {dayjs(app.appointment_datetime || app.scheduled_datetime).format('HH:mm')}</p>
<p>Пациент: {app.patient?.name || 'Не указан'}</p>
<p>Врач: {app.doctor?.name || 'Не указан'}</p>
<p>Тип: {app.type?.name || 'Не указан'}</p>
{app.results && <p>Результаты: {app.results}</p>}
<hr/>
</div>
))}
</Modal>
</div> </div>
</ConfigProvider> </ConfigProvider>
); );
}; };
AppointmentsCalendarPage.propTypes = { AppointmentsCalendarPage.propTypes = {
appointments: PropTypes.arrayOf( appointments: PropTypes.arrayOf(AppointmentPropType).isRequired,
PropTypes.shape({ scheduledAppointments: PropTypes.arrayOf(ScheduledAppointmentPropType).isRequired,
id: PropTypes.number.isRequired,
results: PropTypes.string,
days_until_the_next_appointment: PropTypes.number,
appointment_datetime: PropTypes.string.isRequired,
patient: PropTypes.shape({
first_name: PropTypes.string,
last_name: PropTypes.string,
patronymic: PropTypes.string,
birthday: PropTypes.string,
address: PropTypes.string,
email: PropTypes.string,
phone: PropTypes.string,
diagnosis: PropTypes.string,
correction: PropTypes.string,
}),
doctor: PropTypes.shape({
last_name: PropTypes.string,
first_name: PropTypes.string,
login: PropTypes.string,
}),
type: PropTypes.shape({
title: PropTypes.string,
})
})
),
scheduledAppointments: PropTypes.arrayOf(
PropTypes.shape(
{
id: PropTypes.number.isRequired,
scheduled_datetime: PropTypes.string.isRequired,
patient: PropTypes.shape({
first_name: PropTypes.string,
last_name: PropTypes.string,
patronymic: PropTypes.string,
birthday: PropTypes.string,
address: PropTypes.string,
email: PropTypes.string,
phone: PropTypes.string,
diagnosis: PropTypes.string,
correction: PropTypes.string,
}),
doctor: PropTypes.shape({
last_name: PropTypes.string,
first_name: PropTypes.string,
login: PropTypes.string,
}),
type: PropTypes.shape({
title: PropTypes.string,
}),
}
)
)
}; };
export default AppointmentsCalendarPage; export default AppointmentsCalendarPage;

View File

@ -92,19 +92,9 @@ const LensesPage = () => {
}; };
const fetchLenses = async () => { const fetchLenses = async () => {
try {
const data = await getAllLenses(api); const data = await getAllLenses(api);
setLenses(data); setLenses(data);
setLoading(false); setLoading(false);
} catch (error) {
console.error("Ошибка загрузки линз:", error);
notification.error({
message: "Ошибка загрузки линз",
description: "Проверьте подключение к сети.",
placement: "topRight",
})
setLoading(false);
}
}; };
const fetchViewModeFromCache = () => { const fetchViewModeFromCache = () => {
@ -147,7 +137,6 @@ const LensesPage = () => {
}; };
const handleDeleteLens = async (lensId) => { const handleDeleteLens = async (lensId) => {
try {
await deleteLens(api, lensId); await deleteLens(api, lensId);
await fetchLenses(api); await fetchLenses(api);
notification.success({ notification.success({
@ -155,18 +144,10 @@ const LensesPage = () => {
description: "Линза успешно удалена.", description: "Линза успешно удалена.",
placement: "topRight", placement: "topRight",
}) })
} catch (error) {
console.error("Ошибка удаления линзы:", error);
notification.error({
message: "Ошибка удаления линзы",
description: "Проверьте подключение к сети.",
placement: "topRight",
});
}
}; };
const handleModalSubmit = async (lensData) => { const handleModalSubmit = async (lensData) => {
try { setIsModalVisible(false);
if (selectedLens) { if (selectedLens) {
await updateLens(api, selectedLens.id, lensData); await updateLens(api, selectedLens.id, lensData);
notification.success({ notification.success({
@ -182,16 +163,7 @@ const LensesPage = () => {
placement: "topRight", placement: "topRight",
}); });
} }
setIsModalVisible(false);
await fetchLenses(); await fetchLenses();
} catch (error) {
console.error("Ошибка сохранения линзы:", error);
notification.error({
message: "Ошибка сохранения линзы",
description: "Проверьте подключение к сети.",
placement: "topRight",
});
}
}; };
const toggleAdvancedSearch = () => { const toggleAdvancedSearch = () => {

View File

@ -53,23 +53,10 @@ const SetLensesPage = () => {
}; };
const fetchSets = async () => { const fetchSets = async () => {
try {
const data = await getAllSets(api); const data = await getAllSets(api);
setSets(data); setSets(data);
cacheInfo("setsData", data);
} catch (error) {
console.log(error);
notification.error({
message: "Ошибка загрузки данных",
description: "Проверьте подключение к сети.",
placement: "topRight",
})
}
if (loading) {
setLoading(false); setLoading(false);
} cacheInfo("setsData", data);
}; };
const filteredSets = sets.filter(set => set.title.toLowerCase().includes(searchText.toLowerCase())); const filteredSets = sets.filter(set => set.title.toLowerCase().includes(searchText.toLowerCase()));
@ -82,10 +69,9 @@ const SetLensesPage = () => {
const handleEditSet = (set) => { const handleEditSet = (set) => {
setSelectedSet(set); setSelectedSet(set);
setIsModalVisible(true); setIsModalVisible(true);
} };
const handleDeleteSet = async (set_id) => { const handleDeleteSet = async (set_id) => {
try {
await deleteSet(api, set_id); await deleteSet(api, set_id);
notification.success({ notification.success({
message: "Набор удален", message: "Набор удален",
@ -93,40 +79,24 @@ const SetLensesPage = () => {
placement: "topRight", placement: "topRight",
}); });
await fetchSets(); await fetchSets();
} catch (error) { };
console.error("Ошибка удаления набора:", error);
notification.error({
message: "Ошибка удаления набора",
description: "Проверьте подключение к сети.",
placement: "topRight",
});
}
}
const handleCancel = () => { const handleCancel = () => {
setIsModalVisible(false); setIsModalVisible(false);
}; };
const handleAppendSet = async (set) => { const handleAppendSet = async (set) => {
try {
await appendLensesFromSet(api, set.id); await appendLensesFromSet(api, set.id);
notification.success({ notification.success({
message: "Линзы добавлены", message: "Линзы добавлены",
description: "Линзы успешно добавлены.", description: "Линзы успешно добавлены.",
placement: "topRight", placement: "topRight",
}); });
} catch (error) {
console.error("Ошибка добавления линз:", error);
notification.error({
message: "Ошибка добавления линз",
description: "Проверьте подключение к сети.",
placement: "topRight",
});
}
}; };
const handleModalSetSubmit = async (set, content = []) => { const handleModalSetSubmit = async (set, content = []) => {
try { setIsModalVisible(false);
let refreshed_set; let refreshed_set;
if (selectedSet) { if (selectedSet) {
@ -141,43 +111,15 @@ const SetLensesPage = () => {
await setContent(content, refreshed_set.id); await setContent(content, refreshed_set.id);
} }
setIsModalVisible(false);
await fetchSets(); await fetchSets();
} catch (error) {
console.error("Ошибка сохранения набора:", error);
notification.error({
message: "Ошибка сохранения набора",
description: "Проверьте подключение к сети.",
placement: "topRight",
});
}
}; };
const setContent = async (content, set_id) => { const setContent = async (content, set_id) => {
try {
console.log(content);
await addSetContent(api, content, set_id); await addSetContent(api, content, set_id);
} catch (error) {
console.error("Ошибка сохранения набора:", error);
notification.error({
message: "Ошибка сохранения набора",
description: "Проверьте подключение к сети.",
placement: "topRight",
});
}
}; };
const updateContent = async (content, set_id) => { const updateContent = async (content, set_id) => {
try {
await updateSetContent(api, content, set_id); await updateSetContent(api, content, set_id);
} catch (error) {
console.error("Ошибка сохранения набора:", error);
notification.error({
message: "Ошибка сохранения набора",
description: "Проверьте подключение к сети.",
placement: "topRight",
});
}
}; };
const editCurrentSet = async (set) => { const editCurrentSet = async (set) => {

View File

@ -0,0 +1,17 @@
import PropTypes from "prop-types";
import {PatientPropType} from "./patientPropType.jsx";
import {UserPropType} from "./userPropType.jsx";
import {AppointmentTypePropType} from "./appointmentTypePropType.jsx";
export const AppointmentPropType = PropTypes.shape({
id: PropTypes.number,
results: PropTypes.string,
days_until_the_next_appointment: PropTypes.number,
appointment_datetime: PropTypes.string.isRequired,
patient_id: PropTypes.number.isRequired,
doctor_id: PropTypes.number.isRequired,
type_id: PropTypes.number.isRequired,
patient: PatientPropType,
doctor: UserPropType,
type: AppointmentTypePropType,
});

View File

@ -0,0 +1,6 @@
import PropTypes from "prop-types";
export const AppointmentTypePropType = PropTypes.shape({
id: PropTypes.number,
title: PropTypes.string.isRequired,
})

View File

@ -0,0 +1,15 @@
import PropTypes from "prop-types";
import {PatientPropType} from "./patientPropType.jsx";
import {UserPropType} from "./userPropType.jsx";
import {LensPropType} from "./lensPropType.jsx";
export const LensIssuePropType = PropTypes.shape({
id: PropTypes.number,
issue_date: PropTypes.string.isRequired,
patient_id: PropTypes.number.isRequired,
doctor_id: PropTypes.number,
lens_id: PropTypes.number.isRequired,
patient: PatientPropType,
doctor: UserPropType,
lens: LensPropType,
});

View File

@ -0,0 +1,15 @@
import PropTypes from "prop-types";
export const LensPropType = PropTypes.shape({
id: PropTypes.number,
tor: PropTypes.number.isRequired,
trial: PropTypes.number.isRequired,
esa: PropTypes.number.isRequired,
fvc: PropTypes.number.isRequired,
preset_refraction: PropTypes.number.isRequired,
diameter: PropTypes.number.isRequired,
periphery_toricity: PropTypes.number.isRequired,
side: PropTypes.string.isRequired,
issued: PropTypes.bool.isRequired,
type_id: PropTypes.number.isRequired,
});

View File

@ -0,0 +1,6 @@
import PropTypes from "prop-types";
export const LensTypePropType = PropTypes.shape({
id: PropTypes.number,
title: PropTypes.string.isRequired,
});

View File

@ -0,0 +1,14 @@
import PropTypes from "prop-types";
export const PatientPropType = PropTypes.shape({
id: PropTypes.number,
first_name: PropTypes.string.isRequired,
last_name: PropTypes.string.isRequired,
patronymic: PropTypes.string,
birthday: PropTypes.string.isRequired,
address: PropTypes.string,
email: PropTypes.string,
phone: PropTypes.string,
diagnosis: PropTypes.string,
correction: PropTypes.string,
});

View File

@ -0,0 +1,15 @@
import PropTypes from "prop-types";
import {PatientPropType} from "./patientPropType.jsx";
import {UserPropType} from "./userPropType.jsx";
import {AppointmentTypePropType} from "./appointmentTypePropType.jsx";
export const ScheduledAppointmentPropType = PropTypes.shape({
id: PropTypes.number,
scheduled_datetime: PropTypes.string.isRequired,
patient_id: PropTypes.number.isRequired,
doctor_id: PropTypes.number.isRequired,
type_id: PropTypes.number.isRequired,
patient: PatientPropType,
doctor: UserPropType,
type: AppointmentTypePropType,
})

View File

@ -0,0 +1,15 @@
import PropTypes from "prop-types";
export const SetContentPropType = PropTypes.shape({
id: PropTypes.number,
tor: PropTypes.number.isRequired,
trial: PropTypes.number.isRequired,
esa: PropTypes.number.isRequired,
fvc: PropTypes.number.isRequired,
preset_refraction: PropTypes.number.isRequired,
diameter: PropTypes.number.isRequired,
side: PropTypes.string.isRequired,
count: PropTypes.number.isRequired,
type_id: PropTypes.number.isRequired,
set_id: PropTypes.number,
});

View File

@ -0,0 +1,6 @@
import PropTypes from "prop-types";
export const SetPropType = PropTypes.shape({
id: PropTypes.number,
title: PropTypes.string.isRequired,
});

View File

@ -0,0 +1,10 @@
import PropTypes from "prop-types";
export const UserPropType = PropTypes.shape({
id: PropTypes.number,
first_name: PropTypes.string.isRequired,
last_name: PropTypes.string.isRequired,
patronymic: PropTypes.string,
login: PropTypes.string.isRequired,
role_id: PropTypes.number.isRequired,
});

View File

@ -0,0 +1,7 @@
import PropTypes from "prop-types";
export const ViewModPropType = PropTypes.shape({
value: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
icon: PropTypes.element,
});