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}
${hostMessage.map(paragraph => `${paragraph}
`).join('')}
Menue
${visibleParticipants.map(name => `${name.charAt(0).toUpperCase()}`).join('')}
${remainingParticipants > 0 ? `+${remainingParticipants}` : ''}
${addressPanelMarkup}
${galleryImages.slice(0, 9).map((img, index) => `
`).join('')}
${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();
}
});
}
}
});