diff --git a/bootstrap/bootstrap.mjs b/bootstrap/bootstrap.mjs index 82e4c65..d71b75d 100644 --- a/bootstrap/bootstrap.mjs +++ b/bootstrap/bootstrap.mjs @@ -277,6 +277,7 @@ const collections = [ f('needs_envelope', 'boolean', { interface: 'boolean', default: false }), dropdown('envelope_labeling', ['empfaenger', 'freitext', 'keine']), f('envelope_text', 'text', { interface: 'input-multiline', note: 'Individueller Kuverttext (bei envelope_labeling = freitext).' }), + f('envelope_sender', 'text', { interface: 'input-multiline', note: 'Absender fürs Kuvert (oben links, 5 Zeilen); manuelle Aufträge. Empfänger rutscht dann unten rechts.' }), dropdown('envelope_format', ['c6', 'dinlang'], { note: 'Automatisch aus Format.' }), dropdown('motif_mode', ['katalog', 'upload', 'ohne', 'eigene_karten']), m2o('motif', 'motifs'), diff --git a/extensions/directus-extension-skrift-neuauftrag/dist/index.js b/extensions/directus-extension-skrift-neuauftrag/dist/index.js index 3cf4225..64418f2 100644 --- a/extensions/directus-extension-skrift-neuauftrag/dist/index.js +++ b/extensions/directus-extension-skrift-neuauftrag/dist/index.js @@ -78,14 +78,14 @@ const NeuAuftrag = defineComponent({ // Mehrseitiges Schriftstück (Auto-Fluss über mehrere A4-Seiten). mehrseitig: false, seitenInfo: '', // Kuvert-Adresse je Empfänger: '5zeilen' (5 Zeilen) oder 'individuell' (einzeilig). - adressModus: '5zeilen', kuverts: false, realistic: true, customer_email: '', text: '', rows: [leereZeile()], + adressModus: '5zeilen', kuverts: false, envelopeSender: '', realistic: true, customer_email: '', text: '', rows: [leereZeile()], importOpen: false, importText: '', docs: [], previewSession: null, pLaedt: false, msg: '', err: '', hinweis: '', lb: '', lbDoc: null, }); // Kuverts nur generieren, wenn angehakt (Beschriftung aus der Empfängeradresse). const kuvertPayload = () => (neu.kuverts - ? { needs_envelope: true, envelope_labeling: 'empfaenger', envelope_text: '' } - : { needs_envelope: false, envelope_labeling: 'keine', envelope_text: '' }); + ? { needs_envelope: true, envelope_labeling: 'empfaenger', envelope_text: '', envelope_sender: (neu.envelopeSender || '').trim() } + : { needs_envelope: false, envelope_labeling: 'keine', envelope_text: '', envelope_sender: '' }); async function laden() { try { @@ -109,6 +109,35 @@ const NeuAuftrag = defineComponent({ try { verbotenRe.value = wl ? new RegExp(`[^${wl}\\n\\r\\t]`, 'g') : null; } catch { verbotenRe.value = null; } neu.product = produkte.value[0]?.key || 'briefe'; neu.format = neuFormate()[0]?.key || ''; + // Übernahme aus dem Produktions-Board (localStorage): Text/Format/Schrift + // und alle Empfänger vorbefüllen, danach den Schlüssel wieder entfernen. + try { + const raw = localStorage.getItem('skrift_neuauftrag_uebernahme'); + if (raw) { + localStorage.removeItem('skrift_neuauftrag_uebernahme'); + const d = JSON.parse(raw); + if (d && typeof d === 'object') { + if (d.product) neu.product = d.product; + if (d.format) neu.format = d.format; + if (d.font) neu.font = d.font; + if (typeof d.text === 'string') neu.text = d.text; + if (typeof d.multi_page === 'boolean') neu.mehrseitig = d.multi_page; + if (typeof d.realistic === 'boolean') neu.realistic = d.realistic; + if (d.fontSizeLetter) neu.fontSizeLetter = d.fontSizeLetter; + if (d.fontSizeEnvelope) neu.fontSizeEnvelope = d.fontSizeEnvelope; + if (typeof d.kuverts === 'boolean') neu.kuverts = d.kuverts; + if (d.adressModus) neu.adressModus = d.adressModus; + if (Array.isArray(d.rows) && d.rows.length) { + neu.rows = d.rows.map((r) => ({ + free_text: String(r.free_text || ''), + placeholders: (r.placeholders && typeof r.placeholders === 'object') ? { ...r.placeholders } : {}, + })); + } + neu.previewSession = null; + neu.msg = 'Aus Auftrag übernommen – anpassen, dann Vorschau/Anlegen.'; + } + } + } catch { /* localStorage nicht verfügbar → ignorieren */ } } catch (e) { neu.err = e?.response?.data?.errors?.[0]?.message || e?.message || 'Laden fehlgeschlagen.'; } finally { geladen.value = true; } @@ -237,7 +266,7 @@ const NeuAuftrag = defineComponent({ const kv = kuvertPayload(); const res = await api.post('/skrift-orders/preview', { text: neu.text, font: neu.font, format: neu.format, realistic: neu.realistic, - envelope_labeling: kv.envelope_labeling, envelope_text: kv.envelope_text, + envelope_labeling: kv.envelope_labeling, envelope_text: kv.envelope_text, envelope_sender: kv.envelope_sender, font_scale_letter: neu.fontSizeLetter / 100, font_scale_envelope: neu.fontSizeEnvelope / 100, multi_page: neu.mehrseitig, ...briefSigPayload(), @@ -278,6 +307,33 @@ const NeuAuftrag = defineComponent({ neu.err = e?.response?.data?.error || e?.message || 'Seitenzählung fehlgeschlagen.'; } } + // Das Backend generiert die Dokumente asynchron (fire-and-forget). Fehler + // landen als status_history-Eintrag 'fehler' am Auftrag, Erfolg als 'in_queue'. + // Wir pollen kurz und zeigen ein Problem DIREKT hier im Dashboard – statt dass + // es still verschluckt wird und „nie Dokumente" entstehen. + async function ueberwacheGenerierung(orderId, nr) { + for (let i = 0; i < 20; i += 1) { + await new Promise((r) => setTimeout(r, 1500)); + let sh = []; + try { + sh = (await api.get('/items/status_history', { params: { + 'filter[order][_eq]': orderId, sort: '-date_created', limit: 4, fields: 'status,note', + } })).data?.data || []; + } catch { continue; } + const fehler = sh.find((s) => s.status === 'fehler'); + if (fehler) { + neu.err = `Generierung fehlgeschlagen: ${fehler.note || 'unbekannter Grund'}`; + neu.msg = ''; + return; + } + if (sh.some((s) => s.status === 'in_queue')) { + neu.msg = `✓ Auftrag ${nr} angelegt & generiert – liegt im Produktions-Board unter „Neu". Felder bleiben stehen.`; + return; + } + } + // Kein eindeutiges Signal (langsame Generierung oder Datei-Produkt ohne Kuvert). + neu.msg = `✓ Auftrag ${nr} angelegt – Dokumente werden noch generiert. Status im Produktions-Board prüfen.`; + } async function absenden() { // Empfänger sind optional – ohne Empfänger genau ein Schriftstück (Blankozeile). const ent = entries(); @@ -291,16 +347,19 @@ const NeuAuftrag = defineComponent({ font_scale_letter: neu.fontSizeLetter / 100, font_scale_envelope: neu.fontSizeEnvelope / 100, multi_page: neu.mehrseitig, ...briefSigPayload(), - needs_envelope: kv.needs_envelope, envelope_labeling: kv.envelope_labeling, envelope_text: kv.envelope_text, + needs_envelope: kv.needs_envelope, envelope_labeling: kv.envelope_labeling, envelope_text: kv.envelope_text, envelope_sender: kv.envelope_sender, shipping_type: 'sammel', customer_email: neu.customer_email.trim() || undefined, text_template: neu.text, preview_session: neu.previewSession || undefined, entries: payloadEntries, }); const nr = res.data?.order_number || ''; + const oid = res.data?.id; // Felder bewusst NICHT zurücksetzen – der letzte Auftrag bleibt stehen, // damit er sich als Vorlage für den nächsten leicht anpassen lässt. - neu.msg = `✓ Auftrag ${nr} angelegt – liegt im Produktions-Board unter „Neu". Felder bleiben stehen; mit „Formular leeren" zurücksetzen.`; + neu.msg = `✓ Auftrag ${nr} angelegt – Dokumente werden generiert …`; + // Generierung im Hintergrund überwachen; ein Backend-Fehler erscheint dann hier. + if (oid) ueberwacheGenerierung(oid, nr); } catch (e) { neu.err = e?.response?.data?.error || e?.message || 'Anlegen fehlgeschlagen.'; neu.msg = ''; } } function zuruecksetzen() { @@ -308,7 +367,7 @@ const NeuAuftrag = defineComponent({ product: produkte.value[0]?.key || 'briefe', format: neuFormate()[0]?.key || '', font: 'tilda', fontSizeLetter: 100, fontSizeEnvelope: 100, sigSvg: '', sigLabel: '', sigX: 0, sigY: 0, sigScale: 100, mehrseitig: false, seitenInfo: '', - adressModus: '5zeilen', kuverts: false, realistic: true, customer_email: '', text: '', rows: [leereZeile()], + adressModus: '5zeilen', kuverts: false, envelopeSender: '', realistic: true, customer_email: '', text: '', rows: [leereZeile()], importOpen: false, importText: '', docs: [], previewSession: null, pLaedt: false, msg: '', err: '', hinweis: '', lb: '', }); @@ -449,6 +508,17 @@ const NeuAuftrag = defineComponent({ h('input', { type: 'checkbox', checked: neu.kuverts, onChange: (e) => { neu.kuverts = e.target.checked; neu.previewSession = null; }, style: 'width:16px;height:16px;' }), 'Kuverts generieren', ]), + h('div', { style: 'margin-top:14px;' }, [ + label('Absender aufs Kuvert (oben links, max. 5 Zeilen, optional)'), + h('textarea', { + value: neu.envelopeSender, rows: 5, + placeholder: 'Max Mustermann\nMusterstraße 1\n12345 Musterstadt', + style: WFELD + 'width:100%;font-family:var(--family-monospace,monospace);', + onInput: (e) => { neu.envelopeSender = saeubere(String(e.target.value)).split('\n').slice(0, 5).join('\n'); neu.previewSession = null; }, + }), + h('div', { style: 'font-size:11px;color:var(--foreground-subdued,#888);margin-top:4px;line-height:1.5;' }, + 'Ist ein Absender gesetzt, rutscht der Empfänger auf dem Kuvert automatisch nach unten rechts. Wirkt nur, wenn „Kuverts generieren" aktiv ist.'), + ]), neu.importOpen ? h('div', { style: 'margin-top:12px;' }, [ subdued('Aus Excel kopieren und einfügen – eine Zeile pro Empfänger, Spalten mit Tab/Semikolon/Komma. ' + 'Reihenfolge: erst individuelle Platzhalter, dann ' + (neu.adressModus === 'individuell' ? 'die eine Adresszeile.' : 'Zeile 1–5.'), 'margin-bottom:6px;'), @@ -478,6 +548,8 @@ const NeuAuftrag = defineComponent({ neu.previewSession = null; requestAnimationFrame(() => { try { ta.selectionStart = ta.selectionEnd = s + 1; } catch {} }); } }), + h('div', { style: 'font-size:11px;color:var(--foreground-subdued,#888);margin-top:4px;line-height:1.5;' }, + 'Tipp: [[seitenumbruch]] erzwingt an dieser Stelle eine neue Seite (mehrseitiger Brief). [[signatur1]] setzt die Unterschrift.'), // ── Unterschrift auf den Brief ───────────────────────────────────── h('div', { style: 'margin-top:16px;border-top:1px solid rgba(30,80,165,.15);padding-top:14px;' }, [ label('Unterschrift auf den Brief (optional) · Platzhalter [[signatur1]]'), diff --git a/extensions/directus-extension-skrift-orders/dist/index.js b/extensions/directus-extension-skrift-orders/dist/index.js index 3f94ba6..1ba1018 100644 --- a/extensions/directus-extension-skrift-orders/dist/index.js +++ b/extensions/directus-extension-skrift-orders/dist/index.js @@ -677,6 +677,8 @@ const handler = (router, { services, getSchema, logger }) => { text: istFreitext ? clean(String(body.envelope_text || '')) : adressBlock(e), format: umschlagFormat, font, realisticHandwriting: realistisch, placeholders: platzhalterVon(e, i), fontScale: fsEnv, + // Absender aufs Kuvert (oben links); Empfänger rutscht dann unten rechts. + sender: clean(String(body.envelope_sender || '')), }); } }); @@ -1566,6 +1568,7 @@ const handler = (router, { services, getSchema, logger }) => { envelope_labeling: body.envelope_labeling ?? null, envelope_text: clean(body.envelope_text) ?? null, envelope_format: envelopeFormat, + envelope_sender: clean(body.envelope_sender) ?? null, text_template: clean(body.text_template) ?? null, preview_session: typeof body.preview_session === 'string' ? body.preview_session : null, entries_count: entries.length, diff --git a/extensions/directus-extension-skrift-produktion/dist/index.js b/extensions/directus-extension-skrift-produktion/dist/index.js index e519c74..7b5bb37 100644 --- a/extensions/directus-extension-skrift-produktion/dist/index.js +++ b/extensions/directus-extension-skrift-produktion/dist/index.js @@ -612,14 +612,33 @@ const Board = defineComponent({ } // ── Empfänger-Drawer (mit Dokument-Vorschau je Zeile) ── - const dateiNummer = (name) => { - const z = String(name || '').replace(/\.[^.]*$/, '').replace(/\D/g, ''); - return z ? parseInt(z, 10) : null; + // Zerlegt einen Dateinamen in Brief-Nummer und Seite: + // letter_001_p2.svg → { nummer:1, seite:2 }, letter_003.svg → { nummer:3, seite:1 }. + const briefTeile = (name) => { + const base = String(name || '').replace(/\.[^.]*$/, ''); + const m = base.match(/(\d+)(?:_p(\d+))?$/i); + return { nummer: m ? parseInt(m[1], 10) : null, seite: (m && m[2]) ? parseInt(m[2], 10) : 1 }; }; + // Distinkte Brief-Nummern eines Typs, aufsteigend – für positionsbasierte + // Zuordnung Empfänger↔Brief (robust gegen 0-/1-basierte Namen). Je Nummer die + // nach Seite sortierten Dateien (mehrseitige Briefe = mehrere Einträge). + const briefeNachRang = (kind) => { + const map = new Map(); + for (const f of empf.files.filter((x) => x.kind === kind)) { + const { nummer, seite } = briefTeile(f.name); + const key = nummer ?? f.name; + if (!map.has(key)) map.set(key, []); + map.get(key).push({ f, seite }); + } + const keys = [...map.keys()].sort((a, b) => ((typeof a === 'number' && typeof b === 'number') ? a - b : String(a).localeCompare(String(b)))); + return keys.map((k) => map.get(k).sort((a, b) => a.seite - b.seite).map((x) => x.f)); + }; + // Vorschau-Blob-URLs des Schriftstücks (Array) sauber freigeben. + const revokePl = () => { (Array.isArray(empf.pl) ? empf.pl : []).forEach((p) => { try { URL.revokeObjectURL(p.url); } catch { /* egal */ } }); }; async function openEmpf(order) { empf.open = true; empf.mode = 'order'; empf.titel = `Empfänger · ${order.order_number}`; - empf.order = order.order_number; empf.suche = ''; empf.rows = []; empf.files = []; empf.info = ''; - empf.selected = null; empf.pl = ''; empf.pe = ''; empf.perr = ''; empf.lb = ''; empf.laden = true; + empf.order = order.order_number; empf.orderObj = order; empf.suche = ''; empf.rows = []; empf.files = []; empf.info = ''; + empf.selected = null; revokePl(); empf.pl = []; empf.pe = ''; empf.perr = ''; empf.lb = ''; empf.laden = true; try { const [ent, fl] = await Promise.all([ api.get('/items/order_entries', { params: { 'filter[order][_eq]': order.id, sort: 'letter_number', limit: -1, fields: '*' } }), @@ -631,7 +650,7 @@ const Board = defineComponent({ } function openReqInfo(req) { empf.open = true; empf.mode = 'request'; empf.titel = `Anfrage · ${req.name}`; empf.suche = ''; empf.info = ''; - empf.files = []; empf.selected = null; empf.pl = ''; empf.pe = ''; empf.lb = ''; + empf.files = []; empf.selected = null; revokePl(); empf.pl = []; empf.pe = ''; empf.lb = ''; const a = req.address && typeof req.address === 'object' ? Object.entries(req.address).map(([k, v]) => `${k}: ${v}`).join(' · ') : (req.address || ''); empf.rows = [ { nr: '✉', zeile: `${req.name} · ${req.email}${req.phone ? ' · ' + req.phone : ''}`, extra: '' }, @@ -644,27 +663,67 @@ const Board = defineComponent({ const res = await api.get('/skrift-orders/files/' + encodeURIComponent(empf.order) + '/download', { params: { rel: f.rel }, responseType: 'blob' }); return URL.createObjectURL(res.data); } - // Positionsbasierter Abgleich: der i-te Empfänger → die i-te Datei. Robust - // gegen alte (0-basierte) und neue (1-basierte) Dateinamen. - const nachNummer = (kind) => empf.files - .filter((f) => f.kind === kind) - .sort((a, b) => (dateiNummer(a.name) ?? 0) - (dateiNummer(b.name) ?? 0)); async function previewEntry(nr, rang) { empf.selected = nr; empf.perr = ''; empf.pLoad = true; - if (empf.pl) URL.revokeObjectURL(empf.pl); if (empf.pe) URL.revokeObjectURL(empf.pe); - empf.pl = ''; empf.pe = ''; + revokePl(); if (empf.pe) URL.revokeObjectURL(empf.pe); + empf.pl = []; empf.pe = ''; try { - const brief = nachNummer('schriftstueck')[rang] || empf.files.find((f) => f.kind === 'schriftstueck' && dateiNummer(f.name) === nr); - const kuvert = nachNummer('umschlag')[rang] || empf.files.find((f) => f.kind === 'umschlag' && dateiNummer(f.name) === nr); - if (brief) empf.pl = await empfBlob(brief); + // Positionsbasiert: der rang-te Empfänger → die rang-te Brief-Nummer. + // Mehrseitige Briefe liefern hier mehrere Seiten-Dateien. + const seiten = briefeNachRang('schriftstueck')[rang] || []; + const kuvert = (briefeNachRang('umschlag')[rang] || [])[0] || null; + const geladen = []; + for (let i = 0; i < seiten.length; i += 1) { + geladen.push({ url: await empfBlob(seiten[i]), label: seiten.length > 1 ? `Seite ${i + 1}` : '' }); + } + empf.pl = geladen; if (kuvert) empf.pe = await empfBlob(kuvert); - if (!brief && !kuvert) { + if (!seiten.length && !kuvert) { empf.perr = empf.files.length ? 'Für diese Nummer sind noch keine Dateien generiert.' : 'Für diesen Auftrag wurden noch keine Dateien generiert (erst nach Abschluss/Generierung).'; } } catch (e) { empf.perr = 'Vorschau fehlgeschlagen.'; } finally { empf.pLoad = false; } } + // Auftrag in den manuellen Druck (neuauftrag-Modul) übernehmen: Text, Schrift, + // Format, Kuvert-Option und ALLE Empfänger (free_text + Platzhalter) per + // localStorage übergeben und dorthin navigieren – dort editierbar/neu druckbar. + async function uebernehmenManuell() { + const o = empf.orderObj; + if (!o) return; + try { + const full = (await api.get('/items/orders/' + o.id, { params: { + fields: 'text_template,font,multi_page,realistic,font_scale_letter,font_scale_envelope,needs_envelope,product.key,format.key', + } })).data?.data || {}; + const ent = (await api.get('/items/order_entries', { params: { + 'filter[order][_eq]': o.id, sort: 'letter_number', limit: -1, fields: 'free_text,placeholders,letter_number', + } })).data?.data || []; + const rows = ent.map((e) => ({ + free_text: String(e.free_text || ''), + placeholders: (e.placeholders && typeof e.placeholders === 'object') ? e.placeholders : {}, + })); + const daten = { + product: full.product?.key || '', + format: full.format?.key || o.format?.key || '', + font: full.font || 'tilda', + text: full.text_template || '', + multi_page: !!full.multi_page, + realistic: full.realistic !== false, + fontSizeLetter: Math.round((Number(full.font_scale_letter) || 1) * 100), + fontSizeEnvelope: Math.round((Number(full.font_scale_envelope) || 1) * 100), + kuverts: !!full.needs_envelope, + adressModus: rows.some((r) => r.free_text.includes('\n')) ? '5zeilen' : 'individuell', + rows, + }; + localStorage.setItem('skrift_neuauftrag_uebernahme', JSON.stringify(daten)); + // Zum manuellen Druck-Modul wechseln. Die Admin-App liegt unter /admin/ – + // Basis aus dem aktuellen Pfad ableiten (robust gegen Unterpfad-Prefixe). + const base = window.location.pathname.split('/admin/')[0] + '/admin/'; + window.location.href = base + 'skrift-neuauftrag'; + } catch (e) { + flash('Übernahme fehlgeschlagen: ' + (e?.response?.data?.errors?.[0]?.message || e?.message || 'Fehler')); + } + } const empfTreffer = computed(() => { const q = empf.suche.trim().toLowerCase(); if (!q) return empf.rows; @@ -1074,14 +1133,21 @@ const Board = defineComponent({ : subdued('Keine Treffer.')), ]); // Rechte Spalte (~30 %): Vorschau; Klick aufs Bild → große Lightbox. - const vorschauBox = (titel, url) => h('div', { style: 'display:flex;flex-direction:column;gap:6px;' }, [ - h('div', { style: 'font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:var(--foreground-subdued);' }, titel), - h('div', { - onClick: url ? () => { empf.lb = url; empf.lbTitel = titel; } : undefined, - style: 'border:1px solid var(--border-subdued);border-radius:8px;background:#fff;display:flex;align-items:center;justify-content:center;min-height:150px;padding:8px;' - + (url ? 'cursor:zoom-in;' : ''), - }, url ? h('img', { src: url, style: 'max-width:100%;max-height:30vh;object-fit:contain;' }) : subdued('—')), - ]); + const vorschauBox = (titel, quelle) => { + // quelle: Array [{url,label}] (Schriftstück, evtl. mehrseitig) oder einzelne URL (Kuvert). + const bilder = Array.isArray(quelle) ? quelle : (quelle ? [{ url: quelle, label: '' }] : []); + return h('div', { style: 'display:flex;flex-direction:column;gap:6px;' }, [ + h('div', { style: 'font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:var(--foreground-subdued);' }, + titel + (bilder.length > 1 ? ` · ${bilder.length} Seiten` : '')), + ...(bilder.length ? bilder.map((b) => h('div', { + onClick: () => { empf.lb = b.url; empf.lbTitel = titel + (b.label ? ' · ' + b.label : ''); }, + style: 'position:relative;border:1px solid var(--border-subdued);border-radius:8px;background:#fff;display:flex;align-items:center;justify-content:center;min-height:150px;padding:8px;cursor:zoom-in;', + }, [ + b.label ? h('div', { style: 'position:absolute;top:4px;left:6px;font-size:10px;color:var(--foreground-subdued);background:var(--background-page);padding:0 4px;border-radius:4px;' }, b.label) : null, + h('img', { src: b.url, style: 'max-width:100%;max-height:30vh;object-fit:contain;' }), + ])) : [h('div', { style: 'border:1px solid var(--border-subdued);border-radius:8px;background:#fff;display:flex;align-items:center;justify-content:center;min-height:150px;padding:8px;' }, subdued('—'))]), + ]); + }; const vorschau = istOrder ? h('div', { style: 'flex:0 0 30%;min-width:220px;display:flex;flex-direction:column;gap:14px;' }, empf.selected == null ? [subdued('Empfänger anklicken → Vorschau (Klick aufs Bild = groß).')] : empf.pLoad ? [subdued('Vorschau wird geladen …')] @@ -1090,8 +1156,12 @@ const Board = defineComponent({ vorschauBox('📄 Schriftstück', empf.pl), vorschauBox('✉ Kuvert', empf.pe), ]) : null; + const uebernahme = istOrder ? h('div', { style: 'padding:12px 20px;border-bottom:1px solid var(--border-subdued);display:flex;justify-content:flex-end;align-items:center;gap:10px;flex-wrap:wrap;' }, [ + subdued('Text, Format & alle Empfänger in den manuellen Druck laden – dort anpassen und neu anlegen.'), + btn('✍ In manuellen Druck übernehmen', uebernehmenManuell, 'primary'), + ]) : null; const body = h('div', { style: 'padding:16px 20px;display:flex;gap:20px;flex-wrap:wrap;' }, [liste, vorschau]); - return overlay([modalKopf(empf.titel, () => { empf.open = false; }), body], () => { empf.open = false; }, istOrder ? '980px' : '620px'); + return overlay([modalKopf(empf.titel, () => { empf.open = false; }), uebernahme, body].filter(Boolean), () => { empf.open = false; }, istOrder ? '980px' : '620px'); } // Große Einzelvorschau (Lightbox) über dem Empfänger-Dialog. function lightboxModal() {