сделал редактирование пациента
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 app.database.session import get_db
|
||||
from app.domain.entities.patient import PatientEntity
|
||||
from app.infrastructure.dependencies import get_current_user
|
||||
from app.infrastructure.patients_service import PatientsService
|
||||
|
||||
@ -15,3 +16,24 @@ async def get_all_patients(
|
||||
):
|
||||
patients_service = PatientsService(db)
|
||||
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):
|
||||
id: Optional[int]
|
||||
id: Optional[int] = None
|
||||
first_name: str
|
||||
last_name: str
|
||||
patronymic: Optional[str]
|
||||
patronymic: Optional[str] = None
|
||||
birthday: datetime.date
|
||||
address: Optional[str]
|
||||
email: Optional[str]
|
||||
phone: Optional[str]
|
||||
diagnosis: Optional[str]
|
||||
correction: Optional[str]
|
||||
address: Optional[str] = None
|
||||
email: Optional[str] = None
|
||||
phone: Optional[str] = None
|
||||
diagnosis: Optional[str] = None
|
||||
correction: Optional[str] = None
|
||||
|
||||
@ -55,8 +55,8 @@ class PatientsService:
|
||||
correction=patient_model.correction,
|
||||
)
|
||||
|
||||
async def update_patient(self, user_id: int, patient: PatientEntity) -> Optional[PatientEntity]:
|
||||
patient_model = await self.patient_repository.get_by_id(user_id)
|
||||
async def update_patient(self, patient_id: int, patient: PatientEntity) -> Optional[PatientEntity]:
|
||||
patient_model = await self.patient_repository.get_by_id(patient_id)
|
||||
if patient_model:
|
||||
patient_model.first_name = patient.first_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/palette-antd": "^5.3.0",
|
||||
"antd": "^5.23.1",
|
||||
"antd-mask-input": "^2.0.7",
|
||||
"axios": "^1.7.9",
|
||||
"moment": "^2.30.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "^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": {
|
||||
"@eslint/js": "^9.17.0",
|
||||
@ -1955,6 +1957,19 @@
|
||||
"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": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
@ -3361,6 +3376,15 @@
|
||||
"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": {
|
||||
"version": "5.0.3",
|
||||
"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",
|
||||
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
@ -5766,6 +5792,15 @@
|
||||
"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": {
|
||||
"version": "6.0.7",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.0.7.tgz",
|
||||
|
||||
@ -14,12 +14,14 @@
|
||||
"@react-buddy/ide-toolbox": "^2.4.0",
|
||||
"@react-buddy/palette-antd": "^5.3.0",
|
||||
"antd": "^5.23.1",
|
||||
"antd-mask-input": "^2.0.7",
|
||||
"axios": "^1.7.9",
|
||||
"moment": "^2.30.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "^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": {
|
||||
"@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 {Modal, Form, Input, DatePicker} from "antd";
|
||||
import moment from "moment";
|
||||
import PropTypes from "prop-types";
|
||||
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;
|
||||
|
||||
@ -11,16 +13,15 @@ const PatientModal = ({visible, onCancel, onSubmit, patient}) => {
|
||||
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
form.resetFields();
|
||||
if (patient) {
|
||||
form.setFieldsValue({
|
||||
...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 () => {
|
||||
try {
|
||||
@ -49,7 +50,6 @@ const PatientModal = ({visible, onCancel, onSubmit, patient}) => {
|
||||
centered
|
||||
maskClosable={false}
|
||||
forceRender={true}
|
||||
styles={{body: {padding: 24}}}
|
||||
style={{top: 20}}
|
||||
>
|
||||
<Form form={form} layout="vertical">
|
||||
@ -89,15 +89,27 @@ const PatientModal = ({visible, onCancel, onSubmit, patient}) => {
|
||||
<Form.Item
|
||||
name="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"/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="phone"
|
||||
label="Телефон"
|
||||
rules={[
|
||||
{required: true, message: "Введите телефон"},
|
||||
]}
|
||||
>
|
||||
<Input placeholder="Введите телефон"/>
|
||||
<MaskedInput placeholder="Введите номер телефона" mask="+7 (000) 000-00-00"/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="diagnosis"
|
||||
|
||||
@ -1,27 +1,35 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Input, Select, List, FloatButton, Row, Col, message } from "antd";
|
||||
import { PlusOutlined } from "@ant-design/icons";
|
||||
import { useAuth } from "../AuthContext.jsx";
|
||||
import {useEffect, useState} from "react";
|
||||
import {Input, Select, List, FloatButton, Row, Col, message} from "antd";
|
||||
import {PlusOutlined} from "@ant-design/icons";
|
||||
import {useAuth} from "../AuthContext.jsx";
|
||||
import getAllPatients from "../api/patients/GetAllPatients.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 { user } = useAuth();
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const [sortOrder, setSortOrder] = useState("asc");
|
||||
const [patients, setPatients] = useState([]);
|
||||
const [error, setError] = useState(null);
|
||||
const {user} = useAuth();
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const [sortOrder, setSortOrder] = useState("asc");
|
||||
const [patients, setPatients] = useState([]);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
const [current, setCurrent] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
const [current, setCurrent] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
const [selectedPatient, setSelectedPatient] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isModalVisible) {
|
||||
const intervalId = setInterval(fetchPatients, 5000);
|
||||
return () => clearInterval(intervalId);
|
||||
}
|
||||
}, [user, isModalVisible]);
|
||||
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
const [selectedPatient, setSelectedPatient] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchPatients = async () => {
|
||||
if (!user || !user.token) return;
|
||||
|
||||
@ -33,111 +41,115 @@ const PatientsPage = () => {
|
||||
}
|
||||
};
|
||||
|
||||
fetchPatients();
|
||||
}, [user]);
|
||||
const filteredPatients = patients
|
||||
.filter((patient) =>
|
||||
`${patient.first_name} ${patient.last_name}`.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);
|
||||
});
|
||||
|
||||
const filteredPatients = patients
|
||||
.filter((patient) =>
|
||||
`${patient.first_name} ${patient.last_name}`.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);
|
||||
});
|
||||
const handleAddPatient = () => {
|
||||
setSelectedPatient(null);
|
||||
setIsModalVisible(true);
|
||||
};
|
||||
|
||||
const handleAddPatient = () => {
|
||||
setSelectedPatient(null);
|
||||
setIsModalVisible(true);
|
||||
};
|
||||
const handleEditPatient = (patient) => {
|
||||
setSelectedPatient(patient);
|
||||
setIsModalVisible(true);
|
||||
};
|
||||
|
||||
const handleEditPatient = (patient) => {
|
||||
setSelectedPatient(patient);
|
||||
setIsModalVisible(true);
|
||||
};
|
||||
const handleCancel = () => {
|
||||
setIsModalVisible(false);
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setIsModalVisible(false);
|
||||
};
|
||||
const handleSubmit = async (newPatient) => {
|
||||
if (selectedPatient) {
|
||||
|
||||
const handleSubmit = (newPatient) => {
|
||||
if (selectedPatient) {
|
||||
setPatients((prevPatients) =>
|
||||
prevPatients.map((p) =>
|
||||
p.id === selectedPatient.id ? { ...p, ...newPatient } : p
|
||||
)
|
||||
);
|
||||
message.success("Пациент успешно обновлен!");
|
||||
} else {
|
||||
setPatients((prevPatients) => [...prevPatients, { id: Date.now(), ...newPatient }]);
|
||||
message.success("Пациент успешно добавлен!");
|
||||
}
|
||||
try {
|
||||
await updatePatient(user.token, selectedPatient.id, newPatient);
|
||||
} catch (error) {
|
||||
if (error.response?.status === 401) {
|
||||
throw new Error("Ошибка авторизации: пользователь неяден или токен недействителен");
|
||||
}
|
||||
throw new Error(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
setIsModalVisible(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ padding: 20 }}>
|
||||
<Row gutter={[16, 16]} style={{ marginBottom: 20 }}>
|
||||
<Col xs={24} sm={16}>
|
||||
<Input
|
||||
placeholder="Поиск пациента"
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
style={{ width: "100%" }}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={24} sm={8}>
|
||||
<Select
|
||||
value={sortOrder}
|
||||
onChange={(value) => setSortOrder(value)}
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
<Option value="asc">А-Я</Option>
|
||||
<Option value="desc">Я-А</Option>
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
if (!selectedPatient) {
|
||||
setPatients((prevPatients) => [...prevPatients, {id: Date.now(), ...newPatient}]);
|
||||
message.success("Пациент успешно добавлен!");
|
||||
}
|
||||
|
||||
<List
|
||||
grid={{ gutter: 16, column: 1 }}
|
||||
dataSource={filteredPatients}
|
||||
renderItem={(patient) => (
|
||||
<List.Item
|
||||
onClick={() => {
|
||||
handleEditPatient(patient);
|
||||
setIsModalVisible(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{padding: 20}}>
|
||||
<Row gutter={[16, 16]} style={{marginBottom: 20}}>
|
||||
<Col xs={24} sm={16}>
|
||||
<Input
|
||||
placeholder="Поиск пациента"
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
style={{width: "100%"}}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={24} sm={8}>
|
||||
<Select
|
||||
value={sortOrder}
|
||||
onChange={(value) => setSortOrder(value)}
|
||||
style={{width: "100%"}}
|
||||
>
|
||||
<Option value="asc">А-Я</Option>
|
||||
<Option value="desc">Я-А</Option>
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<List
|
||||
grid={{gutter: 16, column: 1}}
|
||||
dataSource={filteredPatients}
|
||||
renderItem={(patient) => (
|
||||
<List.Item
|
||||
onClick={() => {
|
||||
handleEditPatient(patient);
|
||||
}}
|
||||
>
|
||||
<PatientListCard patient={patient}/>
|
||||
</List.Item>
|
||||
)}
|
||||
pagination={{
|
||||
current,
|
||||
pageSize,
|
||||
showSizeChanger: true,
|
||||
pageSizeOptions: ["5", "10", "20", "50"],
|
||||
onChange: (page, newPageSize) => {
|
||||
setCurrent(page);
|
||||
setPageSize(newPageSize);
|
||||
},
|
||||
}}
|
||||
>
|
||||
<PatientListCard patient={patient}/>
|
||||
</List.Item>
|
||||
)}
|
||||
pagination={{
|
||||
current,
|
||||
pageSize,
|
||||
showSizeChanger: true,
|
||||
pageSizeOptions: ["5", "10", "20", "50"],
|
||||
onChange: (page, newPageSize) => {
|
||||
setCurrent(page);
|
||||
setPageSize(newPageSize);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
/>
|
||||
|
||||
<FloatButton
|
||||
icon={<PlusOutlined />}
|
||||
style={{ position: "fixed", bottom: 20, right: 20 }}
|
||||
onClick={handleAddPatient}
|
||||
/>
|
||||
<FloatButton
|
||||
icon={<PlusOutlined/>}
|
||||
style={{position: "fixed", bottom: 20, right: 20}}
|
||||
onClick={handleAddPatient}
|
||||
/>
|
||||
|
||||
<PatientModal
|
||||
visible={isModalVisible}
|
||||
onCancel={handleCancel}
|
||||
onSubmit={handleSubmit}
|
||||
patient={selectedPatient}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
<PatientModal
|
||||
visible={isModalVisible}
|
||||
onCancel={handleCancel}
|
||||
onSubmit={handleSubmit}
|
||||
patient={selectedPatient}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
;
|
||||
|
||||
export default PatientsPage;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user