Social_Cooking/js/event_overview.js
2026-04-09 17:01:37 +02:00

280 lines
10 KiB
JavaScript

document.addEventListener('DOMContentLoaded', () => {
const EVENTS_STORAGE_KEY = 'socialCookingEvents';
// -------------------------------------------------------------
// DOM references used throughout the page lifecycle.
// -------------------------------------------------------------
const eventGrid = document.getElementById('event-grid');
const filterButtons = document.querySelectorAll('.category-item');
const locationFilter = document.getElementById('location-filter');
const dateFilter = document.getElementById('date-filter');
const locationIconPath = 'assets/location-pin.svg';
// -------------------------------------------------------------
// In-memory state for fetched events and currently active category.
// -------------------------------------------------------------
let allEvents = [];
let activeCategory = 'ALLE';
function getStoredEvents() {
try {
const stored = localStorage.getItem(EVENTS_STORAGE_KEY);
return stored ? JSON.parse(stored) : [];
} catch (error) {
console.error('Lokale Events konnten nicht gelesen werden.', error);
return [];
}
}
// -------------------------------------------------------------
// Initial data bootstrap:
// 1) fetch JSON,
// 2) populate select options,
// 3) restore filter state from sessionStorage,
// 4) render filtered list.
// -------------------------------------------------------------
async function fetchEvents() {
try {
const response = await fetch('data/events.json');
const apiEvents = await response.json();
const localEvents = getStoredEvents();
allEvents = [...localEvents, ...apiEvents];
populateMetaFilters();
const savedCategory = sessionStorage.getItem('activeFilter') || 'ALLE';
const savedLocation = sessionStorage.getItem('activeLocation') || 'ALLE_ORTE';
const savedDate = sessionStorage.getItem('activeDate') || '';
activeCategory = savedCategory;
if (locationFilter) {
locationFilter.value = hasOption(locationFilter, savedLocation) ? savedLocation : 'ALLE_ORTE';
}
if (dateFilter) {
dateFilter.value = savedDate;
}
applyFilters();
} catch (error) {
console.error('Fehler:', error);
eventGrid.innerHTML = '<p>Events konnten nicht geladen werden.</p>';
}
}
// Build location options dynamically from loaded events.
function populateMetaFilters() {
const locations = [...new Set(allEvents.map(event => event.location))].sort();
if (locationFilter) {
locations.forEach(location => {
const option = document.createElement('option');
option.value = location;
option.textContent = location;
locationFilter.appendChild(option);
});
}
}
// Convert localized event date (e.g. 19. MÄR. 2026) into ISO format for date input comparison.
function parseEventDateToIso(dateString) {
if (/^\d{4}-\d{2}-\d{2}$/.test(dateString)) {
return dateString;
}
const months = {
JAN: '01',
FEB: '02',
'MÄR': '03',
MRZ: '03',
APR: '04',
MAI: '05',
JUN: '06',
JUL: '07',
AUG: '08',
SEP: '09',
OKT: '10',
NOV: '11',
DEZ: '12'
};
const match = dateString.match(/^(\d{1,2})\.\s*([A-ZÄÖÜ]{3})\.\s*(\d{4})$/);
if (!match) {
return '';
}
const day = String(match[1]).padStart(2, '0');
const month = months[match[2]];
const year = match[3];
return month ? `${year}-${month}-${day}` : '';
}
// Convert short month notation into full German month label for UI display.
function formatEventDate(dateString) {
if (/^\d{4}-\d{2}-\d{2}$/.test(dateString)) {
const [year, month, day] = dateString.split('-');
return `${Number(day)}. ${['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'][Number(month) - 1]} ${year}`;
}
const labels = {
JAN: 'Januar',
FEB: 'Februar',
'MÄR': 'März',
MRZ: 'März',
APR: 'April',
MAI: 'Mai',
JUN: 'Juni',
JUL: 'Juli',
AUG: 'August',
SEP: 'September',
OKT: 'Oktober',
NOV: 'November',
DEZ: 'Dezember'
};
const match = dateString.match(/^(\d{1,2})\.\s*([A-ZÄÖÜ]{3})\.\s*(\d{4})$/);
if (!match) {
return dateString;
}
const day = Number(match[1]);
const monthLabel = labels[match[2]];
const year = match[3];
return monthLabel ? `${day}. ${monthLabel} ${year}` : dateString;
}
// Normalize time label from UHR to Uhr for consistent typography.
function formatEventTime(timeString) {
if (!timeString) {
return '';
}
return timeString.includes('UHR')
? timeString.replace('UHR', 'Uhr').trim()
: `${timeString} Uhr`;
}
// Safely verify whether a value exists in the given select element.
function hasOption(selectElement, value) {
return Array.from(selectElement.options).some(option => option.value === value);
}
// Apply all filters together (category, location, date), update button state, render and persist.
function applyFilters() {
const selectedLocation = locationFilter ? locationFilter.value : 'ALLE_ORTE';
const selectedDate = dateFilter ? dateFilter.value : '';
filterButtons.forEach(btn => {
if (btn.getAttribute('data-cat') === activeCategory) {
btn.classList.add('active');
} else {
btn.classList.remove('active');
}
});
const filtered = allEvents.filter(event => {
const categoryMatch = activeCategory === 'ALLE' || event.category === activeCategory;
const locationMatch = selectedLocation === 'ALLE_ORTE' || event.location === selectedLocation;
const eventDateIso = parseEventDateToIso(event.date);
const dateMatch = !selectedDate || eventDateIso === selectedDate;
return categoryMatch && locationMatch && dateMatch;
});
renderEvents(filtered);
sessionStorage.setItem('activeFilter', activeCategory);
sessionStorage.setItem('activeLocation', selectedLocation);
sessionStorage.setItem('activeDate', selectedDate);
}
// Render either:
// - empty state call-to-action when no results match,
// - or event cards with status and metadata.
function renderEvents(events) {
eventGrid.innerHTML = '';
if (events.length === 0) {
eventGrid.innerHTML = `
<div class="empty-state">
<p class="empty-state-kicker">Keine Treffer</p>
<h3>Schade, aktuell gibt es hier keine Events.</h3>
<p>Starte dein eigenes Dinner und bringe die Community an deinen Tisch.</p>
<a class="empty-state-link" href="event_create.html">
<button class="empty-state-btn" type="button">Event erstellen</button>
</a>
</div>
`;
return;
}
events.forEach(event => {
// Card shell and click-through navigation to detail page.
const card = document.createElement('article');
card.className = 'event-card';
card.style.cursor = 'pointer';
card.onclick = () => {
window.location.href = `event_detail.html?id=${event.id}`;
};
const displayDate = formatEventDate(event.date);
const displayTime = formatEventTime(event.time);
// Capacity logic:
// spots = total capacity, participants.length = booked seats.
const bookedSeats = event.participants ? event.participants.length : 0;
const totalCapacity = event.spots;
const freePlaces = Math.max(0, totalCapacity - bookedSeats);
const isFull = freePlaces === 0;
// Build optional specification chips only when data exists.
const specsChips = event.specifications && event.specifications.length > 0
? event.specifications.map(spec => `<span class="event-tag">${spec}</span>`).join('')
: '';
card.innerHTML = `
<div class="event-main">
<div class="event-top-row">
<span class="event-location">
<img src="${locationIconPath}" alt="">
${event.location}
</span>
<p class="event-date-time">${displayDate} | ${displayTime} | ${bookedSeats}/${totalCapacity} Gaeste</p>
</div>
<h2 class="event-title">${event.title}</h2>
<div class="event-meta-row">
<span class="event-tag">${event.category}</span>
<span class="event-tag">${event.diet}</span>
${specsChips}
</div>
</div>
<div class="event-side${isFull ? ' event-side-full' : ''}">
<span class="event-spots${isFull ? ' event-spots-full' : ''}">${isFull ? 'AUSGEBUCHT' : `${freePlaces} Plätze FREI`}</span>
${isFull ? '' : '<button class="btn-primary" type="button">Anmelden</button>'}
</div>
`;
eventGrid.appendChild(card);
});
}
// Category filter interactions.
filterButtons.forEach(button => {
button.addEventListener('click', () => {
activeCategory = button.getAttribute('data-cat');
applyFilters();
});
});
// Secondary filter interactions.
if (locationFilter) {
locationFilter.addEventListener('change', applyFilters);
}
if (dateFilter) {
dateFilter.addEventListener('change', applyFilters);
}
// Kick off initial load/render cycle.
fetchEvents();
});