From 1620bf4f891723ae891bade5c0ec6fdbd6bd6e4a Mon Sep 17 00:00:00 2001 From: 3015418 <3015418@stud.th-mannheim.de> Date: Sun, 4 Oct 2026 16:04:32 +0200 Subject: [PATCH] Fehler bei der Terminbuchung behoben --- css/leistungen.css | 1 - css/terminbuchung.css | 6 +- js/terminbuchung-form.js | 156 ++++++++++++++++++++------------------- js/terminbuchung.js | 57 ++++++++++++-- 4 files changed, 134 insertions(+), 86 deletions(-) diff --git a/css/leistungen.css b/css/leistungen.css index 96aba5f..638f941 100644 --- a/css/leistungen.css +++ b/css/leistungen.css @@ -86,7 +86,6 @@ /* Empfehlung kommt aus der JSON-Datei; die Auswahlklasse setzt leistungen.js beim Klick. */ .pakete .paket-karte--empfohlen { - border-color: 1px solid rgba(248, 250, 252, 0.08); box-shadow: 0 0 0 1px var(--accent-soft); } diff --git a/css/terminbuchung.css b/css/terminbuchung.css index 739f884..c575129 100644 --- a/css/terminbuchung.css +++ b/css/terminbuchung.css @@ -37,6 +37,10 @@ position: relative; box-sizing: border-box; width: min(560px, 100%); + /* Bei kleinen Fenstern bleibt der gesamte Dialog erreichbar. */ + max-height: calc(100dvh - 48px); + overflow-y: auto; + overscroll-behavior: contain; background-color: #111a1e; border: 2px solid rgb(255 255 255 / 9%); border-radius: 16px; @@ -1145,4 +1149,4 @@ .tb-overlay * { transition-duration: 0.01ms !important; } -} \ No newline at end of file +} diff --git a/js/terminbuchung-form.js b/js/terminbuchung-form.js index 71499c3..858d4e9 100644 --- a/js/terminbuchung-form.js +++ b/js/terminbuchung-form.js @@ -34,66 +34,6 @@ }); })(); -// ----- Termin-Zusammenfassung aus der URL ----- -(function () { - const params = new URLSearchParams(window.location.search); - const beraterId = params.get("berater"); - const datum = params.get("datum"); - const zeit = params.get("zeit"); - - const elBerater = document.getElementById("tb-berater"); - const elDatum = document.getElementById("tb-datum"); - const elZeit = document.getElementById("tb-zeit"); - const zusammenfassung = document.getElementById("termin-zusammenfassung"); - if (!zusammenfassung) return; - - // Ohne Termin-Parameter (z. B. direkter Seitenaufruf): Hinweis statt Zusammenfassung. - if (!beraterId || !datum || !zeit) { - zusammenfassung.classList.add("tb-summary--fehlend"); - zusammenfassung.innerHTML = ` -
- Kein Termin ausgewählt -

- Du hast noch keinen Termin im Kalender gewählt. - Gehe bitte zurück zur Startseite und öffne dort über einen - Berater die Terminbuchung. -

