сделал заготовку под страницу с линзами
This commit is contained in:
parent
ddd551adb4
commit
8b7c427602
@ -23,7 +23,7 @@ class LensesService:
|
||||
tor=lens.tor,
|
||||
trial=lens.trial,
|
||||
esa=lens.esa,
|
||||
fvc=lens.fvs,
|
||||
fvc=lens.fvc,
|
||||
preset_refraction=lens.preset_refraction,
|
||||
diameter=lens.diameter,
|
||||
periphery_toricity=lens.periphery_toricity,
|
||||
|
||||
@ -4,6 +4,7 @@ from starlette.middleware.cors import CORSMiddleware
|
||||
from app.controllers.auth_router import router as auth_router
|
||||
from app.controllers.register_routes import router as register_router
|
||||
from app.controllers.patients_router import router as patients_router
|
||||
from app.controllers.lenses_router import router as lens_router
|
||||
from app.settings import settings
|
||||
|
||||
|
||||
@ -21,6 +22,7 @@ def start_app():
|
||||
api_app.include_router(auth_router, prefix=settings.APP_PREFIX, tags=['auth'])
|
||||
api_app.include_router(register_router, prefix=settings.APP_PREFIX, tags=['register'])
|
||||
api_app.include_router(patients_router, prefix=settings.APP_PREFIX, tags=['patients'])
|
||||
api_app.include_router(lens_router, prefix=settings.APP_PREFIX, tags=['lenses'])
|
||||
|
||||
return api_app
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import {Routes, Route, Navigate} from "react-router-dom";
|
||||
import PrivateRoute from "./components/patients/PrivateRoute.jsx";
|
||||
import PrivateRoute from "./components/PrivateRoute.jsx";
|
||||
import LoginPage from "./pages/LoginPage.jsx";
|
||||
import MainLayout from "./layouts/MainLayout.jsx";
|
||||
import PatientsPage from "./pages/PatientsPage.jsx";
|
||||
|
||||
22
web-app/src/api/lenses/AddLens.jsx
Normal file
22
web-app/src/api/lenses/AddLens.jsx
Normal file
@ -0,0 +1,22 @@
|
||||
import CONFIG from "../../core/Config.jsx";
|
||||
import axios from "axios";
|
||||
import {useAuth} from "../../AuthContext.jsx";
|
||||
|
||||
|
||||
const addLens = async (token, lens) => {
|
||||
try {
|
||||
const response = await axios.post(`${CONFIG.BASE_URL}/lenses/`, lens, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
if (error.response?.status === 401) {
|
||||
throw new Error("Ошибка авторизации: пользователь не найден или токен недействителен");
|
||||
}
|
||||
throw new Error(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
export default addLens;
|
||||
21
web-app/src/api/lenses/DeleteLens.jsx
Normal file
21
web-app/src/api/lenses/DeleteLens.jsx
Normal file
@ -0,0 +1,21 @@
|
||||
import axios from "axios";
|
||||
import CONFIG from "../../core/Config.jsx";
|
||||
|
||||
|
||||
const deleteLens = async (token, lens_id) => {
|
||||
try {
|
||||
const response = await axios.delete(`${CONFIG.BASE_URL}/lenses/${lens_id}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
if (error.response?.status === 401) {
|
||||
throw new Error("Ошибка авторизации: пользователь не найден или токен недействителен");
|
||||
}
|
||||
throw new Error(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
export default deleteLens;
|
||||
20
web-app/src/api/lenses/GetAllLenses.jsx
Normal file
20
web-app/src/api/lenses/GetAllLenses.jsx
Normal file
@ -0,0 +1,20 @@
|
||||
import axios from "axios";
|
||||
import CONFIG from "../../core/Config.jsx";
|
||||
|
||||
const getAllLenses = async (token) => {
|
||||
try {
|
||||
const response = await axios.get(`${CONFIG.BASE_URL}/lenses/`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
if (error.response?.status === 401) {
|
||||
throw Error("Ошибка авторизации: пользователь неяден или токен недействителен");
|
||||
}
|
||||
throw Error(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
export default getAllLenses;
|
||||
20
web-app/src/api/lenses/UpdateLens.jsx
Normal file
20
web-app/src/api/lenses/UpdateLens.jsx
Normal file
@ -0,0 +1,20 @@
|
||||
import axios from "axios";
|
||||
|
||||
|
||||
const updateLens = async (token, lensId, lensData) => {
|
||||
try {
|
||||
const response = await axios.put(`${CONFIG.BASE_URL}/lenses/${lensId}/`, lensData, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
if (error.response?.status === 401) {
|
||||
throw new Error("Ошибка авторизации: пользователь не найден или токен недействителен");
|
||||
}
|
||||
throw new Error(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
export default updateLens;
|
||||
@ -2,7 +2,7 @@ import axios from "axios";
|
||||
import CONFIG from "../../core/Config.jsx";
|
||||
|
||||
|
||||
const AddPatient = async (token, patient) => {
|
||||
const addPatient = async (token, patient) => {
|
||||
try {
|
||||
const response = await axios.post(`${CONFIG.BASE_URL}/patients/`, patient, {
|
||||
headers: {
|
||||
@ -11,8 +11,11 @@ const AddPatient = async (token, patient) => {
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
if (error.response?.status === 401) {
|
||||
throw new Error("Ошибка авторизации: пользователь не найден или токен недействителен");
|
||||
}
|
||||
throw new Error(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
export default AddPatient;
|
||||
export default addPatient;
|
||||
@ -2,7 +2,7 @@ import axios from "axios";
|
||||
import CONFIG from "../../core/Config.jsx";
|
||||
|
||||
|
||||
const DeletePatient = async (token, patient_id) => {
|
||||
const deletePatient = async (token, patient_id) => {
|
||||
try {
|
||||
const response = await axios.delete(`${CONFIG.BASE_URL}/patients/${patient_id}/`, {
|
||||
headers: {
|
||||
@ -11,9 +11,12 @@ const DeletePatient = async (token, patient_id) => {
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
if (error.response?.status === 401) {
|
||||
throw new Error("Ошибка авторизации: пользователь не найден или токен недействителен");
|
||||
}
|
||||
throw new Error(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default DeletePatient;
|
||||
export default deletePatient;
|
||||
@ -3,11 +3,6 @@ import CONFIG from "../../core/Config.jsx";
|
||||
|
||||
|
||||
const getAllPatients = async (token) => {
|
||||
|
||||
if (!token) {
|
||||
throw new Error("Ошибка авторизации: пользователь не аутентифицирован");
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.get(`${CONFIG.BASE_URL}/patients/`, {
|
||||
headers: {
|
||||
@ -23,4 +18,4 @@ const getAllPatients = async (token) => {
|
||||
}
|
||||
};
|
||||
|
||||
export default getAllPatients;
|
||||
export default getAllPatients;
|
||||
@ -3,10 +3,6 @@ import CONFIG from "../../core/Config.jsx";
|
||||
|
||||
|
||||
const updatePatient = async (token, patientId, patientData) => {
|
||||
if (!token) {
|
||||
throw new Error("Ошибка авторизации: пользователь не аутентифицирован");
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.put(`${CONFIG.BASE_URL}/patients/${patientId}/`, patientData, {
|
||||
headers: {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import {Navigate, Outlet} from "react-router-dom";
|
||||
import {useAuth} from "../../AuthContext.jsx";
|
||||
import {useAuth} from "../AuthContext.jsx";
|
||||
|
||||
const PrivateRoute = () => {
|
||||
const {user} = useAuth();
|
||||
0
web-app/src/components/lenses/LensFormModal.jsx
Normal file
0
web-app/src/components/lenses/LensFormModal.jsx
Normal file
81
web-app/src/components/lenses/LensListCard.jsx
Normal file
81
web-app/src/components/lenses/LensListCard.jsx
Normal file
@ -0,0 +1,81 @@
|
||||
import { Card, Modal, Tooltip } from "antd";
|
||||
import PropTypes from "prop-types";
|
||||
import { DeleteOutlined, EditOutlined, EyeOutlined } from "@ant-design/icons";
|
||||
import { useState } from "react";
|
||||
import LensViewModal from "./LensViewModal.jsx";
|
||||
|
||||
const LensListCard = ({ lens, handleEditLens, handleDeleteLens }) => {
|
||||
const [showModalInfo, setShowModalInfo] = useState(false);
|
||||
|
||||
const deleteLensConfirm = () => {
|
||||
Modal.confirm({
|
||||
title: "Удаление линзы",
|
||||
content: `Вы уверены, что хотите удалить линзу с параметрами ${lens.side} ${lens.diameter}мм?`,
|
||||
okText: "Да, удалить",
|
||||
cancelText: "Отмена",
|
||||
onOk: () => handleDeleteLens(lens.id),
|
||||
});
|
||||
};
|
||||
|
||||
const handleViewLens = () => {
|
||||
setShowModalInfo(true);
|
||||
};
|
||||
|
||||
const actions = [
|
||||
<Tooltip title="Просмотр линзы" key={"viewLens"}>
|
||||
<EyeOutlined onClick={handleViewLens} />
|
||||
</Tooltip>,
|
||||
<Tooltip title="Редактирование линзы" key={"editLens"}>
|
||||
<EditOutlined onClick={() => handleEditLens(lens)} />
|
||||
</Tooltip>,
|
||||
<Tooltip title="Удаление линзы" key={"deleteLens"}>
|
||||
<DeleteOutlined
|
||||
onClick={deleteLensConfirm}
|
||||
style={{ color: "red" }}
|
||||
/>
|
||||
</Tooltip>,
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card
|
||||
type="inner"
|
||||
title={`Линза ${lens.side} ${lens.diameter} мм`}
|
||||
actions={actions}
|
||||
>
|
||||
<p><strong>🔬 Тор:</strong> {lens.tor} D</p>
|
||||
<p><strong>📏 Диаметр:</strong> {lens.diameter} мм</p>
|
||||
<p><strong>🔄 Пресетная рефракция:</strong> {lens.preset_refraction} D</p>
|
||||
<p><strong>⚙️ Перефирийная торичность:</strong> {lens.periphery_toricity} D</p>
|
||||
{lens.issued && <p><strong>✅ Линза выдана</strong></p>}
|
||||
</Card>
|
||||
|
||||
{showModalInfo && (
|
||||
<LensViewModal
|
||||
visible={showModalInfo}
|
||||
onCancel={() => setShowModalInfo(false)}
|
||||
lens={lens}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
LensListCard.propTypes = {
|
||||
lens: PropTypes.shape({
|
||||
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,
|
||||
handleDeleteLens: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default LensListCard;
|
||||
80
web-app/src/components/lenses/LensViewModal.jsx
Normal file
80
web-app/src/components/lenses/LensViewModal.jsx
Normal file
@ -0,0 +1,80 @@
|
||||
import {Button, Col, Modal, Row, Typography, Divider} from "antd";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const {Text, Title} = Typography;
|
||||
|
||||
const LensViewModal = ({visible, onCancel, lens}) => {
|
||||
if (!lens) return null;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title="Просмотр линзы"
|
||||
open={visible}
|
||||
onCancel={onCancel}
|
||||
footer={
|
||||
<Button onClick={onCancel} type="primary">
|
||||
Закрыть
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Row gutter={24}>
|
||||
<Col xs={24} md={12}>
|
||||
<div style={{marginBottom: 12}}>
|
||||
<Title level={5}>🔬 Тор</Title>
|
||||
<Text>{lens.tor} D</Text>
|
||||
</div>
|
||||
|
||||
<div style={{marginBottom: 12}}>
|
||||
<Title level={5}>📏 Диаметр</Title>
|
||||
<Text>{lens.diameter} мм</Text>
|
||||
</div>
|
||||
|
||||
<div style={{marginBottom: 12}}>
|
||||
<Title level={5}>🔄 Пресетная рефракция</Title>
|
||||
<Text>{lens.preset_refraction} D</Text>
|
||||
</div>
|
||||
</Col>
|
||||
|
||||
<Col xs={24} md={12}>
|
||||
<div style={{marginBottom: 12}}>
|
||||
<Title level={5}>⚙️ Перефирийная торичность</Title>
|
||||
<Text>{lens.periphery_toricity} D</Text>
|
||||
</div>
|
||||
|
||||
<div style={{marginBottom: 12}}>
|
||||
<Title level={5}>⛓ Сторона</Title>
|
||||
<Text>{lens.side}</Text>
|
||||
</div>
|
||||
|
||||
<div style={{marginBottom: 12}}>
|
||||
<Title level={5}>✅ Статус выдачи</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>
|
||||
);
|
||||
};
|
||||
|
||||
LensViewModal.propTypes = {
|
||||
visible: PropTypes.bool.isRequired,
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
lens: PropTypes.shape({
|
||||
tor: PropTypes.number.isRequired,
|
||||
diameter: 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;
|
||||
@ -1,12 +1,156 @@
|
||||
import {useState, useEffect} from "react";
|
||||
import {Input, Select, List, FloatButton, Row, Col, Spin} 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 [selectedLens, setSelectedLens] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
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())
|
||||
)
|
||||
).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);
|
||||
}
|
||||
};
|
||||
|
||||
const LensPage = () => {
|
||||
return (
|
||||
<>
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
<div style={{padding: 20}}>
|
||||
<Row gutter={[16, 16]} style={{marginBottom: 20}}>
|
||||
<Col xs={24} sm={16}>
|
||||
<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>
|
||||
</Row>
|
||||
|
||||
export default LensPage;
|
||||
{loading ? (
|
||||
<Spin indicator={<LoadingOutlined style={{fontSize: 48}} spin/>}/>
|
||||
) : (
|
||||
<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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user