385 lines
14 KiB
JavaScript
385 lines
14 KiB
JavaScript
// Online-Terminbuchung – Buchungsseite (html/terminbuchung.html).
|
||
// Liest den gewählten Termin aus der URL (berater, datum, zeit), füllt die
|
||
// Zusammenfassung, verwaltet die Anamnese-Tags und prüft das Formular.
|
||
// Zum Absenden wird wie auf der Kontaktseite das E-Mail-Programm geöffnet.
|
||
|
||
// ----- Scroll-Reveal -----
|
||
(function () {
|
||
const elements = document.querySelectorAll(".terminbuchung-seite .reveal");
|
||
if (!elements.length) return;
|
||
|
||
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
||
|
||
if (reduceMotion || !("IntersectionObserver" in window)) {
|
||
elements.forEach(function (el) {
|
||
el.classList.add("visible");
|
||
});
|
||
return;
|
||
}
|
||
|
||
const observer = new IntersectionObserver(
|
||
function (entries) {
|
||
entries.forEach(function (entry) {
|
||
if (entry.isIntersecting) {
|
||
entry.target.classList.add("visible");
|
||
observer.unobserve(entry.target);
|
||
}
|
||
});
|
||
},
|
||
{ threshold: 0.12, rootMargin: "0px 0px -40px 0px" }
|
||
);
|
||
|
||
elements.forEach(function (el) {
|
||
observer.observe(el);
|
||
});
|
||
})();
|
||
|
||
// ----- Anamnese-Tags (Allergien & Unverträglichkeiten) -----
|
||
(function () {
|
||
const input = document.getElementById("tag-input");
|
||
const addButton = document.getElementById("tag-add");
|
||
const liste = document.getElementById("tag-list");
|
||
if (!input || !addButton || !liste) return;
|
||
|
||
const tags = [];
|
||
const MAX_TAGS = 20;
|
||
|
||
function tagHinzufuegen() {
|
||
let wert = input.value.trim().replace(/^#+/, "").trim();
|
||
if (!wert) return;
|
||
if (tags.length >= MAX_TAGS) {
|
||
input.value = "";
|
||
return;
|
||
}
|
||
// Doppelte Einträge (groß/kleinschreibungsgleich) werden nicht aufgenommen.
|
||
const existiert = tags.some(function (t) {
|
||
return t.toLowerCase() === wert.toLowerCase();
|
||
});
|
||
if (!existiert) {
|
||
tags.push(wert);
|
||
}
|
||
input.value = "";
|
||
zeichnen();
|
||
}
|
||
|
||
function tagEntfernen(index) {
|
||
tags.splice(index, 1);
|
||
zeichnen();
|
||
}
|
||
|
||
function zeichnen() {
|
||
liste.innerHTML = "";
|
||
tags.forEach(function (wert, index) {
|
||
const li = document.createElement("li");
|
||
li.className = "tb-tag";
|
||
|
||
const text = document.createElement("span");
|
||
text.textContent = wert;
|
||
|
||
const entfernen = document.createElement("button");
|
||
entfernen.type = "button";
|
||
entfernen.setAttribute("aria-label", "Tag entfernen: " + wert);
|
||
entfernen.innerHTML = "×";
|
||
entfernen.addEventListener("click", function () {
|
||
tagEntfernen(index);
|
||
});
|
||
|
||
li.appendChild(text);
|
||
li.appendChild(entfernen);
|
||
liste.appendChild(li);
|
||
});
|
||
}
|
||
|
||
addButton.addEventListener("click", tagHinzufuegen);
|
||
input.addEventListener("keydown", function (event) {
|
||
if (event.key === "Enter") {
|
||
event.preventDefault();
|
||
tagHinzufuegen();
|
||
}
|
||
});
|
||
|
||
// Für das E-Mail-Body: aktuelle Tag-Liste als Text.
|
||
window.TerminbuchungTags = {
|
||
holen: function () {
|
||
return tags.slice();
|
||
},
|
||
leeren: function () {
|
||
tags.length = 0;
|
||
zeichnen();
|
||
}
|
||
};
|
||
})();
|
||
|
||
// ----- Formularprüfung und Absenden -----
|
||
(function () {
|
||
const form = document.getElementById("terminbuchung-formular");
|
||
if (!form) return;
|
||
|
||
const EMAIL_TARGET = "info@trihub.de";
|
||
const bestaetigungDialog = document.getElementById("anfrage-bestaetigung");
|
||
|
||
const fields = {
|
||
art: form.querySelector("[name='art']"),
|
||
format: form.querySelector("[name='format']"),
|
||
gespraech: form.querySelector("[name='gespraech']"),
|
||
beschreibung: form.querySelector("[name='beschreibung']"),
|
||
name: form.querySelector("[name='name']"),
|
||
email: form.querySelector("[name='email']"),
|
||
phone: form.querySelector("[name='phone']")
|
||
};
|
||
|
||
const statusEl = document.getElementById("form-status");
|
||
const formularBereich = document.getElementById("formular");
|
||
const pruefBereich = document.getElementById("anfrage-pruefen");
|
||
const pruefDetails = document.getElementById("anfrage-pruefen-details");
|
||
const bearbeitenButton = document.getElementById("anfrage-bearbeiten");
|
||
const sendenLink = document.getElementById("anfrage-senden");
|
||
|
||
function wrap(el) {
|
||
return el.closest(".field");
|
||
}
|
||
|
||
function setFieldState(el, state) {
|
||
const box = wrap(el);
|
||
if (!box) return;
|
||
box.classList.remove("has-error", "is-ok");
|
||
el.classList.remove("invalid", "valid");
|
||
if (state === "error") {
|
||
box.classList.add("has-error");
|
||
el.classList.add("invalid");
|
||
} else if (state === "ok") {
|
||
box.classList.add("is-ok");
|
||
el.classList.add("valid");
|
||
}
|
||
}
|
||
|
||
function validateName() {
|
||
const ok = fields.name.value.trim().length > 0;
|
||
setFieldState(fields.name, ok ? null : "error");
|
||
return ok;
|
||
}
|
||
|
||
function validateEmail() {
|
||
const value = fields.email.value.trim();
|
||
const ok = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(value);
|
||
setFieldState(fields.email, ok ? null : "error");
|
||
return ok;
|
||
}
|
||
|
||
function validateSelect(key) {
|
||
const ok = fields[key].value !== "";
|
||
setFieldState(fields[key], ok ? null : "error");
|
||
return ok;
|
||
}
|
||
|
||
function allValid() {
|
||
return (
|
||
terminIstGueltig() &&
|
||
fields.art.value !== "" &&
|
||
fields.format.value !== "" &&
|
||
fields.gespraech.value !== "" &&
|
||
fields.name.value.trim().length > 0 &&
|
||
/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(fields.email.value.trim())
|
||
);
|
||
}
|
||
|
||
function updateSubmitState() {
|
||
const submit = form.querySelector("[type='submit']");
|
||
if (submit) {
|
||
submit.disabled = !allValid();
|
||
}
|
||
}
|
||
|
||
form.addEventListener("input", updateSubmitState);
|
||
form.addEventListener("change", updateSubmitState);
|
||
|
||
["name", "email"].forEach(function (key) {
|
||
fields[key].addEventListener("blur", function () {
|
||
if (fields[key].value.trim() !== "" || form.dataset.touched === "true") {
|
||
({ name: validateName, email: validateEmail })[key]();
|
||
}
|
||
});
|
||
});
|
||
|
||
["art", "format", "gespraech"].forEach(function (key) {
|
||
fields[key].addEventListener("change", function () {
|
||
validateSelect(key);
|
||
});
|
||
});
|
||
|
||
// Nur ein eingetragener, noch zukünftiger Termin darf angefragt werden.
|
||
const params = new URLSearchParams(window.location.search);
|
||
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();
|
||
}
|
||
|
||
function terminFehlerAnzeigen(text) {
|
||
termin = null;
|
||
const zusammenfassung = document.getElementById("termin-zusammenfassung");
|
||
zusammenfassung.classList.add("tb-summary--fehlend");
|
||
zusammenfassung.innerHTML = `
|
||
<div class="tb-summary__item">
|
||
<span class="tb-summary__label">Bitte einen gültigen Termin wählen</span>
|
||
<p class="tb-summary__value" style="font-size: 0.95rem; font-weight: 400;">
|
||
Wähle auf der <a href="../index.html#berater">Startseite</a> einen Berater,
|
||
einen verfügbaren Tag und eine Uhrzeit.
|
||
</p>
|
||
</div>
|
||
`;
|
||
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];
|
||
return select.options[select.selectedIndex].text;
|
||
}
|
||
|
||
function werteAnzeigen(werte) {
|
||
pruefDetails.replaceChildren();
|
||
werte.forEach(function (eintrag) {
|
||
const label = document.createElement("dt");
|
||
label.textContent = eintrag[0];
|
||
const wert = document.createElement("dd");
|
||
wert.textContent = eintrag[1] || "–";
|
||
pruefDetails.append(label, wert);
|
||
});
|
||
}
|
||
|
||
bearbeitenButton.addEventListener("click", function () {
|
||
pruefBereich.hidden = true;
|
||
formularBereich.hidden = false;
|
||
formularBereich.scrollIntoView({ behavior: "smooth", block: "start" });
|
||
});
|
||
|
||
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;
|
||
});
|
||
|
||
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");
|
||
const okGespraech = validateSelect("gespraech");
|
||
const okName = validateName();
|
||
const okEmail = validateEmail();
|
||
|
||
if (!(okArt && okFormat && okGespraech && okName && okEmail)) {
|
||
showStatus("error", "Bitte überprüfe die markierten Felder und versuche es erneut.");
|
||
return;
|
||
}
|
||
|
||
const tagListe = window.TerminbuchungTags ? window.TerminbuchungTags.holen() : [];
|
||
const beraterText = termin.berater;
|
||
const terminText = termin.text;
|
||
const anfrageDaten = [
|
||
["Berater/in", beraterText],
|
||
["Termin", terminText],
|
||
["Art der Ernährungsberatung", labelVon("art")],
|
||
["Beratungsformat", labelVon("format")],
|
||
["Gesprächsart", labelVon("gespraech")],
|
||
["Kurzbeschreibung", fields.beschreibung.value.trim() || "–"],
|
||
["Anamnese (Allergien & Unverträglichkeiten)", tagListe.length ? tagListe.join(", ") : "–"],
|
||
["Name", fields.name.value.trim()],
|
||
["E-Mail", fields.email.value.trim()],
|
||
["Telefon", fields.phone.value.trim() || "–"]
|
||
];
|
||
|
||
const body = [
|
||
"Terminanfrage – Trainingsplan & Beratung",
|
||
"",
|
||
"Termin: " + terminText,
|
||
"Berater/in: " + beraterText,
|
||
"Art der Ernährungsberatung: " + labelVon("art"),
|
||
"Beratungsformat: " + labelVon("format"),
|
||
"Gesprächsart: " + labelVon("gespraech"),
|
||
"",
|
||
"Kurzbeschreibung: " + (fields.beschreibung.value.trim() || "–"),
|
||
"Anamnese (Allergien & Unverträglichkeiten): " + (tagListe.length ? tagListe.join(", ") : "–"),
|
||
"",
|
||
"Name: " + fields.name.value.trim(),
|
||
"E-Mail: " + fields.email.value.trim(),
|
||
"Telefon: " + (fields.phone.value.trim() || "–")
|
||
].join("\n");
|
||
|
||
werteAnzeigen(anfrageDaten);
|
||
sendenLink.href =
|
||
"mailto:" + EMAIL_TARGET +
|
||
"?subject=" + encodeURIComponent("Terminanfrage: " + terminText) +
|
||
"&body=" + encodeURIComponent(body);
|
||
formularBereich.hidden = true;
|
||
pruefBereich.hidden = false;
|
||
pruefBereich.scrollIntoView({ behavior: "smooth", block: "start" });
|
||
});
|
||
|
||
function showStatus(kind, text) {
|
||
if (!statusEl) return;
|
||
statusEl.textContent = kind === "ok" ? "✓ " + text : "✗ " + text;
|
||
statusEl.className =
|
||
"form-status is-visible " + (kind === "ok" ? "form-status--ok" : "form-status--error");
|
||
}
|
||
|
||
updateSubmitState();
|
||
terminLaden();
|
||
})();
|