feat: Модальное окно просмотра записи: Добавлена загрузка файлов.
This commit is contained in:
parent
64b0a19408
commit
b53ab902f8
@ -1,11 +1,33 @@
|
|||||||
import { createApi } from "@reduxjs/toolkit/query/react";
|
import { createApi } from "@reduxjs/toolkit/query/react";
|
||||||
import { baseQueryWithAuth } from "./baseQuery.js";
|
import { baseQueryWithAuth } from "./baseQuery.js";
|
||||||
|
import { isPlainObject } from "@reduxjs/toolkit";
|
||||||
|
|
||||||
export const appointmentFilesApi = createApi({
|
export const appointmentFilesApi = createApi({
|
||||||
reducerPath: 'appointmentFilesApi',
|
reducerPath: 'appointmentFilesApi',
|
||||||
baseQuery: baseQueryWithAuth,
|
baseQuery: baseQueryWithAuth,
|
||||||
tagTypes: ['AppointmentFile'],
|
tagTypes: ['AppointmentFile'],
|
||||||
endpoints: (builder) => ({
|
endpoints: (builder) => ({
|
||||||
|
getAppointmentFiles: builder.query({
|
||||||
|
query: (appointmentId) => {
|
||||||
|
console.log(`Fetching files for appointment ID: ${appointmentId}`);
|
||||||
|
return `/appointment_files/${appointmentId}/`;
|
||||||
|
},
|
||||||
|
providesTags: ['AppointmentFile'],
|
||||||
|
refetchOnMountOrArgChange: 5,
|
||||||
|
}),
|
||||||
|
downloadAppointmentFile: builder.mutation({
|
||||||
|
query: (fileId) => ({
|
||||||
|
url: `/appointment_files/${fileId}/file/`,
|
||||||
|
method: 'GET',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
deleteAppointmentFile: builder.mutation({
|
||||||
|
query: (fileId) => ({
|
||||||
|
url: `/appointment_files/${fileId}/`,
|
||||||
|
method: 'DELETE',
|
||||||
|
}),
|
||||||
|
invalidatesTags: ['AppointmentFile'],
|
||||||
|
}),
|
||||||
uploadAppointmentFile: builder.mutation({
|
uploadAppointmentFile: builder.mutation({
|
||||||
query: ({ appointmentId, file }) => {
|
query: ({ appointmentId, file }) => {
|
||||||
if (!(file instanceof File)) {
|
if (!(file instanceof File)) {
|
||||||
@ -24,11 +46,20 @@ export const appointmentFilesApi = createApi({
|
|||||||
invalidatesTags: ['AppointmentFile'],
|
invalidatesTags: ['AppointmentFile'],
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
middleware: (defaultMiddleware) =>
|
||||||
|
defaultMiddleware({
|
||||||
|
serializableCheck: {
|
||||||
|
isSerializable: (value) => {
|
||||||
|
if (value instanceof Blob) return true; // Игнорируем Blob
|
||||||
|
return isPlainObject(value) || typeof value !== 'object';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const {
|
export const {
|
||||||
useGetAppointmentFilesQuery,
|
useGetAppointmentFilesQuery,
|
||||||
useDownloadAppointmentFileQuery,
|
useDownloadAppointmentFileMutation,
|
||||||
useUploadAppointmentFileMutation,
|
|
||||||
useDeleteAppointmentFileMutation,
|
useDeleteAppointmentFileMutation,
|
||||||
|
useUploadAppointmentFileMutation,
|
||||||
} = appointmentFilesApi;
|
} = appointmentFilesApi;
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import {Button, Modal, Row, Typography} from "antd";
|
import { Button, Modal, Row, Typography, Spin } from "antd";
|
||||||
import useAppointmentViewUI from "./useAppointmentViewUI.js";
|
import useAppointmentViewUI from "./useAppointmentViewUI.js";
|
||||||
|
|
||||||
const AppointmentViewModal = () => {
|
const AppointmentViewModal = () => {
|
||||||
@ -16,6 +16,10 @@ const AppointmentViewModal = () => {
|
|||||||
getPatientField,
|
getPatientField,
|
||||||
getResults,
|
getResults,
|
||||||
onCancel,
|
onCancel,
|
||||||
|
files,
|
||||||
|
isFilesLoading,
|
||||||
|
downloadingFiles,
|
||||||
|
downloadFile,
|
||||||
} = useAppointmentViewUI();
|
} = useAppointmentViewUI();
|
||||||
|
|
||||||
if (!selectedAppointment) {
|
if (!selectedAppointment) {
|
||||||
@ -63,8 +67,30 @@ const AppointmentViewModal = () => {
|
|||||||
<b>{labels.results}</b>
|
<b>{labels.results}</b>
|
||||||
</p>
|
</p>
|
||||||
<div
|
<div
|
||||||
dangerouslySetInnerHTML={{__html: getResults(selectedAppointment.results)}}
|
dangerouslySetInnerHTML={{ __html: getResults(selectedAppointment.results) }}
|
||||||
/>
|
/>
|
||||||
|
<p>
|
||||||
|
<b>{labels.files}</b>
|
||||||
|
</p>
|
||||||
|
{isFilesLoading ? (
|
||||||
|
<Spin />
|
||||||
|
) : files.length > 0 ? (
|
||||||
|
files.map((file) => (
|
||||||
|
<div key={file.id} style={{ marginBottom: 8 }}>
|
||||||
|
<span>{file.file_title || labels.notSpecified}</span>
|
||||||
|
<Button
|
||||||
|
style={{ marginLeft: 8 }}
|
||||||
|
onClick={() => downloadFile(file.id, file.file_title)}
|
||||||
|
loading={downloadingFiles[file.id] || false}
|
||||||
|
disabled={downloadingFiles[file.id] || false}
|
||||||
|
>
|
||||||
|
{downloadingFiles[file.id] ? labels.downloading : labels.download}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<p>{labels.noFiles}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Row justify="end" style={footerRowStyle}>
|
<Row justify="end" style={footerRowStyle}>
|
||||||
<Button style={footerButtonStyle} onClick={onCancel}>
|
<Button style={footerButtonStyle} onClick={onCancel}>
|
||||||
|
|||||||
@ -1,11 +1,21 @@
|
|||||||
import { useDispatch, useSelector } from "react-redux";
|
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 { useGetAppointmentFilesQuery, useDownloadAppointmentFileMutation } from "../../../Api/appointmentFilesApi.js";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
const useAppointmentViewUI = () => {
|
const useAppointmentViewUI = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { selectedAppointment } = useSelector((state) => state.appointmentsUI);
|
const { selectedAppointment } = useSelector((state) => state.appointmentsUI);
|
||||||
|
|
||||||
|
const { data: files = [], isLoading: isFilesLoading } = useGetAppointmentFilesQuery(
|
||||||
|
selectedAppointment?.id,
|
||||||
|
{ skip: !selectedAppointment?.id }
|
||||||
|
);
|
||||||
|
|
||||||
|
const [downloadAppointmentFile] = useDownloadAppointmentFileMutation();
|
||||||
|
const [downloadingFiles, setDownloadingFiles] = useState({});
|
||||||
|
|
||||||
const modalWidth = 700;
|
const modalWidth = 700;
|
||||||
const blockStyle = { marginBottom: 16 };
|
const blockStyle = { marginBottom: 16 };
|
||||||
const footerRowStyle = { marginTop: 16 };
|
const footerRowStyle = { marginTop: 16 };
|
||||||
@ -24,6 +34,10 @@ const useAppointmentViewUI = () => {
|
|||||||
closeButton: "Закрыть",
|
closeButton: "Закрыть",
|
||||||
notSpecified: "Не указан",
|
notSpecified: "Не указан",
|
||||||
resultsNotSpecified: "Не указаны",
|
resultsNotSpecified: "Не указаны",
|
||||||
|
files: "Файлы:",
|
||||||
|
noFiles: "Файлы отсутствуют",
|
||||||
|
download: "Скачать",
|
||||||
|
downloading: "Загрузка...",
|
||||||
};
|
};
|
||||||
|
|
||||||
const visible = !!selectedAppointment;
|
const visible = !!selectedAppointment;
|
||||||
@ -56,6 +70,26 @@ const useAppointmentViewUI = () => {
|
|||||||
dispatch(setSelectedAppointment(null));
|
dispatch(setSelectedAppointment(null));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const downloadFile = async (fileId, fileName) => {
|
||||||
|
try {
|
||||||
|
setDownloadingFiles((prev) => ({ ...prev, [fileId]: true }));
|
||||||
|
const blob = await downloadAppointmentFile(fileId).unwrap();
|
||||||
|
const url = window.URL.createObjectURL(blob);
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.href = url;
|
||||||
|
link.setAttribute("download", fileName || "file");
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
link.remove();
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Ошибка при скачивании файла:", error);
|
||||||
|
// Можно добавить уведомление об ошибке, например, с antd message
|
||||||
|
} finally {
|
||||||
|
setDownloadingFiles((prev) => ({ ...prev, [fileId]: false }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
modalWidth,
|
modalWidth,
|
||||||
blockStyle,
|
blockStyle,
|
||||||
@ -70,6 +104,10 @@ const useAppointmentViewUI = () => {
|
|||||||
getPatientField,
|
getPatientField,
|
||||||
getResults,
|
getResults,
|
||||||
onCancel,
|
onCancel,
|
||||||
|
files,
|
||||||
|
isFilesLoading,
|
||||||
|
downloadingFiles,
|
||||||
|
downloadFile,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user