feat: Vorschau-Modal für Dokument-Dateien mit Unterstützung für mehrere Formate

- Prop `format` zu DocFullView hinzugefügt, um den Dateityp zu spezifizieren
- Dynamische Blob-Generierung mit korrektem MIME-Typ implementiert
- Unterstützt Inline-Vorschau für Formate wie PDF, HTML, Bilder, etc.
- Fallback zum Download-Link für nicht unterstützte Dateitypen
- Ungenutzte Statusfelder wurden entfernt und der modale Inhalt vereinfacht
This commit is contained in:
tekh 2025-07-14 10:53:09 +02:00
parent 3e7a22f9e2
commit 1a55bd7748
2 changed files with 78 additions and 17 deletions

View File

@ -164,7 +164,7 @@ export function DocItem({
return ( return (
<> <>
<DocFullView data={doc.data} open={openViewDoc} handleClose={() => setOpenViewDoc(false)} /> <DocFullView data={doc.data} open={openViewDoc} handleClose={() => setOpenViewDoc(false)} format={doc.extension} />
<Card sx={sx} {...other} onClick={() => setOpenViewDoc(true)}> <Card sx={sx} {...other} onClick={() => setOpenViewDoc(true)}>
<Box <Box
sx={(theme) => ({ sx={(theme) => ({

View File

@ -1,13 +1,33 @@
import { useState } from 'react'; import { useEffect, useState } from 'react';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import Modal from '@mui/material/Modal'; import Modal from '@mui/material/Modal';
import { TextField } from '@mui/material';
import { Type } from 'src/api/attribute-service'; import { FileFormat } from 'src/api/document-service';
const getMimeType = (format: FileFormat): string => {
switch (format) {
case 'pdf': return 'application/pdf';
case 'docx': return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
case 'xlsx': return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
case 'csv': return 'text/csv';
case 'pptx': return 'application/vnd.openxmlformats-officedocument.presentationml.presentation';
case 'txt': return 'text/plain';
case 'json': return 'application/json';
case 'xml': return 'application/xml';
case 'html': return 'text/html';
case 'jpg': return 'image/jpeg';
case 'png': return 'image/png';
case 'svg': return 'image/svg+xml';
case 'zip': return 'application/zip';
case 'md': return 'text/markdown';
default: return 'application/octet-stream';
}
};
type DocFullViewProps = { type DocFullViewProps = {
data: Uint8Array; data: Uint8Array;
format?: FileFormat;
open: boolean; open: boolean;
handleClose: () => void; handleClose: () => void;
} }
@ -17,29 +37,70 @@ const style = {
top: '50%', top: '50%',
left: '50%', left: '50%',
transform: 'translate(-50%, -50%)', transform: 'translate(-50%, -50%)',
width: 400, width: '90%',
bgcolor: 'background.paper', height: '90%',
border: '2px solid #000', bgcolor: 'transparent',
boxShadow: 24,
p: 4,
}; };
export default function DocFullView({ data, open, handleClose }: DocFullViewProps) { export default function DocFullView({ data, format, open, handleClose }: DocFullViewProps) {
const [name, setName] = useState<string | undefined>(''); const [objectUrl, setObjectUrl] = useState<string | null>(null);
const [label, setLabel] = useState<string | undefined>('');
const [selectedType, setSelectedType] = useState<Type | null>(null); useEffect(() => {
if (!data || !format)
return undefined;
const mimeType = getMimeType(format);
const blob = new Blob([data], { type: mimeType });
const url = URL.createObjectURL(blob);
setObjectUrl(url);
return () => {
URL.revokeObjectURL(url);
};
}, [data, format]);
const renderContent = () => {
if (!objectUrl || !format) return null;
if (['pdf', 'html', 'txt', 'json', 'xml', 'md'].includes(format)) {
return (
<iframe
src={objectUrl}
style={{ width: '100%', height: '100%', border: 'none' }}
title={`Viewer for ${format}`}
/>
);
}
if (['jpg', 'png', 'svg'].includes(format)) {
return (
<img
src={objectUrl}
alt="Document Preview"
style={{ maxWidth: '100%', maxHeight: '100%' }}
/>
);
}
// Download link for unsupported preview formats
return (
<Box textAlign="center">
<p>This file type cannot be displayed. You can download it below:</p>
<a href={objectUrl} download={`document.${format}`}>Download file</a>
</Box>
);
};
return ( return (
<Modal <Modal
open={open} open={open}
onClose={handleClose}
aria-labelledby="modal-modal-title" aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description" aria-describedby="modal-modal-description"
onClose={handleClose}
> >
<Box sx={style}> <Box sx={style}>
<TextField label="Label" variant="filled" value={name} onChange={e => setName(e.target.value)} /> {renderContent()}
<TextField label="Name" variant="filled" value={label} onChange={e => setLabel(e.target.value)} />
</Box> </Box>
</Modal> </Modal>
); );
} }