839 lines
33 KiB
JavaScript
839 lines
33 KiB
JavaScript
document.addEventListener('DOMContentLoaded', () => {
|
||
const EVENTS_STORAGE_KEY = 'socialCookingEvents';
|
||
const CURRENT_USER_KEY = 'socialCookingCurrentUser';
|
||
const USERS_STORAGE_KEY = 'socialCookingUsers';
|
||
const REGISTRATION_STORAGE_KEY = 'socialCookingRegistrations';
|
||
const INFO_MODAL_SHOWN_KEY = 'infoModalShownOnFirstLogin';
|
||
// -------------------------------------------------------------
|
||
// 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/icon_location.svg';
|
||
const calendarIconPath = 'assets/icon_calendar.svg';
|
||
const gastIconPath = 'assets/icon_gast.svg';
|
||
|
||
|
||
// -------------------------------------------------------------
|
||
// In-memory state for fetched events and currently active filters.
|
||
// Separate state for category, diet, and allergie selections.
|
||
// -------------------------------------------------------------
|
||
let allEvents = [];
|
||
let activeCategory = 'ALLE';
|
||
let activeDiets = new Set();
|
||
let activeAllergies = new Set();
|
||
const currentUser = getCurrentUser();
|
||
|
||
function getCurrentUser() {
|
||
try {
|
||
const stored = localStorage.getItem(CURRENT_USER_KEY);
|
||
return stored ? JSON.parse(stored) : null;
|
||
} catch (error) {
|
||
console.error('Aktueller Benutzer konnte nicht gelesen werden.', error);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
function getInfoModalShownKeyForUser(user) {
|
||
const email = String(user?.email || '').trim().toLowerCase();
|
||
return email ? `${INFO_MODAL_SHOWN_KEY}:${email}` : INFO_MODAL_SHOWN_KEY;
|
||
}
|
||
|
||
// Prüft, ob ein Event dem aktuellen Benutzer gehört.
|
||
function isEventOwnedByCurrentUser(event, user) {
|
||
if (!event || !user) {
|
||
return false;
|
||
}
|
||
|
||
const userEmail = String(user.email || '').trim().toLowerCase();
|
||
const hostEmail = String(event.hostEmail || '').trim().toLowerCase();
|
||
|
||
if (userEmail && hostEmail) {
|
||
return userEmail === hostEmail;
|
||
}
|
||
|
||
const userFirstName = String(user.vorname || '').trim().toLowerCase();
|
||
const hostName = String(event.host?.name || '').trim().toLowerCase();
|
||
return Boolean(userFirstName && hostName && userFirstName === hostName);
|
||
}
|
||
|
||
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 [];
|
||
}
|
||
}
|
||
|
||
function getRegistrationMap() {
|
||
try {
|
||
const stored = localStorage.getItem(REGISTRATION_STORAGE_KEY);
|
||
return stored ? JSON.parse(stored) : {};
|
||
} catch (error) {
|
||
console.error('Anmeldedaten konnten nicht gelesen werden.', error);
|
||
return {};
|
||
}
|
||
}
|
||
|
||
function getStoredUsers() {
|
||
try {
|
||
const stored = localStorage.getItem(USERS_STORAGE_KEY);
|
||
return stored ? JSON.parse(stored) : [];
|
||
} catch (error) {
|
||
console.error('Benutzerdaten konnten nicht gelesen werden.', error);
|
||
return [];
|
||
}
|
||
}
|
||
|
||
function getUserDisplayName(user) {
|
||
if (!user) {
|
||
return '';
|
||
}
|
||
|
||
const firstName = String(user.vorname || '').trim();
|
||
const lastName = String(user.nachname || '').trim();
|
||
const fullName = `${firstName} ${lastName}`.trim();
|
||
|
||
return (fullName || firstName || String(user.email || '').trim()).trim();
|
||
}
|
||
|
||
function getResolvedParticipants(event, registrationMap) {
|
||
const baseParticipants = Array.isArray(event.participants)
|
||
? event.participants.map(name => String(name || '').trim()).filter(Boolean)
|
||
: [];
|
||
const usersByEmail = new Map(
|
||
getStoredUsers().map(user => [String(user.email || '').trim().toLowerCase(), user])
|
||
);
|
||
const participantLookup = new Set(baseParticipants.map(name => name.toLowerCase()));
|
||
|
||
Object.entries(registrationMap || {}).forEach(([email, ids]) => {
|
||
const isRegisteredForEvent = Array.isArray(ids)
|
||
&& ids.map(id => Number(id)).includes(Number(event.id));
|
||
|
||
if (!isRegisteredForEvent) {
|
||
return;
|
||
}
|
||
|
||
const user = usersByEmail.get(String(email || '').trim().toLowerCase());
|
||
const displayName = getUserDisplayName(user) || String(email || '').trim();
|
||
const normalizedName = displayName.toLowerCase();
|
||
|
||
if (displayName && !participantLookup.has(normalizedName)) {
|
||
baseParticipants.push(displayName);
|
||
participantLookup.add(normalizedName);
|
||
}
|
||
});
|
||
|
||
return baseParticipants;
|
||
}
|
||
|
||
function setRegistrationMap(registrationMap) {
|
||
localStorage.setItem(REGISTRATION_STORAGE_KEY, JSON.stringify(registrationMap));
|
||
}
|
||
|
||
// -------------------------------------------------------------
|
||
// 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') || '';
|
||
const savedDiets = sessionStorage.getItem('activeDiets') || '';
|
||
const savedAllergies = sessionStorage.getItem('activeAllergies') || '';
|
||
|
||
activeCategory = savedCategory;
|
||
activeDiets = new Set(savedDiets ? savedDiets.split(',') : []);
|
||
activeAllergies = new Set(savedAllergies ? savedAllergies.split(',') : []);
|
||
|
||
if (locationFilter) {
|
||
locationFilter.value = hasOption(locationFilter, savedLocation) ? savedLocation : 'ALLE_ORTE';
|
||
}
|
||
if (dateFilter) {
|
||
dateFilter.value = savedDate;
|
||
}
|
||
|
||
updateDietAvailability();
|
||
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`;
|
||
}
|
||
|
||
// Baut aus Eventdatum/-zeit ein Date-Objekt für Fristlogik und Vergleiche.
|
||
function parseEventDateTime(event) {
|
||
if (!event?.date) {
|
||
return null;
|
||
}
|
||
|
||
const dateValue = String(event.date).trim();
|
||
const isoDateMatch = dateValue.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
||
let year;
|
||
let month;
|
||
let day;
|
||
|
||
if (isoDateMatch) {
|
||
year = Number(isoDateMatch[1]);
|
||
month = Number(isoDateMatch[2]);
|
||
day = Number(isoDateMatch[3]);
|
||
} else {
|
||
const monthMap = {
|
||
jan: 1,
|
||
januar: 1,
|
||
feb: 2,
|
||
februar: 2,
|
||
'mär': 3,
|
||
mrz: 3,
|
||
mar: 3,
|
||
maerz: 3,
|
||
märz: 3,
|
||
apr: 4,
|
||
april: 4,
|
||
mai: 5,
|
||
jun: 6,
|
||
juni: 6,
|
||
jul: 7,
|
||
juli: 7,
|
||
aug: 8,
|
||
august: 8,
|
||
sep: 9,
|
||
sept: 9,
|
||
september: 9,
|
||
okt: 10,
|
||
oktober: 10,
|
||
nov: 11,
|
||
november: 11,
|
||
dez: 12,
|
||
dezember: 12
|
||
};
|
||
const localizedMatch = dateValue.match(/^(\d{1,2})\.\s*([A-Za-zÄÖÜäöü]{3,9})\.?\s*(\d{4})$/);
|
||
|
||
if (!localizedMatch) {
|
||
return null;
|
||
}
|
||
|
||
day = Number(localizedMatch[1]);
|
||
month = monthMap[String(localizedMatch[2]).toLowerCase()];
|
||
year = Number(localizedMatch[3]);
|
||
|
||
if (!month) {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
const timeMatch = String(event.time || '').match(/(\d{1,2}):(\d{2})/);
|
||
const hours = timeMatch ? Number(timeMatch[1]) : 0;
|
||
const minutes = timeMatch ? Number(timeMatch[2]) : 0;
|
||
|
||
return new Date(year, month - 1, day, hours, minutes, 0, 0);
|
||
}
|
||
|
||
// Zählt eindeutige Registrierungen eines Events über alle Benutzer.
|
||
function countRegistrationsForEvent(registrationMap, eventId) {
|
||
return Object.values(registrationMap).reduce((count, ids) => {
|
||
const hasEvent = Array.isArray(ids)
|
||
&& ids.map(id => Number(id)).includes(Number(eventId));
|
||
|
||
return hasEvent ? count + 1 : count;
|
||
}, 0);
|
||
}
|
||
|
||
// Schliesst neue Anmeldungen ab 24h vor Start (inkl. bereits gestarteter Events).
|
||
function isRegistrationClosedForEvent(event) {
|
||
const eventDateTime = parseEventDateTime(event);
|
||
if (!eventDateTime || Number.isNaN(eventDateTime.getTime())) {
|
||
return false;
|
||
}
|
||
|
||
const msUntilStart = eventDateTime.getTime() - Date.now();
|
||
const twentyfourHoursInMs = 24 * 60 * 60 * 1000;
|
||
|
||
return msUntilStart <= twentyfourHoursInMs;
|
||
}
|
||
|
||
// 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, diet, allergie, location, date), update button state, render and persist.
|
||
function applyFilters() {
|
||
const selectedLocation = locationFilter ? locationFilter.value : 'ALLE_ORTE';
|
||
const selectedDate = dateFilter ? dateFilter.value : '';
|
||
|
||
// Update active states for all filter types
|
||
filterButtons.forEach(btn => {
|
||
const isCategoryButton = btn.getAttribute('data-cat') !== null;
|
||
const isDietButton = btn.getAttribute('data-diet') !== null;
|
||
const isAllergieButton = btn.getAttribute('data-allergie') !== null;
|
||
|
||
if (isCategoryButton) {
|
||
if (btn.getAttribute('data-cat') === activeCategory) {
|
||
btn.classList.add('active');
|
||
} else {
|
||
btn.classList.remove('active');
|
||
}
|
||
} else if (isDietButton) {
|
||
if (activeDiets.has(btn.getAttribute('data-diet'))) {
|
||
btn.classList.add('active');
|
||
} else {
|
||
btn.classList.remove('active');
|
||
}
|
||
} else if (isAllergieButton) {
|
||
if (activeAllergies.has(btn.getAttribute('data-allergie'))) {
|
||
btn.classList.add('active');
|
||
} else {
|
||
btn.classList.remove('active');
|
||
}
|
||
}
|
||
});
|
||
|
||
const filtered = allEvents.filter(event => {
|
||
if (event.status === 'canceled') return false;
|
||
|
||
const categoryMatch = activeCategory === 'ALLE' || event.category === activeCategory;
|
||
|
||
// Diet filter: if no diets selected, show all. Otherwise, event MUST have at least one selected diet.
|
||
const dietMatch = activeDiets.size === 0 ||
|
||
(event.diet && event.diet.split(', ').some(d => activeDiets.has(d.trim())));
|
||
|
||
// Allergie filter: if no allergies selected, show all. Otherwise, event MUST have at least one selected allergie.
|
||
const allergieMatch = activeAllergies.size === 0 ||
|
||
(event.specifications && event.specifications.some(spec => activeAllergies.has(spec)));
|
||
|
||
const locationMatch = selectedLocation === 'ALLE_ORTE' || event.location === selectedLocation;
|
||
const eventDateIso = parseEventDateToIso(event.date);
|
||
const dateMatch = !selectedDate || eventDateIso === selectedDate;
|
||
|
||
return categoryMatch && dietMatch && allergieMatch && locationMatch && dateMatch;
|
||
});
|
||
renderEvents(filtered);
|
||
|
||
sessionStorage.setItem('activeFilter', activeCategory);
|
||
sessionStorage.setItem('activeLocation', selectedLocation);
|
||
sessionStorage.setItem('activeDate', selectedDate);
|
||
sessionStorage.setItem('activeDiets', Array.from(activeDiets).join(','));
|
||
sessionStorage.setItem('activeAllergies', Array.from(activeAllergies).join(','));
|
||
}
|
||
|
||
// Render either:
|
||
// - empty state call-to-action when no results match,
|
||
// - or event cards with status and metadata.
|
||
function renderEvents(events) {
|
||
eventGrid.innerHTML = '';
|
||
const registrationMap = getRegistrationMap();
|
||
const userRegistrationSet = currentUser?.email && Array.isArray(registrationMap[currentUser.email])
|
||
? new Set(registrationMap[currentUser.email].map(id => Number(id)))
|
||
: new Set();
|
||
|
||
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 Event und bringe die Community an deinen Tisch.</p>
|
||
<a class="empty-state-link" href="event_create.html">
|
||
<button class="button-primary" 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.addEventListener('click', clickedEvent => {
|
||
if (clickedEvent.target instanceof HTMLElement && clickedEvent.target.closest('button')) {
|
||
return;
|
||
}
|
||
|
||
window.location.href = `event_detail.html?id=${event.id}`;
|
||
});
|
||
|
||
const displayDate = formatEventDate(event.date);
|
||
const displayTime = formatEventTime(event.time);
|
||
|
||
// Capacity logic:
|
||
// spots = total capacity, resolved participants = booked seats.
|
||
const resolvedParticipants = getResolvedParticipants(event, registrationMap);
|
||
const bookedSeats = resolvedParticipants.length;
|
||
const totalCapacity = event.spots;
|
||
const freePlaces = Math.max(0, totalCapacity - bookedSeats);
|
||
const isFull = freePlaces === 0;
|
||
const isOwnEvent = isEventOwnedByCurrentUser(event, currentUser);
|
||
const isRegistered = userRegistrationSet.has(Number(event.id));
|
||
const isRegistrationClosed = isRegistrationClosedForEvent(event);
|
||
const isCanceled = event.status === 'canceled';
|
||
|
||
if (isCanceled) {
|
||
card.style.opacity = '0.6';
|
||
}
|
||
|
||
// 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('')
|
||
: '';
|
||
|
||
// Build diet tags: split by comma and create individual tags
|
||
const dietTags = event.diet && event.diet !== 'Keine Angabe' && event.diet !== '–'
|
||
? event.diet.split(', ').map(d => `<span class="event-tag">${d.trim()}</span>`).join('')
|
||
: '';
|
||
|
||
let actionMarkup = '';
|
||
if (isCanceled) {
|
||
actionMarkup = '<button class="button-primary" type="button" disabled>Abgesagt</button>';
|
||
} else if (isOwnEvent) {
|
||
actionMarkup = '<button class="button-primary-eigener-event" type="button" data-registration-action="own" disabled>Dein Event!</button>';
|
||
} else if (isRegistered) {
|
||
actionMarkup = isRegistrationClosed
|
||
? '<button class="button-primary-abmelden" type="button" disabled>Abmeldung geschlossen</button>'
|
||
: '<button class="button-primary-abmelden" type="button" data-registration-action="unregister">Abmelden</button>';
|
||
} else if (isRegistrationClosed) {
|
||
actionMarkup = '<button class="button-primary-abmelden" type="button" data-registration-action="closed" disabled>Anmeldung geschlossen</button>';
|
||
} else if (!isFull) {
|
||
if (!currentUser) {
|
||
actionMarkup = '<button class="button-primary btn-primary-register" type="button" data-registration-action="login">Anmelden</button>';
|
||
} else {
|
||
actionMarkup = '<button class="button-primary btn-primary-register" type="button" data-registration-action="register">Anmelden</button>';
|
||
}
|
||
}
|
||
|
||
let sideInfoMarkup = '';
|
||
if (isCanceled) {
|
||
sideInfoMarkup = '<span class="button-plaetze">Event abgesagt</span>';
|
||
} else if (!isRegistrationClosed) {
|
||
const spotLabel = freePlaces === 1 ? 'Platz frei' : 'Plätze frei';
|
||
sideInfoMarkup = `<span class="button-plaetze${isFull ? ' event-spots-full' : ''}">${isFull ? 'Ausgebucht' : `${freePlaces} ${spotLabel}`}</span>`;
|
||
}
|
||
|
||
card.innerHTML = `
|
||
<div class="event-main">
|
||
<div class="event-top-row">
|
||
<span class="event-location">
|
||
<img src="${locationIconPath}" class="icon" alt="">
|
||
${event.location}
|
||
</span>
|
||
<span class="event-date-time"> <img src="${calendarIconPath}" class="icon" alt=""> ${displayDate} | ${displayTime}
|
||
</span>
|
||
<span class="event-gast"> <img src="${gastIconPath}" class="icon" alt="Gaeste Icon">${bookedSeats}/${totalCapacity} </span>
|
||
</div>
|
||
<h2>${event.title}</h2>
|
||
<div class="event-meta-row">
|
||
<span class="event-tag">${event.category}</span>
|
||
${dietTags}
|
||
${specsChips}
|
||
</div>
|
||
</div>
|
||
<div class="event-side${isFull ? ' event-side-full' : ''}">
|
||
${actionMarkup}
|
||
${sideInfoMarkup}
|
||
</div>
|
||
`;
|
||
|
||
const actionButton = card.querySelector('[data-registration-action]');
|
||
if (actionButton) {
|
||
actionButton.addEventListener('click', clickEvent => {
|
||
clickEvent.stopPropagation();
|
||
|
||
const action = actionButton.getAttribute('data-registration-action');
|
||
if (action === 'own') {
|
||
return;
|
||
}
|
||
|
||
if (action === 'closed') {
|
||
return;
|
||
}
|
||
|
||
if (action === 'login') {
|
||
window.location.href = 'login.html';
|
||
return;
|
||
}
|
||
|
||
if (!currentUser?.email) {
|
||
window.location.href = 'login.html';
|
||
return;
|
||
}
|
||
|
||
const nextRegistrationMap = getRegistrationMap();
|
||
const currentIds = Array.isArray(nextRegistrationMap[currentUser.email])
|
||
? nextRegistrationMap[currentUser.email].map(id => Number(id))
|
||
: [];
|
||
const idSet = new Set(currentIds);
|
||
|
||
// Anmelde-Modal öffnen
|
||
if (action === 'register' && !isFull && !isRegistrationClosed) {
|
||
const modal = document.getElementById('register-confirm-modal');
|
||
if (modal) {
|
||
modal.classList.add('show');
|
||
document.getElementById('confirm-register-btn').onclick = () => {
|
||
modal.classList.remove('show');
|
||
const map = getRegistrationMap();
|
||
const ids = new Set((map[currentUser.email] || []).map(id => Number(id)));
|
||
ids.add(Number(event.id));
|
||
map[currentUser.email] = Array.from(ids);
|
||
setRegistrationMap(map);
|
||
const snackbar = document.getElementById('snackbar');
|
||
if (snackbar) {
|
||
snackbar.textContent = 'Du wurdest erfolgreich angemeldet.';
|
||
snackbar.classList.add('snackbar--visible');
|
||
setTimeout(() => snackbar.classList.remove('snackbar--visible'), 3000);
|
||
}
|
||
applyFilters();
|
||
};
|
||
}
|
||
return;
|
||
}
|
||
|
||
// Abmelde-Modal öffnen
|
||
if (action === 'unregister') {
|
||
const modal = document.getElementById('unregister-confirm-modal');
|
||
if (modal) {
|
||
modal.classList.add('show');
|
||
document.getElementById('confirm-unregister-btn').onclick = () => {
|
||
modal.classList.remove('show');
|
||
const map = getRegistrationMap();
|
||
const ids = new Set((map[currentUser.email] || []).map(id => Number(id)));
|
||
ids.delete(Number(event.id));
|
||
map[currentUser.email] = Array.from(ids);
|
||
setRegistrationMap(map);
|
||
const snackbar = document.getElementById('snackbar');
|
||
if (snackbar) {
|
||
snackbar.textContent = 'Du wurdest erfolgreich abgemeldet.';
|
||
snackbar.classList.add('snackbar--danger', 'snackbar--visible');
|
||
setTimeout(() => {
|
||
snackbar.classList.remove('snackbar--visible');
|
||
setTimeout(() => snackbar.classList.remove('snackbar--danger'), 400);
|
||
}, 3000);
|
||
}
|
||
applyFilters();
|
||
};
|
||
}
|
||
return;
|
||
}
|
||
|
||
nextRegistrationMap[currentUser.email] = Array.from(idSet);
|
||
setRegistrationMap(nextRegistrationMap);
|
||
applyFilters();
|
||
});
|
||
}
|
||
|
||
eventGrid.appendChild(card);
|
||
});
|
||
}
|
||
|
||
// Verhindert widersprüchliche Ernährungsformen:
|
||
// Fleisch/Fisch schliessen Vegetarisch/Vegan aus.
|
||
// Vegan schliesst alles andere (Fleisch, Fisch, Vegetarisch) aus.
|
||
// Vegetarisch schliesst Fleisch/Fisch aus, aber nicht Vegan.
|
||
function updateDietAvailability() {
|
||
const dietButtons = Array.from(filterButtons).filter(btn => btn.getAttribute('data-diet') !== null);
|
||
const meatFishButtons = dietButtons.filter(btn => ['Fleisch', 'Fisch'].includes(btn.getAttribute('data-diet')));
|
||
const plantButtons = dietButtons.filter(btn => ['Vegetarisch', 'Vegan'].includes(btn.getAttribute('data-diet')));
|
||
const vegetarischBtn = dietButtons.find(btn => btn.getAttribute('data-diet') === 'Vegetarisch');
|
||
const veganBtn = dietButtons.find(btn => btn.getAttribute('data-diet') === 'Vegan');
|
||
|
||
const hasVegan = activeDiets.has('Vegan');
|
||
const hasVegetarisch = activeDiets.has('Vegetarisch');
|
||
const hasMeatOrFish = meatFishButtons.some(btn => activeDiets.has(btn.getAttribute('data-diet')));
|
||
|
||
// If Vegan is selected, disable everything else
|
||
if (hasVegan) {
|
||
meatFishButtons.forEach(btn => {
|
||
btn.classList.add('disabled');
|
||
});
|
||
if (vegetarischBtn) {
|
||
vegetarischBtn.classList.add('disabled');
|
||
}
|
||
}
|
||
// If Vegetarisch is selected, disable only Fleisch/Fisch
|
||
else if (hasVegetarisch) {
|
||
meatFishButtons.forEach(btn => {
|
||
btn.classList.add('disabled');
|
||
});
|
||
if (veganBtn) {
|
||
veganBtn.classList.remove('disabled');
|
||
}
|
||
}
|
||
// If Fleisch/Fisch is selected, disable both Vegetarisch and Vegan
|
||
else if (hasMeatOrFish) {
|
||
if (vegetarischBtn) {
|
||
vegetarischBtn.classList.add('disabled');
|
||
}
|
||
if (veganBtn) {
|
||
veganBtn.classList.add('disabled');
|
||
}
|
||
}
|
||
// No conflicts, enable everything
|
||
else {
|
||
plantButtons.forEach(btn => {
|
||
btn.classList.remove('disabled');
|
||
});
|
||
meatFishButtons.forEach(btn => {
|
||
btn.classList.remove('disabled');
|
||
});
|
||
}
|
||
}
|
||
|
||
// Category filter interactions: mutually exclusive (radio button behavior).
|
||
filterButtons.forEach(button => {
|
||
button.addEventListener('click', () => {
|
||
const categoryValue = button.getAttribute('data-cat');
|
||
const dietValue = button.getAttribute('data-diet');
|
||
const allergieValue = button.getAttribute('data-allergie');
|
||
|
||
if (categoryValue !== null) {
|
||
// Category filter: exclusive selection
|
||
activeCategory = categoryValue;
|
||
} else if (dietValue !== null) {
|
||
// Diet filter: toggle selection with conflict handling
|
||
const isCurrentlySelected = activeDiets.has(dietValue);
|
||
|
||
if (!isCurrentlySelected) {
|
||
// Adding a diet - handle conflicts
|
||
if (dietValue === 'Vegetarisch') {
|
||
// Vegetarisch removes Fleisch/Fisch but not Vegan
|
||
activeDiets.delete('Fleisch');
|
||
activeDiets.delete('Fisch');
|
||
activeDiets.add('Vegetarisch');
|
||
} else if (dietValue === 'Vegan') {
|
||
// Vegan removes all other options
|
||
activeDiets.delete('Vegetarisch');
|
||
activeDiets.delete('Fleisch');
|
||
activeDiets.delete('Fisch');
|
||
activeDiets.add('Vegan');
|
||
} else if (dietValue === 'Fleisch' || dietValue === 'Fisch') {
|
||
// Fleisch/Fisch remove Vegetarisch/Vegan
|
||
activeDiets.delete('Vegetarisch');
|
||
activeDiets.delete('Vegan');
|
||
activeDiets.add(dietValue);
|
||
}
|
||
} else {
|
||
// Removing a diet
|
||
activeDiets.delete(dietValue);
|
||
}
|
||
|
||
updateDietAvailability();
|
||
} else if (allergieValue !== null) {
|
||
// Allergie filter: toggle selection
|
||
if (activeAllergies.has(allergieValue)) {
|
||
activeAllergies.delete(allergieValue);
|
||
} else {
|
||
activeAllergies.add(allergieValue);
|
||
}
|
||
}
|
||
|
||
applyFilters();
|
||
});
|
||
});
|
||
|
||
// Secondary filter interactions.
|
||
if (locationFilter) {
|
||
locationFilter.addEventListener('change', applyFilters);
|
||
}
|
||
|
||
if (dateFilter) {
|
||
dateFilter.addEventListener('change', applyFilters);
|
||
|
||
// Make calendar icon clickable to focus the date input
|
||
const calendarIcon = document.querySelector('.calendar-icon');
|
||
if (calendarIcon) {
|
||
calendarIcon.addEventListener('click', () => {
|
||
dateFilter.focus();
|
||
dateFilter.click();
|
||
});
|
||
}
|
||
}
|
||
|
||
// Clear all filters button
|
||
const clearAllFiltersBtn = document.getElementById('clear-all-filters');
|
||
if (clearAllFiltersBtn) {
|
||
clearAllFiltersBtn.addEventListener('click', () => {
|
||
activeCategory = 'ALLE';
|
||
activeDiets.clear();
|
||
activeAllergies.clear();
|
||
|
||
if (locationFilter) {
|
||
locationFilter.value = 'ALLE_ORTE';
|
||
}
|
||
if (dateFilter) {
|
||
dateFilter.value = '';
|
||
}
|
||
|
||
updateDietAvailability();
|
||
applyFilters();
|
||
});
|
||
}
|
||
|
||
// Info button modal behavior
|
||
const infoButton = document.getElementById('info-button');
|
||
const infoModal = document.getElementById('info-modal');
|
||
const modalClose = infoModal?.querySelector('.modal-close');
|
||
|
||
if (infoButton && infoModal) {
|
||
infoButton.addEventListener('click', () => {
|
||
infoModal.classList.add('show');
|
||
});
|
||
}
|
||
|
||
if (modalClose && infoModal) {
|
||
modalClose.addEventListener('click', () => {
|
||
infoModal.classList.remove('show');
|
||
});
|
||
}
|
||
|
||
if (infoModal) {
|
||
infoModal.addEventListener('click', (event) => {
|
||
if (event.target === infoModal) {
|
||
infoModal.classList.remove('show');
|
||
}
|
||
});
|
||
}
|
||
|
||
// Auto-open info modal on first login
|
||
if (currentUser && infoModal) {
|
||
const userInfoModalKey = getInfoModalShownKeyForUser(currentUser);
|
||
const hasShownInfoModal = localStorage.getItem(userInfoModalKey);
|
||
if (!hasShownInfoModal) {
|
||
infoModal.classList.add('show');
|
||
localStorage.setItem(userInfoModalKey, 'true');
|
||
}
|
||
}
|
||
|
||
// Kick off initial load/render cycle.
|
||
fetchEvents();
|
||
});
|
||
|
||
// Modal closing helper functions
|
||
function closeRegisterModal() {
|
||
const modal = document.getElementById('register-confirm-modal');
|
||
if (modal) {
|
||
modal.classList.remove('show');
|
||
}
|
||
}
|
||
|
||
function closeUnregisterModal() {
|
||
const modal = document.getElementById('unregister-confirm-modal');
|
||
if (modal) {
|
||
modal.classList.remove('show');
|
||
}
|
||
}
|
||
|
||
function closeInfoModal() {
|
||
const modal = document.getElementById('info-modal');
|
||
if (modal) {
|
||
modal.classList.remove('show');
|
||
}
|
||
}
|