Merge branch 'feature/WEB-17_Collection' into develop
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
// URL der öffentlichen ICS-Datei
|
||||
define(
|
||||
'CAL_URL',
|
||||
'https://calendar.google.com/calendar/ical/jv1bq94un3ivoa8ka0rk9ngq4k%40group.calendar.google.com/public/basic.ics',
|
||||
);
|
||||
|
||||
function format_ics_date($date)
|
||||
{
|
||||
// Unterstützt sowohl ganztägige als auch Zeitangaben
|
||||
if (strpos($date, 'T') !== false) {
|
||||
$dt = DateTime::createFromFormat('Ymd\THis', substr($date, 0, 15));
|
||||
|
||||
return $dt ? $dt->format('d.m.Y H:i') : $date;
|
||||
} else {
|
||||
$dt = DateTime::createFromFormat('Ymd', $date);
|
||||
|
||||
return $dt ? $dt->format('d.m.Y') : $date;
|
||||
}
|
||||
}
|
||||
|
||||
// ICS-Datei laden
|
||||
$ics = @file_get_contents(CAL_URL);
|
||||
if ( ! $ics) {
|
||||
echo '<div class="text-red-600">Kalender konnte nicht geladen werden.</div>';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Termine parsen
|
||||
function parse_ics($ics)
|
||||
{
|
||||
$lines = explode("\n", $ics);
|
||||
$events = [];
|
||||
$event = [];
|
||||
$inEvent = false;
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line);
|
||||
if ($line === 'BEGIN:VEVENT') {
|
||||
$inEvent = true;
|
||||
$event = [];
|
||||
} elseif ($line === 'END:VEVENT') {
|
||||
$inEvent = false;
|
||||
$events[] = $event;
|
||||
} elseif ($inEvent) {
|
||||
// Property kann Parameter enthalten, z.B. DTSTART;TZID=Europe/Berlin:20240701T19000000
|
||||
$parts = explode(':', $line, 2);
|
||||
if (count($parts) === 2) {
|
||||
$key = $parts[0];
|
||||
$val = $parts[1];
|
||||
// Nur den eigentlichen Property-Namen extrahieren
|
||||
$key = strtoupper(preg_replace('/;.+$/', '', $key));
|
||||
$event[$key] = $val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
||||
$events = parse_ics($ics);
|
||||
|
||||
return function() use ($events) {
|
||||
return $events;
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,215 @@
|
||||
<?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;
|
||||
});
|
||||
|
||||
// --- 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'] ?? '';
|
||||
$date = format_ics_date($start);
|
||||
$time = (str_contains($start, 'T')) ? substr($date, 11) : 'ganztägig';
|
||||
$day = substr($date, 0, 2);
|
||||
$month = substr($date, 3, 2);
|
||||
?>
|
||||
|
||||
<div class="termine-card flex-none w-full md:w-1/3 bg-gradient-to-br from-blue-50 to-blue-100 rounded-2xl shadow-xl 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-center 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">
|
||||
<h3 class="text-xl font-bold text-sf_grau-900"><?= $event["SUMMARY"] ?></h3>
|
||||
<p class="text-sf_blau-700 text-lg font-medium mt-1">ab <?php echo htmlspecialchars($time); ?> Uhr</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 der Sichtbarkeit der Pfeile
|
||||
function updateArrowVisibility() {
|
||||
scrollLeftBtn.classList.toggle('opacity-50', currentPosition <= 0);
|
||||
scrollRightBtn.classList.toggle('opacity-50', currentPosition >= maxPosition);
|
||||
}
|
||||
|
||||
// Initialisierung - verstecke die Pfeile wenn weniger als cardsToShow+1 Karten vorhanden sind
|
||||
if (totalCards <= cardsToShow) {
|
||||
scrollLeftBtn.classList.add('hidden');
|
||||
scrollRightBtn.classList.add('hidden');
|
||||
} else {
|
||||
scrollLeftBtn.classList.remove('hidden');
|
||||
scrollRightBtn.classList.remove('hidden');
|
||||
}
|
||||
|
||||
// Funktion zum Scrollen nach links
|
||||
scrollLeftBtn.addEventListener('click', () => {
|
||||
if (currentPosition > 0) {
|
||||
currentPosition--;
|
||||
scrollToPosition();
|
||||
}
|
||||
});
|
||||
|
||||
// Funktion zum Scrollen nach rechts
|
||||
scrollRightBtn.addEventListener('click', () => {
|
||||
if (currentPosition < maxPosition) {
|
||||
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)`;
|
||||
updateArrowVisibility();
|
||||
}
|
||||
|
||||
// Initialer Aufruf
|
||||
updateArrowVisibility();
|
||||
|
||||
// Responsives Verhalten - bei Fenstergröße-Änderung
|
||||
window.addEventListener('resize', () => {
|
||||
// Anzahl der Karten je nach Bildschirmgröße anpassen
|
||||
cardsToShow = window.innerWidth < 768 ? 1 : 3;
|
||||
maxPosition = Math.max(0, totalCards - cardsToShow);
|
||||
|
||||
// Sicherstellen, dass die aktuelle Position gültig ist
|
||||
currentPosition = Math.min(currentPosition, maxPosition);
|
||||
|
||||
// Position aktualisieren
|
||||
scrollToPosition();
|
||||
|
||||
// Pfeile aktualisieren
|
||||
if (totalCards <= cardsToShow) {
|
||||
scrollLeftBtn.classList.add('hidden');
|
||||
scrollRightBtn.classList.add('hidden');
|
||||
} else {
|
||||
scrollLeftBtn.classList.remove('hidden');
|
||||
scrollRightBtn.classList.remove('hidden');
|
||||
}
|
||||
});
|
||||
});
|
||||
</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: calc((100% - 48px) / 3); /* 100% minus 2 Gaps (2 * 24px) geteilt durch 3 */
|
||||
}
|
||||
}
|
||||
|
||||
/* 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>
|
||||
@@ -1,50 +1,5 @@
|
||||
<?php
|
||||
|
||||
// URL der öffentlichen ICS-Datei
|
||||
define(
|
||||
'CAL_URL',
|
||||
'https://calendar.google.com/calendar/ical/jv1bq94un3ivoa8ka0rk9ngq4k%40group.calendar.google.com/public/basic.ics',
|
||||
);
|
||||
|
||||
// ICS-Datei laden
|
||||
$ics = @file_get_contents(CAL_URL);
|
||||
if ( ! $ics) {
|
||||
echo '<div class="text-red-600">Kalender konnte nicht geladen werden.</div>';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Termine parsen
|
||||
function parse_ics($ics)
|
||||
{
|
||||
$lines = explode("\n", $ics);
|
||||
$events = [];
|
||||
$event = [];
|
||||
$inEvent = false;
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line);
|
||||
if ($line === 'BEGIN:VEVENT') {
|
||||
$inEvent = true;
|
||||
$event = [];
|
||||
} elseif ($line === 'END:VEVENT') {
|
||||
$inEvent = false;
|
||||
$events[] = $event;
|
||||
} elseif ($inEvent) {
|
||||
// Property kann Parameter enthalten, z.B. DTSTART;TZID=Europe/Berlin:20240701T19000000
|
||||
$parts = explode(':', $line, 2);
|
||||
if (count($parts) === 2) {
|
||||
$key = $parts[0];
|
||||
$val = $parts[1];
|
||||
// Nur den eigentlichen Property-Namen extrahieren
|
||||
$key = strtoupper(preg_replace('/;.+$/', '', $key));
|
||||
$event[$key] = $val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
||||
function format_ics_date($date)
|
||||
{
|
||||
// Unterstützt sowohl ganztägige als auch Zeitangaben
|
||||
@@ -59,7 +14,8 @@ function format_ics_date($date)
|
||||
}
|
||||
}
|
||||
|
||||
$events = parse_ics($ics);
|
||||
$events = collection('termine');
|
||||
|
||||
// Nur Events mit DTSTART berücksichtigen
|
||||
$events = array_filter($events, function ($event) {
|
||||
return isset($event['DTSTART']) && ! empty($event['DTSTART']);
|
||||
@@ -135,6 +91,7 @@ if ($filter_jahr && $filter_monat) {
|
||||
$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 -->
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php snippet('layout', slots: true) ?>
|
||||
|
||||
<h1>Hallo Welt!</h1>
|
||||
|
||||
<?php snippet('termine-home') ?>
|
||||
|
||||
<?php endsnippet() ?>
|
||||
Reference in New Issue
Block a user