From abf60cda0e0903c79629290486269992ae87df8b Mon Sep 17 00:00:00 2001 From: s4luorth Date: Mon, 21 Sep 2026 17:16:01 +0200 Subject: [PATCH] =?UTF-8?q?Produktion:=20Suche-Reiter=20Usability=20?= =?UTF-8?q?=E2=80=93=20wei=C3=9Fes=20Suchfeld,=20klare=20Typ-Auswahl?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hoher Kontrast für die Bedienung: große, eindeutige Typ-Auswahlknöpfe (Schriftstück/Kuvert/Druckobjekt, aktiv blau, sonst weiß mit Rahmen) direkt über einem unverkennbar weißen, großen Suchfeld. Standard- Templates werden nicht mehr im Reiter gewählt, sondern in den Einstellungen den Formaten zugeordnet (format_templates.default_format); der Schnelldruck leitet das Template daraus ab. Ergebnisse zeigen die vollständige Anschrift über der Auftragsnummer – ohne Status/Produkttyp. Co-Authored-By: Claude Opus 4.8 --- .../dist/index.js | 143 +++++++++--------- 1 file changed, 72 insertions(+), 71 deletions(-) diff --git a/extensions/directus-extension-skrift-produktion/dist/index.js b/extensions/directus-extension-skrift-produktion/dist/index.js index cb04175..278e73a 100644 --- a/extensions/directus-extension-skrift-produktion/dist/index.js +++ b/extensions/directus-extension-skrift-produktion/dist/index.js @@ -170,11 +170,11 @@ const Board = defineComponent({ const api = useApi(); const tab = ref('neu'); const suche = ref(''); - // Suche-Reiter: Objekttyp (Schriftstück/Kuvert/Druckobjekt), Standard-Templates - // je Typ + Zielmaschine (im Reiter einstellbar, lokal gespeichert), und ein - // flacher Empfänger-Index (eine Zeile je Empfänger, mit Briefnummer). + // Suche-Reiter: Objekttyp (Schriftstück/Kuvert/Druckobjekt) + ein flacher + // Empfänger-Index (eine Zeile je Empfänger, mit Briefnummer). Die Standard- + // Templates werden NICHT hier gewählt, sondern in den Einstellungen den + // Formaten zugeordnet (format_templates.default_format). const sucheTyp = ref('brief'); - const sucheTpl = reactive({ brief: null, umschlag: null, machine: null }); const sucheEintraege = ref([]); const loading = ref(false); const fehler = ref(''); @@ -270,8 +270,18 @@ const Board = defineComponent({ const nm = entryName(e); if (!namen[oid]) namen[oid] = { erste: nm, letzte: nm, anzahl: 1 }; else { namen[oid].letzte = nm; namen[oid].anzahl += 1; } + // Vollständige Anschrift (mehrzeilig) für die Anzeige im Suche-Reiter. + const adr = []; + const person = [e.salutation, e.first_name, e.last_name].filter(Boolean).join(' '); + if (person) adr.push(person); + const strasse = [e.street, e.house_no].filter(Boolean).join(' '); + if (strasse) adr.push(strasse); + const ortZeile = [e.zip, e.city].filter(Boolean).join(' '); + if (ortZeile) adr.push(ortZeile); + if (e.country && !/^(de|d|deu|deutschland|germany)$/i.test(String(e.country).trim())) adr.push(e.country); + if (!adr.length && e.free_text) adr.push(...String(e.free_text).split('\n').map((z) => z.trim()).filter(Boolean)); // Eine durchsuchbare Zeile je Empfänger (mit Briefnummer) für den Suche-Reiter. - flach.push({ orderId: oid, nr: Number(e.letter_number) || flach.filter((x) => x.orderId === oid).length + 1, name: nm, hay: txt }); + flach.push({ orderId: oid, nr: Number(e.letter_number) || flach.filter((x) => x.orderId === oid).length + 1, name: nm, adr, hay: txt }); } empfIndex.value = idx; empfNamen.value = namen; @@ -401,7 +411,6 @@ const Board = defineComponent({ } finally { if (!leise) loading.value = false; } } onMounted(laden); - onMounted(sucheDefaultsLaden); // Auto-Refresh: hält das Board aktuell, ohne dass man die Seite neu laden muss. // Pausiert bei offenem Sammel-Dialog, im Hintergrund-Tab und während bereits @@ -473,30 +482,22 @@ const Board = defineComponent({ } return res; }); - // Standard-Templates/Maschine für den Schnelldruck lokal sichern/laden. - const SUCHE_KEY = 'skrift_suche_defaults'; - function sucheDefaultsLaden() { - try { - const s = JSON.parse(localStorage.getItem(SUCHE_KEY) || '{}'); - if (s.brief) sucheTpl.brief = s.brief; - if (s.umschlag) sucheTpl.umschlag = s.umschlag; - if (s.machine) sucheTpl.machine = s.machine; - } catch { /* ohne gespeicherte Defaults weiter */ } - } - function sucheDefaultsSpeichern() { - try { localStorage.setItem(SUCHE_KEY, JSON.stringify({ brief: sucheTpl.brief, umschlag: sucheTpl.umschlag, machine: sucheTpl.machine })); } - catch { /* egal */ } - } - // Schnelldruck: NUR den gewählten Empfänger (Briefnummer) des Typs mit dem im - // Reiter gesetzten Standard-Template in die Warteschlange geben. + // Schnelldruck: NUR den gewählten Empfänger (Briefnummer) des Typs in die + // Warteschlange geben. Das Template kommt aus der in den Einstellungen dem + // Auftragsformat zugeordneten Vorlage (default_format), sonst der Fallback. async function schnelldruck(o, nr, typ) { - const tplId = typ === 'umschlag' ? sucheTpl.umschlag : sucheTpl.brief; - const machine = sucheTpl.machine || aktiveMaschinen.value[0]?.id || null; - if (!tplId) { fehler.value = `Bitte oben ein Standard-Template für ${typ === 'umschlag' ? 'Kuvert' : 'Schriftstück'} wählen.`; return; } + const machine = aktiveMaschinen.value[0]?.id || null; if (!machine) { fehler.value = 'Keine aktive Maschine verfügbar.'; return; } + const tpl = typ === 'umschlag' + ? (tplForFormat(o.envelope_format, 'umschlag') || tplByKey(ENVELOPE_TPL[o.envelope_format] || 'kuvert_a_v1', 'umschlag')) + : (tplForFormat(o.format?.key, 'brief') || tplByKey(LETTER_TPL[o.format?.key] || 'a4_single', 'brief')); + if (!tpl?.id) { + fehler.value = `Kein Standard-Template für ${typ === 'umschlag' ? 'Kuvert' : 'Schriftstück'} hinterlegt – bitte in den Einstellungen (Format-Templates) ein Standardformat zuordnen.`; + return; + } try { const sections = {}; - sections[typ === 'umschlag' ? 'umschlag' : 'brief'] = { enabled: true, template: tplId, numbers: nr ? String(nr) : '' }; + sections[typ === 'umschlag' ? 'umschlag' : 'brief'] = { enabled: true, template: tpl.id, numbers: nr ? String(nr) : '' }; await api.post('/skrift-orders/enqueue', { order: o.id, machine, mode: 'queue', sections }); flash(`#${o.order_number} · ${typ === 'umschlag' ? '✉ Kuvert' : '📄 Schriftstück'}${nr ? ` Nr. ${nr}` : ''} → Warteschlange.`); await laden(); @@ -1269,66 +1270,66 @@ const Board = defineComponent({ } // ── Suche-Reiter ────────────────────────────────────────────────────────── + // Bewusst hoher Kontrast (weißes Suchfeld, klar erkennbare Typ-Auswahl) – wird + // auch von weniger technikaffinen Personen bedient. Standard-Templates werden + // NICHT hier gewählt, sondern in den Einstellungen den Formaten zugeordnet. function sucheInhalt() { - const briefTpls = templates.value.filter((t) => t.type === 'brief'); - const umschlagTpls = templates.value.filter((t) => t.type === 'umschlag'); - const tplSelect = (typ) => { - const opts = typ === 'umschlag' ? umschlagTpls : briefTpls; - const val = typ === 'umschlag' ? sucheTpl.umschlag : sucheTpl.brief; - return h('select', { style: S_SELECT + 'width:100%;', value: val ?? '', - onChange: (e) => { const v = e.target.value || null; if (typ === 'umschlag') sucheTpl.umschlag = v; else sucheTpl.brief = v; sucheDefaultsSpeichern(); } }, - [h('option', { value: '' }, '– Standard-Template –')] - .concat(opts.map((t) => h('option', { value: t.id, selected: String(val) === String(t.id) }, t.name)))); - }; - // Kopf: Typ-Umschalter + Standard-Templates/Maschine (im Reiter einstellbar). - const kopf = h('div', { style: SECTION }, [ - h('div', { style: 'max-width:560px;margin-bottom:14px;' }, [ - segToggle(sucheTyp.value, [ - { key: 'brief', label: '📄 Schriftstück' }, - { key: 'umschlag', label: '✉ Kuvert' }, - { key: 'objekt', label: '📎 Druckobjekte' }, - ], (k) => { sucheTyp.value = k; }), - ]), - h('div', { style: 'display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;' }, [ - h('div', {}, [h('label', { style: 'font-size:11px;color:var(--foreground-subdued);' }, 'Standard-Template Schriftstück'), tplSelect('brief')]), - h('div', {}, [h('label', { style: 'font-size:11px;color:var(--foreground-subdued);' }, 'Standard-Template Kuvert'), tplSelect('umschlag')]), - h('div', {}, [h('label', { style: 'font-size:11px;color:var(--foreground-subdued);' }, 'Maschine'), - h('select', { style: S_SELECT + 'width:100%;', value: sucheTpl.machine ?? '', - onChange: (e) => { sucheTpl.machine = e.target.value || null; sucheDefaultsSpeichern(); } }, - [h('option', { value: '' }, aktiveMaschinen.value[0] ? `Automatisch (${aktiveMaschinen.value[0].name})` : '– keine –')] - .concat(aktiveMaschinen.value.map((m) => h('option', { value: m.id, selected: String(sucheTpl.machine) === String(m.id) }, m.name)))), - ]), - ]), - subdued(sucheTyp.value === 'objekt' - ? 'Druckobjekte: fertige Dateien des Auftrags zum manuellen Herunterladen.' - : 'Klick auf „In Warteschlange" gibt genau diesen Empfänger mit dem gewählten Standard-Template in die Queue.', 'margin-top:10px;'), + const TYPEN = [ + { key: 'brief', label: '📄 Schriftstück' }, + { key: 'umschlag', label: '✉ Kuvert' }, + { key: 'objekt', label: '📎 Druckobjekt' }, + ]; + // Große, eindeutige Auswahlknöpfe: aktiv = blau/weiß, sonst weiß mit Rahmen. + const typWahl = h('div', {}, [ + h('div', { style: 'font-size:13px;font-weight:700;color:var(--foreground-normal);margin-bottom:8px;' }, '1. Was möchten Sie?'), + h('div', { style: 'display:flex;gap:10px;flex-wrap:wrap;' }, TYPEN.map((t) => { + const aktiv = sucheTyp.value === t.key; + return h('button', { type: 'button', onClick: () => { sucheTyp.value = t.key; }, + style: 'cursor:pointer;border-radius:10px;padding:14px 22px;font:inherit;font-size:16px;font-weight:700;' + + (aktiv ? `background:${ACCENT};color:#fff;border:2px solid ${ACCENT};box-shadow:0 2px 6px rgba(30,80,165,.3);` + : 'background:#fff;color:#1a2a3a;border:2px solid #cdd6e2;') }, t.label); + })), + ]); + // Großes, unverkennbar weißes Suchfeld. + const feld = h('div', { style: 'margin-top:18px;' }, [ + h('div', { style: 'font-size:13px;font-weight:700;color:var(--foreground-normal);margin-bottom:8px;' }, '2. Empfänger suchen'), + h('input', { type: 'search', value: suche.value, placeholder: 'Name, Straße, Ort oder Auftragsnummer eingeben …', + style: 'width:100%;box-sizing:border-box;background:#fff;color:#111;border:2px solid #cdd6e2;border-radius:10px;' + + 'font:inherit;font-size:22px;padding:16px 20px;outline:none;box-shadow:0 1px 3px rgba(0,0,0,.08);', + onInput: (e) => { suche.value = e.target.value; } }), + ]); + const rahmen = (inhalt) => h('div', { style: 'display:flex;flex-direction:column;gap:0;' }, [ + h('div', { style: 'background:#fff;border:1px solid #cdd6e2;border-radius:12px;padding:20px 22px;margin-bottom:16px;box-shadow:0 1px 3px rgba(0,0,0,.06);' }, [typWahl, feld]), + inhalt, ]); - // Großes Suchfeld (nutzt dasselbe Such-Feld wie oben). - const feld = h('input', { type: 'search', value: suche.value, placeholder: '🔎 Name, Straße, Ort, Auftragsnummer …', - style: S_INPUT + 'width:100%;font-size:20px;padding:14px 18px;margin:14px 0;', onInput: (e) => { suche.value = e.target.value; } }); - if (!suche.value.trim()) return [kopf, feld, subdued('Suchbegriff eingeben – durchsucht Neu, Warteschlange, Im Druck, Kuvertierung und Versandfertig.')]; + if (!suche.value.trim()) return rahmen(subdued('Suchbegriff eingeben – durchsucht Neu, Warteschlange, Im Druck, Kuvertierung und Versandfertig.')); const treffer = sucheTreffer.value; - if (!treffer.length) return [kopf, feld, subdued('Keine Treffer in den aktiven Aufträgen.')]; + if (!treffer.length) return rahmen(subdued('Keine Treffer in den aktiven Aufträgen.')); const zeile = ({ e, o }) => { const gesamt = empfNamen.value[String(o.id)]?.anzahl || 1; - const pos = gesamt > 1 ? `Nr. ${e.nr} von ${gesamt}` : 'Einzelauftrag'; - const statusLbl = STATUS_LABEL[o.production_status || 'eingegangen'] || o.production_status || '—'; + const adrLines = (e.adr && e.adr.length) ? e.adr : [e.name || '—']; + const kannKuvert = !!(o.needs_envelope && o.envelope_labeling && o.envelope_labeling !== 'keine'); const aktion = sucheTyp.value === 'objekt' - ? btn('📎 Dateien', () => openEmpf(o), 'primary') - : btn('🖨 In Warteschlange', () => schnelldruck(o, e.nr, sucheTyp.value), 'primary'); + ? btn('📎 Dateien öffnen', () => openEmpf(o), 'primary') + : (sucheTyp.value === 'umschlag' && !kannKuvert) + ? subdued('Kein Kuvert') + : btn('🖨 In Warteschlange', () => schnelldruck(o, e.nr, sucheTyp.value), 'primary'); return h('div', { key: `s${o.id}-${e.nr}`, style: CARD + 'margin-bottom:8px;display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap;' }, [ h('div', { style: 'min-width:0;' }, [ - h('div', { style: 'font-weight:700;font-size:15px;' }, e.name || '—'), - h('div', { style: 'display:flex;gap:8px;flex-wrap:wrap;margin-top:3px;align-items:center;' }, [ - subdued(`#${o.order_number}`), chip(pos), chip(statusLbl), o.product?.name ? subdued(o.product.name) : null, + // Vollständige Anschrift zuerst (erste Zeile = Name, hervorgehoben). + ...adrLines.map((z, i) => h('div', { style: i === 0 ? 'font-weight:700;font-size:16px;' : 'font-size:14px;color:var(--foreground-normal);' }, z)), + // Darunter die Auftragsnummer (+ Position bei mehreren Empfängern). + h('div', { style: 'display:flex;gap:8px;flex-wrap:wrap;margin-top:5px;align-items:center;' }, [ + h('span', { style: 'font-size:13px;color:var(--foreground-subdued);' }, `Auftrag #${o.order_number}`), + gesamt > 1 ? chip(`Nr. ${e.nr} von ${gesamt}`) : null, ]), ]), aktion, ]); }; - return [kopf, feld, h('div', { style: 'display:flex;flex-direction:column;gap:2px;' }, treffer.map(zeile))]; + return rahmen(h('div', { style: 'display:flex;flex-direction:column;gap:2px;' }, treffer.map(zeile))); } function tabInhalt() {