„Ja, anlegen“ zeichnet nur noch die Hinweis-Liste neu (offene, ungespeicherte Formulare und Zeilen bleiben), statt den ganzen Abschnitt zu ersetzen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
355 lines
22 KiB
JavaScript
355 lines
22 KiB
JavaScript
/* Oeffnungszeiten live im Kundenbereich: Editor, Bereiche 4 „Hinweis-Leiste“ und 5 „Kontakt-Leiste (Handy)“ (Spec 3.4). Jeder Bereich ist eine Funktion
|
||
(t) => Element; t = { ctx, sp, zeichne, wurzel } kommt aus zeiten-editor.js (sp = Speicher des Editors, zeichne([…]) zeichnet einzelne Abschnitte neu).
|
||
Jede Aenderung speichert die ganzen Daten (zeiten-speichern.js); nach einem Speichern wird nur der eigene Abschnitt neu gezeichnet, ungespeicherte Eingaben
|
||
in anderen Formularen bleiben stehen. Abgeschaltete Funktion (ctx.nurLesen): dieselben Felder gesperrt, ohne Knoepfe zum Aendern. Stil: zeiten-ausnahmen.js.
|
||
Hinweis-Leiste: Liste der laufenden und geplanten Hinweise (Vergangenes eingeklappt), „Hinweis-Leiste anlegen“ (dieser Text ist fest: die Relaunch-Station
|
||
tippt den Knopf an) und je Hinweis Text (<= 140), Art (Info, Urlaub, Aktion, Wichtig mit festen Farben wie auf der Website), optionaler Knopf (Link
|
||
https oder Anrufen) und Zeitraum von–bis (Kalendertage, beide optional; „Aktion“ verlangt ein Enddatum). Der Browser prueft frueh (Aktion ohne Ende, Ende vor
|
||
Beginn, leerer Text) und zeigt bei Link und Rufnummer nur einen Hinweis (linkOk): entschieden wird auf dem Server, sein Fehler steht am Feld.
|
||
Ein anderer Abschnitt kann einen Hinweis anlegen (Urlaub-Frage der Ausnahmen): hinweisListeErneuern(t) zeichnet dann nur die Liste neu und laesst offene Formulare dieses
|
||
Abschnitts samt ungespeicherter Eingaben stehen.
|
||
Kontakt-Leiste: Ein/Aus, Telefon, Adresse, WhatsApp (Nummer, Starttext, Haken „Datenschutzerklaerung“). Der Server speichert Rufnummern als Ziffern mit
|
||
Laendervorwahl; der Editor zeigt die gespeicherte Nummer aus der Antwort (+49 …) im Feld und in der Meldung, damit der Kunde sieht, was gilt.
|
||
R12: den Zeitpunkt der WhatsApp-Bestaetigung (kontakt.whatsapp.bestaetigtAt) setzt nur der Kunde selbst. In der Kundenansicht ist der Haken gesperrt und der
|
||
gespeicherte Wert geht unveraendert zurueck (ohne gespeicherten kein Speichern mit WhatsApp); beim Kunden geht ein frischer Zeitpunkt nur hinaus, wenn der Haken
|
||
neu gesetzt wurde (der Server ersetzt ihn durch seine Zeit), sonst der gespeicherte Wert. */
|
||
|
||
import { datumKurz, heuteBerlin, istTag } from './termine-view.js'
|
||
import { abschnitt, allgemeinerFehler, editorZeile, feld, gesperrt, h, hakenFeld, neuerEintrag, zeigeFehler } from './termine-dom.js'
|
||
import { HINWEIS_ARTEN, ZEITEN_LIMITS, ZEITEN_MAX, linkOk, neueZeitenId, preisHinweisNoetig } from './zeiten-view.js'
|
||
import { entfernenKnopf, nachSpeichern, stelleVon } from './zeiten-speichern.js'
|
||
|
||
const istObjekt = (wert) => wert !== null && typeof wert === 'object' && !Array.isArray(wert)
|
||
|
||
/* ----------------------------------------------------------- Hinweis-Leiste --- */
|
||
const ART_NAME = Object.freeze({ info: 'Info', urlaub: 'Urlaub', aktion: 'Aktion', wichtig: 'Wichtig' })
|
||
const KNOPF_WAHL = Object.freeze([['', 'Kein Knopf'], ['link', 'Link'], ['anruf', 'Anrufen']])
|
||
const PREIS_HINWEIS = 'Bei Preisangaben gelten Endpreise. Bei Waren nennst du dazu den niedrigsten Preis der letzten 30 Tage.'
|
||
const LINK_HINWEIS = 'Bitte gib eine vollständige Adresse mit https:// an.'
|
||
const ANRUF_HINWEIS = 'Bitte nur Ziffern, Leerzeichen, + / und - (höchstens 30 Zeichen).'
|
||
const KNOPF_ARTEN = Object.freeze(['link', 'anruf'])
|
||
|
||
/** Rufnummer eines Anruf-Knopfs wie auf dem Server: nur Ziffern, Leerzeichen, / und -, ein + nur vorn, mindestens eine Ziffer, hoechstens 30 Zeichen. */
|
||
const anrufOk = (ziel) => {
|
||
const s = typeof ziel === 'string' ? ziel.trim() : ''
|
||
return s !== '' && [...s].length <= ZEITEN_MAX.telefon && /^\+?[0-9 /-]+$/.test(s) && /\d/.test(s)
|
||
}
|
||
|
||
/** 'JJJJ-MM-TT' oder '' (fehlt, null oder kein Kalendertag). */
|
||
const tagOder = (wert) => (typeof wert === 'string' && istTag(wert) ? wert : '')
|
||
|
||
/** „12.10.2026 – 26.10.2026“, „ab …“, „bis …“ oder „immer sichtbar“. */
|
||
function zeitraumText(x) {
|
||
const von = tagOder(x.von)
|
||
const bis = tagOder(x.bis)
|
||
if (von && bis) return von === bis ? datumKurz(von) : `${datumKurz(von)} – ${datumKurz(bis)}`
|
||
if (von) return `ab ${datumKurz(von)}`
|
||
if (bis) return `bis ${datumKurz(bis)}`
|
||
return 'immer sichtbar'
|
||
}
|
||
|
||
const nachBeginn = (a, b) => (tagOder(a.von) < tagOder(b.von) ? -1 : tagOder(a.von) > tagOder(b.von) ? 1 : 0)
|
||
|
||
/** Eine Zeile der Hinweis-Liste (laufend, geplant oder vergangen) mit ihrem Bearbeiten-Formular unter dem Knopf. */
|
||
function hinweisReihe(t, x, heute) {
|
||
const { ctx } = t
|
||
const vorbei = tagOder(x.bis) !== '' && x.bis < heute
|
||
const art = HINWEIS_ARTEN.includes(x.art) ? x.art : 'info'
|
||
const knopf = istObjekt(x.knopf) && KNOPF_ARTEN.includes(x.knopf.art) ? x.knopf : null
|
||
const von = tagOder(x.von)
|
||
const zustand = vorbei ? ['vorbei', 'off'] : von && von > heute ? ['geplant', 'wait'] : ['läuft', 'ok']
|
||
const text = typeof x.text === 'string' ? x.text : ''
|
||
return editorZeile({
|
||
attrs: { 'data-id': x.id },
|
||
nurLesen: ctx.nurLesen,
|
||
label: `Hinweis ${text}`,
|
||
haupt: [
|
||
h('strong', { class: 'tm-name' }, text),
|
||
h('span', { class: 'tm-meta' },
|
||
h('span', { class: `zt-art zt-art-${art}` }, ART_NAME[art]),
|
||
` · ${zeitraumText(x)}${knopf ? ` · Knopf „${typeof knopf.text === 'string' ? knopf.text : ''}“ (${knopf.art === 'anruf' ? 'Anrufen' : 'Link'})` : ''}`),
|
||
],
|
||
pille: h('span', { class: `tm-pille tm-pille-${zustand[1]}` }, zustand[0]),
|
||
baue: (schliessen) => formular(t, { a: x, schliessen }),
|
||
})
|
||
}
|
||
|
||
/**
|
||
* Die Liste der Hinweise: [Liste der laufenden und geplanten (oder „Keine Hinweise eingetragen.“), eingeklappte Liste der vergangenen]. vorhanden (Map ID => <li>):
|
||
* Zeilen, die schon in der Seite stehen, werden wiederverwendet (samt ihrem offenen Formular) statt neu gebaut; nur für den Fall, dass sich an diesen Hinweisen nichts geändert hat.
|
||
*/
|
||
function hinweisListe(t, vorhanden = new Map()) {
|
||
const alle = (Array.isArray(t.ctx.stand.daten.hinweise) ? t.ctx.stand.daten.hinweise : []).filter((x) => istObjekt(x) && typeof x.id === 'string')
|
||
const heute = heuteBerlin()
|
||
const vorbei = (x) => tagOder(x.bis) !== '' && x.bis < heute
|
||
const reihe = (x) => vorhanden.get(x.id) ?? hinweisReihe(t, x, heute)
|
||
const kommend = alle.filter((x) => !vorbei(x)).sort(nachBeginn)
|
||
const alt = alle.filter(vorbei).sort(nachBeginn).reverse()
|
||
return {
|
||
voll: alle.length >= ZEITEN_LIMITS.hinweise,
|
||
teile: [
|
||
kommend.length
|
||
? h('ul', { class: 'tm-eintraege', 'aria-label': 'Laufende und geplante Hinweise' }, kommend.map(reihe))
|
||
: h('p', { class: 'tm-leer' }, 'Keine Hinweise eingetragen.'),
|
||
alt.length
|
||
? h('details', { class: 'tm-details' }, h('summary', null, `Vergangene Hinweise (${alt.length})`), h('ul', { class: 'tm-eintraege' }, alt.map(reihe)))
|
||
: null,
|
||
],
|
||
}
|
||
}
|
||
|
||
const GRENZ_TEXT = `Mehr als ${ZEITEN_LIMITS.hinweise} Hinweise sind nicht möglich.`
|
||
|
||
export function hinweise(t) {
|
||
const { ctx } = t
|
||
const { voll, teile } = hinweisListe(t)
|
||
const neu = neuerEintrag({
|
||
nurLesen: ctx.nurLesen, text: 'Hinweis-Leiste anlegen', werkzeug: 'hinweis-neu', primaer: true, voll, grenzText: GRENZ_TEXT,
|
||
baue: (schliessen) => formular(t, { a: null, schliessen }),
|
||
})
|
||
return abschnitt('Hinweis-Leiste',
|
||
'Eine Leiste ganz oben auf deiner Website – zum Beispiel für Betriebsurlaub, eine Aktion oder etwas Wichtiges. Die Leiste erscheint nur in dem Zeitraum, den du einstellst. Gelten mehrere gleichzeitig, zeigt die Website nur einen: zuerst „Wichtig“, dann „Urlaub“, „Aktion“ und „Info“.',
|
||
h('div', { class: 'zt-werkzeuge' }, neu.leiste),
|
||
neu.box,
|
||
h('div', { class: 'zt-hinweis-liste' }, teile))
|
||
}
|
||
|
||
/**
|
||
* Nur die Liste der Hinweise neu zeichnen, ohne den Abschnitt anzufassen: für den Fall, dass ein anderer Abschnitt einen Hinweis angelegt hat (die Frage „Hinweis-Leiste dazu
|
||
* anlegen?“ der Ausnahmen). Offene, ungespeicherte Formulare bleiben stehen: das Formular „Neuer Hinweis“ liegt nicht in der Liste, und die Zeilen der bisherigen Hinweise werden
|
||
* mit ihrem offenen Bearbeiten-Formular wiederverwendet. Der Knopf „Hinweis-Leiste anlegen“ folgt der Obergrenze. => false, wenn es den Abschnitt (noch) nicht gibt.
|
||
*/
|
||
export function hinweisListeErneuern(t) {
|
||
const el = t.wurzel.querySelector('[data-abschnitt="hinweise"]')
|
||
const platz = el?.querySelector('.zt-hinweis-liste')
|
||
if (!platz) return false
|
||
const vorhanden = new Map([...platz.querySelectorAll('li[data-id]')].map((li) => [li.getAttribute('data-id'), li]))
|
||
const { voll, teile } = hinweisListe(t, vorhanden)
|
||
platz.replaceChildren(...teile)
|
||
const knopf = el.querySelector('[data-werkzeug="hinweis-neu"]')
|
||
if (knopf) {
|
||
knopf.disabled = voll
|
||
const zeile = knopf.parentNode
|
||
const grenze = zeile.querySelector('p')
|
||
if (voll && !grenze) zeile.append(h('p', { class: 'tm-hinweis' }, GRENZ_TEXT))
|
||
else if (!voll && grenze) grenze.remove()
|
||
}
|
||
return true
|
||
}
|
||
|
||
/**
|
||
* Formular eines neuen (a null) oder bestehenden Hinweises. Die Felder heissen nach dem Pfad der Fehler des Servers (hinweise.<ID>.text, .art, .knopf.art,
|
||
* .knopf.text, .knopf.ziel, .von, .bis); Absenden sucht den Hinweis per ID.
|
||
*/
|
||
function formular(t, { a, schliessen }) {
|
||
const { ctx, sp } = t
|
||
const neu = a === null
|
||
const alt = a || { id: neueZeitenId('h'), art: 'info', text: '', knopf: null, von: null, bis: null }
|
||
const p = `hinweise.${alt.id}`
|
||
const knopfAlt = istObjekt(alt.knopf) && KNOPF_ARTEN.includes(alt.knopf.art) ? alt.knopf : null
|
||
|
||
const text = h('input', { type: 'text', maxlength: ZEITEN_MAX.hinweisText, autocomplete: 'off', placeholder: 'z. B. Heute geschlossen – morgen wieder für dich da', value: typeof alt.text === 'string' ? alt.text : '' })
|
||
const art = h('select', null, HINWEIS_ARTEN.map((k) => h('option', { value: k, selected: k === (HINWEIS_ARTEN.includes(alt.art) ? alt.art : 'info') }, ART_NAME[k])))
|
||
const knopfArt = h('select', null, KNOPF_WAHL.map(([wert, name]) => h('option', { value: wert, selected: wert === (knopfAlt?.art ?? '') }, name)))
|
||
const knopfText = h('input', { type: 'text', maxlength: ZEITEN_MAX.knopfText, autocomplete: 'off', placeholder: 'z. B. Mehr erfahren', value: typeof knopfAlt?.text === 'string' ? knopfAlt.text : '' })
|
||
const knopfZiel = h('input', { type: 'url', maxlength: ZEITEN_MAX.link, autocomplete: 'off', value: typeof knopfAlt?.ziel === 'string' ? knopfAlt.ziel : '' })
|
||
const von = h('input', { type: 'date', value: tagOder(alt.von) })
|
||
const bis = h('input', { type: 'date', value: tagOder(alt.bis) })
|
||
|
||
const preisHinweis = h('p', { class: 'tm-hinweis', 'data-hinweis': 'preis', hidden: true }, PREIS_HINWEIS)
|
||
const zielHinweis = h('p', { class: 'tm-hinweis', 'data-hinweis': 'knopf-ziel', hidden: true }, LINK_HINWEIS)
|
||
const zielFeld = feld(`${p}.knopf.ziel`, 'Link', knopfZiel)
|
||
zielFeld.insertBefore(zielHinweis, zielFeld.querySelector('[data-fehler]'))
|
||
const textFeld = feld(`${p}.text`, 'Text', text, `Höchstens ${ZEITEN_MAX.hinweisText} Zeichen.`)
|
||
textFeld.insertBefore(preisHinweis, textFeld.querySelector('[data-fehler]'))
|
||
const knopfFelder = h('div', { class: 'tm-raster zt-knopf-felder', 'data-knopf-felder': '' },
|
||
feld(`${p}.knopf.text`, 'Beschriftung des Knopfs', knopfText, `Höchstens ${ZEITEN_MAX.knopfText} Zeichen.`),
|
||
zielFeld)
|
||
|
||
// Muster: so sieht die Leiste in der Farbe der Art aus (feste Farben wie auf der Website)
|
||
const musterText = h('span', { class: 'zt-muster-text' })
|
||
const musterKnopf = h('span', { class: 'zt-muster-knopf' })
|
||
const muster = h('div', { class: 'zt-muster', 'aria-hidden': 'true', 'data-muster': '' }, musterText, musterKnopf)
|
||
|
||
const aktualisiere = () => {
|
||
preisHinweis.hidden = !preisHinweisNoetig(text.value)
|
||
const wahl = knopfArt.value
|
||
knopfFelder.hidden = wahl === ''
|
||
knopfZiel.type = wahl === 'anruf' ? 'tel' : 'url'
|
||
knopfZiel.placeholder = wahl === 'anruf' ? 'z. B. 06301 123456' : 'https://'
|
||
zielFeld.querySelector('label').textContent = wahl === 'anruf' ? 'Telefonnummer' : 'Link'
|
||
const ziel = knopfZiel.value.trim()
|
||
zielHinweis.textContent = wahl === 'anruf' ? ANRUF_HINWEIS : LINK_HINWEIS
|
||
zielHinweis.hidden = wahl === '' || ziel === '' || (wahl === 'anruf' ? anrufOk(ziel) : linkOk(ziel))
|
||
muster.className = `zt-muster zt-art-${art.value}`
|
||
musterText.textContent = text.value.trim() || 'Dein Hinweistext'
|
||
musterKnopf.textContent = knopfText.value.trim()
|
||
musterKnopf.hidden = wahl === '' || knopfText.value.trim() === ''
|
||
}
|
||
for (const steuer of [text, art, knopfArt, knopfText, knopfZiel]) {
|
||
steuer.addEventListener('input', aktualisiere)
|
||
steuer.addEventListener('change', aktualisiere)
|
||
}
|
||
aktualisiere()
|
||
|
||
const entfernen = neu || ctx.nurLesen ? null : entfernenKnopf(sp, {
|
||
titel: 'Hinweis entfernen?',
|
||
text: 'Die Leiste verschwindet von deiner Website.',
|
||
aendere: (entwurf) => { entwurf.hinweise = (Array.isArray(entwurf.hinweise) ? entwurf.hinweise : []).filter((x) => x?.id !== alt.id) },
|
||
nachher: () => nachSpeichern(t, 'Hinweis entfernt.', '[data-werkzeug="hinweis-neu"]', () => t.zeichne(['hinweise'])),
|
||
})
|
||
const speichern = h('button', { type: 'submit', class: 'kb-btn-primary', 'data-aktion': 'hinweis-speichern' }, neu ? 'Hinweis speichern' : 'Speichern')
|
||
const form = h('form', { class: 'kb-panel tm-formular zt-hinweis-form', novalidate: true, 'aria-label': neu ? 'Neuer Hinweis' : 'Hinweis bearbeiten' },
|
||
h('h3', null, neu ? 'Neuer Hinweis' : 'Hinweis'),
|
||
gesperrt(ctx.nurLesen, [
|
||
textFeld,
|
||
feld(`${p}.art`, 'Art', art, 'Jede Art hat ihre feste Farbe.'),
|
||
muster,
|
||
feld(`${p}.knopf.art`, 'Knopf (optional)', knopfArt, 'Ein Knopf in der Leiste: ein Link auf eine Seite oder ein Anruf.'),
|
||
knopfFelder,
|
||
h('div', { class: 'tm-raster' },
|
||
feld(`${p}.von`, 'Sichtbar ab (optional)', von),
|
||
feld(`${p}.bis`, 'Sichtbar bis (bei einer Aktion nötig)', bis)),
|
||
h('p', { class: 'tm-hinweis' }, 'Ohne Datum gilt der Hinweis immer. Bei einer Aktion brauchst du ein Enddatum.'),
|
||
]),
|
||
allgemeinerFehler(),
|
||
h('div', { class: 'tm-frage-knoepfe' },
|
||
ctx.nurLesen ? null : speichern,
|
||
h('button', { type: 'button', class: 'kb-btn-outline', onclick: schliessen }, ctx.nurLesen ? 'Schließen' : 'Abbrechen'),
|
||
entfernen?.knopf))
|
||
|
||
form.addEventListener('submit', async (ev) => {
|
||
ev.preventDefault()
|
||
const ok = await sp.speichere((entwurf) => {
|
||
const fehler = {}
|
||
const vonWert = von.value.trim()
|
||
const bisWert = bis.value.trim()
|
||
const hinweisText = text.value.trim()
|
||
if (hinweisText === '') fehler[`${p}.text`] = 'Bitte gib einen Text ein.'
|
||
if (vonWert !== '' && !istTag(vonWert)) fehler[`${p}.von`] = 'Bitte gib ein gültiges Datum an.'
|
||
if (bisWert !== '' && !istTag(bisWert)) fehler[`${p}.bis`] = 'Bitte gib ein gültiges Datum an.'
|
||
if (!fehler[`${p}.von`] && !fehler[`${p}.bis`]) {
|
||
if (vonWert !== '' && bisWert !== '' && bisWert < vonWert) fehler[`${p}.bis`] = 'Das Ende darf nicht vor dem Beginn liegen.'
|
||
else if (art.value === 'aktion' && bisWert === '') fehler[`${p}.bis`] = 'Eine Aktion braucht ein Enddatum.'
|
||
}
|
||
if (Object.keys(fehler).length) {
|
||
zeigeFehler(form, { fehler })
|
||
return false
|
||
}
|
||
// ein Knopf ohne Beschriftung und Ziel ist kein Knopf (wie auf dem Server)
|
||
const ziel = knopfZiel.value.trim()
|
||
const beschriftung = knopfText.value.trim()
|
||
const knopf = knopfArt.value !== '' && (ziel !== '' || beschriftung !== '') ? { art: knopfArt.value, ziel, text: beschriftung } : null
|
||
const eintrag = { id: alt.id, art: art.value, text: hinweisText, knopf, von: vonWert || null, bis: bisWert || null }
|
||
if (neu) {
|
||
entwurf.hinweise = [...(Array.isArray(entwurf.hinweise) ? entwurf.hinweise : []), eintrag]
|
||
return true
|
||
}
|
||
const stelle = stelleVon(entwurf.hinweise, alt.id, form, 'Dieser Hinweis')
|
||
if (stelle < 0) return false
|
||
entwurf.hinweise[stelle] = eintrag
|
||
return true
|
||
}, { container: form })
|
||
if (!ok || !sp.nochHier()) return
|
||
nachSpeichern(t, 'Hinweis gespeichert.', neu ? '[data-werkzeug="hinweis-neu"]' : `li[data-id="${CSS.escape(alt.id)}"] [data-aktion="bearbeiten"]`, () => t.zeichne(['hinweise']))
|
||
})
|
||
return h('div', { class: 'tm-editor' }, form, entfernen?.box)
|
||
}
|
||
|
||
/* ------------------------------------------------------------ Kontakt-Leiste --- */
|
||
const WHATSAPP_HAKEN = 'Ich habe den WhatsApp-Hinweis in meine Datenschutzerklärung aufgenommen'
|
||
const HAKEN_FEHLT = 'Bitte bestätige, dass du den WhatsApp-Hinweis in deine Datenschutzerklärung aufgenommen hast.'
|
||
const KUNDE_BESTAETIGT = 'Der Kunde muss den WhatsApp-Hinweis selbst bestätigen – in der Kundenansicht geht das nicht.'
|
||
|
||
/** Gespeicherte Rufnummer (Ziffern mit Laendervorwahl, z. B. 491711234567) als „+49 1711234567“; alles andere bleibt, wie es gespeichert ist. */
|
||
export function nummerAnzeige(wert) {
|
||
const s = typeof wert === 'string' ? wert.trim() : ''
|
||
if (!/^\d{6,15}$/.test(s)) return s
|
||
return s.startsWith('49') ? `+49 ${s.slice(2)}` : `+${s}`
|
||
}
|
||
|
||
export function kontakt(t) {
|
||
const { ctx, sp } = t
|
||
const nurLesen = ctx.nurLesen
|
||
const kundenansicht = ctx.kundenansicht === true || ctx.stand.kundenansicht === true
|
||
const k = istObjekt(ctx.stand.daten.kontakt) ? ctx.stand.daten.kontakt : {}
|
||
const wa = istObjekt(k.whatsapp) ? k.whatsapp : null
|
||
const gespeichertAt = typeof wa?.bestaetigtAt === 'string' ? wa.bestaetigtAt : ''
|
||
const telefonAlt = typeof k.telefon === 'string' ? k.telefon : ''
|
||
const nummerAlt = typeof wa?.nummer === 'string' ? wa.nummer : ''
|
||
const telefonAnzeige = nummerAnzeige(telefonAlt)
|
||
const nummerAnz = nummerAnzeige(nummerAlt)
|
||
|
||
const an = h('input', { type: 'checkbox', checked: k.an === true })
|
||
const telefon = h('input', { type: 'tel', maxlength: ZEITEN_MAX.telefon, autocomplete: 'off', placeholder: 'z. B. 06301 123456', value: telefonAnzeige })
|
||
const adresse = h('input', { type: 'text', maxlength: ZEITEN_MAX.adresse, autocomplete: 'off', placeholder: 'z. B. Hauptstraße 1, 67722 Winnweiler', value: typeof k.adresse === 'string' ? k.adresse : '' })
|
||
const waAn = h('input', { type: 'checkbox', checked: wa !== null })
|
||
const nummer = h('input', { type: 'tel', maxlength: ZEITEN_MAX.telefon, autocomplete: 'off', placeholder: 'z. B. 0171 1234567', value: nummerAnz })
|
||
const starttext = h('textarea', { rows: 3, maxlength: ZEITEN_MAX.starttext, placeholder: 'z. B. Hallo, ich habe eine Frage.' })
|
||
starttext.value = typeof wa?.starttext === 'string' ? wa.starttext : ''
|
||
const bestaetigt = h('input', { type: 'checkbox', checked: gespeichertAt !== '', disabled: kundenansicht })
|
||
|
||
const waBlock = h('div', { class: 'zt-whatsapp', 'data-whatsapp': '', hidden: wa === null },
|
||
feld('kontakt.whatsapp.nummer', 'WhatsApp-Nummer', nummer, 'Mit Vorwahl. Wir speichern sie mit Ländervorwahl, zum Beispiel +49 …'),
|
||
feld('kontakt.whatsapp.starttext', 'Vorgeschriebener Text (optional)', starttext, `Dieser Text steht schon im Nachrichtenfeld, wenn jemand auf den WhatsApp-Knopf tippt. Höchstens ${ZEITEN_MAX.starttext} Zeichen.`),
|
||
hakenFeld('kontakt.whatsapp.bestaetigtAt', bestaetigt, WHATSAPP_HAKEN, 'Beim Tippen öffnet sich WhatsApp (ein Dienst von Meta). Das gehört in die Datenschutzerklärung deiner Website – frag uns im Chat, wenn du den passenden Absatz brauchst.'),
|
||
kundenansicht ? h('p', { class: 'tm-hinweis', 'data-hinweis': 'whatsapp-kundenansicht' }, 'Kundenansicht: Den Haken zur Datenschutzerklärung kann nur der Kunde selbst setzen.') : null)
|
||
waAn.addEventListener('change', () => { waBlock.hidden = !waAn.checked })
|
||
|
||
const speichern = h('button', { type: 'submit', class: 'kb-btn-primary', 'data-aktion': 'kontakt-speichern' }, 'Kontakt-Leiste speichern')
|
||
const form = h('form', { novalidate: true, 'aria-label': 'Kontakt-Leiste' },
|
||
gesperrt(nurLesen, [
|
||
hakenFeld('kontakt.an', an, 'Kontakt-Leiste auf dem Handy anzeigen', 'Eine Leiste unten auf deiner Website, nur auf dem Handy: Anrufen, Route und WhatsApp – je nachdem, was du einträgst.'),
|
||
feld('kontakt.telefon', 'Telefon', telefon, 'Für „Anrufen“. Wir speichern die Nummer mit Ländervorwahl (+49 für Deutschland).'),
|
||
feld('kontakt.adresse', 'Adresse für „Route“', adresse, `Höchstens ${ZEITEN_MAX.adresse} Zeichen.`),
|
||
hakenFeld('kontakt.whatsapp', waAn, 'WhatsApp-Knopf anbieten'),
|
||
waBlock,
|
||
]),
|
||
allgemeinerFehler(),
|
||
nurLesen ? null : h('div', { class: 'tm-frage-knoepfe' }, speichern))
|
||
|
||
form.addEventListener('submit', async (ev) => {
|
||
ev.preventDefault()
|
||
const ok = await sp.speichere((entwurf) => {
|
||
const fehler = {}
|
||
let whatsapp = null
|
||
if (waAn.checked) {
|
||
const nummerWert = nummer.value === nummerAnz ? nummerAlt : nummer.value.trim()
|
||
if (nummerWert === '') fehler['kontakt.whatsapp.nummer'] = 'Bitte gib die Nummer an.'
|
||
let bestaetigtAt
|
||
if (kundenansicht) {
|
||
// R12: im Namen des Kunden gibt es nie einen neuen Zeitpunkt; ein gespeicherter geht unveraendert zurueck
|
||
if (gespeichertAt !== '') bestaetigtAt = gespeichertAt
|
||
else fehler['kontakt.whatsapp.bestaetigtAt'] = KUNDE_BESTAETIGT
|
||
} else if (bestaetigt.checked) {
|
||
bestaetigtAt = gespeichertAt !== '' ? gespeichertAt : new Date().toISOString() // der Server ersetzt einen neuen Zeitpunkt durch seine Zeit
|
||
} else {
|
||
fehler['kontakt.whatsapp.bestaetigtAt'] = HAKEN_FEHLT
|
||
}
|
||
whatsapp = { nummer: nummerWert, starttext: starttext.value.trim(), bestaetigtAt }
|
||
}
|
||
if (Object.keys(fehler).length) {
|
||
zeigeFehler(form, { fehler })
|
||
return false
|
||
}
|
||
entwurf.kontakt = {
|
||
...(istObjekt(entwurf.kontakt) ? entwurf.kontakt : {}),
|
||
an: an.checked,
|
||
telefon: telefon.value === telefonAnzeige ? telefonAlt : telefon.value.trim(),
|
||
adresse: adresse.value.trim(),
|
||
whatsapp,
|
||
}
|
||
return true
|
||
}, { container: form })
|
||
if (!ok || !sp.nochHier()) return
|
||
// was der Server gespeichert hat, steht danach im Feld (nur dieser Abschnitt wird neu gezeichnet) und in der Meldung
|
||
const neu = istObjekt(ctx.stand.daten.kontakt) ? ctx.stand.daten.kontakt : {}
|
||
const nummern = [neu.telefon ? `Telefon ${nummerAnzeige(neu.telefon)}` : '', neu.whatsapp?.nummer ? `WhatsApp ${nummerAnzeige(neu.whatsapp.nummer)}` : ''].filter(Boolean)
|
||
nachSpeichern(t, `Kontakt-Leiste gespeichert.${nummern.length ? ` Gespeichert als ${nummern.join(', ')}.` : ''}`, 'form[aria-label="Kontakt-Leiste"] button[type="submit"]', () => t.zeichne(['kontakt']))
|
||
})
|
||
|
||
return abschnitt('Kontakt-Leiste (Handy)',
|
||
'Auf dem Handy erscheint unten auf deiner Website eine Leiste mit Knöpfen zum Anrufen, für die Route und für WhatsApp. Auf größeren Bildschirmen erscheint sie nicht.',
|
||
form)
|
||
}
|