сделал заготовку под страницу с линзами

This commit is contained in:
Андрей Дувакин 2025-02-18 13:05:15 +05:00
parent ddd551adb4
commit 8b7c427602
16 changed files with 411 additions and 24 deletions

View File

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

View File

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

View File

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

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

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

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

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

View File

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

View File

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

View File

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

View File

@ -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: {

View File

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

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

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

View File

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