Files
schachfreunde-badsteben/site/snippets/home-termine.php
T
tfeigel 15de43a2b5 Enhances event handling with timezone support
Adds timezone handling to the event parsing and formatting logic.

This allows events to be displayed in the user's local timezone, improving the user experience.

Also restructures the event display on the "Termine" page to include a sidebar for filtering by year and month.
2025-09-06 12:27:56 +02:00

284 lines
13 KiB
PHP

<?php
$events = collection('termine');
// Aktuelles Datum
$today = new DateTime()->format('Ymd');
// Nur zukünftige Termine anzeigen
$future_events = array_filter($events, function ($event) use ($today) {
return isset($event['DTSTART']) && $event['DTSTART'] >= $today;
});
// Termine nach DTSTART in aufsteigender Reihenfolge sortieren
usort($future_events, function ($a, $b) {
return $a['DTSTART'] <=> $b['DTSTART'];
});
// --- Deutsche Monatsnamen ---
$de_months = [
'01' => 'Jan',
'02' => 'Feb',
'03' => 'Mrz',
'04' => 'Apr',
'05' => 'Mai',
'06' => 'Jun',
'07' => 'Jul',
'08' => 'Aug',
'09' => 'Sep',
'10' => 'Okt',
'11' => 'Nov',
'12' => 'Dez',
];
?>
<div class="container mx-auto py-8">
<h2>Bevorstehende Termine</h2>
<div class="relative flex items-center justify-center">
<button id="scroll-left" class="flex-shrink-0 bg-white p-3 rounded-full shadow-lg hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 z-10 hidden md:block mr-4">
<svg class="w-6 h-6 text-gray-700" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path></svg>
</button>
<div id="termine-wrapper" class="relative w-full max-w-7xl overflow-hidden">
<div id="termine-container" class="flex space-x-6 pb-4 transition-transform duration-300 ease-in-out">
<?php foreach ($future_events as $event): ?>
<?php
$start = $event['DTSTART'] ?? '';
$end = $event['DTEND'] ?? '';
$summary = $event['SUMMARY'] ?? '';
$location = $event['LOCATION'] ?? '';
$desc = $event['DESCRIPTION'] ?? '';
$timezone = $event['DTSTART_TZID'] ?? null;
$date_info = format_ics_date_with_timezone($start, $timezone);
$date = $date_info['display'];
$time = $date_info['has_time'] ? substr($date, 11) : 'ganztägig';
$day = substr($date, 0, 2);
$month = substr($date, 3, 2);
$iso_date = $date_info['iso'];
?>
<div class="termine-card flex-none w-full md:w-1/2 bg-gradient-to-br from-blue-50 to-blue-100 rounded-2xl transform hover:scale-102 transition-all duration-300 ease-in-out border border-blue-200">
<!-- Inhalt der ersten Karte bleibt unverändert -->
<div class="p-6 flex flex-col h-full">
<div class="flex items-top mb-5">
<div class="relative flex-shrink-0 bg-white rounded-lg shadow-md overflow-hidden w-16 h-16 flex flex-col border border-blue-200">
<!-- Kopfzeile des Kalenders mit Monat -->
<div class="bg-blue-600 text-white text-xs font-semibold py-1 text-center uppercase">
<span data-month><?php echo $de_months[$month]; ?></span>
</div>
<!-- Datumsanzeige -->
<div class="flex-grow flex items-center justify-center">
<div class="text-3xl font-bold text-sf_grau-800" data-day><?php echo htmlspecialchars(
$day,
); ?></div>
</div>
<!-- Dekorative Elemente - kleine Punkte für Kalendertage -->
<div class="absolute bottom-1 left-0 right-0 flex justify-center space-x-0.5 px-1">
<div class="w-1 h-1 rounded-full bg-sf_blau-200"></div>
<div class="w-1 h-1 rounded-full bg-sf_blau-300"></div>
<div class="w-1 h-1 rounded-full bg-sf_blau-400"></div>
<div class="w-1 h-1 rounded-full bg-sf_blau-500"></div>
<div class="w-1 h-1 rounded-full bg-sf_blau-400"></div>
<div class="w-1 h-1 rounded-full bg-sf_blau-300"></div>
<div class="w-1 h-1 rounded-full bg-sf_blau-200"></div>
</div>
</div>
<div class="ml-5 flex-grow">
<pack class="text-xl font-medium text-sf_grau-900"><?= $event['SUMMARY'] ?></p>
<p class="text-sf_blau-700 text-lg font-medium mt-1">
<?php if ($date_info['has_time']): ?>
ab <span class="local-time" data-iso-date="<?php echo htmlspecialchars(
$iso_date,
); ?>"><?php echo htmlspecialchars($time); ?></span> Uhr
<?php else: ?>
ganztägig
<?php endif; ?>
</p>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<button id="scroll-right" class="flex-shrink-0 bg-white p-3 rounded-full shadow-lg hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 z-10 hidden md:block ml-4">
<svg class="w-6 h-6 text-gray-700" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>
</button>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const termineWrapper = document.getElementById('termine-wrapper');
const termineContainer = document.getElementById('termine-container');
const scrollLeftBtn = document.getElementById('scroll-left');
const scrollRightBtn = document.getElementById('scroll-right');
const cards = document.querySelectorAll('.termine-card');
let currentPosition = 0;
let totalCards = cards.length;
let cardsToShow = window.innerWidth < 768 ? 1 : 3; // Nur 1 Karte auf mobilen Geräten
let maxPosition = Math.max(0, totalCards - cardsToShow);
// Funktion zum Aktualisieren des Zustands der Pfeile
function updateArrowState() {
// Linker Pfeil deaktivieren, wenn es keine vorherigen Termine gibt
if (currentPosition <= 0) {
scrollLeftBtn.disabled = true;
scrollLeftBtn.classList.add('opacity-50', 'cursor-not-allowed');
scrollLeftBtn.classList.remove('hover:bg-gray-100');
} else {
scrollLeftBtn.disabled = false;
scrollLeftBtn.classList.remove('opacity-50', 'cursor-not-allowed');
scrollLeftBtn.classList.add('hover:bg-gray-100');
}
// Rechter Pfeil deaktivieren, wenn es keine weiteren Termine gibt
if (currentPosition >= maxPosition) {
scrollRightBtn.disabled = true;
scrollRightBtn.classList.add('opacity-50', 'cursor-not-allowed');
scrollRightBtn.classList.remove('hover:bg-gray-100');
} else {
scrollRightBtn.disabled = false;
scrollRightBtn.classList.remove('opacity-50', 'cursor-not-allowed');
scrollRightBtn.classList.add('hover:bg-gray-100');
}
}
// Initialisierung - Pfeile immer anzeigen, aber entsprechend deaktivieren
// Entferne alle hidden-Klassen, damit Buttons immer sichtbar sind
scrollLeftBtn.classList.remove('hidden');
scrollRightBtn.classList.remove('hidden');
// Funktion zum Scrollen nach links
scrollLeftBtn.addEventListener('click', () => {
if (currentPosition > 0 && !scrollLeftBtn.disabled) {
currentPosition--;
scrollToPosition();
}
});
// Funktion zum Scrollen nach rechts
scrollRightBtn.addEventListener('click', () => {
if (currentPosition < maxPosition && !scrollRightBtn.disabled) {
currentPosition++;
scrollToPosition();
}
});
// Funktion zum Scrollen zur aktuellen Position
function scrollToPosition() {
const cardWidth = cards[0].offsetWidth;
const gapWidth = 24; // entspricht space-x-6 (1.5rem = 24px)
const scrollAmount = currentPosition * (cardWidth + gapWidth);
termineContainer.style.transform = `translateX(-${scrollAmount}px)`;
updateArrowState();
}
// Initialer Aufruf
updateArrowState();
// Zeitzonenkonvertierung für lokale Zeiten
function convertTimesToLocal() {
const timeElements = document.querySelectorAll('.local-time');
timeElements.forEach(element => {
const isoDate = element.getAttribute('data-iso-date');
if (isoDate) {
try {
// Erstelle ein Date-Objekt aus der ISO-Date
// JavaScript erkennt automatisch UTC-Zeiten (mit Z oder +00:00)
const date = new Date(isoDate);
// Prüfe ob das Datum gültig ist
if (isNaN(date.getTime())) {
console.warn('Ungültiges Datum:', isoDate);
return;
}
// Formatiere die Zeit in der lokalen Zeitzone des Browsers
const localTime = date.toLocaleTimeString('de-DE', {
hour: '2-digit',
minute: '2-digit',
hour12: false
});
// Aktualisiere den Text
element.textContent = localTime;
// Debug-Information (kann später entfernt werden)
console.log('Konvertiert:', isoDate, '->', localTime, 'in Zeitzone:', Intl.DateTimeFormat().resolvedOptions().timeZone);
} catch (error) {
console.warn('Fehler bei der Zeitzonenkonvertierung:', error, 'für Datum:', isoDate);
}
}
});
}
// Zeitzonenkonvertierung beim Laden der Seite
convertTimesToLocal();
// Responsives Verhalten - bei Fenstergröße-Änderung
window.addEventListener('resize', () => {
// Anzahl der Karten je nach Bildschirmgröße anpassen
cardsToShow = window.innerWidth < 768 ? 1 : 1;
maxPosition = Math.max(0, totalCards - cardsToShow);
// Sicherstellen, dass die aktuelle Position gültig ist
currentPosition = Math.min(currentPosition, maxPosition);
// Position aktualisieren
scrollToPosition();
// Pfeile-Zustand aktualisieren
updateArrowState();
});
});
</script>
<style>
/* Versteckt die Scrollbar vollständig, behält aber die Scrollfunktionalität */
#termine-container {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
overflow-x: visible;
}
#termine-container::-webkit-scrollbar {
display: none; /* Chrome, Safari, Opera */
}
/* Gleiche Kartenbreiten sicherstellen */
@media (min-width: 1280px) {
.termine-card {
width: 100%; /* 100% minus 2 Gaps (2 * 24px) geteilt durch 3 */
}
#termine-container {
space-x-0; /* Kein Abstand zwischen Karten nötig */
}
}
/* Auf mobilen Geräten eine Karte anzeigen */
@media (max-width: 1280px) {
.termine-card {
width: 100%; /* Volle Breite */
}
#termine-container {
space-x-0; /* Kein Abstand zwischen Karten nötig */
}
}
/* Stellen Sie sicher, dass Texte abgeschnitten werden, wenn sie zu lang sind */
.termine-card h3 {
overflow: hidden;
text-overflow: ellipsis;
/* white-space: nowrap; */ /* Diese Zeile entfernen oder kommentieren */
display: -webkit-box;
-webkit-line-clamp: 2; /* Maximal 2 Zeilen anzeigen */
-webkit-box-orient: vertical;
line-height: 1.3;
}
</style>