Files
Webklar-Kundenbereich/docs/funktionen/zeiten-einbau.md
T
ClaudeandClaude Opus 5.5 ea4457e6a4 Öffnungszeiten: Fixrunde Doku (Datenschutz-Entwurf, Einbau-Anleitung, Kanzlei-Fragen, Livegang-Notiz)
- 6d-Entwurf § 7: Stripe-Sitzung nur bei Bestellung, IP-Adresse und Browserkennung bei jeder Zustimmung
  (auch Gratisweg); Offener Punkt 4 beantwortet
- Einbau-Anleitung: CSP braucht auch style-src, Portal mit docker compose up -d neu erstellen, Satz zu
  Einzelseiten-Apps, data-api nur am Script-Tag
- Kanzlei-Fragen: ZQ11 (Team-Aktivierung ohne Zustimmung), ZQ12 (Pflichtinformationen als Haken und in K15);
  ZQ3 und Schlussnotiz an den Stand von Wertersatz-Satz und Widerruf-Seite angepasst
- Livegang-Notiz: Zusammenführung über origin/main mit erwarteten Konflikten, genaue Push-Refs und
  Webhook-Warnung, Sicherungen und Prüfungen vor der Compose-Änderung, Schema-Check nach dem Anlegen,
  Runner-Neustart prüfen, setup-legal als ungetestet, Live-Checks und Rückweg, Handrezept für Testdaten,
  Test-Anpassung bei bald:false, Checkliste nachgeführt

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 09:16:42 +00:00

16 KiB
Raw Blame History

Öffnungszeiten live auf der Kundenwebsite einbauen

Anleitung für das WEBklar-Team. „Öffnungszeiten live“ zeigt die Zeiten, die der Kunde im Portal einträgt (Tab „Funktionen“, Kachel „Öffnungszeiten live“), auf seiner Website: als Tabelle, als Live-Status und als Kurzanzeige, dazu eine Hinweis-Leiste, auf dem Handy eine Kontakt-Leiste und Google-Angaben (JSON-LD) im Seitencode. Den siteKey (16 Zeichen, a–z und 2–7) findest Du im WOMS: Kunde → Reiter „Funktionen“, Zeile „Öffnungszeiten live“ (mit Kopierknopf). Im Einrichtungs-Ticket „Öffnungszeiten live einbauen – “ stehen Einbau-Zeile, Ergebnis des automatischen Einbaus (einbau), Beispiele der Platzhalter mit den Plan-Kurznamen und die Prüfliste. Dieses Dokument ist die Referenz dahinter. Die Datenschutz-Absätze für die Kundenseite stehen in docs/funktionen/website-datenschutz-absatz.md.

Die Spec ist docs/superpowers/specs/2026-10-04-oeffnungszeiten-live-design.md (Abschnitte 4.2, 4.3, 4.5, 4.6).

1. Was automatisch geschieht und was Du tust

Teil Wer Wie
Einbau-Zeile (Skript) Runner, automatisch beim nächsten Veröffentlichen in jede HTML-Datei vor </head>, nur wenn die Funktion in setup oder active steht
Hinweis-Leiste, Kontakt-Leiste, JSON-LD Skript kommen mit den Daten, ohne Platzhalter
Platzhalter (Tabelle, Status, Badge) mit Ersatz-HTML Du an die Stellen setzen, an denen feste Zeiten stehen, und die festen Zeiten ersetzen
Datenschutz-Absatz auf der Website Du Abschnitt 8, Punkt 5
Einbau-Zeile bei einbau: manuell oder wenn die Seite woanders liegt Du Abschnitt 9

Der Einbau ist ein Einbau je Website und ist im Preis (30 € oder im Wartungs-Abo ohne Aufpreis) enthalten. Er wird nicht auf das Stundenkontingent angerechnet. Sonderwünsche (zusätzliche Stellen, Sondergestaltung) gehen über ein Angebot oder, mit Abo, über Stunden.

2. Die Einbau-Zeile

