Statistik-Modul, Kontakt-Bulk, Notiz-Fix, Ausland-Erkennung, Weiteres-Kontakt
- Neues Modul „Statistik": versendete Briefe je Kunde/Monat, getrennt Inland/ Ausland, Zeilen aufklappbar (Auftragsliste), mit Suche. Raus aus dem Board. - Produktion: Auto-Refresh pausiert, während ein Feld bearbeitet wird (Notizen gingen sonst beim 15-s-Reload/Statuswechsel verloren). „Neu generieren“/ „In manuellen Druck“ aus dem Empfänger-Dialog entfernt (liegen unter Weiteres). Unter Weiteres jetzt auch Kontakt/Kunde verknüpfen (per E-Mail) und mit × lösen – ohne Kundenmail. - Ausland-Erkennung (zielInland) mit Wortgrenzen (\b) + unbekannter Ländertext → Inland: „Schweizer Straße“, „Neusass“, „Polenz“ werden nicht mehr als Ausland geflaggt (orders + produktion). - Kontakte-Modul: Mehrfachauswahl → Status ändern / löschen. - contacts-Hook: Abwahl von „In Kontakte speichern“ löscht die Kontakte wieder. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
6ccd4f3c53
commit
4aa3fd04e6
6 files changed
+299
-62
No files matched your search
+57
-4
@@ -212,13 +212,46 @@ const Board = defineComponent({
|
||||
document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
// ── Mehrfachauswahl (Bulk: Status ändern / löschen) ──
|
||||
const auswahl = ref(new Set());
|
||||
const istGewaehlt = (id) => auswahl.value.has(id);
|
||||
const toggleSel = (id) => { const s = new Set(auswahl.value); s.has(id) ? s.delete(id) : s.add(id); auswahl.value = s; };
|
||||
const selAlle = () => { auswahl.value = new Set(gefiltert.value.map((k) => k.id)); };
|
||||
const selKeine = () => { auswahl.value = new Set(); };
|
||||
async function bulkStatus(status) {
|
||||
const ids = [...auswahl.value]; if (!ids.length || !status) return;
|
||||
try {
|
||||
await api.patch('/items/contacts', { keys: ids, data: { lead_status: status } });
|
||||
for (const k of kontakte.value) if (auswahl.value.has(k.id)) k.lead_status = status;
|
||||
meldung.value = `Status für ${ids.length} Kontakt(e) geändert.`; setTimeout(() => { meldung.value = ''; }, 2500);
|
||||
selKeine();
|
||||
} catch (e) { fehler.value = e?.response?.data?.errors?.[0]?.message || e.message || 'Status-Änderung fehlgeschlagen.'; }
|
||||
}
|
||||
async function bulkLoeschen() {
|
||||
const ids = [...auswahl.value]; if (!ids.length) return;
|
||||
if (typeof window !== 'undefined' && !window.confirm(`${ids.length} Kontakt(e) endgültig löschen?`)) return;
|
||||
try {
|
||||
await api.delete('/items/contacts', { data: ids });
|
||||
const raus = auswahl.value;
|
||||
kontakte.value = kontakte.value.filter((k) => !raus.has(k.id));
|
||||
selKeine();
|
||||
meldung.value = `${ids.length} Kontakt(e) gelöscht.`; setTimeout(() => { meldung.value = ''; }, 2500);
|
||||
} catch (e) { fehler.value = e?.response?.data?.errors?.[0]?.message || e.message || 'Löschen fehlgeschlagen.'; }
|
||||
}
|
||||
|
||||
// ── Liste ──
|
||||
function karte(k) {
|
||||
return h('div', {
|
||||
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);' : ''),
|
||||
}, [
|
||||
h('div', { style: 'min-width:0;' }, [
|
||||
h('input', {
|
||||
type: 'checkbox', checked: istGewaehlt(k.id),
|
||||
onClick: (e) => { e.stopPropagation(); toggleSel(k.id); },
|
||||
title: 'Auswählen', style: 'width:18px;height:18px;margin-top:3px;cursor:pointer;flex-shrink:0;',
|
||||
}),
|
||||
h('div', { style: 'min-width:0;flex:1;' }, [
|
||||
h('div', { style: 'font-size:16px;font-weight:600;margin-bottom:2px;' }, anzeigeName(k)),
|
||||
subdued(adresse(k) || '—', 'font-size:13px;'),
|
||||
k.phone ? subdued('📞 ' + k.phone, 'font-size:13px;margin-top:2px;') : null,
|
||||
@@ -245,18 +278,38 @@ const Board = defineComponent({
|
||||
]),
|
||||
]),
|
||||
h('div', { style: 'display:flex;gap:8px;flex-wrap:wrap;align-items:center;justify-content:space-between;' }, [
|
||||
subdued(`${gefiltert.value.length} Kontakt(e)`),
|
||||
h('label', { style: 'display:flex;align-items:center;gap:6px;font-size:13px;color:var(--foreground-subdued);cursor:pointer;' }, [
|
||||
h('input', { type: 'checkbox',
|
||||
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;' }),
|
||||
`${gefiltert.value.length} Kontakt(e)`,
|
||||
]),
|
||||
h('div', { style: 'display:flex;gap:8px;' }, [
|
||||
btn('↻ Aktualisieren', laden),
|
||||
btn('⭳ CSV-Export', exportCsv),
|
||||
]),
|
||||
]),
|
||||
]);
|
||||
// Bulk-Leiste, sobald mindestens ein Kontakt ausgewählt ist.
|
||||
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;',
|
||||
}, [
|
||||
h('span', { style: 'font-weight:600;' }, `${auswahl.value.size} ausgewählt`),
|
||||
h('span', {}, 'Status:'),
|
||||
h('select', {
|
||||
style: 'border-radius:6px;padding:5px 8px;font:inherit;font-size:13px;border:none;cursor:pointer;', value: '',
|
||||
onChange: (e) => { if (e.target.value) { bulkStatus(e.target.value); e.target.value = ''; } },
|
||||
}, [h('option', { value: '' }, 'ändern auf …'), ...LEAD.map((l) => h('option', { value: l.key }, l.label))]),
|
||||
h('button', { type: 'button', onClick: bulkLoeschen,
|
||||
style: 'margin-left:auto;cursor:pointer;border:1px solid #fff;background:transparent;color:#fff;border-radius:6px;padding:5px 12px;font:inherit;font-size:13px;font-weight:600;' }, '🗑 Löschen'),
|
||||
h('button', { type: 'button', onClick: selKeine,
|
||||
style: 'cursor:pointer;border:none;background:rgba(255,255,255,.2);color:#fff;border-radius:6px;padding:5px 12px;font:inherit;font-size:13px;' }, 'Aufheben'),
|
||||
]) : null;
|
||||
const liste = loading.value ? subdued('Lädt …')
|
||||
: gefiltert.value.length ? h('div', {}, gefiltert.value.map(karte))
|
||||
: subdued('Keine Kontakte. Sobald ein Auftrag versendet wird, erscheinen die Empfänger hier automatisch.');
|
||||
const hinweis = subdued('CSV-Import: Content → Kontakte → Import (oben rechts).', 'margin-top:16px;font-size:12px;');
|
||||
return [kopf, liste, hinweis];
|
||||
return [kopf, bulk, liste, hinweis].filter(Boolean);
|
||||
}
|
||||
|
||||
// ── Detail ──
|
||||
|
||||
Reference in new issue
Block a user