переделал страницу с пациентами для внедрения redux и вынесения логикки компонента в отдельный хук
This commit is contained in:
parent
3dec01804a
commit
f840a0ab34
@ -55,7 +55,7 @@ const PatientFormModal = ({visible, onCancel, onSubmit, patient}) => {
|
|||||||
cancelText={"Отмена"}
|
cancelText={"Отмена"}
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
forceRender={true}
|
forceRender={true}
|
||||||
style={{top: 20}}
|
style={{marginTop: 20, marginBottom: 50}}
|
||||||
centered
|
centered
|
||||||
>
|
>
|
||||||
<Form form={form} layout={"vertical"}>
|
<Form form={form} layout={"vertical"}>
|
||||||
|
|||||||
120
web-app/src/hooks/usePatients.js
Normal file
120
web-app/src/hooks/usePatients.js
Normal file
@ -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;
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import {useEffect} from 'react';
|
import {useEffect, useMemo} from 'react';
|
||||||
import {
|
import {
|
||||||
Input,
|
Input,
|
||||||
Select,
|
Select,
|
||||||
@ -6,147 +6,77 @@ import {
|
|||||||
FloatButton,
|
FloatButton,
|
||||||
Row,
|
Row,
|
||||||
Col,
|
Col,
|
||||||
notification,
|
|
||||||
Tooltip,
|
|
||||||
Table,
|
Table,
|
||||||
Button,
|
Button,
|
||||||
Popconfirm,
|
Popconfirm,
|
||||||
Typography, Result
|
Typography,
|
||||||
|
Result, Tooltip
|
||||||
} from "antd";
|
} from "antd";
|
||||||
import {
|
import {
|
||||||
BuildOutlined,
|
BuildOutlined,
|
||||||
PlusOutlined,
|
PlusOutlined,
|
||||||
SortAscendingOutlined,
|
SortAscendingOutlined,
|
||||||
SortDescendingOutlined, TableOutlined,
|
SortDescendingOutlined,
|
||||||
|
TableOutlined,
|
||||||
TeamOutlined
|
TeamOutlined
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import {useDispatch, useSelector} from "react-redux";
|
import {useDispatch, useSelector} from "react-redux";
|
||||||
import {
|
import {
|
||||||
useAddPatientMutation,
|
|
||||||
useDeletePatientMutation,
|
|
||||||
useGetPatientsQuery,
|
|
||||||
useUpdatePatientMutation
|
|
||||||
} from "../redux/services/patientsApi.js";
|
|
||||||
import {
|
|
||||||
openModal,
|
|
||||||
selectPatient,
|
|
||||||
setSearchText,
|
setSearchText,
|
||||||
setSortOrder,
|
setSortOrder,
|
||||||
setViewMode,
|
setViewMode,
|
||||||
closeModal, setCurrentPage, setPageSize
|
closeModal,
|
||||||
|
setCurrentPage,
|
||||||
|
setPageSize
|
||||||
} from "../redux/slices/patientsSlice.js";
|
} from "../redux/slices/patientsSlice.js";
|
||||||
import PatientListCard from "../components/patients/PatientListCard.jsx";
|
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 {getCachedInfo} from "../utils/cachedInfoUtils.js";
|
import usePatients from "../hooks/usePatients.js";
|
||||||
|
|
||||||
const {Option} = Select;
|
const {Option} = Select;
|
||||||
const {Title} = Typography
|
const {Title} = Typography;
|
||||||
|
|
||||||
const PatientsPage = () => {
|
const PatientsPage = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const {
|
||||||
|
patients,
|
||||||
|
isLoading,
|
||||||
|
isError,
|
||||||
|
handleAddPatient,
|
||||||
|
handleEditPatient,
|
||||||
|
handleDeletePatient,
|
||||||
|
handleModalPatientSubmit
|
||||||
|
} = usePatients();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
searchText,
|
searchText,
|
||||||
sortOrder,
|
sortOrder,
|
||||||
viewMode,
|
viewMode,
|
||||||
selectedPatient,
|
selectedPatient,
|
||||||
isModalVisible
|
isModalVisible,
|
||||||
|
currentPage,
|
||||||
|
pageSize
|
||||||
} = useSelector(state => state.patientsUI);
|
} = useSelector(state => state.patientsUI);
|
||||||
|
|
||||||
const {data: patients = [], isLoading, isError} = useGetPatientsQuery();
|
|
||||||
const [addPatient] = useAddPatientMutation();
|
|
||||||
const [updatePatient] = useUpdatePatientMutation();
|
|
||||||
const [deletePatient] = useDeletePatientMutation();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = "Пациенты"
|
document.title = "Пациенты";
|
||||||
const cachedViewMode = getCachedInfo("viewModePatients");
|
|
||||||
if (cachedViewMode) dispatch(setViewMode(cachedViewMode));
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleAddPatient = () => {
|
const filteredPatients = useMemo(() => {
|
||||||
dispatch(selectPatient(null));
|
return patients
|
||||||
dispatch(openModal());
|
.filter(patient =>
|
||||||
};
|
Object.values(patient)
|
||||||
|
|
||||||
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",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const filteredPatients = patients
|
|
||||||
.filter(patient => {
|
|
||||||
const searchLower = searchText.toLowerCase();
|
|
||||||
return Object.values(patient)
|
|
||||||
.filter(value => typeof value === "string")
|
.filter(value => typeof value === "string")
|
||||||
.some(value => value.toLowerCase().includes(searchLower));
|
.some(value => value.toLowerCase().includes(searchText.toLowerCase()))
|
||||||
})
|
)
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
const fullNameA = `${a.last_name} ${a.first_name}`;
|
const fullNameA = `${a.last_name} ${a.first_name}`;
|
||||||
const fullNameB = `${b.last_name} ${b.first_name}`;
|
const fullNameB = `${b.last_name} ${b.first_name}`;
|
||||||
return sortOrder === "asc"
|
return sortOrder === "asc" ? fullNameA.localeCompare(fullNameB) : fullNameB.localeCompare(fullNameA);
|
||||||
? fullNameA.localeCompare(fullNameB)
|
|
||||||
: fullNameB.localeCompare(fullNameA);
|
|
||||||
});
|
});
|
||||||
|
}, [patients, searchText, sortOrder]);
|
||||||
const viewModes = [
|
|
||||||
{
|
|
||||||
value: "tile",
|
|
||||||
label: "Плитка",
|
|
||||||
icon: <BuildOutlined style={{marginRight: 8}}/>
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "table",
|
|
||||||
label: "Таблица",
|
|
||||||
icon: <TableOutlined style={{marginRight: 8}}/>
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
@ -192,7 +122,7 @@ const PatientsPage = () => {
|
|||||||
title: "Действия",
|
title: "Действия",
|
||||||
key: "actions",
|
key: "actions",
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
render: (text, record) => (
|
render: (_, record) => (
|
||||||
<Row gutter={[8, 8]}>
|
<Row gutter={[8, 8]}>
|
||||||
<Col xs={24} xl={12}>
|
<Col xs={24} xl={12}>
|
||||||
<Button block onClick={() => handleEditPatient(record)}>Изменить</Button>
|
<Button block onClick={() => handleEditPatient(record)}>Изменить</Button>
|
||||||
@ -213,70 +143,20 @@ const PatientsPage = () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const TableView = () => (
|
const viewModes = [
|
||||||
<Table
|
{
|
||||||
columns={columns}
|
value: "tile",
|
||||||
dataSource={filteredPatients.map(patient => ({...patient, key: patient.id}))}
|
label: "Плитка",
|
||||||
scroll={{
|
icon: <BuildOutlined style={{marginRight: 8}}/>
|
||||||
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));
|
|
||||||
},
|
},
|
||||||
}}
|
{
|
||||||
/>
|
value: "table",
|
||||||
);
|
label: "Таблица",
|
||||||
|
icon: <TableOutlined style={{marginRight: 8}}/>
|
||||||
const TileView = () => (
|
|
||||||
<List
|
|
||||||
grid={{
|
|
||||||
gutter: 16,
|
|
||||||
xs: 1,
|
|
||||||
sm: 1,
|
|
||||||
md: 2,
|
|
||||||
lg: 2,
|
|
||||||
xl: 3,
|
|
||||||
xxl: 3,
|
|
||||||
}}
|
|
||||||
dataSource={filteredPatients}
|
|
||||||
renderItem={(patient) => (
|
|
||||||
<List.Item>
|
|
||||||
<PatientListCard
|
|
||||||
patient={patient}
|
|
||||||
handleEditPatient={handleEditPatient}
|
|
||||||
handleDeletePatient={handleDeletePatient}
|
|
||||||
/>
|
|
||||||
</List.Item>
|
|
||||||
)}
|
|
||||||
pagination={{
|
|
||||||
current: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
showSizeChanger: true,
|
|
||||||
pageSizeOptions: ["5", "10", "20", "50"],
|
|
||||||
onChange: (page, newPageSize) => {
|
|
||||||
dispatch(setCurrentPage(page));
|
|
||||||
dispatch(setPageSize(newPageSize));
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isError) {
|
|
||||||
return (
|
|
||||||
<Result
|
|
||||||
status="error"
|
|
||||||
title="Ошибка"
|
|
||||||
subTitle="Произошла ошибка в работе страницы"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
if (isError) return <Result status="error" title="Ошибка" subTitle="Произошла ошибка в работе страницы"/>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{padding: 20}}>
|
<div style={{padding: 20}}>
|
||||||
@ -320,7 +200,7 @@ const PatientsPage = () => {
|
|||||||
}>
|
}>
|
||||||
<SelectViewMode
|
<SelectViewMode
|
||||||
viewMode={viewMode}
|
viewMode={viewMode}
|
||||||
setViewMode={(value) => dispatch(setViewMode(value))}
|
setViewMode={value => dispatch(setViewMode(value))}
|
||||||
localStorageKey={"viewModePatients"}
|
localStorageKey={"viewModePatients"}
|
||||||
toolTipText={"Формат отображения пациентов"}
|
toolTipText={"Формат отображения пациентов"}
|
||||||
viewModes={viewModes}
|
viewModes={viewModes}
|
||||||
@ -328,22 +208,40 @@ const PatientsPage = () => {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
{isLoading ? (
|
{isLoading ? <LoadingIndicator/> : viewMode === "tile" ? (
|
||||||
<LoadingIndicator/>
|
<List
|
||||||
) : viewMode === "tile" ? (
|
grid={{gutter: 16, column: 3}}
|
||||||
<TileView/>
|
dataSource={filteredPatients}
|
||||||
|
renderItem={patient => (
|
||||||
|
<List.Item>
|
||||||
|
<PatientListCard
|
||||||
|
patient={patient}
|
||||||
|
handleEditPatient={handleEditPatient}
|
||||||
|
handleDeletePatient={handleDeletePatient}
|
||||||
|
/>
|
||||||
|
</List.Item>
|
||||||
|
)}
|
||||||
|
pagination={{
|
||||||
|
current: currentPage, pageSize, onChange: (p, s) => {
|
||||||
|
dispatch(setCurrentPage(p));
|
||||||
|
dispatch(setPageSize(s));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<TableView/>
|
<Table
|
||||||
|
columns={columns}
|
||||||
|
dataSource={filteredPatients.map(p => ({...p, key: p.id}))}
|
||||||
|
pagination={{
|
||||||
|
current: currentPage, pageSize, onChange: (p, s) => {
|
||||||
|
dispatch(setCurrentPage(p));
|
||||||
|
dispatch(setPageSize(s));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<FloatButton
|
<FloatButton icon={<PlusOutlined/>} type="primary" onClick={handleAddPatient} tooltip="Добавить пациента"/>
|
||||||
icon={<PlusOutlined/>}
|
|
||||||
type="primary"
|
|
||||||
style={{position: "fixed", bottom: 40, right: 40}}
|
|
||||||
onClick={handleAddPatient}
|
|
||||||
tooltip="Добавить пациента"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<PatientFormModal
|
<PatientFormModal
|
||||||
visible={isModalVisible}
|
visible={isModalVisible}
|
||||||
onCancel={() => dispatch(closeModal())}
|
onCancel={() => dispatch(closeModal())}
|
||||||
|
|||||||
@ -16,7 +16,7 @@ export const patientsApi = createApi({
|
|||||||
getPatients: builder.query({
|
getPatients: builder.query({
|
||||||
query: () => '/patients/',
|
query: () => '/patients/',
|
||||||
providesTags: ['Patient'],
|
providesTags: ['Patient'],
|
||||||
refetchOnMountOrArgChange: 60
|
refetchOnMountOrArgChange: 5
|
||||||
}),
|
}),
|
||||||
addPatient: builder.mutation({
|
addPatient: builder.mutation({
|
||||||
query: (patient) => ({
|
query: (patient) => ({
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import {createSlice} from '@reduxjs/toolkit'
|
import {createSlice} from '@reduxjs/toolkit'
|
||||||
|
import {cacheInfo} from "../../utils/cachedInfoUtils.js";
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
searchText: '',
|
searchText: '',
|
||||||
@ -22,7 +23,7 @@ const patientsSlice = createSlice({
|
|||||||
},
|
},
|
||||||
setViewMode: (state, action) => {
|
setViewMode: (state, action) => {
|
||||||
state.viewMode = action.payload;
|
state.viewMode = action.payload;
|
||||||
localStorage.setItem('viewModePatients', action.payload);
|
cacheInfo("viewModePatients", action.payload);
|
||||||
},
|
},
|
||||||
setCurrentPage: (state, action) => {
|
setCurrentPage: (state, action) => {
|
||||||
state.currentPage = action.payload;
|
state.currentPage = action.payload;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user