сделал основу вкладки с наборами линз, а также методы API, и компонент модального окна

This commit is contained in:
Андрей Дувакин 2025-02-26 10:54:20 +05:00
parent b0bca54f22
commit 033776deb6
10 changed files with 598 additions and 60 deletions

View File

@ -2,9 +2,9 @@ import axios from "axios";
import CONFIG from "../../core/Config.jsx";
const addSetContent = async (token, set_content) => {
const addSetContent = async (token, set_content, set_id) => {
try {
const response = await axios.post(`${CONFIG.BASE_URL}/set_content/`, set_content, {
const response = await axios.post(`${CONFIG.BASE_URL}/set_content/${set_id}/`, set_content, {
headers: {
Authorization: `Bearer ${token}`,
},

View File

@ -2,9 +2,9 @@ import axios from "axios";
import CONFIG from "../../core/Config.jsx";
const AddSet = async (token, set) => {
const addSet = async (token, set) => {
try {
const response = await axios.post(`${CONFIG.API_URL}/sets`, set, {
const response = await axios.post(`${CONFIG.BASE_URL}/sets/`, set, {
headers: {
Authorization: `Bearer ${token}`,
},
@ -18,4 +18,4 @@ const AddSet = async (token, set) => {
}
};
export default AddSet;
export default addSet;

View File

@ -1,10 +1,10 @@
import { Card, Modal, Tooltip } from "antd";
import {Card, Modal, Tooltip} from "antd";
import PropTypes from "prop-types";
import { DeleteOutlined, EditOutlined, EyeOutlined } from "@ant-design/icons";
import { useState } from "react";
import {DeleteOutlined, EditOutlined, EyeOutlined} from "@ant-design/icons";
import {useState} from "react";
import LensViewModal from "./LensViewModal.jsx";
const LensListCard = ({ lens, handleEditLens, handleDeleteLens }) => {
const LensListCard = ({lens, handleEditLens, handleDeleteLens}) => {
const [showModalInfo, setShowModalInfo] = useState(false);
const deleteLensConfirm = () => {
@ -23,15 +23,15 @@ const LensListCard = ({ lens, handleEditLens, handleDeleteLens }) => {
const actions = [
<Tooltip title="Просмотр линзы" key={"viewLens"}>
<EyeOutlined onClick={handleViewLens} />
<EyeOutlined onClick={handleViewLens}/>
</Tooltip>,
<Tooltip title="Редактирование линзы" key={"editLens"}>
<EditOutlined onClick={() => handleEditLens(lens)} />
<EditOutlined onClick={() => handleEditLens(lens)}/>
</Tooltip>,
<Tooltip title="Удаление линзы" key={"deleteLens"}>
<DeleteOutlined
onClick={deleteLensConfirm}
style={{ color: "red" }}
style={{color: "red"}}
/>
</Tooltip>,
];
@ -50,13 +50,11 @@ const LensListCard = ({ lens, handleEditLens, handleDeleteLens }) => {
{lens.issued && <p><strong> Линза выдана</strong></p>}
</Card>
{showModalInfo && (
<LensViewModal
visible={showModalInfo}
onCancel={() => setShowModalInfo(false)}
lens={lens}
/>
)}
<LensViewModal
visible={showModalInfo}
onCancel={() => setShowModalInfo(false)}
lens={lens}
/>
</>
);
};

View File

@ -61,14 +61,12 @@ const PatientListCard = ({patient, handleEditPatient, handleDeletePatient}) => {
{patient.email && <p><strong> Email:</strong> {patient.email}</p>}
</Card>
{showModalInfo && (
<PatientViewModal
visible={showModalInfo}
onCancel={() => setShowModalInfo(false)}
patient={patient}
/>
)}
</>
<PatientViewModal
visible={showModalInfo}
onCancel={() => setShowModalInfo(false)}
patient={patient}
/>
</>
);
};

View File

@ -0,0 +1,257 @@
import {useState, useEffect} from "react";
import {Modal, Button, Form, Input, Table, InputNumber, Select, Space, notification} from "antd";
import {PlusOutlined, DeleteOutlined} from "@ant-design/icons";
import axios from "axios";
import getAllLensTypes from "../../api/lens_types/GetAllLensTypes.jsx";
import {useAuth} from "../../AuthContext.jsx";
import PropTypes from "prop-types";
import getSetContentBySetId from "../../api/set_content/GetSetContentBySetId.jsx";
const {Option} = Select;
const SetFormModal = ({visible, onCancel, setData, onSubmit}) => {
const {user} = useAuth();
const [form] = Form.useForm();
const [content, setContent] = useState([]);
const [lensTypes, setLensTypes] = useState([]);
useEffect(() => {
fetchLensTypes();
fetchSetContents();
}, []);
useEffect(() => {
if (setData) {
form.setFieldsValue({ title: setData.title || "" });
}
fetchSetContents();
}, [setData, form]);
const fetchSetContents = async () => {
if (!setData) return;
try {
const data = await getSetContentBySetId(user.token, setData.id);
setContent(data);
} catch (error) {
console.log(error);
notification.error({
message: "Ошибка загрузки контента",
description: "Проверьте подключение к сети.",
placement: "topRight",
});
}
};
const fetchLensTypes = async () => {
try {
const data = await getAllLensTypes(user.token);
setLensTypes(data);
} catch (error) {
console.log(error);
notification.error({
message: "Ошибка загрузки типов линз",
description: "Проверьте подключение к сети.",
placement: "topRight",
});
}
};
const addContentItem = () => {
setContent([...content, {
id: Date.now(),
tor: 0,
trial: 0,
esa: 0,
fvc: 0,
preset_refraction: 0,
diameter: 0,
periphery_toricity: 0,
side: "left",
count: 1,
type_id: null
}]);
};
const updateContentItem = (index, field, value) => {
const updated = [...content];
updated[index][field] = value;
setContent(updated);
};
const removeContentItem = (index) => {
setContent(content.filter((_, i) => i !== index));
};
const handleSubmit = () => {
form.validateFields().then(values => {
onSubmit({...values, contents: content});
});
};
const columns = [
{
title: "Tor",
dataIndex: "tor",
render: (_, record, index) => <InputNumber
step={0.1}
value={record.tor}
onChange={value => updateContentItem(index, "tor", value)}
/>,
},
{
title: "Trial",
dataIndex: "trial",
render: (_, record, index) => <InputNumber
step={0.1}
value={record.trial}
onChange={value => updateContentItem(index, "trial", value)}
/>,
},
{
title: "ESA",
dataIndex: "esa",
render: (_, record, index) => <InputNumber
step={0.1}
value={record.esa}
onChange={value => updateContentItem(index, "esa", value)}
/>,
},
{
title: "FVC",
dataIndex: "fvc",
render: (_, record, index) => <InputNumber
step={0.1}
value={record.fvc}
onChange={value => updateContentItem(index, "fvc", value)}
/>,
},
{
title: "Пресетная рефракция",
dataIndex: "preset_refraction",
render: (_, record, index) => <InputNumber
step={0.1}
value={record.preset_refraction}
onChange={value => updateContentItem(index, "preset_refraction", value)}
/>,
},
{
title: "Диаметр",
dataIndex: "diameter",
render: (_, record, index) => <InputNumber
step={0.1}
value={record.diameter}
onChange={value => updateContentItem(index, "diameter", value)}
/>,
},
{
title: "Периферийная торичность",
dataIndex: "periphery_toricity",
render: (_, record, index) => <InputNumber
step={0.1}
value={record.periphery_toricity}
onChange={value => updateContentItem(index, "periphery_toricity", value)}
/>,
},
{
title: "Сторона",
dataIndex: "side",
render: (_, record, index) => <Select
value={record.side}
onChange={value => updateContentItem(index, "side", value)}
>
<Option value="left">Левая</Option>
<Option value="right">Правая</Option>
</Select>,
},
{
title: "Количество",
dataIndex: "count",
render: (_, record, index) => <InputNumber
min={1}
value={record.count}
onChange={value => updateContentItem(index, "count", value)}
/>,
},
{
title: "Тип",
dataIndex: "type_id",
render: (_, record, index) => (
<Select
value={record.type_id}
onChange={value => updateContentItem(index, "type_id", value)}
style={{width: "100%"}}
defaultValue={lensTypes[0]?.id || null}
>
{lensTypes.map(lensType =>
<Option key={lensType.id} value={lensType.id}>{lensType.title}</Option>
)}
</Select>
),
width: 200,
minWidth: 100
},
{
title: "Действие",
dataIndex: "actions",
render: (_, __, index) => (
<Button danger icon={<DeleteOutlined/>} onClick={() => removeContentItem(index)}/>
),
minWidth: 200,
}
];
return (
<Modal
title={setData ? "Редактировать набор" : "Создать набор"}
open={visible}
onCancel={() => {
form.resetFields();
setContent([]);
onCancel();
}}
onOk={handleSubmit}
okText="Сохранить"
cancelText={"Отмена"}
width="90vw"
style={{maxWidth: 2500}}
>
<Form form={form} layout="vertical">
<Form.Item label="Название набора" name="title" rules={[{required: true, message: "Введите название"}]}>
<Input/>
</Form.Item>
</Form>
<div style={{maxWidth: "100%", overflowX: "auto"}}>
<Table
dataSource={content}
columns={columns}
rowKey="id"
pagination={false}
scroll={{x: "max-content", y: 300}}
locale={{ emptyText: "Добавьте элементы" }}
/>
</div>
<Space style={{marginTop: 10}}>
<Button icon={<PlusOutlined/>} onClick={addContentItem}>Добавить элемент</Button>
</Space>
</Modal>
);
};
SetFormModal.propTypes = {
visible: PropTypes.bool.isRequired,
onCancel: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
setData: PropTypes.shape({
id: PropTypes.number,
title: PropTypes.string,
}),
}
export default SetFormModal;

View File

@ -0,0 +1,62 @@
import PropTypes from "prop-types";
import {Card, Modal, Tooltip} from "antd";
import {DeleteOutlined, EditOutlined, PlusOutlined} from "@ant-design/icons";
const SetListCard = ({set, handleEditSet, handleAddSet, handleDeleteSet}) => {
const confirmSetDelete = () => {
Modal.confirm({
title: "Удаление набора",
content: `Вы уверены, что хотите удалить набор ${set.title}?`,
okText: "Да, удалить",
cancelText: "Отмена",
onOk: () => handleDeleteSet(set.id),
});
};
const actions = [
<Tooltip title="Добавить набор" key={"add"}>
<PlusOutlined
onClick={handleAddSet}
/>
</Tooltip>,
<Tooltip title="Редактирование набора" key={"edit"}>
<EditOutlined
onClick={() => {
handleEditSet(set);
}}
/>
</Tooltip>,
<Tooltip title="Удаление набора" key={"delete"}>
<DeleteOutlined
style={{color: "red"}}
onClick={confirmSetDelete}
/>
</Tooltip>,
];
return (
<>
<Card
type="inner"
title={set.title}
actions={actions}
/>
</>
);
};
SetListCard.propTypes = {
set: PropTypes.shape({
id: PropTypes.number.isRequired,
title: PropTypes.string.isRequired,
}).isRequired,
handleEditSet: PropTypes.func.isRequired,
handleAddSet: PropTypes.func.isRequired,
handleDeleteSet: PropTypes.func.isRequired,
};
export default SetListCard;

View File

@ -1,21 +1,10 @@
import {useState, useEffect} from "react";
import {
Input,
Select,
List,
FloatButton,
Row,
Col,
Spin,
Button,
Form,
InputNumber,
Card, Grid, notification, Tabs
Tabs
} from "antd";
import LensesPage from "../pages/LensesPage.jsx";
import {FolderViewOutlined, SwitcherOutlined} from "@ant-design/icons";
import SetLensesPage from "../pages/SetLensesPage.jsx";
const {Option} = Select;
const items = [
{
@ -27,7 +16,7 @@ const items = [
{
key: '2',
label: 'Наборы линз',
children: '1233',
children: <SetLensesPage/>,
icon: <SwitcherOutlined/>
}
]

View File

@ -124,11 +124,9 @@ const LensesPage = () => {
};
const handleDeleteLens = async (lensId) => {
if (!user || !user.token) return;
try {
await deleteLens(user.token, lensId);
fetchLenses(user.token);
await fetchLenses(user.token);
notification.success({
message: "Линза удалена",
description: "Линза успешно удалена.",
@ -215,7 +213,6 @@ const LensesPage = () => {
boxShadow: "0 1px 6px rgba(0, 0, 0, 0.15)",
borderRadius: 8
}}
type={"inner"}
>
<Row gutter={16}>
<Col xs={24} sm={12}>

View File

@ -96,8 +96,6 @@ const PatientsPage = () => {
};
const handleDeletePatient = async (patient_id) => {
if (!user || !user.token) return;
try {
await deletePatient(user.token, patient_id);
await fetchPatients();
@ -123,19 +121,9 @@ const PatientsPage = () => {
const handleModalPatientSubmit = async (newPatient) => {
try {
if (selectedPatient) {
await updatePatient(user.token, selectedPatient.id, newPatient);
notification.success({
message: "Пациент обновлён",
description: `Данные пациента ${newPatient.first_name} ${newPatient.last_name} успешно обновлены.`,
placement: "topRight",
});
await editPatient(newPatient);
} else {
await addPatient(user.token, newPatient);
notification.success({
message: "Пациент добавлен",
description: `Пациент ${newPatient.first_name} ${newPatient.last_name} успешно добавлен.`,
placement: "topRight",
});
await addPatient(newPatient);
}
setIsModalVisible(false);
await fetchPatients();
@ -150,6 +138,24 @@ const PatientsPage = () => {
}
};
const editPatient = async (patient) => {
await updatePatient(user.token, selectedPatient.id, patient);
notification.success({
message: "Пациент обновлён",
description: `Данные пациента ${patient.first_name} ${patient.last_name} успешно обновлены.`,
placement: "topRight",
});
};
const addPatient = async (patient) => {
await addPatient(user.token, patient);
notification.success({
message: "Пациент добавлен",
description: `Пациент ${patient.first_name} ${patient.last_name} успешно добавлен.`,
placement: "topRight",
});
};
return (
<div style={{padding: 20}}>
<Row gutter={[16, 16]} style={{marginBottom: 20}}>

View File

@ -1,8 +1,239 @@
import {useAuth} from "../AuthContext.jsx";
import {useEffect, useState} from "react";
import {Col, FloatButton, Input, List, notification, Row, Select, Spin, Tooltip} from "antd";
import getAllSets from "../api/sets/GetAllSets.jsx";
import {LoadingOutlined, PlusOutlined} from "@ant-design/icons";
import SetListCard from "../components/sets/SetListCard.jsx";
import SetFormModal from "../components/sets/SetFormModal.jsx";
import updateSet from "../api/sets/UpdateSet.jsx";
import addSet from "../api/sets/AddSet.jsx";
import deleteSet from "../api/sets/DeleteSet.jsx";
import addSetContent from "../api/set_content/AddSetContent.jsx";
const SetLensesPage = () => {
const {user} = useAuth();
const [current, setCurrent] = useState(1);
const [pageSize, setPageSize] = useState(10);
const [searchText, setSearchText] = useState("");
const [sets, setSets] = useState([]);
const [loading, setLoading] = useState(true);
const [isModalVisible, setIsModalVisible] = useState(false);
const [selectedSet, setSelectedSet] = useState(null);
useEffect(() => {
fetchSetsWithCache();
}, []);
useEffect(() => {
if (!isModalVisible) {
const intervalId = setInterval(fetchSets, 5000);
return () => clearInterval(intervalId);
}
}, [user, isModalVisible]);
const fetchSetsWithCache = async () => {
const cachedData = localStorage.getItem("setsData");
const cacheTimestamp = localStorage.getItem("setsTimestamp");
if (cachedData && cacheTimestamp && (Date.now() - parseInt(cacheTimestamp)) < 60 * 1000) {
setSets(JSON.parse(cachedData));
setLoading(false);
} else {
await fetchSets();
}
};
const fetchSets = async () => {
if (!user || !user.token) return;
try {
const data = await getAllSets(user.token);
setSets(data);
localStorage.setItem("setsData", JSON.stringify(data));
localStorage.setItem("setsTimestamp", Date.now().toString());
} catch (error) {
console.log(error);
notification.error({
message: "Ошибка загрузки данных",
description: "Проверьте подключение к сети.",
placement: "topRight",
})
}
if (loading) {
setLoading(false);
}
};
const filteredSets = sets.filter(set => set.title.toLowerCase().includes(searchText.toLowerCase()));
const handleAddSet = () => {
setSelectedSet(null);
setIsModalVisible(true);
};
const handleEditSet = (set) => {
setSelectedSet(set);
setIsModalVisible(true);
}
const handleDeleteSet = async (set_id) => {
try {
await deleteSet(user.token, set_id);
notification.success({
message: "Набор удален",
description: "Набор успешно удален.",
placement: "topRight",
});
await fetchSets();
} catch (error) {
console.error("Ошибка удаления набора:", error);
notification.error({
message: "Ошибка удаления набора",
description: "Проверьте подключение к сети.",
placement: "topRight",
});
}
}
const handleCancel = () => {
setIsModalVisible(false);
};
const handleModalSetSubmit = async (set, content = []) => {
try {
let refreshed_set;
if (selectedSet) {
refreshed_set = await editCurrentSet(set);
} else {
refreshed_set = await addNewSet(set);
}
if (refreshed_set) {
await setContent(content, refreshed_set.id);
}
setIsModalVisible(false);
await fetchSets();
} catch (error) {
console.error("Ошибка сохранения набора:", error);
notification.error({
message: "Ошибка сохранения набора",
description: "Проверьте подключение к сети.",
placement: "topRight",
});
}
};
const setContent = async (content, set_id) => {
try {
await addSetContent(user.token, content, set_id);
} catch (error) {
console.error("Ошибка сохранения набора:", error);
notification.error({
message: "Ошибка сохранения набора",
description: "Проверьте подключение к сети.",
placement: "topRight",
});
}
};
const editCurrentSet = async (set) => {
const refreshed_set = await updateSet(user.token, selectedSet.id, set);
notification.success({
message: "Набор обновлен",
description: "Набор успешно обновлен.",
placement: "topRight",
});
return refreshed_set;
};
const addNewSet = async (set) => {
const refreshed_set = await addSet(user.token, set);
notification.success({
message: "Набор добавлен",
description: "Набор успешно добавлен.",
placement: "topRight",
});
return refreshed_set;
};
return (
<div style={{padding: 20}}>
<Row style={{marginBottom: 20}}>
<Input
placeholder="Поиск набора"
onChange={(e) => setSearchText(e.target.value)}
style={{width: "100%"}}
allowClear
/>
</Row>
{loading ? (
<div style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
height: "100vh",
}}>
<Spin indicator={<LoadingOutlined style={{fontSize: 64, color: "#1890ff"}} spin/>}/>
</div>
) : (
<List
grid={{
gutter: 16,
xs: 1,
sm: 1,
md: 2,
lg: 2,
xl: 3,
xxl: 3,
}}
dataSource={filteredSets}
renderItem={(set) => (
<List.Item>
<SetListCard
set={set}
handleEditSet={handleEditSet}
handleAddSet={handleAddSet}
handleDeleteSet={handleDeleteSet}
/>
</List.Item>
)}
pagination={{
current,
pageSize,
showSizeChanger: true,
pageSizeOptions: ["5", "10", "20", "50"],
onChange: (page, newPageSize) => {
setCurrent(page);
setPageSize(newPageSize);
},
}}
/>
)}
<FloatButton
icon={<PlusOutlined/>}
type="primary"
style={{position: "fixed", bottom: 40, right: 40}}
tooltip="Добавить набор"
onClick={handleAddSet}
/>
<SetFormModal
visible={isModalVisible}
onCancel={handleCancel}
onSubmit={handleModalSetSubmit}
setData={selectedSet}
/>
</div>
);
};
export default SetLensesPage;