/* 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 =>
  • ): * 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..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) }