feat: Обновление UI и исправление ошибок.
Внесены изменения в UI компонентов, исправлены ошибки. Добавлена возможность удаления файлов. Изменен CORS для API. Удален axiosConfig.js.
This commit is contained in:
parent
e7433a27bb
commit
5098b05003
@ -1,4 +1,3 @@
|
|||||||
.venv
|
.venv
|
||||||
k8s
|
k8s
|
||||||
.idea
|
.idea
|
||||||
k8s
|
|
||||||
@ -41,7 +41,7 @@ async def download_project_file(
|
|||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
'/{appointment_id}/upload',
|
'/{appointment_id}/upload/',
|
||||||
response_model=AppointmentFileEntity,
|
response_model=AppointmentFileEntity,
|
||||||
summary='Upload a new file for the appointment',
|
summary='Upload a new file for the appointment',
|
||||||
description='Uploads a new file and associates it with the specified appointment.'
|
description='Uploads a new file and associates it with the specified appointment.'
|
||||||
|
|||||||
@ -23,7 +23,7 @@ def start_app():
|
|||||||
|
|
||||||
api_app.add_middleware(
|
api_app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
allow_origins=['http://localhost:5173', 'http://localhost:5173'],
|
allow_origins=['https://api.visus.numerum.team', 'http://localhost:5173'],
|
||||||
allow_credentials=True,
|
allow_credentials=True,
|
||||||
allow_methods=['*'],
|
allow_methods=['*'],
|
||||||
allow_headers=['*'],
|
allow_headers=['*'],
|
||||||
|
|||||||
1
web-app/.gitignore
vendored
1
web-app/.gitignore
vendored
@ -22,3 +22,4 @@ dist-ssr
|
|||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
.env
|
||||||
@ -331,7 +331,7 @@ const AppointmentFormModal = () => {
|
|||||||
<Button
|
<Button
|
||||||
style={appointmentFormModalUI.footerButtonStyle}
|
style={appointmentFormModalUI.footerButtonStyle}
|
||||||
onClick={appointmentFormModalUI.handleClickBackButton}
|
onClick={appointmentFormModalUI.handleClickBackButton}
|
||||||
disabled={appointmentFormModalUI.disableBackButton}
|
disabled={appointmentFormModalUI.disableBackButton || appointmentFormModalUI.isUploadingFile || appointmentFormModalData.isCreating}
|
||||||
>
|
>
|
||||||
Назад
|
Назад
|
||||||
</Button>
|
</Button>
|
||||||
@ -339,6 +339,7 @@ const AppointmentFormModal = () => {
|
|||||||
type="primary"
|
type="primary"
|
||||||
onClick={appointmentFormModalUI.handleClickNextButton}
|
onClick={appointmentFormModalUI.handleClickNextButton}
|
||||||
disabled={appointmentFormModalUI.disableNextButton}
|
disabled={appointmentFormModalUI.disableNextButton}
|
||||||
|
loading={appointmentFormModalData.isCreating || appointmentFormModalUI.isUploadingFile}
|
||||||
>
|
>
|
||||||
{appointmentFormModalUI.nextButtonText}
|
{appointmentFormModalUI.nextButtonText}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -29,6 +29,7 @@ const useAppointmentFormModal = () => {
|
|||||||
useGetByPatientIdQuery,
|
useGetByPatientIdQuery,
|
||||||
isLoading: isLoadingPatients || isLoadingAppointmentTypes || isCreating,
|
isLoading: isLoadingPatients || isLoadingAppointmentTypes || isCreating,
|
||||||
isError: isErrorPatients || isErrorAppointmentTypes || isErrorCreating,
|
isError: isErrorPatients || isErrorAppointmentTypes || isErrorCreating,
|
||||||
|
isCreating,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -28,7 +28,7 @@ const useAppointmentFormModalUI = (createAppointment, patients, cancelAppointmen
|
|||||||
const [draftFiles, setDraftFiles] = useState([]);
|
const [draftFiles, setDraftFiles] = useState([]);
|
||||||
const editorRef = useRef(null);
|
const editorRef = useRef(null);
|
||||||
|
|
||||||
const [uploadAppointmentFile] = useUploadAppointmentFileMutation();
|
const [uploadAppointmentFile, {isLoading: isUploadingFile}] = useUploadAppointmentFileMutation();
|
||||||
|
|
||||||
const {data: appointments = []} = useGetAppointmentsQuery(userData.id, {
|
const {data: appointments = []} = useGetAppointmentsQuery(userData.id, {
|
||||||
pollingInterval: 20000,
|
pollingInterval: 20000,
|
||||||
@ -325,8 +325,8 @@ const useAppointmentFormModalUI = (createAppointment, patients, cancelAppointmen
|
|||||||
placement: "topRight",
|
placement: "topRight",
|
||||||
});
|
});
|
||||||
|
|
||||||
dispatch(closeModal());
|
|
||||||
resetForm();
|
resetForm();
|
||||||
|
dispatch(closeModal());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error creating appointment:", error);
|
console.error("Error creating appointment:", error);
|
||||||
notification.error({
|
notification.error({
|
||||||
@ -404,6 +404,7 @@ const useAppointmentFormModalUI = (createAppointment, patients, cancelAppointmen
|
|||||||
draftFiles,
|
draftFiles,
|
||||||
handleAddFile,
|
handleAddFile,
|
||||||
handleRemoveFile,
|
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";
|
import useAppointmentView from "./useAppointmentView.js";
|
||||||
|
|
||||||
const AppointmentViewModal = () => {
|
const AppointmentViewModal = () => {
|
||||||
@ -20,6 +20,8 @@ const AppointmentViewModal = () => {
|
|||||||
isFilesLoading,
|
isFilesLoading,
|
||||||
downloadingFiles,
|
downloadingFiles,
|
||||||
downloadFile,
|
downloadFile,
|
||||||
|
deletingFiles,
|
||||||
|
deleteFile,
|
||||||
} = useAppointmentView();
|
} = useAppointmentView();
|
||||||
|
|
||||||
if (!selectedAppointment) {
|
if (!selectedAppointment) {
|
||||||
@ -78,14 +80,31 @@ const AppointmentViewModal = () => {
|
|||||||
files.map((file) => (
|
files.map((file) => (
|
||||||
<Row key={file.id} align="middle" justify="space-between">
|
<Row key={file.id} align="middle" justify="space-between">
|
||||||
<span>{file.file_title || labels.notSpecified}</span>
|
<span>{file.file_title || labels.notSpecified}</span>
|
||||||
|
<div>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => downloadFile(file.id, file.file_title)}
|
onClick={() => downloadFile(file.id, file.file_title)}
|
||||||
loading={downloadingFiles[file.id] || false}
|
loading={downloadingFiles[file.id] || false}
|
||||||
disabled={downloadingFiles[file.id] || false}
|
disabled={downloadingFiles[file.id] || deletingFiles[file.id] || false}
|
||||||
type={"dashed"}
|
type={"dashed"}
|
||||||
|
style={{marginRight: 8}}
|
||||||
>
|
>
|
||||||
{downloadingFiles[file.id] ? labels.downloading : labels.download}
|
{downloadingFiles[file.id] ? labels.downloading : labels.download}
|
||||||
</Button>
|
</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/>
|
<Divider/>
|
||||||
</Row>
|
</Row>
|
||||||
))
|
))
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import {useDispatch, useSelector} from "react-redux";
|
|||||||
import {setSelectedAppointment} from "../../../Redux/Slices/appointmentsSlice.js";
|
import {setSelectedAppointment} from "../../../Redux/Slices/appointmentsSlice.js";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import {useState} from "react";
|
import {useState} from "react";
|
||||||
import {useGetAppointmentFilesQuery} from "../../../Api/appointmentFilesApi.js";
|
import {useGetAppointmentFilesQuery, useDeleteAppointmentFileMutation} from "../../../Api/appointmentFilesApi.js";
|
||||||
import {baseQueryWithAuth} from "../../../Api/baseQuery.js";
|
import {baseQueryWithAuth} from "../../../Api/baseQuery.js";
|
||||||
import {notification} from "antd";
|
import {notification} from "antd";
|
||||||
|
|
||||||
@ -15,7 +15,10 @@ const useAppointmentView = () => {
|
|||||||
{skip: !selectedAppointment?.id}
|
{skip: !selectedAppointment?.id}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [deleteAppointmentFile, {isLoading: isDeletingFile}] = useDeleteAppointmentFileMutation();
|
||||||
|
|
||||||
const [downloadingFiles, setDownloadingFiles] = useState({});
|
const [downloadingFiles, setDownloadingFiles] = useState({});
|
||||||
|
const [deletingFiles, setDeletingFiles] = useState({});
|
||||||
|
|
||||||
const modalWidth = 700;
|
const modalWidth = 700;
|
||||||
const blockStyle = {marginBottom: 16};
|
const blockStyle = {marginBottom: 16};
|
||||||
@ -39,6 +42,9 @@ const useAppointmentView = () => {
|
|||||||
noFiles: "Файлы отсутствуют",
|
noFiles: "Файлы отсутствуют",
|
||||||
download: "Скачать",
|
download: "Скачать",
|
||||||
downloading: "Загрузка...",
|
downloading: "Загрузка...",
|
||||||
|
delete: "Удалить",
|
||||||
|
deleting: "Удаление...",
|
||||||
|
confirmDelete: "Вы уверены, что хотите удалить файл?",
|
||||||
};
|
};
|
||||||
|
|
||||||
const visible = !!selectedAppointment;
|
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 {
|
return {
|
||||||
modalWidth,
|
modalWidth,
|
||||||
blockStyle,
|
blockStyle,
|
||||||
@ -138,6 +165,9 @@ const useAppointmentView = () => {
|
|||||||
isFilesLoading,
|
isFilesLoading,
|
||||||
downloadingFiles,
|
downloadingFiles,
|
||||||
downloadFile,
|
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 = {
|
const CONFIG = {
|
||||||
BASE_URL: 'http://localhost:8000/api/v1',
|
BASE_URL: import.meta.env.VITE_BASE_URL,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CONFIG;
|
export default CONFIG;
|
||||||
@ -9,5 +9,5 @@ export default defineConfig({
|
|||||||
publicDir: 'public',
|
publicDir: 'public',
|
||||||
optimizeDeps: {
|
optimizeDeps: {
|
||||||
include: ['jodit-react']
|
include: ['jodit-react']
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user