From 5196e30343afa7731dd89dc7bc5fcf9e0bf0e076 Mon Sep 17 00:00:00 2001 From: s4luorth Date: Thu, 1 Oct 2026 18:03:02 +0200 Subject: [PATCH] Mitarbeiter-Uebersicht: Empfaenger ausklappbar + Kommentar; Auftragsnr nur 1x; Board: Versanddatum setzbar Uebersicht: - Empfaenger-Zelle: erster Empfaenger + Umschalter "alle N Empfaenger" -> klappt eine Detailzeile mit der VOLLEN Empfaengerliste aus (kein gesondertes erster/letzter mehr) - gleiche Detailzeile hat ein Kommentarfeld je Auftrag (internal_note) - Auftragsnummer wird nur noch EINMAL angezeigt (keine Doppelung mehr) - volle Empfaengerliste + internal_note werden mitgeladen Produktionsboard: - Versanddatum je Auftrag unter "Weiteres -> Aufgaben umstellen" setzbar (Datum, leer = schnellstmoeglich). Mitarbeiter-Uebersicht bleibt bei Versandart/-datum weiterhin nur lesbar (wie gewuenscht). Co-Authored-By: Claude Opus 4.8 --- .../dist/index.js | 3 + .../dist/index.js | 68 ++++++++++++++----- 2 files changed, 55 insertions(+), 16 deletions(-) diff --git a/extensions/directus-extension-skrift-produktion/dist/index.js b/extensions/directus-extension-skrift-produktion/dist/index.js index 0a8c12d..bf70dfb 100644 --- a/extensions/directus-extension-skrift-produktion/dist/index.js +++ b/extensions/directus-extension-skrift-produktion/dist/index.js @@ -1405,6 +1405,9 @@ const Board = defineComponent({ h('div', { style: 'font-size:11.5px;font-weight:500;color:#566070;text-transform:uppercase;letter-spacing:.03em;margin-bottom:8px;' }, 'Aufgaben umstellen'), h('div', { style: 'display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:14px;' }, [ h('div', {}, [feldLabel('Versandart'), segToggle(o.shipping_type === 'einzeln' ? 'einzeln' : 'sammel', [{ key: 'einzeln', label: 'Einzelversand' }, { key: 'sammel', label: 'Sammelversand' }], (k) => setOrderFeld(o, { shipping_type: k }))]), + h('div', {}, [feldLabel('Versanddatum (leer = schnellstmöglich)'), h('input', { type: 'date', value: o.versanddatum || '', + style: 'width:100%;box-sizing:border-box;border:1px solid #b4c0d2;border-radius:8px;padding:9px 11px;font:inherit;font-size:14px;background:#ffffff;color:#1b2430;', + onChange: (e) => setOrderFeld(o, { versanddatum: e.target.value || null }) })]), h('div', {}, [feldLabel('Frankierung'), segToggle(((typeof o.frankiert === 'boolean') ? o.frankiert : (o.shipping_type !== 'sammel')) ? 'ja' : 'nein', [{ key: 'ja', label: 'Frankieren' }, { key: 'nein', label: 'Nicht' }], (k) => setOrderFeld(o, { frankiert: k === 'ja' }))]), h('div', {}, [feldLabel('Kuvert schreiben'), segToggle(o.needs_envelope ? 'ja' : 'nein', [{ key: 'ja', label: 'Mit Kuvert' }, { key: 'nein', label: 'Ohne' }], (k) => setOrderFeld(o, { needs_envelope: k === 'ja' }))]), h('div', {}, [feldLabel('Blatt einlegen (kuvertieren)'), segToggle((o.kuvertieren !== false) ? 'ja' : 'nein', [{ key: 'ja', label: 'Blatt rein' }, { key: 'nein', label: 'Nur Kuvert' }], (k) => setOrderFeld(o, { kuvertieren: k === 'ja' }))]), diff --git a/extensions/directus-extension-skrift-uebersicht/dist/index.js b/extensions/directus-extension-skrift-uebersicht/dist/index.js index a5bcfaf..8e45a83 100644 --- a/extensions/directus-extension-skrift-uebersicht/dist/index.js +++ b/extensions/directus-extension-skrift-uebersicht/dist/index.js @@ -58,6 +58,7 @@ const Board = { const entriesByOrder = ref({}); // { orderId: {erster, letzter, anzahl} } const sucheEintraege = ref([]); // flache Empfängerliste für die Suche const expandedCharges = reactive({}); + const expandedEmpf = reactive({}); // je Auftrag: volle Empfängerliste + Kommentar ausklappen const loading = ref(false); const msg = ref(''); const err = ref(''); const sucheQuery = ref(''); let sucheTimer = null; @@ -92,7 +93,7 @@ const Board = { // Sichere Basisfelder (alle auf orders/formats/products – von der // Mitarbeiter-Rolle lesbar). api_client (API-Kundenname) und kuvertieren // sind optional: fehlende Rechte/Felder dürfen die Ansicht NICHT blockieren. - const basis = 'id,order_number,order_name,production_status,needs_envelope,brief_gedruckt,kuvert_gedruckt,frankiert,' + const basis = 'id,order_number,order_name,production_status,needs_envelope,brief_gedruckt,kuvert_gedruckt,frankiert,internal_note,' + 'format.key,format.name,shipping_day,shipping_type,versanddatum,date_created,entries_count,source,' + 'envelope_labeling,envelope_format,product.name'; const mitClient = basis + ',api_client.id,api_client.name'; @@ -137,8 +138,8 @@ const Board = { const idx = {}; for (const e of (ent.data?.data || [])) { const k = String(e.order?.id ?? e.order); if (!k || k === 'null') continue; - if (!idx[k]) idx[k] = { erster: e, letzter: e, anzahl: 1 }; - else { idx[k].letzter = e; idx[k].anzahl += 1; } + if (!idx[k]) idx[k] = { erster: e, letzter: e, anzahl: 1, alle: [e] }; + else { idx[k].letzter = e; idx[k].anzahl += 1; if (idx[k].alle.length < 2000) idx[k].alle.push(e); } } entriesByOrder.value = idx; } catch { /* Empfänger optional */ } @@ -192,6 +193,7 @@ const Board = { const setStatus = (o, v) => patchOrder(o, { production_status: v }); const setVersand = (o, v) => patchOrder(o, { shipping_type: v }); const setVersanddatum = (o, v) => patchOrder(o, { versanddatum: v || null }); + const setNote = (o, v) => patchOrder(o, { internal_note: v }); // Status für einen ganzen Sammelauftrag (alle Teil-Aufträge) auf einmal. async function setChargeStatus(c, v) { try { @@ -324,24 +326,58 @@ const Board = { ]; return h('div', { style: 'display:flex;gap:5px;flex-wrap:wrap;' }, steps.map(([l, b, e]) => stepPill(l, b, e))); } + // Gibt ein Zeilen-ARRAY zurück: Hauptzeile + (bei Ausklappen) Detailzeile mit + // voller Empfängerliste und Kommentarfeld. function zeile(o, indent) { - const e = entriesByOrder.value[String(o.id)]; - return h('tr', { key: 'o' + o.id }, [ - td(h('div', { style: `line-height:1.45;${indent ? 'padding-left:18px;border-left:2px solid #d2dae6;' : ''}` }, [ - h('div', { style: 'font-weight:500;' }, empfKurz(e?.erster) || '—'), - (e && e.anzahl > 1) ? h('div', { style: 'font-size:12.5px;color:#566070;' }, '… ' + empfKurz(e.letzter)) : null, - ])), - td(h('div', { style: 'line-height:1.4;font-size:13px;' }, [ - o.order_name ? h('div', { style: 'font-weight:500;color:#1E50A5;' }, o.order_name) : null, - h('div', {}, kundeName(o) || `#${o.order_number}`), - h('div', { style: 'color:#566070;' }, `#${o.order_number} · ${o.entries_count || 0} Empf.`), - ])), + const id = String(o.id); + const e = entriesByOrder.value[id]; + const auf = !!expandedEmpf[id]; + const anzahl = e ? e.anzahl : (o.entries_count || 0); + // Empfänger: erster + Umschalter „alle anzeigen" (kein gesondertes letzter mehr). + const empfZelle = h('div', { style: `line-height:1.45;${indent ? 'padding-left:18px;border-left:2px solid #d2dae6;' : ''}` }, [ + h('div', { style: 'font-weight:500;' }, empfKurz(e?.erster) || '—'), + (anzahl > 1) ? h('button', { type: 'button', onClick: () => { expandedEmpf[id] = !auf; }, + style: 'cursor:pointer;border:none;background:transparent;padding:2px 0;margin-top:2px;color:#1E50A5;font:inherit;font-size:12.5px;font-weight:600;display:inline-flex;align-items:center;gap:3px;' }, + [icon(auf ? 'expand_less' : 'expand_more'), auf ? 'Empfänger ausblenden' : `alle ${anzahl} Empfänger`]) : null, + ].filter(Boolean)); + // Auftrag: Name/Kunde + Nummer NUR EINMAL. + const name = o.order_name || kundeName(o); + const auftragZelle = h('div', { style: 'line-height:1.4;font-size:13px;' }, [ + name ? h('div', { style: 'font-weight:500;' + (o.order_name ? 'color:#1E50A5;' : '') }, name) : null, + h('div', { style: 'color:#566070;' }, `#${o.order_number} · ${o.entries_count || 0} Empf.`), + ].filter(Boolean)); + const haupt = h('tr', { key: 'o' + o.id }, [ + td(empfZelle), + td(auftragZelle), td(h('span', {}, o.format?.name || FORMAT_LABEL[o.format?.key] || o.format?.key || '—')), td(ablaufStrip(o)), td(versandToggle(o)), td(datumFeld(o)), td(statusPill(o)), ]); + if (!auf) return [haupt]; + const liste = (e && e.alle) ? e.alle : []; + const detail = h('tr', { key: 'd' + o.id }, [ + h('td', { colspan: 7, style: `padding:0 12px 14px;border-bottom:1px solid #d2dae6;${indent ? 'padding-left:30px;' : ''}` }, [ + h('div', { style: 'display:grid;grid-template-columns:1fr 1fr;gap:16px;background:#f7f9fc;border:1px solid #d2dae6;border-radius:10px;padding:12px 14px;' }, [ + h('div', {}, [ + h('div', { style: 'font-size:11.5px;font-weight:600;color:#566070;text-transform:uppercase;letter-spacing:.03em;margin-bottom:6px;' }, `Empfänger (${liste.length || anzahl})`), + h('div', { style: 'max-height:220px;overflow:auto;display:flex;flex-direction:column;gap:3px;' }, + liste.length ? liste.map((x, i) => h('div', { key: i, style: 'font-size:13px;display:flex;gap:8px;' }, [ + h('span', { style: 'color:#566070;min-width:28px;' }, (i + 1) + '.'), + h('span', {}, empfKurz(x) || '—'), + ])) : [h('span', { style: 'color:#566070;font-size:13px;' }, 'Keine Empfängerdaten geladen.')]), + ]), + h('div', {}, [ + h('div', { style: 'font-size:11.5px;font-weight:600;color:#566070;text-transform:uppercase;letter-spacing:.03em;margin-bottom:6px;' }, 'Kommentar'), + h('textarea', { rows: 4, placeholder: 'Kommentar zum Auftrag …', + style: 'width:100%;box-sizing:border-box;border:1px solid #b4c0d2;border-radius:8px;padding:9px 11px;font:inherit;font-size:13px;background:#ffffff;color:#1b2430;resize:vertical;', + value: o.internal_note || '', onChange: (ev) => setNote(o, ev.target.value) }), + ]), + ]), + ]), + ]); + return [haupt, detail]; } // Aggregierter Ablauf über alle Aufträge einer Charge. function ablaufStripAgg(orders) { @@ -398,8 +434,8 @@ const Board = { const api = list.filter((o) => o.source === 'api' || o.api_client); const rest = list.filter((o) => !(o.source === 'api' || o.api_client)); const rows = []; - for (const c of bildeChargen(api)) { rows.push(chargeZeile(c)); if (expandedCharges[c.key]) for (const o of c.orders) rows.push(zeile(o, true)); } - for (const o of rest) rows.push(zeile(o)); + for (const c of bildeChargen(api)) { rows.push(chargeZeile(c)); if (expandedCharges[c.key]) for (const o of c.orders) rows.push(...zeile(o, true)); } + for (const o of rest) rows.push(...zeile(o)); return rows; } const kopfRow = () => h('thead', {}, h('tr', {}, [th('Empfänger (erster · letzter)'), th('Auftrag'), th('Format'), th('Ablauf'), th('Versand'), th('Versanddatum'), th('Status')]));