// Online-Terminbuchung – Kalender-Pop-up auf der Startseite (index.html). // Daten: json/berater.json (Trainer mit ID) und json/terminbuchung.json // (Verfügbarkeit pro Trainer-ID: Datum JJJJ-MM-TT → Array von Uhrzeiten HH:MM). (function () { "use strict"; const BERATER_URL = "json/berater.json"; const TERMINE_URL = "json/terminbuchung.json"; const ZIELSEITE = "html/terminbuchung.html"; const MONATE = [ "Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember" ]; const WOCHE = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"]; let beraterDaten = []; // [{id, name, ...}] let terminDaten = {}; // { "1": { "2026-10-05": ["09:00", ...] }, ... } let geladen = null; // Promise, die auf das Laden beider JSON-Dateien wartet let overlay = null; // DOM: Pop-up (wird einmalig erzeugt) let istGeoeffnet = false; let fokusVorher = null; // Element, das vor dem Öffnen Fokus hatte const zustand = { beraterId: null, jahr: 0, monat: 0, // 0-basiert datum: null, // "JJJJ-MM-TT" zeit: null // "HH:MM" }; // ----- Daten laden ----- function ladeDaten() { if (geladen) return geladen; geladen = Promise.all([ fetch(BERATER_URL).then((r) => (r.ok ? r.json() : Promise.reject(new Error("berater.json: HTTP " + r.status)))), fetch(TERMINE_URL).then((r) => (r.ok ? r.json() : Promise.reject(new Error("terminbuchung.json: HTTP " + r.status)))) ]) .then(([berater, termine]) => { beraterDaten = Array.isArray(berater.berater) ? berater.berater : []; terminDaten = termine && typeof termine === "object" ? termine : {}; }) .catch((error) => { console.error("Terminbuchung: Daten konnten nicht geladen werden.", error); }); return geladen; } // ----- Hilfsfunktionen ----- function pad(n) { return String(n).padStart(2, "0"); } function datumSchluessel(jahr, monat, tag) { return `${jahr}-${pad(monat + 1)}-${pad(tag)}`; } function heutigerTag() { const jetzt = new Date(); return { jahr: jetzt.getFullYear(), monat: jetzt.getMonth(), tag: jetzt.getDate() }; } function istVergangen(jahr, monat, tag) { const heute = heutigerTag(); return ( jahr < heute.jahr || (jahr === heute.jahr && monat < heute.monat) || (jahr === heute.jahr && monat === heute.monat && tag < heute.tag) ); } function zeitenFuer(beraterId, schluessel) { const tabelle = terminDaten[String(beraterId)]; if (!tabelle || typeof tabelle !== "object") return []; const zeiten = tabelle[schluessel]; return Array.isArray(zeiten) ? zeiten : []; } function istVerfuegbar(jahr, monat, tag) { if (istVergangen(jahr, monat, tag)) return false; return zeitenFuer(zustand.beraterId, datumSchluessel(jahr, monat, tag)).length > 0; } function deutscherTagText(schluessel) { const [jahr, monat, tag] = schluessel.split("-").map(Number); const datum = new Date(jahr, monat - 1, tag); return ( WOCHE[(datum.getDay() + 6) % 7] + ", " + pad(tag) + "." + pad(monat) + "." + jahr ); } function beraterName(beraterId) { const treffer = beraterDaten.find((b) => Number(b.id) === Number(beraterId)); return treffer ? String(treffer.name).trim() : "Berater/in"; } // ----- Pop-up aufbauen (einmalig) ----- function bauPopUp() { if (overlay) return overlay; const div = document.createElement("div"); div.innerHTML = ` `; overlay = div.firstElementChild; document.body.appendChild(overlay); // Trainer-Buttons füllen const trainerBox = overlay.querySelector(".tb-trainer"); beraterDaten.forEach((berater) => { const btn = document.createElement("button"); btn.type = "button"; btn.className = "tb-trainer-btn"; btn.textContent = String(berater.name).trim(); btn.dataset.beraterId = berater.id; btn.addEventListener("click", () => trainerWaehlen(Number(berater.id))); trainerBox.appendChild(btn); }); // Monatsnavigation overlay.querySelectorAll(".tb-cal-nav").forEach((btn) => { btn.addEventListener("click", () => { verschiebeMonat(Number(btn.dataset.richtung)); }); }); // Schließen: Knopf, Klick auf den Schleier, Escape overlay.querySelector(".tb-close").addEventListener("click", schliessen); overlay.addEventListener("click", (event) => { if (event.target === overlay) schliessen(); }); document.addEventListener("keydown", (event) => { if (istGeoeffnet && event.key === "Escape") schliessen(); }); // Buchen overlay.querySelector(".tb-book").addEventListener("click", buchen); return overlay; } // ----- Öffnen / Schließen ----- async function oeffnen(beraterId) { await ladeDaten(); // Ohne Berater-Daten gibt es nichts auszuwählen – Pop-up bleibt geschlossen. if (!beraterDaten.length) { console.error("Terminbuchung: Keine Berater geladen – Pop-up wird nicht geöffnet."); return; } bauPopUp(); const heute = heutigerTag(); zustand.jahr = heute.jahr; zustand.monat = heute.monat; trainerWaehlen(beraterId != null ? Number(beraterId) : beraterDaten[0].id); overlay.hidden = false; istGeoeffnet = true; fokusVorher = document.activeElement; document.body.style.overflow = "hidden"; overlay.querySelector(".tb-close").focus(); } function schliessen() { if (!istGeoeffnet) return; overlay.hidden = true; istGeoeffnet = false; document.body.style.overflow = ""; if (fokusVorher && typeof fokusVorher.focus === "function") { fokusVorher.focus(); } } // ----- Auswahl-Logik ----- function trainerWaehlen(beraterId) { zustand.beraterId = beraterId; zustand.datum = null; zustand.zeit = null; overlay.querySelectorAll(".tb-trainer-btn").forEach((btn) => { const aktiv = Number(btn.dataset.beraterId) === Number(beraterId); btn.classList.toggle("is-active", aktiv); btn.setAttribute("aria-pressed", aktiv ? "true" : "false"); }); zeitenBereichZuruecksetzen(); kalenderZeichnen(); buchButtonAktualisieren(); } function verschiebeMonat(richtung) { const heute = heutigerTag(); let ziel = zustand.jahr * 12 + zustand.monat + richtung; if (ziel < heute.jahr * 12 + heute.monat) return; // nicht vor dem aktuellen Monat zustand.jahr = Math.floor(ziel / 12); zustand.monat = ziel % 12; zustand.datum = null; zustand.zeit = null; zeitenBereichZuruecksetzen(); kalenderZeichnen(); buchButtonAktualisieren(); } function tagKlicken(jahr, monat, tag) { if (!istVerfuegbar(jahr, monat, tag)) return; zustand.datum = datumSchluessel(jahr, monat, tag); zustand.zeit = null; kalenderZeichnen(); zeitenZeichnen(); buchButtonAktualisieren(); } function zeitKlicken(zeit) { zustand.zeit = zeit; overlay.querySelectorAll(".tb-time-chip").forEach((chip) => { chip.classList.toggle("is-active", chip.dataset.zeit === zeit); }); buchButtonAktualisieren(); } function buchen() { if (!zustand.datum || !zustand.zeit || zustand.beraterId == null) return; const ziel = ZIELSEITE + "?berater=" + encodeURIComponent(zustand.beraterId) + "&datum=" + encodeURIComponent(zustand.datum) + "&zeit=" + encodeURIComponent(zustand.zeit); window.location.href = ziel; } // ----- Zeichnen ----- function kalenderZeichnen() { const grid = overlay.querySelector(".tb-cal-grid"); const monatsLabel = overlay.querySelector(".tb-cal-month"); const navVorher = overlay.querySelector('.tb-cal-nav[data-richtung="-1"]'); monatsLabel.textContent = `${MONATE[zustand.monat]} ${zustand.jahr}`; // Vor dem aktuellen Monat kann nicht zurückgeblättert werden. const heute = heutigerTag(); navVorher.disabled = zustand.jahr * 12 + zustand.monat <= heute.jahr * 12 + heute.monat; const ersterTag = new Date(zustand.jahr, zustand.monat, 1); const versatz = (ersterTag.getDay() + 6) % 7; // Montag als erste Spalte const tageImMonat = new Date(zustand.jahr, zustand.monat + 1, 0).getDate(); const heuteInfo = heutigerTag(); grid.innerHTML = ""; for (let i = 0; i < versatz; i++) { const leer = document.createElement("span"); leer.className = "tb-day tb-day--leer"; grid.appendChild(leer); } for (let tag = 1; tag <= tageImMonat; tag++) { const verfuegbar = istVerfuegbar(zustand.jahr, zustand.monat, tag); const schluessel = datumSchluessel(zustand.jahr, zustand.monat, tag); const btn = document.createElement("button"); btn.type = "button"; let klassen = "tb-day "; if (verfuegbar) { klassen += "tb-day--verfuegbar"; } else { klassen += "tb-day--nicht"; } if ( zustand.jahr === heuteInfo.jahr && zustand.monat === heuteInfo.monat && tag === heuteInfo.tag ) { klassen += " tb-day--heute"; } if (zustand.datum === schluessel) { klassen += " tb-day--ausgewaehlt"; } btn.className = klassen; btn.textContent = tag; btn.disabled = !verfuegbar; btn.setAttribute("aria-label", `${deutscherTagText(schluessel)} – ${verfuegbar ? "verfügbar" : "nicht verfügbar"}`); if (verfuegbar) { btn.addEventListener("click", () => tagKlicken(zustand.jahr, zustand.monat, tag)); } grid.appendChild(btn); } } function zeitenBereichZuruecksetzen() { const bereich = overlay.querySelector(".tb-times"); bereich.hidden = true; overlay.querySelector(".tb-time-chips").innerHTML = ""; overlay.querySelector(".tb-times-label").textContent = ""; } function zeitenZeichnen() { const bereich = overlay.querySelector(".tb-times"); const label = overlay.querySelector(".tb-times-label"); const chips = overlay.querySelector(".tb-time-chips"); const zeiten = zeitenFuer(zustand.beraterId, zustand.datum); if (!zeiten.length) { bereich.hidden = true; return; } label.innerHTML = `Wähle eine Uhrzeit für ${deutscherTagText(zustand.datum)} bei ${beraterName(zustand.beraterId)}:`; chips.innerHTML = ""; zeiten.forEach((zeit) => { const chip = document.createElement("button"); chip.type = "button"; chip.className = "tb-time-chip" + (zustand.zeit === zeit ? " is-active" : ""); chip.dataset.zeit = zeit; chip.textContent = zeit.replace(":", ".") + " Uhr"; chip.addEventListener("click", () => zeitKlicken(zeit)); chips.appendChild(chip); }); bereich.hidden = false; } function buchButtonAktualisieren() { const btn = overlay.querySelector(".tb-book"); btn.disabled = !(zustand.datum && zustand.zeit && zustand.beraterId != null); } // ----- Öffentliche Schnittstelle (wird von berater.js genutzt) ----- window.Terminbuchung = { oeffnen, schliessen, istGeoeffnet: () => istGeoeffnet }; })();