diff --git a/web-app/src/AppRouter.jsx b/web-app/src/AppRouter.jsx index de9d263..61d4beb 100644 --- a/web-app/src/AppRouter.jsx +++ b/web-app/src/AppRouter.jsx @@ -5,6 +5,7 @@ import MainLayout from "./layouts/MainLayout.jsx"; import PatientsPage from "./pages/PatientsPage.jsx"; import HomePage from "./pages/HomePage.jsx"; import LensesLayout from "./layouts/LensesLayout.jsx"; +import IssuesPage from "./pages/IssuesPage.jsx"; const AppRouter = () => ( @@ -15,6 +16,7 @@ const AppRouter = () => ( }> }/> }/> + }/> }/> diff --git a/web-app/src/api/lens_issues/AddLensIssue.jsx b/web-app/src/api/lens_issues/AddLensIssue.jsx new file mode 100644 index 0000000..eb318f0 --- /dev/null +++ b/web-app/src/api/lens_issues/AddLensIssue.jsx @@ -0,0 +1,21 @@ +import axios from "axios"; +import CONFIG from "../../core/Config.jsx"; + + +const AddLensIssue = async (token, lens_issue) => { + try { + const response = await axios.post(`${CONFIG.BASE_URL}/lens_issues/`, lens_issue, { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + return response.data; + } catch (error) { + if (error.response?.status === 403) { + throw new Error("Ошибка авторизации: пользователь неайден или токен недействителен"); + } + throw new Error(error.message); + } +}; + +export default AddLensIssue; \ No newline at end of file diff --git a/web-app/src/api/lens_issues/GetAllLensIssues.jsx b/web-app/src/api/lens_issues/GetAllLensIssues.jsx new file mode 100644 index 0000000..28671f8 --- /dev/null +++ b/web-app/src/api/lens_issues/GetAllLensIssues.jsx @@ -0,0 +1,21 @@ +import axios from "axios"; +import CONFIG from "../../core/Config.jsx"; + + +const GetAllLensIssues = async (token) => { + try { + const response = await axios.get(`${CONFIG.BASE_URL}/lens_issues/`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + return response.data; + } catch (error) { + if (error.response?.status === 403) { + throw new Error("Ошибка авторизации: пользователь не найден или токен недействителен"); + } + throw new Error(error.message); + } +}; + +export default GetAllLensIssues; \ No newline at end of file diff --git a/web-app/src/layouts/MainLayout.jsx b/web-app/src/layouts/MainLayout.jsx index 03459b4..2c5d39b 100644 --- a/web-app/src/layouts/MainLayout.jsx +++ b/web-app/src/layouts/MainLayout.jsx @@ -26,7 +26,7 @@ const MainLayout = () => { const menuItems = [ getItem("Главная", "/", ), getItem("Приёмы", "/appointments", ), - getItem("Выдачи линз", "/dispensing", ), + getItem("Выдачи линз", "/issues", ), getItem("Линзы и наборы", "/lenses", ), getItem("Пациенты", "/patients", ), getItem("Рассылки", "/mailing", ), diff --git a/web-app/src/pages/IssuesPage.jsx b/web-app/src/pages/IssuesPage.jsx new file mode 100644 index 0000000..4eff316 --- /dev/null +++ b/web-app/src/pages/IssuesPage.jsx @@ -0,0 +1,126 @@ +import {notification, Spin, Timeline, Input, Modal, Button} from "antd"; +import getAllLensIssues from "../api/lens_issues/GetAllLensIssues.jsx"; +import {useEffect, useState} from "react"; +import {useAuth} from "../AuthContext.jsx"; +import {LoadingOutlined} from "@ant-design/icons"; + +const IssuesPage = () => { + const {user} = useAuth(); + + const [loading, setLoading] = useState(true); + const [lensIssues, setLensIssues] = useState([]); + const [searchTerm, setSearchTerm] = useState(""); + const [selectedIssue, setSelectedIssue] = useState(null); + + const [current, setCurrent] = useState(1); + const [pageSize, setPageSize] = useState(10); + + useEffect(() => { + fetchLensIssuesWithCache(); + }, []); + + useEffect(() => { + const interval = setInterval(fetchLensIssues, 5000); + return () => clearInterval(interval); + }, [user]); + + const fetchLensIssuesWithCache = async () => { + const cachedData = localStorage.getItem("lensIssuesData"); + const cacheTimestamp = localStorage.getItem("lensIssuesTimestamp"); + + if (cachedData && cacheTimestamp && (Date.now() - parseInt(cacheTimestamp)) < 60 * 1000) { + setLensIssues(JSON.parse(cachedData)); + setLoading(false); + } else { + await fetchLensIssues(); + } + }; + + const fetchLensIssues = async () => { + try { + const data = await getAllLensIssues(user.token); + setLensIssues(data); + setLoading(false); + } catch (error) { + console.log(error); + notification.error({ + message: "Ошибка загрузки данных", + description: "Проверьте подключение к сети.", + placement: "topRight", + }); + setLoading(false); + } + }; + + const handleSearch = (e) => { + setSearchTerm(e.target.value.toLowerCase()); + }; + + const filteredIssues = lensIssues.filter(issue => + issue.patient.first_name.toLowerCase().includes(searchTerm) || + issue.patient.last_name.toLowerCase().includes(searchTerm) || + new Date(issue.issue_date).toLocaleDateString().includes(searchTerm) + ); + + return ( +
+ + + {loading ? ( +
+ }/> +
+ ) : ( + ( + { + key: issue.id, + label: new Date(issue.issue_date).toLocaleDateString(), + children: ( +
+

Дата выдачи: {new Date(issue.issue_date).toLocaleDateString()}

+

Пациент: {issue.patient.last_name} {issue.patient.first_name}

+

Врач: {issue.doctor.last_name} {issue.doctor.first_name}

+

Линза: {issue.lens.side}, Диаметр: {issue.lens.diameter}

+ +
+ ), + } + ))} + pagination={{ + current, + pageSize, + showSizeChanger: true, + pageSizeOptions: ["5", "10", "20", "50"], + onChange: (page, newPageSize) => { + setCurrent(page); + setPageSize(newPageSize); + }, + }} + /> + )} + + setSelectedIssue(null)} + footer={null} + > + {selectedIssue && ( +
+

Дата выдачи: {new Date(selectedIssue.issue_date).toLocaleDateString()}

+

Пациент: {selectedIssue.patient.last_name} {selectedIssue.patient.first_name}

+

Врач: {selectedIssue.doctor.last_name} {selectedIssue.doctor.first_name}

+

Линза: {selectedIssue.lens.side}, Диаметр: {selectedIssue.lens.diameter}

+
+ )} +
+
+ ); +}; + +export default IssuesPage; diff --git a/web-app/src/pages/LensesPage.jsx b/web-app/src/pages/LensesPage.jsx index c33ebc4..7b06da4 100644 --- a/web-app/src/pages/LensesPage.jsx +++ b/web-app/src/pages/LensesPage.jsx @@ -10,7 +10,7 @@ import { Button, Form, InputNumber, - Card, Grid, notification, Table, Popconfirm + Card, Grid, notification, Table, Popconfirm, Tooltip } from "antd"; import {LoadingOutlined, PlusOutlined, DownOutlined, UpOutlined} from "@ant-design/icons"; import LensCard from "../components/lenses/LensListCard.jsx"; @@ -75,8 +75,6 @@ const LensesPage = () => { }; const fetchLenses = async () => { - if (!user || !user.token) return; - try { const data = await getAllLenses(user.token); setLenses(data); @@ -334,14 +332,18 @@ const LensesPage = () => { - + + diff --git a/web-app/src/pages/PatientsPage.jsx b/web-app/src/pages/PatientsPage.jsx index fbd3b82..4a38d12 100644 --- a/web-app/src/pages/PatientsPage.jsx +++ b/web-app/src/pages/PatientsPage.jsx @@ -304,14 +304,18 @@ const PatientsPage = () => { - + +