feat: Добавлена сортировка пациентов по фамилии

This commit is contained in:
Андрей Дувакин 2025-06-07 16:07:24 +05:00
parent 342e021211
commit 05bfec81fc
8 changed files with 356 additions and 233 deletions

View File

@ -1,6 +1,6 @@
from typing import Sequence, Optional from typing import Sequence, Optional, Tuple, Literal
from sqlalchemy import select, desc from sqlalchemy import select, desc, or_, func
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from app.domain.models import Lens from app.domain.models import Lens
@ -10,10 +10,80 @@ class LensesRepository:
def __init__(self, db: AsyncSession): def __init__(self, db: AsyncSession):
self.db = db self.db = db
async def get_all(self) -> Sequence[Lens]: async def get_all(
stmt = select(Lens).order_by(Lens.id) self,
skip: int = 0,
limit: int = 10,
search: str = None,
sort_order: Literal["asc", "desc"] = "desc",
tor: float = None,
diameter: float = None,
preset_refraction: float = None,
periphery_toricity: float = None,
side: str = "all",
issued: bool = None,
trial: float = None,
) -> Tuple[Sequence[Lens], int]:
stmt = select(Lens)
count_stmt = select(func.count()).select_from(Lens)
if search:
search = f"%{search}%"
stmt = stmt.filter(
or_(
Lens.tor.cast(str).ilike(search),
Lens.diameter.cast(str).ilike(search),
Lens.preset_refraction.cast(str).ilike(search),
Lens.periphery_toricity.cast(str).ilike(search),
Lens.side.ilike(search),
Lens.fvc.cast(str).ilike(search),
Lens.trial.cast(str).ilike(search),
)
)
count_stmt = count_stmt.filter(
or_(
Lens.tor.cast(str).ilike(search),
Lens.diameter.cast(str).ilike(search),
Lens.preset_refraction.cast(str).ilike(search),
Lens.periphery_toricity.cast(str).ilike(search),
Lens.side.ilike(search),
Lens.fvc.cast(str).ilike(search),
Lens.trial.cast(str).ilike(search),
)
)
if tor is not None:
stmt = stmt.filter(Lens.tor == tor)
count_stmt = count_stmt.filter(Lens.tor == tor)
if diameter is not None:
stmt = stmt.filter(Lens.diameter == diameter)
count_stmt = count_stmt.filter(Lens.diameter == diameter)
if preset_refraction is not None:
stmt = stmt.filter(Lens.preset_refraction == preset_refraction)
count_stmt = count_stmt.filter(Lens.preset_refraction == preset_refraction)
if periphery_toricity is not None:
stmt = stmt.filter(Lens.periphery_toricity == periphery_toricity)
count_stmt = count_stmt.filter(Lens.periphery_toricity == periphery_toricity)
if side != "all":
stmt = stmt.filter(Lens.side == side)
count_stmt = count_stmt.filter(Lens.side == side)
if issued is not None:
stmt = stmt.filter(Lens.issued == issued)
count_stmt = count_stmt.filter(Lens.issued == issued)
if trial is not None:
stmt = stmt.filter(Lens.trial == trial)
count_stmt = count_stmt.filter(Lens.trial == trial)
stmt = stmt.order_by(Lens.id.desc() if sort_order == "desc" else Lens.id.asc())
stmt = stmt.offset(skip).limit(limit)
result = await self.db.execute(stmt) result = await self.db.execute(stmt)
return result.scalars().all() lenses = result.scalars().all()
count_result = await self.db.execute(count_stmt)
total_count = count_result.scalar()
return lenses, total_count
async def get_all_not_issued(self) -> Sequence[Lens]: async def get_all_not_issued(self) -> Sequence[Lens]:
stmt = select(Lens).filter_by(issued=False).order_by(desc(Lens.id)) stmt = select(Lens).filter_by(issued=False).order_by(desc(Lens.id))

View File

