Uebersicht: Empfaenger als Hauptinfo, Status-getrennte Tabellen, Aufgabe, Versand-Toggle

- Empfaenger (erster/letzter) ist die fuehrende Spalte; Ladefehler behoben
  (order _in statt Relationsfilter). Auftrag/Kunde sekundaer.
- Je Status eine eigene Tabelle (Im Druck / Kuvertierung / Versandfertig /
  Eingegangen).
- Neue Spalte "Aufgabe" (kuvertieren/frankieren/Versandart aus Status abgeleitet).
- Versandart pro Auftrag umschaltbar (Sammel/Einzel).
- Maschinen-Panel zaehlt nur WARTENDE Jobs (nicht "im Druck").

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
s4luorth
2026-10-01 11:08:29 +02:00
parent 1eb3b14a13
commit d2559de22c

View File

@@ -85,12 +85,13 @@ const Board = {
machines.value = m.data?.data || []; machines.value = m.data?.data || [];
// Erster/letzter Empfänger je Auftrag (für die Adressspalte). // Erster/letzter Empfänger je Auftrag (für die Adressspalte).
const idx = {}; const idx = {};
const ids = orders.value.map((x) => x.id);
try { try {
const ent = await api.get('/items/order_entries', { params: { const ent = ids.length ? await api.get('/items/order_entries', { params: {
limit: -1, sort: 'order,letter_number', limit: -1, sort: 'letter_number',
filter: { order: { production_status: { _in: AKTIV_STATUS } } }, filter: { order: { _in: ids } },
fields: 'order,first_name,last_name,company,city,letter_number,free_text', fields: 'order,first_name,last_name,company,city,letter_number,free_text',
} }); } }) : { data: { data: [] } };
for (const e of (ent.data?.data || [])) { for (const e of (ent.data?.data || [])) {
const k = String(e.order?.id ?? e.order); const k = String(e.order?.id ?? e.order);
if (!k || k === 'null') continue; if (!k || k === 'null') continue;
@@ -111,7 +112,12 @@ const Board = {
const eingeplantSet = computed(() => new Set(jobs.value.map((j) => String(j.order?.id)).filter((x) => x && x !== 'null'))); const eingeplantSet = computed(() => new Set(jobs.value.map((j) => String(j.order?.id)).filter((x) => x && x !== 'null')));
const queuedByFormat = computed(() => { const queuedByFormat = computed(() => {
const map = {}; const map = {};
for (const j of jobs.value) { const f = j.format || '?'; if (!map[f]) map[f] = { n: 0, label: j.format_label || FORMAT_LABEL[f] || f }; map[f].n += 1; } for (const j of jobs.value) {
if (j.status !== 'queued') continue; // nur wartende Jobs (nicht „im Druck")
const f = j.format || '?';
if (!map[f]) map[f] = { n: 0, label: j.format_label || FORMAT_LABEL[f] || f };
map[f].n += 1;
}
return map; return map;
}); });
const gefiltert = computed(() => { const gefiltert = computed(() => {
@@ -154,33 +160,53 @@ const Board = {
const th = (t, extra) => h('th', { style: `text-align:left;font-size:12px;font-weight:500;color:var(--foreground-subdued);text-transform:uppercase;letter-spacing:.03em;padding:10px 12px;border-bottom:1px solid var(--border-normal);white-space:nowrap;${extra || ''}` }, t); const th = (t, extra) => h('th', { style: `text-align:left;font-size:12px;font-weight:500;color:var(--foreground-subdued);text-transform:uppercase;letter-spacing:.03em;padding:10px 12px;border-bottom:1px solid var(--border-normal);white-space:nowrap;${extra || ''}` }, t);
const td = (kids, extra) => h('td', { style: `padding:11px 12px;border-bottom:1px solid var(--border-subdued);font-size:14px;vertical-align:top;${extra || ''}` }, kids); const td = (kids, extra) => h('td', { style: `padding:11px 12px;border-bottom:1px solid var(--border-subdued);font-size:14px;vertical-align:top;${extra || ''}` }, kids);
// Nächste konkrete Aufgabe für den Mitarbeiter (aus Status + Kuvert + Versandart).
function aufgabe(o) {
const einzeln = o.shipping_type === 'einzeln';
switch (o.production_status) {
case 'eingegangen': return { t: 'Wartet auf Druck', c: 'var(--foreground-subdued)', i: 'hourglass_empty' };
case 'im_druck': return { t: 'Im Druck', c: 'var(--primary,#1e50a5)', i: 'print' };
case 'kuvertieren': return { t: (o.needs_envelope ? 'Kuvertieren' : 'Vorbereiten') + ' + frankieren', c: '#6a3fd0', i: 'mail' };
case 'versandfertig': return { t: einzeln ? 'Einzelversand' : 'Sammelversand', c: '#8a6d00', i: 'local_shipping' };
default: return { t: STATUS_LABEL[o.production_status] || '', c: 'var(--foreground-subdued)', i: 'info' };
}
}
async function setVersand(o, v) {
try { await api.patch('/items/orders/' + o.id, { shipping_type: v }); o.shipping_type = v; }
catch (e) { err.value = 'Versandart konnte nicht gesetzt werden.'; }
}
function versandToggle(o) {
const einzeln = o.shipping_type === 'einzeln';
return h('button', { type: 'button', title: 'Umschalten Sammel/Einzel', onClick: () => setVersand(o, einzeln ? 'sammel' : 'einzeln'),
style: 'cursor:pointer;border:1px solid var(--border-normal);background:var(--background-page,#fff);border-radius:999px;padding:4px 11px;font:inherit;font-size:12.5px;font-weight:500;display:inline-flex;align-items:center;gap:5px;' },
[icon(einzeln ? 'person' : 'groups'), einzeln ? 'Einzeln' : 'Sammel']);
}
function zeile(o) { function zeile(o) {
const e = entriesByOrder.value[String(o.id)]; const e = entriesByOrder.value[String(o.id)];
const faellig = faelligBis(o.date_created, o.shipping_day); const faellig = faelligBis(o.date_created, o.shipping_day);
const empf = e const ag = aufgabe(o);
? h('div', { style: 'line-height:1.5;' }, [ const eingeplant = eingeplantSet.value.has(String(o.id));
h('div', {}, empfKurz(e.erster) || '—'),
e.anzahl > 1 ? h('div', { style: 'color:var(--foreground-subdued);' }, '… ' + empfKurz(e.letzter)) : null,
])
: h('span', { style: 'color:var(--foreground-subdued);' }, '—');
const briefOk = !!o.brief_gedruckt;
const kuvOk = !!o.kuvert_gedruckt;
return h('tr', { key: 'o' + o.id }, [ return h('tr', { key: 'o' + o.id }, [
td(h('div', { style: 'line-height:1.4;' }, [ // Empfänger = Hauptinfo.
o.order_name ? h('div', { style: 'font-weight:500;color:var(--primary,#1e50a5);' }, o.order_name) : null, td(h('div', { style: 'line-height:1.45;' }, [
h('div', { style: 'font-weight:500;' }, kundeName(o) || `#${o.order_number}`), h('div', { style: 'font-weight:500;' }, empfKurz(e?.erster) || '—'),
h('div', { style: 'font-size:12.5px;color:var(--foreground-subdued);' }, `#${o.order_number} · ${o.entries_count || 0} Empf.`), (e && e.anzahl > 1) ? h('div', { style: 'font-size:12.5px;color:var(--foreground-subdued);' }, '… ' + empfKurz(e.letzter)) : null,
])),
// Auftrag = sekundär.
td(h('div', { style: 'line-height:1.4;font-size:13px;' }, [
o.order_name ? h('div', { style: 'font-weight:500;color:var(--primary,#1e50a5);' }, o.order_name) : null,
h('div', {}, kundeName(o) || `#${o.order_number}`),
h('div', { style: 'color:var(--foreground-subdued);' }, `#${o.order_number} · ${o.entries_count || 0} Empf.`),
])), ])),
td(empf),
td(h('span', {}, o.format?.name || FORMAT_LABEL[o.format?.key] || o.format?.key || '—')), td(h('span', {}, o.format?.name || FORMAT_LABEL[o.format?.key] || o.format?.key || '—')),
td(h('span', { style: `display:inline-flex;align-items:center;gap:5px;font-weight:500;color:${ag.c};` }, [icon(ag.i), ag.t])),
td(versandToggle(o)),
td(h('span', { style: faellig && faellig.getTime() < Date.now() ? 'color:var(--danger,#c62828);font-weight:500;' : '' }, dmY(faellig))), td(h('span', { style: faellig && faellig.getTime() < Date.now() ? 'color:var(--danger,#c62828);font-weight:500;' : '' }, dmY(faellig))),
td(h('div', { style: 'display:flex;flex-direction:column;gap:4px;align-items:flex-start;' }, [ td(h('div', { style: 'display:flex;flex-direction:column;gap:4px;align-items:flex-start;' }, [
druckBadge(briefOk, 'Brief'), druckBadge(!!o.brief_gedruckt, 'Brief'),
o.needs_envelope ? druckBadge(kuvOk, 'Kuvert') : h('span', { style: 'font-size:12px;color:var(--foreground-subdued);' }, 'ohne Kuvert'), o.needs_envelope ? druckBadge(!!o.kuvert_gedruckt, 'Kuvert') : null,
])), eingeplant ? h('span', { style: 'display:inline-flex;align-items:center;gap:3px;font-size:11.5px;font-weight:500;color:var(--primary,#1e50a5);' }, [icon('check'), 'eingeplant']) : null,
td(eingeplantSet.value.has(String(o.id)) ].filter(Boolean))),
? h('span', { style: 'display:inline-flex;align-items:center;gap:4px;font-size:12.5px;font-weight:500;color:var(--primary,#1e50a5);' }, [icon('check'), 'eingeplant'])
: h('span', { style: 'font-size:12.5px;color:var(--foreground-subdued);' }, 'nein')),
td(statusPill(o)), td(statusPill(o)),
]); ]);
} }
@@ -218,13 +244,24 @@ const Board = {
maschinenPanel(), maschinenPanel(),
loading.value loading.value
? h('div', { style: 'color:var(--foreground-subdued);' }, 'Lädt …') ? h('div', { style: 'color:var(--foreground-subdued);' }, 'Lädt …')
: h('div', { style: 'border:1px solid var(--border-normal);border-radius:12px;overflow:auto;background:var(--background-page,#fff);' }, [ : (() => {
h('table', { style: 'width:100%;border-collapse:collapse;' }, [ const GRUPPEN = [['im_druck', 'Im Druck'], ['kuvertieren', 'Kuvertierung'], ['versandfertig', 'Versandfertig'], ['eingegangen', 'Eingegangen']];
h('thead', {}, h('tr', {}, [th('Auftrag'), th('Empfänger (erster · letzter)'), th('Format'), th('Fällig'), th('Druck'), th('Eingeplant'), th('Status')])), const kopf = () => h('thead', {}, h('tr', {}, [th('Empfänger (erster · letzter)'), th('Auftrag'), th('Format'), th('Aufgabe'), th('Versand'), th('Fällig'), th('Stand'), th('Status')]));
h('tbody', {}, gefiltert.value.length ? gefiltert.value.map(zeile) : [h('tr', {}, h('td', { colspan: 7, style: 'padding:24px;text-align:center;color:var(--foreground-subdued);' }, 'Keine Aufträge.'))]), const sek = GRUPPEN.map(([st, label]) => {
]), const list = gefiltert.value.filter((o) => (o.production_status || 'eingegangen') === st);
]), if (!list.length) return null;
h('div', { style: 'margin-top:10px;font-size:12.5px;color:var(--foreground-subdued);' }, `${gefiltert.value.length} von ${orders.value.length} offenen Aufträgen`), const m = STATUS_META[st];
return h('div', { key: 'sec' + st, style: 'margin-bottom:22px;' }, [
h('div', { style: 'display:flex;align-items:center;gap:10px;margin-bottom:10px;' }, [
h('span', { style: `display:inline-flex;align-items:center;gap:6px;font-size:16px;font-weight:500;color:${m.fg};` }, [icon(m.icon), label]),
h('span', { style: 'font-size:13px;color:var(--foreground-subdued);' }, `${list.length} Auftrag/Aufträge`),
]),
h('div', { style: 'border:1px solid var(--border-normal);border-radius:12px;overflow:auto;background:var(--background-page,#fff);' },
[h('table', { style: 'width:100%;border-collapse:collapse;' }, [kopf(), h('tbody', {}, list.map(zeile))])]),
]);
}).filter(Boolean);
return sek.length ? h('div', {}, sek) : h('div', { style: 'padding:28px;text-align:center;color:var(--foreground-subdued);border:1px solid var(--border-normal);border-radius:12px;background:var(--background-page,#fff);' }, 'Keine offenen Aufträge.');
})(),
]); ]);
return h(resolveComponent('private-view'), { title: 'Übersicht' }, { default: () => inhalt }); return h(resolveComponent('private-view'), { title: 'Übersicht' }, { default: () => inhalt });
}; };