сделал загатовку модального окна по добавление/изменению линз. сделал страницу с линзами
This commit is contained in:
parent
b08c469950
commit
ee2f82d062
@ -4,7 +4,7 @@ import LoginPage from "./pages/LoginPage.jsx";
|
||||
import MainLayout from "./layouts/MainLayout.jsx";
|
||||
import PatientsPage from "./pages/PatientsPage.jsx";
|
||||
import HomePage from "./pages/HomePage.jsx";
|
||||
import LensPage from "./pages/LensPage.jsx";
|
||||
import LensPage from "./pages/LensesPage.jsx";
|
||||
|
||||
|
||||
const AppRouter = () => (
|
||||
|
||||
21
web-app/src/api/lens_types/GetAllLensTypes.jsx
Normal file
21
web-app/src/api/lens_types/GetAllLensTypes.jsx
Normal file
@ -0,0 +1,21 @@
|
||||
import axios from "axios";
|
||||
import CONFIG from "../../core/Config.jsx";
|
||||
|
||||
|
||||
const getAllLensTypes = async (token) => {
|
||||
try {
|
||||
const response = await axios.get(`${CONFIG.BASE_URL}/lens_types/`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
if (error.response?.status === 401) {
|
||||
throw new Error("Ошибка авторизации: пользователь не найден или токен недействителен");
|
||||
}
|
||||
throw new Error(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
export default getAllLensTypes;
|
||||
@ -1,6 +1,5 @@
|
||||
import CONFIG from "../../core/Config.jsx";
|
||||
import axios from "axios";
|
||||
import {useAuth} from "../../AuthContext.jsx";
|
||||
|
||||
|
||||
const addLens = async (token, lens) => {
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import axios from "axios";
|
||||
import CONFIG from "../../core/Config.jsx";
|
||||
|
||||
|
||||
const updateLens = async (token, lensId, lensData) => {
|
||||
console.log(lensId, lensData);
|
||||
try {
|
||||
const response = await axios.put(`${CONFIG.BASE_URL}/lenses/${lensId}/`, lensData, {
|
||||
headers: {
|
||||
|
||||
@ -0,0 +1,178 @@
|
||||
import {Form, InputNumber, Modal, notification, Select} from "antd";
|
||||
import {useEffect, useState} from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import getAllLensTypes from "../../api/lens_types/GetAllLensTypes.jsx";
|
||||
import {useAuth} from "../../AuthContext.jsx";
|
||||
|
||||
|
||||
const LensFormModal = ({visible, onCancel, onSubmit, lens}) => {
|
||||
const {user} = useAuth();
|
||||
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const [lensTypes, setLensTypes] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchLensTypes();
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
form.resetFields();
|
||||
if (lens) {
|
||||
form.setFieldsValue({
|
||||
...lens,
|
||||
})
|
||||
}
|
||||
}
|
||||
}, [visible, lens]);
|
||||
|
||||
const fetchLensTypes = async () => {
|
||||
try {
|
||||
const data = await getAllLensTypes(user.token);
|
||||
setLensTypes(data);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
notification.error({
|
||||
message: "Ошибка загрузки типов линз",
|
||||
description: "Проверьте подключение к сети.",
|
||||
placement: "topRight",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const handleOk = async () => {
|
||||
try {
|
||||
const values = await form.validateFields();
|
||||
onSubmit(values);
|
||||
form.resetFields();
|
||||
} catch (error) {
|
||||
console.log("Validation Failed:", error)
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={lens ? "Редактировать лину" : "Добавить линзу"}
|
||||
open={visible}
|
||||
onCancel={() => {
|
||||
form.resetFields();
|
||||
onCancel();
|
||||
}}
|
||||
onOk={handleOk}
|
||||
okText={"Сохранить"}
|
||||
cancelText={"Отмена"}
|
||||
maskClosable={false}
|
||||
forceRender={true}
|
||||
style={{top: 20}}
|
||||
centered
|
||||
>
|
||||
<Form form={form} layout={"vertical"}>
|
||||
<Form.Item
|
||||
name="tor"
|
||||
label="Тор"
|
||||
rules={[{required: true, message: "Введите тор"}]}
|
||||
>
|
||||
<InputNumber
|
||||
step={0.1}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="trial"
|
||||
label="Острота зрения (Trial)"
|
||||
rules={[{required: true, message: "Введите остроту зрения"}]}
|
||||
>
|
||||
<InputNumber
|
||||
step={0.1}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="esa"
|
||||
label="Esa"
|
||||
rules={[{required: true, message: "Введите esa"}]}
|
||||
>
|
||||
<InputNumber/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="fvc"
|
||||
label="FVC"
|
||||
rules={[{required: true, message: "Введите fvc"}]}
|
||||
>
|
||||
<InputNumber/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="preset_refraction"
|
||||
label="Пресетная рефракция"
|
||||
rules={[{required: true, message: "Введите пресетную рефракцию"}]}
|
||||
>
|
||||
<InputNumber/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="diameter"
|
||||
label="Диаметр"
|
||||
rules={[{required: true, message: "Введите диаметр"}]}
|
||||
>
|
||||
<InputNumber/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="periphery_toricity"
|
||||
label="Периферия торичность"
|
||||
rules={[{required: true, message: "Введите периферию торичность"}]}
|
||||
>
|
||||
<InputNumber/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="side"
|
||||
label="Сторона"
|
||||
rules={[{required: true, message: "Выберите сторону"}]}
|
||||
>
|
||||
<Select>
|
||||
<Select.Option value="left">Левая</Select.Option>
|
||||
<Select.Option value="right">Правая</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="type_id"
|
||||
label="Тип линзы"
|
||||
rules={[{required: true, message: "Выберите тип линзы"}]}
|
||||
>
|
||||
<Select>
|
||||
{lensTypes.map((type) => (
|
||||
<Select.Option key={type.id} value={type.id}>
|
||||
{type.title}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
)
|
||||
};
|
||||
|
||||
LensFormModal.propTypes = {
|
||||
visible: PropTypes.bool.isRequired,
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
onSubmit: PropTypes.func.isRequired,
|
||||
lens: PropTypes.shape({
|
||||
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;
|
||||
@ -1,4 +1,4 @@
|
||||
import {Button, Col, Modal, Row, Typography, Divider} from "antd";
|
||||
import {Button, Col, Modal, Row, Typography} from "antd";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const {Text, Title} = Typography;
|
||||
@ -29,10 +29,20 @@ const LensViewModal = ({visible, onCancel, lens}) => {
|
||||
<Text>{lens.diameter} мм</Text>
|
||||
</div>
|
||||
|
||||
<div style={{marginBottom: 12}}>
|
||||
<Title level={5}>🔭 FVC</Title>
|
||||
<Text>{lens.fvc} мм</Text>
|
||||
</div>
|
||||
|
||||
<div style={{marginBottom: 12}}>
|
||||
<Title level={5}>🔄 Пресетная рефракция</Title>
|
||||
<Text>{lens.preset_refraction} D</Text>
|
||||
</div>
|
||||
|
||||
<div style={{marginBottom: 12}}>
|
||||
<Title level={5}>👀 Острота зрения (Trial)</Title>
|
||||
<Text>{lens.trial.toFixed(2)} D</Text>
|
||||
</div>
|
||||
</Col>
|
||||
|
||||
<Col xs={24} md={12}>
|
||||
@ -46,19 +56,17 @@ const LensViewModal = ({visible, onCancel, lens}) => {
|
||||
<Text>{lens.side}</Text>
|
||||
</div>
|
||||
|
||||
<div style={{marginBottom: 12}}>
|
||||
<Title level={5}>👓 Esa</Title>
|
||||
<Text>{lens.esa}</Text>
|
||||
</div>
|
||||
|
||||
<div style={{marginBottom: 12}}>
|
||||
<Title level={5}>{lens.issued ? '✅' : '❌'} Статус выдачи</Title>
|
||||
<Text>{lens.issued ? 'Выдана' : 'Не выдана'}</Text>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Divider/>
|
||||
|
||||
<div style={{marginBottom: 12}}>
|
||||
<Title level={5}>👀 Острота зрения (Trial)</Title>
|
||||
<Text>{lens.trial.toFixed(2)} D</Text>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@ -69,11 +77,13 @@ LensViewModal.propTypes = {
|
||||
lens: PropTypes.shape({
|
||||
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, // Указываем, что это число
|
||||
trial: PropTypes.number.isRequired,
|
||||
}),
|
||||
};
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import {useEffect} from "react";
|
||||
import {Modal, Form, Input, DatePicker} from "antd";
|
||||
import {Modal, Form, Input, DatePicker, notification} from "antd";
|
||||
import PropTypes from "prop-types";
|
||||
import locale from "antd/es/date-picker/locale/ru_RU";
|
||||
import validator from "validator";
|
||||
@ -31,8 +31,13 @@ const PatientFormModal = ({visible, onCancel, onSubmit, patient}) => {
|
||||
}
|
||||
onSubmit(values);
|
||||
form.resetFields();
|
||||
} catch (errorInfo) {
|
||||
console.log("Validation Failed:", errorInfo);
|
||||
} catch (error) {
|
||||
console.log("Validation Failed:", error);
|
||||
notification.error({
|
||||
message: "Ошибка валидации",
|
||||
description: "Проверьте правильность заполнения полей.",
|
||||
placement: "topRight",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@ -45,14 +50,14 @@ const PatientFormModal = ({visible, onCancel, onSubmit, patient}) => {
|
||||
onCancel();
|
||||
}}
|
||||
onOk={handleOk}
|
||||
okText="Сохранить"
|
||||
cancelText="Отмена"
|
||||
centered
|
||||
okText={"Сохранить"}
|
||||
cancelText={"Отмена"}
|
||||
maskClosable={false}
|
||||
forceRender={true}
|
||||
style={{top: 20}}
|
||||
centered
|
||||
>
|
||||
<Form form={form} layout="vertical">
|
||||
<Form form={form} layout={"vertical"}>
|
||||
<Form.Item
|
||||
name="first_name"
|
||||
label="Имя"
|
||||
@ -78,7 +83,11 @@ const PatientFormModal = ({visible, onCancel, onSubmit, patient}) => {
|
||||
label="Дата рождения"
|
||||
rules={[{required: true, message: "Выберите дату рождения"}]}
|
||||
>
|
||||
<DatePicker style={{width: "100%"}} locale={locale} format="DD.MM.YYYY"/>
|
||||
<DatePicker
|
||||
style={{width: "100%"}}
|
||||
locale={locale} format="DD.MM.YYYY"
|
||||
maxDate={dayjs(new Date())}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="address"
|
||||
|
||||
@ -1,195 +0,0 @@
|
||||
import {useState, useEffect} from "react";
|
||||
import {Input, Select, List, FloatButton, Row, Col, Spin, Checkbox} from "antd";
|
||||
import {LoadingOutlined, PlusOutlined} from "@ant-design/icons";
|
||||
import LensCard from "../components/lenses/LensListCard.jsx";
|
||||
import getAllLenses from "../api/lenses/GetAllLenses.jsx";
|
||||
import addLens from "../api/lenses/AddLens.jsx";
|
||||
import updateLens from "../api/lenses/UpdateLens.jsx";
|
||||
import deleteLens from "../api/lenses/DeleteLens.jsx";
|
||||
import {useAuth} from "../AuthContext.jsx";
|
||||
|
||||
const {Option} = Select;
|
||||
|
||||
const LensesPage = () => {
|
||||
const {user} = useAuth();
|
||||
|
||||
const [current, setCurrent] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const [sortOrder, setSortOrder] = useState("asc");
|
||||
const [lenses, setLenses] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
const [showIssuedLenses, setShowIssuedLenses] = useState(false);
|
||||
const [selectedLens, setSelectedLens] = useState(null);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
fetchLensWithCache();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isModalVisible) {
|
||||
const intervalId = setInterval(fetchLenses, 5000);
|
||||
return () => clearInterval(intervalId);
|
||||
}
|
||||
}, [user, isModalVisible]);
|
||||
|
||||
const fetchLensWithCache = async () => {
|
||||
const cachedData = localStorage.getItem("lensData");
|
||||
const cacheTimestamp = localStorage.getItem("lensTimestamp");
|
||||
|
||||
if (cachedData && cacheTimestamp && (Date.now() - parseInt(cacheTimestamp)) < 60 * 1000) {
|
||||
setLenses(JSON.parse(cachedData));
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await fetchLenses();
|
||||
};
|
||||
|
||||
const fetchLenses = async () => {
|
||||
if (!user || !user.token) return;
|
||||
|
||||
try {
|
||||
const data = await getAllLenses(user.token);
|
||||
setLenses(data);
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
console.error("Ошибка загрузки линз:", error);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const filteredLenses = lenses.filter((lens) =>
|
||||
Object.values(lens).some((value) =>
|
||||
value?.toString().toLowerCase().includes(searchText.toLowerCase())
|
||||
) &&
|
||||
(showIssuedLenses || lens.issued === false)
|
||||
).sort((a, b) => {
|
||||
return sortOrder === "asc"
|
||||
? a.preset_refraction - b.preset_refraction
|
||||
: b.preset_refraction - a.preset_refraction;
|
||||
});
|
||||
|
||||
const handleAddLens = () => {
|
||||
setSelectedLens(null);
|
||||
setIsModalVisible(true);
|
||||
};
|
||||
|
||||
const handleEditLens = (lens) => {
|
||||
setSelectedLens(lens);
|
||||
setIsModalVisible(true);
|
||||
};
|
||||
|
||||
const handleDeleteLens = async (lensId) => {
|
||||
if (!user || !user.token) return;
|
||||
|
||||
try {
|
||||
await deleteLens(lensId, user.token);
|
||||
fetchLenses(user.token);
|
||||
} catch (error) {
|
||||
console.error("Ошибка удаления линзы:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleModalSubmit = async (lensData) => {
|
||||
try {
|
||||
if (selectedLens) {
|
||||
await updateLens(selectedLens.id, lensData);
|
||||
} else {
|
||||
await addLens(lensData);
|
||||
}
|
||||
setIsModalVisible(false);
|
||||
fetchLenses();
|
||||
} catch (error) {
|
||||
console.error("Ошибка сохранения линзы:", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{padding: 20}}>
|
||||
<Row gutter={[16, 16]} style={{marginBottom: 20}}>
|
||||
<Col xs={24} sm={12}>
|
||||
<Input
|
||||
placeholder="Поиск линзы"
|
||||
value={searchText}
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
style={{width: "100%"}}
|
||||
allowClear
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={24} sm={8}>
|
||||
<Select
|
||||
value={sortOrder}
|
||||
onChange={(value) => setSortOrder(value)}
|
||||
style={{width: "100%"}}
|
||||
>
|
||||
<Option value="asc">По возрастанию рефракции</Option>
|
||||
<Option value="desc">По убыванию рефракции</Option>
|
||||
</Select>
|
||||
</Col>
|
||||
<Col xs={24} sm={4}>
|
||||
<Checkbox
|
||||
onChange={(e) => {
|
||||
setShowIssuedLenses(e.target.checked);
|
||||
}}
|
||||
>
|
||||
Показать выданные
|
||||
</Checkbox>
|
||||
</Col>
|
||||
</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: 3, xl: 4}}
|
||||
dataSource={filteredLenses}
|
||||
renderItem={(lens) => (
|
||||
<List.Item>
|
||||
<LensCard
|
||||
lens={lens}
|
||||
handleDeleteLens={() => handleDeleteLens(lens.id)}
|
||||
handleEditLens={() => handleEditLens(lens)}
|
||||
/>
|
||||
</List.Item>
|
||||
)}
|
||||
pagination={{
|
||||
current,
|
||||
pageSize,
|
||||
showSizeChanger: true,
|
||||
pageSizeOptions: ["5", "10", "20", "50"],
|
||||
onChange: (page, newPageSize) => {
|
||||
setCurrent(page);
|
||||
setPageSize(newPageSize);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<FloatButton
|
||||
icon={<PlusOutlined/>}
|
||||
style={{position: "fixed", bottom: 20, right: 20}}
|
||||
onClick={handleAddLens}
|
||||
/>
|
||||
|
||||
{/*<LensModal*/}
|
||||
{/* visible={isModalVisible}*/}
|
||||
{/* onCancel={() => setIsModalVisible(false)}*/}
|
||||
{/* onSubmit={handleModalSubmit}*/}
|
||||
{/* lens={selectedLens}*/}
|
||||
{/*/>*/}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LensesPage;
|
||||
382
web-app/src/pages/LensesPage.jsx
Normal file
382
web-app/src/pages/LensesPage.jsx
Normal file
@ -0,0 +1,382 @@
|
||||
import {useState, useEffect} from "react";
|
||||
import {
|
||||
Input,
|
||||
Select,
|
||||
List,
|
||||
FloatButton,
|
||||
Row,
|
||||
Col,
|
||||
Spin,
|
||||
Button,
|
||||
Form,
|
||||
InputNumber,
|
||||
Card, Grid, notification
|
||||
} from "antd";
|
||||
import {LoadingOutlined, PlusOutlined, DownOutlined, UpOutlined} from "@ant-design/icons";
|
||||
import LensCard from "../components/lenses/LensListCard.jsx";
|
||||
import getAllLenses from "../api/lenses/GetAllLenses.jsx";
|
||||
import addLens from "../api/lenses/AddLens.jsx";
|
||||
import updateLens from "../api/lenses/UpdateLens.jsx";
|
||||
import deleteLens from "../api/lenses/DeleteLens.jsx";
|
||||
import {useAuth} from "../AuthContext.jsx";
|
||||
import LensFormModal from "../components/lenses/LensFormModal.jsx";
|
||||
|
||||
const {Option} = Select;
|
||||
const {useBreakpoint} = Grid;
|
||||
|
||||
const LensesPage = () => {
|
||||
const {user} = useAuth();
|
||||
const screens = useBreakpoint();
|
||||
|
||||
const [current, setCurrent] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const [lenses, setLenses] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
const [selectedLens, setSelectedLens] = useState(null);
|
||||
const [showAdvancedSearch, setShowAdvancedSearch] = useState(false);
|
||||
|
||||
const [searchParams, setSearchParams] = useState({
|
||||
tor: null,
|
||||
diameter: null,
|
||||
preset_refraction: null,
|
||||
periphery_toricity: null,
|
||||
side: 'all',
|
||||
issued: false,
|
||||
trial: null
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
fetchLensWithCache();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isModalVisible) {
|
||||
const intervalId = setInterval(fetchLenses, 5000);
|
||||
return () => clearInterval(intervalId);
|
||||
}
|
||||
}, [user, isModalVisible]);
|
||||
|
||||
const fetchLensWithCache = async () => {
|
||||
const cachedData = localStorage.getItem("lensData");
|
||||
const cacheTimestamp = localStorage.getItem("lensTimestamp");
|
||||
|
||||
if (cachedData && cacheTimestamp && (Date.now() - parseInt(cacheTimestamp)) < 60 * 1000) {
|
||||
setLenses(JSON.parse(cachedData));
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await fetchLenses();
|
||||
};
|
||||
|
||||
const fetchLenses = async () => {
|
||||
if (!user || !user.token) return;
|
||||
|
||||
try {
|
||||
const data = await getAllLenses(user.token);
|
||||
setLenses(data);
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
console.error("Ошибка загрузки линз:", error);
|
||||
notification.error({
|
||||
message: "Ошибка загрузки линз",
|
||||
description: "Проверьте подключение к сети.",
|
||||
placement: "topRight",
|
||||
})
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const filteredLenses = lenses.filter((lens) => {
|
||||
const textMatch = Object.values(lens).some((value) =>
|
||||
value?.toString().toLowerCase().includes(searchText.toLowerCase())
|
||||
);
|
||||
|
||||
const advancedMatch = Object.entries(searchParams).every(([key, value]) => {
|
||||
if (value === null || value === '') return true;
|
||||
if (key === 'side') {
|
||||
if (value === 'all') return true;
|
||||
return lens.side === value;
|
||||
}
|
||||
if (key === 'issued') {
|
||||
return lens.issued === value || value === "all";
|
||||
}
|
||||
return lens[key] === value;
|
||||
});
|
||||
|
||||
return textMatch && advancedMatch && (searchParams.issued || lens.issued === false);
|
||||
}).sort((a, b) => {
|
||||
return a.preset_refraction - b.preset_refraction;
|
||||
});
|
||||
|
||||
|
||||
const handleAddLens = () => {
|
||||
setSelectedLens(null);
|
||||
setIsModalVisible(true);
|
||||
};
|
||||
|
||||
const handleEditLens = (lens) => {
|
||||
setSelectedLens(lens);
|
||||
setIsModalVisible(true);
|
||||
};
|
||||
|
||||
const handleDeleteLens = async (lensId) => {
|
||||
if (!user || !user.token) return;
|
||||
|
||||
try {
|
||||
await deleteLens(user.token, lensId);
|
||||
fetchLenses(user.token);
|
||||
notification.success({
|
||||
message: "Линза удалена",
|
||||
description: "Линза успешно удалена.",
|
||||
placement: "topRight",
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("Ошибка удаления линзы:", error);
|
||||
notification.error({
|
||||
message: "Ошибка удаления линзы",
|
||||
description: "Проверьте подключение к сети.",
|
||||
placement: "topRight",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleModalSubmit = async (lensData) => {
|
||||
try {
|
||||
if (selectedLens) {
|
||||
await updateLens(user.token, selectedLens.id, lensData);
|
||||
notification.success({
|
||||
message: "Линза обновлена",
|
||||
description: "Линза успешно обновлена.",
|
||||
placement: "topRight",
|
||||
});
|
||||
} else {
|
||||
await addLens(user.token, lensData);
|
||||
notification.success({
|
||||
message: "Линза добавлена",
|
||||
description: "Линза успешно добавлена.",
|
||||
placement: "topRight",
|
||||
});
|
||||
}
|
||||
setIsModalVisible(false);
|
||||
await fetchLenses();
|
||||
} catch (error) {
|
||||
console.error("Ошибка сохранения линзы:", error);
|
||||
notification.error({
|
||||
message: "Ошибка сохранения линзы",
|
||||
description: "Проверьте подключение к сети.",
|
||||
placement: "topRight",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const toggleAdvancedSearch = () => {
|
||||
setShowAdvancedSearch(!showAdvancedSearch);
|
||||
};
|
||||
|
||||
const handleParamChange = (param, value) => {
|
||||
setSearchParams({...searchParams, [param]: value});
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setIsModalVisible(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{padding: 20}}>
|
||||
<Row gutter={[16, 16]} style={{marginBottom: 20}}>
|
||||
<Col xs={24} md={17} sm={14} xl={20}>
|
||||
<Input
|
||||
placeholder="Поиск линзы"
|
||||
value={searchText}
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
style={{width: "100%"}}
|
||||
allowClear
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={24} md={7} sm={10} xl={4}>
|
||||
<Button
|
||||
onClick={toggleAdvancedSearch} icon={showAdvancedSearch ? <UpOutlined/> : <DownOutlined/>}
|
||||
block
|
||||
>
|
||||
Расширенный поиск
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{showAdvancedSearch && (
|
||||
<Card
|
||||
title="Расширенный поиск"
|
||||
style={{
|
||||
marginBottom: 20,
|
||||
boxShadow: "0 1px 6px rgba(0, 0, 0, 0.15)",
|
||||
borderRadius: 8
|
||||
}}
|
||||
type={"inner"}
|
||||
>
|
||||
<Row gutter={16}>
|
||||
<Col xs={24} sm={12}>
|
||||
<Form layout="vertical">
|
||||
<Form.Item label="Тор">
|
||||
<InputNumber
|
||||
value={searchParams.tor || 0}
|
||||
onChange={(value) => handleParamChange("tor", value)}
|
||||
style={{width: "100%"}}
|
||||
defaultValue={0}
|
||||
step={0.1}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Диаметр">
|
||||
<InputNumber
|
||||
value={searchParams.diameter || 0}
|
||||
onChange={(value) => handleParamChange("diameter", value)}
|
||||
style={{width: "100%"}}
|
||||
defaultValue={0}
|
||||
step={0.1}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Рефракция">
|
||||
<InputNumber
|
||||
value={searchParams.preset_refraction || 0}
|
||||
onChange={(value) => handleParamChange("preset_refraction", value)}
|
||||
style={{width: "100%"}}
|
||||
defaultValue={0}
|
||||
step={0.1}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Периферическая торичность">
|
||||
<InputNumber
|
||||
value={searchParams.periphery_toricity || 0}
|
||||
onChange={(value) => handleParamChange("periphery_toricity", value)}
|
||||
style={{width: "100%"}}
|
||||
defaultValue={0}
|
||||
step={0.1}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Col>
|
||||
|
||||
<Col xs={24} sm={12}>
|
||||
<Form layout="vertical">
|
||||
<Form.Item label="Сторона">
|
||||
<Select
|
||||
value={searchParams.side}
|
||||
onChange={(value) => handleParamChange("side", value)}
|
||||
style={{width: "100%"}}
|
||||
>
|
||||
<Option value="all">Все</Option>
|
||||
<Option value="левая">Левая</Option>
|
||||
<Option value="правая">Правая</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Выдана">
|
||||
<Select
|
||||
value={searchParams.issued}
|
||||
onChange={(value) => handleParamChange("issued", value)}
|
||||
style={{width: "100%"}}
|
||||
>
|
||||
<Option value="all">Все</Option>
|
||||
<Option value={true}>Да</Option>
|
||||
<Option value={false}>Нет</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Острота зрения (Trial)">
|
||||
<InputNumber
|
||||
value={searchParams.trial || 0}
|
||||
onChange={(value) => handleParamChange("trial", value)}
|
||||
style={{width: "100%"}}
|
||||
defaultValue={0}
|
||||
step={0.1}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Row>
|
||||
<Col xs={24} sm={12} offset={screens.sm ? 12 : 0}
|
||||
style={{textAlign: screens.sm ? "right" : "center", marginTop: "1.7rem"}}>
|
||||
<Button
|
||||
type="primary"
|
||||
block={!screens.sm}
|
||||
onClick={() => {
|
||||
setSearchParams({
|
||||
tor: null,
|
||||
diameter: null,
|
||||
preset_refraction: null,
|
||||
periphery_toricity: null,
|
||||
side: 'all',
|
||||
issued: false,
|
||||
trial: null
|
||||
});
|
||||
}}
|
||||
>
|
||||
Сбросить
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{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: 3, xl: 4}}
|
||||
dataSource={filteredLenses}
|
||||
renderItem={(lens) => (
|
||||
<List.Item>
|
||||
<LensCard
|
||||
lens={lens}
|
||||
handleDeleteLens={() => handleDeleteLens(lens.id)}
|
||||
handleEditLens={() => handleEditLens(lens)}
|
||||
/>
|
||||
</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}}
|
||||
onClick={handleAddLens}
|
||||
tooltip="Добавить линзу"
|
||||
/>
|
||||
|
||||
<LensFormModal
|
||||
visible={isModalVisible}
|
||||
onCancel={handleCancel}
|
||||
onSubmit={handleModalSubmit}
|
||||
lens={selectedLens}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LensesPage;
|
||||
@ -1,5 +1,5 @@
|
||||
import {useEffect, useState} from "react";
|
||||
import {Input, Select, List, FloatButton, Row, Col, Spin, notification} from "antd";
|
||||
import {Input, Select, List, FloatButton, Row, Col, Spin, notification, Tooltip} from "antd";
|
||||
import {LoadingOutlined, PlusOutlined} from "@ant-design/icons";
|
||||
import {useAuth} from "../AuthContext.jsx";
|
||||
import getAllPatients from "../api/patients/GetAllPatients.jsx";
|
||||
@ -162,14 +162,18 @@ const PatientsPage = () => {
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={24} sm={8}>
|
||||
<Select
|
||||
value={sortOrder}
|
||||
onChange={(value) => setSortOrder(value)}
|
||||
style={{width: "100%"}}
|
||||
<Tooltip
|
||||
title={"Сортировка пациентов"}
|
||||
>
|
||||
<Option value="asc">А-Я</Option>
|
||||
<Option value="desc">Я-А</Option>
|
||||
</Select>
|
||||
<Select
|
||||
value={sortOrder}
|
||||
onChange={(value) => setSortOrder(value)}
|
||||
style={{width: "100%"}}
|
||||
>
|
||||
<Option value="asc">А-Я</Option>
|
||||
<Option value="desc">Я-А</Option>
|
||||
</Select>
|
||||
</Tooltip>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
@ -218,8 +222,10 @@ const PatientsPage = () => {
|
||||
|
||||
<FloatButton
|
||||
icon={<PlusOutlined/>}
|
||||
style={{position: "fixed", bottom: 20, right: 20}}
|
||||
type="primary"
|
||||
style={{position: "fixed", bottom: 40, right: 40}}
|
||||
onClick={handleAddPatient}
|
||||
tooltip="Добавить пациента"
|
||||
/>
|
||||
|
||||
<PatientFormModal
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user