feat: Модальное окно просмотра записи: Добавлена загрузка файлов.

This commit is contained in:
Андрей Дувакин 2025-06-04 19:05:49 +05:00
parent 64b0a19408
commit b53ab902f8
3 changed files with 99 additions and 4 deletions

View File

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

View File

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

View File

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