feat: Исправление фильтрации и UI для выдачи линз

This commit is contained in:
Андрей Дувакин 2025-06-08 14:35:03 +05:00
parent b8bc7023a0
commit 0a7fd16a29
6 changed files with 56 additions and 61 deletions

View File

@ -1,3 +1,4 @@
from datetime import date
from typing import Optional, Sequence, Tuple, Literal from typing import Optional, Sequence, Tuple, Literal
from sqlalchemy import select, desc, or_, func, asc from sqlalchemy import select, desc, or_, func, asc
@ -17,8 +18,8 @@ class LensIssuesRepository:
limit: int = 10, limit: int = 10,
search: Optional[str] = None, search: Optional[str] = None,
sort_order: Literal["asc", "desc"] = "desc", sort_order: Literal["asc", "desc"] = "desc",
start_date: Optional[str] = None, start_date: Optional[date] = None,
end_date: Optional[str] = None end_date: Optional[date] = None
) -> Tuple[Sequence[LensIssue], int]: ) -> Tuple[Sequence[LensIssue], int]:
stmt = ( stmt = (
select(LensIssue) select(LensIssue)
@ -62,6 +63,7 @@ class LensIssuesRepository:
) )
if start_date: if start_date:
count_stmt = count_stmt.filter(LensIssue.issue_date >= start_date) count_stmt = count_stmt.filter(LensIssue.issue_date >= start_date)
if end_date: if end_date:
count_stmt = count_stmt.filter(LensIssue.issue_date <= end_date) count_stmt = count_stmt.filter(LensIssue.issue_date <= end_date)

View File

@ -1,3 +1,4 @@
from datetime import date
from typing import Optional, Literal from typing import Optional, Literal
from fastapi import APIRouter, Depends, Query from fastapi import APIRouter, Depends, Query
@ -23,8 +24,8 @@ async def get_all_lens_issues(
page_size: int = Query(10, ge=1, le=100), page_size: int = Query(10, ge=1, le=100),
search: Optional[str] = Query(None), search: Optional[str] = Query(None),
sort_order: Literal["asc", "desc"] = Query("desc"), sort_order: Literal["asc", "desc"] = Query("desc"),
start_date: Optional[str] = Query(None), start_date: Optional[date] = Query(None),
end_date: Optional[str] = Query(None), end_date: Optional[date] = Query(None),
db: AsyncSession = Depends(get_db), db: AsyncSession = Depends(get_db),
user=Depends(get_current_user), user=Depends(get_current_user),
): ):

View File

