/* global React */ // Termine block — renders the public-facing course cards. // Data flows from the Admin page via localStorage (`konzentriert.termine`) // with a fallback to window.TERMINE_DEFAULTS shipped in Onepager.html. // // Two card variants: // "a" — list-style (DateColumn with weekdays) // "b" — stamp-style (big day-month stamp + compact date list) const STORAGE_KEY = "konzentriert.termine"; function HeartIcon({ size = 18, color = "#FF616E" }) { return ( ); } // ─── Data normalisation ───────────────────────────────────────────────── // Accepts both the legacy { t1, t2, t3 } shape and the new { list: [...] } // shape so existing TERMINE_DEFAULTS keep working. function normalize(source) { if (!source || typeof source !== "object") source = {}; const list = Array.isArray(source.list) ? source.list : ["t1", "t2", "t3"].map((k) => source[k]).filter(Boolean); return { variant: source.variant === "b" ? "b" : "a", email: source.email || "", list: list.map((x, i) => ({ id: x.id || `k${i}`, title: x.title || "", dates: Array.isArray(x.dates) ? x.dates.filter(Boolean) : [], time: x.time || "", location: x.location || "", ageGroup: x.ageGroup || "", seats: x.seats || "", active: x.active !== false })) }; } function loadData() { try { const raw = localStorage.getItem(STORAGE_KEY); if (raw) return normalize(JSON.parse(raw)); } catch (e) {/* fall through */} return normalize(window.TERMINE_DEFAULTS || {}); } // ─── Date helpers ─────────────────────────────────────────────────────── const parseDates = (raw) => Array.isArray(raw) ? raw.filter(Boolean) : []; const parse = (iso) => { const d = new Date(iso + "T00:00:00"); return isNaN(d) ? null : d; }; const weekdayShort = (d) => d.toLocaleDateString("de-DE", { weekday: "short" }).replace(".", ""); const monthLong = (d) => d.toLocaleDateString("de-DE", { month: "long" }); const monthYear = (d) => d.toLocaleDateString("de-DE", { month: "long", year: "numeric" }); const compactDM = (d) => `${d.getDate()}.${d.getMonth() + 1}.`; function groupByMonth(isoDates) { const dates = isoDates.map(parse).filter(Boolean).sort((a, b) => a - b); const groups = []; for (const d of dates) { const key = `${d.getFullYear()}-${d.getMonth()}`; let g = groups[groups.length - 1]; if (!g || g.key !== key) { g = { key, monthLabel: monthYear(d), items: [] }; groups.push(g); } g.items.push({ date: d, weekday: weekdayShort(d), day: String(d.getDate()).padStart(2, "0"), dayMonth: compactDM(d) }); } return groups; } function summariseDates(isoDates) { const dates = isoDates.map(parse).filter(Boolean).sort((a, b) => a - b); if (!dates.length) return ""; const consecutive = dates.every((d, i) => { if (i === 0) return true; const diff = (d - dates[i - 1]) / 86400000; return diff === 1; }); if (consecutive && dates.length >= 4) { const s = dates[0],e = dates[dates.length - 1]; const sameMonth = s.getMonth() === e.getMonth(); return sameMonth ? `${s.getDate()}. – ${e.getDate()}. ${monthLong(e)} ${e.getFullYear()}` : `${compactDM(s)} – ${e.getDate()}. ${monthLong(e)} ${e.getFullYear()}`; } return dates.map((d) => `${weekdayShort(d)} ${compactDM(d)}`).join(" · "); } function renderTitle(title) { const parts = title.split("\\n"); if (parts.length === 1) return title; return parts.map((p, i) => i === 0 ? p : [React.createElement("br", { key: i }), p]); } function mailtoLink(email, termin) { const summary = summariseDates(parseDates(termin.dates)); const plainTitle = termin.title.replace(/\\n/g, " "); const subject = encodeURIComponent(`Anfrage: ${plainTitle} (${summary})`); const body = encodeURIComponent( `Liebe Julia, liebe Damaris,\n\n` + `ich interessiere mich für den Kurs:\n` + `· ${plainTitle}\n` + `· ${summary}\n` + `· ${termin.time || ""}\n\n` + `Mein Kind ist ___ Jahre alt.\n\n` + `Bitte melden Sie sich bei mir.\n\nViele Grüße,\n` ); return `mailto:${email}?subject=${subject}&body=${body}`; } // ─── Date column for variant A ────────────────────────────────────────── function DateColumn({ dates }) { const groups = groupByMonth(dates); if (!groups.length) return null; return (
{groups.map((g, gi) =>
{g.monthLabel}
)}
); } function TerminCardA({ termin, email }) { const dates = parseDates(termin.dates); return (

{renderTitle(termin.title)}

  • {summariseDates(dates)}
  • {termin.time &&
  • {termin.time}
  • } {termin.location &&
  • {termin.location}
  • } {termin.ageGroup &&
  • {termin.ageGroup}
  • }
Jetzt anfragen → {termin.seats && {termin.seats}}
); } function TerminCardB({ termin, email }) { const dates = parseDates(termin.dates); return (

{renderTitle(termin.title)}

{termin.time && <>{termin.time}
} {termin.location}{termin.location && termin.ageGroup ? " · " : ""}{termin.ageGroup}

Jetzt anfragen → {termin.seats && {termin.seats}}
); } function TermineSection() { const [data, setData] = React.useState(loadData); // Live-sync from admin tab via storage events. (Same-tab updates also // re-fire by listening to a custom 'konzentriert:termine' event.) React.useEffect(() => { const onStorage = (e) => { if (e.key === STORAGE_KEY) setData(loadData()); }; const onLocal = () => setData(loadData()); window.addEventListener("storage", onStorage); window.addEventListener("konzentriert:termine", onLocal); return () => { window.removeEventListener("storage", onStorage); window.removeEventListener("konzentriert:termine", onLocal); }; }, []); const Card = data.variant === "b" ? TerminCardB : TerminCardA; const GridClass = data.variant === "b" ? "t-grid-b" : "t-grid-a"; const visible = data.list.filter((t) => t.active !== false); return (
Nächste Kurse

Feste Termine
Begrenzte Plätze

Unsere Kurse finden in Kleingruppen statt. Wir freuen uns auf Ihre Anfrage.

{visible.length > 0 ?
{visible.map((termin) => )}
:

Aktuell sind keine offenen Termine eingetragen. Bitte schauen Sie bald wieder vorbei — oder schreiben Sie uns direkt an {data.email}.

}
Tipp: Bitte fragen Sie früh an, denn die Plätze sind begrenzt.
); } window.TermineSection = TermineSection;