From 60b8a5fd3ace4959e6dade7cf8f90c2629c92533 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Estelle=20K=C3=B6hler?= Date: Thu, 23 Apr 2026 11:30:50 +0200 Subject: [PATCH] =?UTF-8?q?Pop-up=20auch=20bei=20Detailseite=20hinzuf?= =?UTF-8?q?=C3=BCgen.=20Text=20angepasst.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- event_detail.html | 32 +++ js/event_detail.js | 618 ++++++++++++++++----------------------------- 2 files changed, 247 insertions(+), 403 deletions(-) diff --git a/event_detail.html b/event_detail.html index a13ab96..e6bf43e 100644 --- a/event_detail.html +++ b/event_detail.html @@ -38,6 +38,38 @@ + + + +
diff --git a/js/event_detail.js b/js/event_detail.js index a7082ad..a2ec1c1 100644 --- a/js/event_detail.js +++ b/js/event_detail.js @@ -3,16 +3,13 @@ const CURRENT_USER_KEY = 'socialCookingCurrentUser'; const USERS_STORAGE_KEY = 'socialCookingUsers'; const REGISTRATION_STORAGE_KEY = 'socialCookingRegistrations'; - // ------------------------------------------------------------- - // DOM entry point and shared asset path. - // ------------------------------------------------------------- + const detailcontainer = document.getElementById('detail-view'); const locationIconPath = 'assets/icon_location.svg'; const calendarIconPath = 'assets/icon_calendar.svg'; const gastIconPath = 'assets/icon_gast.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')); @@ -62,14 +59,10 @@ } function getUserDisplayName(user) { - if (!user) { - return ''; - } - + 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(); } @@ -85,10 +78,7 @@ Object.entries(registrationMap || {}).forEach(([email, ids]) => { const isRegisteredForEvent = Array.isArray(ids) && ids.map(id => Number(id)).includes(Number(event.id)); - - if (!isRegisteredForEvent) { - return; - } + if (!isRegisteredForEvent) return; const user = usersByEmail.get(String(email || '').trim().toLowerCase()); const displayName = getUserDisplayName(user) || String(email || '').trim(); @@ -105,19 +95,9 @@ function getParticipantNameForViewer(name, canSeeLastName) { const rawName = String(name || '').trim(); - if (!rawName) { - return ''; - } - - if (canSeeLastName) { - return rawName; - } - - // Bei E-Mail-Fallback nur den lokalen Teil anzeigen. - if (rawName.includes('@')) { - return rawName.split('@')[0].trim() || rawName; - } - + if (!rawName) return ''; + if (canSeeLastName) return rawName; + if (rawName.includes('@')) return rawName.split('@')[0].trim() || rawName; return rawName.split(/\s+/)[0]; } @@ -126,153 +106,98 @@ } function parseEventDateTime(event) { - if (!event?.date) { - return null; - } - + 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; + let year, month, 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 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; - } - + if (!localizedMatch) return null; day = Number(localizedMatch[1]); month = monthMap[localizedMatch[2]]; year = Number(localizedMatch[3]); - - if (!month) { - return null; - } + 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; - } - + if (!eventDateTime || Number.isNaN(eventDateTime.getTime())) return false; const msUntilStart = eventDateTime.getTime() - Date.now(); - const twentyfourHoursInMs = 24 * 60 * 60 * 1000; - - return msUntilStart <= twentyfourHoursInMs; + return msUntilStart <= 24 * 60 * 60 * 1000; } - // Abmeldefrist: 1 Tag (24 h) vor Eventstart. function getDeregistrationInfo(event) { const eventDateTime = parseEventDateTime(event); - if (!eventDateTime || Number.isNaN(eventDateTime.getTime())) { - return { daysLeft: null, isClosed: false }; - } - + if (!eventDateTime || Number.isNaN(eventDateTime.getTime())) return { daysLeft: null, isClosed: false }; const oneDayMs = 24 * 60 * 60 * 1000; - const deadlineMs = eventDateTime.getTime() - oneDayMs; - const msUntilDeadline = deadlineMs - Date.now(); - - if (msUntilDeadline <= 0) { - return { daysLeft: 0, isClosed: true }; - } - - const daysLeft = Math.ceil(msUntilDeadline / oneDayMs); - return { daysLeft, isClosed: false }; + const msUntilDeadline = (eventDateTime.getTime() - oneDayMs) - Date.now(); + if (msUntilDeadline <= 0) return { daysLeft: 0, isClosed: true }; + return { daysLeft: Math.ceil(msUntilDeadline / oneDayMs), isClosed: false }; } - // Adresse ist nur im 24h-Fenster VOR Eventstart sichtbar. function isAddressVisibleWindow(event) { const eventDateTime = parseEventDateTime(event); - if (!eventDateTime || Number.isNaN(eventDateTime.getTime())) { - return false; - } - + if (!eventDateTime || Number.isNaN(eventDateTime.getTime())) return false; const msUntilStart = eventDateTime.getTime() - Date.now(); - const twentyfourHoursInMs = 24 * 60 * 60 * 1000; - - return msUntilStart >= 0 && msUntilStart <= twentyfourHoursInMs; + return msUntilStart >= 0 && msUntilStart <= 24 * 60 * 60 * 1000; } - 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; - } - + 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 für ältere Datensätze ohne hostEmail. + 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); } - // Prüft, ob der aktuelle Benutzer bereits in der Teilnehmerliste des Events steht. function isUserListedInEventParticipants(event, user) { - if (!event || !user || !Array.isArray(event.participants)) { - return false; - } - + if (!event || !user || !Array.isArray(event.participants)) return false; const participantSet = new Set( - event.participants - .map(name => String(name || '').trim().toLowerCase()) - .filter(Boolean) + 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(); - + const userFullName = `${String(user.vorname || '').trim()} ${String(user.nachname || '').trim()}`.trim().toLowerCase(); return Boolean( (userFirstName && participantSet.has(userFirstName)) || (userFullName && participantSet.has(userFullName)) ); } + 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 monthLabel = labels[match[2]]; + return monthLabel ? `${Number(match[1])}. ${monthLabel} ${match[3]}` : dateString; + } + + function formatEventTime(timeString) { + return timeString.replace('UHR', 'Uhr').trim(); + } + + function getDietLabel(diet) { + const labels = { FLEISCH:'Fleisch', FISCH:'Fisch', VEGGIE:'Vegetarisch', VEGAN:'Vegan' }; + return labels[diet] || diet; + } + // Fetch data source and resolve the matching event record. try { const response = await fetch('data/events.json'); @@ -289,62 +214,11 @@ 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 = { - FLEISCH: 'Fleisch', - FISCH: 'Fisch', - VEGGIE: 'Vegetarisch', - VEGAN: 'Vegan' - }; - - 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.']; @@ -352,28 +226,23 @@ ? event.menu : ['Menü wird in Kürze bekannt gegeben.']; const specifications = Array.isArray(event.specifications) && event.specifications.length > 0 - ? event.specifications - : []; + ? event.specifications : []; const registrationMap = getRegistrationMap(); const participants = getResolvedParticipants(event, registrationMap); const isOwnEvent = isEventOwnedByCurrentUser(event, currentUser); const participantNamesForView = participants .map(name => getParticipantNameForViewer(name, isOwnEvent)) .filter(Boolean); - const galleryImages = Array.isArray(event.gallery) - ? event.gallery.filter(Boolean) - : []; + const galleryImages = Array.isArray(event.gallery) ? event.gallery.filter(Boolean) : []; const galleryMarkup = galleryImages.length > 0 - ? ` - - ` - : ''; + ? `` : ''; + const visibleParticipants = participantNamesForView.slice(0, 6); const remainingParticipants = Math.max(0, participantNamesForView.length - visibleParticipants.length); const totalGuests = Number.isFinite(event.spots) ? event.spots : 0; @@ -383,245 +252,207 @@ const isRegistrationClosed = isRegistrationClosedForEvent(event); const deregInfo = getDeregistrationInfo(event); const userRegistrations = currentUser?.email && Array.isArray(registrationMap[currentUser.email]) - ? registrationMap[currentUser.email].map(id => Number(id)) - : []; + ? 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 - ? (deregInfo.isClosed ? 'Abmeldung geschlossen' : 'Abmelden') - : isRegistrationClosed - ? 'Anmeldung geschlossen' - : 'Anmelden'; + + const actionButtonLabel = isOwnEvent ? 'Dein Event!' + : !currentUser ? 'Einloggen' + : isRegistered ? (deregInfo.isClosed ? 'Abmeldung geschlossen' : 'Abmelden') + : isRegistrationClosed ? 'Anmeldung geschlossen' + : 'Anmelden'; const actionButtonDisabled = isOwnEvent || (!isRegistered && (isFull || isRegistrationClosed)) || (isRegistered && deregInfo.isClosed); - const actionButtonVariantClass = isOwnEvent - ? ' button-primary-eigener-event' - : isRegistered - ? ' button-primary-abmelden ' - : isRegistrationClosed - ? ' button-primary ' - : ' button-primary '; + const actionButtonVariantClass = isOwnEvent ? ' button-primary-eigener-event' + : isRegistered ? ' button-primary-abmelden ' + : ' button-primary '; + const shouldRevealAddress = Boolean(event.address) && isAddressVisibleWindow(event) && hasAddressAccess; const addressPanelMarkup = shouldRevealAddress - ? ` -
-