-
- `; - return; - } - - // Datum lesbar formatieren, z. B. "Montag, 05.10.2026" - const [jahr, monat, tag] = datum.split("-").map(Number); - if (!jahr || !monat || !tag) return; - const datumObjekt = new Date(jahr, monat - 1, tag); - const formatiert = new Intl.DateTimeFormat("de-DE", { - weekday: "long", - day: "2-digit", - month: "2-digit", - year: "numeric" - }).format(datumObjekt); - - elDatum.textContent = formatiert; - elZeit.textContent = zeit.replace(":", ".") + " Uhr"; - - // Berater-Namen aus json/berater.json auflösen (ID stammt aus dem Kalender). - fetch("../json/berater.json") - .then(function (response) { - if (!response.ok) throw new Error("HTTP " + response.status); - return response.json(); - }) - .then(function (daten) { - const treffer = (daten.berater || []).find(function (b) { - return String(b.id) === String(beraterId); - }); - elBerater.textContent = treffer ? String(treffer.name).trim() : "Unbekannter Berater"; - }) - .catch(function () { - elBerater.textContent = "Unbekannter Berater"; - }); -})(); - // ----- Anamnese-Tags (Allergien & Unverträglichkeiten) ----- (function () { const input = document.getElementById("tag-input"); @@ -234,6 +174,7 @@ function allValid() { return ( + terminIstGueltig() && fields.art.value !== "" && fields.format.value !== "" && fields.gespraech.value !== "" && @@ -266,25 +207,75 @@ }); }); - // Termin-Angaben aus der URL für das E-Mail übernehmen. + // Nur ein eingetragener, noch zukünftiger Termin darf angefragt werden. const params = new URLSearchParams(window.location.search); - const datumRoh = params.get("datum") || ""; - const zeitRoh = params.get("zeit") || ""; - let terminText = "–"; - if (datumRoh && zeitRoh) { - const [jahr, monat, tag] = datumRoh.split("-").map(Number); - if (jahr && monat && tag) { - const formatiert = new Intl.DateTimeFormat("de-DE", { - weekday: "long", - day: "2-digit", - month: "2-digit", - year: "numeric" - }).format(new Date(jahr, monat - 1, tag)); - terminText = formatiert + " um " + zeitRoh.replace(":", ".") + " Uhr"; - } + const beraterId = params.get("berater"); + const datum = params.get("datum"); + const zeit = params.get("zeit"); + let termin = null; + + function terminIstGueltig() { + return termin !== null && termin.zeitpunkt > Date.now(); } - const elBerater = document.getElementById("tb-berater"); + function terminFehlerAnzeigen(text) { + termin = null; + const zusammenfassung = document.getElementById("termin-zusammenfassung"); + zusammenfassung.classList.add("tb-summary--fehlend"); + zusammenfassung.innerHTML = ` +
+ Bitte einen gültigen Termin wählen +

+ Wähle auf der Startseite einen Berater, + einen verfügbaren Tag und eine Uhrzeit. +