<script data-wk-zeiten src="https://project.webklar.com/embed/zeiten.js" data-site="SITEKEY" defer></script>
  • Ins <head> jeder HTML-Seite, einmal pro Seite. Der Runner setzt sie vor </head> ein (liegt kein </head> vor, nach <body>); eine schon vorhandene Zeile mit data-wk-zeiten ersetzt er, der Einbau ist also wiederholbar und doppelt nichts.
  • data-site ist der siteKey. Optional data-api="https://project.webklar.com" (nur am Script-Tag, nie an einem Platzhalter); ohne Angabe nimmt das Skript die Herkunft seiner eigenen Adresse (das klappt nur bei einem normalen <script src>; bei Tag-Manager oder nachgeladenem Skript ist data-api Pflicht). Den siteKey darf auch ein Platzhalter tragen (data-webklar-zeiten="SITEKEY").
  • Die Adresse des Skripts kommt aus der Runner-Variable ZEITEN_PUBLIC_BASE (Vorgabe https://project.webklar.com/embed/, muss mit https:// beginnen und mit / enden). Für die Produktion nichts ändern.
  • Das Skript hat keine Abhängigkeiten, setzt keine Cookies (keine Cookies, kein localStorage, kein sessionStorage), fragt nur project.webklar.com ab und lädt nichts von Dritten. Kundentexte setzt es nur als Text ein, nie als HTML. Es wird 5 Minuten gecacht und darf von jeder Domain geladen werden. Ein Abruf je Seite für alle Teile, danach höchstens alle 10 Minuten.
  • Hat die Seite eine Content-Security-Policy, muss project.webklar.com in script-src und connect-src stehen, und style-src muss Inline-Stile erlauben ('unsafe-inline'): das Skript legt ein <style>-Element an (Tabelle, Status, Leisten). Ohne diese Erlaubnis erscheinen sie ohne Gestaltung.

3. Platzhalter

Ein Platzhalter ist ein Element mit dem Attribut data-webklar-zeiten. Innen steht das Ersatz-HTML (Abschnitt 4).

<div data-webklar-zeiten data-modus="tabelle" data-plan="oeffnungszeiten">…Ersatz-HTML…</div>
<div data-webklar-zeiten data-modus="status" data-plan="oeffnungszeiten">Mo–Fr 9–18 Uhr</div>
<span data-webklar-zeiten data-modus="badge" data-plan="oeffnungszeiten">Mo–Fr 9–18 Uhr</span>
data-modus Zeigt Gedacht für
tabelle Wochentabelle (echtes <table> mit Caption und Zeilenköpfen, heutiger Tag mit aria-current="date"), darunter „Abweichungen in den nächsten 14 Tagen“ Kontaktseite, Footer
status Punkt und Text, zum Beispiel „Jetzt geöffnet · schließt um 18 Uhr“, ohne aria-live Kopfbereich, Hero
badge Kurzform „Geöffnet“, „Geschlossen“, „Nach Vereinbarung“ Menü, Header
  • data-plan ist der Kurzname des Zeitplans (Kleinbuchstaben, Ziffern und Bindestrich, 1–24 Zeichen, nach dem Anlegen unveränderlich). Der Hauptplan heißt oeffnungszeiten. Weitere Pläne (zum Beispiel kueche, lieferung) hat der Kunde im Editor angelegt; die Kurznamen stehen im Editor und im Einrichtungs-Ticket („Pläne (Kurznamen für data-plan)“). Ohne data-plan gilt der Hauptplan.
  • Einzelseiten-Apps (Vite/React): Das Skript beobachtet <body> und füllt Platzhalter, die erst nach einem Seitenwechsel auf die Seite kommen, nach etwa 0,2 Sekunden (auch einen wieder eingehängten Knoten). Setze Platzhalter nicht in Teile, die die App beim Rendern entfernt oder neu aufbaut: ein neu gebauter Knoten startet wieder mit seinem Ersatz-HTML, und was das Skript hineingeschrieben hat, kann die App überschreiben.
  • Ein unbekannter data-plan oder data-modus lässt das Ersatz-HTML stehen und schreibt eine Warnung in die Konsole. Es gibt kein stilles Umschalten auf den Hauptplan. Bei Fehlern, ohne JavaScript, bei Daten älter als 24 Stunden und nach dem Abschalten bleibt ebenfalls das Ersatz-HTML.
  • Das Skript füllt die Platzhalter ab setup, damit Du vor dem Schließen des Tickets prüfen kannst.
  • Gestaltung: Light DOM, alle Klassen beginnen mit wkz-, die Stile stehen in @layer wkz. Jede Regel der Kundenseite (ohne Layer) gewinnt also gegen die Standard-Stile. Schrift und Farben erbt die Seite. Variablen (alle optional, am Platzhalter oder auf :root): --wkz-offen, --wkz-zu, --wkz-unklar (Farben der Punkte), --wkz-offen-hell, --wkz-zu-hell, --wkz-unklar-hell, --wkz-offen-text, --wkz-zu-text, --wkz-unklar-text (Badge), --wkz-rand, --wkz-heute. Kontrast mindestens 4,5 : 1 prüfen.
  • Hat das Theme globale Regeln wie table{…} oder td{…}, greifen sie auch in der Tabelle. Prüfe das bei der Abnahme und überschreibe gezielt, zum Beispiel [data-webklar-zeiten] table{ … }.

4. Ersatz-HTML

Jeder Platzhalter behält eine statische Ersatz-Tabelle: Sie steht ohne JavaScript, bei einem Fehler und nach der Abschaltung. Ohne sie wäre die Stelle leer.

  1. WOMS → Funktionen → Ersatz-HTML kopieren (je Plan ein Knopf; bei mehreren Plänen mit Namen dahinter). Erst nach dem ersten Speichern des Kunden; vorher meldet das WOMS „Der Kunde hat noch keine Zeiten eingetragen“ (die Schnittstelle antwortet 409: GET /api/team/funktionen/:customerId/zeiten/ersatz?projectId=…&plan=<kurz>).
  2. In jeden Platzhalter einsetzen. Das Ersatz-HTML ist eine schlichte Wochentabelle als reines ASCII mit Entities (&auml;, &#8211;), also in jeder Dateikodierung sicher. Ein Fensterende um Mitternacht steht als 00:00 („18:00–00:00“), ein Fenster von 00:00 bis 24:00 als „rund um die Uhr“.
  3. Die Tabelle zeigt den Wochenplan zum Zeitpunkt des Einbaus. Ändert der Kunde seinen Wochenplan später, bleibt das Ersatz-HTML zunächst stehen (mit Skript unsichtbar).
  4. Beim Abschalten der Funktion, wenn der aktuelle Wochenplan des Hauptplans vom Stand beim Einbau abweicht, entsteht ein WOMS-Ticket „Ersatz-Tabelle aktualisieren – “ mit dem aktuellen Ersatz-HTML. Ersetze die Tabelle in allen Platzhaltern (gehört zum enthaltenen Einbau, Leistungsbeschreibung Nr. 4 Abs. 3). Die Mail K27 sagt dem Kunden, dass wieder feste Zeiten auf der Seite stehen. Liegt die Seite woanders, schicke dem Kunden den Text.

5. Header-Selektor (navSelektor)

Die Hinweis-Leiste steht als erstes Kind von <body> im Fluss und schiebt den Inhalt nach unten. Feste und klebende Elemente mit top: 0 rückt das Skript, solange die Leiste sichtbar ist, um max(0, Leistenhöhe − Scrollposition) nach unten (Inline-top, beim Entfernen der Leiste zurückgesetzt) und erhöht scroll-padding-top.

Überlappt die Leiste trotzdem den Kopf der Seite (zum Beispiel ein fester Header mit top: 12px, mit transform oder in einem Wrapper), setze im WOMS bei Kunde → Funktionen → „Öffnungszeiten live“ das Feld Header-Selektor (Team-Einstellung navSelektor):

  • ein CSS-Selektor des Headers, höchstens 120 Zeichen, erlaubt sind nur A–Z a–z 0–9, Leerzeichen und # . - _ > : [ ] = " ', nicht erlaubt <, {, } und ;. Beispiel: header.site-header oder #nav.
  • Das Skript wendet ihn mit querySelectorAll in einem try/catch an; ein falscher Selektor bricht nichts, er bewirkt nur nichts.
  • Leer lassen = Standard (nur top: 0). Speichern mit teamVersion (bei 409 „Inzwischen geändert – bitte neu laden“).
  • Prüfe nach dem Setzen: oben auf der Seite, beim Scrollen (die Leiste scrollt weg, der Header klebt wieder oben) und auf dem Handy.

6. Leisten ausschalten

Im selben Bereich (WOMS → Funktionen → „Öffnungszeiten live“) gibt es zwei Haken: kontaktleisteAus („Kontakt-Leiste aus“) und hinweisleisteAus („Hinweis-Leiste aus“). Sie gelten je Website.

  • Die Kontakt-Leiste erscheint nur bei (max-width: 768px), unten fest mit z-index: 40, Knöpfe „Anrufen“, „Route“ und, wenn der Kunde sie eingerichtet hat, „WhatsApp“. Hat die Seite selbst ein festes Element am unteren Rand mit einem a[href^="tel:"], bleibt sie automatisch aus. Setze den Haken, wenn die Seite eine eigene Anruf-Leiste hat, die das Skript nicht erkennt, oder der Kunde die Leiste nicht möchte.
  • WhatsApp und die Route (Google Maps) öffnen erst beim Klick. Den Haken „Ich habe den WhatsApp-Hinweis in meine Datenschutzerklärung aufgenommen“ setzt der Kunde im Portal; in der Kundenansicht setzt ihn kein Mitarbeiter.
  • Die Hinweis-Leiste zeigt zu jedem Zeitpunkt den gültigen Hinweis mit dem höchsten Rang (Wichtig, Urlaub, Aktion, Info). Sie hat keinen Schließen-Knopf (das Skript speichert nichts im Browser).

7. JSON-LD

Das Skript pflegt die Öffnungszeiten im Seitencode: Gibt es einen application/ld+json-Block mit einem Knoten vom Typ LocalBusiness oder einem Untertyp (auch in @graph), ersetzt es dort nur openingHoursSpecification und specialOpeningHoursSpecification (alles andere im Block bleibt); unlesbare Blöcke bleiben unberührt. Gibt es keinen, legt es einen eigenen Block [data-wkz-jsonld] mit name, address und url an. Prüfe auf der Seite, ob JSON-LD mit festen Öffnungszeiten an einer anderen Stelle steht (zum Beispiel als Mikrodaten im Footer); das würde dem Skript widersprechen.

8. Prüfliste

Das Einrichtungs-Ticket nennt dieselben fünf Punkte:

  1. Feste Zeiten im Repo suchen und ersetzen: grep -ril "öffnungszeit" . und Ähnliches (geöffnet, Mo-Fr, Uhr), auch Footer, Kontaktseite, <meta> und eigene Status-Skripte. Jede Stelle wird ein Platzhalter mit Ersatz-HTML oder fällt weg.
  2. Header: Überlappt die Hinweis-Leiste den Kopf der Seite? Sonst navSelektor setzen (Abschnitt 5).
  3. Anruf-Leiste: Hat die Seite schon eine eigene feste Anruf-Leiste? Dann kontaktleisteAus (Abschnitt 6), damit sie nicht doppelt erscheint.
  4. Vorhandenes JSON-LD (Öffnungszeiten der Seite) prüfen, damit nichts doppelt oder widersprüchlich steht (Abschnitt 7).
  5. Datenschutz-Absatz auf der Website: den Absatz „Öffnungszeiten auf dieser Website“ aus docs/funktionen/website-datenschutz-absatz.md einsetzen (Variante A bei WEBklar-Hosting, Variante B bei Fremdhosting). Die Bausteine „Route“ und „WhatsApp“ nur, wenn die Kontakt-Leiste die Knöpfe zeigt. Liegt die Datenschutzerklärung in der von WEBklar betreuten Website, setzt Du den Absatz ein (gehört zum Einbau, nicht auf das Stundenkontingent anrechnen); liegt sie woanders (externer Generator), schicke dem Kunden den Textbaustein, er fügt ihn selbst ein. Die Texte sind Entwurf, bis die Rechtsrunde sie freigibt.

Zur Abnahme außerdem: Seite in der Vorschau öffnen und mit ausgeschaltetem JavaScript ansehen (Ersatz-Tabelle muss stehen), Konsole auf Warnungen zu data-plan prüfen, auf einem Handy (höchstens 768 px Breite) die Kontakt-Leiste, den Regler „Uhrzeit testen“ im Editor gegen die Anzeige halten, Kontrast und Tippflächen (mindestens 44 px) ansehen.

9. Sperrliste ZEITEN_KEIN_NEUBAU und Seiten ohne automatischen Einbau

Neubau und Einsetzen. Bei jedem Übergang in setup oder active (Einschalten im Abo, Zahlung, Aktivierung durch das Team, Rückkehr des Abos, Behalten) stößt das Portal einen Neubau der Seite an (höchstens einer gleichzeitig). Das Ergebnis steht in einbau: angestossen (Auftrag angenommen, die Zeile erscheint nach dem Veröffentlichen) oder einbau: manuell. Änderungen an Zeiten und Leisten brauchen keinen Neubau.

einbau: manuell heißt: kein Repo, Vorschau aus, Dev-Modus, Projekt auf der Sperrliste, Fehler oder Runner ausgelastet. Dann setzt Du die Einbau-Zeile von Hand ins Repo.

Sperrliste. ZEITEN_KEIN_NEUBAU (Umgebungsvariable des Portals, Liste von Subdomains, durch Komma getrennt): Für diese Projekte gibt es weder einen angestoßenen Neubau noch das automatische Einsetzen bei normalen Deploys; die Funktion zählt sie immer als manuell. Vorgabe ist emmas-schatzkiste (Emmas Seiten, deren Dateien teilweise nicht in Git liegen). Achtung: Setzt Du die Variable, ersetzt sie die Vorgabe. Willst Du eine Seite zusätzlich sperren, nenne emmas-schatzkiste mit in der Liste. Änderung in compose und .env des Portals, danach das Portal mit docker compose -f /home/webklar/apps/docker-compose.yml up -d --no-deps kundenbereich-server neu erstellen (kein docker restart: es liest geänderte Werte nicht neu ein). Für gesperrte Seiten setzt Du die Zeile und die Platzhalter von Hand ein (im Repo, soweit die Dateien dort liegen, sonst direkt auf dem Server) und prüfst nach jedem Deploy, ob sie noch da sind.

Seite liegt woanders (anderer Hoster, keine WEBklar-Veröffentlichung): Das Ticket enthält die Einbau-Zeile; Du setzt sie ein, soweit WEBklar Zugriff auf die Seite hat. Hat WEBklar keinen Zugriff, schicke dem Kunden im Chat Einbau-Zeile, Platzhalter-Beispiele und Ersatz-HTML und bitte ihn, sie selbst oder durch seinen Anbieter einzufügen. Schließe das Ticket erst, wenn die Anzeige auf der Live-Seite stimmt: Öffne die Seite, sieh in den Netzwerk-Aufrufen nach der Antwort von /api/public/zeiten/v1/<siteKey> ("aktiv": true) und nach der gefüllten Tabelle.

10. Freischalten und Ende

  • Freischalten: Der Job setzt active (und sendet K16), wenn das Ticket geschlossen ist und der Kunde Zeiten eingetragen hat (Hauptplan gespeichert, mindestens ein Tag nicht „zu“). Sonst bleibt setup, das WOMS zeigt „wartet auf die Zeiten des Kunden“. Solange der Kunde nichts eingetragen hat, kannst Du die Tabelle nicht einbauen.
  • Ende des Abos (Gratisweg): K26 mit Frist zum Behalten (30 Tage), danach disabled und K27, Eingaben noch 30 Tage, dann gelöscht. Das Ticket „Ersatz-Tabelle aktualisieren“ gehört zum Abschalten (Abschnitt 4).
  • Abschalten durch das Team (Grund team): keine automatische Löschung.

11. Häufige Fehlerbilder

Symptom Meist liegt es an
Es erscheint nur das Ersatz-HTML Funktion nicht in setup/active, einbau: manuell und Zeile fehlt, unbekannter data-plan, kein JavaScript, Daten älter als 24 Stunden, CSP blockiert project.webklar.com
Hinweis-Leiste verdeckt den Header navSelektor nicht gesetzt (Abschnitt 5)
Zwei Anruf-Leisten auf dem Handy kontaktleisteAus setzen (Abschnitt 6)
Kontakt-Leiste fehlt auf dem Handy Seite hat schon eine feste Anruf-Leiste unten, oder Breite über 768 px, oder kontaktleisteAus
Öffnungszeiten doppelt im Suchergebnis oder Test festes JSON-LD an anderer Stelle (Abschnitt 7)
Tabelle sieht fremd aus globale Regeln des Themes (Abschnitt 3)