Adresse

-

${event.address}

-
- ` - : ` -
-

Adresse

-

Vielen Dank für die Anmeldung! Die Adresse für diesen Event wird 24 Stunden vorher genau hier sichtbar sein.

-
- `; + ? `

Adresse

${event.address}

` + : `

Adresse

Vielen Dank für die Anmeldung! Die Adresse für diesen Event wird 24 Stunden vorher genau hier sichtbar sein.

`; + const detailChips = [ `${eventCategory}`, ...event.diet.split(', ').filter(d => d.trim() && d !== 'Keine Angabe').map(d => `${getDietLabel(d.trim())}`), ...specifications.map(item => `${item}`) ].join(''); - // Render complete detail page layout including: - // hero metadata, host card, menu, participants, gallery and sticky action bar. detailcontainer.innerHTML = ` - -
-
- - ${event.location} - - - - ${displayDate} | ${displayTime} - - - - ${confirmedGuests}/${totalGuests} - -
-

${event.title}

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

${paragraph}

`).join('')} -
- -
-

Menu

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

Teilnehmer

- -
-
- ${visibleParticipants.map(name => `${name.charAt(0).toUpperCase()}`).join('')} - ${remainingParticipants > 0 ? `+${remainingParticipants}` : ''} -
- -
- - ${addressPanelMarkup} -
- - ${galleryMarkup} -
- -
-
- - - - ${event.location} - - - - ${displayDate} | ${displayTime} - - - - - ${confirmedGuests}/${totalGuests} - - - - ${event.title} -
- -
- ${isFull ? ` - - ` : ` - - `} - - ${isRegistered && deregInfo.daysLeft !== null ? ` - - ${deregInfo.isClosed - ? 'Abmeldefrist abgelaufen' - : deregInfo.daysLeft === 1 - ? 'Noch 1 Tag zur Abmeldung' - : `Noch ${deregInfo.daysLeft} Tage zur Abmeldung`} - - ` : ''} -
-
- -