diff --git a/api/app/controllers/patients_router.py b/api/app/controllers/patients_router.py index 4c1a48c..e547458 100644 --- a/api/app/controllers/patients_router.py +++ b/api/app/controllers/patients_router.py @@ -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) diff --git a/api/app/domain/entities/patient.py b/api/app/domain/entities/patient.py index d8babde..e6843f0 100644 --- a/api/app/domain/entities/patient.py +++ b/api/app/domain/entities/patient.py @@ -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 diff --git a/api/app/infrastructure/patients_service.py b/api/app/infrastructure/patients_service.py index 78bd84b..6b2d137 100644 --- a/api/app/infrastructure/patients_service.py +++ b/api/app/infrastructure/patients_service.py @@ -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 diff --git a/web-app/package-lock.json b/web-app/package-lock.json index ea09e61..fbb83e1 100644 --- a/web-app/package-lock.json +++ b/web-app/package-lock.json @@ -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", diff --git a/web-app/package.json b/web-app/package.json index 3182af2..99a31b7 100644 --- a/web-app/package.json +++ b/web-app/package.json @@ -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", diff --git a/web-app/src/api/patients/UpdatePatient.jsx b/web-app/src/api/patients/UpdatePatient.jsx new file mode 100644 index 0000000..14f4545 --- /dev/null +++ b/web-app/src/api/patients/UpdatePatient.jsx @@ -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; \ No newline at end of file diff --git a/web-app/src/components/PatientModal.jsx b/web-app/src/components/PatientModal.jsx index 84b16e0..01cdcc4 100644 --- a/web-app/src/components/PatientModal.jsx +++ b/web-app/src/components/PatientModal.jsx @@ -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}} >