subnav
This commit is contained in:
@@ -369,7 +369,7 @@ export const deliveryEditorAutomat = new Automat(initialState, {
|
|||||||
updateAttachment(payload) {
|
updateAttachment(payload) {
|
||||||
const { id, ...fields } = payload || {};
|
const { id, ...fields } = payload || {};
|
||||||
const current = deliveryEditorAutomat.state.attachments ?? [];
|
const current = deliveryEditorAutomat.state.attachments ?? [];
|
||||||
const idx = current.findIndex((a) => a.id === id);
|
const idx = current.findIndex((a) => a.id === id || String(a.id) === String(id));
|
||||||
if (idx !== -1) {
|
if (idx !== -1) {
|
||||||
const next = [...current];
|
const next = [...current];
|
||||||
next[idx] = { ...next[idx], ...fields };
|
next[idx] = { ...next[idx], ...fields };
|
||||||
@@ -377,14 +377,20 @@ export const deliveryEditorAutomat = new Automat(initialState, {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
removeAttachment(idOrIndex) {
|
removeAttachment(idOrPayload) {
|
||||||
const current = deliveryEditorAutomat.state.attachments ?? [];
|
const current = deliveryEditorAutomat.state.attachments ?? [];
|
||||||
|
const targetId =
|
||||||
|
typeof idOrPayload === 'object' && idOrPayload !== null
|
||||||
|
? (idOrPayload.id ?? idOrPayload.fileId)
|
||||||
|
: idOrPayload;
|
||||||
|
const hasMatch = current.some((a) => a.id === targetId || String(a.id) === String(targetId));
|
||||||
let next;
|
let next;
|
||||||
if (typeof idOrIndex === 'number') {
|
if (hasMatch) {
|
||||||
next = current.filter((_, idx) => idx !== idOrIndex);
|
next = current.filter((a) => a.id !== targetId && String(a.id) !== String(targetId));
|
||||||
|
} else if (typeof idOrPayload === 'number' && idOrPayload >= 0 && idOrPayload < current.length) {
|
||||||
|
next = current.filter((_, idx) => idx !== idOrPayload);
|
||||||
} else {
|
} else {
|
||||||
const id = typeof idOrIndex === 'string' ? idOrIndex : idOrIndex?.id;
|
next = current.filter((a) => a.id !== targetId && String(a.id) !== String(targetId));
|
||||||
next = current.filter((a) => a.id !== id);
|
|
||||||
}
|
}
|
||||||
deliveryEditorAutomat.setState({ attachments: next, files: next });
|
deliveryEditorAutomat.setState({ attachments: next, files: next });
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import List from '@mui/material/List';
|
|||||||
import ListItemButton from '@mui/material/ListItemButton';
|
import ListItemButton from '@mui/material/ListItemButton';
|
||||||
import ListItemIcon from '@mui/material/ListItemIcon';
|
import ListItemIcon from '@mui/material/ListItemIcon';
|
||||||
import ListItemText from '@mui/material/ListItemText';
|
import ListItemText from '@mui/material/ListItemText';
|
||||||
import IconButton from '@mui/material/IconButton';
|
import ButtonBase from '@mui/material/ButtonBase';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
import { Outlet, useLocation, useNavigate } from 'react-router-dom';
|
import { Outlet, useLocation, useNavigate } from 'react-router-dom';
|
||||||
import UserMenu from './UserMenu';
|
import UserMenu from './UserMenu';
|
||||||
import { NAV_ITEMS, SUB_NAV, resolveActiveNav, resolveActiveSub } from '../nav';
|
import { NAV_ITEMS, SUB_NAV, resolveActiveNav, resolveActiveSub } from '../nav';
|
||||||
@@ -12,7 +13,7 @@ import { artikelQueryPath } from '../pages/artikel/artikelQuery';
|
|||||||
import { defaultCategoryPath } from '../pages/artikel/categoryRoute';
|
import { defaultCategoryPath } from '../pages/artikel/categoryRoute';
|
||||||
|
|
||||||
const SIDEBAR_WIDTH = 144;
|
const SIDEBAR_WIDTH = 144;
|
||||||
const SUB_NAV_WIDTH = 80;
|
const SUB_NAV_WIDTH = 88;
|
||||||
|
|
||||||
class AppSidebar extends PureComponent {
|
class AppSidebar extends PureComponent {
|
||||||
render() {
|
render() {
|
||||||
@@ -110,7 +111,7 @@ class AppSubNav extends PureComponent {
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: 1,
|
gap: 1.5,
|
||||||
position: 'sticky',
|
position: 'sticky',
|
||||||
top: 0,
|
top: 0,
|
||||||
alignSelf: 'flex-start',
|
alignSelf: 'flex-start',
|
||||||
@@ -121,7 +122,7 @@ class AppSubNav extends PureComponent {
|
|||||||
const active = segment === activeSub;
|
const active = segment === activeSub;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IconButton
|
<ButtonBase
|
||||||
key={segment || 'index'}
|
key={segment || 'index'}
|
||||||
type="button"
|
type="button"
|
||||||
aria-label={label}
|
aria-label={label}
|
||||||
@@ -140,21 +141,42 @@ class AppSubNav extends PureComponent {
|
|||||||
navigate(segment ? `${activeNav}/${segment}` : activeNav);
|
navigate(segment ? `${activeNav}/${segment}` : activeNav);
|
||||||
}}
|
}}
|
||||||
sx={(theme) => ({
|
sx={(theme) => ({
|
||||||
width: 80,
|
width: SUB_NAV_WIDTH,
|
||||||
height: 80,
|
py: 1,
|
||||||
borderRadius: '50%',
|
px: 0.5,
|
||||||
|
borderRadius: 2,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: 0.5,
|
||||||
color: active
|
color: active
|
||||||
? theme.palette.primary.main
|
? theme.palette.primary.main
|
||||||
: theme.palette.text.secondary,
|
: theme.palette.text.secondary,
|
||||||
bgcolor: 'transparent',
|
bgcolor: active ? theme.palette.action.selected : 'transparent',
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
bgcolor: theme.palette.action.hover,
|
bgcolor: theme.palette.action.hover,
|
||||||
color: theme.palette.primary.main,
|
color: theme.palette.primary.main,
|
||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Icon sx={{ fontSize: 40 }} />
|
<Icon sx={{ fontSize: 32 }} />
|
||||||
</IconButton>
|
{label ? (
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
noWrap
|
||||||
|
sx={{
|
||||||
|
fontSize: '0.68rem',
|
||||||
|
fontWeight: 500,
|
||||||
|
lineHeight: 1.2,
|
||||||
|
textAlign: 'center',
|
||||||
|
maxWidth: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</Typography>
|
||||||
|
) : null}
|
||||||
|
</ButtonBase>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -68,10 +68,10 @@ export default function SectionLayout({ sectionPath }) {
|
|||||||
isArticleDetail || isKundeDetail || isLieferungDetail || isAngebotDoc;
|
isArticleDetail || isKundeDetail || isLieferungDetail || isAngebotDoc;
|
||||||
|
|
||||||
const angebotLabel =
|
const angebotLabel =
|
||||||
subNav?.find((item) => item.segment === '')?.label ?? 'Angebot';
|
subNav?.find((item) => item.segment === '')?.title ?? subNav?.find((item) => item.segment === '')?.label ?? 'Angebot';
|
||||||
let pageTitle = isDetail
|
let pageTitle = isDetail
|
||||||
? title
|
? title
|
||||||
: (activeSub?.label ?? title);
|
: (activeSub?.title ?? activeSub?.label ?? title);
|
||||||
if (pathname === '/auftrag/neu') pageTitle = 'Neues Angebot';
|
if (pathname === '/auftrag/neu') pageTitle = 'Neues Angebot';
|
||||||
else if (isAngebotDoc) pageTitle = angebotLabel;
|
else if (isAngebotDoc) pageTitle = angebotLabel;
|
||||||
else if (pathname === '/auftrag/bestellung/neu') pageTitle = 'Neue Bestellung';
|
else if (pathname === '/auftrag/bestellung/neu') pageTitle = 'Neue Bestellung';
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import ManifestLines from './ManifestLines';
|
|||||||
import ManifestPdfDialog from './ManifestPdfDialog';
|
import ManifestPdfDialog from './ManifestPdfDialog';
|
||||||
import {
|
import {
|
||||||
buildManifestBody,
|
buildManifestBody,
|
||||||
|
restoreAttachmentsFromApi,
|
||||||
dateInputValue,
|
dateInputValue,
|
||||||
emptyLine,
|
emptyLine,
|
||||||
lineFromApi,
|
lineFromApi,
|
||||||
@@ -38,6 +39,7 @@ class ManifestForm extends PureComponent {
|
|||||||
number: '',
|
number: '',
|
||||||
externalNumber: '',
|
externalNumber: '',
|
||||||
lines: [emptyLine()],
|
lines: [emptyLine()],
|
||||||
|
attachments: [],
|
||||||
saving: false,
|
saving: false,
|
||||||
pdfUploading: false,
|
pdfUploading: false,
|
||||||
pdfDialogOpen: false,
|
pdfDialogOpen: false,
|
||||||
@@ -125,6 +127,10 @@ class ManifestForm extends PureComponent {
|
|||||||
const lines =
|
const lines =
|
||||||
apiLines.length > 0 ? apiLines.map(lineFromApi) : [emptyLine()];
|
apiLines.length > 0 ? apiLines.map(lineFromApi) : [emptyLine()];
|
||||||
|
|
||||||
|
// Restore attachments if available
|
||||||
|
const apiFiles = doc.files ?? [];
|
||||||
|
const attachments = await restoreAttachmentsFromApi(apiFiles);
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
status: 'ready',
|
status: 'ready',
|
||||||
parties,
|
parties,
|
||||||
@@ -135,6 +141,7 @@ class ManifestForm extends PureComponent {
|
|||||||
externalNumber:
|
externalNumber:
|
||||||
doc.externalNumber != null ? String(doc.externalNumber) : '',
|
doc.externalNumber != null ? String(doc.externalNumber) : '',
|
||||||
lines,
|
lines,
|
||||||
|
attachments,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (this._cancelled) return;
|
if (this._cancelled) return;
|
||||||
@@ -294,13 +301,14 @@ class ManifestForm extends PureComponent {
|
|||||||
const requireAtLeastOne = mode === 'create';
|
const requireAtLeastOne = mode === 'create';
|
||||||
let body;
|
let body;
|
||||||
try {
|
try {
|
||||||
body = buildManifestBody(
|
body = await buildManifestBody(
|
||||||
{
|
{
|
||||||
number: this.state.number,
|
number: this.state.number,
|
||||||
externalNumber: this.state.externalNumber,
|
externalNumber: this.state.externalNumber,
|
||||||
date: this.state.date,
|
date: this.state.date,
|
||||||
partyId: this.state.partyId,
|
partyId: this.state.partyId,
|
||||||
lines: this.state.lines,
|
lines: this.state.lines,
|
||||||
|
attachments: this.state.attachments,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
partyField: cfg.partyField,
|
partyField: cfg.partyField,
|
||||||
|
|||||||
@@ -14,5 +14,7 @@ export {
|
|||||||
emptyLine,
|
emptyLine,
|
||||||
lineFromApi,
|
lineFromApi,
|
||||||
buildManifestBody,
|
buildManifestBody,
|
||||||
|
buildFilesForUpload,
|
||||||
|
restoreAttachmentsFromApi,
|
||||||
dateInputValue,
|
dateInputValue,
|
||||||
} from './manifestConfig';
|
} from './manifestConfig';
|
||||||
|
|||||||
@@ -130,10 +130,225 @@ export function buildLines(lines, { requireAtLeastOne }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {{ number: string, externalNumber?: string, date: string, partyId: number|null, lines: object[] }} form
|
* Convert File objects to base64 for API upload
|
||||||
|
* @param {Array<{file: File, name: string, type: string, thumbnail?: string, text?: string, parentPdfId?: string|number, isPersisted?: boolean, isDirty?: boolean}>} attachments
|
||||||
|
* @param {{ onlyNew?: boolean }} [options]
|
||||||
|
* @returns {Promise<Array<{mimetype: string, name: string, content: string, thumbavif?: string, meta?: object}>>}
|
||||||
|
*/
|
||||||
|
export async function buildFilesForUpload(attachments = [], { onlyNew = false } = {}) {
|
||||||
|
const files = [];
|
||||||
|
for (const attachment of attachments) {
|
||||||
|
if (!attachment) continue;
|
||||||
|
// Skip already persisted attachments in update mode unless modified
|
||||||
|
if (onlyNew && attachment.isPersisted && !attachment.isDirty) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (attachment.isPersisted && !attachment.isDirty && (!attachment.file || attachment.file.size === 0)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let fileToUpload = attachment.file;
|
||||||
|
if (!fileToUpload || fileToUpload.size === 0) {
|
||||||
|
if (attachment.text) {
|
||||||
|
fileToUpload = new File([attachment.text], attachment.name || 'file.txt', {
|
||||||
|
type: 'text/plain;charset=utf-8',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const base64 = await fileToBase64(fileToUpload);
|
||||||
|
const filePayload = {
|
||||||
|
mimetype: attachment.type || fileToUpload.type,
|
||||||
|
name: attachment.name || fileToUpload.name,
|
||||||
|
content: base64,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (attachment.thumbnail) {
|
||||||
|
const thumbBase64 = attachment.thumbnail.includes(',')
|
||||||
|
? attachment.thumbnail.split(',')[1]
|
||||||
|
: attachment.thumbnail;
|
||||||
|
filePayload.thumbavif = thumbBase64;
|
||||||
|
}
|
||||||
|
|
||||||
|
const meta = {};
|
||||||
|
if (attachment.thumbnail) {
|
||||||
|
meta.thumbnail = attachment.thumbnail;
|
||||||
|
}
|
||||||
|
if (attachment.text) {
|
||||||
|
meta.text = attachment.text;
|
||||||
|
}
|
||||||
|
if (attachment.parentPdfId) {
|
||||||
|
meta.parentPdfId = String(attachment.parentPdfId);
|
||||||
|
}
|
||||||
|
if (Object.keys(meta).length > 0) {
|
||||||
|
filePayload.meta = meta;
|
||||||
|
}
|
||||||
|
|
||||||
|
files.push(filePayload);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Fehler beim Konvertieren von Datei zu Base64:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert File to base64 string
|
||||||
|
* @param {File} file
|
||||||
|
* @returns {Promise<string>}
|
||||||
|
*/
|
||||||
|
function fileToBase64(file) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = () => {
|
||||||
|
const result = reader.result;
|
||||||
|
if (typeof result === 'string') {
|
||||||
|
// Remove data URL prefix (e.g., "data:application/pdf;base64,")
|
||||||
|
const base64 = result.split(',')[1] || result;
|
||||||
|
resolve(base64);
|
||||||
|
} else {
|
||||||
|
reject(new Error('Invalid file reader result'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.onerror = () => reject(new Error('File reader error'));
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert base64 string to Blob
|
||||||
|
* @param {string} base64
|
||||||
|
* @param {string} mimeType
|
||||||
|
* @returns {Blob}
|
||||||
|
*/
|
||||||
|
function base64ToBlob(base64, mimeType) {
|
||||||
|
const clean = base64.includes(',') ? base64.split(',')[1] : base64;
|
||||||
|
const byteCharacters = atob(clean);
|
||||||
|
const byteArrays = [];
|
||||||
|
const sliceSize = 512;
|
||||||
|
|
||||||
|
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
|
||||||
|
const slice = byteCharacters.slice(offset, offset + sliceSize);
|
||||||
|
const byteNumbers = new Array(slice.length);
|
||||||
|
for (let i = 0; i < slice.length; i++) {
|
||||||
|
byteNumbers[i] = byteCharacters.charCodeAt(i);
|
||||||
|
}
|
||||||
|
const byteArray = new Uint8Array(byteNumbers);
|
||||||
|
byteArrays.push(byteArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Blob(byteArrays, { type: mimeType });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restore attachments from API response
|
||||||
|
* @param {Array<{id: number, fileId?: number, manifestId?: number, mimetype: string, name: string, thumbavif?: any, size: number, created?: string, meta?: object}>} apiFiles
|
||||||
|
* @param {Array<object>} [existingAttachments]
|
||||||
|
* @returns {Promise<Array<{id: number, fileId: number, manifestId: number, file: File, name: string, type: string, thumbnail: string|null, size: number, text: string|null, parentPdfId: string|null, isPersisted: boolean}>>}
|
||||||
|
*/
|
||||||
|
export async function restoreAttachmentsFromApi(apiFiles = [], existingAttachments = []) {
|
||||||
|
const existingMap = new Map();
|
||||||
|
for (const existing of existingAttachments) {
|
||||||
|
if (!existing) continue;
|
||||||
|
if (existing.id != null) existingMap.set(String(existing.id), existing);
|
||||||
|
if (existing.fileId != null) existingMap.set(String(existing.fileId), existing);
|
||||||
|
if (existing.name) existingMap.set(existing.name, existing);
|
||||||
|
}
|
||||||
|
|
||||||
|
const attachments = [];
|
||||||
|
for (const apiFile of apiFiles) {
|
||||||
|
try {
|
||||||
|
let file = null;
|
||||||
|
let thumbnail = null;
|
||||||
|
|
||||||
|
// 1. Try to restore thumbnail from apiFile.thumbavif
|
||||||
|
if (apiFile.thumbavif) {
|
||||||
|
try {
|
||||||
|
if (typeof apiFile.thumbavif === 'string') {
|
||||||
|
if (apiFile.thumbavif.startsWith('data:')) {
|
||||||
|
thumbnail = apiFile.thumbavif;
|
||||||
|
} else {
|
||||||
|
const thumbBlob = base64ToBlob(apiFile.thumbavif, 'image/avif');
|
||||||
|
thumbnail = URL.createObjectURL(thumbBlob);
|
||||||
|
}
|
||||||
|
} else if (typeof apiFile.thumbavif === 'object') {
|
||||||
|
const rawBytes = apiFile.thumbavif.data || apiFile.thumbavif;
|
||||||
|
if (Array.isArray(rawBytes) || rawBytes instanceof Uint8Array) {
|
||||||
|
const thumbBlob = new Blob([new Uint8Array(rawBytes)], { type: 'image/avif' });
|
||||||
|
thumbnail = URL.createObjectURL(thumbBlob);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (thumbErr) {
|
||||||
|
console.error('Fehler beim Dekodieren von thumbavif:', thumbErr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Check meta.thumbnail if thumbavif wasn't available
|
||||||
|
if (!thumbnail && apiFile.meta?.thumbnail) {
|
||||||
|
try {
|
||||||
|
const metaThumb = apiFile.meta.thumbnail;
|
||||||
|
if (typeof metaThumb === 'string') {
|
||||||
|
if (metaThumb.startsWith('data:') || metaThumb.startsWith('blob:')) {
|
||||||
|
thumbnail = metaThumb;
|
||||||
|
} else {
|
||||||
|
const thumbBlob = base64ToBlob(metaThumb, 'image/avif');
|
||||||
|
thumbnail = URL.createObjectURL(thumbBlob);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (metaThumbErr) {
|
||||||
|
console.error('Fehler beim Dekodieren von meta.thumbnail:', metaThumbErr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Fallback to existing matching attachment thumbnail
|
||||||
|
const matchingExisting =
|
||||||
|
existingMap.get(String(apiFile.id)) ||
|
||||||
|
existingMap.get(String(apiFile.fileId)) ||
|
||||||
|
existingMap.get(apiFile.name);
|
||||||
|
|
||||||
|
if (!thumbnail && matchingExisting?.thumbnail) {
|
||||||
|
thumbnail = matchingExisting.thumbnail;
|
||||||
|
}
|
||||||
|
|
||||||
|
const placeholderName = apiFile.name || 'file';
|
||||||
|
const placeholderType = apiFile.mimetype || 'application/octet-stream';
|
||||||
|
file =
|
||||||
|
matchingExisting?.file instanceof File && matchingExisting.file.size > 0
|
||||||
|
? matchingExisting.file
|
||||||
|
: new File([''], placeholderName, { type: placeholderType });
|
||||||
|
|
||||||
|
const text = apiFile.meta?.text || matchingExisting?.text || null;
|
||||||
|
const parentPdfId = apiFile.meta?.parentPdfId || matchingExisting?.parentPdfId || null;
|
||||||
|
|
||||||
|
attachments.push({
|
||||||
|
id: apiFile.id,
|
||||||
|
fileId: apiFile.fileId ?? apiFile.id,
|
||||||
|
manifestId: apiFile.manifestId,
|
||||||
|
isPersisted: true,
|
||||||
|
file,
|
||||||
|
name: apiFile.name,
|
||||||
|
type: apiFile.mimetype,
|
||||||
|
thumbnail,
|
||||||
|
size: apiFile.size,
|
||||||
|
text,
|
||||||
|
parentPdfId,
|
||||||
|
uploadedAt: apiFile.created,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Fehler beim Wiederherstellen von Datei:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return attachments;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{ number: string, externalNumber?: string, date: string, partyId: number|null, lines: object[], attachments?: object[] }} form
|
||||||
* @param {{ partyField: 'supplierId'|'entityId', requireAtLeastOne: boolean, externalNumber?: boolean }} opts
|
* @param {{ partyField: 'supplierId'|'entityId', requireAtLeastOne: boolean, externalNumber?: boolean }} opts
|
||||||
*/
|
*/
|
||||||
export function buildManifestBody(
|
export async function buildManifestBody(
|
||||||
form,
|
form,
|
||||||
{ partyField, requireAtLeastOne, externalNumber: useExternalNumber },
|
{ partyField, requireAtLeastOne, externalNumber: useExternalNumber },
|
||||||
) {
|
) {
|
||||||
@@ -161,6 +376,16 @@ export function buildManifestBody(
|
|||||||
}
|
}
|
||||||
if (form.partyId != null) body[partyField] = form.partyId;
|
if (form.partyId != null) body[partyField] = form.partyId;
|
||||||
else if (!requireAtLeastOne) body[partyField] = null;
|
else if (!requireAtLeastOne) body[partyField] = null;
|
||||||
|
|
||||||
|
// Add files if attachments exist
|
||||||
|
if (form.attachments && form.attachments.length > 0) {
|
||||||
|
const isUpdate = !requireAtLeastOne;
|
||||||
|
const filesToUpload = await buildFilesForUpload(form.attachments, { onlyNew: isUpdate });
|
||||||
|
if (filesToUpload.length > 0) {
|
||||||
|
body.files = filesToUpload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
14
src/nav.js
14
src/nav.js
@@ -24,13 +24,13 @@ export const NAV_ITEMS = [
|
|||||||
|
|
||||||
export const SUB_NAV = {
|
export const SUB_NAV = {
|
||||||
'/artikel': [
|
'/artikel': [
|
||||||
{ segment: '', icon: ViewListIcon },
|
{ segment: '', label: 'Liste', title: 'Artikel', icon: ViewListIcon },
|
||||||
{ segment: 'kategorien', label: 'Kategorien', icon: CategoryIcon },
|
{ segment: 'kategorien', label: 'Kategorieen', title: 'Kategorien', icon: CategoryIcon },
|
||||||
{ segment: 'lager', label: 'Lager', icon: WarehouseIcon },
|
{ segment: 'lager', label: 'Lager', icon: WarehouseIcon },
|
||||||
],
|
],
|
||||||
'/lieferung': [
|
'/lieferung': [
|
||||||
{ segment: '', label: 'Lieferungen', icon: ListAltIcon },
|
{ segment: '', label: 'Liste', title: 'Lieferungen', icon: ListAltIcon },
|
||||||
{ segment: 'neu', label: 'Neue Lieferung', icon: AddBoxIcon },
|
{ segment: 'neu', label: 'Neu', title: 'Neue Lieferung', icon: AddBoxIcon },
|
||||||
{ segment: 'lieferanten', label: 'Lieferanten', icon: StorefrontIcon },
|
{ segment: 'lieferanten', label: 'Lieferanten', icon: StorefrontIcon },
|
||||||
],
|
],
|
||||||
'/auftrag': [
|
'/auftrag': [
|
||||||
@@ -38,9 +38,9 @@ export const SUB_NAV = {
|
|||||||
{ segment: 'bestellung', label: 'Bestellung', icon: ShoppingCartIcon },
|
{ segment: 'bestellung', label: 'Bestellung', icon: ShoppingCartIcon },
|
||||||
],
|
],
|
||||||
'/kunde': [
|
'/kunde': [
|
||||||
{ segment: '', label: 'Kundenliste', icon: PeopleIcon },
|
{ segment: '', label: 'Liste', title: 'Kundenliste', icon: PeopleIcon },
|
||||||
{ segment: 'neu', label: 'Neuer Kunde', icon: AddBoxIcon },
|
{ segment: 'neu', label: 'neu', title: 'Neuer Kunde', icon: AddBoxIcon },
|
||||||
{ segment: 'gruppen', label: 'Kundengruppen', icon: GroupsIcon },
|
{ segment: 'gruppen', label: 'Gruppen', icon: GroupsIcon },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
ManifestLines,
|
ManifestLines,
|
||||||
DELIVERY_LINE_COLUMNS,
|
DELIVERY_LINE_COLUMNS,
|
||||||
buildManifestBody,
|
buildManifestBody,
|
||||||
|
restoreAttachmentsFromApi,
|
||||||
dateInputValue,
|
dateInputValue,
|
||||||
emptyLine,
|
emptyLine,
|
||||||
lineFromApi,
|
lineFromApi,
|
||||||
@@ -160,6 +161,11 @@ class LieferungNeuPage extends PureComponent {
|
|||||||
if (doc.externalNumber != null) deliveryEditorAutomat.actions.setExternalNumber(String(doc.externalNumber));
|
if (doc.externalNumber != null) deliveryEditorAutomat.actions.setExternalNumber(String(doc.externalNumber));
|
||||||
deliveryEditorAutomat.actions.setLineitems(lines);
|
deliveryEditorAutomat.actions.setLineitems(lines);
|
||||||
|
|
||||||
|
// Restore attachments with thumbnails
|
||||||
|
const apiFiles = doc.files ?? [];
|
||||||
|
const attachments = await restoreAttachmentsFromApi(apiFiles, this.state.attachments);
|
||||||
|
deliveryEditorAutomat.actions.setAttachments(attachments);
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
status: 'ready',
|
status: 'ready',
|
||||||
partiesLoading: false,
|
partiesLoading: false,
|
||||||
@@ -573,8 +579,10 @@ class LieferungNeuPage extends PureComponent {
|
|||||||
textEditorAttachment: attachment,
|
textEditorAttachment: attachment,
|
||||||
});
|
});
|
||||||
} else if (attachment.file instanceof Blob || attachment.file instanceof File) {
|
} else if (attachment.file instanceof Blob || attachment.file instanceof File) {
|
||||||
const url = URL.createObjectURL(attachment.file);
|
if (attachment.file.size > 0) {
|
||||||
window.open(url, '_blank');
|
const url = URL.createObjectURL(attachment.file);
|
||||||
|
window.open(url, '_blank');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -614,13 +622,14 @@ class LieferungNeuPage extends PureComponent {
|
|||||||
const isCreate = docId == null;
|
const isCreate = docId == null;
|
||||||
let body;
|
let body;
|
||||||
try {
|
try {
|
||||||
body = buildManifestBody(
|
body = await buildManifestBody(
|
||||||
{
|
{
|
||||||
number: this.state.number ?? '',
|
number: this.state.number ?? '',
|
||||||
externalNumber: this.state.externalNumber ?? '',
|
externalNumber: this.state.externalNumber ?? '',
|
||||||
date: this.state.date ?? '',
|
date: this.state.date ?? '',
|
||||||
partyId: this.state.partyId,
|
partyId: this.state.partyId,
|
||||||
lines: this.state.lines,
|
lines: this.state.lines,
|
||||||
|
attachments: this.state.attachments,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
partyField: cfg.partyField,
|
partyField: cfg.partyField,
|
||||||
@@ -663,6 +672,11 @@ class LieferungNeuPage extends PureComponent {
|
|||||||
if (doc.externalNumber != null) deliveryEditorAutomat.actions.setExternalNumber(String(doc.externalNumber));
|
if (doc.externalNumber != null) deliveryEditorAutomat.actions.setExternalNumber(String(doc.externalNumber));
|
||||||
deliveryEditorAutomat.actions.setLineitems(lines);
|
deliveryEditorAutomat.actions.setLineitems(lines);
|
||||||
|
|
||||||
|
// Restore attachments with thumbnails
|
||||||
|
const apiFiles = doc.files ?? [];
|
||||||
|
const attachments = await restoreAttachmentsFromApi(apiFiles, this.state.attachments);
|
||||||
|
deliveryEditorAutomat.actions.setAttachments(attachments);
|
||||||
|
|
||||||
enqueueSnackbar(cfg.savedMsg, { variant: 'success' });
|
enqueueSnackbar(cfg.savedMsg, { variant: 'success' });
|
||||||
this.setState({ saving: false });
|
this.setState({ saving: false });
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user