From ed49e09fc144ff505dcf73bf9546dc8b7ae5e67c Mon Sep 17 00:00:00 2001 From: koriboh Date: Wed, 30 Sep 2026 15:59:10 +0200 Subject: [PATCH] feat: Implement online appointment booking feature - Added new HTML page for appointment booking (terminbuchung.html) with form for user input and summary display. - Created JavaScript file (terminbuchung-form.js) to handle form validation, tag management for allergies, and email submission. - Developed JavaScript file (terminbuchung.js) for calendar pop-up functionality on the homepage, allowing users to select available dates and times. - Introduced JSON file (terminbuchung.json) to store availability data for trainers, linking dates to available time slots. --- css/kontakt.css | 1 + css/terminbuchung.css | 933 +++++++++++++++++++++++++++++++++++++++ html/terminbuchung.html | 210 +++++++++ index.html | 3 + js/berater.js | 23 +- js/terminbuchung-form.js | 350 +++++++++++++++ js/terminbuchung.js | 379 ++++++++++++++++ json/berater.json | 6 +- json/terminbuchung.json | 27 ++ 9 files changed, 1929 insertions(+), 3 deletions(-) create mode 100644 css/terminbuchung.css create mode 100644 html/terminbuchung.html create mode 100644 js/terminbuchung-form.js create mode 100644 js/terminbuchung.js create mode 100644 json/terminbuchung.json diff --git a/css/kontakt.css b/css/kontakt.css index 26112e6..af6e4d3 100644 --- a/css/kontakt.css +++ b/css/kontakt.css @@ -580,6 +580,7 @@ opacity: 0; transform: translateY(18px); transition: opacity 0.6s ease, transform 0.6s ease; + } .kontakt-seite .reveal.visible { diff --git a/css/terminbuchung.css b/css/terminbuchung.css new file mode 100644 index 0000000..44cdc9f --- /dev/null +++ b/css/terminbuchung.css @@ -0,0 +1,933 @@ +/* Online-Terminbuchung – zwei Bereiche: + 1. .tb-overlay & Co.: Kalender-Pop-up auf der Startseite (index.html). + 2. .terminbuchung-seite: Buchungsformular-Seite (html/terminbuchung.html), + Tokens und Formular-Styles entsprechen css/kontakt.css. */ + +/* ========================================================= + 1. Terminbuchungs-Pop-up (Kalender) auf der Startseite + ========================================================= */ + +.tb-overlay { + --background-panel: #152027; + --text-primary: #ffffff; + --text-muted: #9ba7aa; + --accent: #4597A3; + --border: rgba(234, 8, 8, 0.1); + --font-display: "Sora", sans-serif; + --font-body: "DM Sans", Arial, Helvetica, sans-serif; + --radius: 1.1rem; + + position: fixed; + inset: 0; + z-index: 1000; + display: flex; + align-items: center; + justify-content: center; + padding: 24px; + background: rgb(7 20 25 / 72%); + backdrop-filter: blur(6px); + -webkit-backdrop-filter: blur(6px); +} + +.tb-overlay[hidden] { + display: none; +} + +.tb-dialog { + position: relative; + width: min(560px, 100%); + max-height: calc(100vh - 48px); + overflow-y: auto; + background-color: var(--background-panel); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: calc(var(--radius) + 0.4rem); + box-shadow: 0 30px 90px rgb(0 0 0 / 45%); + padding: 28px; + color: var(--text-primary); + font-family: var(--font-body); + line-height: 1.6; +} + +.tb-close { + position: absolute; + top: 14px; + right: 14px; + display: grid; + place-items: center; + width: 36px; + height: 36px; + border: 1px solid var(--border); + border-radius: 10px; + background: rgb(255 255 255 / 4%); + color: var(--text-primary); + font-size: 1.1rem; + line-height: 1; + cursor: pointer; + transition: border-color 0.15s ease, background-color 0.15s ease; +} + +.tb-close:hover, +.tb-close:focus-visible { + border-color: var(--accent); + background: rgb(69 151 163 / 14%); +} + +.tb-kicker { + display: inline-flex; + align-items: center; + gap: 10px; + font-size: 0.72rem; + font-weight: 600; + letter-spacing: 0.18em; + text-transform: uppercase; + color: var(--accent); + margin-bottom: 10px; +} + +.tb-kicker::before { + content: ""; + width: 24px; + height: 2px; + border-radius: 2px; + background: var(--accent); +} + +.tb-title { + margin: 0 0 6px; + font-family: var(--font-display); + font-size: 1.5rem; + font-weight: 600; + letter-spacing: -0.02em; +} + +.tb-subtitle { + margin: 0 0 20px; + font-size: 0.9rem; + color: var(--text-muted); +} + +/* ---------- Trainer-Auswahl ---------- */ + +.tb-trainer-label { + display: block; + font-size: 0.85rem; + font-weight: 600; + margin-bottom: 10px; +} + +.tb-trainer { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-bottom: 22px; +} + +.tb-trainer-btn { + padding: 9px 16px; + border-radius: 999px; + border: 1px solid rgba(255, 255, 255, 0.14); + background: rgb(255 255 255 / 4%); + color: var(--text-primary); + font-family: var(--font-body); + font-size: 0.9rem; + font-weight: 600; + cursor: pointer; + transition: border-color 0.15s ease, background-color 0.15s ease; +} + +.tb-trainer-btn:hover { + border-color: var(--accent); +} + +.tb-trainer-btn.is-active { + border-color: var(--accent); + background: rgb(69 151 163 / 18%); +} + +/* ---------- Kalender ---------- */ + +.tb-cal-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + margin-bottom: 12px; +} + +.tb-cal-month { + font-family: var(--font-display); + font-size: 1rem; + font-weight: 600; +} + +.tb-cal-nav { + display: grid; + place-items: center; + width: 34px; + height: 34px; + border-radius: 9px; + border: 1px solid rgba(255, 255, 255, 0.14); + background: rgb(255 255 255 / 4%); + color: var(--text-primary); + font-size: 1.05rem; + cursor: pointer; + transition: border-color 0.15s ease, background-color 0.15s ease; +} + +.tb-cal-nav:hover:not(:disabled) { + border-color: var(--accent); + background: rgb(69 151 163 / 14%); +} + +.tb-cal-nav:disabled { + opacity: 0.35; + cursor: not-allowed; +} + +.tb-cal-weekdays, +.tb-cal-grid { + display: grid; + grid-template-columns: repeat(7, minmax(0, 1fr)); + gap: 6px; +} + +.tb-cal-weekdays span { + text-align: center; + font-size: 0.72rem; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--text-muted); + padding-bottom: 4px; +} + +.tb-day { + position: relative; + display: grid; + place-items: center; + min-height: 44px; + border-radius: 10px; + border: 1px solid transparent; + background: transparent; + font-family: var(--font-body); + font-size: 0.88rem; + color: var(--text-primary); + cursor: pointer; + transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease; +} + +/* Leere Zellen vor dem ersten bzw. nach dem letzten Tag des Monats */ +.tb-day--leer { + visibility: hidden; + pointer-events: none; +} + +/* Verfügbarkeitskategorien */ +.tb-day--verfuegbar { + background: rgb(34 197 94 / 12%); + border-color: rgb(34 197 94 / 40%); +} + +.tb-day--verfuegbar:hover, +.tb-day--verfuegbar:focus-visible { + background: rgb(34 197 94 / 22%); + border-color: rgb(34 197 94 / 70%); +} + +.tb-day--nicht { + color: rgb(255 255 255 / 26%); + background: rgb(255 255 255 / 3%); + cursor: default; +} + +/* Heute: dezent markiert, ohne die Kategorie zu verdecken */ +.tb-day--heute { + font-weight: 700; + box-shadow: inset 0 -2px 0 0 var(--accent); +} + +/* Ausgewählter Tag */ +.tb-day--ausgewaehlt { + background: var(--accent); + border-color: var(--accent); + color: #03242b; + font-weight: 700; +} + +/* ---------- Legende ---------- */ + +.tb-legend { + display: flex; + flex-wrap: wrap; + gap: 18px; + list-style: none; + margin: 14px 0 0; + padding: 0; + font-size: 0.82rem; + color: var(--text-muted); +} + +.tb-legend li { + display: inline-flex; + align-items: center; + gap: 7px; +} + +.tb-dot { + width: 10px; + height: 10px; + border-radius: 50%; +} + +.tb-dot--verfuegbar { + background: #22c55e; +} + +.tb-dot--nicht { + background: rgb(255 255 255 / 22%); +} + +/* ---------- Uhrzeiten des gewählten Tages ---------- */ + +.tb-times { + margin-top: 20px; + padding-top: 18px; + border-top: 1px solid var(--border); +} + +.tb-times-label { + margin: 0 0 12px; + font-size: 0.9rem; + color: var(--text-muted); +} + +.tb-times-label strong { + color: var(--text-primary); +} + +.tb-time-chips { + display: flex; + flex-wrap: wrap; + gap: 10px; +} + +.tb-time-chip { + padding: 8px 16px; + border-radius: 999px; + border: 1px solid rgba(255, 255, 255, 0.16); + background: rgb(255 255 255 / 4%); + color: var(--text-primary); + font-family: var(--font-body); + font-size: 0.9rem; + font-weight: 600; + cursor: pointer; + transition: border-color 0.15s ease, background-color 0.15s ease, color 0.15s ease; +} + +.tb-time-chip:hover { + border-color: var(--accent); +} + +.tb-time-chip.is-active { + background: var(--accent); + border-color: var(--accent); + color: #03242b; +} + +/* ---------- Buchen-Button ---------- */ + +.tb-book { + display: block; + width: 100%; + margin-top: 22px; + padding: 0.85rem 1.5rem; + border-radius: 999px; + border: none; + background: var(--accent); + color: #03242b; + font-family: var(--font-body); + font-size: 0.98rem; + font-weight: 600; + cursor: pointer; + transition: transform 0.18s ease, box-shadow 0.18s ease, opacity 0.18s ease; +} + +.tb-book:hover:not(:disabled) { + transform: translateY(-1px); + box-shadow: 0 10px 28px rgb(0 179 194 / 28%); +} + +.tb-book:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.tb-hinweis { + margin: 12px 0 0; + font-size: 0.78rem; + color: var(--text-muted); +} + +/* ========================================================= + 2. Buchungsformular-Seite (html/terminbuchung.html) + ========================================================= */ + +.terminbuchung-seite { + /* Farben – Tokens von tri-hub.de (wie css/kontakt.css) */ + --background: #0714198a; + --background-rgb: 7 20 25; + --background-panel: #152027; + --text-primary: #ffffff; + --text-muted: #9ba7aa; + --accent: #4597A3; + --accent-warm: #f7c230; + --border: rgba(234, 8, 8, 0.1); + + /* Typografie */ + --font-display: "Sora", sans-serif; + --font-body: "DM Sans", Arial, Helvetica, sans-serif; + + /* Layout */ + --container: 1120px; + --radius: 1.1rem; +} + +/* ---------- Basic ---------- */ + +.terminbuchung-seite, +.terminbuchung-seite *, +.terminbuchung-seite::before, +.terminbuchung-seite *::before, +.terminbuchung-seite::after, +.terminbuchung-seite *::after { + box-sizing: border-box; +} + +.terminbuchung-seite { + min-height: 100vh; + margin: 0; + background-color: var(--background); + color: var(--text-primary); + font-family: var(--font-body); + line-height: 1.625; + -webkit-font-smoothing: antialiased; + position: relative; + isolation: isolate; + scroll-behavior: smooth; +} + +/* Licht */ +.terminbuchung-seite::before { + content: ""; + position: absolute; + inset: 0; + z-index: -1; + background: + radial-gradient(900px 520px at 78% -10%, rgb(0 179 194 / 13%), transparent 65%), + radial-gradient(700px 480px at -10% 30%, rgb(35 137 148 / 10%), transparent 60%), + radial-gradient(600px 420px at 90% 95%, rgb(247 194 48 / 5%), transparent 60%); + pointer-events: none; +} + +.terminbuchung-seite h1, +.terminbuchung-seite h2, +.terminbuchung-seite h3 { + margin-top: 0; + font-family: var(--font-display); + color: var(--text-primary); + line-height: 1.2; + letter-spacing: -0.01em; +} + +.terminbuchung-seite p { + margin: 0 0 1em; +} + +.terminbuchung-seite .container { + width: min(100% - 48px, var(--container)); + margin-inline: auto; +} + +/* ---------- Typo-Helfer ---------- */ + +.terminbuchung-seite .kicker { + display: inline-flex; + align-items: center; + gap: 10px; + font-size: 0.78rem; + font-weight: 600; + letter-spacing: 0.18em; + text-transform: uppercase; + color: var(--accent); + margin-bottom: 18px; +} + +.terminbuchung-seite .kicker::before { + content: ""; + width: 28px; + height: 2px; + border-radius: 2px; + background: var(--accent); +} + +.terminbuchung-seite .lead { + font-size: 1.15rem; + color: var(--text-muted); + max-width: 660px; +} + +/* Die Navigationsleiste oben wird von css/navigation.css gestylt (.site-navigation). */ + +/* ---------- Kopfzeile / Hero ---------- */ + +.terminbuchung-seite .hero { + padding: 72px 0 56px; +} + +.terminbuchung-seite .hero h1 { + font-size: clamp(2.2rem, 5vw, 3.2rem); + font-weight: 500; + letter-spacing: -0.04em; + margin-bottom: 16px; +} + +.terminbuchung-seite .hero__text { + font-size: 1.1rem; + color: var(--text-muted); + max-width: 640px; +} + +/* ---------- Sektionen ---------- */ + +.terminbuchung-seite .section { + padding: 56px 0 88px; +} + +.terminbuchung-seite .section + .section { + border-top: 1px solid var(--border); +} + +.terminbuchung-seite .section h2 { + font-size: clamp(1.7rem, 3.5vw, 2.4rem); + margin-bottom: 16px; +} + +/* ---------- Termin-Zusammenfassung ---------- */ + +.tb-summary { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 20px; + margin-top: 32px; +} + +.tb-summary__item { + background-color: var(--background-panel); + border: 1px solid var(--border); + border-radius: calc(var(--radius) + 0.4rem); + padding: 22px; +} + +.tb-summary__label { + display: block; + font-size: 0.7rem; + letter-spacing: 0.2em; + text-transform: uppercase; + color: var(--text-muted); + margin-bottom: 8px; +} + +.tb-summary__value { + font-family: var(--font-display); + font-size: 1.05rem; + font-weight: 600; + color: var(--text-primary); + margin: 0; +} + +/* Hinweis, wenn die Seite ohne Termin-Parameter geöffnet wurde */ +.tb-summary--fehlend { + grid-template-columns: minmax(0, 1fr); +} + +.tb-summary--fehlend .tb-summary__item { + border-color: rgb(247 194 48 / 45%); + background: rgb(247 194 48 / 6%); +} + +.tb-summary--fehlend a { + color: var(--accent); +} + +/* ---------- Formular ---------- */ + +.terminbuchung-seite form { + margin-top: 32px; + display: grid; + gap: 20px; +} + +.terminbuchung-seite .field label { + display: block; + font-size: 0.9rem; + font-weight: 500; + color: var(--text-primary); + margin-bottom: 8px; +} + +.terminbuchung-seite .field input, +.terminbuchung-seite .field select, +.terminbuchung-seite .field textarea { + width: 100%; + padding: 12px 16px; + border-radius: 10px; + border: 1px solid var(--border); + background: rgb(255 255 255 / 4%); + color: var(--text-primary); + font-family: var(--font-body); + font-size: 0.95rem; + transition: border-color 0.15s ease, background-color 0.15s ease; +} + +.terminbuchung-seite .field input::placeholder, +.terminbuchung-seite .field textarea::placeholder { + color: rgb(255 255 255 / 40%); +} + +.terminbuchung-seite .field input:focus, +.terminbuchung-seite .field select:focus, +.terminbuchung-seite .field textarea:focus { + outline: none; + border-color: var(--accent); + background: rgb(255 255 255 / 6%); +} + +.terminbuchung-seite .field select option { + background: #0b1a20; + color: var(--text-primary); +} + +.terminbuchung-seite .field textarea { + min-height: 110px; + resize: vertical; +} + +/* terminbuchung-form.js setzt die Zustandsklassen für ungültige Felder und Rückmeldungen. */ +.terminbuchung-seite .field input.invalid, +.terminbuchung-seite .field select.invalid, +.terminbuchung-seite .field textarea.invalid { + border-color: #ef4444; +} + +.terminbuchung-seite .field input.valid, +.terminbuchung-seite .field textarea.valid { + border-color: #22c55e; +} + +.terminbuchung-seite .field__error { + display: none; + margin-top: 6px; + font-size: 0.8rem; + color: #f87171; +} + +.terminbuchung-seite .field__hint { + margin-top: 6px; + font-size: 0.8rem; + color: var(--text-muted); +} + +.terminbuchung-seite .field.has-error .field__error { + display: block; +} + +/* ---------- Anamnese-Tags (Allergien & Unverträglichkeiten) ---------- */ + +.tb-tag-row { + display: flex; + gap: 10px; +} + +.tb-tag-row input { + flex: 1; +} + +.tb-tag-add { + flex-shrink: 0; + padding: 0 18px; + border-radius: 10px; + border: 1px solid var(--accent); + background: rgb(69 151 163 / 14%); + color: var(--text-primary); + font-family: var(--font-body); + font-size: 0.9rem; + font-weight: 600; + cursor: pointer; + transition: background-color 0.15s ease, border-color 0.15s ease; +} + +.tb-tag-add:hover { + background: rgb(69 151 163 / 28%); +} + +.tb-tags { + display: flex; + flex-wrap: wrap; + gap: 8px; + list-style: none; + margin: 10px 0 0; + padding: 0; +} + +.tb-tags:empty { + display: none; +} + +.tb-tag { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 5px 12px; + border-radius: 999px; + background: rgb(69 151 163 / 16%); + border: 1px solid rgb(69 151 163 / 45%); + font-size: 0.85rem; + color: var(--text-primary); +} + +.tb-tag button { + display: grid; + place-items: center; + width: 18px; + height: 18px; + border: none; + border-radius: 50%; + background: rgb(255 255 255 / 12%); + color: var(--text-primary); + font-size: 0.7rem; + line-height: 1; + cursor: pointer; + transition: background-color 0.15s ease; +} + +.tb-tag button:hover { + background: rgb(239 68 68 / 60%); +} + +/* ---------- Button ---------- */ + +.terminbuchung-seite .btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + padding: 0.8rem 1.5rem; + border-radius: 999px; + font-family: var(--font-body); + font-weight: 600; + font-size: 0.95rem; + line-height: 1.2; + text-decoration: none; + border: 1px solid transparent; + cursor: pointer; + transition: transform 0.18s ease, box-shadow 0.18s ease, + background-color 0.18s ease, border-color 0.18s ease, color 0.18s ease; +} + +.terminbuchung-seite .btn-accent { + background: var(--accent); + color: #03242b; +} + +.terminbuchung-seite .btn-accent:hover:not(:disabled) { + transform: translateY(-1px); + box-shadow: 0 10px 28px rgb(0 179 194 / 28%); +} + +.terminbuchung-seite .btn-block { + width: 100%; +} + +.terminbuchung-seite .btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.terminbuchung-seite .form-status { + display: none; + font-size: 0.9rem; +} + +.terminbuchung-seite .form-status.is-visible { + display: block; +} + +.terminbuchung-seite .form-status--ok { + color: #4ade80; +} + +.terminbuchung-seite .form-status--error { + color: #f87171; +} + +/* ---------- Footer ---------- */ + +.terminbuchung-seite .logo { + font-family: var(--font-display); + font-size: 1.35rem; + letter-spacing: -0.02em; + color: var(--text-muted); + text-decoration: none; + white-space: nowrap; +} + +.terminbuchung-seite .logo span { + color: var(--accent); +} + +.terminbuchung-seite .site-footer { + margin-top: 0; + padding: 64px 0 40px; + border-top: 1px solid var(--border); + background: rgb(255 255 255 / 1.5%); +} + +.terminbuchung-seite .footer-grid { + display: grid; + grid-template-columns: 2fr 1fr 1fr; + gap: 48px; +} + +.terminbuchung-seite .footer-brand p { + color: var(--text-muted); + font-size: 0.95rem; + max-width: 340px; + margin-top: 14px; +} + +.terminbuchung-seite .footer-col h3 { + font-size: 0.85rem; + letter-spacing: 0.14em; + text-transform: uppercase; + color: var(--text-muted); + margin-bottom: 16px; +} + +.terminbuchung-seite .footer-col ul { + list-style: none; + margin: 0; + padding: 0; +} + +.terminbuchung-seite .footer-col a { + display: block; + padding: 5px 0; + color: var(--text-muted); + text-decoration: none; + font-size: 0.92rem; + transition: color 0.15s ease; +} + +.terminbuchung-seite .footer-col a:hover { + color: var(--text-primary); +} + +.terminbuchung-seite .footer-legal { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + gap: 12px; + margin-top: 48px; + padding-top: 24px; + border-top: 1px solid var(--border); + color: var(--text-muted); + font-size: 0.85rem; +} + +.terminbuchung-seite .footer-legal a { + color: inherit; + text-decoration: none; +} + +.terminbuchung-seite .footer-legal a:hover { + color: var(--text-primary); +} + +/* ---------- Scroll-Reveal ---------- */ + +.terminbuchung-seite .reveal { + opacity: 0; + transform: translateY(18px); + transition: opacity 0.6s ease, transform 0.6s ease; +} + +.terminbuchung-seite .reveal.visible { + opacity: 1; + transform: none; +} + +/* ---------- Responsiv ---------- */ + +@media (max-width: 980px) { + .terminbuchung-seite .tb-summary { + grid-template-columns: minmax(0, 1fr); + } + + .terminbuchung-seite .footer-grid { + grid-template-columns: 1fr 1fr; + } +} + +@media (max-width: 760px) { + .terminbuchung-seite .section { + padding: 48px 0 64px; + } + + .terminbuchung-seite .hero { + padding: 56px 0 48px; + } + + .terminbuchung-seite .footer-grid { + grid-template-columns: 1fr; + gap: 32px; + } +} + +/* Pop-up auf schmalen Bildschirmen */ +@media (max-width: 560px) { + .tb-overlay { + padding: 12px; + align-items: stretch; + } + + .tb-dialog { + max-height: calc(100vh - 24px); + padding: 22px 18px; + } + + .tb-day { + min-height: 38px; + font-size: 0.8rem; + } +} + +/* ---------- Reduzierte Bewegung ---------- */ + +@media (prefers-reduced-motion: reduce) { + .terminbuchung-seite { + scroll-behavior: auto; + } + + .terminbuchung-seite .reveal { + opacity: 1; + transform: none; + transition: none; + } + + .terminbuchung-seite, + .terminbuchung-seite *, + .tb-overlay, + .tb-overlay * { + transition-duration: 0.01ms !important; + } +} diff --git a/html/terminbuchung.html b/html/terminbuchung.html new file mode 100644 index 0000000..461fb77 --- /dev/null +++ b/html/terminbuchung.html @@ -0,0 +1,210 @@ + + + + + + + Terminbuchung | Ernährungsberatung für Triathleten | tri-hub + + + + + + + + + + + + + +
+ + + +
+ +
+
+
+ Online-Terminbuchung +

Dein Termin ist fast gebucht

+

+ Du hast dir einen Termin im Kalender ausgesucht. Bestätige ihn hier – und gib noch ein paar + Details zur Beratung sowie deine Kontaktdaten an. +

+ + +
+
+ Berater/in +

–

+
+
+ Datum +

–

+
+
+ Uhrzeit +

–

+
+
+
+
+
+ + +
+
+
+ Deine Angaben +

Noch ein paar Details zur Beratung

+

Damit dein Gegenüber den Termin gut vorbereiten kann: Wähle die Art der + Ernährungsberatung und das Format, beschreibe kurz dein Anliegen und nimm Allergien oder + Unverträglichkeiten als Tags auf.

+ + +
+
+ + +

Bitte wähle eine Art der Ernährungsberatung.

+
+ +
+ + +

Bitte wähle ein Beratungsformat.

+
+ +
+ + +

Bitte wähle aus, ob es ein Erst- oder Folgegespräch ist.

+
+ +
+ + +
+ +
+ +
+ + +
+
    +

    Enter oder der Button fügt den Eintrag als Tag hinzu – so behält dein Gegenüber die Anamnese im Blick.

    +
    + +
    + + +

    Bitte gib deinen Namen an.

    +
    + +
    + + +

    Bitte gib eine gültige E-Mail-Adresse an.

    +
    + +
    + + +
    + + +

    +
    +
    +
    +
    + + + +
    +
    + + + diff --git a/index.html b/index.html index e05e288..7820763 100644 --- a/index.html +++ b/index.html @@ -17,11 +17,14 @@ + + + diff --git a/js/berater.js b/js/berater.js index 7b14961..a783aaf 100644 --- a/js/berater.js +++ b/js/berater.js @@ -51,7 +51,13 @@ document.addEventListener('DOMContentLoaded', async () => { if (berater.links && berater.links.length > 0) { linksHtml = ``; } @@ -100,4 +106,19 @@ document.addEventListener('DOMContentLoaded', async () => { // Die fertige Karte zum Container auf der Seite hinzufügen container.appendChild(card); }); + + // "Trainingsplan buchen"-Buttons öffnen das Kalender-Pop-up (js/terminbuchung.js) + // mit der jeweiligen Berater-ID, statt eine externe Seite zu laden. + if (container) { + container.addEventListener('click', (event) => { + const button = event.target.closest('[data-terminbuchung]'); + if (!button) return; + if (window.Terminbuchung && typeof window.Terminbuchung.oeffnen === 'function') { + window.Terminbuchung.oeffnen(Number(button.dataset.terminbuchung)); + } else { + // Fallback, falls terminbuchung.js nicht geladen wurde: ursprüngliches Linkziel. + window.location.href = button.dataset.url || '#'; + } + }); + } }); \ No newline at end of file diff --git a/js/terminbuchung-form.js b/js/terminbuchung-form.js new file mode 100644 index 0000000..1a2eab9 --- /dev/null +++ b/js/terminbuchung-form.js @@ -0,0 +1,350 @@ +// 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); + }); +})(); + +// ----- 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"); + 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 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"); + + 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 ( + 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); + }); + }); + + // Termin-Angaben aus der URL für das E-Mail übernehmen. + 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 elBerater = document.getElementById("tb-berater"); + + form.addEventListener("submit", function (event) { + event.preventDefault(); + form.dataset.touched = "true"; + + 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; + } + + function labelVon(key) { + const select = fields[key]; + return select.options[select.selectedIndex].text; + } + + const tagListe = window.TerminbuchungTags ? window.TerminbuchungTags.holen() : []; + + const body = [ + "Terminanfrage – Trainingsplan & Beratung", + "", + "Termin: " + terminText, + "Berater/in: " + (elBerater ? elBerater.textContent.trim() : "–"), + "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"); + + // Statische Seite ohne Backend: E-Mail-Programm wird mit allen Angaben geöffnet. + window.location.href = + "mailto:" + EMAIL_TARGET + + "?subject=" + encodeURIComponent("Terminanfrage: " + terminText) + + "&body=" + encodeURIComponent(body); + + showStatus( + "ok", + "Danke! Dein E-Mail-Programm wurde geöffnet. Sende die Nachricht ab – wir bestätigen den Termin kurzfristig." + ); + form.reset(); + if (window.TerminbuchungTags) { + window.TerminbuchungTags.leeren(); + } + Object.values(fields).forEach(function (el) { + setFieldState(el, null); + }); + updateSubmitState(); + }); + + 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(); +})(); diff --git a/js/terminbuchung.js b/js/terminbuchung.js new file mode 100644 index 0000000..dd369cf --- /dev/null +++ b/js/terminbuchung.js @@ -0,0 +1,379 @@ +// 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 + }; +})(); diff --git a/json/berater.json b/json/berater.json index fff67f7..417f55d 100644 --- a/json/berater.json +++ b/json/berater.json @@ -22,7 +22,8 @@ }, { "text": "Trainingsplan buchen", - "url": "https://tri-hub.de/booking" + "url": "https://tri-hub.de/booking", + "aktion": "terminbuchung" } ] }, @@ -44,7 +45,8 @@ }, { "text": "Trainingsplan buchen", - "url": "https://tri-hub.de/booking" + "url": "https://tri-hub.de/booking", + "aktion": "terminbuchung" } ] } diff --git a/json/terminbuchung.json b/json/terminbuchung.json new file mode 100644 index 0000000..33eedb1 --- /dev/null +++ b/json/terminbuchung.json @@ -0,0 +1,27 @@ +{ + "_hinweis": "Verfügbare Termine pro Trainer. Schlüssel = Berater-ID aus json/berater.json (z. B. \"1\"). Wert = Objekt: Datum im Format JJJJ-MM-TT als Schlüssel, Array mit Uhrzeiten (HH:MM) als Wert. Tage ohne Eintrag werden im Kalender als 'Nicht verfügbar' angezeigt. Schüssel, die mit '_' beginnen, werden von terminbuchung.js ignoriert.", + "1": { + "2026-10-05": ["09:00", "11:00", "14:30"], + "2026-10-07": ["10:00", "15:00"], + "2026-10-08": ["09:30", "11:30", "16:00"], + "2026-10-12": ["10:00", "14:00"], + "2026-10-14": ["09:00", "10:30", "15:30"], + "2026-10-15": ["11:00", "16:00"], + "2026-10-19": ["09:30", "14:30"], + "2026-10-21": ["10:00", "11:30"], + "2026-10-22": ["15:00", "16:30"], + "2026-10-26": ["09:00", "10:30", "14:00"], + "2026-10-28": ["11:00", "15:00"], + "2026-10-29": ["09:30", "16:00"] + }, + "2": { + "2026-10-06": ["10:00", "14:00", "16:00"], + "2026-10-09": ["09:00", "11:00"], + "2026-10-13": ["10:30", "15:00"], + "2026-10-16": ["09:30", "14:30"], + "2026-10-20": ["10:00", "11:30", "16:00"], + "2026-10-23": ["09:00", "15:30"], + "2026-10-27": ["10:30", "14:00"], + "2026-10-30": ["11:00", "16:00"] + } +}