diff --git a/web-app/src/api/lenses/addLens.js b/web-app/src/api/lenses/addLens.js deleted file mode 100644 index 7d3be02..0000000 --- a/web-app/src/api/lenses/addLens.js +++ /dev/null @@ -1,8 +0,0 @@ -import CONFIG from "../../core/сonfig.js"; - -const addLens = async (api, lens) => { - const response = await api.post(`${CONFIG.BASE_URL}/lenses/`, lens); - return response.data; -}; - -export default addLens; \ No newline at end of file diff --git a/web-app/src/api/lenses/deleteLens.js b/web-app/src/api/lenses/deleteLens.js deleted file mode 100644 index dcaa41e..0000000 --- a/web-app/src/api/lenses/deleteLens.js +++ /dev/null @@ -1,8 +0,0 @@ -import CONFIG from "../../core/сonfig.js"; - -const deleteLens = async (api, lens_id) => { - const response = await api.delete(`${CONFIG.BASE_URL}/lenses/${lens_id}/`); - return response.data; -}; - -export default deleteLens; \ No newline at end of file diff --git a/web-app/src/api/lenses/getAllLenses.js b/web-app/src/api/lenses/getAllLenses.js deleted file mode 100644 index 9e0e095..0000000 --- a/web-app/src/api/lenses/getAllLenses.js +++ /dev/null @@ -1,8 +0,0 @@ -import CONFIG from "../../core/сonfig.js"; - -const getAllLenses = async (api) => { - const response = await api.get(`${CONFIG.BASE_URL}/lenses/`); - return response.data; -}; - -export default getAllLenses; \ No newline at end of file diff --git a/web-app/src/api/lenses/updateLens.js b/web-app/src/api/lenses/updateLens.js deleted file mode 100644 index a0fd1af..0000000 --- a/web-app/src/api/lenses/updateLens.js +++ /dev/null @@ -1,8 +0,0 @@ -import CONFIG from "../../core/сonfig.js"; - -const updateLens = async (api, lensId, lensData) => { - const response = await api.put(`${CONFIG.BASE_URL}/lenses/${lensId}/`, lensData); - return response.data; -}; - -export default updateLens; \ No newline at end of file diff --git a/web-app/src/api/patients/addPatient.js b/web-app/src/api/patients/addPatient.js deleted file mode 100644 index f0ed081..0000000 --- a/web-app/src/api/patients/addPatient.js +++ /dev/null @@ -1,8 +0,0 @@ -import CONFIG from "../../core/сonfig.js"; - -const addPatient = async (api, patient) => { - const response = await api.post(`${CONFIG.BASE_URL}/patients/`, patient); - return response.data; -}; - -export default addPatient; \ No newline at end of file diff --git a/web-app/src/api/patients/deletePatient.js b/web-app/src/api/patients/deletePatient.js deleted file mode 100644 index dea07ad..0000000 --- a/web-app/src/api/patients/deletePatient.js +++ /dev/null @@ -1,8 +0,0 @@ -import CONFIG from "../../core/сonfig.js"; - -const deletePatient = async (api, patient_id) => { - const response = await api.delete(`${CONFIG.BASE_URL}/patients/${patient_id}/`); - return response.data; -}; - -export default deletePatient; \ No newline at end of file diff --git a/web-app/src/api/patients/updatePatient.js b/web-app/src/api/patients/updatePatient.js deleted file mode 100644 index 6040132..0000000 --- a/web-app/src/api/patients/updatePatient.js +++ /dev/null @@ -1,8 +0,0 @@ -import CONFIG from "../../core/сonfig.js"; - -const updatePatient = async (api, patientId, patientData) => { - const response = await api.put(`${CONFIG.BASE_URL}/patients/${patientId}/`, patientData); - return response.data; -}; - -export default updatePatient; \ No newline at end of file diff --git a/web-app/src/api/set_content/addSetContent.js b/web-app/src/api/set_content/addSetContent.js deleted file mode 100644 index 311b85a..0000000 --- a/web-app/src/api/set_content/addSetContent.js +++ /dev/null @@ -1,8 +0,0 @@ -import CONFIG from "../../core/сonfig.js"; - -const addSetContent = async (api, set_content, set_id) => { - const response = await api.post(`${CONFIG.BASE_URL}/set_content/${set_id}/`, set_content); - return response.data; -}; - -export default addSetContent; \ No newline at end of file diff --git a/web-app/src/api/set_content/updateSetContent.js b/web-app/src/api/set_content/updateSetContent.js deleted file mode 100644 index b2edc4c..0000000 --- a/web-app/src/api/set_content/updateSetContent.js +++ /dev/null @@ -1,8 +0,0 @@ -import CONFIG from "../../core/сonfig.js"; - -const updateSetContent = async (api, set_content, set_id) => { - const response = await api.put(`${CONFIG.BASE_URL}/set_content/${set_id}/`, set_content); - return response.data; -}; - -export default updateSetContent; \ No newline at end of file diff --git a/web-app/src/api/sets/addSet.js b/web-app/src/api/sets/addSet.js deleted file mode 100644 index c199d30..0000000 --- a/web-app/src/api/sets/addSet.js +++ /dev/null @@ -1,8 +0,0 @@ -import CONFIG from "../../core/сonfig.js"; - -const addSet = async (api, set) => { - const response = await api.post(`${CONFIG.BASE_URL}/sets/`, set); - return response.data; -}; - -export default addSet; \ No newline at end of file diff --git a/web-app/src/api/sets/appendLensesFromSet.js b/web-app/src/api/sets/appendLensesFromSet.js deleted file mode 100644 index fc549f9..0000000 --- a/web-app/src/api/sets/appendLensesFromSet.js +++ /dev/null @@ -1,8 +0,0 @@ -import CONFIG from "../../core/сonfig.js"; - -const appendLensesFromSet = async (api, set_id) => { - const response = await api.post(`${CONFIG.BASE_URL}/sets/append_lenses/${set_id}/`); - return response.data; -}; - -export default appendLensesFromSet; \ No newline at end of file diff --git a/web-app/src/api/sets/deleteSet.js b/web-app/src/api/sets/deleteSet.js deleted file mode 100644 index b983050..0000000 --- a/web-app/src/api/sets/deleteSet.js +++ /dev/null @@ -1,8 +0,0 @@ -import CONFIG from "../../core/сonfig.js"; - -const deleteSet = async (api, set_id) => { - const response = await api.delete(`${CONFIG.BASE_URL}/sets/${set_id}/`); - return response.data; -}; - -export default deleteSet; \ No newline at end of file diff --git a/web-app/src/api/sets/getAllSets.js b/web-app/src/api/sets/getAllSets.js deleted file mode 100644 index 2e9cb8e..0000000 --- a/web-app/src/api/sets/getAllSets.js +++ /dev/null @@ -1,8 +0,0 @@ -import CONFIG from "../../core/сonfig.js"; - -const getAllSets = async (api) => { - const response = await api.get(`${CONFIG.BASE_URL}/sets/`); - return response.data; -}; - -export default getAllSets; \ No newline at end of file diff --git a/web-app/src/api/sets/updateSet.js b/web-app/src/api/sets/updateSet.js deleted file mode 100644 index 9855b72..0000000 --- a/web-app/src/api/sets/updateSet.js +++ /dev/null @@ -1,8 +0,0 @@ -import CONFIG from "../../core/сonfig.js"; - -const updateSet = async (api, set_id, set) => { - const response = await api.put(`${CONFIG.BASE_URL}/sets/${set_id}/`, set); - return response.data; -}; - -export default updateSet; \ No newline at end of file diff --git a/web-app/src/components/lens_issues/LensIssueViewModal.jsx b/web-app/src/components/lens_issues/LensIssueViewModal.jsx index e96fdbc..ac0429f 100644 --- a/web-app/src/components/lens_issues/LensIssueViewModal.jsx +++ b/web-app/src/components/lens_issues/LensIssueViewModal.jsx @@ -72,7 +72,7 @@ const LensIssueViewModal = ({visible, onCancel, lensIssue}) => { LensIssueViewModal.propTypes = { visible: PropTypes.bool.isRequired, onCancel: PropTypes.func.isRequired, - lensIssue: LensIssuePropType.isRequired, + lensIssue: LensIssuePropType, }; export default LensIssueViewModal; \ No newline at end of file diff --git a/web-app/src/hooks/data/useIssues.js b/web-app/src/hooks/data/useIssues.js new file mode 100644 index 0000000..0d6f379 --- /dev/null +++ b/web-app/src/hooks/data/useIssues.js @@ -0,0 +1,47 @@ +import {useDispatch, useSelector} from "react-redux"; +import {useAddLensIssuesMutation, useGetLensIssuesQuery} from "../../redux/services/lensIssuesApi.js"; +import {notification} from "antd"; +import {closeModal} from "../../redux/slices/lensIssuesSlice.js"; + + +const useIssues = () => { + const dispatch = useDispatch(); + const { + selectedIssue, + } = useSelector(state => state.lensIssuesUI); + + const {data: issues = [], isLoading, isError, error} = useGetLensIssuesQuery(undefined, { + pollingInterval: 20000, + }); + const [addIssue] = useAddLensIssuesMutation(); + + const handleSubmitFormModal = async (issueDate, patientId, lensId) => { + dispatch(closeModal()); + + try { + await addIssue({issue_date: issueDate, patient_id: patientId, lens_id: lensId}); + notification.success({ + message: "Линза выдана", + description: "Линза успешно выдана пациенту.", + placement: "topRight", + }); + } catch (error) { + notification.error({ + message: "Ошибка выдачи линзы", + description: error.data?.message || "Не удалось выдать линзу пациенту.", + placement: "topRight", + }); + } + }; + + return { + issues, + isLoading, + isError, + error, + selectedIssue, + handleSubmitFormModal, + }; +}; + +export default useIssues; \ No newline at end of file diff --git a/web-app/src/hooks/ui/useIssuesUI.js b/web-app/src/hooks/ui/useIssuesUI.js new file mode 100644 index 0000000..3cc5b97 --- /dev/null +++ b/web-app/src/hooks/ui/useIssuesUI.js @@ -0,0 +1,131 @@ +import {useDispatch, useSelector} from "react-redux"; +import {getCachedInfo} from "../../utils/cachedInfoUtils.js"; +import { + closeModal, + openModal, + selectIssue, + setCurrentPage, + setEndFilterDate, + setPageSize, + setSearchText, + setStartFilterDate, + setViewMode +} from "../../redux/slices/lensIssuesSlice.js"; +import {useEffect, useMemo} from "react"; +import dayjs from "dayjs"; + + +const useIssuesUI = (issues) => { + const dispatch = useDispatch(); + const { + searchText, + currentPage, + pageSize, + selectedIssue, + isModalVisible, + viewMode, + startFilterDate, + endFilterDate, + } = useSelector(state => state.lensIssuesUI); + + useEffect(() => { + document.title = "Выдача линз"; + const cachedViewMode = getCachedInfo("viewModeIssues"); + if (cachedViewMode) dispatch(setViewMode(cachedViewMode)); + }, [dispatch]) + + const startFilterDateConverted = startFilterDate ? dayjs(startFilterDate) : null; + const endFilterDateConverted = endFilterDate ? dayjs(endFilterDate) : null; + const filterDates = [startFilterDateConverted, endFilterDateConverted]; + const isFilterDates = startFilterDate && endFilterDate; + + const handleSetSearchText = (value) => dispatch(setSearchText(value)); + const handleSetViewMode = (mode) => dispatch(setViewMode(mode)); + const handleSetCurrentPage = (page) => dispatch(setCurrentPage(page)); + const handleSetPageSize = (size) => dispatch(setPageSize(size)); + const handleCloseModal = () => dispatch(closeModal()); + const handleSelectIssue = (issue) => dispatch(selectIssue(issue)); + const resetSelectedIssue = () => dispatch(selectIssue(null)); + + const handleAddIssue = () => { + dispatch(selectIssue(null)); + dispatch(openModal()); + }; + + const handlePaginationChange = (page, pageSize) => { + handleSetCurrentPage(page); + handleSetPageSize(pageSize); + }; + + const handleFilterDateChange = (dates) => { + if (dates) { + const [start, end] = dates; + dispatch(setStartFilterDate(start.toISOString())); + dispatch(setEndFilterDate(end.toISOString())); + } + }; + + const handleResetFilterDate = () => { + dispatch(setStartFilterDate(null)); + dispatch(setEndFilterDate(null)); + }; + + const filteredIssues = useMemo(() => { + return issues.filter(issue => { + let dateFilter = true; + + if (startFilterDateConverted && endFilterDateConverted) { + const issueDate = dayjs(issue.issue_date); + + dateFilter = issueDate.isAfter(startFilterDateConverted) && issueDate.isBefore(endFilterDateConverted); + } + + return ( + ( + issue.patient.last_name.toLowerCase().includes(searchText) || + issue.patient.first_name.toLowerCase().includes(searchText) || + issue.doctor.last_name.toLowerCase().includes(searchText) || + issue.doctor.first_name.toLowerCase().includes(searchText) + ) && + dateFilter + ) + }); + }, [issues, searchText, startFilterDateConverted, endFilterDateConverted]); + + const pagination = { + current: currentPage, + pageSize: pageSize, + showSizeChanger: true, + pageSizeOptions: ["5", "10", "20", "50"], + onChange: (page, newPageSize) => { + setCurrentPage(page); + setPageSize(newPageSize); + }, + }; + + return { + filteredIssues, + pagination, + searchText, + selectedIssue, + isModalVisible, + viewMode, + currentPage, + pageSize, + filterDates, + isFilterDates, + handleAddIssue, + handlePaginationChange, + handleSetSearchText, + handleSetViewMode, + handleSetCurrentPage, + handleSetPageSize, + handleCloseModal, + handleFilterDateChange, + resetSelectedIssue, + handleSelectIssue, + handleResetFilterDate, + }; +}; + +export default useIssuesUI; \ No newline at end of file diff --git a/web-app/src/hooks/ui/useSetsUI.js b/web-app/src/hooks/ui/useSetsUI.js index b91f806..7223605 100644 --- a/web-app/src/hooks/ui/useSetsUI.js +++ b/web-app/src/hooks/ui/useSetsUI.js @@ -1,5 +1,5 @@ import {useDispatch, useSelector} from "react-redux"; -import {useEffect} from "react"; +import {useEffect, useMemo} from "react"; import { closeModal, openModal, @@ -24,9 +24,9 @@ const useSetsUI = (sets) => { document.title = "Наборы линз"; }, [dispatch]); - const containerStyle = { padding: 20 }; - const filterBarStyle = { marginBottom: 20 }; - const formItemStyle = { width: "100%" }; + const containerStyle = {padding: 20}; + const filterBarStyle = {marginBottom: 20}; + const formItemStyle = {width: "100%"}; const handleSetSearchText = (value) => dispatch(setSearchText(value)); const handleCloseModal = () => dispatch(closeModal()); @@ -58,7 +58,10 @@ const useSetsUI = (sets) => { }, }; - const filteredSets = sets.filter(set => set.title.toLowerCase().includes(searchText.toLowerCase())); + const filteredSets = useMemo( + () => sets.filter(set => set.title.toLowerCase().includes(searchText.toLowerCase())), + [sets, searchText] + ); return { searchText, diff --git a/web-app/src/pages/IssuesPage.jsx b/web-app/src/pages/IssuesPage.jsx index 13e0507..5a48499 100644 --- a/web-app/src/pages/IssuesPage.jsx +++ b/web-app/src/pages/IssuesPage.jsx @@ -1,5 +1,4 @@ import { - notification, Table, Input, Row, @@ -9,130 +8,28 @@ import { Button, FloatButton, Typography, - Timeline, Grid, Pagination + Timeline, + Grid, + Pagination } from "antd"; -import getAllLensIssues from "../api/lens_issues/getAllLensIssues.js"; -import {useEffect, useState} from "react"; -import {useAuth} from "../AuthContext.jsx"; import {DatabaseOutlined, PlusOutlined, UnorderedListOutlined} from "@ant-design/icons"; import LensIssueViewModal from "../components/lens_issues/LensIssueViewModal.jsx"; import dayjs from "dayjs"; import LensIssueFormModal from "../components/lens_issues/LensIssueFormModal.jsx"; -import addLensIssue from "../api/lens_issues/addLensIssue.js"; import SelectViewMode from "../components/SelectViewMode.jsx"; import LoadingIndicator from "../components/LoadingIndicator.jsx"; -import {getCachedInfo, getCacheTimestamp} from "../utils/cachedInfoUtils.js"; +import useIssues from "../hooks/data/useIssues.js"; +import useIssuesUI from "../hooks/ui/useIssuesUI.js"; const {Title} = Typography; const {useBreakpoint} = Grid; const IssuesPage = () => { - const {api} = useAuth(); + const issuesData = useIssues(); + const issuesUI = useIssuesUI(issuesData.issues); + const screens = useBreakpoint(); - const [loading, setLoading] = useState(true); - const [lensIssues, setLensIssues] = useState([]); - const [searchTerm, setSearchTerm] = useState(""); - const [selectedIssue, setSelectedIssue] = useState(null); - const [isModalVisible, setIsModalVisible] = useState(false); - const [viewMode, setViewMode] = useState("table"); - - const [currentPage, setCurrentPage] = useState(1); - const [pageSize, setPageSize] = useState(10); - - const [timelinePage, setTimelinePage] = useState(1); - const [timeLinePageSize, setTimeLinePageSize] = useState(10); - - const [startFilterDate, setStartFilterDate] = useState(null); - const [endFilterDate, setEndFilterDate] = useState(null); - - useEffect(() => { - fetchLensIssuesWithCache(); - fetchViewModeFromCache(); - document.title = "Выдача линз"; - }, []); - - useEffect(() => { - if (!isModalVisible) { - const intervalId = setInterval(fetchLensIssues, 5000); - return () => clearInterval(intervalId); - } - }, [isModalVisible]); - - const fetchLensIssuesWithCache = async () => { - const cachedData = getCachedInfo("lensIssuesData"); - const cacheTimestamp = getCacheTimestamp("lensIssuesData"); - - if (cachedData && cacheTimestamp && (Date.now() - cacheTimestamp) < 60 * 1000) { - setLensIssues(JSON.parse(cachedData)); - setLoading(false); - } else { - await fetchLensIssues(); - } - }; - - const fetchViewModeFromCache = () => { - const cachedViewMode = getCachedInfo("viewModeIssues"); - if (cachedViewMode) { - setViewMode(cachedViewMode); - } - }; - - const handleAddIssue = () => { - setSelectedIssue(null); - setIsModalVisible(true); - }; - - const handleCloseViewModal = () => { - setSelectedIssue(null); - }; - - const handleCloseFormModal = () => { - setIsModalVisible(false); - }; - - const handleSubmitFormModal = async (issue_date, patient_id, lens_id) => { - setIsModalVisible(false); - await addLensIssue(api, {issue_date, patient_id, lens_id}); - notification.success({ - message: "Линза выдана", - description: "Линза успешно выдана пациенту.", - placement: "topRight", - }); - await fetchLensIssues(); - }; - - const fetchLensIssues = async () => { - const data = await getAllLensIssues(api); - setLensIssues(data); - setLoading(false); - }; - - const handleSearch = (e) => { - setSearchTerm(e.target.value.toLowerCase()); - }; - - const filteredIssues = lensIssues.filter(issue => { - let dateFilter = true; - - if (startFilterDate && endFilterDate) { - const issueDate = dayjs(issue.issue_date); - - dateFilter = issueDate.isAfter(startFilterDate) && issueDate.isBefore(endFilterDate); - } - - return ( - ( - issue.patient.last_name.toLowerCase().includes(searchTerm) || - issue.patient.first_name.toLowerCase().includes(searchTerm) || - issue.doctor.last_name.toLowerCase().includes(searchTerm) || - issue.doctor.first_name.toLowerCase().includes(searchTerm) - ) && - dateFilter - ) - } - ); - const viewModes = [ { value: "table", @@ -176,7 +73,7 @@ const IssuesPage = () => { title: "Действия", key: "actions", render: (_, issue) => ( - + ), }, ]; @@ -184,23 +81,14 @@ const IssuesPage = () => { const TableView = () => (