переделал страницу с пациентами для внедрения redux и вынесения логикки компонента в отдельный хук
This commit is contained in:
parent
3dec01804a
commit
f840a0ab34
@ -55,7 +55,7 @@ const PatientFormModal = ({visible, onCancel, onSubmit, patient}) => {
|
||||
cancelText={"Отмена"}
|
||||
maskClosable={false}
|
||||
forceRender={true}
|
||||
style={{top: 20}}
|
||||
style={{marginTop: 20, marginBottom: 50}}
|
||||
centered
|
||||
>
|
||||
<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 {
|
||||
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: <BuildOutlined style={{marginRight: 8}}/>
|
||||
},
|
||||
{
|
||||
value: "table",
|
||||
label: "Таблица",
|
||||
icon: <TableOutlined style={{marginRight: 8}}/>
|
||||
}
|
||||
];
|
||||
}, [patients, searchText, sortOrder]);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
@ -192,7 +122,7 @@ const PatientsPage = () => {
|
||||
title: "Действия",
|
||||
key: "actions",
|
||||
fixed: 'right',
|
||||
render: (text, record) => (
|
||||
render: (_, record) => (
|
||||
<Row gutter={[8, 8]}>
|
||||
<Col xs={24} xl={12}>
|
||||
<Button block onClick={() => handleEditPatient(record)}>Изменить</Button>
|
||||
@ -213,70 +143,20 @@ const PatientsPage = () => {
|
||||
},
|
||||
];
|
||||
|
||||
const TableView = () => (
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={filteredPatients.map(patient => ({...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: <BuildOutlined style={{marginRight: 8}}/>
|
||||
},
|
||||
{
|
||||
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 (
|
||||
<div style={{padding: 20}}>
|
||||
@ -320,7 +200,7 @@ const PatientsPage = () => {
|
||||
}>
|
||||
<SelectViewMode
|
||||
viewMode={viewMode}
|
||||
setViewMode={(value) => dispatch(setViewMode(value))}
|
||||
setViewMode={value => dispatch(setViewMode(value))}
|
||||
localStorageKey={"viewModePatients"}
|
||||
toolTipText={"Формат отображения пациентов"}
|
||||
viewModes={viewModes}
|
||||
@ -328,22 +208,40 @@ const PatientsPage = () => {
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{isLoading ? (
|
||||
<LoadingIndicator/>
|
||||
) : viewMode === "tile" ? (
|
||||
<TileView/>
|
||||
{isLoading ? <LoadingIndicator/> : viewMode === "tile" ? (
|
||||
<List
|
||||
grid={{gutter: 16, column: 3}}
|
||||
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
|
||||
icon={<PlusOutlined/>}
|
||||
type="primary"
|
||||
style={{position: "fixed", bottom: 40, right: 40}}
|
||||
onClick={handleAddPatient}
|
||||
tooltip="Добавить пациента"
|
||||
/>
|
||||
|
||||
<FloatButton icon={<PlusOutlined/>} type="primary" onClick={handleAddPatient} tooltip="Добавить пациента"/>
|
||||
<PatientFormModal
|
||||
visible={isModalVisible}
|
||||
onCancel={() => dispatch(closeModal())}
|
||||
|
||||
@ -16,7 +16,7 @@ export const patientsApi = createApi({
|
||||
getPatients: builder.query({
|
||||
query: () => '/patients/',
|
||||
providesTags: ['Patient'],
|
||||
refetchOnMountOrArgChange: 60
|
||||
refetchOnMountOrArgChange: 5
|
||||
}),
|
||||
addPatient: builder.mutation({
|
||||
query: (patient) => ({
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user