trihub-ernaehrung-advinters/js/terminbuchung-form.js

362 lines
13 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// 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.
// Die Bestätigung wird nur auf der Seite angezeigt; es wird keine E-Mail versendet.
// ----- 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 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 sendenButton = 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;
sendenButton.disabled = true;
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" });
});
sendenButton.addEventListener("click", function () {
if (!terminIstGueltig()) {
terminFehlerAnzeigen("Der Termin ist nicht mehr gültig. Bitte wähle einen neuen Termin.");
formularBereich.scrollIntoView({ block: "start" });
return;
}
bestaetigungDialog.showModal();
});
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() || "–"]
];
werteAnzeigen(anfrageDaten);
sendenButton.disabled = false;
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();
})();