document.addEventListener('DOMContentLoaded', async () => { const EVENTS_STORAGE_KEY = 'socialCookingEvents'; const CURRENT_USER_KEY = 'socialCookingCurrentUser'; const REGISTRATION_STORAGE_KEY = 'socialCookingRegistrations'; // ------------------------------------------------------------- // DOM entry point and shared asset path. // ------------------------------------------------------------- const detailContainer = document.getElementById('detail-view'); const locationIconPath = 'assets/location-pin.svg'; const currentUser = getCurrentUser(); // Read event id from query string (detail page deep-link support). const params = new URLSearchParams(window.location.search); const eventId = parseInt(params.get('id')); if (!eventId) { window.location.href = 'event_overview.html'; return; } 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 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 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 setRegistrationMap(registrationMap) { localStorage.setItem(REGISTRATION_STORAGE_KEY, JSON.stringify(registrationMap)); } 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, FEB: 2, 'MÄR': 3, MRZ: 3, APR: 4, MAI: 5, JUN: 6, JUL: 7, AUG: 8, SEP: 9, OKT: 10, NOV: 11, DEZ: 12 }; const localizedMatch = dateValue.match(/^(\d{1,2})\.\s*([A-ZÄÖÜ]{3})\.\s*(\d{4})$/); if (!localizedMatch) { return null; } day = Number(localizedMatch[1]); month = monthMap[localizedMatch[2]]; 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); } function isRegistrationClosedForEvent(event) { const eventDateTime = parseEventDateTime(event); if (!eventDateTime || Number.isNaN(eventDateTime.getTime())) { return false; } const msUntilStart = eventDateTime.getTime() - Date.now(); const twelveHoursInMs = 12 * 60 * 60 * 1000; return msUntilStart <= twelveHoursInMs; } // Adresse ist nur im 12h-Fenster VOR Eventstart sichtbar. function isAddressVisibleWindow(event) { const eventDateTime = parseEventDateTime(event); if (!eventDateTime || Number.isNaN(eventDateTime.getTime())) { return false; } const msUntilStart = eventDateTime.getTime() - Date.now(); const twelveHoursInMs = 12 * 60 * 60 * 1000; return msUntilStart >= 0 && msUntilStart <= twelveHoursInMs; } 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); } // Ermittelt, ob das Event vom aktuell eingeloggten Benutzer erstellt wurde. 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; } // Fallback fuer aeltere Datensaetze ohne hostEmail. const userFirstName = String(user.vorname || '').trim().toLowerCase(); const hostName = String(event.host?.name || '').trim().toLowerCase(); return Boolean(userFirstName && hostName && userFirstName === hostName); } // Prueft, ob der aktuelle Benutzer bereits in der Teilnehmerliste des Events steht. function isUserListedInEventParticipants(event, user) { if (!event || !user || !Array.isArray(event.participants)) { return false; } const participantSet = new Set( event.participants .map(name => String(name || '').trim().toLowerCase()) .filter(Boolean) ); const userFirstName = String(user.vorname || '').trim().toLowerCase(); const userFullName = `${String(user.vorname || '').trim()} ${String(user.nachname || '').trim()}` .trim() .toLowerCase(); return Boolean( (userFirstName && participantSet.has(userFirstName)) || (userFullName && participantSet.has(userFullName)) ); } // Fetch data source and resolve the matching event record. try { const response = await fetch('data/events.json'); const apiEvents = await response.json(); const allEvents = [...getStoredEvents(), ...apiEvents]; const event = allEvents.find(e => e.id === eventId); if (event) { renderDetailPage(event); } else { detailContainer.innerHTML = "

Event wurde nicht gefunden.

Zurück zur Übersicht"; } } catch (error) { console.error("Fehler beim Laden der Details:", error); } // Format localized date token into full readable date. function formatEventDate(dateString) { 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 casing for UI consistency. function formatEventTime(timeString) { return timeString.replace('UHR', 'Uhr').trim(); } // Map diet keys to readable labels while keeping unknown values untouched. function getDietLabel(diet) { const labels = { VEGGIE: 'Vegetarisch', VEGAN: 'Vegan', FLEISCH: 'Fleisch', FISCH: 'Fisch' }; return labels[diet] || diet; } // Compose and inject the full detail UI for a single event. function renderDetailPage(event) { // Core display values and resilient fallbacks for optional data fields. const displayDate = formatEventDate(event.date); const displayTime = formatEventTime(event.time); const dietLabel = getDietLabel(event.diet); const eventCategory = event.category || 'EVENT'; const hostName = event.host?.name || 'Host'; const hostInitial = (event.host?.initial || hostName.charAt(0) || 'H').charAt(0).toUpperCase(); const hostMessage = Array.isArray(event.hostMessage) && event.hostMessage.length > 0 ? event.hostMessage : ['Der Host hat für dieses Event noch keine Nachricht hinterlegt.']; const menuItems = Array.isArray(event.menu) && event.menu.length > 0 ? event.menu : ['Menü wird in Kuerze bekannt gegeben.']; const specifications = Array.isArray(event.specifications) && event.specifications.length > 0 ? event.specifications : []; const participants = Array.isArray(event.participants) ? event.participants : []; const galleryImages = Array.isArray(event.gallery) && event.gallery.length > 0 ? event.gallery : [event.image, event.image, event.image]; const visibleParticipants = participants.slice(0, 6); const registrationMap = getRegistrationMap(); const extraRegistrations = countRegistrationsForEvent(registrationMap, event.id); const remainingParticipants = Math.max(0, participants.length + extraRegistrations - visibleParticipants.length); const totalGuests = Number.isFinite(event.spots) ? event.spots : 0; const confirmedGuests = participants.length + extraRegistrations; const freePlaces = Math.max(0, totalGuests - confirmedGuests); const isFull = freePlaces === 0; const isRegistrationClosed = isRegistrationClosedForEvent(event); const isOwnEvent = isEventOwnedByCurrentUser(event, currentUser); const userRegistrations = currentUser?.email && Array.isArray(registrationMap[currentUser.email]) ? registrationMap[currentUser.email].map(id => Number(id)) : []; const isRegistered = userRegistrations.includes(Number(event.id)); const isListedParticipant = isUserListedInEventParticipants(event, currentUser); const hasAddressAccess = isRegistered || isListedParticipant; const actionButtonLabel = isOwnEvent ? 'Dein Event!' : !currentUser ? 'Einloggen' : isRegistered ? 'Abmelden' : isRegistrationClosed ? 'Anmeldung geschlossen' : 'Anmelden'; const actionButtonDisabled = isOwnEvent || (!isRegistered && (isFull || isRegistrationClosed)); const actionButtonVariantClass = isOwnEvent ? ' detail-primary-btn-own' : isRegistered ? ' detail-primary-btn-danger' : isRegistrationClosed ? ' detail-primary-btn-danger' : ' detail-primary-btn-register'; const shouldRevealAddress = Boolean(event.address) && isAddressVisibleWindow(event) && hasAddressAccess; const addressPanelMarkup = shouldRevealAddress ? `

Adresse

${event.address}

` : ''; const detailChips = [ `${eventCategory}`, `${dietLabel}`, ...specifications.map(item => `${item}`) ].join(''); // Render complete detail page layout including: // hero metadata, host card, menu, participants, gallery and sticky action bar. detailContainer.innerHTML = `
Alle Events
${event.location}

${displayDate} | ${displayTime} | ${confirmedGuests}/${totalGuests} Gaeste

${event.title}

${detailChips}
${hostInitial} ${hostName} Host
${hostMessage.map(paragraph => `

${paragraph}

`).join('')}

Menue

    ${menuItems.map(item => `
  • ${item}
  • `).join('')}

Teilnehmer

Alle ansehen
${visibleParticipants.map(name => `${name.charAt(0).toUpperCase()}`).join('')} ${remainingParticipants > 0 ? `+${remainingParticipants}` : ''}
${addressPanelMarkup}
${event.location} | ${displayDate} | ${displayTime} | ${confirmedGuests}/${totalGuests} Gaeste ${event.title}
${isFull ? 'AUSGEBUCHT' : `${freePlaces} Plaetze frei`}
`; // --------------------------------------------------------- // Lightbox behavior for gallery images: // open on image click, close via backdrop, close button or ESC. // --------------------------------------------------------- const lightbox = detailContainer.querySelector('.detail-lightbox'); const lightboxImage = detailContainer.querySelector('.detail-lightbox-image'); const lightboxClose = detailContainer.querySelector('.detail-lightbox-close'); const galleryButtons = detailContainer.querySelectorAll('.detail-gallery-item'); const registerButton = detailContainer.querySelector('[data-register-button]'); // Harte Absicherung: Eigene Events sind auf der Detailseite immer deaktiviert. if (registerButton && isOwnEvent) { registerButton.disabled = true; registerButton.textContent = 'Dein Event!'; registerButton.setAttribute('aria-disabled', 'true'); } // Anmeldung toggeln und im lokalen Registrierungs-Store persistieren. if (registerButton) { registerButton.addEventListener('click', () => { if (isOwnEvent) { return; } if (!currentUser || !currentUser.email) { window.location.href = 'login.html'; return; } const nextRegistrationMap = getRegistrationMap(); const currentList = Array.isArray(nextRegistrationMap[currentUser.email]) ? nextRegistrationMap[currentUser.email].map(id => Number(id)) : []; const registrationSet = new Set(currentList); if (registrationSet.has(Number(event.id))) { registrationSet.delete(Number(event.id)); } else if (!isFull && !isRegistrationClosed) { registrationSet.add(Number(event.id)); } nextRegistrationMap[currentUser.email] = Array.from(registrationSet); setRegistrationMap(nextRegistrationMap); // Re-Render aktualisiert Buttonzustand und CTA ohne Seitenreload. renderDetailPage(event); }); } // Central close helper to keep all close paths consistent. function closeLightbox() { if (!lightbox) { return; } lightbox.classList.remove('is-open'); lightbox.setAttribute('aria-hidden', 'true'); } if (lightbox && lightboxImage) { // Open with selected image source. galleryButtons.forEach(button => { button.addEventListener('click', () => { const imageSrc = button.getAttribute('data-fullsrc'); if (!imageSrc) { return; } lightboxImage.src = imageSrc; lightbox.classList.add('is-open'); lightbox.setAttribute('aria-hidden', 'false'); }); }); // Close when user clicks on backdrop. lightbox.addEventListener('click', event => { const target = event.target; if (target instanceof HTMLElement && target.hasAttribute('data-close-lightbox')) { closeLightbox(); } }); // Close via dedicated icon/button. lightboxClose?.addEventListener('click', closeLightbox); // Close with keyboard for accessibility. document.addEventListener('keydown', event => { if (event.key === 'Escape') { closeLightbox(); } }); } } });