feat: Обновление UI и исправление ошибок.

Внесены изменения в UI компонентов, исправлены ошибки.
Добавлена возможность удаления файлов.
Изменен CORS для API.
Удален axiosConfig.js.
This commit is contained in:
Андрей Дувакин 2025-06-06 17:12:42 +05:00
parent e7433a27bb
commit 5098b05003
12 changed files with 73 additions and 79 deletions

View File

@ -1,4 +1,3 @@
.venv
k8s
.idea
k8s
.idea

View File

@ -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.'

View File

@ -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=['*'],

1
web-app/.gitignore vendored
View File

@ -22,3 +22,4 @@ dist-ssr
*.njsproj
*.sln
*.sw?
.env

View File

@ -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
>
<Button icon={<UploadOutlined />}>Выбрать файлы</Button>
<Button icon={<UploadOutlined/>}>Выбрать файлы</Button>
</Upload>
</Form.Item>
</Form>
@ -331,7 +331,7 @@ const AppointmentFormModal = () => {
<Button
style={appointmentFormModalUI.footerButtonStyle}
onClick={appointmentFormModalUI.handleClickBackButton}
disabled={appointmentFormModalUI.disableBackButton}
disabled={appointmentFormModalUI.disableBackButton || appointmentFormModalUI.isUploadingFile || appointmentFormModalData.isCreating}
>
Назад
</Button>
@ -339,6 +339,7 @@ const AppointmentFormModal = () => {
type="primary"
onClick={appointmentFormModalUI.handleClickNextButton}
disabled={appointmentFormModalUI.disableNextButton}
loading={appointmentFormModalData.isCreating || appointmentFormModalUI.isUploadingFile}
>
{appointmentFormModalUI.nextButtonText}
</Button>

View File

@ -29,6 +29,7 @@ const useAppointmentFormModal = () => {
useGetByPatientIdQuery,
isLoading: isLoadingPatients || isLoadingAppointmentTypes || isCreating,
isError: isErrorPatients || isErrorAppointmentTypes || isErrorCreating,
isCreating,
};
};

View File

@ -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,
};
};

View File

@ -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) => (
<Row key={file.id} align="middle" justify="space-between">
<span>{file.file_title || labels.notSpecified}</span>
<Button
onClick={() => downloadFile(file.id, file.file_title)}
loading={downloadingFiles[file.id] || false}
disabled={downloadingFiles[file.id] || false}
type={"dashed"}
>
{downloadingFiles[file.id] ? labels.downloading : labels.download}
</Button>
<div>
<Button
onClick={() => downloadFile(file.id, file.file_title)}
loading={downloadingFiles[file.id] || false}
disabled={downloadingFiles[file.id] || deletingFiles[file.id] || false}
type={"dashed"}
style={{marginRight: 8}}
>
{downloadingFiles[file.id] ? labels.downloading : labels.download}
</Button>
<Popconfirm
title={labels.confirmDelete}
onConfirm={() => deleteFile(file.id, file.file_title)}
>
<Button
loading={deletingFiles[file.id] || false}
disabled={deletingFiles[file.id] || downloadingFiles[file.id] || false}
type={"dashed"}
danger
>
{deletingFiles[file.id] ? labels.deleting : labels.delete}
</Button>
</Popconfirm>
</div>
<Divider/>
</Row>
))

View File

@ -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,
};
};

View File

@ -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;

View File

@ -1,5 +1,5 @@
const CONFIG = {
BASE_URL: 'http://localhost:8000/api/v1',
BASE_URL: import.meta.env.VITE_BASE_URL,
};
export default CONFIG;

View File

@ -9,5 +9,5 @@ export default defineConfig({
publicDir: 'public',
optimizeDeps: {
include: ['jodit-react']
}
},
})