@ -1,3 +1,4 @@
from datetime import date
from typing import Optional, Literal, Tuple from typing import Optional, Literal, Tuple
from fastapi import HTTPException from fastapi import HTTPException
@ -28,8 +29,8 @@ class LensIssuesService:
limit: int = 10, limit: int = 10,
search: Optional[str] = None, search: Optional[str] = None,
sort_order: Literal["asc", "desc"] = "desc", sort_order: Literal["asc", "desc"] = "desc",
start_date: Optional[str] = None, start_date: Optional[date] = None,
end_date: Optional[str] = None end_date: Optional[date] = None
) -> Tuple[list[LensIssueEntity], int]: ) -> Tuple[list[LensIssueEntity], int]:
lens_issues, total_count = await self.lens_issues_repository.get_all( lens_issues, total_count = await self.lens_issues_repository.get_all(
skip=skip, skip=skip,

View File

@ -25,7 +25,6 @@ import timezone from "dayjs/plugin/timezone";
dayjs.extend(utc); dayjs.extend(utc);
dayjs.extend(timezone); dayjs.extend(timezone);
dayjs.tz.setDefault("Europe/Moscow");
const { useBreakpoint } = Grid; const { useBreakpoint } = Grid;
@ -35,13 +34,13 @@ const useAppointments = () => {
const { collapsed, siderWidth, hovered, selectedAppointment } = useSelector(state => state.appointmentsUI); const { collapsed, siderWidth, hovered, selectedAppointment } = useSelector(state => state.appointmentsUI);
const screens = useBreakpoint(); const screens = useBreakpoint();
const [currentMonth, setCurrentMonth] = useState(dayjs().tz("Europe/Moscow").startOf('month')); const [currentMonth, setCurrentMonth] = useState(dayjs().startOf('month'));
const startDate = currentMonth.startOf('month').tz("Europe/Moscow").format('YYYY-MM-DD'); const startDate = currentMonth.startOf('month').format('YYYY-MM-DD');
const endDate = currentMonth.endOf('month').tz("Europe/Moscow").format('YYYY-MM-DD'); const endDate = currentMonth.endOf('month').format('YYYY-MM-DD');
const handleMonthChange = (newMonth) => { const handleMonthChange = (newMonth) => {
setCurrentMonth(dayjs(newMonth).tz("Europe/Moscow").startOf('month')); setCurrentMonth(dayjs(newMonth).startOf('month'));
}; };
const { const {
@ -155,7 +154,7 @@ const useAppointments = () => {
const upcomingEvents = useMemo(() => const upcomingEvents = useMemo(() =>
[...upcomingAppointments, ...upcomingScheduledAppointments] [...upcomingAppointments, ...upcomingScheduledAppointments]
.sort((a, b) => dayjs(a.appointment_datetime || a.scheduled_datetime).tz("Europe/Moscow") - dayjs(b.appointment_datetime || b.scheduled_datetime).tz("Europe/Moscow")) .sort((a, b) => dayjs(a.appointment_datetime || a.scheduled_datetime) - dayjs(b.appointment_datetime || b.scheduled_datetime))
.slice(0, 5), .slice(0, 5),
[upcomingAppointments, upcomingScheduledAppointments] [upcomingAppointments, upcomingScheduledAppointments]
); );

View File

@ -13,17 +13,17 @@ import {
Pagination, Pagination,
Result Result
} from "antd"; } from "antd";
import { DatabaseOutlined, PlusOutlined, UnorderedListOutlined } from "@ant-design/icons"; import {DatabaseOutlined, PlusOutlined, UnorderedListOutlined} from "@ant-design/icons";
import LensIssueViewModal from "./Components/LensIssueViewModal/LensIssueViewModal.jsx"; import LensIssueViewModal from "./Components/LensIssueViewModal/LensIssueViewModal.jsx";
import dayjs from "dayjs"; import dayjs from "dayjs";
import LensIssueFormModal from "./Components/LensIssueFormModal/LensIssueFormModal.jsx"; import LensIssueFormModal from "./Components/LensIssueFormModal/LensIssueFormModal.jsx";
import SelectViewMode from "../../Widgets/SelectViewMode/SelectViewMode.jsx"; import SelectViewMode from "../../Widgets/SelectViewMode/SelectViewMode.jsx";
import LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.jsx"; import LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.jsx";
import useIssues from "./useIssues.js"; import useIssues from "./useIssues.js";
import { useMemo } from "react"; import {useMemo} from "react";
const { Title } = Typography; const {Title} = Typography;
const { useBreakpoint } = Grid; const {useBreakpoint} = Grid;
const IssuesPage = () => { const IssuesPage = () => {
const issuesData = useIssues(); const issuesData = useIssues();
@ -33,12 +33,12 @@ const IssuesPage = () => {
{ {
value: "table", value: "table",
label: "Таблица", label: "Таблица",
icon: <DatabaseOutlined style={issuesData.viewModIconStyle} />, icon: <DatabaseOutlined style={issuesData.viewModIconStyle}/>,
}, },
{ {
value: "timeline", value: "timeline",
label: "Лента", label: "Лента",
icon: <UnorderedListOutlined style={issuesData.viewModIconStyle} />, icon: <UnorderedListOutlined style={issuesData.viewModIconStyle}/>,
}, },
]; ];
@ -87,53 +87,43 @@ const IssuesPage = () => {
/> />
); );
const TimeLineView = () => {
const timeLineItems = useMemo(() => issuesData.issues.map(issue => ({ const timeLineItems = useMemo(() => issuesData.issues.map(issue => ({
label: dayjs(issue.issue_date).format("DD.MM.YYYY"), label: dayjs(issue.issue_date).format("DD.MM.YYYY"),
children: ( children: (
<Row gutter={[16, 16]} align="middle"> <Row gutter={[16, 16]} align="middle">
<Col xs={24} sm={24} md={13}> <Col xs={24} sm={24} md={13}>
<p style={{ textAlign: "right" }}>Пациент: {issue.patient.last_name} {issue.patient.first_name}</p> <p style={{textAlign: "right"}}>Пациент: {issue.patient.last_name} {issue.patient.first_name}</p>
</Col> </Col>
<Col xs={24} sm={24} md={5}> <Col xs={24} sm={24} md={5}>
<p style={{ textAlign: "right" }}>Линза: {issue.lens.side} {issue.lens.diameter}</p> <p style={{textAlign: "right"}}>Линза: {issue.lens.side} {issue.lens.diameter}</p>
</Col> </Col>
<Col xs={24} sm={24} md={6}> <Col xs={24} sm={24} md={6}>
<Button <Button
type="dashed" type="dashed"
onClick={() => issuesData.handleSelectIssue(issue)} onClick={() => issuesData.handleSelectIssue(issue)}
style={{ marginRight: 40 }} style={{marginRight: 40}}
> >
Подробнее Подробнее
</Button> </Button>
</Col> </Col>
</Row> </Row>
), ),
})), [issuesData]); })), []);
const TimeLineView = () => {
const paginatedItems = timeLineItems.slice(
(issuesData.currentPage - 1) * issuesData.pageSize,
issuesData.currentPage * issuesData.pageSize
);
return ( return (
<> <>
<Timeline <Timeline
items={paginatedItems} items={timeLineItems}
mode={screens.xs ? "left" : "right"} mode={screens.xs ? "left" : "right"}
/> />
<Row <Row
style={{ textAlign: "center", marginTop: 20 }} style={{textAlign: "center", marginTop: 20}}
align="middle" align="middle"
justify="end" justify="end"
> >
<Pagination <Pagination
current={issuesData.currentPage} {...issuesData.pagination}
pageSize={issuesData.pageSize}
total={issuesData.total_count}
onChange={issuesData.handlePaginationChange}
showSizeChanger={true}
pageSizeOptions={["5", "10", "20", "50"]}
/> />
</Row> </Row>
</> </>
@ -150,14 +140,13 @@ const IssuesPage = () => {
return ( return (
<div style={issuesData.containerStyle}> <div style={issuesData.containerStyle}>
<Title level={1}><DatabaseOutlined /> Выдача линз</Title> <Title level={1}><DatabaseOutlined/> Выдача линз</Title>
<Row gutter={[16, 16]} style={issuesData.filterBarStyle}> <Row gutter={[16, 16]} style={issuesData.filterBarStyle}>
<Col xs={24} sm={24} md={12}> <Col xs={24} sm={24} md={12}>
<Input <Input
placeholder="Поиск по пациенту или врачу" placeholder="Поиск по пациенту или врачу"
value={issuesData.tempSearchText} value={issuesData.tempSearchText}
onChange={(e) => issuesData.handleSetTempSearchText(e.target.value)} onChange={(e) => issuesData.handleSetTempSearchText(e.target.value)}
onPressEnter={issuesData.handleSearch}
style={issuesData.formItemStyle} style={issuesData.formItemStyle}
allowClear allowClear
onClear={issuesData.handleClearSearch} onClear={issuesData.handleClearSearch}
@ -199,15 +188,15 @@ const IssuesPage = () => {
</Col> </Col>
</Row> </Row>
{issuesData.isLoading ? ( {issuesData.isLoading ? (
<LoadingIndicator /> <LoadingIndicator/>
) : issuesData.viewMode === "table" ? ( ) : issuesData.viewMode === "table" ? (
<TableView /> <TableView/>
) : ( ) : (
<TimeLineView /> <TimeLineView/>
)} )}
<FloatButton <FloatButton
icon={<PlusOutlined />} icon={<PlusOutlined/>}
type="primary" type="primary"
onClick={issuesData.handleAddIssue} onClick={issuesData.handleAddIssue}
tooltip="Добавить выдачу линзы" tooltip="Добавить выдачу линзы"

View File

@ -105,7 +105,10 @@ const useIssues = () => {
borderRadius: 8 borderRadius: 8
}; };
const handleSetTempSearchText = (value) => setTempSearchText(value); const handleSetTempSearchText = (value) => {
setTempSearchText(value);
handleSearch();
};
const handleSearch = () => { const handleSearch = () => {
dispatch(setSearchText(tempSearchText)); dispatch(setSearchText(tempSearchText));