сделал редактирование пациента
This commit is contained in:
parent
0ca1978c81
commit
44c092ecb9
@ -2,6 +2,7 @@ from fastapi import APIRouter, Depends
|
|||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from app.database.session import get_db
|
from app.database.session import get_db
|
||||||
|
from app.domain.entities.patient import PatientEntity
|
||||||
from app.infrastructure.dependencies import get_current_user
|
from app.infrastructure.dependencies import get_current_user
|
||||||
from app.infrastructure.patients_service import PatientsService
|
from app.infrastructure.patients_service import PatientsService
|
||||||
|
|
||||||
@ -15,3 +16,24 @@ async def get_all_patients(
|
|||||||
):
|
):
|
||||||
patients_service = PatientsService(db)
|
patients_service = PatientsService(db)
|
||||||
return await patients_service.get_all_patients()
|
return await patients_service.get_all_patients()
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/patients/")
|
||||||
|
async def create_patient(
|
||||||
|
patient: PatientEntity,
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
user=Depends(get_current_user)
|
||||||
|
):
|
||||||
|
patients_service = PatientsService(db)
|
||||||
|
return await patients_service.create_patient(patient)
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/patients/{patient_id}/")
|
||||||
|
async def update_patient(
|
||||||
|
patient_id: int,
|
||||||
|
patient: PatientEntity,
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
user=Depends(get_current_user)
|
||||||
|
):
|
||||||
|
patients_service = PatientsService(db)
|
||||||
|
return await patients_service.update_patient(patient_id, patient)
|
||||||
|
|||||||
@ -5,13 +5,13 @@ from pydantic import BaseModel
|
|||||||
|
|
||||||
|
|
||||||
class PatientEntity(BaseModel):
|
class PatientEntity(BaseModel):
|
||||||
id: Optional[int]
|
id: Optional[int] = None
|
||||||
first_name: str
|
first_name: str
|
||||||
last_name: str
|
last_name: str
|
||||||
patronymic: Optional[str]
|
patronymic: Optional[str] = None
|
||||||
birthday: datetime.date
|
birthday: datetime.date
|
||||||
address: Optional[str]
|
address: Optional[str] = None
|
||||||
email: Optional[str]
|
email: Optional[str] = None
|
||||||
phone: Optional[str]
|
phone: Optional[str] = None
|
||||||
diagnosis: Optional[str]
|
diagnosis: Optional[str] = None
|
||||||
correction: Optional[str]
|
correction: Optional[str] = None
|
||||||
|
|||||||
@ -55,8 +55,8 @@ class PatientsService:
|
|||||||
correction=patient_model.correction,
|
correction=patient_model.correction,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def update_patient(self, user_id: int, patient: PatientEntity) -> Optional[PatientEntity]:
|
async def update_patient(self, patient_id: int, patient: PatientEntity) -> Optional[PatientEntity]:
|
||||||
patient_model = await self.patient_repository.get_by_id(user_id)
|
patient_model = await self.patient_repository.get_by_id(patient_id)
|
||||||
if patient_model:
|
if patient_model:
|
||||||
patient_model.first_name = patient.first_name
|
patient_model.first_name = patient.first_name
|
||||||
patient_model.last_name = patient.last_name
|
patient_model.last_name = patient.last_name
|
||||||
|
|||||||
39
web-app/package-lock.json
generated
39
web-app/package-lock.json
generated
@ -12,12 +12,14 @@
|
|||||||
"@react-buddy/ide-toolbox": "^2.4.0",
|
"@react-buddy/ide-toolbox": "^2.4.0",
|
||||||
"@react-buddy/palette-antd": "^5.3.0",
|
"@react-buddy/palette-antd": "^5.3.0",
|
||||||
"antd": "^5.23.1",
|
"antd": "^5.23.1",
|
||||||
|
"antd-mask-input": "^2.0.7",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
"moment": "^2.30.1",
|
"dayjs": "^1.11.13",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-router-dom": "^7.1.1"
|
"react-router-dom": "^7.1.1",
|
||||||
|
"validator": "^13.12.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.17.0",
|
"@eslint/js": "^9.17.0",
|
||||||
@ -1955,6 +1957,19 @@
|
|||||||
"react-dom": ">=16.9.0"
|
"react-dom": ">=16.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/antd-mask-input": {
|
||||||
|
"version": "2.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/antd-mask-input/-/antd-mask-input-2.0.7.tgz",
|
||||||
|
"integrity": "sha512-EFv/Hjar5nzYuzvgCcIeEcbVzw5+4RynUQTl2S8bLMaR9iajiWoeV/VYvENaP6X+ytnM0OqemE2pHY9ZRR6hEw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"imask": "6.4.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"antd": ">=4.19.0",
|
||||||
|
"react": ">=16.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/argparse": {
|
"node_modules/argparse": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||||
@ -3361,6 +3376,15 @@
|
|||||||
"node": ">= 4"
|
"node": ">= 4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/imask": {
|
||||||
|
"version": "6.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/imask/-/imask-6.4.2.tgz",
|
||||||
|
"integrity": "sha512-xvEgbTdk6y2dW2UAysq0NRPmO6PuaXM5NHIt4TXEJEwXUHj26M0p/fXqyrSJdNXFaGVOtqYjPRnNdrjQQhDuuA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"npm": ">=4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/immutable": {
|
"node_modules/immutable": {
|
||||||
"version": "5.0.3",
|
"version": "5.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.0.3.tgz",
|
||||||
@ -4012,6 +4036,8 @@
|
|||||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
|
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
|
||||||
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
|
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "*"
|
"node": "*"
|
||||||
}
|
}
|
||||||
@ -5766,6 +5792,15 @@
|
|||||||
"punycode": "^2.1.0"
|
"punycode": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/validator": {
|
||||||
|
"version": "13.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz",
|
||||||
|
"integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "6.0.7",
|
"version": "6.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-6.0.7.tgz",
|
||||||
|
|||||||
@ -14,12 +14,14 @@
|
|||||||
"@react-buddy/ide-toolbox": "^2.4.0",
|
"@react-buddy/ide-toolbox": "^2.4.0",
|
||||||
"@react-buddy/palette-antd": "^5.3.0",
|
"@react-buddy/palette-antd": "^5.3.0",
|
||||||
"antd": "^5.23.1",
|
"antd": "^5.23.1",
|
||||||
|
"antd-mask-input": "^2.0.7",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
"moment": "^2.30.1",
|
"dayjs": "^1.11.13",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-router-dom": "^7.1.1"
|
"react-router-dom": "^7.1.1",
|
||||||
|
"validator": "^13.12.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.17.0",
|
"@eslint/js": "^9.17.0",
|
||||||
|
|||||||
25
web-app/src/api/patients/UpdatePatient.jsx
Normal file
25
web-app/src/api/patients/UpdatePatient.jsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
import CONFIG from "../../core/Config.jsx";
|
||||||
|
|
||||||
|
|
||||||
|
const updatePatient = async (token, patientId, patientData) => {
|
||||||
|
if (!token) {
|
||||||
|
throw new Error("Ошибка авторизации: пользователь не аутентифицирован");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.put(`${CONFIG.BASE_URL}/patients/${patientId}/`, patientData, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response?.status === 401) {
|
||||||
|
throw new Error("Ошибка авторизации: пользователь не найден или токен недействителен");
|
||||||
|
}
|
||||||
|
throw new Error(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default updatePatient;
|
||||||
@ -1,8 +1,10 @@
|
|||||||
import {useEffect} from "react";
|
import {useEffect} from "react";
|
||||||
import {Modal, Form, Input, DatePicker} from "antd";
|
import {Modal, Form, Input, DatePicker} from "antd";
|
||||||
import moment from "moment";
|
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import locale from "antd/es/date-picker/locale/ru_RU";
|
import locale from "antd/es/date-picker/locale/ru_RU";
|
||||||
|
import validator from "validator";
|
||||||
|
import {MaskedInput} from "antd-mask-input";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
const {TextArea} = Input;
|
const {TextArea} = Input;
|
||||||
|
|
||||||
@ -11,16 +13,15 @@ const PatientModal = ({visible, onCancel, onSubmit, patient}) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
|
form.resetFields();
|
||||||
if (patient) {
|
if (patient) {
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
...patient,
|
...patient,
|
||||||
birthday: patient.birthday ? moment(patient.birthday) : null,
|
birthday: patient.birthday ? dayjs(patient.birthday, "YYYY-MM-DD") : null,
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
form.resetFields();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [visible, patient, form]);
|
}, [visible, patient]);
|
||||||
|
|
||||||
const handleOk = async () => {
|
const handleOk = async () => {
|
||||||
try {
|
try {
|
||||||
@ -49,7 +50,6 @@ const PatientModal = ({visible, onCancel, onSubmit, patient}) => {
|
|||||||
centered
|
centered
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
forceRender={true}
|
forceRender={true}
|
||||||
styles={{body: {padding: 24}}}
|
|
||||||
style={{top: 20}}
|
style={{top: 20}}
|
||||||
>
|
>
|
||||||
<Form form={form} layout="vertical">
|
<Form form={form} layout="vertical">
|
||||||
@ -89,15 +89,27 @@ const PatientModal = ({visible, onCancel, onSubmit, patient}) => {
|
|||||||
<Form.Item
|
<Form.Item
|
||||||
name="email"
|
name="email"
|
||||||
label="Email"
|
label="Email"
|
||||||
rules={[{type: "email", message: "Введите корректный email"}]}
|
rules={[
|
||||||
|
{
|
||||||
|
validator: (_, value) => {
|
||||||
|
if (value && !validator.isEmail(value)) {
|
||||||
|
return Promise.reject("Некорректный email");
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<Input placeholder="Введите email"/>
|
<Input placeholder="Введите email"/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="phone"
|
name="phone"
|
||||||
label="Телефон"
|
label="Телефон"
|
||||||
|
rules={[
|
||||||
|
{required: true, message: "Введите телефон"},
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<Input placeholder="Введите телефон"/>
|
<MaskedInput placeholder="Введите номер телефона" mask="+7 (000) 000-00-00"/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="diagnosis"
|
name="diagnosis"
|
||||||
|
|||||||
@ -1,15 +1,16 @@
|
|||||||
import { useEffect, useState } from "react";
|
import {useEffect, useState} from "react";
|
||||||
import { Input, Select, List, FloatButton, Row, Col, message } from "antd";
|
import {Input, Select, List, FloatButton, Row, Col, message} from "antd";
|
||||||
import { PlusOutlined } from "@ant-design/icons";
|
import {PlusOutlined} from "@ant-design/icons";
|
||||||
import { useAuth } from "../AuthContext.jsx";
|
import {useAuth} from "../AuthContext.jsx";
|
||||||
import getAllPatients from "../api/patients/GetAllPatients.jsx";
|
import getAllPatients from "../api/patients/GetAllPatients.jsx";
|
||||||
import PatientListCard from "../components/PatientListCard.jsx";
|
import PatientListCard from "../components/PatientListCard.jsx";
|
||||||
import PatientModal from "../components/PatientModal.jsx"; // Подключаем модальное окно
|
import PatientModal from "../components/PatientModal.jsx";
|
||||||
|
import updatePatient from "../api/patients/UpdatePatient.jsx"; // Подключаем модальное окно
|
||||||
|
|
||||||
const { Option } = Select;
|
const {Option} = Select;
|
||||||
|
|
||||||
const PatientsPage = () => {
|
const PatientsPage = () => {
|
||||||
const { user } = useAuth();
|
const {user} = useAuth();
|
||||||
const [searchText, setSearchText] = useState("");
|
const [searchText, setSearchText] = useState("");
|
||||||
const [sortOrder, setSortOrder] = useState("asc");
|
const [sortOrder, setSortOrder] = useState("asc");
|
||||||
const [patients, setPatients] = useState([]);
|
const [patients, setPatients] = useState([]);
|
||||||
@ -22,6 +23,13 @@ const PatientsPage = () => {
|
|||||||
const [selectedPatient, setSelectedPatient] = useState(null);
|
const [selectedPatient, setSelectedPatient] = useState(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!isModalVisible) {
|
||||||
|
const intervalId = setInterval(fetchPatients, 5000);
|
||||||
|
return () => clearInterval(intervalId);
|
||||||
|
}
|
||||||
|
}, [user, isModalVisible]);
|
||||||
|
|
||||||
|
|
||||||
const fetchPatients = async () => {
|
const fetchPatients = async () => {
|
||||||
if (!user || !user.token) return;
|
if (!user || !user.token) return;
|
||||||
|
|
||||||
@ -33,9 +41,6 @@ const PatientsPage = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchPatients();
|
|
||||||
}, [user]);
|
|
||||||
|
|
||||||
const filteredPatients = patients
|
const filteredPatients = patients
|
||||||
.filter((patient) =>
|
.filter((patient) =>
|
||||||
`${patient.first_name} ${patient.last_name}`.toLowerCase().includes(searchText.toLowerCase())
|
`${patient.first_name} ${patient.last_name}`.toLowerCase().includes(searchText.toLowerCase())
|
||||||
@ -62,16 +67,22 @@ const PatientsPage = () => {
|
|||||||
setIsModalVisible(false);
|
setIsModalVisible(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = (newPatient) => {
|
const handleSubmit = async (newPatient) => {
|
||||||
if (selectedPatient) {
|
if (selectedPatient) {
|
||||||
setPatients((prevPatients) =>
|
|
||||||
prevPatients.map((p) =>
|
try {
|
||||||
p.id === selectedPatient.id ? { ...p, ...newPatient } : p
|
await updatePatient(user.token, selectedPatient.id, newPatient);
|
||||||
)
|
} catch (error) {
|
||||||
);
|
if (error.response?.status === 401) {
|
||||||
message.success("Пациент успешно обновлен!");
|
throw new Error("Ошибка авторизации: пользователь неяден или токен недействителен");
|
||||||
} else {
|
}
|
||||||
setPatients((prevPatients) => [...prevPatients, { id: Date.now(), ...newPatient }]);
|
throw new Error(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!selectedPatient) {
|
||||||
|
setPatients((prevPatients) => [...prevPatients, {id: Date.now(), ...newPatient}]);
|
||||||
message.success("Пациент успешно добавлен!");
|
message.success("Пациент успешно добавлен!");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,20 +90,20 @@ const PatientsPage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: 20 }}>
|
<div style={{padding: 20}}>
|
||||||
<Row gutter={[16, 16]} style={{ marginBottom: 20 }}>
|
<Row gutter={[16, 16]} style={{marginBottom: 20}}>
|
||||||
<Col xs={24} sm={16}>
|
<Col xs={24} sm={16}>
|
||||||
<Input
|
<Input
|
||||||
placeholder="Поиск пациента"
|
placeholder="Поиск пациента"
|
||||||
onChange={(e) => setSearchText(e.target.value)}
|
onChange={(e) => setSearchText(e.target.value)}
|
||||||
style={{ width: "100%" }}
|
style={{width: "100%"}}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={24} sm={8}>
|
<Col xs={24} sm={8}>
|
||||||
<Select
|
<Select
|
||||||
value={sortOrder}
|
value={sortOrder}
|
||||||
onChange={(value) => setSortOrder(value)}
|
onChange={(value) => setSortOrder(value)}
|
||||||
style={{ width: "100%" }}
|
style={{width: "100%"}}
|
||||||
>
|
>
|
||||||
<Option value="asc">А-Я</Option>
|
<Option value="asc">А-Я</Option>
|
||||||
<Option value="desc">Я-А</Option>
|
<Option value="desc">Я-А</Option>
|
||||||
@ -101,7 +112,7 @@ const PatientsPage = () => {
|
|||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<List
|
<List
|
||||||
grid={{ gutter: 16, column: 1 }}
|
grid={{gutter: 16, column: 1}}
|
||||||
dataSource={filteredPatients}
|
dataSource={filteredPatients}
|
||||||
renderItem={(patient) => (
|
renderItem={(patient) => (
|
||||||
<List.Item
|
<List.Item
|
||||||
@ -125,8 +136,8 @@ const PatientsPage = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<FloatButton
|
<FloatButton
|
||||||
icon={<PlusOutlined />}
|
icon={<PlusOutlined/>}
|
||||||
style={{ position: "fixed", bottom: 20, right: 20 }}
|
style={{position: "fixed", bottom: 20, right: 20}}
|
||||||
onClick={handleAddPatient}
|
onClick={handleAddPatient}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -138,6 +149,7 @@ const PatientsPage = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
;
|
||||||
|
|
||||||
export default PatientsPage;
|
export default PatientsPage;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user