переделал страницу с наборами линз с помощью redux
This commit is contained in:
parent
94ef12b336
commit
0c0fbd89c0
@ -191,7 +191,7 @@ LensFormModal.propTypes = {
|
|||||||
visible: PropTypes.bool.isRequired,
|
visible: PropTypes.bool.isRequired,
|
||||||
onCancel: PropTypes.func.isRequired,
|
onCancel: PropTypes.func.isRequired,
|
||||||
onSubmit: PropTypes.func.isRequired,
|
onSubmit: PropTypes.func.isRequired,
|
||||||
lens: LensPropType.isRequired,
|
lens: LensPropType,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LensFormModal;
|
export default LensFormModal;
|
||||||
@ -1,7 +1,6 @@
|
|||||||
import {useState, useEffect} from "react";
|
import {useState, useEffect} from "react";
|
||||||
import {Modal, Button, Form, Input, Table, InputNumber, Select, Space, notification} from "antd";
|
import {Modal, Button, Form, Input, Table, InputNumber, Select, Space, notification} from "antd";
|
||||||
import {PlusOutlined, DeleteOutlined} from "@ant-design/icons";
|
import {PlusOutlined, DeleteOutlined} from "@ant-design/icons";
|
||||||
import axios from "axios";
|
|
||||||
import getAllLensTypes from "../../api/lens_types/getAllLensTypes.js";
|
import getAllLensTypes from "../../api/lens_types/getAllLensTypes.js";
|
||||||
import {useAuth} from "../../AuthContext.jsx";
|
import {useAuth} from "../../AuthContext.jsx";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
@ -29,7 +28,6 @@ const SetFormModal = ({visible, onCancel, setData, onSubmit}) => {
|
|||||||
fetchSetContents();
|
fetchSetContents();
|
||||||
}, [setData, form]);
|
}, [setData, form]);
|
||||||
|
|
||||||
|
|
||||||
const fetchSetContents = async () => {
|
const fetchSetContents = async () => {
|
||||||
if (!setData) return;
|
if (!setData) return;
|
||||||
|
|
||||||
@ -96,7 +94,8 @@ const SetFormModal = ({visible, onCancel, setData, onSubmit}) => {
|
|||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
form.validateFields().then(values => {
|
form.validateFields().then(values => {
|
||||||
if (!validateContent()) return;
|
if (!validateContent()) return;
|
||||||
onSubmit({...values}, content);
|
const sanitizedContent = content.map(({id, ...rest}) => rest);
|
||||||
|
onSubmit({...values}, sanitizedContent);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -255,7 +254,7 @@ SetFormModal.propTypes = {
|
|||||||
visible: PropTypes.bool.isRequired,
|
visible: PropTypes.bool.isRequired,
|
||||||
onCancel: PropTypes.func.isRequired,
|
onCancel: PropTypes.func.isRequired,
|
||||||
onSubmit: PropTypes.func.isRequired,
|
onSubmit: PropTypes.func.isRequired,
|
||||||
setData: SetPropType.isRequired,
|
setData: SetPropType,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SetFormModal;
|
export default SetFormModal;
|
||||||
@ -4,11 +4,9 @@ import {
|
|||||||
useDeleteLensMutation,
|
useDeleteLensMutation,
|
||||||
useGetLensesQuery,
|
useGetLensesQuery,
|
||||||
useUpdateLensMutation
|
useUpdateLensMutation
|
||||||
} from "../redux/services/lensesApi.js";
|
} from "../../redux/services/lensesApi.js";
|
||||||
import {
|
|
||||||
closeModal
|
|
||||||
} from "../redux/slices/lensesSlice.js";
|
|
||||||
import {notification} from "antd";
|
import {notification} from "antd";
|
||||||
|
import {closeModal} from "../../redux/slices/lensesSlice.js";
|
||||||
|
|
||||||
|
|
||||||
const useLenses = () => {
|
const useLenses = () => {
|
||||||
@ -44,6 +42,8 @@ const useLenses = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleModalSubmit = async (lensData) => {
|
const handleModalSubmit = async (lensData) => {
|
||||||
|
dispatch(closeModal());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (selectedLens) {
|
if (selectedLens) {
|
||||||
await updateLens({id: selectedLens.id, ...lensData}).unwrap();
|
await updateLens({id: selectedLens.id, ...lensData}).unwrap();
|
||||||
@ -60,7 +60,6 @@ const useLenses = () => {
|
|||||||
placement: "topRight",
|
placement: "topRight",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
dispatch(closeModal());
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notification.error({
|
notification.error({
|
||||||
message: "Ошибка",
|
message: "Ошибка",
|
||||||
@ -5,10 +5,8 @@ import {
|
|||||||
useDeletePatientMutation,
|
useDeletePatientMutation,
|
||||||
useGetPatientsQuery,
|
useGetPatientsQuery,
|
||||||
useUpdatePatientMutation
|
useUpdatePatientMutation
|
||||||
} from "../redux/services/patientsApi";
|
} from "../../redux/services/patientsApi.js";
|
||||||
import {
|
import {closeModal} from "../../redux/slices/patientsSlice.js";
|
||||||
closeModal,
|
|
||||||
} from "../redux/slices/patientsSlice";
|
|
||||||
|
|
||||||
const usePatients = () => {
|
const usePatients = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -41,6 +39,8 @@ const usePatients = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleModalSubmit = async (patientData) => {
|
const handleModalSubmit = async (patientData) => {
|
||||||
|
dispatch(closeModal());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (selectedPatient) {
|
if (selectedPatient) {
|
||||||
await updatePatient({ id: selectedPatient.id, ...patientData }).unwrap();
|
await updatePatient({ id: selectedPatient.id, ...patientData }).unwrap();
|
||||||
@ -57,7 +57,6 @@ const usePatients = () => {
|
|||||||
placement: "topRight",
|
placement: "topRight",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
dispatch(closeModal());
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notification.error({
|
notification.error({
|
||||||
message: "Ошибка",
|
message: "Ошибка",
|
||||||
@ -76,4 +75,4 @@ const usePatients = () => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default usePatients;
|
export default usePatients;
|
||||||
118
web-app/src/hooks/data/useSets.js
Normal file
118
web-app/src/hooks/data/useSets.js
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import {useDispatch, useSelector} from "react-redux";
|
||||||
|
import {notification} from "antd";
|
||||||
|
import {
|
||||||
|
useAddSetMutation, useAppendLensesFromSetMutation,
|
||||||
|
useDeleteSetMutation,
|
||||||
|
useGetSetsQuery,
|
||||||
|
useUpdateSetMutation
|
||||||
|
} from "../../redux/services/setsApi.js";
|
||||||
|
import {closeModal} from "../../redux/slices/setsSlice.js";
|
||||||
|
import {useAddSetContentMutation, useUpdateSetContentMutation} from "../../redux/services/setContentApi.js";
|
||||||
|
|
||||||
|
|
||||||
|
const useSets = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const {
|
||||||
|
selectedSet,
|
||||||
|
} = useSelector(state => state.setsUI);
|
||||||
|
|
||||||
|
const {data: sets = [], isLoading, isError} = useGetSetsQuery({
|
||||||
|
pollingInterval: 20000,
|
||||||
|
});
|
||||||
|
const [addSet] = useAddSetMutation();
|
||||||
|
const [appendLensFromSet] = useAppendLensesFromSetMutation();
|
||||||
|
const [updateSet] = useUpdateSetMutation();
|
||||||
|
const [deleteSet] = useDeleteSetMutation();
|
||||||
|
|
||||||
|
const [setContent] = useAddSetContentMutation();
|
||||||
|
const [updateContent] = useUpdateSetContentMutation();
|
||||||
|
|
||||||
|
const handleDeleteSet = async (setId) => {
|
||||||
|
try {
|
||||||
|
await deleteSet(setId).unwrap();
|
||||||
|
notification.success({
|
||||||
|
message: "Набор удален",
|
||||||
|
description: "Набор успешно удален.",
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
notification.error({
|
||||||
|
message: "Ошибка удаления",
|
||||||
|
description: error.data?.message || "Не удалось удалить набор",
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAppendSet = async (set) => {
|
||||||
|
try {
|
||||||
|
await appendLensFromSet(set.id).unwrap();
|
||||||
|
notification.success({
|
||||||
|
message: "Линзы добавлены",
|
||||||
|
description: "Линзы успешно добавлены.",
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
notification.error({
|
||||||
|
message: "Ошибка добавления",
|
||||||
|
description: error.data?.message || "Не удалось добавить линзы из набора в общий список",
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleModalSetSubmit = async (set, content = []) => {
|
||||||
|
dispatch(closeModal());
|
||||||
|
|
||||||
|
try {
|
||||||
|
let refreshedSet;
|
||||||
|
|
||||||
|
if (selectedSet) {
|
||||||
|
refreshedSet = await updateSet({id: selectedSet.id, ...set}).unwrap();
|
||||||
|
notification.success({
|
||||||
|
message: "Набор обновлен",
|
||||||
|
description: "Набор успешно обновлен.",
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
refreshedSet = await addSet(set).unwrap();
|
||||||
|
notification.success({
|
||||||
|
message: "Набор добавлен",
|
||||||
|
description: "Набор успешно добавлен.",
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (refreshedSet && selectedSet) {
|
||||||
|
await updateContent({setId: refreshedSet.id, setContent: content}).unwrap();
|
||||||
|
} else if (refreshedSet && !selectedSet) {
|
||||||
|
await setContent({setId: refreshedSet.id, setContent: content}).unwrap();
|
||||||
|
} else {
|
||||||
|
notification.error({
|
||||||
|
message: "Ошибка",
|
||||||
|
description: "Не удалось сохранить содержимое набора",
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
notification.error({
|
||||||
|
message: "Ошибка добавления",
|
||||||
|
description: error.data?.message || "Произошла ошибка при сохранении",
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
sets,
|
||||||
|
isLoading,
|
||||||
|
isError,
|
||||||
|
addSet,
|
||||||
|
updateSet,
|
||||||
|
handleDeleteSet,
|
||||||
|
handleAppendSet,
|
||||||
|
handleModalSetSubmit,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useSets;
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import {useEffect} from "react";
|
import {useEffect} from "react";
|
||||||
import {getCachedInfo} from "../utils/cachedInfoUtils.js";
|
import {getCachedInfo} from "../../utils/cachedInfoUtils.js";
|
||||||
import {
|
import {
|
||||||
closeModal,
|
closeModal,
|
||||||
openModal,
|
openModal,
|
||||||
@ -7,7 +7,7 @@ import {
|
|||||||
setSearchParams,
|
setSearchParams,
|
||||||
setSearchText, setShowAdvancedSearch,
|
setSearchText, setShowAdvancedSearch,
|
||||||
setViewMode
|
setViewMode
|
||||||
} from "../redux/slices/lensesSlice.js";
|
} from "../../redux/slices/lensesSlice.js";
|
||||||
import {useDispatch, useSelector} from "react-redux";
|
import {useDispatch, useSelector} from "react-redux";
|
||||||
|
|
||||||
|
|
||||||
@ -30,6 +30,10 @@ const useLensesUI = (lenses) => {
|
|||||||
if (cachedViewMode) dispatch(setViewMode(cachedViewMode));
|
if (cachedViewMode) dispatch(setViewMode(cachedViewMode));
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
|
const containerStyle = { padding: 20 };
|
||||||
|
const filterBarStyle = { marginBottom: 20 };
|
||||||
|
const formItemStyle = { width: "100%" };
|
||||||
|
|
||||||
const handleSetSearchText = (value) => dispatch(setSearchText(value));
|
const handleSetSearchText = (value) => dispatch(setSearchText(value));
|
||||||
const handleCloseModal = () => dispatch(closeModal());
|
const handleCloseModal = () => dispatch(closeModal());
|
||||||
const handleSetCurrentPage = (page) => dispatch(setCurrentPage(page));
|
const handleSetCurrentPage = (page) => dispatch(setCurrentPage(page));
|
||||||
@ -101,6 +105,9 @@ const useLensesUI = (lenses) => {
|
|||||||
showAdvancedSearch,
|
showAdvancedSearch,
|
||||||
searchParams,
|
searchParams,
|
||||||
pagination,
|
pagination,
|
||||||
|
containerStyle,
|
||||||
|
filterBarStyle,
|
||||||
|
formItemStyle,
|
||||||
filteredLenses: filteredLenses.map(lens => ({...lens, key: lens.id})),
|
filteredLenses: filteredLenses.map(lens => ({...lens, key: lens.id})),
|
||||||
handleSetSearchText,
|
handleSetSearchText,
|
||||||
handleAddLens,
|
handleAddLens,
|
||||||
@ -1,6 +1,7 @@
|
|||||||
import { useEffect, useMemo } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import {
|
import {
|
||||||
|
closeModal,
|
||||||
openModal,
|
openModal,
|
||||||
selectPatient,
|
selectPatient,
|
||||||
setCurrentPage,
|
setCurrentPage,
|
||||||
@ -8,9 +9,8 @@ import {
|
|||||||
setSearchText,
|
setSearchText,
|
||||||
setSortOrder,
|
setSortOrder,
|
||||||
setViewMode
|
setViewMode
|
||||||
} from "../redux/slices/patientsSlice";
|
} from "../../redux/slices/patientsSlice.js";
|
||||||
import { closeModal } from "../redux/slices/lensesSlice";
|
import { getCachedInfo } from "../../utils/cachedInfoUtils.js";
|
||||||
import { getCachedInfo } from "../utils/cachedInfoUtils";
|
|
||||||
|
|
||||||
const usePatientsUI = (patients) => {
|
const usePatientsUI = (patients) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
81
web-app/src/hooks/ui/useSetsUI.js
Normal file
81
web-app/src/hooks/ui/useSetsUI.js
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import {useDispatch, useSelector} from "react-redux";
|
||||||
|
import {useEffect} from "react";
|
||||||
|
import {
|
||||||
|
closeModal,
|
||||||
|
openModal,
|
||||||
|
selectSet,
|
||||||
|
setCurrentPage,
|
||||||
|
setPageSize,
|
||||||
|
setSearchText
|
||||||
|
} from "../../redux/slices/setsSlice.js";
|
||||||
|
|
||||||
|
|
||||||
|
const useSetsUI = (sets) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const {
|
||||||
|
searchText,
|
||||||
|
currentPage,
|
||||||
|
pageSize,
|
||||||
|
selectedSet,
|
||||||
|
isModalVisible,
|
||||||
|
} = useSelector(state => state.setsUI);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.title = "Наборы линз";
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
const containerStyle = { padding: 20 };
|
||||||
|
const filterBarStyle = { marginBottom: 20 };
|
||||||
|
const formItemStyle = { width: "100%" };
|
||||||
|
|
||||||
|
const handleSetSearchText = (value) => dispatch(setSearchText(value));
|
||||||
|
const handleCloseModal = () => dispatch(closeModal());
|
||||||
|
const handleSetCurrentPage = (page) => dispatch(setCurrentPage(page));
|
||||||
|
const handleSetPageSize = (size) => dispatch(setPageSize(size));
|
||||||
|
|
||||||
|
const handleAddSet = () => {
|
||||||
|
dispatch(selectSet(null));
|
||||||
|
dispatch(openModal());
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditSet = (set) => {
|
||||||
|
dispatch(selectSet(set));
|
||||||
|
dispatch(openModal());
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePaginationChange = (page, pageSize) => {
|
||||||
|
handleSetCurrentPage(page);
|
||||||
|
handleSetPageSize(pageSize);
|
||||||
|
};
|
||||||
|
|
||||||
|
const pagination = {
|
||||||
|
currentPage: currentPage,
|
||||||
|
pageSize: pageSize,
|
||||||
|
showSizeChanger: true,
|
||||||
|
pageSizeOptions: ["5", "10", "20", "50"],
|
||||||
|
onChange: (page, newPageSize) => {
|
||||||
|
handlePaginationChange(page, newPageSize);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredSets = sets.filter(set => set.title.toLowerCase().includes(searchText.toLowerCase()));
|
||||||
|
|
||||||
|
return {
|
||||||
|
searchText,
|
||||||
|
currentPage,
|
||||||
|
pageSize,
|
||||||
|
selectedSet,
|
||||||
|
isModalVisible,
|
||||||
|
pagination,
|
||||||
|
filteredSets,
|
||||||
|
containerStyle,
|
||||||
|
filterBarStyle,
|
||||||
|
formItemStyle,
|
||||||
|
handleSetSearchText,
|
||||||
|
handleAddSet,
|
||||||
|
handleEditSet,
|
||||||
|
handleCloseModal,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useSetsUI;
|
||||||
@ -22,8 +22,8 @@ import PatientListCard from "../components/patients/PatientListCard.jsx";
|
|||||||
import PatientFormModal from "../components/patients/PatientFormModal.jsx";
|
import PatientFormModal from "../components/patients/PatientFormModal.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 usePatients from "../hooks/usePatients.js";
|
import usePatients from "../hooks/data/usePatients.js";
|
||||||
import usePatientsUI from "../hooks/usePatientsUI.js";
|
import usePatientsUI from "../hooks/ui/usePatientsUI.js";
|
||||||
|
|
||||||
const {Option} = Select;
|
const {Option} = Select;
|
||||||
const {Title} = Typography;
|
const {Title} = Typography;
|
||||||
|
|||||||
@ -26,8 +26,8 @@ import LensCard from "../../components/lenses/LensListCard.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 useLenses from "../../hooks/useLenses.js";
|
import useLenses from "../../hooks/data/useLenses.js";
|
||||||
import useLensesUI from "../../hooks/useLensesUI.js";
|
import useLensesUI from "../../hooks/ui/useLensesUI.js";
|
||||||
|
|
||||||
const {Option} = Select;
|
const {Option} = Select;
|
||||||
const {useBreakpoint} = Grid;
|
const {useBreakpoint} = Grid;
|
||||||
|
|||||||
@ -1,160 +1,31 @@
|
|||||||
import {useAuth} from "../../AuthContext.jsx";
|
import {FloatButton, Input, List, Row, Typography} from "antd";
|
||||||
import {useEffect, useState} from "react";
|
|
||||||
import {FloatButton, Input, List, notification, Row, Typography} from "antd";
|
|
||||||
import getAllSets from "../../api/sets/getAllSets.js";
|
|
||||||
import {PlusOutlined, SwitcherOutlined} from "@ant-design/icons";
|
import {PlusOutlined, SwitcherOutlined} from "@ant-design/icons";
|
||||||
import SetListCard from "../../components/sets/SetListCard.jsx";
|
import SetListCard from "../../components/sets/SetListCard.jsx";
|
||||||
import SetFormModal from "../../components/sets/SetFormModal.jsx";
|
import SetFormModal from "../../components/sets/SetFormModal.jsx";
|
||||||
import updateSet from "../../api/sets/updateSet.js";
|
|
||||||
import addSet from "../../api/sets/addSet.js";
|
|
||||||
import deleteSet from "../../api/sets/deleteSet.js";
|
|
||||||
import addSetContent from "../../api/set_content/addSetContent.js";
|
|
||||||
import updateSetContent from "../../api/set_content/updateSetContent.js";
|
|
||||||
import appendLensesFromSet from "../../api/sets/appendLensesFromSet.js";
|
|
||||||
import LoadingIndicator from "../../components/LoadingIndicator.jsx";
|
import LoadingIndicator from "../../components/LoadingIndicator.jsx";
|
||||||
import {cacheInfo, getCachedInfo, getCacheTimestamp} from "../../utils/cachedInfoUtils.js";
|
import useSets from "../../hooks/data/useSets.js";
|
||||||
|
import useSetsUI from "../../hooks/ui/useSetsUI.js";
|
||||||
|
|
||||||
|
|
||||||
const {Title} = Typography;
|
const {Title} = Typography;
|
||||||
|
|
||||||
const SetLensesPage = () => {
|
const SetLensesPage = () => {
|
||||||
const {api} = useAuth();
|
const setsData = useSets();
|
||||||
|
const setsUI = useSetsUI(setsData.sets);
|
||||||
const [current, setCurrent] = useState(1);
|
|
||||||
const [pageSize, setPageSize] = useState(10);
|
|
||||||
|
|
||||||
const [searchText, setSearchText] = useState("");
|
|
||||||
const [sets, setSets] = useState([]);
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
|
||||||
const [selectedSet, setSelectedSet] = useState(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchSetsWithCache();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isModalVisible) {
|
|
||||||
const intervalId = setInterval(fetchSets, 5000);
|
|
||||||
return () => clearInterval(intervalId);
|
|
||||||
}
|
|
||||||
}, [isModalVisible]);
|
|
||||||
|
|
||||||
const fetchSetsWithCache = async () => {
|
|
||||||
const cachedData = getCachedInfo("setsData");
|
|
||||||
const cacheTimestamp = getCacheTimestamp("setsData");
|
|
||||||
|
|
||||||
if (cachedData && cacheTimestamp && (Date.now() - cacheTimestamp) < 60 * 1000) {
|
|
||||||
setSets(cachedData);
|
|
||||||
setLoading(false);
|
|
||||||
} else {
|
|
||||||
await fetchSets();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchSets = async () => {
|
|
||||||
const data = await getAllSets(api);
|
|
||||||
setSets(data);
|
|
||||||
setLoading(false);
|
|
||||||
cacheInfo("setsData", data);
|
|
||||||
};
|
|
||||||
|
|
||||||
const filteredSets = sets.filter(set => set.title.toLowerCase().includes(searchText.toLowerCase()));
|
|
||||||
|
|
||||||
const handleAddSet = () => {
|
|
||||||
setSelectedSet(null);
|
|
||||||
setIsModalVisible(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEditSet = (set) => {
|
|
||||||
setSelectedSet(set);
|
|
||||||
setIsModalVisible(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDeleteSet = async (set_id) => {
|
|
||||||
await deleteSet(api, set_id);
|
|
||||||
notification.success({
|
|
||||||
message: "Набор удален",
|
|
||||||
description: "Набор успешно удален.",
|
|
||||||
placement: "topRight",
|
|
||||||
});
|
|
||||||
await fetchSets();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCancel = () => {
|
|
||||||
setIsModalVisible(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAppendSet = async (set) => {
|
|
||||||
await appendLensesFromSet(api, set.id);
|
|
||||||
notification.success({
|
|
||||||
message: "Линзы добавлены",
|
|
||||||
description: "Линзы успешно добавлены.",
|
|
||||||
placement: "topRight",
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleModalSetSubmit = async (set, content = []) => {
|
|
||||||
setIsModalVisible(false);
|
|
||||||
|
|
||||||
let refreshed_set;
|
|
||||||
|
|
||||||
if (selectedSet) {
|
|
||||||
refreshed_set = await editCurrentSet(set);
|
|
||||||
} else {
|
|
||||||
refreshed_set = await addNewSet(set);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (refreshed_set && selectedSet) {
|
|
||||||
await updateContent(content, refreshed_set.id);
|
|
||||||
} else if (refreshed_set && !selectedSet) {
|
|
||||||
await setContent(content, refreshed_set.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
await fetchSets();
|
|
||||||
};
|
|
||||||
|
|
||||||
const setContent = async (content, set_id) => {
|
|
||||||
await addSetContent(api, content, set_id);
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateContent = async (content, set_id) => {
|
|
||||||
await updateSetContent(api, content, set_id);
|
|
||||||
};
|
|
||||||
|
|
||||||
const editCurrentSet = async (set) => {
|
|
||||||
const refreshed_set = await updateSet(api, selectedSet.id, set);
|
|
||||||
notification.success({
|
|
||||||
message: "Набор обновлен",
|
|
||||||
description: "Набор успешно обновлен.",
|
|
||||||
placement: "topRight",
|
|
||||||
});
|
|
||||||
return refreshed_set;
|
|
||||||
};
|
|
||||||
|
|
||||||
const addNewSet = async (set) => {
|
|
||||||
const refreshed_set = await addSet(api, set);
|
|
||||||
notification.success({
|
|
||||||
message: "Набор добавлен",
|
|
||||||
description: "Набор успешно добавлен.",
|
|
||||||
placement: "topRight",
|
|
||||||
});
|
|
||||||
return refreshed_set;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{padding: 20}}>
|
<div style={setsUI.containerStyle}>
|
||||||
<Title level={1}><SwitcherOutlined/> Наборы линз</Title>
|
<Title level={1}><SwitcherOutlined/> Наборы линз</Title>
|
||||||
<Row style={{marginBottom: 20}}>
|
<Row style={setsUI.filterBarStyle}>
|
||||||
<Input
|
<Input
|
||||||
placeholder="Поиск набора"
|
placeholder="Поиск набора"
|
||||||
onChange={(e) => setSearchText(e.target.value)}
|
onChange={(e) => setsUI.handleSetSearchText(e.target.value)}
|
||||||
style={{width: "100%"}}
|
style={setsUI.formItemStyle}
|
||||||
allowClear
|
allowClear
|
||||||
/>
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
{loading ? (
|
{setsData.isLoading ? (
|
||||||
<LoadingIndicator/>
|
<LoadingIndicator/>
|
||||||
) : (
|
) : (
|
||||||
<List
|
<List
|
||||||
@ -167,43 +38,33 @@ const SetLensesPage = () => {
|
|||||||
xl: 3,
|
xl: 3,
|
||||||
xxl: 3,
|
xxl: 3,
|
||||||
}}
|
}}
|
||||||
dataSource={filteredSets}
|
dataSource={setsUI.filteredSets}
|
||||||
renderItem={(set) => (
|
renderItem={(set) => (
|
||||||
<List.Item>
|
<List.Item>
|
||||||
<SetListCard
|
<SetListCard
|
||||||
set={set}
|
set={set}
|
||||||
handleEditSet={handleEditSet}
|
handleEditSet={setsUI.handleEditSet}
|
||||||
handleDeleteSet={handleDeleteSet}
|
handleDeleteSet={setsData.handleDeleteSet}
|
||||||
handleAppendSet={handleAppendSet}
|
handleAppendSet={setsData.handleAppendSet}
|
||||||
/>
|
/>
|
||||||
</List.Item>
|
</List.Item>
|
||||||
)}
|
)}
|
||||||
pagination={{
|
pagination={setsUI.pagination}
|
||||||
current,
|
|
||||||
pageSize,
|
|
||||||
showSizeChanger: true,
|
|
||||||
pageSizeOptions: ["5", "10", "20", "50"],
|
|
||||||
onChange: (page, newPageSize) => {
|
|
||||||
setCurrent(page);
|
|
||||||
setPageSize(newPageSize);
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<FloatButton
|
<FloatButton
|
||||||
icon={<PlusOutlined/>}
|
icon={<PlusOutlined/>}
|
||||||
type="primary"
|
type="primary"
|
||||||
style={{position: "fixed", bottom: 40, right: 40}}
|
|
||||||
tooltip="Добавить набор"
|
tooltip="Добавить набор"
|
||||||
onClick={handleAddSet}
|
onClick={setsUI.handleAddSet}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SetFormModal
|
<SetFormModal
|
||||||
visible={isModalVisible}
|
visible={setsUI.isModalVisible}
|
||||||
onCancel={handleCancel}
|
onCancel={setsUI.handleCloseModal}
|
||||||
onSubmit={handleModalSetSubmit}
|
onSubmit={setsData.handleModalSetSubmit}
|
||||||
setData={selectedSet}
|
setData={setsUI.selectedSet}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
|
import {createApi, fetchBaseQuery} from '@reduxjs/toolkit/query/react'
|
||||||
import CONFIG from "../../core/сonfig.js";
|
import CONFIG from "../../core/сonfig.js";
|
||||||
|
|
||||||
export const patientsApi = createApi({
|
export const patientsApi = createApi({
|
||||||
@ -27,7 +27,7 @@ export const patientsApi = createApi({
|
|||||||
invalidatesTags: ['Patient']
|
invalidatesTags: ['Patient']
|
||||||
}),
|
}),
|
||||||
updatePatient: builder.mutation({
|
updatePatient: builder.mutation({
|
||||||
query: ({ id, ...patient }) => ({
|
query: ({id, ...patient}) => ({
|
||||||
url: `/patients/${id}/`,
|
url: `/patients/${id}/`,
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: patient
|
body: patient
|
||||||
@ -48,5 +48,5 @@ export const {
|
|||||||
useGetPatientsQuery,
|
useGetPatientsQuery,
|
||||||
useAddPatientMutation,
|
useAddPatientMutation,
|
||||||
useUpdatePatientMutation,
|
useUpdatePatientMutation,
|
||||||
useDeletePatientMutation
|
useDeletePatientMutation,
|
||||||
} = patientsApi
|
} = patientsApi;
|
||||||
|
|||||||
43
web-app/src/redux/services/setContentApi.js
Normal file
43
web-app/src/redux/services/setContentApi.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import {createApi, fetchBaseQuery} from '@reduxjs/toolkit/query/react';
|
||||||
|
import CONFIG from "../../core/сonfig.js";
|
||||||
|
|
||||||
|
export const setContentApi = createApi({
|
||||||
|
reducerPath: 'setContentApi',
|
||||||
|
baseQuery: fetchBaseQuery({
|
||||||
|
baseUrl: CONFIG.BASE_URL,
|
||||||
|
prepareHeaders: (headers) => {
|
||||||
|
const token = localStorage.getItem('access_token');
|
||||||
|
if (token) headers.set('Authorization', `Bearer ${token}`);
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
tagTypes: ['SetContent'],
|
||||||
|
endpoints: (builder) => ({
|
||||||
|
getSetContent: builder.query({
|
||||||
|
query: (setId) => `/set_content/${setId}`,
|
||||||
|
providesTags: ['SetContent'],
|
||||||
|
}),
|
||||||
|
addSetContent: builder.mutation({
|
||||||
|
query: ({setId, setContent}) => ({
|
||||||
|
url: `/set_content/${setId}/`,
|
||||||
|
method: 'POST',
|
||||||
|
body: setContent
|
||||||
|
}),
|
||||||
|
invalidatesTags: ['SetContent']
|
||||||
|
}),
|
||||||
|
updateSetContent: builder.mutation({
|
||||||
|
query: ({setId, setContent}) => ({
|
||||||
|
url: `/set_content/${setId}/`,
|
||||||
|
method: 'PUT',
|
||||||
|
body: setContent
|
||||||
|
}),
|
||||||
|
invalidatesTags: ['SetContent']
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const {
|
||||||
|
useGetSetContentQuery,
|
||||||
|
useAddSetContentMutation,
|
||||||
|
useUpdateSetContentMutation,
|
||||||
|
} = setContentApi;
|
||||||
60
web-app/src/redux/services/setsApi.js
Normal file
60
web-app/src/redux/services/setsApi.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import {createApi, fetchBaseQuery} from '@reduxjs/toolkit/query/react'
|
||||||
|
import CONFIG from "../../core/сonfig.js";
|
||||||
|
|
||||||
|
export const setsApi = createApi({
|
||||||
|
reducerPath: 'setsApi',
|
||||||
|
baseQuery: fetchBaseQuery({
|
||||||
|
baseUrl: CONFIG.BASE_URL,
|
||||||
|
prepareHeaders: (headers) => {
|
||||||
|
const token = localStorage.getItem('access_token');
|
||||||
|
if (token) headers.set('Authorization', `Bearer ${token}`);
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
tagTypes: ['Set'],
|
||||||
|
endpoints: (builder) => ({
|
||||||
|
getSets: builder.query({
|
||||||
|
query: () => '/sets/',
|
||||||
|
providesTags: ['Set'],
|
||||||
|
refetchOnMountOrArgChange: 5
|
||||||
|
}),
|
||||||
|
addSet: builder.mutation({
|
||||||
|
query: (set) => ({
|
||||||
|
url: '/sets/',
|
||||||
|
method: 'POST',
|
||||||
|
body: set
|
||||||
|
}),
|
||||||
|
invalidatesTags: ['Set']
|
||||||
|
}),
|
||||||
|
updateSet: builder.mutation({
|
||||||
|
query: ({id, ...set}) => ({
|
||||||
|
url: `/sets/${id}/`,
|
||||||
|
method: 'PUT',
|
||||||
|
body: set
|
||||||
|
}),
|
||||||
|
invalidatesTags: ['Set']
|
||||||
|
}),
|
||||||
|
deleteSet: builder.mutation({
|
||||||
|
query: (id) => ({
|
||||||
|
url: `/sets/${id}/`,
|
||||||
|
method: 'DELETE'
|
||||||
|
}),
|
||||||
|
invalidatesTags: ['Set']
|
||||||
|
}),
|
||||||
|
appendLensesFromSet: builder.mutation({
|
||||||
|
query: (id) => ({
|
||||||
|
url: `/sets/append_lenses/${id}/`,
|
||||||
|
method: 'POST',
|
||||||
|
}),
|
||||||
|
invalidatesTags: ['Set']
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const {
|
||||||
|
useGetSetsQuery,
|
||||||
|
useAddSetMutation,
|
||||||
|
useUpdateSetMutation,
|
||||||
|
useDeleteSetMutation,
|
||||||
|
useAppendLensesFromSetMutation,
|
||||||
|
} = setsApi;
|
||||||
46
web-app/src/redux/slices/setsSlice.js
Normal file
46
web-app/src/redux/slices/setsSlice.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import {createSlice} from '@reduxjs/toolkit'
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
searchText: '',
|
||||||
|
currentPage: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
selectedSet: null,
|
||||||
|
isModalVisible: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const setsSlice = createSlice({
|
||||||
|
name: 'setsUI',
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
setSearchText: (state, action) => {
|
||||||
|
state.searchText = action.payload;
|
||||||
|
},
|
||||||
|
setCurrentPage: (state, action) => {
|
||||||
|
state.currentPage = action.payload;
|
||||||
|
},
|
||||||
|
setPageSize: (state, action) => {
|
||||||
|
state.pageSize = action.payload;
|
||||||
|
},
|
||||||
|
openModal: (state) => {
|
||||||
|
state.isModalVisible = true;
|
||||||
|
},
|
||||||
|
closeModal: (state) => {
|
||||||
|
state.isModalVisible = false;
|
||||||
|
state.selectedSet = null;
|
||||||
|
},
|
||||||
|
selectSet: (state, action) => {
|
||||||
|
state.selectedSet = action.payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const {
|
||||||
|
selectSet,
|
||||||
|
setSearchText,
|
||||||
|
setCurrentPage,
|
||||||
|
setPageSize,
|
||||||
|
openModal,
|
||||||
|
closeModal
|
||||||
|
} = setsSlice.actions;
|
||||||
|
|
||||||
|
export default setsSlice.reducer;
|
||||||
@ -2,7 +2,10 @@ 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 {lensesApi} from './services/lensesApi.js';
|
||||||
import lensesReducer from './slices/lensesSlice.js';
|
import lensesUIReducer from './slices/lensesSlice.js';
|
||||||
|
import {setsApi} from "./services/setsApi.js";
|
||||||
|
import setsUIReducer from './slices/setsSlice.js';
|
||||||
|
import {setContentApi} from "./services/setContentApi.js";
|
||||||
|
|
||||||
export const store = configureStore({
|
export const store = configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
@ -10,10 +13,20 @@ export const store = configureStore({
|
|||||||
patientsUI: patientsUIReducer,
|
patientsUI: patientsUIReducer,
|
||||||
|
|
||||||
[lensesApi.reducerPath]: lensesApi.reducer,
|
[lensesApi.reducerPath]: lensesApi.reducer,
|
||||||
lensesUI: lensesReducer,
|
lensesUI: lensesUIReducer,
|
||||||
|
|
||||||
|
[setsApi.reducerPath]: setsApi.reducer,
|
||||||
|
setsUI: setsUIReducer,
|
||||||
|
|
||||||
|
[setContentApi.reducerPath]: setContentApi.reducer,
|
||||||
},
|
},
|
||||||
middleware: (getDefaultMiddleware) => (
|
middleware: (getDefaultMiddleware) => (
|
||||||
getDefaultMiddleware().concat(patientsApi.middleware, lensesApi.middleware)
|
getDefaultMiddleware().concat(
|
||||||
|
patientsApi.middleware,
|
||||||
|
lensesApi.middleware,
|
||||||
|
setsApi.middleware,
|
||||||
|
setContentApi.middleware,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user