Board: Karte neu gedacht (Name fuehrt) + echte Icons statt Emoji
Auftragskarte: Kunden-/Empfaengername als Hero, Anzahl Schriftstuecke gross, Status als klarer farbiger Pill (Icon + Dropdown), Druckfortschritt als eigene Zeile (was/wann gedruckt), Bestellnummer klein in der Subzeile. Linker Rand faerbt nach Status. Emoji durch Directus v-icon (Material Symbols) ersetzt in Navigation, Karten, Buttons, Charge-/Angebotskarten, Bulk-Leiste, Bestand. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -53,21 +53,30 @@ function faelligBis(dateCreated, shippingDay) {
|
|||||||
const dmY = (dt) => dt ? `${String(dt.getDate()).padStart(2, '0')}.${String(dt.getMonth() + 1).padStart(2, '0')}.${dt.getFullYear()}` : '';
|
const dmY = (dt) => dt ? `${String(dt.getDate()).padStart(2, '0')}.${String(dt.getMonth() + 1).padStart(2, '0')}.${dt.getFullYear()}` : '';
|
||||||
|
|
||||||
const TABS = [
|
const TABS = [
|
||||||
{ key: 'suche', label: '🔎 Suche' },
|
{ key: 'suche', label: 'Suche', icon: 'search' },
|
||||||
{ key: 'neu', label: '🆕 Neu' },
|
{ key: 'neu', label: 'Neu', icon: 'inbox' },
|
||||||
{ key: 'warteschlange', label: '⏳ Warteschlange' },
|
{ key: 'warteschlange', label: 'Warteschlange', icon: 'hourglass_empty' },
|
||||||
{ key: 'druck', label: '🖨 Im Druck' },
|
{ key: 'druck', label: 'Im Druck', icon: 'print' },
|
||||||
{ key: 'kuvertierung', label: '✉ Kuvertierung' },
|
{ key: 'kuvertierung', label: 'Kuvertierung', icon: 'mail' },
|
||||||
{ key: 'versandfertig', label: '📦 Versandfertig' },
|
{ key: 'versandfertig', label: 'Versandfertig', icon: 'inventory_2' },
|
||||||
{ key: 'versendet', label: '🚚 Versendet' },
|
{ key: 'versendet', label: 'Versendet', icon: 'local_shipping' },
|
||||||
{ key: 'batch', label: '📦 Übermittlungen' },
|
{ key: 'batch', label: 'Übermittlungen', icon: 'swap_horiz' },
|
||||||
{ key: 'bestand', label: '🧾 Bestand' },
|
{ key: 'bestand', label: 'Bestand', icon: 'shelves' },
|
||||||
{ key: 'archiv', label: '🗄 Archiv' },
|
{ key: 'archiv', label: 'Archiv', icon: 'archive' },
|
||||||
];
|
];
|
||||||
const STATUS_LABEL = {
|
const STATUS_LABEL = {
|
||||||
eingegangen: 'Eingegangen', im_druck: 'Im Druck', kuvertieren: 'Kuvertierung',
|
eingegangen: 'Eingegangen', im_druck: 'Im Druck', kuvertieren: 'Kuvertierung',
|
||||||
versandfertig: 'Versandfertig', versendet: 'Versendet', storniert: 'Storniert',
|
versandfertig: 'Versandfertig', versendet: 'Versendet', storniert: 'Storniert',
|
||||||
};
|
};
|
||||||
|
// Farbe + Material-Icon je Produktionsstatus (für den klaren Status-Pill).
|
||||||
|
const STATUS_META = {
|
||||||
|
eingegangen: { bg: 'var(--background-subdued,#eef1f6)', fg: 'var(--foreground-subdued)', icon: 'inbox' },
|
||||||
|
im_druck: { bg: 'rgba(30,80,165,.12)', fg: 'var(--primary,#1e50a5)', icon: 'print' },
|
||||||
|
kuvertieren: { bg: 'rgba(106,63,208,.12)', fg: '#6a3fd0', icon: 'mail' },
|
||||||
|
versandfertig: { bg: 'rgba(230,160,0,.16)', fg: '#8a6d00', icon: 'inventory_2' },
|
||||||
|
versendet: { bg: 'rgba(46,125,50,.14)', fg: 'var(--success,#2e7d32)', icon: 'local_shipping' },
|
||||||
|
storniert: { bg: 'rgba(198,40,40,.12)', fg: 'var(--danger,#c62828)', icon: 'cancel' },
|
||||||
|
};
|
||||||
// Tage, nach denen ein versendeter Auftrag automatisch ins Archiv wandert.
|
// Tage, nach denen ein versendeter Auftrag automatisch ins Archiv wandert.
|
||||||
const ARCHIV_TAGE = 14;
|
const ARCHIV_TAGE = 14;
|
||||||
const PARAM_FELDER = [
|
const PARAM_FELDER = [
|
||||||
@@ -90,15 +99,20 @@ const ACCENT = 'var(--primary,#1E50A5)'; // Aufträge / Druck → blau (Theme-P
|
|||||||
const FARBE_MUSTER = '#d13438'; // Musteranfrage → rot
|
const FARBE_MUSTER = '#d13438'; // Musteranfrage → rot
|
||||||
const FARBE_ANGEBOT = '#e07b00'; // Angebotsanfrage → orange
|
const FARBE_ANGEBOT = '#e07b00'; // Angebotsanfrage → orange
|
||||||
|
|
||||||
|
// Echtes Directus-Icon (Material Symbols) statt Emoji → nativer Look.
|
||||||
|
// Rendert während des Renders (resolveComponent hat dann die aktive Instanz);
|
||||||
|
// fällt ein Name mal aus, bleibt der Text-Button sichtbar.
|
||||||
|
const icon = (name, extra = {}) => h(resolveComponent('v-icon'), { name, small: true, ...extra });
|
||||||
// Button: klarer Primär (gefüllt), ruhiger Sekundär (Umriss), dezenter Danger.
|
// Button: klarer Primär (gefüllt), ruhiger Sekundär (Umriss), dezenter Danger.
|
||||||
function btn(label, onClick, kind) {
|
// ic = optionaler Material-Symbols-Iconname (führend).
|
||||||
const base = 'cursor:pointer;border-radius:8px;padding:8px 16px;font:inherit;font-size:14px;font-weight:500;border:1px solid;display:inline-flex;align-items:center;justify-content:center;gap:6px;line-height:1.25;';
|
function btn(label, onClick, kind, ic) {
|
||||||
|
const base = 'cursor:pointer;border-radius:8px;padding:8px 15px;font:inherit;font-size:14px;font-weight:500;border:1px solid;display:inline-flex;align-items:center;justify-content:center;gap:7px;line-height:1.25;';
|
||||||
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 === 'danger'
|
: kind === 'danger'
|
||||||
? base + 'border-color:var(--border-normal);background:var(--background-page,#fff);color:var(--danger,#c62828);'
|
? base + 'border-color:var(--border-normal);background:var(--background-page,#fff);color:var(--danger,#c62828);'
|
||||||
: base + 'border-color:var(--border-normal);background:var(--background-page,#fff);color:var(--foreground-normal);';
|
: base + 'border-color:var(--border-normal);background:var(--background-page,#fff);color:var(--foreground-normal);';
|
||||||
return h('button', { type: 'button', onClick, style }, label);
|
return h('button', { type: 'button', onClick, style }, [ic ? icon(ic) : null, label].filter(Boolean));
|
||||||
}
|
}
|
||||||
// Pill (Status/Badge): ruhig, lesbar. farbe = Hintergrund (Theme-Tint), textFarbe optional.
|
// Pill (Status/Badge): ruhig, lesbar. farbe = Hintergrund (Theme-Tint), textFarbe optional.
|
||||||
function chip(text, farbe, textFarbe) {
|
function chip(text, farbe, textFarbe) {
|
||||||
@@ -1335,10 +1349,10 @@ const Board = defineComponent({
|
|||||||
// Drucken + Empfänger immer sichtbar; Rest hinter „Weiteres".
|
// Drucken + Empfänger immer sichtbar; Rest hinter „Weiteres".
|
||||||
const offen = !!weiteresOffen.value[order.id];
|
const offen = !!weiteresOffen.value[order.id];
|
||||||
return h('div', { style: 'display:flex;gap:8px;flex-wrap:wrap;' }, [
|
return h('div', { style: 'display:flex;gap:8px;flex-wrap:wrap;' }, [
|
||||||
btn('🖨 Drucken', () => openDruck(order), 'primary'),
|
btn('Drucken', () => openDruck(order), 'primary', 'print'),
|
||||||
btn('👥 Empfänger', () => openEmpf(order)),
|
btn('Empfänger', () => openEmpf(order), 'default', 'groups'),
|
||||||
orderDateiIds(order).length ? btn('📎 Dateien', () => openDateien(order)) : null,
|
orderDateiIds(order).length ? btn('Dateien', () => openDateien(order), 'default', 'attach_file') : null,
|
||||||
btn(offen ? '⚙ Weiteres ▴' : '⚙ Weiteres ▾', () => toggleWeiteres(order.id)),
|
btn('Weiteres', () => toggleWeiteres(order.id), 'default', offen ? 'expand_less' : 'expand_more'),
|
||||||
].filter(Boolean));
|
].filter(Boolean));
|
||||||
}
|
}
|
||||||
// „Weiteres": Verknüpfen · In Kontakte speichern · Neu generieren · In manuellen Druck.
|
// „Weiteres": Verknüpfen · In Kontakte speichern · Neu generieren · In manuellen Druck.
|
||||||
@@ -1348,18 +1362,18 @@ const Board = defineComponent({
|
|||||||
return h('div', { style: 'margin-top:10px;padding:12px;border:1px solid var(--border-subdued);border-radius:8px;background:var(--background-subdued);display:flex;flex-direction:column;gap:10px;' }, [
|
return h('div', { style: 'margin-top:10px;padding:12px;border:1px solid var(--border-subdued);border-radius:8px;background:var(--background-subdued);display:flex;flex-direction:column;gap:10px;' }, [
|
||||||
// Druckdatum + Erster/Letzter Empfänger – zum Wiederfinden/Auseinandersortieren.
|
// Druckdatum + Erster/Letzter Empfänger – zum Wiederfinden/Auseinandersortieren.
|
||||||
h('div', { style: 'font-size:12px;color:var(--foreground-normal);line-height:1.6;' }, [
|
h('div', { style: 'font-size:12px;color:var(--foreground-normal);line-height:1.6;' }, [
|
||||||
h('div', {}, dz ? `🖨 In den Druck: ${dmYhm(dz.erste)}${dz.letzte && dz.letzte.slice(0, 16) !== dz.erste.slice(0, 16) ? ' – ' + dmYhm(dz.letzte) : ''}` : '🖨 Noch nicht gedruckt.'),
|
h('div', {}, dz ? `In den Druck: ${dmYhm(dz.erste)}${dz.letzte && dz.letzte.slice(0, 16) !== dz.erste.slice(0, 16) ? ' – ' + dmYhm(dz.letzte) : ''}` : 'Noch nicht gedruckt.'),
|
||||||
nm ? h('div', {}, `👥 Erster: ${nm.erste}${nm.anzahl > 1 ? ` · Letzter: ${nm.letzte}` : ''}`) : null,
|
nm ? h('div', {}, `Erster: ${nm.erste}${nm.anzahl > 1 ? ` · Letzter: ${nm.letzte}` : ''}`) : null,
|
||||||
]),
|
]),
|
||||||
h('div', { style: 'display:flex;align-items:center;gap:8px;flex-wrap:wrap;' }, [
|
h('div', { style: 'display:flex;align-items:center;gap:8px;flex-wrap:wrap;' }, [
|
||||||
subdued('🔗 Verknüpfter Auftrag:', ''),
|
subdued('Verknüpfter Auftrag:', ''),
|
||||||
h('input', { placeholder: 'Auftragsnr. (leer = keine)', value: o.linked_order?.order_number || '',
|
h('input', { placeholder: 'Auftragsnr. (leer = keine)', value: o.linked_order?.order_number || '',
|
||||||
style: 'flex:1;min-width:140px;max-width:220px;border:1px solid var(--border-subdued);border-radius:6px;padding:5px 8px;font:inherit;font-size:12px;background:var(--background-page);color:var(--foreground-normal);',
|
style: 'flex:1;min-width:140px;max-width:220px;border:1px solid var(--border-subdued);border-radius:6px;padding:5px 8px;font:inherit;font-size:12px;background:var(--background-page);color:var(--foreground-normal);',
|
||||||
onChange: (e) => setOrderLink(o, e.target.value) }),
|
onChange: (e) => setOrderLink(o, e.target.value) }),
|
||||||
]),
|
]),
|
||||||
// Kontakt/Kunde verknüpfen (per E-Mail) oder mit „×" lösen. Keine Kundenmail.
|
// Kontakt/Kunde verknüpfen (per E-Mail) oder mit „×" lösen. Keine Kundenmail.
|
||||||
h('div', { style: 'display:flex;align-items:center;gap:8px;flex-wrap:wrap;' }, [
|
h('div', { style: 'display:flex;align-items:center;gap:8px;flex-wrap:wrap;' }, [
|
||||||
subdued('👤 Kontakt:', ''),
|
subdued('Kontakt:', ''),
|
||||||
o.customer ? h('span', { style: 'font-size:12px;' }, `${kundeName(o)}${o.customer.email ? ' · ' + o.customer.email : ''}`) : null,
|
o.customer ? h('span', { style: 'font-size:12px;' }, `${kundeName(o)}${o.customer.email ? ' · ' + o.customer.email : ''}`) : null,
|
||||||
o.customer
|
o.customer
|
||||||
? h('button', { type: 'button', title: 'Kontakt lösen', onClick: () => setOrderCustomer(o, ''),
|
? h('button', { type: 'button', title: 'Kontakt lösen', onClick: () => setOrderCustomer(o, ''),
|
||||||
@@ -1369,19 +1383,24 @@ const Board = defineComponent({
|
|||||||
onChange: (e) => setOrderCustomer(o, e.target.value) }),
|
onChange: (e) => setOrderCustomer(o, e.target.value) }),
|
||||||
]),
|
]),
|
||||||
h('div', { style: 'display:flex;gap:8px;flex-wrap:wrap;' }, [
|
h('div', { style: 'display:flex;gap:8px;flex-wrap:wrap;' }, [
|
||||||
btn(o.keep_contacts ? '📇 In Kontakte gespeichert ✓' : '📇 In Kontakte speichern',
|
btn(o.keep_contacts ? 'In Kontakte gespeichert' : 'In Kontakte speichern',
|
||||||
() => toggleKeepContacts(o), o.keep_contacts ? 'primary' : 'default'),
|
() => toggleKeepContacts(o), o.keep_contacts ? 'primary' : 'default', o.keep_contacts ? 'how_to_reg' : 'contacts'),
|
||||||
btn('🔄 Neu generieren', () => regenerieren(o.id), 'danger'),
|
btn('Neu generieren', () => regenerieren(o.id), 'danger', 'autorenew'),
|
||||||
btn('✍ In manuellen Druck übernehmen', () => uebernehmenManuell(o), 'primary'),
|
btn('In manuellen Druck übernehmen', () => uebernehmenManuell(o), 'primary', 'edit_note'),
|
||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
function statusSelect(order) {
|
function statusSelect(order) {
|
||||||
return h('select', {
|
const k = order.production_status || 'eingegangen';
|
||||||
style: S_SELECT + 'font-size:13px;padding:6px 12px;font-weight:500;flex-shrink:0;',
|
const m = STATUS_META[k] || STATUS_META.eingegangen;
|
||||||
value: order.production_status || 'eingegangen',
|
// Farbiger Pill, der zugleich das Status-Dropdown ist (ein Element, klarer Status).
|
||||||
onChange: (e) => setStatus(order, e.target.value),
|
return h('div', { style: `display:inline-flex;align-items:center;gap:6px;border-radius:999px;padding:6px 12px 6px 14px;background:${m.bg};color:${m.fg};font-weight:500;font-size:13px;flex-shrink:0;` }, [
|
||||||
}, Object.entries(STATUS_LABEL).map(([k, v]) => h('option', { value: k, selected: (order.production_status || 'eingegangen') === k }, v)));
|
icon(m.icon),
|
||||||
|
h('select', {
|
||||||
|
style: `border:none;background:transparent;color:${m.fg};font:inherit;font-size:13px;font-weight:500;cursor:pointer;padding:0;`,
|
||||||
|
value: k, onChange: (e) => setStatus(order, e.target.value),
|
||||||
|
}, Object.entries(STATUS_LABEL).map(([kk, v]) => h('option', { value: kk, selected: k === kk }, v))),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
// Lieferadresse für den (Sammel-)Versand: abweichende Order-Lieferadresse →
|
// Lieferadresse für den (Sammel-)Versand: abweichende Order-Lieferadresse →
|
||||||
// Liefer-/Rechnungsadresse des Kunden. So muss der Versand nicht in die Order.
|
// Liefer-/Rechnungsadresse des Kunden. So muss der Versand nicht in die Order.
|
||||||
@@ -1396,63 +1415,69 @@ const Board = defineComponent({
|
|||||||
}
|
}
|
||||||
function orderCard(order, opts) {
|
function orderCard(order, opts) {
|
||||||
const o = order;
|
const o = order;
|
||||||
|
const opts2 = opts || {};
|
||||||
|
const bezahlt = o.payment_status === 'bezahlt';
|
||||||
|
// Kopf: Name = Hero, darunter Anzahl Schriftstücke groß + Eckdaten. Rechts: Status-Pill.
|
||||||
|
const spec = [
|
||||||
|
o.needs_envelope ? `${o.entries_count || 0} Kuverts` : 'ohne Kuvert',
|
||||||
|
o.format?.name || o.format?.key || 'Format?',
|
||||||
|
o.shipping_type === 'einzeln' ? 'Einzelversand' : 'Sammelversand',
|
||||||
|
].join(' · ');
|
||||||
const kopf = h('div', { style: 'display:flex;justify-content:space-between;align-items:flex-start;gap:12px;flex-wrap:wrap;' }, [
|
const kopf = h('div', { style: 'display:flex;justify-content:space-between;align-items:flex-start;gap:12px;flex-wrap:wrap;' }, [
|
||||||
h('div', { style: 'display:flex;gap:12px;align-items:flex-start;' }, [
|
h('div', { style: 'display:flex;gap:12px;align-items:flex-start;min-width:0;flex:1;' }, [
|
||||||
h('input', { type: 'checkbox', checked: istGewaehlt(o.id), onClick: (e) => toggleAuswahl(o.id, e),
|
opts2.dragHandle
|
||||||
title: 'Für Sammel-Druck auswählen', style: 'width:18px;height:18px;margin-top:4px;cursor:pointer;flex-shrink:0;' }),
|
? h('span', { style: 'color:var(--foreground-subdued);cursor:grab;margin-top:5px;flex-shrink:0;', title: 'Ziehen zum Sortieren' }, icon('drag_indicator'))
|
||||||
h('div', {}, [
|
: h('input', { type: 'checkbox', checked: istGewaehlt(o.id), onClick: (e) => toggleAuswahl(o.id, e),
|
||||||
h('div', { style: 'font-weight:500;font-size:18px;letter-spacing:-.01em;' }, `#${o.order_number}`),
|
title: 'Für Sammel-Druck auswählen', style: 'width:18px;height:18px;margin-top:5px;cursor:pointer;flex-shrink:0;' }),
|
||||||
subdued(`${kundeName(o)}${o.customer?.email ? ' · ' + o.customer.email : ''}${o.customer?.phone ? ' · ' + o.customer.phone : ''}`, 'margin-top:1px;'),
|
h('div', { style: 'min-width:0;' }, [
|
||||||
h('a', { href: `/admin/content/orders/${o.id}`, target: '_blank', rel: 'noopener',
|
h('div', { style: 'font-size:19px;font-weight:500;letter-spacing:-.01em;line-height:1.25;' }, kundeName(o) || `#${o.order_number}`),
|
||||||
style: 'display:inline-flex;align-items:center;gap:4px;margin-top:5px;font-size:12px;color:var(--primary,#1e50a5);text-decoration:none;font-weight:500;' },
|
h('div', { style: 'display:flex;align-items:baseline;gap:8px;margin-top:4px;flex-wrap:wrap;' }, [
|
||||||
'↗ Auftrag öffnen'),
|
h('span', { style: `font-size:22px;font-weight:500;color:${ACCENT};line-height:1;` }, String(o.entries_count || 0)),
|
||||||
|
h('span', { style: 'font-size:14px;color:var(--foreground-subdued);' }, 'Schriftstücke · ' + spec),
|
||||||
|
]),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
statusSelect(o),
|
statusSelect(o),
|
||||||
]);
|
]);
|
||||||
// Eckdaten: eine ruhige Zeile mit dezenten Trennpunkten (immer vorhanden).
|
// Druckfortschritt: klar sehen, was/wann gedruckt wurde.
|
||||||
const trenn = () => h('span', { style: 'color:var(--border-normal);', 'aria-hidden': 'true' }, '·');
|
|
||||||
const eck = [
|
|
||||||
metaItem(`📄 ${o.entries_count || 0} Briefe`),
|
|
||||||
metaItem(o.needs_envelope ? `✉ ${o.entries_count || 0} Kuverts` : 'ohne Kuvert'),
|
|
||||||
metaItem(o.format?.name || o.format?.key || 'Format?'),
|
|
||||||
metaItem(o.shipping_type === 'einzeln' ? 'Einzelversand' : 'Sammelversand'),
|
|
||||||
(Number(o.count_ausland) > 0) ? metaItem(`🇩🇪 ${o.count_inland ?? 0} · 🌍 ${o.count_ausland}`) : null,
|
|
||||||
o.shipping_day ? metaItem('📅 ' + o.shipping_day.charAt(0).toUpperCase() + o.shipping_day.slice(1)) : null,
|
|
||||||
].filter(Boolean);
|
|
||||||
const metaRow = h('div', { style: 'display:flex;gap:11px;align-items:center;flex-wrap:wrap;margin:12px 0;padding:11px 0;border-top:1px solid var(--border-subdued);border-bottom:1px solid var(--border-subdued);' },
|
|
||||||
eck.reduce((acc, el, i) => (i === 0 ? [el] : acc.concat(trenn(), el)), []));
|
|
||||||
// Status-Pills: nur Aussagekräftiges, Farbe nur wo sie Bedeutung trägt.
|
|
||||||
const bezahlt = o.payment_status === 'bezahlt';
|
|
||||||
const pills = [
|
|
||||||
chip(bezahlt ? '✓ bezahlt' : (o.payment_status === 'storniert' ? 'storniert' : 'offen'),
|
|
||||||
bezahlt ? 'var(--success-25,rgba(46,125,50,.14))' : 'var(--warning-25,rgba(230,160,0,.16))',
|
|
||||||
bezahlt ? 'var(--success,#2e7d32)' : '#8a6d00'),
|
|
||||||
(o.motif?.name || (o.motif_mode && !['ohne', ''].includes(o.motif_mode)))
|
|
||||||
? chip('🖼 Motiv' + (o.motif?.name ? ': ' + o.motif.name : (o.motif_mode === 'eigene_karten' ? ': eigene Karten' : (o.motif_mode === 'upload' ? ': Upload' : ''))), 'var(--warning-25,rgba(230,160,0,.18))', '#8a6d00')
|
|
||||||
: null,
|
|
||||||
o.product?.name ? chip(o.product.name) : null,
|
|
||||||
o.linked_order?.order_number ? chip('🔗 Kuverts: #' + o.linked_order.order_number) : null,
|
|
||||||
o.approval_status === 'pending' ? chip('⏳ wartet auf Freigabe', 'var(--warning-25,rgba(230,160,0,.18))', '#8a6d00') : null,
|
|
||||||
o.approval_status === 'approved' ? chip('✓ Motiv freigegeben', 'var(--success-25,rgba(46,125,50,.14))', 'var(--success,#2e7d32)') : null,
|
|
||||||
o.approval_status === 'changes' ? chip('✏ Änderungswunsch', 'var(--danger-25,rgba(198,40,40,.14))', 'var(--danger,#c62828)') : null,
|
|
||||||
].filter(Boolean);
|
|
||||||
const pillRow = pills.length ? h('div', { style: 'display:flex;gap:7px;flex-wrap:wrap;margin-bottom:12px;' }, pills) : null;
|
|
||||||
// „Auf einen Blick": welche Nummern wurden schon in den Druck gesendet.
|
|
||||||
const send = sendIndex.value[String(o.id)];
|
const send = sendIndex.value[String(o.id)];
|
||||||
const badges = send && (send.brief.length || send.umschlag.length)
|
const hatDruck = send && (send.brief.length || send.umschlag.length);
|
||||||
? h('div', { style: 'display:flex;gap:7px;flex-wrap:wrap;margin-bottom:12px;' }, [
|
const druckZeile = h('div', { style: 'display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin:12px 0;padding:10px 0;border-top:1px solid var(--border-subdued);border-bottom:1px solid var(--border-subdued);font-size:13.5px;' },
|
||||||
send.brief.length ? chip('🖨 Brief gesendet: ' + send.brief.join(' · '), 'var(--primary-25,rgba(30,80,165,.1))', 'var(--primary,#1e50a5)') : null,
|
hatDruck ? [
|
||||||
send.umschlag.length ? chip('✉ Kuvert gesendet: ' + send.umschlag.join(' · '), 'var(--primary-25,rgba(30,80,165,.1))', 'var(--primary,#1e50a5)') : null,
|
h('span', { style: 'display:inline-flex;align-items:center;gap:5px;color:var(--success,#2e7d32);font-weight:500;' }, [icon('check_circle'), 'gedruckt']),
|
||||||
]) : null;
|
send.brief.length ? h('span', { style: 'color:var(--foreground-subdued);' }, 'Brief: ' + send.brief.join(' · ')) : null,
|
||||||
// Sammelversand: Lieferadresse direkt auf der Karte (kein Sprung in die Order nötig).
|
send.umschlag.length ? h('span', { style: 'color:var(--foreground-subdued);' }, '· Kuvert: ' + send.umschlag.join(' · ')) : null,
|
||||||
|
].filter(Boolean)
|
||||||
|
: [h('span', { style: 'display:inline-flex;align-items:center;gap:5px;color:var(--foreground-subdued);' }, [icon('print_disabled'), 'Noch nicht gedruckt'])]);
|
||||||
|
// Warn-/Hinweis-Pills: nur Aussagekräftiges (Motiv, Freigabe, verknüpfte Kuverts).
|
||||||
|
const pills = [
|
||||||
|
(o.motif?.name || (o.motif_mode && !['ohne', ''].includes(o.motif_mode)))
|
||||||
|
? chip('Motiv' + (o.motif?.name ? ': ' + o.motif.name : (o.motif_mode === 'eigene_karten' ? ': eigene Karten' : (o.motif_mode === 'upload' ? ': Upload' : ''))), 'rgba(230,160,0,.18)', '#8a6d00')
|
||||||
|
: null,
|
||||||
|
o.linked_order?.order_number ? chip('Kuverts: #' + o.linked_order.order_number) : null,
|
||||||
|
o.approval_status === 'pending' ? chip('wartet auf Freigabe', 'rgba(230,160,0,.18)', '#8a6d00') : null,
|
||||||
|
o.approval_status === 'approved' ? chip('Motiv freigegeben', 'rgba(46,125,50,.14)', 'var(--success,#2e7d32)') : null,
|
||||||
|
o.approval_status === 'changes' ? chip('Änderungswunsch', 'rgba(198,40,40,.14)', 'var(--danger,#c62828)') : null,
|
||||||
|
].filter(Boolean);
|
||||||
|
const pillRow = pills.length ? h('div', { style: 'display:flex;gap:7px;flex-wrap:wrap;margin-bottom:10px;' }, pills) : null;
|
||||||
|
// Sammelversand: Lieferadresse direkt auf der Karte.
|
||||||
const liefer = (o.shipping_type !== 'einzeln') ? lieferAdresse(o) : null;
|
const liefer = (o.shipping_type !== 'einzeln') ? lieferAdresse(o) : null;
|
||||||
const lieferBox = liefer ? h('div', { style: 'margin:0 0 12px;padding:11px 13px;background:var(--background-subdued,#f1f4f9);border-radius:10px;font-size:13.5px;line-height:1.55;' }, [
|
const lieferBox = liefer ? h('div', { style: 'margin:0 0 10px;padding:11px 13px;background:var(--background-subdued,#f1f4f9);border-radius:10px;font-size:13.5px;line-height:1.55;' }, [
|
||||||
h('div', { style: 'font-weight:500;color:var(--foreground-subdued);font-size:11px;text-transform:uppercase;letter-spacing:.04em;margin-bottom:3px;' }, '📦 Versand an · ' + liefer.quelle),
|
h('div', { style: 'display:flex;align-items:center;gap:5px;font-weight:500;color:var(--foreground-subdued);font-size:11px;text-transform:uppercase;letter-spacing:.04em;margin-bottom:3px;' }, [icon('local_shipping', { small: false, style: 'font-size:15px;' }), 'Versand an · ' + liefer.quelle]),
|
||||||
...liefer.zeilen.map((z) => h('div', {}, z)),
|
...liefer.zeilen.map((z) => h('div', {}, z)),
|
||||||
]) : null;
|
]) : null;
|
||||||
return h('div', { key: 'o' + o.id, style: CARD + `margin-bottom:14px;border-left:3px solid ${ACCENT};` }, [
|
// Subzeile: kleine, ruhige Sekundär-Infos (Nummer, Zahlung, Versandtag, Link).
|
||||||
kopf, metaRow, pillRow, badges, lieferBox,
|
const sub = [
|
||||||
|
h('span', { style: 'font-variant-numeric:tabular-nums;' }, '#' + o.order_number),
|
||||||
|
h('span', { style: `color:${bezahlt ? 'var(--success,#2e7d32)' : '#8a6d00'};font-weight:500;` }, bezahlt ? 'bezahlt' : (o.payment_status === 'storniert' ? 'storniert' : 'offen')),
|
||||||
|
(Number(o.count_ausland) > 0) ? h('span', {}, `${o.count_inland ?? 0} DE · ${o.count_ausland} Ausland`) : null,
|
||||||
|
o.shipping_day ? h('span', {}, 'Versandtag ' + o.shipping_day.charAt(0).toUpperCase() + o.shipping_day.slice(1)) : null,
|
||||||
|
h('a', { href: `/admin/content/orders/${o.id}`, target: '_blank', rel: 'noopener', style: 'display:inline-flex;align-items:center;gap:3px;color:var(--primary,#1e50a5);text-decoration:none;font-weight:500;' }, [icon('open_in_new'), 'Auftrag öffnen']),
|
||||||
|
].filter(Boolean);
|
||||||
|
const subline = h('div', { style: 'display:flex;align-items:center;gap:12px;flex-wrap:wrap;font-size:12.5px;color:var(--foreground-subdued);margin-bottom:12px;' },
|
||||||
|
sub.reduce((acc, el, i) => (i === 0 ? [el] : acc.concat(h('span', { style: 'color:var(--border-normal);', 'aria-hidden': 'true' }, '·'), el)), []));
|
||||||
|
return h('div', { key: 'o' + o.id, style: CARD + `margin-bottom:14px;border-left:3px solid ${(STATUS_META[o.production_status] || STATUS_META.eingegangen).fg};` }, [
|
||||||
|
kopf, druckZeile, pillRow, lieferBox, subline,
|
||||||
notizFeld(o.internal_note, (v) => setOrderNotiz(o, v), 'Notiz zum Auftrag …'),
|
notizFeld(o.internal_note, (v) => setOrderNotiz(o, v), 'Notiz zum Auftrag …'),
|
||||||
actionButtons(o),
|
actionButtons(o),
|
||||||
weiteresOffen.value[o.id] ? weiteresPanel(o) : null,
|
weiteresOffen.value[o.id] ? weiteresPanel(o) : null,
|
||||||
@@ -1478,8 +1503,8 @@ const Board = defineComponent({
|
|||||||
const nachricht = req.message ? h('div', { style: 'margin:0 0 12px;padding:12px 14px;background:var(--background-subdued,#f1f4f9);border-radius:10px;font-size:14px;line-height:1.6;white-space:pre-wrap;color:var(--foreground-normal);' }, req.message) : null;
|
const nachricht = req.message ? h('div', { style: 'margin:0 0 12px;padding:12px 14px;background:var(--background-subdued,#f1f4f9);border-radius:10px;font-size:14px;line-height:1.6;white-space:pre-wrap;color:var(--foreground-normal);' }, req.message) : null;
|
||||||
const aktionen = h('div', { style: 'display:flex;gap:8px;flex-wrap:wrap;' }, [
|
const aktionen = h('div', { style: 'display:flex;gap:8px;flex-wrap:wrap;' }, [
|
||||||
// Muster (Druckprodukt) → manueller Druck; individuelles Angebot → kein Druck.
|
// Muster (Druckprodukt) → manueller Druck; individuelles Angebot → kein Druck.
|
||||||
istMuster ? btn('✍ In manuellen Druck', () => musterUebernehmen(req), 'primary') : null,
|
istMuster ? btn('In manuellen Druck', () => musterUebernehmen(req), 'primary', 'edit_note') : null,
|
||||||
btn('👤 Details', () => openReqInfo(req)),
|
btn('Details', () => openReqInfo(req), 'default', 'person'),
|
||||||
]);
|
]);
|
||||||
return h('div', { key: 'r' + req.id, style: CARD + `margin-bottom:14px;border-left:3px solid ${akzent};` }, [kopf, metaRow, nachricht, aktionen]);
|
return h('div', { key: 'r' + req.id, style: CARD + `margin-bottom:14px;border-left:3px solid ${akzent};` }, [kopf, metaRow, nachricht, aktionen]);
|
||||||
}
|
}
|
||||||
@@ -1497,12 +1522,12 @@ const Board = defineComponent({
|
|||||||
h('input', { type: 'checkbox', checked: chargeGewaehlt(c), onChange: () => toggleCharge(c),
|
h('input', { type: 'checkbox', checked: chargeGewaehlt(c), onChange: () => toggleCharge(c),
|
||||||
title: 'Ganze Charge auswählen', style: 'width:16px;height:16px;cursor:pointer;flex-shrink:0;' }),
|
title: 'Ganze Charge auswählen', style: 'width:16px;height:16px;cursor:pointer;flex-shrink:0;' }),
|
||||||
h('button', { type: 'button', onClick: () => toggleExpand(c.key),
|
h('button', { type: 'button', onClick: () => toggleExpand(c.key),
|
||||||
style: 'cursor:pointer;border:none;background:transparent;font-size:15px;color:var(--foreground-normal);', title: 'Aufklappen' }, auf ? '▾' : '▸'),
|
style: 'cursor:pointer;border:none;background:transparent;color:var(--foreground-subdued);display:inline-flex;align-items:center;', title: 'Aufklappen' }, icon(auf ? 'expand_more' : 'chevron_right')),
|
||||||
h('div', { style: 'font-weight:700;font-size:15px;' }, `📦 ${c.kundeName} · ${c.produkt}`),
|
h('div', { style: 'display:flex;align-items:center;gap:7px;font-weight:500;font-size:16px;' }, [icon('inbox'), `${c.kundeName} · ${c.produkt}`]),
|
||||||
chip(`${c.orders.length} Aufträge`),
|
chip(`${c.orders.length} Aufträge`),
|
||||||
c.versandtag ? chip('📅 ' + c.versandtag.charAt(0).toUpperCase() + c.versandtag.slice(1)) : null,
|
c.versandtag ? chip('Versandtag ' + c.versandtag.charAt(0).toUpperCase() + c.versandtag.slice(1)) : null,
|
||||||
c.druckZeit ? chip('🖨 gedruckt ' + dmYhm(c.druckZeit), 'var(--primary-25,rgba(30,80,165,.14))') : null,
|
c.druckZeit ? chip('gedruckt ' + dmYhm(c.druckZeit), 'rgba(30,80,165,.1)', 'var(--primary,#1e50a5)') : null,
|
||||||
c.faellig ? chip('⏰ fällig bis ' + dmY(c.faellig), faelligFarbe) : null,
|
c.faellig ? chip('fällig bis ' + dmY(c.faellig), faelligFarbe) : null,
|
||||||
]);
|
]);
|
||||||
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:12px;display:flex;flex-direction:column;gap:2px;' }, 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 = notizFeld(chargeNotizen.value[c.key], (v) => setChargeNotiz(c.key, v), 'Notiz zur Charge …');
|
||||||
@@ -1702,7 +1727,7 @@ const Board = defineComponent({
|
|||||||
});
|
});
|
||||||
return h('div', { key: 'st' + s.id, style: CARD + 'margin-bottom:16px;' }, [
|
return h('div', { key: 'st' + s.id, style: CARD + 'margin-bottom:16px;' }, [
|
||||||
h('div', { style: 'display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;flex-wrap:wrap;gap:8px;' }, [
|
h('div', { style: 'display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;flex-wrap:wrap;gap:8px;' }, [
|
||||||
h('div', { style: 'font-weight:700;font-size:16px;' }, '🏭 ' + s.name),
|
h('div', { style: 'display:flex;align-items:center;gap:7px;font-weight:500;font-size:16px;' }, [icon('store'), s.name]),
|
||||||
unter ? chip(`⚠ ${unter}× nachbestellen`, 'var(--danger-25,rgba(198,40,40,.15))') : chip('✓ Bestand ok', 'var(--success-25,rgba(46,125,50,.15))'),
|
unter ? chip(`⚠ ${unter}× nachbestellen`, 'var(--danger-25,rgba(198,40,40,.15))') : chip('✓ Bestand ok', 'var(--success-25,rgba(46,125,50,.15))'),
|
||||||
]),
|
]),
|
||||||
rows.length ? h('div', {}, katBloecke) : subdued('Keine Materialzeilen an diesem Standort.'),
|
rows.length ? h('div', {}, katBloecke) : subdued('Keine Materialzeilen an diesem Standort.'),
|
||||||
@@ -2081,7 +2106,7 @@ const Board = defineComponent({
|
|||||||
+ 'cursor:pointer;border:none;border-radius:8px;padding:11px 13px;margin-bottom:3px;font:inherit;font-size:14px;'
|
+ 'cursor:pointer;border:none;border-radius:8px;padding:11px 13px;margin-bottom:3px;font:inherit;font-size:14px;'
|
||||||
+ (aktiv ? `background:${ACCENT};color:#fff;font-weight:500;` : 'background:transparent;color:var(--foreground-normal);'),
|
+ (aktiv ? `background:${ACCENT};color:#fff;font-weight:500;` : 'background:transparent;color:var(--foreground-normal);'),
|
||||||
}, [
|
}, [
|
||||||
h('span', {}, t.label),
|
h('span', { style: 'display:inline-flex;align-items:center;gap:9px;min-width:0;' }, [icon(t.icon), h('span', {}, t.label)]),
|
||||||
h('span', { style: 'font-size:12px;font-weight:500;min-width:20px;text-align:center;border-radius:999px;padding:1px 8px;'
|
h('span', { style: 'font-size:12px;font-weight:500;min-width:20px;text-align:center;border-radius:999px;padding:1px 8px;'
|
||||||
+ (aktiv ? 'background:rgba(255,255,255,.22);color:#fff;' : 'background:var(--background-subdued);color:var(--foreground-subdued);') }, String(tabCount(t.key))),
|
+ (aktiv ? 'background:rgba(255,255,255,.22);color:#fff;' : 'background:var(--background-subdued);color:var(--foreground-subdued);') }, String(tabCount(t.key))),
|
||||||
]);
|
]);
|
||||||
@@ -2092,19 +2117,20 @@ const Board = defineComponent({
|
|||||||
// große Suchfeld, das nicht in die anderen Reiter übernommen wird).
|
// große Suchfeld, das nicht in die anderen Reiter übernommen wird).
|
||||||
tab.value === 'suche' ? null : h('input', { type: 'search', value: suche.value, placeholder: '🔎 Auftrag, Kunde, Empfänger …',
|
tab.value === 'suche' ? null : h('input', { type: 'search', value: suche.value, placeholder: '🔎 Auftrag, Kunde, Empfänger …',
|
||||||
style: S_INPUT + 'flex:1;min-width:240px;', onInput: (e) => { suche.value = e.target.value; } }),
|
style: S_INPUT + 'flex:1;min-width:240px;', onInput: (e) => { suche.value = e.target.value; } }),
|
||||||
sichtbareOrders().length ? btn(alleSichtbarGewaehlt() ? '☐ Auswahl aufheben' : '☑ Alle auswählen', alleAuswaehlen) : null,
|
sichtbareOrders().length ? btn(alleSichtbarGewaehlt() ? 'Auswahl aufheben' : 'Alle auswählen', alleAuswaehlen, 'default', alleSichtbarGewaehlt() ? 'deselect' : 'select_all') : null,
|
||||||
btn('↻ Aktualisieren', laden),
|
btn('Aktualisieren', laden, 'default', 'refresh'),
|
||||||
btn('🔄 Programm neustarten', programmNeustarten),
|
btn('Programm neustarten', programmNeustarten, 'default', 'restart_alt'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Leicht getönte Inhaltsfläche → weiße Karten heben sich klar ab (wie im Referenz-Dashboard).
|
// Leicht getönte Inhaltsfläche → weiße Karten heben sich klar ab (wie im Referenz-Dashboard).
|
||||||
const bulkBar = auswahl.value.length ? h('div', { style: `display:flex;align-items:center;gap:12px;flex-wrap:wrap;background:${ACCENT};color:#fff;padding:10px 16px;border-radius:8px;margin-bottom:12px;` }, [
|
const bulkWBtn = 'cursor:pointer;border:none;background:#fff;color:#1E50A5;border-radius:8px;padding:8px 15px;font:inherit;font-size:14px;font-weight:500;display:inline-flex;align-items:center;gap:6px;';
|
||||||
h('span', { style: 'font-weight:600;' }, `${auswahl.value.length} Auftrag/Aufträge ausgewählt`),
|
const bulkBar = auswahl.value.length ? h('div', { style: `display:flex;align-items:center;gap:12px;flex-wrap:wrap;background:${ACCENT};color:#fff;padding:12px 16px;border-radius:12px;margin-bottom:12px;` }, [
|
||||||
h('button', { type: 'button', onClick: openBulk, style: `cursor:pointer;border:none;background:#fff;color:${ACCENT};border-radius:7px;padding:6px 14px;font:inherit;font-size:13px;font-weight:600;` }, '🖨 In Druck geben'),
|
h('span', { style: 'font-weight:500;font-size:15px;' }, `${auswahl.value.length} ausgewählt`),
|
||||||
h('button', { type: 'button', onClick: chargePdf, style: `cursor:pointer;border:none;background:#fff;color:${ACCENT};border-radius:7px;padding:6px 14px;font:inherit;font-size:13px;font-weight:600;` }, '📄 PDFs drucken'),
|
h('button', { type: 'button', onClick: openBulk, style: bulkWBtn }, [icon('print'), 'In Druck geben']),
|
||||||
h('button', { type: 'button', onClick: () => regenerieren(auswahlSortiert()), style: `cursor:pointer;border:none;background:#fff;color:${ACCENT};border-radius:7px;padding:6px 14px;font:inherit;font-size:13px;font-weight:600;` }, '🔄 Neu generieren'),
|
h('button', { type: 'button', onClick: chargePdf, style: bulkWBtn }, [icon('picture_as_pdf'), 'PDFs drucken']),
|
||||||
|
h('button', { type: 'button', onClick: () => regenerieren(auswahlSortiert()), style: bulkWBtn }, [icon('autorenew'), 'Neu generieren']),
|
||||||
h('select', {
|
h('select', {
|
||||||
style: `cursor:pointer;border:none;background:#fff;color:${ACCENT};border-radius:7px;padding:7px 12px;font:inherit;font-size:13px;font-weight:600;`,
|
style: `cursor:pointer;border:none;background:#fff;color:#1E50A5;border-radius:8px;padding:9px 12px;font:inherit;font-size:14px;font-weight:500;`,
|
||||||
value: '', onChange: (e) => { if (e.target.value) { bulkStatus(e.target.value); e.target.value = ''; } },
|
value: '', onChange: (e) => { if (e.target.value) { bulkStatus(e.target.value); e.target.value = ''; } },
|
||||||
}, [
|
}, [
|
||||||
h('option', { value: '' }, 'Status setzen …'),
|
h('option', { value: '' }, 'Status setzen …'),
|
||||||
|
|||||||
Reference in New Issue
Block a user