feat(lieferung/neu): PDF upload with SSE streaming and result dialog
- Add PDF-Upload button to ManifestHeader (delivery forms only) - New ManifestPdfDialog: live SSE event log, structured basedata, and line-items table streamed from POST /api/delivery/parsePDF - ApiClient.delivery.parsePdfSse: multipart/form-data upload, async generator yielding typed SSE events (status, llm_call, tool_call, usage, progress, result, error) - ManifestForm.handlePdfUpload: drives SSE generator, updates dialog state in real-time; dialog opens immediately on upload - Fix MUI icon imports (CheckCircle, ErrorOutlined)
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
import { PureComponent } from 'react';
|
||||
import { PureComponent, createRef } from 'react';
|
||||
import Autocomplete from '@mui/material/Autocomplete';
|
||||
import Button from '@mui/material/Button';
|
||||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import UploadFileIcon from '@mui/icons-material/UploadFile';
|
||||
import SingleLineTextEdit from 'dashgui/SingleLineTextEdit';
|
||||
import { PanelTile } from 'dashgui/panels';
|
||||
import { manifestConfig, partyDisplayName } from './manifestConfig';
|
||||
@@ -9,13 +12,27 @@ import { manifestConfig, partyDisplayName } from './manifestConfig';
|
||||
class ManifestHeader extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.fileInputRef = createRef();
|
||||
this.handlePartyChange = this.handlePartyChange.bind(this);
|
||||
this.handleUploadClick = this.handleUploadClick.bind(this);
|
||||
this.handleFileChange = this.handleFileChange.bind(this);
|
||||
}
|
||||
|
||||
handlePartyChange(_event, option) {
|
||||
this.props.onPartyChange(option?.id ?? null);
|
||||
}
|
||||
|
||||
handleUploadClick() {
|
||||
this.fileInputRef.current?.click();
|
||||
}
|
||||
|
||||
handleFileChange(event) {
|
||||
const file = event.target.files?.[0];
|
||||
event.target.value = '';
|
||||
if (!file) return;
|
||||
this.props.onPdfUpload?.(file);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
kind,
|
||||
@@ -29,6 +46,8 @@ class ManifestHeader extends PureComponent {
|
||||
number,
|
||||
externalNumber,
|
||||
saving,
|
||||
pdfUploading,
|
||||
onPdfUpload,
|
||||
onDateChange,
|
||||
onNumberChange,
|
||||
onExternalNumberChange,
|
||||
@@ -44,13 +63,13 @@ class ManifestHeader extends PureComponent {
|
||||
|
||||
return (
|
||||
<PanelTile wide={3} minHeight={160} color={headerColor} title="Kopfdaten">
|
||||
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={2}>
|
||||
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={2} alignItems={{ xs: 'stretch', sm: 'center' }}>
|
||||
<TextField
|
||||
size="small"
|
||||
type="date"
|
||||
label={dateLabel}
|
||||
value={date}
|
||||
disabled={saving}
|
||||
disabled={saving || pdfUploading}
|
||||
onChange={(e) => onDateChange(e.target.value)}
|
||||
autoComplete="off"
|
||||
slotProps={{
|
||||
@@ -64,7 +83,7 @@ class ManifestHeader extends PureComponent {
|
||||
options={parties}
|
||||
value={selected}
|
||||
loading={Boolean(partiesLoading)}
|
||||
disabled={saving}
|
||||
disabled={saving || pdfUploading}
|
||||
getOptionLabel={(option) => partyDisplayName(kind, option)}
|
||||
isOptionEqualToValue={(option, value) => option.id === value.id}
|
||||
onChange={this.handlePartyChange}
|
||||
@@ -88,7 +107,7 @@ class ManifestHeader extends PureComponent {
|
||||
size="small"
|
||||
label="Nummer"
|
||||
value={number}
|
||||
disabled={saving || useExternalNumber}
|
||||
disabled={saving || pdfUploading || useExternalNumber}
|
||||
onChange={onNumberChange}
|
||||
sx={{ minWidth: 180, flex: 1 }}
|
||||
/>
|
||||
@@ -97,15 +116,37 @@ class ManifestHeader extends PureComponent {
|
||||
size="small"
|
||||
label="Referenz"
|
||||
value={externalNumber ?? ''}
|
||||
disabled={saving}
|
||||
disabled={saving || pdfUploading}
|
||||
onChange={onExternalNumberChange}
|
||||
sx={{ minWidth: 180, flex: 1 }}
|
||||
/>
|
||||
) : null}
|
||||
{onPdfUpload ? (
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="small"
|
||||
startIcon={pdfUploading ? <CircularProgress size={16} color="inherit" /> : <UploadFileIcon />}
|
||||
disabled={saving || pdfUploading}
|
||||
onClick={this.handleUploadClick}
|
||||
sx={{ whiteSpace: 'nowrap', height: 40, alignSelf: { xs: 'stretch', sm: 'center' }, px: 2 }}
|
||||
>
|
||||
{pdfUploading ? 'Wird analysiert…' : 'PDF hochladen'}
|
||||
</Button>
|
||||
) : null}
|
||||
</Stack>
|
||||
{onPdfUpload ? (
|
||||
<input
|
||||
ref={this.fileInputRef}
|
||||
type="file"
|
||||
accept="application/pdf,.pdf"
|
||||
style={{ display: 'none' }}
|
||||
onChange={this.handleFileChange}
|
||||
/>
|
||||
) : null}
|
||||
</PanelTile>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ManifestHeader;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user