From e2a2d02cb4f13e09ef6873655fa925d6ff1bd31e Mon Sep 17 00:00:00 2001 From: seb Date: Wed, 14 Oct 2026 23:24:45 +0200 Subject: [PATCH] u --- src/api/delivery.js | 13 ++ src/api/order.js | 13 ++ src/automats/manifestDraftAutomat.js | 12 +- src/components/AppShell.jsx | 140 +++++++++++++++------ src/components/UserMenu.jsx | 24 +++- src/components/chat/ChatFab.jsx | 2 +- src/components/manifest/ManifestForm.jsx | 36 ++---- src/components/manifest/ManifestHeader.jsx | 1 + 8 files changed, 170 insertions(+), 71 deletions(-) diff --git a/src/api/delivery.js b/src/api/delivery.js index d57c3dc..0a9d23d 100644 --- a/src/api/delivery.js +++ b/src/api/delivery.js @@ -84,6 +84,19 @@ export const delivery = { }, // #endregion + /** + * DELETE /api/delivery/:id/file/:fileId + * Deletes delivery file attachment. + * @param {number|string} id Delivery ID + * @param {number|string} fileId File ID + * → 204 void + */ + // #region DELETE /api/delivery/:id/file/:fileId — deleteFile + deleteFile(id, fileId) { + return http(`/api/delivery/${id}/file/${fileId}`, { method: 'DELETE' }); + }, + // #endregion + /** * POST /api/delivery/parse — multipart/form-data, field "file" * Accepts PDF, TXT or CSV document (max 50 MB) diff --git a/src/api/order.js b/src/api/order.js index e56d412..419aaa9 100644 --- a/src/api/order.js +++ b/src/api/order.js @@ -62,4 +62,17 @@ export const order = { return http(`/api/order/${id}`, { method: 'DELETE' }); }, // #endregion + + /** + * DELETE /api/order/:id/file/:fileId + * Deletes order file attachment. + * @param {number|string} id Order ID + * @param {number|string} fileId File ID + * → 204 void + */ + // #region DELETE /api/order/:id/file/:fileId — deleteFile + deleteFile(id, fileId) { + return http(`/api/order/${id}/file/${fileId}`, { method: 'DELETE' }); + }, + // #endregion }; diff --git a/src/automats/manifestDraftAutomat.js b/src/automats/manifestDraftAutomat.js index 1be1db6..c5feea7 100644 --- a/src/automats/manifestDraftAutomat.js +++ b/src/automats/manifestDraftAutomat.js @@ -363,7 +363,7 @@ export function getManifestDirtyDetails(current, baseline) { const count = dirtyLineKeys.length; summary.push(count > 0 ? `${count} Artikelzeile${count === 1 ? '' : 'n'}` : 'Artikelzeilen'); } - if (fields.attachments) summary.push('Dateien'); + if (fields.attachments && current.kind !== 'quote') summary.push('Dateien'); const isDirty = summary.length > 0; @@ -455,6 +455,16 @@ export function createManifestEditAutomat({ kind, name }) { }); }, + setAttachments(attachments) { + const list = Array.isArray(attachments) ? attachments : []; + const base = automat.state.baseline; + const nextBaseline = base ? { ...base, attachments: [...list] } : null; + automat.setState({ + attachments: list, + baseline: nextBaseline, + }); + }, + restoreBaseline() { if (!automat.state.baseline) return; const base = automat.state.baseline; diff --git a/src/components/AppShell.jsx b/src/components/AppShell.jsx index b3a5610..82e1fe4 100644 --- a/src/components/AppShell.jsx +++ b/src/components/AppShell.jsx @@ -66,19 +66,37 @@ class AppSidebar extends PureComponent { ({ - width: SIDEBAR_WIDTH, + width: { xs: '100%', lg: SIDEBAR_WIDTH }, flexShrink: 0, display: 'flex', - flexDirection: 'column', + flexDirection: { xs: 'row', lg: 'column' }, + alignItems: { xs: 'center', lg: 'stretch' }, backgroundColor: theme.nav.backgroundColor || NAV_SURFACE, background: theme.nav.background, position: 'sticky', top: 0, + zIndex: theme.zIndex.appBar || 1100, alignSelf: 'flex-start', - height: 'var(--df-vh, 100vh)', + height: { xs: 52, sm: 56, lg: 'var(--df-vh, 100vh)' }, + px: { xs: 1, sm: 1.5, lg: 0 }, + boxSizing: 'border-box', + boxShadow: { xs: '0 2px 8px rgba(0,0,0,0.18)', lg: 'none' }, })} > - + {NAV_ITEMS.map(({ path, label, help, icon: Icon }) => { const active = path === activeNav; @@ -110,12 +128,21 @@ class AppSidebar extends PureComponent { } }} sx={(theme) => ({ - borderRadius: 0, - px: 1.5, - py: 1.25, - borderLeft: '3px solid transparent', + borderRadius: { xs: 1, lg: 0 }, + width: { xs: 'auto', lg: '100%' }, + height: { xs: '100%', lg: 'auto' }, + px: { xs: 1.25, sm: 1.5 }, + py: { xs: 0.75, sm: 1, lg: 1.25 }, + borderLeft: { xs: 'none', lg: '3px solid transparent' }, + borderBottom: { xs: '3px solid transparent', lg: 'none' }, color: theme.nav.item, - '& .MuiListItemIcon-root': { color: 'inherit' }, + whiteSpace: 'nowrap', + flexShrink: 0, + '& .MuiListItemIcon-root': { + color: 'inherit', + minWidth: { xs: 'auto', lg: 36 }, + mr: { xs: 0.75, lg: 0 }, + }, '&:hover': { bgcolor: theme.nav.itemHover, color: theme.nav.itemHoverText, @@ -124,7 +151,8 @@ class AppSidebar extends PureComponent { bgcolor: theme.nav.itemActive, color: theme.nav.itemActiveText, fontWeight: 600, - borderLeftColor: theme.nav.itemActiveAccent, + borderLeftColor: { xs: 'transparent', lg: theme.nav.itemActiveAccent }, + borderBottomColor: { xs: theme.nav.itemActiveAccent, lg: 'transparent' }, '&:hover': { bgcolor: theme.nav.itemActive, color: theme.nav.itemActiveText, @@ -132,18 +160,28 @@ class AppSidebar extends PureComponent { }, })} > - + - + ); })} - + + + ); } @@ -187,10 +225,11 @@ class AppSubNav extends PureComponent { return ( ); @@ -201,19 +240,23 @@ class AppSubNav extends PureComponent { component="nav" aria-label="Unternavigation" sx={{ - width: SUB_NAV_WIDTH, + width: { xs: 60, sm: 72, md: SUB_NAV_WIDTH }, flexShrink: 0, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 1.5, position: 'sticky', - top: 0, + top: { xs: 52, sm: 56, lg: 0 }, alignSelf: 'flex-start', - height: 'var(--df-vh, 100vh)', - py: 4, - ml: 3, - mr: 2, + height: { + xs: 'calc(var(--df-vh, 100vh) - 52px)', + sm: 'calc(var(--df-vh, 100vh) - 56px)', + lg: 'var(--df-vh, 100vh)', + }, + py: { xs: 2, lg: 4 }, + ml: { xs: 1, sm: 2, lg: 3 }, + mr: { xs: 1, sm: 1.5, lg: 2 }, }} > {subNav.map(({ segment, label, title, help, icon: Icon }) => { @@ -247,7 +290,7 @@ class AppSubNav extends PureComponent { navigate(segment ? `${activeNav}/${segment}` : activeNav); }} sx={(theme) => ({ - width: SUB_NAV_WIDTH, + width: { xs: 60, sm: 72, md: SUB_NAV_WIDTH }, py: 1, px: 0.5, borderRadius: 2, @@ -271,14 +314,14 @@ class AppSubNav extends PureComponent { })} > - + {label ? ( + - ({ - flexGrow: 1, + sx={{ display: 'flex', - flexDirection: 'column', - py: 4, - pr: 3, - bgcolor: theme.nav.mainBackground, + flexDirection: 'row', + flexGrow: 1, minWidth: 0, - })} + }} > - + + ({ + flexGrow: 1, + display: 'flex', + flexDirection: 'column', + py: { xs: 2, md: 3, lg: 4 }, + pr: { xs: 1.5, sm: 2, lg: 3 }, + pl: { xs: 1.5, sm: 2, lg: 0 }, + bgcolor: theme.nav.mainBackground, + minWidth: 0, + })} + > + + diff --git a/src/components/UserMenu.jsx b/src/components/UserMenu.jsx index 87d1592..8baad42 100644 --- a/src/components/UserMenu.jsx +++ b/src/components/UserMenu.jsx @@ -110,9 +110,21 @@ class UserMenu extends PureComponent { return `data:${mime};base64,${pic}`; })(); + const isTop = Boolean( + anchorEl && typeof anchorEl.getBoundingClientRect === 'function' && anchorEl.getBoundingClientRect().top < 200 + ); + return ( <> - + {pwaUpdateAvailable && ( diff --git a/src/components/chat/ChatFab.jsx b/src/components/chat/ChatFab.jsx index cd48ff5..539ad3a 100644 --- a/src/components/chat/ChatFab.jsx +++ b/src/components/chat/ChatFab.jsx @@ -73,7 +73,7 @@ export default class ChatFab extends PureComponent { sx={(theme) => ({ position: 'fixed', bottom: { xs: 20, sm: 28 }, - left: { xs: 20, sm: 164 }, + left: { xs: 20, lg: 164 }, zIndex: theme.zIndex.speedDial || 1050, })} > diff --git a/src/components/manifest/ManifestForm.jsx b/src/components/manifest/ManifestForm.jsx index 2cb4984..7bb56bf 100644 --- a/src/components/manifest/ManifestForm.jsx +++ b/src/components/manifest/ManifestForm.jsx @@ -220,15 +220,13 @@ class ManifestForm extends PureComponent { prevState.date !== this.state.date || prevState.partyId !== this.state.partyId || prevState.externalNumber !== this.state.externalNumber || - prevState.lines !== this.state.lines || - prevState.attachments !== this.state.attachments + prevState.lines !== this.state.lines ) { this.editAutomat.actions.update({ date: this.state.date, partyId: this.state.partyId, externalNumber: this.state.externalNumber, lines: this.state.lines, - attachments: this.state.attachments, }); const { isDirty, dirtyFields, dirtyLineKeys, dirtyLineFields, dirtySummary } = this.editAutomat.state; if ( @@ -809,18 +807,13 @@ class ManifestForm extends PureComponent { this.setState({ pdfDialogOpen: false, pdfUploading: false }); } - async handleCreatePdf() { + async handleCreatePdf(e) { + e?.preventDefault?.(); + e?.stopPropagation?.(); const { docId } = this.props; if (this.state.pdfCreating || this.state.saving) return; if (docId == null) return; - if (this.state.isDirty) { - await this.handleSave(); - if (this.state.isDirty) { - return; - } - } - this.setState({ pdfCreating: true }); try { const res = await quoteApi.createPdf(docId); @@ -834,20 +827,11 @@ class ManifestForm extends PureComponent { ); this.setState({ attachments }); if (this.editAutomat) { - this.editAutomat.actions.updateDraft({ attachments }); + this.editAutomat.actions.setAttachments?.(attachments); } } enqueueSnackbar('Angebot-PDF erfolgreich erstellt.', { variant: 'success' }); - - const fileUrl = - res.fileUrl || - (res.attachment?.fileId - ? `/api/quote/${docId}/file/${res.attachment.fileId}` - : null); - if (fileUrl) { - window.open(fileUrl, '_blank'); - } } catch (err) { if (!this._cancelled) { notifyError(err); @@ -861,23 +845,23 @@ class ManifestForm extends PureComponent { async handleAttachmentDelete(attachment) { if (!attachment) return; - const targetId = attachment.id ?? attachment.fileId; + const fileId = attachment.fileId ?? attachment.id; const { docId } = this.props; const nextAttachments = (this.state.attachments || []).filter( - (a) => (a.id ?? a.fileId) !== targetId && a !== attachment, + (a) => (a.fileId ?? a.id) !== fileId && a !== attachment, ); this.setState({ attachments: nextAttachments }); if (this.editAutomat) { - this.editAutomat.actions.updateDraft({ attachments: nextAttachments }); + this.editAutomat.actions.setAttachments?.(nextAttachments); } - if (docId != null && targetId != null && attachment.isPersisted) { + if (docId != null && fileId != null && attachment.isPersisted) { try { const api = apiFor(this.cfg); if (typeof api.deleteFile === 'function') { - await api.deleteFile(docId, targetId); + await api.deleteFile(docId, fileId); enqueueSnackbar('Anhang gelöscht.', { variant: 'success' }); } } catch (err) { diff --git a/src/components/manifest/ManifestHeader.jsx b/src/components/manifest/ManifestHeader.jsx index c31acfb..710b914 100644 --- a/src/components/manifest/ManifestHeader.jsx +++ b/src/components/manifest/ManifestHeader.jsx @@ -525,6 +525,7 @@ class ManifestHeader extends PureComponent { )} {kind === 'quote' && onPdfCreate && docId != null && Boolean(number) && (