+
+ `; + pruefBereich.hidden = true; + formularBereich.hidden = false; + sendenLink.removeAttribute("href"); + showStatus("error", text); + updateSubmitState(); + } + + async function terminLaden() { + if (!beraterId || !datum || !zeit) { + terminFehlerAnzeigen("Bitte wähle zuerst einen Termin im Kalender."); + return; + } + try { + const [beraterDaten, terminDaten] = await Promise.all( + ["../json/berater.json", "../json/terminbuchung.json"].map(async function (url) { + const response = await fetch(url); + if (!response.ok) throw new Error("HTTP " + response.status); + return response.json(); + }) + ); + const berater = beraterDaten.berater.find(function (eintrag) { + return String(eintrag.id) === beraterId; + }); + const zeiten = terminDaten[beraterId]?.[datum]; + const zeitpunkt = new Date(`${datum}T${zeit}:00`).getTime(); + if (!berater || !Array.isArray(zeiten) || !zeiten.includes(zeit) || !(zeitpunkt > Date.now())) { + terminFehlerAnzeigen("Dieser Termin ist nicht verfügbar oder bereits vergangen. Bitte wähle einen neuen Termin."); + return; + } + const datumText = new Intl.DateTimeFormat("de-DE", { + weekday: "long", day: "2-digit", month: "2-digit", year: "numeric" + }).format(new Date(zeitpunkt)); + termin = { + zeitpunkt, + berater: String(berater.name).trim(), + text: datumText + " um " + zeit.replace(":", ".") + " Uhr" + }; + document.getElementById("tb-berater").textContent = termin.berater; + document.getElementById("tb-datum").textContent = datumText; + document.getElementById("tb-zeit").textContent = zeit.replace(":", ".") + " Uhr"; + updateSubmitState(); + } catch (error) { + terminFehlerAnzeigen("Der Termin konnte nicht geprüft werden. Bitte lade die Seite erneut."); + } + } function labelVon(key) { const select = fields[key]; @@ -310,6 +301,11 @@ sendenLink.addEventListener("click", function (event) { event.preventDefault(); + if (!terminIstGueltig()) { + terminFehlerAnzeigen("Der Termin ist nicht mehr gültig. Bitte wähle einen neuen Termin."); + formularBereich.scrollIntoView({ block: "start" }); + return; + } bestaetigungDialog.showModal(); window.location.href = sendenLink.href; }); @@ -317,6 +313,10 @@ form.addEventListener("submit", function (event) { event.preventDefault(); form.dataset.touched = "true"; + if (!terminIstGueltig()) { + terminFehlerAnzeigen("Bitte wähle zuerst einen verfügbaren, zukünftigen Termin."); + return; + } const okArt = validateSelect("art"); const okFormat = validateSelect("format"); @@ -330,7 +330,8 @@ } const tagListe = window.TerminbuchungTags ? window.TerminbuchungTags.holen() : []; - const beraterText = elBerater ? elBerater.textContent.trim() : "–"; + const beraterText = termin.berater; + const terminText = termin.text; const anfrageDaten = [ ["Berater/in", beraterText], ["Termin", terminText], @@ -379,4 +380,5 @@ } updateSubmitState(); + terminLaden(); })(); diff --git a/js/terminbuchung.js b/js/terminbuchung.js index 5f35e54..69c022a 100644 --- a/js/terminbuchung.js +++ b/js/terminbuchung.js @@ -77,18 +77,17 @@ const tabelle = terminDaten[String(beraterId)]; if (!tabelle || typeof tabelle !== "object") return []; const zeiten = tabelle[schluessel]; - return Array.isArray(zeiten) ? zeiten : []; + // Auch bereits vergangene Uhrzeiten am heutigen Tag ausblenden. + return Array.isArray(zeiten) + ? zeiten.filter((zeit) => new Date(`${schluessel}T${zeit}:00`).getTime() > Date.now()) + : []; } function hatFreieTermine(beraterId) { const tabelle = terminDaten[String(beraterId)]; if (!tabelle || typeof tabelle !== "object") return false; - return Object.entries(tabelle).some(([schluessel, zeiten]) => { - const datum = /^(\d{4})-(\d{2})-(\d{2})$/.exec(schluessel); - if (!datum || !Array.isArray(zeiten) || zeiten.length === 0) return false; - return !istVergangen(Number(datum[1]), Number(datum[2]) - 1, Number(datum[3])); - }); + return Object.keys(tabelle).some((schluessel) => zeitenFuer(beraterId, schluessel).length > 0); } function istVerfuegbar(jahr, monat, tag) { @@ -181,7 +180,32 @@ if (event.target === overlay) schliessen(); }); document.addEventListener("keydown", (event) => { - if (istGeoeffnet && event.key === "Escape") schliessen(); + if (!istGeoeffnet) return; + if (event.key === "Escape") { + event.preventDefault(); + schliessen(); + return; + } + if (event.key !== "Tab") return; + + // Tab und Shift+Tab bleiben zwischen den sichtbaren Dialog-Buttons. + const buttons = [...overlay.querySelectorAll("button:not(:disabled)")] + .filter((button) => button.getClientRects().length > 0); + const erster = buttons[0]; + const letzter = buttons[buttons.length - 1]; + const fokus = document.activeElement; + if (!overlay.contains(fokus) || (event.shiftKey && fokus === erster)) { + event.preventDefault(); + (event.shiftKey ? letzter : erster).focus(); + } else if (!event.shiftKey && fokus === letzter) { + event.preventDefault(); + erster.focus(); + } + }); + document.addEventListener("focusin", (event) => { + if (istGeoeffnet && !overlay.contains(event.target)) { + overlay.querySelector(".tb-close").focus(); + } }); // Buchen @@ -273,9 +297,14 @@ kalenderZeichnen(); zeitenZeichnen(); buchButtonAktualisieren(); + overlay.querySelector(".tb-day--ausgewaehlt")?.focus(); } function zeitKlicken(zeit) { + if (!zeitenFuer(zustand.beraterId, zustand.datum).includes(zeit)) { + auswahlAktualisieren(); + return; + } zustand.zeit = zeit; overlay.querySelectorAll(".tb-time-chip").forEach((chip) => { chip.classList.toggle("is-active", chip.dataset.zeit === zeit); @@ -285,6 +314,11 @@ function buchen() { if (!zustand.datum || !zustand.zeit || zustand.beraterId == null) return; + // Die Seite kann seit der Auswahl offen geblieben sein. + if (!zeitenFuer(zustand.beraterId, zustand.datum).includes(zustand.zeit)) { + auswahlAktualisieren(); + return; + } const ziel = ZIELSEITE + "?berater=" + encodeURIComponent(zustand.beraterId) + @@ -293,6 +327,15 @@ window.location.href = ziel; } + function auswahlAktualisieren() { + zustand.zeit = null; + kalenderZeichnen(); + zeitenZeichnen(); + legendeAktualisieren(); + buchButtonAktualisieren(); + overlay.querySelector(".tb-close").focus(); + } + // ----- Zeichnen ----- function kalenderZeichnen() {