feat: Обновление UI и исправление ошибок.
Внесены изменения в UI компонентов, исправлены ошибки. Добавлена возможность удаления файлов. Изменен CORS для API. Удален axiosConfig.js.
This commit is contained in:
parent
e7433a27bb
commit
5098b05003
@ -1,4 +1,3 @@
|
||||
.venv
|
||||
k8s
|
||||
.idea
|
||||
k8s
|
||||
.idea
|
||||
@ -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.'
|
||||
|
||||
@ -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
1
web-app/.gitignore
vendored
@ -22,3 +22,4 @@ dist-ssr
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
.env
|
||||
@ -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>
|
||||
|
||||
@ -29,6 +29,7 @@ const useAppointmentFormModal = () => {
|
||||
useGetByPatientIdQuery,
|
||||
isLoading: isLoadingPatients || isLoadingAppointmentTypes || isCreating,
|
||||
isError: isErrorPatients || isErrorAppointmentTypes || isErrorCreating,
|
||||
isCreating,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -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>
|
||||
))
|
||||
|
||||
@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -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;
|
||||
@ -1,5 +1,5 @@
|
||||
const CONFIG = {
|
||||
BASE_URL: 'http://localhost:8000/api/v1',
|
||||
BASE_URL: import.meta.env.VITE_BASE_URL,
|
||||
};
|
||||
|
||||
export default CONFIG;
|
||||
@ -9,5 +9,5 @@ export default defineConfig({
|
||||
publicDir: 'public',
|
||||
optimizeDeps: {
|
||||
include: ['jodit-react']
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user