15de43a2b5
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.
251 lines
11 KiB
PHP
251 lines
11 KiB
PHP
<?php
|
|
|
|
$events = collection('termine');
|
|
|
|
// Nur Events mit DTSTART berücksichtigen
|
|
$events = array_filter($events, function ($event) {
|
|
return isset($event['DTSTART']) && !empty($event['DTSTART']);
|
|
});
|
|
// Nach Datum sortieren
|
|
usort($events, function ($a, $b) {
|
|
return strcmp($a['DTSTART'], $b['DTSTART']);
|
|
});
|
|
|
|
// 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;
|
|
});
|
|
|
|
// --- Deutsche Monatsnamen ---
|
|
$de_months = [
|
|
'01' => 'Januar',
|
|
'02' => 'Februar',
|
|
'03' => 'März',
|
|
'04' => 'April',
|
|
'05' => 'Mai',
|
|
'06' => 'Juni',
|
|
'07' => 'Juli',
|
|
'08' => 'August',
|
|
'09' => 'September',
|
|
'10' => 'Oktober',
|
|
'11' => 'November',
|
|
'12' => 'Dezember',
|
|
];
|
|
|
|
// --- Gruppierung aller Termine nach Jahr und Monat für die Sidebar ---
|
|
function group_events_by_year_month($events)
|
|
{
|
|
$grouped = [];
|
|
foreach ($events as $event) {
|
|
if (!isset($event['DTSTART'])) {
|
|
continue;
|
|
}
|
|
$date = $event['DTSTART'];
|
|
$year = substr($date, 0, 4);
|
|
$month = substr($date, 4, 2);
|
|
$grouped[$year][$month][] = $event;
|
|
}
|
|
krsort($grouped); // Jahre absteigend
|
|
foreach ($grouped as &$months) {
|
|
krsort($months); // Monate absteigend
|
|
}
|
|
|
|
return $grouped;
|
|
}
|
|
|
|
$all_events_grouped = group_events_by_year_month($events);
|
|
|
|
// --- Filter aus URL ---
|
|
$filter_jahr = $_GET['jahr'] ?? null;
|
|
$filter_monat = $_GET['monat'] ?? null;
|
|
if ($filter_jahr && $filter_monat) {
|
|
$filtered_events = array_filter($events, function ($event) use ($filter_jahr, $filter_monat) {
|
|
$date = $event['DTSTART'] ?? '';
|
|
|
|
return substr($date, 0, 4) === $filter_jahr && substr($date, 4, 2) === $filter_monat;
|
|
});
|
|
} elseif ($filter_jahr) {
|
|
$filtered_events = array_filter($events, function ($event) use ($filter_jahr) {
|
|
$date = $event['DTSTART'] ?? '';
|
|
|
|
return substr($date, 0, 4) === $filter_jahr;
|
|
});
|
|
} else {
|
|
$filtered_events = $future_events;
|
|
}
|
|
?>
|
|
|
|
<section class="py-24 bg-sf_grau-50">
|
|
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 flex flex-col md:flex-row gap-8">
|
|
<!-- Sidebar -->
|
|
<aside class="md:w-1/4 w-full mb-8 md:mb-0">
|
|
<div class="bg-white rounded-lg shadow p-4">
|
|
<h2 class="text-lg font-semibold mb-3">Termine nach Jahr/Monat</h2>
|
|
<ul class="space-y-1">
|
|
<?php foreach ($all_events_grouped as $year => $months): ?>
|
|
<li class="mb-2">
|
|
<div class="flex items-center">
|
|
<a href="?jahr=<?php echo $year; ?>"
|
|
class="font-bold text-sf_blau-600 focus:outline-none flex items-center group<?php
|
|
if ($filter_jahr === $year && !$filter_monat) {
|
|
echo ' underline';
|
|
}
|
|
if ($filter_jahr === $year && !$filter_monat) {
|
|
echo ' selected';
|
|
}
|
|
?>" onclick="event.stopPropagation(); openYear('<?php echo $year; ?>')">
|
|
<span><?php echo $year; ?></span>
|
|
</a>
|
|
<button type="button" class="ml-1 focus:outline-none" onclick="toggleYear('<?php echo $year; ?>')">
|
|
<svg class="w-4 h-4 transition-transform" id="arrow-<?php echo $year; ?>" fill="none" stroke="currentColor" stroke-width="2"
|
|
viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<ul class="ml-4 mt-1 hidden" id="months-<?php echo $year; ?>">
|
|
<?php foreach ($months as $month => $evts): ?>
|
|
<li>
|
|
<a href="?jahr=<?php echo $year; ?>&monat=<?php echo $month; ?>" class="text-sf_blau-500 hover:underline<?php if (
|
|
$filter_jahr === $year &&
|
|
$filter_monat === $month
|
|
) {
|
|
echo ' font-bold underline selected';
|
|
} ?>">
|
|
<?php echo $de_months[$month]; ?> (<?php echo count($evts); ?>)
|
|
</a>
|
|
</li>
|
|
<?php endforeach; ?>
|
|
</ul>
|
|
</li>
|
|
<?php endforeach; ?>
|
|
</ul>
|
|
</div>
|
|
<script>
|
|
function toggleYear(year) {
|
|
var ul = document.getElementById('months-' + year);
|
|
var arrow = document.getElementById('arrow-' + year);
|
|
if (ul.classList.contains('hidden')) {
|
|
ul.classList.remove('hidden');
|
|
arrow.style.transform = 'rotate(90deg)';
|
|
} else {
|
|
ul.classList.add('hidden');
|
|
arrow.style.transform = '';
|
|
}
|
|
}
|
|
|
|
function openYear(year) {
|
|
var ul = document.getElementById('months-' + year);
|
|
var arrow = document.getElementById('arrow-' + year);
|
|
if (ul && ul.classList.contains('hidden')) {
|
|
ul.classList.remove('hidden');
|
|
arrow.style.transform = 'rotate(90deg)';
|
|
}
|
|
}
|
|
</script>
|
|
</aside>
|
|
<!-- Hauptinhalt Termine -->
|
|
<div class="md:w-3/4 w-full">
|
|
<?php if (empty($filtered_events)): ?>
|
|
<div class="text-gray-500">Keine Termine gefunden.</div>
|
|
<?php else: ?>
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full border border-gray-200 bg-white rounded-lg shadow">
|
|
<thead>
|
|
<tr class="bg-gray-100">
|
|
<th class="py-2 px-4 border-b text-left">Datum</th>
|
|
<th class="py-2 px-4 border-b text-left">Uhrzeit</th>
|
|
<th class="py-2 px-4 border-b text-left">Titel</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($filtered_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';
|
|
$date = substr($date, 0, 10);
|
|
$iso_date = $date_info['iso'];
|
|
?>
|
|
<tr class="hover:bg-gray-50">
|
|
<td class="py-2 px-4 border-b whitespace-nowrap"><?php echo htmlspecialchars(
|
|
$date,
|
|
); ?></td>
|
|
<td class="py-2 px-4 border-b whitespace-nowrap">
|
|
<?php if ($date_info['has_time']): ?>
|
|
<span class="local-time" data-iso-date="<?php echo htmlspecialchars(
|
|
$iso_date,
|
|
); ?>"><?php echo htmlspecialchars($time); ?></span>
|
|
<?php else: ?>
|
|
ganztägig
|
|
<?php endif; ?>
|
|
</td>
|
|
<td class="py-2 px-4 border-b"><?php echo htmlspecialchars($summary); ?></td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
<style>
|
|
.selected {
|
|
text-decoration: underline;
|
|
text-underline-offset: 3px;
|
|
font-weight: bold;
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
// 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
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
convertTimesToLocal();
|
|
});
|
|
</script>
|
|
</section>
|