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

This commit is contained in:
Андрей Дувакин 2025-03-25 13:03:21 +05:00
parent 793a47a58d
commit 94ef12b336
7 changed files with 209 additions and 265 deletions

View File

@ -5,12 +5,9 @@ import {
useGetLensesQuery, useGetLensesQuery,
useUpdateLensMutation useUpdateLensMutation
} from "../redux/services/lensesApi.js"; } from "../redux/services/lensesApi.js";
import {useEffect} from "react";
import { import {
closeModal, closeModal
setViewMode,
} from "../redux/slices/lensesSlice.js"; } from "../redux/slices/lensesSlice.js";
import {getCachedInfo} from "../utils/cachedInfoUtils.js";
import {notification} from "antd"; import {notification} from "antd";
@ -24,16 +21,11 @@ const useLenses = () => {
const {data: lenses = [], isLoading, isError} = useGetLensesQuery(undefined, { const {data: lenses = [], isLoading, isError} = useGetLensesQuery(undefined, {
pollingInterval: 20000, pollingInterval: 20000,
}); });
const [addLens] = useAddLensMutation(); const [addLens] = useAddLensMutation();
const [updateLens] = useUpdateLensMutation(); const [updateLens] = useUpdateLensMutation();
const [deleteLens] = useDeleteLensMutation(); const [deleteLens] = useDeleteLensMutation();
useEffect(() => {
document.title = "Линзы";
const cachedViewMode = getCachedInfo("viewModeLenses");
if (cachedViewMode) dispatch(setViewMode(cachedViewMode));
}, [dispatch]);
const handleDeleteLens = async (lensId) => { const handleDeleteLens = async (lensId) => {
try { try {
await deleteLens(lensId).unwrap(); await deleteLens(lensId).unwrap();

View File

@ -0,0 +1,115 @@
import {useEffect} 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 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 = 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 a.preset_refraction - b.preset_refraction;
});
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,
filteredLenses: filteredLenses.map(lens => ({...lens, key: lens.id})),
handleSetSearchText,
handleAddLens,
handleEditLens,
handleCloseModal,
handleParamChange,
toggleAdvancedSearch,
handleSetViewMode,
}
};
export default useLensesUI;

View File

@ -11,7 +11,6 @@ import {
} from "../redux/slices/patientsSlice"; } from "../redux/slices/patientsSlice";
import { closeModal } from "../redux/slices/lensesSlice"; import { closeModal } from "../redux/slices/lensesSlice";
import { getCachedInfo } from "../utils/cachedInfoUtils"; import { getCachedInfo } from "../utils/cachedInfoUtils";
import {BuildOutlined, TableOutlined} from "@ant-design/icons";
const usePatientsUI = (patients) => { const usePatientsUI = (patients) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -73,7 +72,15 @@ const usePatientsUI = (patients) => {
const formatDate = (date) => new Date(date).toLocaleDateString(); const formatDate = (date) => new Date(date).toLocaleDateString();
const pagination = {
currentPage: currentPage,
pageSize: pageSize,
showSizeChanger: true,
pageSizeOptions: ["5", "10", "20", "50"],
onChange: (page, newPageSize) => {
handlePaginationChange(page, newPageSize);
},
};
return { return {
searchText, searchText,
@ -86,7 +93,8 @@ const usePatientsUI = (patients) => {
containerStyle, containerStyle,
filterBarStyle, filterBarStyle,
formItemStyle, formItemStyle,
filteredPatients: patients.map(p => ({ ...p, key: p.id })), pagination,
filteredPatients: filteredPatients.map(p => ({ ...p, key: p.id })),
handleSetSearchText, handleSetSearchText,
handleSetSortOrder, handleSetSortOrder,
handleSetViewMode, handleSetViewMode,

View File

@ -30,7 +30,6 @@ const {Title} = Typography;
const PatientsPage = () => { const PatientsPage = () => {
const patientsData = usePatients(); const patientsData = usePatients();
const patientsUI = usePatientsUI(patientsData.patients); const patientsUI = usePatientsUI(patientsData.patients);
const columns = [ const columns = [
@ -170,29 +169,13 @@ const PatientsPage = () => {
/> />
</List.Item> </List.Item>
)} )}
pagination={{ pagination={patientsUI.pagination}
currentPage: patientsUI.currentPage,
pageSize: patientsUI.pageSize,
showSizeChanger: true,
pageSizeOptions: ["5", "10", "20", "50"],
onChange: (page, newPageSize) => {
patientsUI.handlePaginationChange(page, newPageSize);
},
}}
/> />
) : ( ) : (
<Table <Table
columns={columns} columns={columns}
dataSource={patientsUI.filteredPatients} dataSource={patientsUI.filteredPatients}
pagination={{ pagination={patientsUI.pagination}
currentPage: patientsUI.currentPage,
pageSize: patientsUI.pageSize,
showSizeChanger: true,
pageSizeOptions: ["5", "10", "20", "50"],
onChange: (page, newPageSize) => {
patientsUI.handlePaginationChange(page, newPageSize);
},
}}
/> />
)} )}

View File

@ -1,4 +1,3 @@
import {useState, useEffect} from "react";
import { import {
Input, Input,
Select, Select,
@ -11,10 +10,9 @@ import {
InputNumber, InputNumber,
Card, Card,
Grid, Grid,
notification,
Table, Table,
Popconfirm, Popconfirm,
Typography Typography, Result
} from "antd"; } from "antd";
import { import {
PlusOutlined, PlusOutlined,
@ -25,188 +23,22 @@ import {
BuildOutlined BuildOutlined
} from "@ant-design/icons"; } from "@ant-design/icons";
import LensCard from "../../components/lenses/LensListCard.jsx"; import LensCard from "../../components/lenses/LensListCard.jsx";
import getAllLenses from "../../api/lenses/getAllLenses.js";
import addLens from "../../api/lenses/addLens.js";
import updateLens from "../../api/lenses/updateLens.js";
import deleteLens from "../../api/lenses/deleteLens.js";
import {useAuth} from "../../AuthContext.jsx";
import LensFormModal from "../../components/lenses/LensFormModal.jsx"; import LensFormModal from "../../components/lenses/LensFormModal.jsx";
import SelectViewMode from "../../components/SelectViewMode.jsx"; import SelectViewMode from "../../components/SelectViewMode.jsx";
import LoadingIndicator from "../../components/LoadingIndicator.jsx"; import LoadingIndicator from "../../components/LoadingIndicator.jsx";
import {getCachedInfo, getCacheTimestamp} from "../../utils/cachedInfoUtils.js";
import {useDispatch} from "react-redux";
import useLenses from "../../hooks/useLenses.js"; import useLenses from "../../hooks/useLenses.js";
import {openModal, selectLens} from "../../redux/slices/lensesSlice.js"; import useLensesUI from "../../hooks/useLensesUI.js";
const {Option} = Select; const {Option} = Select;
const {useBreakpoint} = Grid; const {useBreakpoint} = Grid;
const {Title} = Typography; const {Title} = Typography;
const LensesPage = () => { const LensesPage = () => {
const {api} = useAuth(); const lensesData = useLenses();
const lensesUI = useLensesUI(lensesData.lenses);
const screens = useBreakpoint(); const screens = useBreakpoint();
const [current, setCurrent] = useState(1);
const [pageSize, setPageSize] = useState(10);
const [searchText, setSearchText] = useState("");
const [viewMode, setViewMode] = useState("tile");
const [loading, setLoading] = useState(true);
const [isModalVisible, setIsModalVisible] = useState(false);
const [showAdvancedSearch, setShowAdvancedSearch] = useState(false);
const [selectedLens, setSelectedLens] = useState(null);
const [lenses, setLenses] = useState([]);
const [searchParams, setSearchParams] = useState({
tor: null,
diameter: null,
preset_refraction: null,
periphery_toricity: null,
side: 'all',
issued: false,
trial: null
});
useEffect(() => {
fetchLensWithCache();
fetchViewModeFromCache();
document.title = "Линзы";
}, []);
useEffect(() => {
if (!isModalVisible && !selectedLens) {
const intervalId = setInterval(fetchLenses, 5000);
return () => clearInterval(intervalId);
}
}, [isModalVisible, selectedLens]);
const fetchLensWithCache = async () => {
const cachedData = getCachedInfo("lensData");
const cacheTimestamp = getCacheTimestamp("lensData");
if (cachedData && cacheTimestamp && (Date.now() - parseInt(cacheTimestamp)) < 60 * 1000) {
setLenses(JSON.parse(cachedData));
setLoading(false);
return;
}
await fetchLenses();
};
const fetchLenses = async () => {
const data = await getAllLenses(api);
setLenses(data);
setLoading(false);
};
const fetchViewModeFromCache = () => {
const cachedViewMode = getCachedInfo("viewModeLenses");
if (cachedViewMode) {
setViewMode(cachedViewMode);
}
};
const filteredLenses = 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 a.preset_refraction - b.preset_refraction;
});
const handleAddLens = () => {
setSelectedLens(null);
setIsModalVisible(true);
};
const handleEditLens = (lens) => {
setSelectedLens(lens);
setIsModalVisible(true);
};
const handleDeleteLens = async (lensId) => {
await deleteLens(api, lensId);
await fetchLenses(api);
notification.success({
message: "Линза удалена",
description: "Линза успешно удалена.",
placement: "topRight",
})
};
const handleModalSubmit = async (lensData) => {
setIsModalVisible(false);
if (selectedLens) {
await updateLens(api, selectedLens.id, lensData);
notification.success({
message: "Линза обновлена",
description: "Линза успешно обновлена.",
placement: "topRight",
});
} else {
await addLens(api, lensData);
notification.success({
message: "Линза добавлена",
description: "Линза успешно добавлена.",
placement: "topRight",
});
}
await fetchLenses();
};
const toggleAdvancedSearch = () => {
setShowAdvancedSearch(!showAdvancedSearch);
};
const handleParamChange = (param, value) => {
setSearchParams({...searchParams, [param]: value});
};
const handleCancel = () => {
setIsModalVisible(false);
};
const TileView = () => (
<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);
},
}}
/>
);
const viewModes = [ const viewModes = [
{ {
value: "tile", value: "tile",
@ -284,11 +116,11 @@ const LensesPage = () => {
fixed: 'right', fixed: 'right',
render: (text, record) => ( render: (text, record) => (
<div style={{display: "flex", gap: "8px"}}> <div style={{display: "flex", gap: "8px"}}>
<Button onClick={() => handleEditLens(record)}>Изменить</Button> <Button onClick={() => lensesUI.handleEditLens(record)}>Изменить</Button>
<Popconfirm <Popconfirm
title="Вы уверены, что хотите удалить линзу?" title="Вы уверены, что хотите удалить линзу?"
onConfirm={() => handleDeleteLens(record.id)} onConfirm={() => lensesData.handleDeleteLens(record.id)}
okText="Да, удалить" okText="Да, удалить"
cancelText="Отмена" cancelText="Отмена"
> >
@ -299,24 +131,11 @@ const LensesPage = () => {
}, },
]; ];
const TableView = () => ( if (lensesData.isError) return (
<Table <Result
columns={columns} status="error"
dataSource={filteredLenses.map(lens => ({...lens, key: lens.id}))} title="Ошибка"
scroll={{ subTitle="Произошла ошибка в работе страницы"
x: "max-content"
}}
showSorterTooltip={false}
pagination={{
current,
pageSize,
showSizeChanger: true,
pageSizeOptions: ["5", "10", "20", "50"],
onChange: (page, newPageSize) => {
setCurrent(page);
setPageSize(newPageSize);
},
}}
/> />
); );
@ -327,15 +146,16 @@ const LensesPage = () => {
<Col xs={24} md={24} sm={24} xl={15}> <Col xs={24} md={24} sm={24} xl={15}>
<Input <Input
placeholder="Поиск линзы" placeholder="Поиск линзы"
value={searchText} value={lensesUI.searchText}
onChange={(e) => setSearchText(e.target.value)} onChange={(e) => lensesUI.handleSetSearchText(e.target.value)}
style={{width: "100%"}} style={{width: "100%"}}
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={toggleAdvancedSearch} icon={showAdvancedSearch ? <UpOutlined/> : <DownOutlined/>} onClick={lensesUI.toggleAdvancedSearch}
icon={lensesUI.showAdvancedSearch ? <UpOutlined/> : <DownOutlined/>}
block block
> >
Расширенный поиск Расширенный поиск
@ -343,8 +163,8 @@ const LensesPage = () => {
</Col> </Col>
<Col xs={24} md={12} sm={24} xl={4}> <Col xs={24} md={12} sm={24} xl={4}>
<SelectViewMode <SelectViewMode
viewMode={viewMode} viewMode={lensesUI.viewMode}
setViewMode={setViewMode} setViewMode={lensesUI.handleSetViewMode}
localStorageKey={"viewModeLenses"} localStorageKey={"viewModeLenses"}
toolTipText={"Формат отображения линз"} toolTipText={"Формат отображения линз"}
viewModes={viewModes} viewModes={viewModes}
@ -352,7 +172,7 @@ const LensesPage = () => {
</Col> </Col>
</Row> </Row>
{showAdvancedSearch && ( {lensesUI.showAdvancedSearch && (
<Card <Card
title="Расширенный поиск" title="Расширенный поиск"
style={{ style={{
@ -366,8 +186,8 @@ const LensesPage = () => {
<Form layout="vertical"> <Form layout="vertical">
<Form.Item label="Тор"> <Form.Item label="Тор">
<InputNumber <InputNumber
value={searchParams.tor || 0} value={lensesUI.searchParams.tor || 0}
onChange={(value) => handleParamChange("tor", value)} onChange={(value) => lensesUI.handleParamChange("tor", value)}
style={{width: "100%"}} style={{width: "100%"}}
defaultValue={0} defaultValue={0}
step={0.1} step={0.1}
@ -376,8 +196,8 @@ const LensesPage = () => {
<Form.Item label="Диаметр"> <Form.Item label="Диаметр">
<InputNumber <InputNumber
value={searchParams.diameter || 0} value={lensesUI.searchParams.diameter || 0}
onChange={(value) => handleParamChange("diameter", value)} onChange={(value) => lensesUI.handleParamChange("diameter", value)}
style={{width: "100%"}} style={{width: "100%"}}
defaultValue={0} defaultValue={0}
step={0.1} step={0.1}
@ -386,8 +206,8 @@ const LensesPage = () => {
<Form.Item label="Рефракция"> <Form.Item label="Рефракция">
<InputNumber <InputNumber
value={searchParams.preset_refraction || 0} value={lensesUI.searchParams.preset_refraction || 0}
onChange={(value) => handleParamChange("preset_refraction", value)} onChange={(value) => lensesUI.handleParamChange("preset_refraction", value)}
style={{width: "100%"}} style={{width: "100%"}}
defaultValue={0} defaultValue={0}
step={0.1} step={0.1}
@ -396,8 +216,8 @@ const LensesPage = () => {
<Form.Item label="Периферическая торичность"> <Form.Item label="Периферическая торичность">
<InputNumber <InputNumber
value={searchParams.periphery_toricity || 0} value={lensesUI.searchParams.periphery_toricity || 0}
onChange={(value) => handleParamChange("periphery_toricity", value)} onChange={(value) => lensesUI.handleParamChange("periphery_toricity", value)}
style={{width: "100%"}} style={{width: "100%"}}
defaultValue={0} defaultValue={0}
step={0.1} step={0.1}
@ -410,8 +230,8 @@ const LensesPage = () => {
<Form layout="vertical"> <Form layout="vertical">
<Form.Item label="Сторона"> <Form.Item label="Сторона">
<Select <Select
value={searchParams.side} value={lensesUI.searchParams.side}
onChange={(value) => handleParamChange("side", value)} onChange={(value) => lensesUI.handleParamChange("side", value)}
style={{width: "100%"}} style={{width: "100%"}}
> >
<Option value="all">Все</Option> <Option value="all">Все</Option>
@ -422,8 +242,8 @@ const LensesPage = () => {
<Form.Item label="Выдана"> <Form.Item label="Выдана">
<Select <Select
value={searchParams.issued} value={lensesUI.searchParams.issued}
onChange={(value) => handleParamChange("issued", value)} onChange={(value) => lensesUI.handleParamChange("issued", value)}
style={{width: "100%"}} style={{width: "100%"}}
> >
<Option value="all">Все</Option> <Option value="all">Все</Option>
@ -434,8 +254,8 @@ const LensesPage = () => {
<Form.Item label="Острота зрения (Trial)"> <Form.Item label="Острота зрения (Trial)">
<InputNumber <InputNumber
value={searchParams.trial || 0} value={lensesUI.searchParams.trial || 0}
onChange={(value) => handleParamChange("trial", value)} onChange={(value) => lensesUI.handleParamChange("trial", value)}
style={{width: "100%"}} style={{width: "100%"}}
defaultValue={0} defaultValue={0}
step={0.1} step={0.1}
@ -449,7 +269,7 @@ const LensesPage = () => {
type="primary" type="primary"
block={!screens.sm} block={!screens.sm}
onClick={() => { onClick={() => {
setSearchParams({ lensesUI.handleParamChange({
tor: null, tor: null,
diameter: null, diameter: null,
preset_refraction: null, preset_refraction: null,
@ -470,27 +290,48 @@ const LensesPage = () => {
</Card> </Card>
)} )}
{loading ? ( {lensesData.isLoading ? (
<LoadingIndicator/> <LoadingIndicator/>
) : viewMode === "tile" ? ( ) : lensesUI.viewMode === "tile" ? (
<TileView/> <List
grid={{gutter: 16, xs: 1, sm: 1, md: 2, lg: 3, xl: 4}}
dataSource={lensesUI.filteredLenses}
renderItem={(lens) => (
<List.Item>
<LensCard
lens={lens}
handleDeleteLens={() => lensesData.handleDeleteLens(lens.id)}
handleEditLens={() => lensesUI.handleEditLens(lens)}
/>
</List.Item>
)}
pagination={lensesUI.pagination}
/>
) : ( ) : (
<TableView/> <Table
columns={columns}
dataSource={lensesUI.filteredLenses}
scroll={{
x: "max-content"
}}
showSorterTooltip={false}
pagination={lensesUI.pagination}
/>
)} )}
<FloatButton <FloatButton
icon={<PlusOutlined/>} icon={<PlusOutlined/>}
type="primary" type="primary"
style={{position: "fixed", bottom: 40, right: 40}} style={{position: "fixed", bottom: 40, right: 40}}
onClick={handleAddLens} onClick={lensesUI.handleAddLens}
tooltip="Добавить линзу" tooltip="Добавить линзу"
/> />
<LensFormModal <LensFormModal
visible={isModalVisible} visible={lensesUI.isModalVisible}
onCancel={handleCancel} onCancel={lensesUI.handleCloseModal}
onSubmit={handleModalSubmit} onSubmit={lensesData.handleModalSubmit}
lens={selectedLens} lens={lensesUI.selectedLens}
/> />
</div> </div>
); );

View File

@ -9,7 +9,7 @@ const initialState = {
pageSize: 10, pageSize: 10,
selectedLens: null, selectedLens: null,
isModalVisible: false, isModalVisible: false,
showAvancedSearch: false, showAdvancedSearch: false,
searchParams: { searchParams: {
tor: null, tor: null,
diameter: null, diameter: null,
@ -52,7 +52,7 @@ const lensesSlice = createSlice({
state.searchParams = action.payload; state.searchParams = action.payload;
}, },
setShowAdvancedSearch: (state, action) => { setShowAdvancedSearch: (state, action) => {
state.showAvancedSearch = action.payload; state.showAdvancedSearch = action.payload;
}, },
} }
}); });

View File

@ -1,15 +1,20 @@
import { configureStore } from '@reduxjs/toolkit' import {configureStore} from '@reduxjs/toolkit';
import {patientsApi} from "./services/patientsApi.js"; import {patientsApi} from './services/patientsApi.js';
import patientsUIReducer from './slices/patientsSlice.js' import patientsUIReducer from './slices/patientsSlice.js';
import {lensesApi} from './services/lensesApi.js';
import lensesReducer from './slices/lensesSlice.js';
export const store = configureStore({ export const store = configureStore({
reducer: { reducer: {
[patientsApi.reducerPath]: patientsApi.reducer, [patientsApi.reducerPath]: patientsApi.reducer,
patientsUI: patientsUIReducer patientsUI: patientsUIReducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(patientsApi.middleware)
})
[lensesApi.reducerPath]: lensesApi.reducer,
lensesUI: lensesReducer,
},
middleware: (getDefaultMiddleware) => (
getDefaultMiddleware().concat(patientsApi.middleware, lensesApi.middleware)
)
});
export default store; export default store;