@ -1,8 +1,11 @@
from fastapi import APIRouter, Depends from typing import Literal
from fastapi import APIRouter, Depends, Query
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from app.database.session import get_db from app.database.session import get_db
from app.domain.entities.lens import LensEntity from app.domain.entities.lens import LensEntity
from app.domain.entities.responses.paginated_lens import PaginatedLensesResponseEntity
from app.infrastructure.dependencies import get_current_user from app.infrastructure.dependencies import get_current_user
from app.infrastructure.lenses_service import LensesService from app.infrastructure.lenses_service import LensesService
@ -11,16 +14,40 @@ router = APIRouter()
@router.get( @router.get(
"/", "/",
response_model=list[LensEntity], response_model=PaginatedLensesResponseEntity,
summary="Get all lenses", summary="Get all lenses with pagination and filtering",
description="Returns a list of all lenses", description="Returns a paginated list of lenses with optional search, sorting, and advanced filtering",
) )
async def get_all_lenses( async def get_all_lenses(
db: AsyncSession = Depends(get_db), page: int = Query(1, ge=1, description="Page number"),
user=Depends(get_current_user), page_size: int = Query(10, ge=1, le=100, description="Number of lenses per page"),
search: str = Query(None, description="Search term for filtering lenses"),
sort_order: Literal["asc", "desc"] = Query("desc", description="Sort order by id (asc or desc)"),
tor: float = Query(None, description="Filter by tor"),
diameter: float = Query(None, description="Filter by diameter"),
preset_refraction: float = Query(None, description="Filter by preset refraction"),
periphery_toricity: float = Query(None, description="Filter by periphery toricity"),
side: Literal["левая", "правая", "all"] = Query("all", description="Filter by side"),
issued: bool = Query(None, description="Filter by issued status"),
trial: float = Query(None, description="Filter by trial"),
db: AsyncSession = Depends(get_db),
user=Depends(get_current_user),
): ):
lenses_service = LensesService(db) lenses_service = LensesService(db)
return await lenses_service.get_all_lenses() lenses, total_count = await lenses_service.get_all_lenses(
page=page,
page_size=page_size,
search=search,
sort_order=sort_order,
tor=tor,
diameter=diameter,
preset_refraction=preset_refraction,
periphery_toricity=periphery_toricity,
side=side,
issued=issued,
trial=trial,
)
return {"lenses": lenses, "total_count": total_count}
@router.get( @router.get(

View File

@ -0,0 +1,8 @@
from pydantic import BaseModel
from app.domain.entities.lens import LensEntity
class PaginatedLensesResponseEntity(BaseModel):
lenses: list[LensEntity]
total_count: int

View File

@ -1,4 +1,4 @@
from typing import Optional from typing import Optional, Tuple, Literal
from fastapi import HTTPException from fastapi import HTTPException
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
@ -16,13 +16,35 @@ class LensesService:
self.lenses_repository = LensesRepository(db) self.lenses_repository = LensesRepository(db)
self.lens_types_repository = LensTypesRepository(db) self.lens_types_repository = LensTypesRepository(db)
async def get_all_lenses(self) -> list[LensEntity]: async def get_all_lenses(
lenses = await self.lenses_repository.get_all() self,
page: int = 1,
return [ page_size: int = 10,
self.model_to_entity(lens) search: str = None,
for lens in lenses sort_order: Literal["asc", "desc"] = "desc",
] tor: float = None,
diameter: float = None,
preset_refraction: float = None,
periphery_toricity: float = None,
side: str = "all",
issued: bool = None,
trial: float = None,
) -> Tuple[list[LensEntity], int]:
skip = (page - 1) * page_size
lenses, total_count = await self.lenses_repository.get_all(
skip=skip,
limit=page_size,
search=search,
sort_order=sort_order,
tor=tor,
diameter=diameter,
preset_refraction=preset_refraction,
periphery_toricity=periphery_toricity,
side=side,
issued=issued,
trial=trial,
)
return [self.model_to_entity(lens) for lens in lenses], total_count
async def get_all_not_issued_lenses(self) -> list[LensEntity]: async def get_all_not_issued_lenses(self) -> list[LensEntity]:
lenses = await self.lenses_repository.get_all_not_issued() lenses = await self.lenses_repository.get_all_not_issued()

View File

@ -1,5 +1,5 @@
import { Component } from "react"; import { Component } from "react";
import {Result} from "antd"; import {Alert, Result} from "antd";
class ErrorBoundary extends Component { class ErrorBoundary extends Component {
state = { hasError: false }; state = { hasError: false };
@ -14,7 +14,12 @@ class ErrorBoundary extends Component {
render() { render() {
if (this.state.hasError) { if (this.state.hasError) {
return <Result status="500" title="500" subTitle="Something went wrong."/>; return (
<>
<Result status="500" title="500" subTitle="Произошла ошибка в работе приложения."/>
</>
);
} }
return this.props.children; return this.props.children;
} }

View File

@ -12,7 +12,8 @@ import {
Grid, Grid,
Table, Table,
Popconfirm, Popconfirm,
Typography, Result Typography,
Result
} from "antd"; } from "antd";
import { import {
PlusOutlined, PlusOutlined,
@ -27,28 +28,25 @@ import LensFormModal from "./Components/LensFormModal/LensFormModal.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 useLenses from "./useLenses.js"; import useLenses from "./useLenses.js";
import useLensesUI from "./useLensesUI.js";
const {Option} = Select; const { Option } = Select;
const {useBreakpoint} = Grid; const { useBreakpoint } = Grid;
const {Title} = Typography; const { Title } = Typography;
const LensesTab = () => { const LensesTab = () => {
const lensesData = useLenses(); const lensesData = useLenses();
const lensesUI = useLensesUI(lensesData.lenses);
const screens = useBreakpoint(); const screens = useBreakpoint();
const viewModes = [ const viewModes = [
{ {
value: "tile", value: "tile",
label: "Плитка", label: "Плитка",
icon: <BuildOutlined style={lensesUI.viewModIconStyle}/> icon: <BuildOutlined style={lensesData.viewModIconStyle}/>
}, },
{ {
value: "table", value: "table",
label: "Таблица", label: "Таблица",
icon: <TableOutlined style={lensesUI.viewModIconStyle}/> icon: <TableOutlined style={lensesData.viewModIconStyle}/>
} }
]; ];
@ -94,8 +92,8 @@ const LensesTab = () => {
dataIndex: "side", dataIndex: "side",
key: "side", key: "side",
filters: [ filters: [
{text: "Левая", value: "левая"}, { text: "Левая", value: "левая" },
{text: "Правая", value: "правая"}, { text: "Правая", value: "правая" },
], ],
onFilter: (value, record) => record.side === value, onFilter: (value, record) => record.side === value,
}, },
@ -105,8 +103,8 @@ const LensesTab = () => {
key: "issued", key: "issued",
render: (issued) => (issued ? "Да" : "Нет"), render: (issued) => (issued ? "Да" : "Нет"),
filters: [ filters: [
{text: "Да", value: true}, { text: "Да", value: true },
{text: "Нет", value: false}, { text: "Нет", value: false },
], ],
onFilter: (value, record) => record.issued === value, onFilter: (value, record) => record.issued === value,
}, },
@ -115,9 +113,8 @@ const LensesTab = () => {
key: "actions", key: "actions",
fixed: 'right', fixed: 'right',
render: (text, record) => ( render: (text, record) => (
<div style={lensesUI.tableActionButtonsStyle}> <div style={lensesData.tableActionButtonsStyle}>
<Button onClick={() => lensesUI.handleEditLens(record)}>Изменить</Button> <Button onClick={() => lensesData.handleEditLens(record)}>Изменить</Button>
<Popconfirm <Popconfirm
title="Вы уверены, что хотите удалить линзу?" title="Вы уверены, что хотите удалить линзу?"
onConfirm={() => lensesData.handleDeleteLens(record.id)} onConfirm={() => lensesData.handleDeleteLens(record.id)}
@ -145,22 +142,22 @@ const LensesTab = () => {
); );
return ( return (
<div style={lensesUI.containerStyle}> <div style={lensesData.containerStyle}>
<Title level={1}><FolderViewOutlined/> Линзы</Title> <Title level={1}><FolderViewOutlined/> Линзы</Title>
<Row gutter={[16, 16]} style={lensesUI.filterBarStyle}> <Row gutter={[16, 16]} style={lensesData.filterBarStyle}>
<Col xs={24} md={24} sm={24} xl={15}> <Col xs={24} md={24} sm={24} xl={15}>
<Input <Input
placeholder="Поиск линзы" placeholder="Поиск линзы"
value={lensesUI.searchText} value={lensesData.searchText}
onChange={(e) => lensesUI.handleSetSearchText(e.target.value)} onChange={(e) => lensesData.handleSetSearchText(e.target.value)}
style={lensesUI.formItemStyle} style={lensesData.formItemStyle}
allowClear allowClear
/> />
</Col> </Col>
<Col xs={24} md={12} sm={24} xl={5}> <Col xs={24} md={12} sm={24} xl={5}>
<Button <Button
onClick={lensesUI.toggleAdvancedSearch} onClick={lensesData.toggleAdvancedSearch}
icon={lensesUI.showAdvancedSearch ? <UpOutlined/> : <DownOutlined/>} icon={lensesData.showAdvancedSearch ? <UpOutlined/> : <DownOutlined/>}
block block
> >
Расширенный поиск Расширенный поиск
@ -168,8 +165,8 @@ const LensesTab = () => {
</Col> </Col>
<Col xs={24} md={12} sm={24} xl={4}> <Col xs={24} md={12} sm={24} xl={4}>
<SelectViewMode <SelectViewMode
viewMode={lensesUI.viewMode} viewMode={lensesData.viewMode}
setViewMode={lensesUI.handleSetViewMode} setViewMode={lensesData.handleSetViewMode}
localStorageKey={"viewModeLenses"} localStorageKey={"viewModeLenses"}
toolTipText={"Формат отображения линз"} toolTipText={"Формат отображения линз"}
viewModes={viewModes} viewModes={viewModes}
@ -177,100 +174,93 @@ const LensesTab = () => {
</Col> </Col>
</Row> </Row>
{lensesUI.showAdvancedSearch && ( {lensesData.showAdvancedSearch && (
<Card <Card
title="Расширенный поиск" title="Расширенный поиск"
style={lensesUI.advancedSearchCardStyle} style={lensesData.advancedSearchCardStyle}
> >
<Row gutter={16}> <Row gutter={16}>
<Col xs={24} sm={12}> <Col xs={24} sm={12}>
<Form layout="vertical"> <Form layout="vertical">
<Form.Item label="Тор"> <Form.Item label="Тор">
<InputNumber <InputNumber
value={lensesUI.searchParams.tor || 0} value={lensesData.searchParams.tor || 0}
onChange={(value) => lensesUI.handleParamChange("tor", value)} onChange={(value) => lensesData.handleParamChange("tor", value)}
style={lensesUI.formItemStyle} style={lensesData.formItemStyle}
defaultValue={0} defaultValue={0}
step={0.1} step={0.1}
/> />
</Form.Item> </Form.Item>
<Form.Item label="Диаметр"> <Form.Item label="Диаметр">
<InputNumber <InputNumber
value={lensesUI.searchParams.diameter || 0} value={lensesData.searchParams.diameter || 0}
onChange={(value) => lensesUI.handleParamChange("diameter", value)} onChange={(value) => lensesData.handleParamChange("diameter", value)}
style={lensesUI.formItemStyle} style={lensesData.formItemStyle}
defaultValue={0} defaultValue={0}
step={0.1} step={0.1}
/> />
</Form.Item> </Form.Item>
<Form.Item label="Рефракция"> <Form.Item label="Рефракция">
<InputNumber <InputNumber
value={lensesUI.searchParams.preset_refraction || 0} value={lensesData.searchParams.preset_refraction || 0}
onChange={(value) => lensesUI.handleParamChange("preset_refraction", value)} onChange={(value) => lensesData.handleParamChange("preset_refraction", value)}
style={lensesUI.formItemStyle} style={lensesData.formItemStyle}
defaultValue={0} defaultValue={0}
step={0.1} step={0.1}
/> />
</Form.Item> </Form.Item>
<Form.Item label="Периферическая торичность"> <Form.Item label="Периферическая торичность">
<InputNumber <InputNumber
value={lensesUI.searchParams.periphery_toricity || 0} value={lensesData.searchParams.periphery_toricity || 0}
onChange={(value) => lensesUI.handleParamChange("periphery_toricity", value)} onChange={(value) => lensesData.handleParamChange("periphery_toricity", value)}
style={lensesUI.formItemStyle} style={lensesData.formItemStyle}
defaultValue={0} defaultValue={0}
step={0.1} step={0.1}
/> />
</Form.Item> </Form.Item>
</Form> </Form>
</Col> </Col>
<Col xs={24} sm={12}> <Col xs={24} sm={12}>
<Form layout="vertical"> <Form layout="vertical">
<Form.Item label="Сторона"> <Form.Item label="Сторона">
<Select <Select
value={lensesUI.searchParams.side} value={lensesData.searchParams.side}
onChange={(value) => lensesUI.handleParamChange("side", value)} onChange={(value) => lensesData.handleParamChange("side", value)}
style={lensesUI.formItemStyle} style={lensesData.formItemStyle}
> >
<Option value="all">Все</Option> <Option value="all">Все</Option>
<Option value="левая">Левая</Option> <Option value="левая">Левая</Option>
<Option value="правая">Правая</Option> <Option value="правая">Правая</Option>
</Select> </Select>
</Form.Item> </Form.Item>
<Form.Item label="Выдана"> <Form.Item label="Выдана">
<Select <Select
value={lensesUI.searchParams.issued} value={lensesData.searchParams.issued}
onChange={(value) => lensesUI.handleParamChange("issued", value)} onChange={(value) => lensesData.handleParamChange("issued", value)}
style={lensesUI.formItemStyle} style={lensesData.formItemStyle}
> >
<Option value="all">Все</Option> <Option value="all">Все</Option>
<Option value={true}>Да</Option> <Option value={true}>Да</Option>
<Option value={false}>Нет</Option> <Option value={false}>Нет</Option>
</Select> </Select>
</Form.Item> </Form.Item>
<Form.Item label="Острота зрения (Trial)"> <Form.Item label="Острота зрения (Trial)">
<InputNumber <InputNumber
value={lensesUI.searchParams.trial || 0} value={lensesData.searchParams.trial || 0}
onChange={(value) => lensesUI.handleParamChange("trial", value)} onChange={(value) => lensesData.handleParamChange("trial", value)}
style={lensesUI.formItemStyle} style={lensesData.formItemStyle}
defaultValue={0} defaultValue={0}
step={0.1} step={0.1}
/> />
</Form.Item> </Form.Item>
<Row> <Row>
<Col xs={24} sm={12} offset={screens.sm ? 12 : 0} <Col xs={24} sm={12} offset={screens.sm ? 12 : 0}
style={{textAlign: screens.sm ? "right" : "center", marginTop: "1.7rem"}}> style={{ textAlign: screens.sm ? "right" : "center", marginTop: "1.7rem" }}>
<Button <Button
type="primary" type="primary"
block={!screens.sm} block={!screens.sm}
onClick={() => { onClick={() => {
lensesUI.handleParamChange({ lensesData.handleParamChange({
tor: null, tor: null,
diameter: null, diameter: null,
preset_refraction: null, preset_refraction: null,
@ -293,45 +283,45 @@ const LensesTab = () => {
{lensesData.isLoading ? ( {lensesData.isLoading ? (
<LoadingIndicator/> <LoadingIndicator/>
) : lensesUI.viewMode === "tile" ? ( ) : lensesData.viewMode === "tile" ? (
<List <List
grid={{gutter: 16, xs: 1, sm: 1, md: 2, lg: 3, xl: 4}} grid={{ gutter: 16, xs: 1, sm: 1, md: 2, lg: 3, xl: 4 }}
dataSource={lensesUI.filteredLenses} dataSource={lensesData.filteredLenses}
renderItem={(lens) => ( renderItem={(lens) => (
<List.Item> <List.Item>
<LensCard <LensCard
lens={lens} lens={lens}
handleDeleteLens={() => lensesData.handleDeleteLens(lens.id)} handleDeleteLens={() => lensesData.handleDeleteLens(lens.id)}
handleEditLens={() => lensesUI.handleEditLens(lens)} handleEditLens={() => lensesData.handleEditLens(lens)}
/> />
</List.Item> </List.Item>
)} )}
pagination={lensesUI.pagination} pagination={lensesData.pagination}
/> />
) : ( ) : (
<Table <Table
columns={columns} columns={columns}
dataSource={lensesUI.filteredLenses} dataSource={lensesData.filteredLenses}
scroll={{ scroll={{
x: "max-content" x: "max-content"
}} }}
showSorterTooltip={false} showSorterTooltip={false}
pagination={lensesUI.pagination} pagination={lensesData.pagination}
/> />
)} )}
<FloatButton <FloatButton
icon={<PlusOutlined/>} icon={<PlusOutlined/>}
type="primary" type="primary"
onClick={lensesUI.handleAddLens} onClick={lensesData.handleAddLens}
tooltip="Добавить линзу" tooltip="Добавить линзу"
/> />
<LensFormModal <LensFormModal
visible={lensesUI.isModalVisible} visible={lensesData.isModalVisible}
onCancel={lensesUI.handleCloseModal} onCancel={lensesData.handleCloseModal}
onSubmit={lensesData.handleModalSubmit} onSubmit={lensesData.handleModalSubmit}
lens={lensesUI.selectedLens} lens={lensesData.selectedLens}
isProcessing={lensesData.isProcessing} isProcessing={lensesData.isProcessing}
/> />
</div> </div>

View File

@ -1,28 +1,100 @@
import {useDispatch, useSelector} from "react-redux"; import { useEffect, useMemo } from "react";
import { useDispatch, useSelector } from "react-redux";
import { notification } from "antd";
import { import {
useAddLensMutation, useAddLensMutation,
useDeleteLensMutation, useDeleteLensMutation,
useGetLensesQuery, useGetLensesQuery,
useUpdateLensMutation useUpdateLensMutation
} from "../../../../../Api/lensesApi.js"; } from "../../../../../Api/lensesApi.js";
import {notification} from "antd"; import {
import {closeModal} from "../../../../../Redux/Slices/lensesSlice.js"; closeModal,
openModal,
selectLens,
setCurrentPage,
setPageSize,
setSearchParams,
setSearchText,
setShowAdvancedSearch,
setViewMode
} from "../../../../../Redux/Slices/lensesSlice.js";
import { getCachedInfo } from "../../../../../Utils/cachedInfoUtils.js";
const useLenses = () => { const useLenses = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const { const {
searchText,
viewMode,
currentPage,
pageSize,
selectedLens, selectedLens,
isModalVisible,
showAdvancedSearch,
searchParams,
} = useSelector(state => state.lensesUI); } = useSelector(state => state.lensesUI);
const {data: lenses = [], isLoading, isError} = useGetLensesQuery(undefined, { const { data = { lenses: [], total_count: 0 }, isLoading, isError } = useGetLensesQuery(
pollingInterval: 20000, { page: currentPage, pageSize, search: searchText || undefined },
}); {
pollingInterval: 20000,
refetchOnMountOrArgChange: true,
}
);
const [addLens, {isLoading: isAdding}] = useAddLensMutation(); const [addLens, { isLoading: isAdding }] = useAddLensMutation();
const [updateLens, {isLoading: isUpdating}] = useUpdateLensMutation(); const [updateLens, { isLoading: isUpdating }] = useUpdateLensMutation();
const [deleteLens, {isLoading: isDeleting}] = useDeleteLensMutation(); const [deleteLens, { isLoading: isDeleting }] = useDeleteLensMutation();
useEffect(() => {
document.title = "Линзы";
const cachedViewMode = getCachedInfo("viewModeLenses");
if (cachedViewMode) dispatch(setViewMode(cachedViewMode));
}, [dispatch]);
const containerStyle = { padding: 20 };
const filterBarStyle = { marginBottom: 20 };
const formItemStyle = { width: "100%" };
const viewModIconStyle = { marginRight: 8 };
const tableActionButtonsStyle = { display: "flex", gap: "8px" };
const advancedSearchCardStyle = { marginBottom: 20 };
const handleSetSearchText = (value) => {
dispatch(setSearchText(value));
dispatch(setCurrentPage(1)); // Сбрасываем на первую страницу при изменении поиска
};
const handleCloseModal = () => dispatch(closeModal());
const handleSetCurrentPage = (page) => dispatch(setCurrentPage(page));
const handleSetPageSize = (size) => dispatch(setPageSize(size));
const handleSetViewMode = (mode) => dispatch(setViewMode(mode));
const handleAddLens = () => {
dispatch(selectLens(null));
dispatch(openModal());
};
const handleEditLens = (lens) => {
dispatch(selectLens(lens));
dispatch(openModal());
};
const handleParamChange = (param, value) => {
if (typeof param === "object") {
dispatch(setSearchParams(param));
} else {
dispatch(setSearchParams({ ...searchParams, [param]: value }));
}
};
const toggleAdvancedSearch = () => {
dispatch(setShowAdvancedSearch(!showAdvancedSearch));
};
const handlePaginationChange = (page, pageSize) => {
handleSetCurrentPage(page);
handleSetPageSize(pageSize);
};
const handleDeleteLens = async (lensId) => { const handleDeleteLens = async (lensId) => {
try { try {
@ -43,10 +115,9 @@ const useLenses = () => {
const handleModalSubmit = async (lensData) => { const handleModalSubmit = async (lensData) => {
dispatch(closeModal()); dispatch(closeModal());
try { try {
if (selectedLens) { if (selectedLens) {
await updateLens({id: selectedLens.id, ...lensData}).unwrap(); await updateLens({ id: selectedLens.id, ...lensData }).unwrap();
notification.success({ notification.success({
message: "Линза обновлена", message: "Линза обновлена",
description: "Линза успешно обновлена.", description: "Линза успешно обновлена.",
@ -69,14 +140,72 @@ const useLenses = () => {
} }
}; };
const filteredLenses = useMemo(() => {
return data.lenses.filter((lens) => {
const textMatch = Object.values(lens).some((value) =>
value?.toString().toLowerCase().includes(searchText.toLowerCase())
);
const advancedMatch = Object.entries(searchParams).every(([key, value]) => {
if (value === null || value === '') return true;
if (key === 'side') {
if (value === 'all') return true;
return lens.side === value;
}
if (key === 'issued') {
return lens.issued === value || value === "all";
}
return lens[key] === value;
});
return textMatch && advancedMatch && (searchParams.issued || lens.issued === false);
}).sort((a, b) => {
return b.id - a.id; // Сортировка по убыванию id
});
}, [data.lenses, searchText, searchParams]);
const pagination = {
current: currentPage,
pageSize: pageSize,
total: data.total_count,
showSizeChanger: true,
pageSizeOptions: ["5", "10", "20", "50"],
onChange: (page, newPageSize) => {
handlePaginationChange(page, newPageSize);
},
};
return { return {
lenses, lenses: data.lenses,
filteredLenses: filteredLenses.map(lens => ({ ...lens, key: lens.id })),
isLoading, isLoading,
isError, isError,
isProcessing: isAdding || isUpdating || isDeleting, isProcessing: isAdding || isUpdating || isDeleting,
searchText,
viewMode,
currentPage,
pageSize,
selectedLens,
isModalVisible,
showAdvancedSearch,
searchParams,
pagination,
containerStyle,
filterBarStyle,
formItemStyle,
viewModIconStyle,
tableActionButtonsStyle,
advancedSearchCardStyle,
handleSetSearchText,
handleAddLens,
handleEditLens,
handleCloseModal,
handleDeleteLens, handleDeleteLens,
handleModalSubmit, handleModalSubmit,
} handleParamChange,
toggleAdvancedSearch,
handleSetViewMode,
};
}; };
export default useLenses; export default useLenses;

View File

@ -1,128 +0,0 @@
import {useEffect, useMemo} from "react";
import {getCachedInfo} from "../../../../../Utils/cachedInfoUtils.js";
import {
closeModal,
openModal,
selectLens, setCurrentPage, setPageSize,
setSearchParams,
setSearchText, setShowAdvancedSearch,
setViewMode
} from "../../../../../Redux/Slices/lensesSlice.js";
import {useDispatch, useSelector} from "react-redux";
const useLensesUI = (lenses) => {
const dispatch = useDispatch();
const {
searchText,
viewMode,
currentPage,
pageSize,
selectedLens,
isModalVisible,
showAdvancedSearch,
searchParams,
} = useSelector(state => state.lensesUI);
useEffect(() => {
document.title = "Линзы";
const cachedViewMode = getCachedInfo("viewModeLenses");
if (cachedViewMode) dispatch(setViewMode(cachedViewMode));
}, [dispatch]);
const containerStyle = {padding: 20};
const filterBarStyle = {marginBottom: 20};
const formItemStyle = {width: "100%"};
const viewModIconStyle = {marginRight: 8};
const tableActionButtonsStyle = {display: "flex", gap: "8px"};
const handleSetSearchText = (value) => dispatch(setSearchText(value));
const handleCloseModal = () => dispatch(closeModal());
const handleSetCurrentPage = (page) => dispatch(setCurrentPage(page));
const handleSetPageSize = (size) => dispatch(setPageSize(size));
const handleSetViewMode = (mode) => dispatch(setViewMode(mode));
const handleAddLens = () => {
dispatch(selectLens(null));
dispatch(openModal());
};
const handleEditLens = (lens) => {
dispatch(selectLens(lens));
dispatch(openModal());
};
const handleParamChange = (param, value) => {
dispatch(setSearchParams({...searchParams, [param]: value}));
};
const toggleAdvancedSearch = () => {
dispatch(setShowAdvancedSearch(!showAdvancedSearch));
};
const handlePaginationChange = (page, pageSize) => {
handleSetCurrentPage(page);
handleSetPageSize(pageSize);
};
const filteredLenses = useMemo(() => {
return lenses.filter((lens) => {
const textMatch = Object.values(lens).some((value) =>
value?.toString().toLowerCase().includes(searchText.toLowerCase())
);
const advancedMatch = Object.entries(searchParams).every(([key, value]) => {
if (value === null || value === '') return true;
if (key === 'side') {
if (value === 'all') return true;
return lens.side === value;
}
if (key === 'issued') {
return lens.issued === value || value === "all";
}
return lens[key] === value;
});
return textMatch && advancedMatch && (searchParams.issued || lens.issued === false);
}).sort((a, b) => {
return b.id - a.id;
});
}, [lenses, searchText, searchParams]);
const pagination = {
currentPage: currentPage,
pageSize: pageSize,
showSizeChanger: true,
pageSizeOptions: ["5", "10", "20", "50"],
onChange: (page, newPageSize) => {
handlePaginationChange(page, newPageSize);
},
};
return {
searchText,
viewMode,
currentPage,
pageSize,
selectedLens,
isModalVisible,
showAdvancedSearch,
searchParams,
pagination,
containerStyle,
filterBarStyle,
formItemStyle,
viewModIconStyle,
tableActionButtonsStyle,
filteredLenses: filteredLenses.map(lens => ({...lens, key: lens.id})),
handleSetSearchText,
handleAddLens,
handleEditLens,
handleCloseModal,
handleParamChange,
toggleAdvancedSearch,
handleSetViewMode,
}
};
export default useLensesUI;