import { PureComponent } from 'react'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import CircularProgress from '@mui/material/CircularProgress'; import IconButton from '@mui/material/IconButton'; import TextField from '@mui/material/TextField'; import Typography from '@mui/material/Typography'; import Chip from '@mui/material/Chip'; import Tooltip from '@mui/material/Tooltip'; 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 VerticalAlignTopIcon from '@mui/icons-material/VerticalAlignTop'; import VerticalAlignBottomIcon from '@mui/icons-material/VerticalAlignBottom'; import ViewSidebarIcon from '@mui/icons-material/ViewSidebar'; import { enqueueSnackbar } from 'notistack'; import { PanelTile } from 'dashgui/panels'; import { delivery as deliveryApi } from '../../api'; import { SURFACE_BASE, SURFACE_WASH, WHITE, } from '../../colors'; /** * Inline text-file viewer/editor rendered as a full-width panel tile below the * manifest header ("Kopfdaten"). Mount it only while a text attachment is * selected; unmounting closes it. */ export class TextEditorPanel extends PureComponent { constructor(props) { super(props); this.state = { content: '', initialContent: '', loading: false, }; 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() { this.loadContent(); } componentDidUpdate(prevProps) { if (this.props.attachment !== prevProps.attachment) { this.loadContent(); } } async loadContent() { const { attachment, docId, onContentLoad } = this.props; if (!attachment) return; if (typeof attachment.text === 'string') { this.setState({ content: attachment.text, initialContent: attachment.text, loading: false, }); return; } if ( (attachment.file instanceof Blob || attachment.file instanceof File) && attachment.file.size > 0 ) { this.setState({ loading: true }); try { const text = await attachment.file.text(); this.setState({ content: text, 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 blob = await deliveryApi.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) { this.setState({ content: e.target.value }); } handleSave() { const { onSave, onClose } = this.props; const { content } = this.state; onSave?.(content); enqueueSnackbar('Textdatei gespeichert', { variant: 'success' }); onClose?.(); } async handleCopy() { try { await navigator.clipboard.writeText(this.state.content); enqueueSnackbar('In die Zwischenablage kopiert', { variant: 'info' }); } catch { enqueueSnackbar('Kopieren fehlgeschlagen', { variant: 'error' }); } } 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 { onClose, attachment, readOnly = false, wide = 3, position = 'top', onPositionChange, height = '60vh', } = this.props; if (!attachment) return null; const { content, initialContent, loading } = this.state; const filename = attachment?.name || 'Dokument.txt'; const isDirty = content !== initialContent; const linesCount = content ? content.split('\n').length : 0; const charCount = content ? content.length : 0; return ( {filename} {linesCount} Zeilen · {charCount} Zeichen {isDirty && ( )} onPositionChange?.('top')} > onPositionChange?.('right')} > onPositionChange?.('below')} > {loading && ( )} {!readOnly && ( )} ); } } export default TextEditorPanel;