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']
- }
+ },
})