From 5098b05003fd8cf6c7f438efbc1fa245b41dc283 Mon Sep 17 00:00:00 2001 From: andrei Date: Fri, 6 Jun 2025 17:12:42 +0500 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20UI=20=D0=B8=20=D0=B8=D1=81=D0=BF?= =?UTF-8?q?=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BE=D1=88?= =?UTF-8?q?=D0=B8=D0=B1=D0=BE=D0=BA.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Внесены изменения в UI компонентов, исправлены ошибки. Добавлена возможность удаления файлов. Изменен CORS для API. Удален axiosConfig.js. --- api/.dockerignore | 3 +- .../controllers/appointment_files_router.py | 2 +- api/app/main.py | 2 +- web-app/.gitignore | 1 + .../AppointmentFormModal.jsx | 7 ++- .../useAppointmentFormModal.js | 1 + .../useAppointmentFormModalUI.js | 5 +- .../AppointmentViewModal.jsx | 37 +++++++++--- .../useAppointmentView.js | 32 +++++++++- web-app/src/Core/axiosConfig.js | 58 ------------------- web-app/src/Core/сonfig.js | 2 +- web-app/vite.config.js | 2 +- 12 files changed, 73 insertions(+), 79 deletions(-) delete mode 100644 web-app/src/Core/axiosConfig.js diff --git a/api/.dockerignore b/api/.dockerignore index 509f007..95d9b1f 100644 --- a/api/.dockerignore +++ b/api/.dockerignore @@ -1,4 +1,3 @@ .venv k8s -.idea -k8s \ No newline at end of file +.idea \ No newline at end of file diff --git a/api/app/controllers/appointment_files_router.py b/api/app/controllers/appointment_files_router.py index 8b47438..a6341d4 100644 --- a/api/app/controllers/appointment_files_router.py +++ b/api/app/controllers/appointment_files_router.py @@ -41,7 +41,7 @@ async def download_project_file( @router.post( - '/{appointment_id}/upload', + '/{appointment_id}/upload/', response_model=AppointmentFileEntity, summary='Upload a new file for the appointment', description='Uploads a new file and associates it with the specified appointment.' diff --git a/api/app/main.py b/api/app/main.py index 1c49422..d3ca1e5 100644 --- a/api/app/main.py +++ b/api/app/main.py @@ -23,7 +23,7 @@ def start_app(): api_app.add_middleware( CORSMiddleware, - allow_origins=['http://localhost:5173', 'http://localhost:5173'], + allow_origins=['https://api.visus.numerum.team', 'http://localhost:5173'], allow_credentials=True, allow_methods=['*'], allow_headers=['*'], diff --git a/web-app/.gitignore b/web-app/.gitignore index a547bf3..1cac559 100644 --- a/web-app/.gitignore +++ b/web-app/.gitignore @@ -22,3 +22,4 @@ dist-ssr *.njsproj *.sln *.sw? +.env \ No newline at end of file diff --git a/web-app/src/Components/Dummies/AppointmentFormModal/AppointmentFormModal.jsx b/web-app/src/Components/Dummies/AppointmentFormModal/AppointmentFormModal.jsx index f5c18a3..c375eee 100644 --- a/web-app/src/Components/Dummies/AppointmentFormModal/AppointmentFormModal.jsx +++ b/web-app/src/Components/Dummies/AppointmentFormModal/AppointmentFormModal.jsx @@ -18,7 +18,7 @@ import { Upload, List, } from "antd"; -import { UploadOutlined } from '@ant-design/icons'; +import {UploadOutlined} from '@ant-design/icons'; import useAppointmentFormModal from "./useAppointmentFormModal.js"; import useAppointmentFormModalUI from "./useAppointmentFormModalUI.js"; import LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.jsx"; @@ -208,7 +208,7 @@ const AppointmentFormModal = () => { accept=".pdf,.doc,.docx,.jpg,.jpeg,.png" multiple > - + @@ -331,7 +331,7 @@ const AppointmentFormModal = () => { @@ -339,6 +339,7 @@ const AppointmentFormModal = () => { type="primary" onClick={appointmentFormModalUI.handleClickNextButton} disabled={appointmentFormModalUI.disableNextButton} + loading={appointmentFormModalData.isCreating || appointmentFormModalUI.isUploadingFile} > {appointmentFormModalUI.nextButtonText} diff --git a/web-app/src/Components/Dummies/AppointmentFormModal/useAppointmentFormModal.js b/web-app/src/Components/Dummies/AppointmentFormModal/useAppointmentFormModal.js index b129a20..62a081d 100644 --- a/web-app/src/Components/Dummies/AppointmentFormModal/useAppointmentFormModal.js +++ b/web-app/src/Components/Dummies/AppointmentFormModal/useAppointmentFormModal.js @@ -29,6 +29,7 @@ const useAppointmentFormModal = () => { useGetByPatientIdQuery, isLoading: isLoadingPatients || isLoadingAppointmentTypes || isCreating, isError: isErrorPatients || isErrorAppointmentTypes || isErrorCreating, + isCreating, }; }; diff --git a/web-app/src/Components/Dummies/AppointmentFormModal/useAppointmentFormModalUI.js b/web-app/src/Components/Dummies/AppointmentFormModal/useAppointmentFormModalUI.js index 3064292..848949f 100644 --- a/web-app/src/Components/Dummies/AppointmentFormModal/useAppointmentFormModalUI.js +++ b/web-app/src/Components/Dummies/AppointmentFormModal/useAppointmentFormModalUI.js @@ -28,7 +28,7 @@ const useAppointmentFormModalUI = (createAppointment, patients, cancelAppointmen const [draftFiles, setDraftFiles] = useState([]); const editorRef = useRef(null); - const [uploadAppointmentFile] = useUploadAppointmentFileMutation(); + const [uploadAppointmentFile, {isLoading: isUploadingFile}] = useUploadAppointmentFileMutation(); const {data: appointments = []} = useGetAppointmentsQuery(userData.id, { pollingInterval: 20000, @@ -325,8 +325,8 @@ const useAppointmentFormModalUI = (createAppointment, patients, cancelAppointmen placement: "topRight", }); - dispatch(closeModal()); resetForm(); + dispatch(closeModal()); } catch (error) { console.error("Error creating appointment:", error); notification.error({ @@ -404,6 +404,7 @@ const useAppointmentFormModalUI = (createAppointment, patients, cancelAppointmen draftFiles, handleAddFile, handleRemoveFile, + isUploadingFile, }; }; diff --git a/web-app/src/Components/Widgets/AppointmentViewModal/AppointmentViewModal.jsx b/web-app/src/Components/Widgets/AppointmentViewModal/AppointmentViewModal.jsx index 4fff4aa..c248eea 100644 --- a/web-app/src/Components/Widgets/AppointmentViewModal/AppointmentViewModal.jsx +++ b/web-app/src/Components/Widgets/AppointmentViewModal/AppointmentViewModal.jsx @@ -1,4 +1,4 @@ -import {Button, Modal, Row, Typography, Spin, Splitter, Divider} from "antd"; +import {Button, Modal, Row, Typography, Spin, Divider, Popconfirm} from "antd"; import useAppointmentView from "./useAppointmentView.js"; const AppointmentViewModal = () => { @@ -20,6 +20,8 @@ const AppointmentViewModal = () => { isFilesLoading, downloadingFiles, downloadFile, + deletingFiles, + deleteFile, } = useAppointmentView(); if (!selectedAppointment) { @@ -78,14 +80,31 @@ const AppointmentViewModal = () => { files.map((file) => ( {file.file_title || labels.notSpecified} - +
+ + deleteFile(file.id, file.file_title)} + > + + + +
)) diff --git a/web-app/src/Components/Widgets/AppointmentViewModal/useAppointmentView.js b/web-app/src/Components/Widgets/AppointmentViewModal/useAppointmentView.js index 1b85f22..9f680f5 100644 --- a/web-app/src/Components/Widgets/AppointmentViewModal/useAppointmentView.js +++ b/web-app/src/Components/Widgets/AppointmentViewModal/useAppointmentView.js @@ -2,7 +2,7 @@ import {useDispatch, useSelector} from "react-redux"; import {setSelectedAppointment} from "../../../Redux/Slices/appointmentsSlice.js"; import dayjs from "dayjs"; import {useState} from "react"; -import {useGetAppointmentFilesQuery} from "../../../Api/appointmentFilesApi.js"; +import {useGetAppointmentFilesQuery, useDeleteAppointmentFileMutation} from "../../../Api/appointmentFilesApi.js"; import {baseQueryWithAuth} from "../../../Api/baseQuery.js"; import {notification} from "antd"; @@ -15,7 +15,10 @@ const useAppointmentView = () => { {skip: !selectedAppointment?.id} ); + const [deleteAppointmentFile, {isLoading: isDeletingFile}] = useDeleteAppointmentFileMutation(); + const [downloadingFiles, setDownloadingFiles] = useState({}); + const [deletingFiles, setDeletingFiles] = useState({}); const modalWidth = 700; const blockStyle = {marginBottom: 16}; @@ -39,6 +42,9 @@ const useAppointmentView = () => { noFiles: "Файлы отсутствуют", download: "Скачать", downloading: "Загрузка...", + delete: "Удалить", + deleting: "Удаление...", + confirmDelete: "Вы уверены, что хотите удалить файл?", }; const visible = !!selectedAppointment; @@ -120,6 +126,27 @@ const useAppointmentView = () => { } }; + const deleteFile = async (fileId, fileName) => { + try { + setDeletingFiles((prev) => ({...prev, [fileId]: true})); + await deleteAppointmentFile(fileId).unwrap(); + notification.success({ + message: "Файл удален", + description: `Файл ${fileName || "неизвестный"} успешно удален.`, + placement: "topRight", + }); + } catch (error) { + console.error("Error deleting file:", error); + notification.error({ + message: "Ошибка при удалении файла", + description: `Не удалось удалить файл ${fileName || "неизвестный"}: ${error.data?.detail || error.message}`, + placement: "topRight", + }); + } finally { + setDeletingFiles((prev) => ({...prev, [fileId]: false})); + } + }; + return { modalWidth, blockStyle, @@ -138,6 +165,9 @@ const useAppointmentView = () => { isFilesLoading, downloadingFiles, downloadFile, + deletingFiles, + deleteFile, + isDeletingFile, }; }; diff --git a/web-app/src/Core/axiosConfig.js b/web-app/src/Core/axiosConfig.js deleted file mode 100644 index a8d5d50..0000000 --- a/web-app/src/Core/axiosConfig.js +++ /dev/null @@ -1,58 +0,0 @@ -import axios from "axios"; -import CONFIG from "./сonfig.js"; -import {notification} from "antd"; - -const createApi = (logoutAndRedirect) => { - const api = axios.create({ - baseURL: CONFIG.BASE_URL, - }); - - api.interceptors.request.use(config => { - const token = localStorage.getItem('access_token'); - - if (token) { - config.headers.Authorization = `Bearer ${token}`; - } - - return config; - }); - - api.interceptors.response.use( - (response) => response, - (error) => { - if (error.response) { - const {status} = error.response; - - if (status === 401 || status === 403) { - notification.error({ - message: "Ошибка авторизации", - description: "Пользователь не найден или токен недействителен", - }); - - logoutAndRedirect(); - } else { - notification.error({ - message: "Ошибка API", - description: error.response.data.message || "Что-то пошло не так", - }); - } - } else if (error.request) { - notification.error({ - message: "Ошибка соединения", - description: "Сервер не отвечает. Проверьте соединение с сетью.", - }); - } else { - notification.error({ - message: "Неизвестная ошибка", - description: "Проверьте соединение с сетью.", - }); - } - - return Promise.reject(error); - } - ); - - return api; -}; - -export default createApi; diff --git a/web-app/src/Core/сonfig.js b/web-app/src/Core/сonfig.js index 76f01b4..9196e01 100644 --- a/web-app/src/Core/сonfig.js +++ b/web-app/src/Core/сonfig.js @@ -1,5 +1,5 @@ const CONFIG = { - BASE_URL: 'http://localhost:8000/api/v1', + BASE_URL: import.meta.env.VITE_BASE_URL, }; export default CONFIG; \ No newline at end of file diff --git a/web-app/vite.config.js b/web-app/vite.config.js index fbc7c47..ad37c15 100644 --- a/web-app/vite.config.js +++ b/web-app/vite.config.js @@ -9,5 +9,5 @@ export default defineConfig({ publicDir: 'public', optimizeDeps: { include: ['jodit-react'] - } + }, })