сделал заготовку страницы для выдачи линз
This commit is contained in:
parent
321dfd5628
commit
a2fdc3f115
@ -5,6 +5,7 @@ import MainLayout from "./layouts/MainLayout.jsx";
|
|||||||
import PatientsPage from "./pages/PatientsPage.jsx";
|
import PatientsPage from "./pages/PatientsPage.jsx";
|
||||||
import HomePage from "./pages/HomePage.jsx";
|
import HomePage from "./pages/HomePage.jsx";
|
||||||
import LensesLayout from "./layouts/LensesLayout.jsx";
|
import LensesLayout from "./layouts/LensesLayout.jsx";
|
||||||
|
import IssuesPage from "./pages/IssuesPage.jsx";
|
||||||
|
|
||||||
|
|
||||||
const AppRouter = () => (
|
const AppRouter = () => (
|
||||||
@ -15,6 +16,7 @@ const AppRouter = () => (
|
|||||||
<Route element={<MainLayout/>}>
|
<Route element={<MainLayout/>}>
|
||||||
<Route path={"/patients"} element={<PatientsPage/>}/>
|
<Route path={"/patients"} element={<PatientsPage/>}/>
|
||||||
<Route path={"/lenses"} element={<LensesLayout/>}/>
|
<Route path={"/lenses"} element={<LensesLayout/>}/>
|
||||||
|
<Route path={"/issues"} element={<IssuesPage/>}/>
|
||||||
<Route path={"/"} element={<HomePage/>}/>
|
<Route path={"/"} element={<HomePage/>}/>
|
||||||
</Route>
|
</Route>
|
||||||
</Route>
|
</Route>
|
||||||
|
|||||||
21
web-app/src/api/lens_issues/AddLensIssue.jsx
Normal file
21
web-app/src/api/lens_issues/AddLensIssue.jsx
Normal file
@ -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;
|
||||||
21
web-app/src/api/lens_issues/GetAllLensIssues.jsx
Normal file
21
web-app/src/api/lens_issues/GetAllLensIssues.jsx
Normal file
@ -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;
|
||||||
@ -26,7 +26,7 @@ const MainLayout = () => {
|
|||||||
const menuItems = [
|
const menuItems = [
|
||||||
getItem("Главная", "/", <HomeOutlined/>),
|
getItem("Главная", "/", <HomeOutlined/>),
|
||||||
getItem("Приёмы", "/appointments", <CalendarOutlined/>),
|
getItem("Приёмы", "/appointments", <CalendarOutlined/>),
|
||||||
getItem("Выдачи линз", "/dispensing", <DatabaseOutlined/>),
|
getItem("Выдачи линз", "/issues", <DatabaseOutlined/>),
|
||||||
getItem("Линзы и наборы", "/lenses", <FolderViewOutlined/>),
|
getItem("Линзы и наборы", "/lenses", <FolderViewOutlined/>),
|
||||||
getItem("Пациенты", "/patients", <TeamOutlined/>),
|
getItem("Пациенты", "/patients", <TeamOutlined/>),
|
||||||
getItem("Рассылки", "/mailing", <MessageOutlined/>),
|
getItem("Рассылки", "/mailing", <MessageOutlined/>),
|
||||||
|
|||||||
126
web-app/src/pages/IssuesPage.jsx
Normal file
126
web-app/src/pages/IssuesPage.jsx
Normal file
@ -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 (
|
||||||
|
<div style={{padding: 20}}>
|
||||||
|
<Input.Search
|
||||||
|
placeholder="Поиск по пациенту или дате"
|
||||||
|
onChange={handleSearch}
|
||||||
|
style={{marginBottom: 20, width: 300}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{loading ? (
|
||||||
|
<div style={{display: "flex", justifyContent: "center", alignItems: "center", height: "100vh"}}>
|
||||||
|
<Spin indicator={<LoadingOutlined style={{fontSize: 64, color: "#1890ff"}} spin/>}/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Timeline
|
||||||
|
items={filteredIssues.map((issue) => (
|
||||||
|
{
|
||||||
|
key: issue.id,
|
||||||
|
label: new Date(issue.issue_date).toLocaleDateString(),
|
||||||
|
children: (
|
||||||
|
<div>
|
||||||
|
<p><b>Дата выдачи:</b> {new Date(issue.issue_date).toLocaleDateString()}</p>
|
||||||
|
<p><b>Пациент:</b> {issue.patient.last_name} {issue.patient.first_name}</p>
|
||||||
|
<p><b>Врач:</b> {issue.doctor.last_name} {issue.doctor.first_name}</p>
|
||||||
|
<p><b>Линза:</b> {issue.lens.side}, Диаметр: {issue.lens.diameter}</p>
|
||||||
|
<Button type="link" onClick={() => setSelectedIssue(issue)}>Подробнее</Button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}
|
||||||
|
))}
|
||||||
|
pagination={{
|
||||||
|
current,
|
||||||
|
pageSize,
|
||||||
|
showSizeChanger: true,
|
||||||
|
pageSizeOptions: ["5", "10", "20", "50"],
|
||||||
|
onChange: (page, newPageSize) => {
|
||||||
|
setCurrent(page);
|
||||||
|
setPageSize(newPageSize);
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
visible={!!selectedIssue}
|
||||||
|
title="Детали выдачи линзы"
|
||||||
|
onCancel={() => setSelectedIssue(null)}
|
||||||
|
footer={null}
|
||||||
|
>
|
||||||
|
{selectedIssue && (
|
||||||
|
<div>
|
||||||
|
<p><b>Дата выдачи:</b> {new Date(selectedIssue.issue_date).toLocaleDateString()}</p>
|
||||||
|
<p><b>Пациент:</b> {selectedIssue.patient.last_name} {selectedIssue.patient.first_name}</p>
|
||||||
|
<p><b>Врач:</b> {selectedIssue.doctor.last_name} {selectedIssue.doctor.first_name}</p>
|
||||||
|
<p><b>Линза:</b> {selectedIssue.lens.side}, Диаметр: {selectedIssue.lens.diameter}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default IssuesPage;
|
||||||
@ -10,7 +10,7 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Form,
|
Form,
|
||||||
InputNumber,
|
InputNumber,
|
||||||
Card, Grid, notification, Table, Popconfirm
|
Card, Grid, notification, Table, Popconfirm, Tooltip
|
||||||
} from "antd";
|
} from "antd";
|
||||||
import {LoadingOutlined, PlusOutlined, DownOutlined, UpOutlined} from "@ant-design/icons";
|
import {LoadingOutlined, PlusOutlined, DownOutlined, UpOutlined} from "@ant-design/icons";
|
||||||
import LensCard from "../components/lenses/LensListCard.jsx";
|
import LensCard from "../components/lenses/LensListCard.jsx";
|
||||||
@ -75,8 +75,6 @@ const LensesPage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const fetchLenses = async () => {
|
const fetchLenses = async () => {
|
||||||
if (!user || !user.token) return;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await getAllLenses(user.token);
|
const data = await getAllLenses(user.token);
|
||||||
setLenses(data);
|
setLenses(data);
|
||||||
@ -334,14 +332,18 @@ const LensesPage = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={24} md={3} sm={4} xl={2}>
|
<Col xs={24} md={3} sm={4} xl={2}>
|
||||||
<Select
|
<Tooltip
|
||||||
value={viewMode}
|
title={"Отображение линз"}
|
||||||
onChange={(value) => setViewMode(value)}
|
|
||||||
style={{width: "100%"}}
|
|
||||||
>
|
>
|
||||||
<Option value={"tile"}>Плиткой</Option>
|
<Select
|
||||||
<Option value={"table"}>Таблицей</Option>
|
value={viewMode}
|
||||||
</Select>
|
onChange={(value) => setViewMode(value)}
|
||||||
|
style={{width: "100%"}}
|
||||||
|
>
|
||||||
|
<Option value={"tile"}>Плиткой</Option>
|
||||||
|
<Option value={"table"}>Таблицей</Option>
|
||||||
|
</Select>
|
||||||
|
</Tooltip>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
|
|||||||
@ -304,14 +304,18 @@ const PatientsPage = () => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={24} md={5} sm={8} xl={6}>
|
<Col xs={24} md={5} sm={8} xl={6}>
|
||||||
<Select
|
<Tooltip
|
||||||
value={viewMode}
|
title={"Отображение пациентов"}
|
||||||
onChange={(value) => setViewMode(value)}
|
|
||||||
style={{width: "100%"}}
|
|
||||||
>
|
>
|
||||||
<Option value={"tile"}>Плиткой</Option>
|
<Select
|
||||||
<Option value={"table"}>Таблицей</Option>
|
value={viewMode}
|
||||||
</Select>
|
onChange={(value) => setViewMode(value)}
|
||||||
|
style={{width: "100%"}}
|
||||||
|
>
|
||||||
|
<Option value={"tile"}>Плиткой</Option>
|
||||||
|
<Option value={"table"}>Таблицей</Option>
|
||||||
|
</Select>
|
||||||
|
</Tooltip>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user