From ba9c073bb9bd3b427711e9a14d9ded4f39286521 Mon Sep 17 00:00:00 2001 From: s4luorth Date: Fri, 11 Sep 2026 21:43:28 +0200 Subject: [PATCH] WP-Feld-Mapping robuster + Logging; Muster ohne Direktdruck; Kontakte-Fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - skrift-orders: sample-/offer-request nehmen viele Feldnamen-Varianten an (feldWert-Helfer) und loggen eingehende + gefüllte Feldnamen zur Diagnose. - skrift-produktion: „Direkt drucken" bei Musteranfragen entfernt (nur noch „In manuellen Druck übernehmen"). - skrift-kontakte: Bulk-Status-Dropdown lesbar (weiße Schrift auf weiß behoben); Shift-Klick wählt den ganzen Bereich zwischen zwei Checkboxen. Co-Authored-By: Claude Opus 4.8 --- .../dist/index.js | 23 ++++++-- .../dist/index.js | 52 ++++++++++++------- .../dist/index.js | 1 - 3 files changed, 52 insertions(+), 24 deletions(-) diff --git a/extensions/directus-extension-skrift-kontakte/dist/index.js b/extensions/directus-extension-skrift-kontakte/dist/index.js index 4d789e1..907ad21 100644 --- a/extensions/directus-extension-skrift-kontakte/dist/index.js +++ b/extensions/directus-extension-skrift-kontakte/dist/index.js @@ -214,8 +214,24 @@ const Board = defineComponent({ // ── Mehrfachauswahl (Bulk: Status ändern / löschen) ── const auswahl = ref(new Set()); + const lastIdx = ref(-1); // zuletzt geklickte Zeile (für Shift-Bereichsauswahl) 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; }; + // Klick auf eine Karten-Checkbox: mit Shift den ganzen Bereich seit dem letzten Klick. + const klickSel = (id, ev) => { + const list = gefiltert.value; + const idx = list.findIndex((x) => x.id === id); + if (ev && ev.shiftKey && lastIdx.value >= 0 && idx >= 0) { + const a = Math.min(lastIdx.value, idx); + const b = Math.max(lastIdx.value, idx); + const s = new Set(auswahl.value); + for (let i = a; i <= b; i += 1) s.add(list[i].id); + auswahl.value = s; + } else { + toggleSel(id); + } + lastIdx.value = idx; + }; const selAlle = () => { auswahl.value = new Set(gefiltert.value.map((k) => k.id)); }; const selKeine = () => { auswahl.value = new Set(); }; async function bulkStatus(status) { @@ -248,7 +264,7 @@ const Board = defineComponent({ }, [ h('input', { type: 'checkbox', checked: istGewaehlt(k.id), - onClick: (e) => { e.stopPropagation(); toggleSel(k.id); }, + onClick: (e) => { e.stopPropagation(); klickSel(k.id, e); }, title: 'Auswählen', style: 'width:18px;height:18px;margin-top:3px;cursor:pointer;flex-shrink:0;', }), h('div', { style: 'min-width:0;flex:1;' }, [ @@ -297,9 +313,10 @@ const Board = defineComponent({ 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: '', + style: 'border-radius:6px;padding:5px 8px;font:inherit;font-size:13px;border:none;cursor:pointer;background:#fff;color:#1a1a1a;', 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('option', { value: '', style: 'color:#1a1a1a;' }, 'ändern auf …'), + ...LEAD.map((l) => h('option', { value: l.key, style: 'color:#1a1a1a;background:#fff;' }, 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, diff --git a/extensions/directus-extension-skrift-orders/dist/index.js b/extensions/directus-extension-skrift-orders/dist/index.js index 0ba80a0..1e3d272 100644 --- a/extensions/directus-extension-skrift-orders/dist/index.js +++ b/extensions/directus-extension-skrift-orders/dist/index.js @@ -163,6 +163,14 @@ async function paypalToken() { // Auslandsländer als GANZE Wörter (\b) – sonst matchte „schweiz" in „Schweizer // Straße", „usa" in „Neusass", „polen" in „Polenz" usw. und flaggte fälschlich Ausland. const AUSLAND_RE = /\b(österreich|schweiz|austria|switzerland|frankreich|france|italien|italy|niederlande|netherlands|belgien|belgium|luxemburg|luxembourg|spanien|spain|portugal|usa|vereinigte staaten|united states|kanada|canada|england|großbritannien|grossbritannien|united kingdom|dänemark|denmark|schweden|sweden|norwegen|norway|finnland|finland|polen|poland|tschechien|czech|ungarn|hungary|griechenland|greece|irland|ireland|australien|australia|liechtenstein)\b/i; +// Erstes nicht-leeres Feld aus mehreren möglichen Namen (App + WordPress-Varianten). +function feldWert(body, ...keys) { + for (const k of keys) { + const v = body && body[k]; + if (v != null && String(v).trim()) return String(v).trim(); + } + return ''; +} function zielInland(e) { const land = String(e?.country || '').trim(); if (land) { @@ -1426,15 +1434,15 @@ const handler = (router, { services, getSchema, logger }) => { // ── Angebotsanfrage („Individuelles Angebot") ──────────────────────────── router.post('/offer-request', async (req, res) => { const body = req.body || {}; - // Feld-Aliase (auch WordPress): vorname→first_name, name→last_name, telefon→phone, - // produkt_interesse→product. App-Feldnamen haben Vorrang. - const vorname = String(body.first_name ?? body.vorname ?? '').trim(); - const nachname = String(body.last_name ?? body.name ?? '').trim(); + try { logger.info('[skrift-orders] offer-request Felder: ' + JSON.stringify(Object.keys(body))); } catch { /* egal */ } + // Feld-Aliase (App + WordPress + Varianten). App-Feldnamen haben Vorrang. + const vorname = feldWert(body, 'first_name', 'vorname', 'firstname', 'first', 'given_name'); + const nachname = feldWert(body, 'last_name', 'nachname', 'name', 'lastname', 'surname', 'familienname'); const name = [vorname, nachname].filter(Boolean).join(' '); - const phone = String(body.phone ?? body.telefon ?? '').trim(); - const product = String(body.product ?? body.produkt_interesse ?? '').trim(); - const email = norm(body.email); - const message = String(body.message || '').trim(); + const phone = feldWert(body, 'phone', 'telefon', 'tel', 'telephone', 'telefonnummer'); + const product = feldWert(body, 'product', 'produkt_interesse', 'produkt', 'produkt_interese'); + const email = norm(feldWert(body, 'email', 'e_mail', 'e-mail', 'mail')); + const message = String(body.message || body.nachricht || body.vorhaben || '').trim(); const errors = []; if (!name) errors.push('Name fehlt.'); @@ -1486,18 +1494,22 @@ const handler = (router, { services, getSchema, logger }) => { // ── Musterbestellung (kostenlos) ───────────────────────────────────────── router.post('/sample-request', async (req, res) => { const body = req.body || {}; - const email = norm(body.email); - // Feld-Aliase, damit auch das WordPress-Formular direkt anlegen kann: - // vorname→first_name, name→last_name, telefon→phone, zipcode→zip, ort→city, - // produkt_interesse→product. Die App-Feldnamen haben Vorrang. - const first = String(body.first_name ?? body.vorname ?? '').trim(); - const last = String(body.last_name ?? body.name ?? '').trim(); - const product = String(body.product ?? body.produkt_interesse ?? '').trim(); - const phone = String(body.phone ?? body.telefon ?? '').trim(); - const street = String(body.street ?? '').trim(); - const zip = String(body.zip ?? body.zipcode ?? '').trim(); - const city = String(body.city ?? body.ort ?? '').trim(); - const country = String(body.country ?? '').trim(); + // Diagnose: welche Feldnamen kommen rein + welche davon GEFÜLLT (Werte selbst + // NICHT loggen – Datenschutz). So sieht man, ob WP z. B. name/ort leer schickt. + try { + logger.info('[skrift-orders] sample-request Felder: ' + JSON.stringify(Object.keys(body)) + + ' | gefüllt: ' + JSON.stringify(Object.keys(body).filter((k) => String(body[k] ?? '').trim()))); + } catch { /* egal */ } + const email = norm(feldWert(body, 'email', 'e_mail', 'e-mail', 'mail')); + // Feld-Aliase (App + WordPress + gängige Varianten). App-Feldnamen haben Vorrang. + const first = feldWert(body, 'first_name', 'vorname', 'firstname', 'first', 'given_name'); + const last = feldWert(body, 'last_name', 'nachname', 'name', 'lastname', 'surname', 'familienname'); + const product = feldWert(body, 'product', 'produkt_interesse', 'produkt', 'produkt_interese'); + const phone = feldWert(body, 'phone', 'telefon', 'tel', 'telephone', 'telefonnummer'); + const street = feldWert(body, 'street', 'strasse', 'straße', 'adresse', 'address', 'strasse_hausnummer'); + const zip = feldWert(body, 'zip', 'zipcode', 'plz', 'postleitzahl', 'postal_code'); + const city = feldWert(body, 'city', 'ort', 'stadt', 'wohnort'); + const country = feldWert(body, 'country', 'land'); const vollName = [first, last].filter(Boolean).join(' '); const errors = []; if (!product) errors.push('Produkt fehlt.'); diff --git a/extensions/directus-extension-skrift-produktion/dist/index.js b/extensions/directus-extension-skrift-produktion/dist/index.js index 1bb0067..61218b2 100644 --- a/extensions/directus-extension-skrift-produktion/dist/index.js +++ b/extensions/directus-extension-skrift-produktion/dist/index.js @@ -1111,7 +1111,6 @@ const Board = defineComponent({ const aktionen = h('div', { style: 'display:flex;gap:8px;flex-wrap:wrap;' }, [ // Muster (Druckprodukt) → manueller Druck; individuelles Angebot → kein Druck. istMuster ? btn('✍ In manuellen Druck', () => musterUebernehmen(req), 'primary') : null, - istMuster ? btn('🖨 Direkt drucken', () => openMuster(req)) : null, btn('👤 Details', () => openReqInfo(req)), ]); return h('div', { key: 'r' + req.id, style: CARD + `margin-bottom:14px;border-left:4px solid ${istMuster ? FARBE_MUSTER : FARBE_ANGEBOT};` }, [kopf, meta, nachricht, aktionen]);