Neue opt-in-Prop groupsInDash in group-schedule zieht schlichte Gruppen (ohne intro/details) als volle Bodenreihe MIT ins Dashboard-Bento, ohne eigene Sektion und ohne "Unsere Gruppen"-Ueberschrift. Trampolin nutzt das, damit Hinweis, Trainingsort, Kontakt und die drei Gruppen-Karten ein zusammenhaengender Block sind statt einer losgeloesten Kartenreihe darunter. Wiederverwendung von .group-list--grid/.group-card, Karten-Markup DRY in renderSimpleCard. Wettkampfturnen/Kinderturnen bleiben unberuehrt. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HXDvgRDHQ9iGjs4KCXykEj
322 lines
16 KiB
PHP
322 lines
16 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* Turn-Disziplin im einheitlichen Bento-Look (wie die Mannschaftsseiten). Props:
|
|
* $location array{venue, street?, zip?, city?} (optional) — Trainingsort + „Route planen"
|
|
* $lead string (optional) — Ansprechperson der Disziplin (Akzent-Kachel)
|
|
* $contactEmail string (optional) — persönliche E-Mail der Ansprechperson für die
|
|
* Kontakt-Kachel. Überschreibt die Abteilungs-Mail (club.email_turnen), falls
|
|
* gesetzt — genutzt, wenn eine Disziplin einen eigenen direkten Draht angibt.
|
|
* $contactPhone string (optional) — Telefonnummer der Ansprechperson (Anzeige-Form). Ergibt
|
|
* einen zusätzlichen tel:-Link in der Kontakt-Kachel. tel:-Href wie in person-card.php.
|
|
* $notice string (optional) — hervorgehobener Hinweis (z. B. Wartelisten-Status)
|
|
* $noticeTitle string (optional) — Überschrift dazu
|
|
* $noticeTone 'info' (Default) | 'stop' — Tonlage, siehe components/notice.php
|
|
* $noticeIcon string (optional) — Icon-Name, siehe components/notice.php
|
|
* Gerendert wird über component('notice') — dieselbe Komponente, die
|
|
* contact-form.php über dem Formular benutzt, damit derselbe Hinweis an
|
|
* beiden Orten gleich aussieht und nur EINMAL in data/turnen.json steht.
|
|
* $groups array von Gruppen:
|
|
* { name, level?, contact?, intro?, details?:[{heading,text}], recommendation?,
|
|
* note?, trainers?:[…], sessions:[{ label?, day, time, note?, trainers?:[…] }] }
|
|
* contact = Ansprechperson NUR dieser Gruppe. Braucht es, sobald eine Disziplin
|
|
* mehrere Gruppen mit verschiedenen Ansprechpersonen hat (Wettkampfturnen:
|
|
* Flamingos → Theresa Diener, Leistungsturnen → Franziska Trotzke) — die
|
|
* Kontakt-Kachel oben kann nur EINE nennen und wäre für die andere falsch.
|
|
* Beschriftet als „Kontakt" wie die Kachel, nicht als „Ansprechpartner":
|
|
* das Label kennt das Geschlecht der Person nicht (Haus-Stil Paarformen).
|
|
* $footnote string (optional) — Erläuterung (z. B. zur Sternchen-Kennzeichnung)
|
|
* $groupsTitle string (optional, Default 'Unsere Gruppen')
|
|
* $groupsTitleHidden bool (optional) — H2 bleibt im DOM (Dokumentgliederung für
|
|
* Screenreader, Sektion behält ihren Namen), ist aber visuell versteckt.
|
|
* Dasselbe Muster wie $title_hidden in team-cards. NICHT die Überschrift
|
|
* weglassen: dann hätte die Sektion keinen zugänglichen Namen mehr.
|
|
* $layout 'tabs' (Default) | 'stacked' — greift nur bei beschreibenden Gruppen.
|
|
* 'stacked' zeigt alle Karten offen untereinander (wenige Gruppen),
|
|
* 'tabs' ordnet viele textreiche Gruppen (Kinderturnen).
|
|
* $groupsInDash bool (optional) — nur für SCHLICHTE Gruppen (ohne intro/details): zieht die
|
|
* Gruppen-Karten als volle Bodenreihe MIT INS Bento-Dashboard, ohne eigene Sektion
|
|
* und ohne „Unsere Gruppen"-Überschrift. Für Disziplinen mit wenigen kurzen Gruppen
|
|
* (Trampolin), damit das Dashboard ein zusammenhängender Block bleibt statt einer
|
|
* losgelösten Kartenreihe darunter. Bei beschreibenden Gruppen (Wettkampfturnen,
|
|
* Kinderturnen) wirkungslos — die brauchen Platz und ihre eigene Sektion.
|
|
* $anchor string (optional, Default 'training')
|
|
*
|
|
* Eine Gruppe → Trainingszeiten landen als Bento-Kachel im Dashboard.
|
|
* Mehrere → Dashboard zeigt nur Ort/Verantwortlich, darunter eine Gruppen-Sektion.
|
|
* Gruppen mit Beschreibung werden gestapelt, schlichte Gruppen als Raster.
|
|
*/
|
|
$anchor = $anchor ?? 'training';
|
|
$groupsTitle = $groupsTitle ?? 'Unsere Gruppen';
|
|
$groupsTitleHidden = $groupsTitleHidden ?? false;
|
|
$layout = ($layout ?? 'tabs') === 'stacked' ? 'stacked' : 'tabs';
|
|
$lead = $lead ?? '';
|
|
$contactEmail = $contactEmail ?? '';
|
|
$contactPhone = $contactPhone ?? '';
|
|
$notice = $notice ?? '';
|
|
$noticeTitle = $noticeTitle ?? '';
|
|
$noticeTone = $noticeTone ?? 'info';
|
|
$noticeIcon = $noticeIcon ?? 'info-circle';
|
|
$footnote = $footnote ?? '';
|
|
$groupsInDash = !empty($groupsInDash);
|
|
$groups = $groups ?? [];
|
|
$location = $location ?? [];
|
|
|
|
if ($groups === []) {
|
|
return;
|
|
}
|
|
|
|
$club = json_load('club');
|
|
$email = $contactEmail !== '' ? $contactEmail : ($club['email_turnen'] ?? ($club['email'] ?? ''));
|
|
$telHref = $contactPhone !== '' ? 'tel:' . preg_replace('/[^+\d]/', '', $contactPhone) : '';
|
|
|
|
$single = count($groups) === 1;
|
|
$rich = false;
|
|
foreach ($groups as $g) {
|
|
if (!empty($g['intro']) || !empty($g['details'])) {
|
|
$rich = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Schlichte Gruppen direkt ins Dashboard ziehen (nur wenn mehrere und keine „reich").
|
|
$groupsInline = $groupsInDash && !$single && !$rich;
|
|
|
|
$hasVenue = !empty($location['venue']);
|
|
$mapsQuery = $hasVenue ? rawurlencode(trim(
|
|
($location['venue'] ?? '') . ', '
|
|
. ($location['street'] ?? '') . ', '
|
|
. ($location['zip'] ?? '') . ' ' . ($location['city'] ?? ''),
|
|
', '
|
|
)) : '';
|
|
|
|
/**
|
|
* Eine Trainingszeit (Tag/Zeit + optional Label, Notiz, Trainerteam) ausgeben.
|
|
*
|
|
* Das Label heißt „Trainerteam", nicht „Übungsleitung": in der Liste stehen auch
|
|
* Personen, die KEINE Übungsleitung sind (Eva Wünsch aus der Abteilung, 31.07.2026).
|
|
* Wer eine ist, trägt ein Sternchen, und das erklärt die Fußnote der Sektion. Die alte
|
|
* Seite schrieb dafür „TRAINERTEAM (* = ÜBUNGSLEITER)". Nicht zurückbenennen.
|
|
*/
|
|
$renderSession = static function (array $s): void {
|
|
?>
|
|
<li class="schedule-row">
|
|
<?php if (!empty($s['label'])): ?>
|
|
<span class="schedule-row__label"><?= e($s['label']) ?></span>
|
|
<?php endif; ?>
|
|
<span class="schedule-row__when">
|
|
<span class="schedule-row__day"><?= e($s['day']) ?></span>
|
|
<span class="schedule-row__time"><?= e($s['time']) ?></span>
|
|
</span>
|
|
<?php if (!empty($s['note'])): ?>
|
|
<span class="schedule-row__note text-muted"><?= e($s['note']) ?></span>
|
|
<?php endif; ?>
|
|
<?php if (!empty($s['trainers'])): ?>
|
|
<span class="schedule-row__trainers"><span class="schedule-row__trainers-label">Trainerteam:</span> <?= e(implode(', ', $s['trainers'])) ?></span>
|
|
<?php endif; ?>
|
|
</li>
|
|
<?php
|
|
};
|
|
|
|
/**
|
|
* Inhalt einer beschreibenden Gruppen-Karte (ohne das <article> drumherum). Als Closure,
|
|
* weil es diese Karte in zwei Fassungen gibt: als Tab-Panel (mit role/aria) und gestapelt
|
|
* (ohne). Vorher stand das Markup nur in der Tab-Fassung; eine zweite Kopie daneben wäre
|
|
* die Stelle, an der beide auseinanderlaufen.
|
|
*/
|
|
$renderCardBody = static function (array $g) use ($renderSession): void {
|
|
?>
|
|
<div class="group-card__body">
|
|
<div class="group-card__header">
|
|
<h3 class="group-card__name"><?= e($g['name']) ?></h3>
|
|
<?php if (!empty($g['level'])): ?>
|
|
<p class="group-card__level"><?= e($g['level']) ?></p>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php if (!empty($g['intro'])): ?>
|
|
<p class="group-card__intro"><?= e($g['intro']) ?></p>
|
|
<?php endif; ?>
|
|
<?php foreach ($g['details'] ?? [] as $det): ?>
|
|
<?php if (!empty($det['heading'])): ?>
|
|
<h4 class="group-card__detail-heading"><?= e($det['heading']) ?></h4>
|
|
<?php endif; ?>
|
|
<?php if (!empty($det['text'])): ?>
|
|
<p class="group-card__detail-text"><?= e($det['text']) ?></p>
|
|
<?php endif; ?>
|
|
<?php endforeach; ?>
|
|
<?php if (!empty($g['recommendation'])): ?>
|
|
<p class="group-card__recommendation text-muted"><?= e($g['recommendation']) ?></p>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php if (!empty($g['sessions'])): ?>
|
|
<div class="group-card__schedule">
|
|
<p class="group-card__schedule-title"><?= icon('clock', 'group-card__schedule-icon') ?>Trainingszeiten</p>
|
|
<ul class="schedule-list">
|
|
<?php foreach ($g['sessions'] as $s): $renderSession($s); endforeach; ?>
|
|
</ul>
|
|
<?php if (!empty($g['contact'])): ?>
|
|
<p class="group-card__trainers"><span class="schedule-row__trainers-label">Kontakt:</span> <?= e($g['contact']) ?></p>
|
|
<?php endif; ?>
|
|
<?php if (!empty($g['trainers'])): ?>
|
|
<p class="group-card__trainers"><span class="schedule-row__trainers-label">Trainerteam:</span> <?= e(implode(', ', $g['trainers'])) ?></p>
|
|
<?php endif; ?>
|
|
<?php if (!empty($g['note'])): ?>
|
|
<p class="group-card__note text-muted"><?= e($g['note']) ?></p>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
<?php
|
|
};
|
|
|
|
/**
|
|
* Schlichte Gruppen-Karte (Name + Trainingszeiten). Als Closure, weil sie an zwei Stellen
|
|
* gebraucht wird: im Raster der eigenen Gruppen-Sektion und — bei $groupsInline — als Kachel
|
|
* direkt im Bento-Dashboard.
|
|
*/
|
|
$renderSimpleCard = static function (array $g) use ($renderSession): void {
|
|
?>
|
|
<article class="group-card">
|
|
<div class="group-card__body">
|
|
<div class="group-card__header">
|
|
<h3 class="group-card__name"><?= e($g['name']) ?></h3>
|
|
<?php if (!empty($g['level'])): ?>
|
|
<p class="group-card__level"><?= e($g['level']) ?></p>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
<?php if (!empty($g['sessions'])): ?>
|
|
<div class="group-card__schedule">
|
|
<ul class="schedule-list">
|
|
<?php foreach ($g['sessions'] as $s): $renderSession($s); endforeach; ?>
|
|
</ul>
|
|
</div>
|
|
<?php endif; ?>
|
|
</article>
|
|
<?php
|
|
};
|
|
|
|
$soleSessions = $single ? ($groups[0]['sessions'] ?? []) : [];
|
|
$soleNote = $single ? ($groups[0]['note'] ?? '') : '';
|
|
?>
|
|
<section class="section turnen-dash-section" id="<?= e($anchor) ?>">
|
|
<div class="container">
|
|
<div class="turnen-dash<?= $single ? ' turnen-dash--single' : '' ?><?= $notice !== '' ? ' turnen-dash--notice' : '' ?><?= $groupsInline ? ' turnen-dash--groups' : '' ?>">
|
|
<?php if ($notice !== ''): ?>
|
|
<?php component('notice', [
|
|
'title' => $noticeTitle,
|
|
'text' => $notice,
|
|
'tone' => $noticeTone,
|
|
'icon' => $noticeIcon,
|
|
'class' => 'turnen-dash__notice',
|
|
]); ?>
|
|
<?php endif; ?>
|
|
<?php if ($single): ?>
|
|
<article class="bento__tile turnen-dash__training">
|
|
<h2 class="bento__heading"><?= icon('clock', 'bento__hicon') ?>Trainingszeiten</h2>
|
|
<ul class="bento__sessions">
|
|
<?php foreach ($soleSessions as $s): ?>
|
|
<li>
|
|
<span class="bento__day"><?= e($s['day']) ?></span>
|
|
<span class="bento__time"><?= e($s['time']) ?></span>
|
|
</li>
|
|
<?php endforeach; ?>
|
|
</ul>
|
|
<?php if ($soleNote !== ''): ?>
|
|
<p class="bento__note text-muted"><?= e($soleNote) ?></p>
|
|
<?php endif; ?>
|
|
<span class="bento__deco" aria-hidden="true"><?= icon('clock') ?></span>
|
|
</article>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($lead !== '' || $email !== '' || $telHref !== ''): ?>
|
|
<article class="bento__tile bento__tile--accent turnen-dash__contact">
|
|
<span class="bento__stat-label">Kontakt</span>
|
|
<?php if ($lead !== ''): ?>
|
|
<span class="bento__stat-value"><?= e($lead) ?></span>
|
|
<?php endif; ?>
|
|
<?php if ($email !== ''): ?>
|
|
<a class="turnen-dash__mail" href="mailto:<?= e($email) ?>"><?= icon('envelope', 'turnen-dash__mail-icon') ?>E-Mail schreiben</a>
|
|
<?php endif; ?>
|
|
<?php if ($telHref !== ''): ?>
|
|
<a class="turnen-dash__mail" href="<?= e($telHref) ?>"><?= icon('telephone', 'turnen-dash__mail-icon') ?><?= e($contactPhone) ?></a>
|
|
<?php endif; ?>
|
|
</article>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($hasVenue): ?>
|
|
<article class="bento__tile turnen-dash__venue">
|
|
<div class="turnen-dash__venue-main">
|
|
<h2 class="bento__heading"><?= icon('geo-alt', 'bento__hicon') ?>Trainingsort</h2>
|
|
<p class="bento__address">
|
|
<strong><?= e($location['venue']) ?></strong>
|
|
<?php if (!empty($location['street'])): ?> · <?= e($location['street']) ?><?php endif; ?>
|
|
<?php if (!empty($location['zip']) || !empty($location['city'])): ?>, <?= e(trim(($location['zip'] ?? '') . ' ' . ($location['city'] ?? ''))) ?><?php endif; ?>
|
|
</p>
|
|
</div>
|
|
<a class="btn btn--outline turnen-dash__route" href="https://www.google.com/maps/search/?api=1&query=<?= e($mapsQuery) ?>" target="_blank" rel="noopener">Route planen<span class="visually-hidden"> (öffnet Google Maps in neuem Tab)</span></a>
|
|
<span class="bento__deco" aria-hidden="true"><?= icon('geo-alt') ?></span>
|
|
</article>
|
|
<?php endif; ?>
|
|
<?php if ($groupsInline): ?>
|
|
<div class="turnen-dash__groups group-list--grid">
|
|
<?php foreach ($groups as $g): $renderSimpleCard($g); endforeach; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php if ($groupsInline && $footnote !== ''): ?>
|
|
<p class="group-list__footnote text-muted"><?= e($footnote) ?></p>
|
|
<?php endif; ?>
|
|
</div>
|
|
</section>
|
|
|
|
<?php if (!$single && !$groupsInline): ?>
|
|
<section class="section group-list-section<?= $groupsTitleHidden ? ' group-list-section--tight' : '' ?>" id="gruppen">
|
|
<div class="container">
|
|
<h2 class="group-list__title<?= $groupsTitleHidden ? ' visually-hidden' : '' ?>"><?= e($groupsTitle) ?></h2>
|
|
<?php if ($rich && $layout === 'stacked'): ?>
|
|
<?php /* Karten direkt untereinander, alle sichtbar. Für Disziplinen mit wenigen Gruppen:
|
|
Tabs verstecken dort mehr Inhalt als sie ordnen, und die offene Kartenreihe hält
|
|
den Bento-Eindruck der Seite. Bei vielen textreichen Gruppen (Kinderturnen) sind
|
|
Tabs weiter richtig, sonst wird die Seite endlos. */ ?>
|
|
<div class="group-list group-list--stacked">
|
|
<?php foreach ($groups as $g): ?>
|
|
<article class="group-card">
|
|
<?php $renderCardBody($g); ?>
|
|
</article>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php elseif ($rich): ?>
|
|
<div class="group-tabs" data-group-tabs>
|
|
<div class="group-tabs__bar" role="tablist" aria-label="<?= e($groupsTitle) ?>">
|
|
<?php foreach ($groups as $i => $g): ?>
|
|
<button class="tab group-tabs__tab" role="tab" type="button"
|
|
id="gtab-<?= $i ?>"
|
|
aria-controls="gpanel-<?= $i ?>"
|
|
aria-selected="<?= $i === 0 ? 'true' : 'false' ?>"
|
|
><?= e($g['name']) ?></button>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php foreach ($groups as $i => $g): ?>
|
|
<article class="group-card group-tabs__panel" role="tabpanel"
|
|
id="gpanel-<?= $i ?>"
|
|
aria-labelledby="gtab-<?= $i ?>"
|
|
tabindex="0"
|
|
>
|
|
<?php $renderCardBody($g); ?>
|
|
</article>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php else: ?>
|
|
<div class="group-list group-list--grid">
|
|
<?php foreach ($groups as $g): $renderSimpleCard($g); endforeach; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
<?php if ($footnote !== ''): ?>
|
|
<p class="group-list__footnote text-muted"><?= e($footnote) ?></p>
|
|
<?php endif; ?>
|
|
</div>
|
|
</section>
|
|
<?php endif; ?>
|