diff --git a/web-app/src/components/patients/PatientFormModal.jsx b/web-app/src/components/patients/PatientFormModal.jsx index 130ca83..dd6df9a 100644 --- a/web-app/src/components/patients/PatientFormModal.jsx +++ b/web-app/src/components/patients/PatientFormModal.jsx @@ -55,7 +55,7 @@ const PatientFormModal = ({visible, onCancel, onSubmit, patient}) => { cancelText={"Отмена"} maskClosable={false} forceRender={true} - style={{top: 20}} + style={{marginTop: 20, marginBottom: 50}} centered >
diff --git a/web-app/src/hooks/usePatients.js b/web-app/src/hooks/usePatients.js new file mode 100644 index 0000000..c2388a9 --- /dev/null +++ b/web-app/src/hooks/usePatients.js @@ -0,0 +1,120 @@ +import { useEffect } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { notification } from "antd"; +import { + useAddPatientMutation, + useDeletePatientMutation, + useGetPatientsQuery, + useUpdatePatientMutation +} from "../redux/services/patientsApi"; +import { + openModal, + closeModal, + selectPatient, + setSearchText, + setSortOrder, + setViewMode, + setCurrentPage, + setPageSize +} from "../redux/slices/patientsSlice"; +import { getCachedInfo } from "../utils/cachedInfoUtils"; + +const usePatients = () => { + const dispatch = useDispatch(); + const { + searchText, + sortOrder, + viewMode, + selectedPatient, + isModalVisible + } = useSelector(state => state.patientsUI); + + const { data: patients = [], isLoading, isError } = useGetPatientsQuery(undefined, { + pollingInterval: 20000, + }); + const [addPatient] = useAddPatientMutation(); + const [updatePatient] = useUpdatePatientMutation(); + const [deletePatient] = useDeletePatientMutation(); + + useEffect(() => { + document.title = "Пациенты"; + const cachedViewMode = getCachedInfo("viewModePatients"); + if (cachedViewMode) dispatch(setViewMode(cachedViewMode)); + }, [dispatch]); + + const handleAddPatient = () => { + dispatch(selectPatient(null)); + dispatch(openModal()); + }; + + const handleEditPatient = (patient) => { + dispatch(selectPatient(patient)); + dispatch(openModal()); + }; + + const handleDeletePatient = async (patientId) => { + try { + await deletePatient(patientId).unwrap(); + notification.success({ + message: "Пациент удалён", + description: "Пациент успешно удалён из базы.", + placement: "topRight", + }); + } catch (error) { + notification.error({ + message: "Ошибка удаления", + description: error.data?.message || "Не удалось удалить пациента", + placement: "topRight", + }); + } + }; + + const handleModalPatientSubmit = async (patientData) => { + try { + if (selectedPatient) { + await updatePatient({ id: selectedPatient.id, ...patientData }).unwrap(); + notification.success({ + message: "Пациент обновлён", + description: `Данные пациента ${patientData.first_name} ${patientData.last_name} успешно обновлены.`, + placement: "topRight", + }); + } else { + await addPatient(patientData).unwrap(); + notification.success({ + message: "Пациент добавлен", + description: `Пациент ${patientData.first_name} ${patientData.last_name} успешно добавлен.`, + placement: "topRight", + }); + } + dispatch(closeModal()); + } catch (error) { + notification.error({ + message: "Ошибка", + description: error.data?.message || "Произошла ошибка при сохранении", + placement: "topRight", + }); + } + }; + + return { + patients, + isLoading, + isError, + searchText, + sortOrder, + viewMode, + isModalVisible, + selectedPatient, + handleAddPatient, + handleEditPatient, + handleDeletePatient, + handleModalPatientSubmit, + setSearchText: (text) => dispatch(setSearchText(text)), + setSortOrder: (order) => dispatch(setSortOrder(order)), + setViewMode: (mode) => dispatch(setViewMode(mode)), + setCurrentPage: (page) => dispatch(setCurrentPage(page)), + setPageSize: (size) => dispatch(setPageSize(size)), + }; +}; + +export default usePatients; diff --git a/web-app/src/pages/PatientsPage.jsx b/web-app/src/pages/PatientsPage.jsx index 8d70a7f..ddceac4 100644 --- a/web-app/src/pages/PatientsPage.jsx +++ b/web-app/src/pages/PatientsPage.jsx @@ -1,4 +1,4 @@ -import {useEffect} from 'react'; +import {useEffect, useMemo} from 'react'; import { Input, Select, @@ -6,147 +6,77 @@ import { FloatButton, Row, Col, - notification, - Tooltip, Table, Button, Popconfirm, - Typography, Result + Typography, + Result, Tooltip } from "antd"; import { BuildOutlined, PlusOutlined, SortAscendingOutlined, - SortDescendingOutlined, TableOutlined, + SortDescendingOutlined, + TableOutlined, TeamOutlined } from "@ant-design/icons"; import {useDispatch, useSelector} from "react-redux"; import { - useAddPatientMutation, - useDeletePatientMutation, - useGetPatientsQuery, - useUpdatePatientMutation -} from "../redux/services/patientsApi.js"; -import { - openModal, - selectPatient, setSearchText, setSortOrder, setViewMode, - closeModal, setCurrentPage, setPageSize + closeModal, + setCurrentPage, + setPageSize } from "../redux/slices/patientsSlice.js"; import PatientListCard from "../components/patients/PatientListCard.jsx"; import PatientFormModal from "../components/patients/PatientFormModal.jsx"; import SelectViewMode from "../components/SelectViewMode.jsx"; import LoadingIndicator from "../components/LoadingIndicator.jsx"; -import {getCachedInfo} from "../utils/cachedInfoUtils.js"; +import usePatients from "../hooks/usePatients.js"; const {Option} = Select; -const {Title} = Typography +const {Title} = Typography; const PatientsPage = () => { const dispatch = useDispatch(); + const { + patients, + isLoading, + isError, + handleAddPatient, + handleEditPatient, + handleDeletePatient, + handleModalPatientSubmit + } = usePatients(); const { searchText, sortOrder, viewMode, selectedPatient, - isModalVisible + isModalVisible, + currentPage, + pageSize } = useSelector(state => state.patientsUI); - const {data: patients = [], isLoading, isError} = useGetPatientsQuery(); - const [addPatient] = useAddPatientMutation(); - const [updatePatient] = useUpdatePatientMutation(); - const [deletePatient] = useDeletePatientMutation(); - useEffect(() => { - document.title = "Пациенты" - const cachedViewMode = getCachedInfo("viewModePatients"); - if (cachedViewMode) dispatch(setViewMode(cachedViewMode)); + document.title = "Пациенты"; }, []); - const handleAddPatient = () => { - dispatch(selectPatient(null)); - dispatch(openModal()); - }; - - const handleEditPatient = (patient) => { - dispatch(selectPatient(patient)); - dispatch(openModal()); - }; - - const handleDeletePatient = async (patientId) => { - try { - await deletePatient(patientId).unwrap() - notification.success({ - message: "Пациент удалён", - description: "Пациент успешно удалён из базы.", - placement: "topRight", + const filteredPatients = useMemo(() => { + return patients + .filter(patient => + Object.values(patient) + .filter(value => typeof value === "string") + .some(value => value.toLowerCase().includes(searchText.toLowerCase())) + ) + .sort((a, b) => { + const fullNameA = `${a.last_name} ${a.first_name}`; + const fullNameB = `${b.last_name} ${b.first_name}`; + return sortOrder === "asc" ? fullNameA.localeCompare(fullNameB) : fullNameB.localeCompare(fullNameA); }); - } catch (error) { - notification.error({ - message: "Ошибка удаления", - description: error.data?.message || "Не удалось удалить пациента", - placement: "topRight", - }); - } - }; - - const handleModalPatientSubmit = async (patientData) => { - try { - if (selectedPatient) { - await updatePatient({id: selectedPatient.id, ...patientData}).unwrap() - notification.success({ - message: "Пациент обновлён", - description: `Данные пациента ${patientData.first_name} ${patientData.last_name} успешно обновлены.`, - placement: "topRight", - }) - } else { - await addPatient(patientData).unwrap() - notification.success({ - message: "Пациент добавлен", - description: `Пациент ${patientData.first_name} ${patientData.last_name} успешно добавлен.`, - placement: "topRight", - }); - } - dispatch(closeModal()); - } catch (error) { - notification.error({ - message: "Ошибка", - description: error.data?.message || "Произошла ошибка при сохранении", - placement: "topRight", - }); - } - }; - - const filteredPatients = patients - .filter(patient => { - const searchLower = searchText.toLowerCase(); - return Object.values(patient) - .filter(value => typeof value === "string") - .some(value => value.toLowerCase().includes(searchLower)); - }) - .sort((a, b) => { - const fullNameA = `${a.last_name} ${a.first_name}`; - const fullNameB = `${b.last_name} ${b.first_name}`; - return sortOrder === "asc" - ? fullNameA.localeCompare(fullNameB) - : fullNameB.localeCompare(fullNameA); - }); - - const viewModes = [ - { - value: "tile", - label: "Плитка", - icon: - }, - { - value: "table", - label: "Таблица", - icon: - } - ]; + }, [patients, searchText, sortOrder]); const columns = [ { @@ -192,7 +122,7 @@ const PatientsPage = () => { title: "Действия", key: "actions", fixed: 'right', - render: (text, record) => ( + render: (_, record) => ( @@ -213,70 +143,20 @@ const PatientsPage = () => { }, ]; - const TableView = () => ( - ({...patient, key: patient.id}))} - scroll={{ - x: "max-content" - }} - showSorterTooltip={false} - pagination={{ - current: 1, - pageSize: 10, - showSizeChanger: true, - pageSizeOptions: ["5", "10", "20", "50"], - onChange: (page, newPageSize) => { - dispatch(setCurrentPage(page)); - dispatch(setPageSize(newPageSize)); - }, - }} - /> - ); + const viewModes = [ + { + value: "tile", + label: "Плитка", + icon: + }, + { + value: "table", + label: "Таблица", + icon: + } + ]; - const TileView = () => ( - ( - - - - )} - pagination={{ - current: 1, - pageSize: 10, - showSizeChanger: true, - pageSizeOptions: ["5", "10", "20", "50"], - onChange: (page, newPageSize) => { - dispatch(setCurrentPage(page)); - dispatch(setPageSize(newPageSize)); - }, - }} - /> - ); - - if (isError) { - return ( - - ); - } + if (isError) return ; return (
@@ -320,7 +200,7 @@ const PatientsPage = () => { }> dispatch(setViewMode(value))} + setViewMode={value => dispatch(setViewMode(value))} localStorageKey={"viewModePatients"} toolTipText={"Формат отображения пациентов"} viewModes={viewModes} @@ -328,22 +208,40 @@ const PatientsPage = () => { - {isLoading ? ( - - ) : viewMode === "tile" ? ( - + {isLoading ? : viewMode === "tile" ? ( + ( + + + + )} + pagination={{ + current: currentPage, pageSize, onChange: (p, s) => { + dispatch(setCurrentPage(p)); + dispatch(setPageSize(s)); + } + }} + /> ) : ( - +
({...p, key: p.id}))} + pagination={{ + current: currentPage, pageSize, onChange: (p, s) => { + dispatch(setCurrentPage(p)); + dispatch(setPageSize(s)); + } + }} + /> )} - } - type="primary" - style={{position: "fixed", bottom: 40, right: 40}} - onClick={handleAddPatient} - tooltip="Добавить пациента" - /> - + } type="primary" onClick={handleAddPatient} tooltip="Добавить пациента"/> dispatch(closeModal())} diff --git a/web-app/src/redux/services/patientsApi.js b/web-app/src/redux/services/patientsApi.js index cc14178..39ce83a 100644 --- a/web-app/src/redux/services/patientsApi.js +++ b/web-app/src/redux/services/patientsApi.js @@ -16,7 +16,7 @@ export const patientsApi = createApi({ getPatients: builder.query({ query: () => '/patients/', providesTags: ['Patient'], - refetchOnMountOrArgChange: 60 + refetchOnMountOrArgChange: 5 }), addPatient: builder.mutation({ query: (patient) => ({ diff --git a/web-app/src/redux/slices/patientsSlice.js b/web-app/src/redux/slices/patientsSlice.js index d8ead09..f6f1fb1 100644 --- a/web-app/src/redux/slices/patientsSlice.js +++ b/web-app/src/redux/slices/patientsSlice.js @@ -1,4 +1,5 @@ import {createSlice} from '@reduxjs/toolkit' +import {cacheInfo} from "../../utils/cachedInfoUtils.js"; const initialState = { searchText: '', @@ -22,7 +23,7 @@ const patientsSlice = createSlice({ }, setViewMode: (state, action) => { state.viewMode = action.payload; - localStorage.setItem('viewModePatients', action.payload); + cacheInfo("viewModePatients", action.payload); }, setCurrentPage: (state, action) => { state.currentPage = action.payload;