Social_Cooking/js/event_detail.js
viiivo 1efa4dcd39 feat: Implement user profile management and navigation updates
- Added a new profile page (my_profil.html) for users to manage their events and personal information.
- Introduced a new CSS file (my_profil.css) for styling the profile page.
- Created a JavaScript file (my_profil.js) to handle profile data retrieval, event registration management, and form submission.
- Updated navigation logic (navigation.js) to dynamically display login/signup or event management links based on user authentication status.
- Enhanced event creation and detail pages to support user-specific actions (registration/unregistration).
- Improved login and signup processes to handle user data more robustly, including fallback user creation.
- Refactored event overview to show user-specific events and registrations.
- Added error handling and validation for user input in profile management.
2026-04-10 16:28:44 +02:00

360 lines
16 KiB
JavaScript

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));
}
// 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);
}
// 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 = "<h1>Event wurde nicht gefunden.</h1><a href='event_overview.html'>Zurück zur Übersicht</a>";
}
} 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 remainingParticipants = Math.max(0, participants.length - visibleParticipants.length);
const totalGuests = Number.isFinite(event.spots) ? event.spots : 0;
const confirmedGuests = participants.length;
const freePlaces = Math.max(0, totalGuests - confirmedGuests);
const isFull = freePlaces === 0;
const isOwnEvent = isEventOwnedByCurrentUser(event, currentUser);
const registrationMap = getRegistrationMap();
const userRegistrations = currentUser?.email && Array.isArray(registrationMap[currentUser.email])
? registrationMap[currentUser.email].map(id => Number(id))
: [];
const isRegistered = userRegistrations.includes(Number(event.id));
const actionButtonLabel = isOwnEvent ? 'Dein Event' : !currentUser ? 'Einloggen' : isRegistered ? 'Abmelden' : 'Anmelden';
const actionButtonDisabled = isOwnEvent || (!isRegistered && isFull);
const detailChips = [
`<span class="event-tag">${eventCategory}</span>`,
`<span class="event-tag">${dietLabel}</span>`,
...specifications.map(item => `<span class="event-tag">${item}</span>`)
].join('');
// Render complete detail page layout including:
// hero metadata, host card, menu, participants, gallery and sticky action bar.
detailContainer.innerHTML = `
<div class="detail-page">
<a class="detail-back" href="event_overview.html">
<span aria-hidden="true">&lsaquo;</span>
Alle Events
</a>
<section class="detail-hero">
<div class="detail-top-row">
<span class="event-location">
<img src="${locationIconPath}" alt="">
${event.location}
</span>
<p class="event-date-time">${displayDate} | ${displayTime} | ${confirmedGuests}/${totalGuests} Gaeste</p>
</div>
<h1 class="detail-title">${event.title}</h1>
<div class="event-meta-row detail-chip-row">
${detailChips}
</div>
</section>
<section class="detail-content-grid">
<div class="detail-side-stack">
<article class="host-card detail-panel">
<header class="host-header">
<span class="host-avatar">${hostInitial}</span>
<span class="host-name">${hostName}</span>
<span class="host-role">Host</span>
</header>
${hostMessage.map(paragraph => `<p>${paragraph}</p>`).join('')}
</article>
<article class="detail-panel detail-panel-compact">
<h2 class="detail-section-title">Menue</h2>
<ul class="detail-menu-list">
${menuItems.map(item => `<li>${item}</li>`).join('')}
</ul>
</article>
<article class="detail-panel detail-panel-compact">
<div class="detail-participants-head">
<h2 class="detail-section-title">Teilnehmer</h2>
<a href="#" class="detail-participants-link">Alle ansehen</a>
</div>
<div class="detail-avatar-row">
${visibleParticipants.map(name => `<span class="participant-avatar">${name.charAt(0).toUpperCase()}</span>`).join('')}
${remainingParticipants > 0 ? `<span class="participant-more">+${remainingParticipants}</span>` : ''}
</div>
</article>
</div>
<div class="detail-gallery detail-gallery-large">
${galleryImages.slice(0, 9).map((img, index) => `
<button class="detail-gallery-item" type="button" aria-label="Bild ${index + 1} gross anzeigen" data-fullsrc="${img}">
<img src="${img}" alt="${event.title} Bild ${index + 1}" class="detail-gallery-image">
</button>
`).join('')}
</div>
</section>
<section class="detail-action-bar">
<div class="detail-action-summary">
<small class="detail-action-meta">
<span class="event-location detail-action-location">
<img src="${locationIconPath}" alt="">
${event.location}
</span>
<span class="detail-action-meta-text">| ${displayDate} | ${displayTime} | ${confirmedGuests}/${totalGuests} Gaeste</span>
</small>
<strong>${event.title}</strong>
</div>
<div class="detail-action-buttons">
<span class="detail-spots-pill${isFull ? ' detail-spots-pill-full' : ''}">
${isFull ? 'AUSGEBUCHT' : `${freePlaces} Plaetze frei`}
</span>
<button class="detail-primary-btn${isOwnEvent ? ' detail-primary-btn-own' : ''}" type="button" data-register-button ${actionButtonDisabled ? 'disabled' : ''}>
${actionButtonLabel}
</button>
</div>
</section>
<div class="detail-lightbox" aria-hidden="true">
<div class="detail-lightbox-backdrop" data-close-lightbox="true"></div>
<figure class="detail-lightbox-content" role="dialog" aria-modal="true" aria-label="Bildansicht">
<button class="detail-lightbox-close" type="button" aria-label="Schliessen">&times;</button>
<img class="detail-lightbox-image" src="" alt="Grossansicht Eventbild">
</figure>
</div>
</div>
`;
// ---------------------------------------------------------
// 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]');
// 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) {
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();
}
});
}
}
});