file api
This commit is contained in:
@@ -31,8 +31,19 @@ export class Attachment extends PureComponent {
|
||||
if (onClick && attachment) {
|
||||
onClick(attachment);
|
||||
} else if (attachment?.file instanceof Blob || attachment?.file instanceof File) {
|
||||
const url = URL.createObjectURL(attachment.file);
|
||||
window.open(url, '_blank');
|
||||
if (attachment.file.size > 0) {
|
||||
const url = URL.createObjectURL(attachment.file);
|
||||
window.open(url, '_blank');
|
||||
} else {
|
||||
const docId = attachment.manifestId;
|
||||
const fileId = attachment.fileId ?? attachment.id;
|
||||
if (docId != null && fileId != null) {
|
||||
window.open(
|
||||
`/api/delivery/${encodeURIComponent(docId)}/file/${encodeURIComponent(fileId)}`,
|
||||
'_blank',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -49,7 +60,8 @@ export class Attachment extends PureComponent {
|
||||
const name = attachment?.name || 'Dokument';
|
||||
const size = attachment?.size;
|
||||
const isPdf = attachment?.type === 'application/pdf' || name.toLowerCase().endsWith('.pdf');
|
||||
const isText = attachment?.type?.startsWith('text/') || name.toLowerCase().endsWith('.txt');
|
||||
const isCsv = attachment?.type === 'text/csv' || name.toLowerCase().endsWith('.csv');
|
||||
const isText = attachment?.type?.startsWith('text/') || name.toLowerCase().endsWith('.txt') || isCsv;
|
||||
|
||||
return (
|
||||
<Box
|
||||
@@ -125,7 +137,7 @@ export class Attachment extends PureComponent {
|
||||
letterSpacing: '0.05em',
|
||||
}}
|
||||
>
|
||||
TXT
|
||||
{isCsv ? 'CSV' : 'TXT'}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Typography
|
||||
|
||||
@@ -134,7 +134,7 @@ class ManifestHeader extends PureComponent {
|
||||
) : null}
|
||||
{onPdfUpload ? (
|
||||
<FileDropzone
|
||||
accept="application/pdf,.pdf"
|
||||
accept="application/pdf,.pdf,text/plain,.txt,text/csv,.csv"
|
||||
disabled={saving || pdfUploading}
|
||||
onFileDrop={onPdfUpload}
|
||||
sx={{
|
||||
@@ -150,6 +150,7 @@ class ManifestHeader extends PureComponent {
|
||||
startIcon={pdfUploading ? <CircularProgress size={16} color="inherit" /> : <UploadFileIcon />}
|
||||
disabled={saving || pdfUploading}
|
||||
onClick={this.handleUploadClick}
|
||||
title="PDF, TXT oder CSV hochladen"
|
||||
sx={{
|
||||
whiteSpace: 'nowrap',
|
||||
height: 40,
|
||||
@@ -164,8 +165,8 @@ class ManifestHeader extends PureComponent {
|
||||
{pdfUploading
|
||||
? 'Wird analysiert…'
|
||||
: isDragging
|
||||
? 'PDF hier ablegen'
|
||||
: 'PDF hochladen'}
|
||||
? 'Datei hier ablegen'
|
||||
: 'Datei hochladen'}
|
||||
</Button>
|
||||
)}
|
||||
</FileDropzone>
|
||||
@@ -182,7 +183,7 @@ class ManifestHeader extends PureComponent {
|
||||
<input
|
||||
ref={this.fileInputRef}
|
||||
type="file"
|
||||
accept="application/pdf,.pdf"
|
||||
accept="application/pdf,.pdf,text/plain,.txt,text/csv,.csv"
|
||||
style={{ display: 'none' }}
|
||||
onChange={this.handleFileChange}
|
||||
/>
|
||||
|
||||
@@ -15,6 +15,7 @@ import CloseIcon from '@mui/icons-material/Close';
|
||||
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
||||
import ErrorOutlinedIcon from '@mui/icons-material/ErrorOutlined';
|
||||
import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf';
|
||||
import TextSnippetOutlinedIcon from '@mui/icons-material/TextSnippetOutlined';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
|
||||
|
||||
@@ -97,6 +98,9 @@ export default function ManifestPdfDialog({ open, filename, data, onClose }) {
|
||||
const { streaming = false, messages = [], result = null, error = null } = data ?? {};
|
||||
const hasResult = Boolean(result);
|
||||
|
||||
const isTextDoc =
|
||||
filename?.toLowerCase().endsWith('.txt') || filename?.toLowerCase().endsWith('.csv');
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
@@ -121,12 +125,18 @@ export default function ManifestPdfDialog({ open, filename, data, onClose }) {
|
||||
<ErrorOutlinedIcon color="error" />
|
||||
) : streaming ? (
|
||||
<CircularProgress size={22} />
|
||||
) : isTextDoc ? (
|
||||
<TextSnippetOutlinedIcon color="action" />
|
||||
) : (
|
||||
<PictureAsPdfIcon color="action" />
|
||||
)}
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Typography variant="subtitle1" component="div" fontWeight={600}>
|
||||
{streaming ? 'PDF wird analysiert…' : error ? 'PDF-Analyse fehlgeschlagen' : 'PDF-Analyse abgeschlossen'}
|
||||
{streaming
|
||||
? (isTextDoc ? 'Dokument wird analysiert…' : 'PDF wird analysiert…')
|
||||
: error
|
||||
? 'Analyse fehlgeschlagen'
|
||||
: (isTextDoc ? 'Dokument-Analyse abgeschlossen' : 'PDF-Analyse abgeschlossen')}
|
||||
</Typography>
|
||||
{filename ? (
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { PureComponent } from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import Button from '@mui/material/Button';
|
||||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
@@ -12,10 +13,15 @@ import Chip from '@mui/material/Chip';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import TextSnippetOutlinedIcon from '@mui/icons-material/TextSnippetOutlined';
|
||||
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
||||
import DownloadOutlinedIcon from '@mui/icons-material/DownloadOutlined';
|
||||
import SaveOutlinedIcon from '@mui/icons-material/SaveOutlined';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import { StorageManagerContext } from '../../api/storageManagerContext';
|
||||
import { delivery as deliveryApi } from '../../api';
|
||||
|
||||
export class TextEditorDialog extends PureComponent {
|
||||
static contextType = StorageManagerContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
@@ -26,6 +32,7 @@ export class TextEditorDialog extends PureComponent {
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.handleSave = this.handleSave.bind(this);
|
||||
this.handleCopy = this.handleCopy.bind(this);
|
||||
this.handleDownload = this.handleDownload.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -34,12 +41,16 @@ export class TextEditorDialog extends PureComponent {
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.attachment !== prevProps.attachment || (this.props.open && !prevProps.open)) {
|
||||
this.loadContent();
|
||||
if (this.props.open) {
|
||||
this.loadContent();
|
||||
} else {
|
||||
this.setState({ content: '', initialContent: '', loading: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async loadContent() {
|
||||
const { attachment, open } = this.props;
|
||||
const { attachment, open, docId, onContentLoad } = this.props;
|
||||
if (!open || !attachment) return;
|
||||
|
||||
if (typeof attachment.text === 'string') {
|
||||
@@ -51,7 +62,10 @@ export class TextEditorDialog extends PureComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
if (attachment.file instanceof Blob || attachment.file instanceof File) {
|
||||
if (
|
||||
(attachment.file instanceof Blob || attachment.file instanceof File) &&
|
||||
attachment.file.size > 0
|
||||
) {
|
||||
this.setState({ loading: true });
|
||||
try {
|
||||
const text = await attachment.file.text();
|
||||
@@ -60,11 +74,44 @@ export class TextEditorDialog extends PureComponent {
|
||||
initialContent: text,
|
||||
loading: false,
|
||||
});
|
||||
return;
|
||||
} catch (err) {
|
||||
console.error('Fehler beim Lesen der Textdatei:', err);
|
||||
this.setState({ loading: false });
|
||||
}
|
||||
}
|
||||
|
||||
// Attachment has no text and empty file — load from API (GET /delivery/{id}/file/{fileId})
|
||||
const effectiveDocId = docId ?? attachment.manifestId;
|
||||
const fileId = attachment.fileId ?? attachment.id;
|
||||
if (effectiveDocId != null && fileId != null) {
|
||||
this.setState({ loading: true });
|
||||
try {
|
||||
const getFile =
|
||||
this.context?.delivery?.getFile?.bind(this.context.delivery) ||
|
||||
deliveryApi.getFile;
|
||||
const blob = await getFile(effectiveDocId, fileId);
|
||||
const text = await blob.text();
|
||||
this.setState({
|
||||
content: text,
|
||||
initialContent: text,
|
||||
loading: false,
|
||||
});
|
||||
onContentLoad?.(attachment, text, blob);
|
||||
return;
|
||||
} catch (err) {
|
||||
console.error('Fehler beim Laden der Textdatei:', err);
|
||||
enqueueSnackbar('Fehler beim Laden der Datei', { variant: 'error' });
|
||||
this.setState({ loading: false });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({
|
||||
content: '',
|
||||
initialContent: '',
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
|
||||
handleChange(e) {
|
||||
@@ -88,6 +135,22 @@ export class TextEditorDialog extends PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
handleDownload() {
|
||||
const { attachment } = this.props;
|
||||
const { content } = this.state;
|
||||
if (!content) return;
|
||||
const filename = attachment?.name || 'Dokument.txt';
|
||||
const blob = new Blob([content], { type: 'text/plain;charset=utf-8;' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = filename;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { open, onClose, attachment, readOnly = false } = this.props;
|
||||
if (!open) return null;
|
||||
@@ -152,8 +215,24 @@ export class TextEditorDialog extends PureComponent {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
bgcolor: '#fafafa',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
{loading && (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
bgcolor: 'rgba(250, 250, 250, 0.7)',
|
||||
zIndex: 2,
|
||||
}}
|
||||
>
|
||||
<CircularProgress size={36} />
|
||||
</Box>
|
||||
)}
|
||||
<TextField
|
||||
multiline
|
||||
fullWidth
|
||||
@@ -216,15 +295,26 @@ export class TextEditorDialog extends PureComponent {
|
||||
justifyContent: 'space-between',
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
size="small"
|
||||
variant="outlined"
|
||||
startIcon={<ContentCopyIcon />}
|
||||
onClick={this.handleCopy}
|
||||
disabled={loading || !content}
|
||||
>
|
||||
Kopieren
|
||||
</Button>
|
||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||
<Button
|
||||
size="small"
|
||||
variant="outlined"
|
||||
startIcon={<ContentCopyIcon />}
|
||||
onClick={this.handleCopy}
|
||||
disabled={loading || !content}
|
||||
>
|
||||
Kopieren
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
variant="outlined"
|
||||
startIcon={<DownloadOutlinedIcon />}
|
||||
onClick={this.handleDownload}
|
||||
disabled={loading || !content}
|
||||
>
|
||||
Download
|
||||
</Button>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||
<Button size="small" onClick={onClose}>
|
||||
{readOnly ? 'Schließen' : 'Abbrechen'}
|
||||
|
||||
Reference in New Issue
Block a user