Board: Charge-Abstand + Aufgaben-Umschalter unter Weiteres; Kontakte/Statistik-Farben

- Board Charge-Karte: gleichmaessige Abstaende (Flex-Spalte), Notiz mit Label,
  aufgeklappte Teil-Auftraege klar abgetrennt + Luft (kein Aneinanderkleben)
- Board "Weiteres": Versandart, Frankierung, Kuvertierung, Brief/Kuvert gedruckt
  direkt umschaltbar (setOrderFeld); frankiert+versanddatum in Order-Abfrage ergaenzt
- Kontakte + Statistik: gleiche kontraststarke feste Farben + sichtbare Rahmen nachgezogen

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
s4luorth
2026-10-01 13:03:28 +02:00
parent 550abd30e7
commit a043a624d4
3 changed files with 58 additions and 42 deletions

View File

@@ -42,29 +42,29 @@ const FIELDS = ['id', 'salutation', 'first_name', 'last_name', 'company', 'stree
'source_order.order_number']; 'source_order.order_number'];
// ── Stil-Helfer ────────────────────────────────────────────────────────────── // ── Stil-Helfer ──────────────────────────────────────────────────────────────
const CARD = 'border:1px solid var(--border-subdued);border-radius:10px;padding:16px 18px;background:var(--background-page,#fff);box-shadow:0 1px 3px rgba(0,0,0,.08);'; const CARD = 'border:1px solid #d2dae6;border-radius:10px;padding:16px 18px;background:#ffffff;box-shadow:0 1px 3px rgba(0,0,0,.08);';
// Klar erkennbares Eingabefeld: kräftigere Umrandung + leicht getönter Grund, // Klar erkennbares Eingabefeld: kräftigere Umrandung + leicht getönter Grund,
// damit es sich deutlich von der weißen Karte abhebt. // damit es sich deutlich von der weißen Karte abhebt.
const S_INPUT = 'width:100%;padding:11px 12px;border:1.5px solid var(--border-normal,#c4cdde);border-radius:8px;background:var(--background-subdued,#eef1f7);color:var(--foreground-normal);font:inherit;font-size:15px;box-sizing:border-box;'; const S_INPUT = 'width:100%;padding:11px 12px;border:1.5px solid #b4c0d2;border-radius:8px;background:#eef2f8;color:#1b2430;font:inherit;font-size:15px;box-sizing:border-box;';
const LBL = 'font-size:12px;font-weight:600;color:var(--foreground-subdued);margin:0 0 4px;text-transform:uppercase;letter-spacing:.03em;'; const LBL = 'font-size:12px;font-weight:600;color:#566070;margin:0 0 4px;text-transform:uppercase;letter-spacing:.03em;';
function btn(label, onClick, kind) { function btn(label, onClick, kind) {
const base = 'cursor:pointer;border-radius:8px;padding:10px 16px;font:inherit;font-size:14px;border:1px solid;'; const base = 'cursor:pointer;border-radius:8px;padding:10px 16px;font:inherit;font-size:14px;border:1px solid;';
const style = kind === 'primary' const style = kind === 'primary'
? base + `border-color:${ACCENT};background:${ACCENT};color:#fff;` ? base + `border-color:${ACCENT};background:${ACCENT};color:#fff;`
: kind === 'ghost' : kind === 'ghost'
? base + 'border-color:transparent;background:transparent;color:var(--foreground-normal);' ? base + 'border-color:transparent;background:transparent;color:#1b2430;'
: base + 'border-color:var(--border-normal);background:var(--background-subdued);color:var(--foreground-normal);'; : base + 'border-color:#b4c0d2;background:#eef2f8;color:#1b2430;';
return h('button', { type: 'button', onClick, style }, label); return h('button', { type: 'button', onClick, style }, label);
} }
function chip(text, farbe) { function chip(text, farbe) {
return h('span', { return h('span', {
style: 'display:inline-flex;align-items:center;gap:4px;font-size:12px;padding:3px 10px;border-radius:20px;' style: 'display:inline-flex;align-items:center;gap:4px;font-size:12px;padding:3px 10px;border-radius:20px;'
+ `background:${farbe || 'var(--background-subdued)'};color:#fff;white-space:nowrap;`, + `background:${farbe || '#eef2f8'};color:#fff;white-space:nowrap;`,
}, text); }, text);
} }
function subdued(text, extra) { function subdued(text, extra) {
return h('div', { style: `color:var(--foreground-subdued);font-size:14px;${extra || ''}` }, text); return h('div', { style: `color:#566070;font-size:14px;${extra || ''}` }, text);
} }
function feld(label, node) { function feld(label, node) {
return h('div', { style: 'margin-bottom:14px;' }, [h('div', { style: LBL }, label), node]); return h('div', { style: 'margin-bottom:14px;' }, [h('div', { style: LBL }, label), node]);
@@ -260,7 +260,7 @@ const Board = defineComponent({
return h('div', { return h('div', {
onClick: () => { aktivId.value = k.id; fehler.value = ''; }, onClick: () => { aktivId.value = k.id; fehler.value = ''; },
style: CARD + 'cursor:pointer;margin-bottom:10px;display:flex;justify-content:space-between;align-items:flex-start;gap:12px;' style: CARD + 'cursor:pointer;margin-bottom:10px;display:flex;justify-content:space-between;align-items:flex-start;gap:12px;'
+ (istGewaehlt(k.id) ? 'outline:2px solid var(--primary,#1E50A5);' : ''), + (istGewaehlt(k.id) ? 'outline:2px solid #1E50A5;' : ''),
}, [ }, [
h('input', { h('input', {
type: 'checkbox', checked: istGewaehlt(k.id), type: 'checkbox', checked: istGewaehlt(k.id),
@@ -294,7 +294,7 @@ const Board = defineComponent({
]), ]),
]), ]),
h('div', { style: 'display:flex;gap:8px;flex-wrap:wrap;align-items:center;justify-content:space-between;' }, [ h('div', { style: 'display:flex;gap:8px;flex-wrap:wrap;align-items:center;justify-content:space-between;' }, [
h('label', { style: 'display:flex;align-items:center;gap:6px;font-size:13px;color:var(--foreground-subdued);cursor:pointer;' }, [ h('label', { style: 'display:flex;align-items:center;gap:6px;font-size:13px;color:#566070;cursor:pointer;' }, [
h('input', { type: 'checkbox', h('input', { type: 'checkbox',
checked: gefiltert.value.length > 0 && auswahl.value.size >= gefiltert.value.length, checked: gefiltert.value.length > 0 && auswahl.value.size >= gefiltert.value.length,
onChange: (e) => { e.target.checked ? selAlle() : selKeine(); }, style: 'width:16px;height:16px;cursor:pointer;' }), onChange: (e) => { e.target.checked ? selAlle() : selKeine(); }, style: 'width:16px;height:16px;cursor:pointer;' }),
@@ -308,7 +308,7 @@ const Board = defineComponent({
]); ]);
// Bulk-Leiste, sobald mindestens ein Kontakt ausgewählt ist. // Bulk-Leiste, sobald mindestens ein Kontakt ausgewählt ist.
const bulk = auswahl.value.size ? h('div', { const bulk = auswahl.value.size ? h('div', {
style: 'position:sticky;top:8px;z-index:5;background:var(--primary,#1E50A5);color:#fff;border-radius:10px;padding:10px 14px;margin-bottom:12px;display:flex;gap:10px;flex-wrap:wrap;align-items:center;', style: 'position:sticky;top:8px;z-index:5;background:#1E50A5;color:#fff;border-radius:10px;padding:10px 14px;margin-bottom:12px;display:flex;gap:10px;flex-wrap:wrap;align-items:center;',
}, [ }, [
h('span', { style: 'font-weight:600;' }, `${auswahl.value.size} ausgewählt`), h('span', { style: 'font-weight:600;' }, `${auswahl.value.size} ausgewählt`),
h('span', {}, 'Status:'), h('span', {}, 'Status:'),
@@ -347,10 +347,10 @@ const Board = defineComponent({
btn('◀ Liste', () => { aktivId.value = null; }, 'ghost'), btn('◀ Liste', () => { aktivId.value = null; }, 'ghost'),
h('div', { style: 'display:flex;align-items:center;gap:8px;' }, [ h('div', { style: 'display:flex;align-items:center;gap:8px;' }, [
h('button', { type: 'button', disabled: idx <= 0, onClick: () => geh(-1), h('button', { type: 'button', disabled: idx <= 0, onClick: () => geh(-1),
style: 'cursor:pointer;border:1px solid var(--border-normal);background:var(--background-subdued);border-radius:8px;padding:8px 12px;font-size:16px;' + (idx <= 0 ? 'opacity:.4;cursor:not-allowed;' : '') }, '◀'), style: 'cursor:pointer;border:1px solid #b4c0d2;background:#eef2f8;border-radius:8px;padding:8px 12px;font-size:16px;' + (idx <= 0 ? 'opacity:.4;cursor:not-allowed;' : '') }, '◀'),
subdued(gesamt ? `${idx + 1} / ${gesamt}` : '–'), subdued(gesamt ? `${idx + 1} / ${gesamt}` : '–'),
h('button', { type: 'button', disabled: idx >= gesamt - 1, onClick: () => geh(1), h('button', { type: 'button', disabled: idx >= gesamt - 1, onClick: () => geh(1),
style: 'cursor:pointer;border:1px solid var(--border-normal);background:var(--background-subdued);border-radius:8px;padding:8px 12px;font-size:16px;' + (idx >= gesamt - 1 ? 'opacity:.4;cursor:not-allowed;' : '') }, '▶'), style: 'cursor:pointer;border:1px solid #b4c0d2;background:#eef2f8;border-radius:8px;padding:8px 12px;font-size:16px;' + (idx >= gesamt - 1 ? 'opacity:.4;cursor:not-allowed;' : '') }, '▶'),
]), ]),
]); ]);
@@ -409,10 +409,10 @@ const Board = defineComponent({
]); ]);
const preset = (label, d) => h('button', { type: 'button', const preset = (label, d) => h('button', { type: 'button',
style: 'cursor:pointer;border-radius:20px;padding:6px 12px;font:inherit;font-size:13px;border:1px solid var(--border-normal);' style: 'cursor:pointer;border-radius:20px;padding:6px 12px;font:inherit;font-size:13px;border:1px solid #b4c0d2;'
+ (rueckruf.date === ymd(isoDatum(d)) ? `background:${ACCENT};color:#fff;` : 'background:var(--background-page);color:var(--foreground-normal);'), + (rueckruf.date === ymd(isoDatum(d)) ? `background:${ACCENT};color:#fff;` : 'background:#ffffff;color:#1b2430;'),
onClick: () => { rueckruf.date = ymd(isoDatum(d)); } }, label); onClick: () => { rueckruf.date = ymd(isoDatum(d)); } }, label);
const rueckrufForm = rueckruf.open ? h('div', { style: 'border:1px solid var(--border-normal);border-radius:10px;padding:14px;margin-top:10px;margin-bottom:12px;background:var(--background-subdued);' }, [ const rueckrufForm = rueckruf.open ? h('div', { style: 'border:1px solid #b4c0d2;border-radius:10px;padding:14px;margin-top:10px;margin-bottom:12px;background:#eef2f8;' }, [
h('div', { style: 'display:flex;gap:6px;flex-wrap:wrap;margin-bottom:10px;' }, [preset('Morgen', 1), preset('In 3 Tagen', 3), preset('Nächste Woche', 7)]), h('div', { style: 'display:flex;gap:6px;flex-wrap:wrap;margin-bottom:10px;' }, [preset('Morgen', 1), preset('In 3 Tagen', 3), preset('Nächste Woche', 7)]),
h('div', { style: 'display:flex;gap:10px;flex-wrap:wrap;' }, [ h('div', { style: 'display:flex;gap:10px;flex-wrap:wrap;' }, [
h('div', { style: 'flex:2;min-width:150px;' }, feld('Datum', h('input', { type: 'date', value: rueckruf.date, style: S_INPUT, onInput: (e) => { rueckruf.date = e.target.value; } }))), h('div', { style: 'flex:2;min-width:150px;' }, feld('Datum', h('input', { type: 'date', value: rueckruf.date, style: S_INPUT, onInput: (e) => { rueckruf.date = e.target.value; } }))),
@@ -421,18 +421,18 @@ const Board = defineComponent({
h('div', { style: 'display:flex;gap:8px;' }, [btn('Erinnerung setzen', () => planeRueckruf(k), 'primary'), btn('Abbrechen', () => { rueckruf.open = false; }, 'ghost')]), h('div', { style: 'display:flex;gap:8px;' }, [btn('Erinnerung setzen', () => planeRueckruf(k), 'primary'), btn('Abbrechen', () => { rueckruf.open = false; }, 'ghost')]),
]) : null; ]) : null;
const mailForm = mailMich.open ? h('div', { style: 'border:1px solid var(--border-normal);border-radius:10px;padding:14px;margin-top:10px;margin-bottom:12px;background:var(--background-subdued);' }, [ const mailForm = mailMich.open ? h('div', { style: 'border:1px solid #b4c0d2;border-radius:10px;padding:14px;margin-top:10px;margin-bottom:12px;background:#eef2f8;' }, [
feld('Notiz an mich', h('textarea', { value: mailMich.note, rows: 3, placeholder: 'Was soll dir per Mail zugehen? (Eckdaten kommen automatisch mit)', style: S_INPUT + 'resize:vertical;', onInput: (e) => { mailMich.note = e.target.value; } })), feld('Notiz an mich', h('textarea', { value: mailMich.note, rows: 3, placeholder: 'Was soll dir per Mail zugehen? (Eckdaten kommen automatisch mit)', style: S_INPUT + 'resize:vertical;', onInput: (e) => { mailMich.note = e.target.value; } })),
h('div', { style: 'display:flex;gap:8px;' }, [btn('Mail an mich senden', () => sendeMail(k), 'primary'), btn('Abbrechen', () => { mailMich.open = false; }, 'ghost')]), h('div', { style: 'display:flex;gap:8px;' }, [btn('Mail an mich senden', () => sendeMail(k), 'primary'), btn('Abbrechen', () => { mailMich.open = false; }, 'ghost')]),
]) : null; ]) : null;
const offene = aktionen.value.filter((a) => a.type === 'rueckruf' && a.status === 'offen'); const offene = aktionen.value.filter((a) => a.type === 'rueckruf' && a.status === 'offen');
const offeneListe = offene.length ? h('div', { style: 'margin-bottom:12px;display:flex;flex-direction:column;gap:6px;' }, offene.map((a) => h('div', { const offeneListe = offene.length ? h('div', { style: 'margin-bottom:12px;display:flex;flex-direction:column;gap:6px;' }, offene.map((a) => h('div', {
style: 'display:flex;align-items:center;justify-content:space-between;gap:10px;border:1px solid var(--border-normal);border-radius:8px;padding:8px 12px;background:var(--background-page);', style: 'display:flex;align-items:center;justify-content:space-between;gap:10px;border:1px solid #b4c0d2;border-radius:8px;padding:8px 12px;background:#ffffff;',
}, [ }, [
h('div', { style: 'font-size:13px;' }, [ h('div', { style: 'font-size:13px;' }, [
h('span', { style: 'font-weight:600;' }, '📞 Rückruf ' + (a.due_date ? new Date(a.due_date).toLocaleString('de-DE', { dateStyle: 'short', timeStyle: 'short' }) : '')), h('span', { style: 'font-weight:600;' }, '📞 Rückruf ' + (a.due_date ? new Date(a.due_date).toLocaleString('de-DE', { dateStyle: 'short', timeStyle: 'short' }) : '')),
a.note ? h('div', { style: 'color:var(--foreground-subdued);margin-top:2px;white-space:pre-wrap;' }, a.note) : null, a.note ? h('div', { style: 'color:#566070;margin-top:2px;white-space:pre-wrap;' }, a.note) : null,
]), ]),
btn('✓ erledigt', () => erledige(a, k), 'default'), btn('✓ erledigt', () => erledige(a, k), 'default'),
]))) : null; ]))) : null;
@@ -448,12 +448,12 @@ const Board = defineComponent({
const inhalt = h('div', { style: 'max-width:760px;margin:0 auto;padding:20px 16px 60px;' }, [ const inhalt = h('div', { style: 'max-width:760px;margin:0 auto;padding:20px 16px 60px;' }, [
// Aktions-Buttons: am Desktop nebeneinander (gleich breit), am Handy gestapelt in voller Breite. // Aktions-Buttons: am Desktop nebeneinander (gleich breit), am Handy gestapelt in voller Breite.
h('style', {}, '.sk-akt{display:flex;gap:8px;flex-wrap:wrap;}.sk-akt>*{flex:1 1 auto;justify-content:center;}@media(max-width:600px){.sk-akt{flex-direction:column;}.sk-akt>*{width:100%;flex:none;}}'), h('style', {}, '.sk-akt{display:flex;gap:8px;flex-wrap:wrap;}.sk-akt>*{flex:1 1 auto;justify-content:center;}@media(max-width:600px){.sk-akt{flex-direction:column;}.sk-akt>*{width:100%;flex:none;}}'),
fehler.value ? h('div', { style: 'background:var(--danger-25,rgba(198,40,40,.12));color:var(--danger,#c62828);padding:10px 14px;border-radius:8px;margin-bottom:12px;font-size:14px;' }, fehler.value) : null, fehler.value ? h('div', { style: 'background:#fbe3e0;color:#c0392b;padding:10px 14px;border-radius:8px;margin-bottom:12px;font-size:14px;' }, fehler.value) : null,
meldung.value ? h('div', { style: 'background:var(--success-25,rgba(46,125,50,.15));color:var(--success,#2e7d32);padding:10px 14px;border-radius:8px;margin-bottom:12px;font-size:14px;' }, meldung.value) : null, meldung.value ? h('div', { style: 'background:#dff2e2;color:#1b6b2e;padding:10px 14px;border-radius:8px;margin-bottom:12px;font-size:14px;' }, meldung.value) : null,
aktivId.value ? detailAnsicht() : listenAnsicht(), aktivId.value ? detailAnsicht() : listenAnsicht(),
]); ]);
return h(resolveComponent('private-view'), { title: 'Kontakte' }, { return h(resolveComponent('private-view'), { title: 'Kontakte' }, {
default: () => h('div', { style: 'min-height:100%;background:var(--background-subdued,#f1f4f9);' }, [inhalt]), default: () => h('div', { style: 'min-height:100%;background:#eef2f8;' }, [inhalt]),
}); });
}; };
}, },

View File

@@ -263,7 +263,7 @@ const Board = defineComponent({
// Basis-Felder ohne die evtl. noch nicht gebootstrapten Felder – diese // Basis-Felder ohne die evtl. noch nicht gebootstrapten Felder – diese
// werden separat versucht, damit ein fehlendes Feld nicht das ganze Board sperrt. // werden separat versucht, damit ein fehlendes Feld nicht das ganze Board sperrt.
const ORDER_FELDER = 'id,order_number,order_name,status,payment_status,production_status,needs_envelope,envelope_format,' const ORDER_FELDER = 'id,order_number,order_name,status,payment_status,production_status,needs_envelope,envelope_format,'
+ 'envelope_labeling,shipping_type,shipping_day,keep_contacts,entries_count,count_inland,count_ausland,brief_gedruckt,kuvert_gedruckt,' + 'envelope_labeling,shipping_type,shipping_day,keep_contacts,entries_count,count_inland,count_ausland,brief_gedruckt,kuvert_gedruckt,frankiert,versanddatum,'
+ 'date_updated,date_created,format.key,format.name,product.name,product.type,product.input_mode,' + 'date_updated,date_created,format.key,format.name,product.name,product.type,product.input_mode,'
+ 'source,api_client.id,api_client.name,motif_mode,motif.name,shipping_address,' + 'source,api_client.id,api_client.name,motif_mode,motif.name,shipping_address,'
+ 'source_file,internal_note,approval_status,approval_note,linked_order.id,linked_order.order_number,' + 'source_file,internal_note,approval_status,approval_note,linked_order.id,linked_order.order_number,'
@@ -393,6 +393,11 @@ const Board = defineComponent({
try { await api.patch('/items/orders/' + order.id, { internal_note: val }); order.internal_note = val; } try { await api.patch('/items/orders/' + order.id, { internal_note: val }); order.internal_note = val; }
catch { fehler.value = 'Notiz konnte nicht gespeichert werden.'; } catch { fehler.value = 'Notiz konnte nicht gespeichert werden.'; }
} }
// Einzelne Auftrags-Felder (Versandart, Frankierung, Druck-/Kuvert-Flags) direkt setzen.
async function setOrderFeld(order, patch) {
try { await api.patch('/items/orders/' + order.id, patch); Object.assign(order, patch); flash(`${order.order_number}: aktualisiert`); }
catch (e) { fehler.value = e?.message || 'Konnte nicht gespeichert werden.'; }
}
// Verknüpften Auftrag (Kuverts) setzen/ändern/entfernen – per Auftragsnummer. // Verknüpften Auftrag (Kuverts) setzen/ändern/entfernen – per Auftragsnummer.
async function setOrderLink(order, nr) { async function setOrderLink(order, nr) {
const num = String(nr || '').trim(); const num = String(nr || '').trim();
@@ -1381,6 +1386,17 @@ const Board = defineComponent({
nm ? h('div', { style: 'color:#566070;' }, `Erster: ${nm.erste}${nm.anzahl > 1 ? ` · Letzter: ${nm.letzte}` : ''}`) : null, nm ? h('div', { style: 'color:#566070;' }, `Erster: ${nm.erste}${nm.anzahl > 1 ? ` · Letzter: ${nm.letzte}` : ''}`) : null,
]), ]),
]), ]),
// Aufgaben umstellen: Versandart, Frankierung, Kuvertierung, Druck-Flags.
h('div', { style: 'border-top:1px solid #d2dae6;padding-top:14px;' }, [
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('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('Kuvertierung'), 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('Brief gedruckt'), segToggle(o.brief_gedruckt ? 'ja' : 'nein', [{ key: 'ja', label: 'Ja' }, { key: 'nein', label: 'Nein' }], (k) => setOrderFeld(o, { brief_gedruckt: k === 'ja' }))]),
h('div', {}, [feldLabel('Kuvert gedruckt'), segToggle(o.kuvert_gedruckt ? 'ja' : 'nein', [{ key: 'ja', label: 'Ja' }, { key: 'nein', label: 'Nein' }], (k) => setOrderFeld(o, { kuvert_gedruckt: k === 'ja' }))]),
]),
]),
// Verknüpfungen: beschriftete Felder. // Verknüpfungen: beschriftete Felder.
h('div', { style: 'display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:14px;' }, [ h('div', { style: 'display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:14px;' }, [
h('div', {}, [feldLabel('Verknüpfter Auftrag'), h('div', {}, [feldLabel('Verknüpfter Auftrag'),
@@ -1552,15 +1568,15 @@ const Board = defineComponent({
const sumKuvert = c.orders.reduce((a, o) => a + (o.needs_envelope ? (Number(o.entries_count) || 0) : 0), 0); const sumKuvert = c.orders.reduce((a, o) => a + (o.needs_envelope ? (Number(o.entries_count) || 0) : 0), 0);
const fmts = [...new Set(c.orders.map((o) => o.format?.name || o.format?.key).filter(Boolean))]; const fmts = [...new Set(c.orders.map((o) => o.format?.name || o.format?.key).filter(Boolean))];
const versands = [...new Set(c.orders.map((o) => o.shipping_type === 'einzeln' ? 'Einzel' : 'Sammel'))]; const versands = [...new Set(c.orders.map((o) => o.shipping_type === 'einzeln' ? 'Einzel' : 'Sammel'))];
const chargeSpecs = h('div', { style: 'display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:10px;margin-top:12px;' }, [ const chargeSpecs = h('div', { style: 'display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:10px;' }, [
tile('Schriftstücke', String(sumSchrift), true), tile('Schriftstücke', String(sumSchrift), true),
tile('Kuverts', sumKuvert ? String(sumKuvert) : 'ohne', true), tile('Kuverts', sumKuvert ? String(sumKuvert) : 'ohne', true),
tile('Format', fmts.length ? fmts.join(', ') : '—'), tile('Format', fmts.length ? fmts.join(', ') : '—'),
tile('Versand', versands.length === 1 ? versands[0] : versands.join('/')), tile('Versand', versands.length === 1 ? versands[0] : versands.join('/')),
]); ]);
const body = auf ? h('div', { style: 'margin-top:12px;display:flex;flex-direction:column;gap:2px;' }, c.orders.map((o) => orderCard(o, opts))) : null; const body = auf ? h('div', { style: 'margin-top:2px;padding-top:16px;border-top:1px solid #d2dae6;display:flex;flex-direction:column;gap:14px;' }, c.orders.map((o) => orderCard(o, opts))) : null;
const chargeNotiz = notizFeld(chargeNotizen.value[c.key], (v) => setChargeNotiz(c.key, v), 'Notiz zur Charge …'); const chargeNotiz = h('div', {}, [feldLabel('Notiz zur Charge'), notizFeld(chargeNotizen.value[c.key], (v) => setChargeNotiz(c.key, v), 'Notiz zur Charge …')]);
return h('div', { key: 'c' + c.key, style: 'border:1px solid #b4c0d2;border-radius:12px;background:#ffffff;padding:16px 18px;margin-bottom:14px;' }, [kopf, chargeSpecs, chargeNotiz, body]); return h('div', { key: 'c' + c.key, style: 'border:1px solid #b4c0d2;border-radius:12px;background:#ffffff;padding:18px 20px;margin-bottom:16px;display:flex;flex-direction:column;gap:16px;' }, [kopf, chargeSpecs, chargeNotiz, body].filter(Boolean));
} }
// Aufträge einer Ansicht rendern: bei aktiver Suche als EINZELaufträge (damit // Aufträge einer Ansicht rendern: bei aktiver Suche als EINZELaufträge (damit

View File

@@ -14,12 +14,12 @@ import { defineComponent, ref, computed, onMounted, h, resolveComponent } from '
import { useApi } from '@directus/extensions-sdk'; import { useApi } from '@directus/extensions-sdk';
const ACCENT = '#1E50A5'; const ACCENT = '#1E50A5';
const CARD = 'border:1px solid var(--border-subdued);border-radius:10px;padding:16px 18px;background:var(--background-page,#fff);box-shadow:0 1px 3px rgba(0,0,0,.08);'; const CARD = 'border:1px solid #d2dae6;border-radius:10px;padding:16px 18px;background:#ffffff;box-shadow:0 1px 3px rgba(0,0,0,.08);';
const S_INPUT = 'border:1px solid var(--border-normal,#c9d3e0);border-radius:8px;padding:9px 12px;font:inherit;font-size:14px;background:var(--background-page,#fff);color:var(--foreground-normal);outline:none;width:100%;'; const S_INPUT = 'border:1px solid #b4c0d2;border-radius:8px;padding:9px 12px;font:inherit;font-size:14px;background:#ffffff;color:#1b2430;outline:none;width:100%;';
const subdued = (t, extra) => h('div', { style: `color:var(--foreground-subdued);font-size:13px;${extra || ''}` }, t); const subdued = (t, extra) => h('div', { style: `color:#566070;font-size:13px;${extra || ''}` }, t);
function chip(text, farbe) { function chip(text, farbe) {
return h('span', { style: `display:inline-block;padding:3px 10px;border-radius:999px;font-size:12px;font-weight:600;background:${farbe || 'var(--background-subdued,#eef2f7)'};color:var(--foreground-normal);` }, text); return h('span', { style: `display:inline-block;padding:3px 10px;border-radius:999px;font-size:12px;font-weight:600;background:${farbe || '#eef2f8'};color:#1b2430;` }, text);
} }
const kundeName = (c) => [c?.first_name, c?.last_name].filter(Boolean).join(' ') || c?.email || '—'; const kundeName = (c) => [c?.first_name, c?.last_name].filter(Boolean).join(' ') || c?.email || '—';
@@ -84,17 +84,17 @@ const Board = defineComponent({
const toggle = (key) => { const s = new Set(offen.value); s.has(key) ? s.delete(key) : s.add(key); offen.value = s; }; const toggle = (key) => { const s = new Set(offen.value); s.has(key) ? s.delete(key) : s.add(key); offen.value = s; };
const th = (t, extra) => h('th', { style: `text-align:${extra || 'left'};padding:9px 12px;border-bottom:2px solid var(--border-subdued);font-size:12px;text-transform:uppercase;letter-spacing:.04em;color:var(--foreground-subdued);white-space:nowrap;` }, t); const th = (t, extra) => h('th', { style: `text-align:${extra || 'left'};padding:9px 12px;border-bottom:2px solid #d2dae6;font-size:12px;text-transform:uppercase;letter-spacing:.04em;color:#566070;white-space:nowrap;` }, t);
const td = (t, extra) => h('td', { style: `padding:9px 12px;border-bottom:1px solid var(--border-subdued);${extra || ''}` }, t); const td = (t, extra) => h('td', { style: `padding:9px 12px;border-bottom:1px solid #d2dae6;${extra || ''}` }, t);
function auftragsListe(z) { function auftragsListe(z) {
const rows = z.orders.slice().sort((a, b) => String(b.versandt_am || '').localeCompare(String(a.versandt_am || ''))); const rows = z.orders.slice().sort((a, b) => String(b.versandt_am || '').localeCompare(String(a.versandt_am || '')));
return h('tr', {}, h('td', { colspan: 5, style: 'padding:0 12px 12px;background:var(--background-subdued,#f7f9fc);' }, [ return h('tr', {}, h('td', { colspan: 5, style: 'padding:0 12px 12px;background:#eef2f8;' }, [
h('table', { style: 'width:100%;border-collapse:collapse;font-size:13px;' }, [ h('table', { style: 'width:100%;border-collapse:collapse;font-size:13px;' }, [
h('tbody', {}, rows.map((o) => h('tr', {}, [ h('tbody', {}, rows.map((o) => h('tr', {}, [
td(`#${o.order_number}`, 'font-weight:600;'), td(`#${o.order_number}`, 'font-weight:600;'),
td(String(o.versandt_am || o.date_updated || '').slice(0, 10) || '—', 'color:var(--foreground-subdued);'), td(String(o.versandt_am || o.date_updated || '').slice(0, 10) || '—', 'color:#566070;'),
td(o.product?.name || '—', 'color:var(--foreground-subdued);'), td(o.product?.name || '—', 'color:#566070;'),
td(`🇩🇪 ${inlandVon(o)}`, 'text-align:right;'), td(`🇩🇪 ${inlandVon(o)}`, 'text-align:right;'),
td(`🌍 ${auslandVon(o)}`, 'text-align:right;'), td(`🌍 ${auslandVon(o)}`, 'text-align:right;'),
]))), ]))),
@@ -111,12 +111,12 @@ const Board = defineComponent({
h('input', { type: 'search', value: suche.value, placeholder: '🔎 Kunde, E-Mail, Auftragsnr. …', h('input', { type: 'search', value: suche.value, placeholder: '🔎 Kunde, E-Mail, Auftragsnr. …',
style: S_INPUT + 'width:auto;flex:1;min-width:200px;', onInput: (e) => { suche.value = e.target.value; } }), style: S_INPUT + 'width:auto;flex:1;min-width:200px;', onInput: (e) => { suche.value = e.target.value; } }),
h('button', { type: 'button', onClick: laden, h('button', { type: 'button', onClick: laden,
style: 'cursor:pointer;border:1px solid var(--border-normal);background:var(--background-subdued);border-radius:7px;padding:8px 14px;font:inherit;font-size:13px;' }, '↻'), style: 'cursor:pointer;border:1px solid #b4c0d2;background:#eef2f8;border-radius:7px;padding:8px 14px;font:inherit;font-size:13px;' }, '↻'),
]); ]);
const summen = h('div', { style: 'display:flex;gap:8px;flex-wrap:wrap;margin-bottom:16px;' }, [ const summen = h('div', { style: 'display:flex;gap:8px;flex-wrap:wrap;margin-bottom:16px;' }, [
chip(`🇩🇪 Inland: ${s.inland}`, 'var(--primary-25,rgba(30,80,165,.14))'), chip(`🇩🇪 Inland: ${s.inland}`, '#e3ecfa'),
chip(`🌍 Ausland: ${s.ausland}`, 'var(--warning-25,rgba(230,160,0,.15))'), chip(`🌍 Ausland: ${s.ausland}`, '#fbeccb'),
chip(`Σ Gesamt: ${s.gesamt} Briefe`, 'var(--success-25,rgba(46,125,50,.15))'), chip(`Σ Gesamt: ${s.gesamt} Briefe`, '#dff2e2'),
]); ]);
const inhalt = loading.value ? subdued('Lädt …') const inhalt = loading.value ? subdued('Lädt …')
: !s.zeilen.length ? subdued('Keine versendeten Briefe in diesem Monat.') : !s.zeilen.length ? subdued('Keine versendeten Briefe in diesem Monat.')
@@ -129,7 +129,7 @@ const Board = defineComponent({
onClick: () => toggle(z.key), onClick: () => toggle(z.key),
style: 'cursor:pointer;', style: 'cursor:pointer;',
}, [ }, [
td(auf ? '▾' : '▸', 'color:var(--foreground-subdued);width:24px;'), td(auf ? '▾' : '▸', 'color:#566070;width:24px;'),
td(h('div', {}, [ td(h('div', {}, [
h('div', { style: 'font-weight:600;' }, z.kunde), h('div', { style: 'font-weight:600;' }, z.kunde),
z.email ? subdued(z.email, 'font-size:12px;') : null, z.email ? subdued(z.email, 'font-size:12px;') : null,
@@ -143,11 +143,11 @@ const Board = defineComponent({
]), ]),
]); ]);
const seite = h('div', { style: 'max-width:960px;margin:0 auto;padding:24px 20px 60px;' }, [ const seite = h('div', { style: 'max-width:960px;margin:0 auto;padding:24px 20px 60px;' }, [
fehler.value ? h('div', { style: 'background:var(--danger-25,rgba(198,40,40,.12));color:var(--danger,#c62828);padding:10px 14px;border-radius:8px;margin-bottom:12px;font-size:14px;' }, fehler.value) : null, fehler.value ? h('div', { style: 'background:#fbe3e0;color:#c0392b;padding:10px 14px;border-radius:8px;margin-bottom:12px;font-size:14px;' }, fehler.value) : null,
kopf, summen, inhalt, kopf, summen, inhalt,
]); ]);
return h(resolveComponent('private-view'), { title: 'Statistik' }, { return h(resolveComponent('private-view'), { title: 'Statistik' }, {
default: () => h('div', { style: 'min-height:100%;background:var(--background-subdued,#f1f4f9);' }, [seite]), default: () => h('div', { style: 'min-height:100%;background:#eef2f8;' }, [seite]),
}); });
}; };
}, },