Social_Cooking/js/my_profil.js
2026-04-26 10:49:53 +02:00

848 lines
32 KiB
JavaScript

document.addEventListener('DOMContentLoaded', () => {
const EVENTS_STORAGE_KEY = 'socialCookingEvents';
const USERS_STORAGE_KEY = 'socialCookingUsers';
const CURRENT_USER_KEY = 'socialCookingCurrentUser';
const REGISTRATION_STORAGE_KEY = 'socialCookingRegistrations';
// Zentrale DOM-Referenzen für klare, testbare Funktionen.
const loggedOutState = document.getElementById('logged-out-state');
const loggedInContent = document.getElementById('logged-in-content');
const profileHeadline = document.getElementById('headline');
const profileSubline = document.getElementById('profile-subline');
const logoutButton = document.getElementById('logout-button');
const profileTabButtons = Array.from(document.querySelectorAll('[data-category-item]'));
const profileTabPanels = Array.from(document.querySelectorAll('[data-profile-panel]'));
const myEventsCount = document.getElementById('my-events-count');
const myEventsBtnCount = document.getElementById('btn-my-events-count');
const myRegistrationsCount = document.getElementById('my-registrations-count');
const myRegistrationsBtnCount = document.getElementById('btn-my-registrations-count');
const myEventsList = document.getElementById('my-events-list');
const myRegistrationsList = document.getElementById('my-registrations-list');
const profileForm = document.getElementById('profile-form');
const profileFeedback = document.getElementById('profile-feedback');
const vornameInput = document.getElementById('vorname');
const nachnameInput = document.getElementById('nachname');
const emailInput = document.getElementById('email');
const passwortInput = document.getElementById('passwort');
let currentUser = getCurrentUser();
let allEvents = [];
init();
async function init() {
if (!currentUser) {
renderLoggedOutState();
return;
}
renderLoggedInState(currentUser);
bindFormHandlers();
activateProfileTab('hosting');
allEvents = await loadAllEvents();
renderMyEvents(allEvents, currentUser);
renderMyRegistrations(allEvents, currentUser);
}
// Liest den aktuell eingeloggten Benutzer robust aus dem Storage.
function getCurrentUser() {
try {
const raw = localStorage.getItem(CURRENT_USER_KEY);
return raw ? JSON.parse(raw) : null;
} catch (error) {
console.error('Der aktuelle Benutzer konnte nicht geladen werden.', error);
return null;
}
}
// Liest lokal erstellte Events aus dem Storage.
function getStoredEvents() {
try {
const raw = localStorage.getItem(EVENTS_STORAGE_KEY);
return raw ? JSON.parse(raw) : [];
} catch (error) {
console.error('Lokale Events konnten nicht gelesen werden.', error);
return [];
}
}
// Liest den Anmeldestatus pro Benutzer-E-Mail.
function getRegistrationMap() {
try {
const raw = localStorage.getItem(REGISTRATION_STORAGE_KEY);
return raw ? JSON.parse(raw) : {};
} catch (error) {
console.error('Anmeldedaten konnten nicht gelesen werden.', error);
return {};
}
}
// Schreibt den gesamten Registrierungszustand in localStorage.
function setRegistrationMap(registrationMap) {
localStorage.setItem(REGISTRATION_STORAGE_KEY, JSON.stringify(registrationMap));
}
// Schreibt die lokal erstellten Events in den Storage.
function setStoredEvents(events) {
localStorage.setItem(EVENTS_STORAGE_KEY, JSON.stringify(events));
}
// Fuehrt JSON-Daten und lokal erstellte Events in einer Liste zusammen.
async function loadAllEvents() {
try {
const response = await fetch('data/events.json');
const apiEvents = await response.json();
return [...getStoredEvents(), ...apiEvents];
} catch (error) {
console.error('Events konnten nicht geladen werden.', error);
return getStoredEvents();
}
}
// Schaltet in den ausgeloggten Zustand und blendet geschuetzte Inhalte aus.
function renderLoggedOutState() {
loggedOutState.classList.remove('hidden');
loggedInContent.classList.add('hidden');
logoutButton.classList.add('hidden');
profileHeadline.textContent = 'Mein Profil';
profileSubline.textContent = 'Bitte logge dich ein, um deinen Bereich zu sehen.';
}
// Füllt Überschriften und Formular mit den aktuellen Benutzerdaten.
function renderLoggedInState(user) {
loggedOutState.classList.add('hidden');
loggedInContent.classList.remove('hidden');
logoutButton.classList.remove('hidden');
profileHeadline.textContent = `Hallo ${user.vorname || 'Gast'}`;
profileSubline.textContent = 'Hier kannst du deine Events und Anmeldungen verwalten.';
vornameInput.value = user.vorname || '';
nachnameInput.value = user.nachname || '';
emailInput.value = user.email || '';
}
// Bindet Submit-, Input- und Logout-Verhalten an die Profilseite.
function bindFormHandlers() {
profileForm.addEventListener('submit', handleProfileSubmit);
myRegistrationsList.addEventListener('click', handleRegistrationListClick);
myEventsList.addEventListener('click', handleHostedListClick);
profileTabButtons.forEach(button => {
button.addEventListener('click', () => {
const tabName = button.getAttribute('data-category-item');
if (!tabName) {
return;
}
activateProfileTab(tabName);
});
});
[vornameInput, nachnameInput, emailInput, passwortInput].forEach(input => {
input.addEventListener('input', () => {
input.parentElement.classList.remove('has-error');
profileFeedback.textContent = '';
});
});
logoutButton.addEventListener('click', () => {
const logoutModal = document.getElementById('logoutModal');
logoutModal.classList.add('show');
document.body.style.overflow = 'hidden';
});
}
// Globale Funktionen für das Logout-Modal.
window.closeLogoutModal = function() {
const logoutModal = document.getElementById('logoutModal');
logoutModal.classList.remove('show');
document.body.style.overflow = 'auto';
};
window.confirmLogout = function() {
localStorage.removeItem(CURRENT_USER_KEY);
window.location.href = 'index.html';
};
// Reagiert auf Aktionen in der Liste "Meine Events" per Event Delegation.
function handleHostedListClick(event) {
const target = event.target;
if (!(target instanceof HTMLElement)) {
return;
}
const cancelButton = target.closest('[data-cancel-event-id]');
if (cancelButton && currentUser?.email) {
const eventId = Number(cancelButton.getAttribute('data-cancel-event-id'));
if (Number.isFinite(eventId)) {
openCancelEventModal(eventId);
}
return;
}
if (target.closest('a, button')) {
return;
}
const card = target.closest('[data-event-id]');
if (!card) {
return;
}
const eventId = Number(card.getAttribute('data-event-id'));
if (!Number.isFinite(eventId)) {
return;
}
window.location.href = `event_detail.html?id=${eventId}`;
}
// Schaltet den sichtbaren Profilbereich per Tabname um.
function activateProfileTab(tabName) {
profileTabButtons.forEach(button => {
const isActive = button.getAttribute('data-category-item') === tabName;
button.classList.toggle('is-active', isActive);
button.setAttribute('aria-selected', isActive ? 'true' : 'false');
});
profileTabPanels.forEach(panel => {
const isActive = panel.getAttribute('data-profile-panel') === tabName;
panel.classList.toggle('hidden', !isActive);
});
if (tabName === 'teilnehmen') {
const registeredEvents = getMyRegisteredEvents(allEvents, currentUser);
markRegistrationsAsRead(registeredEvents);
}
}
// Reagiert auf Aktionen in der Liste "Meine Anmeldungen" per Event Delegation.
function handleRegistrationListClick(event) {
const target = event.target;
if (!(target instanceof HTMLElement)) {
return;
}
const unregisterButton = target.closest('[data-unregister-id]');
if (unregisterButton) {
if (!currentUser?.email) return;
const eventId = Number(unregisterButton.getAttribute('data-unregister-id'));
if (!Number.isFinite(eventId)) return;
const modal = document.getElementById('unregister-confirm-modal');
if (modal) modal.classList.add('show');
document.getElementById('confirm-unregister-btn').onclick = () => {
modal.classList.remove('show');
unregisterFromEvent(eventId, currentUser.email);
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);
}
};
document.getElementById('unregister-modal-close').onclick = () => modal.classList.remove('show');
document.getElementById('unregister-modal-cancel').onclick = () => modal.classList.remove('show');
modal.addEventListener('click', e => { if (e.target === modal) modal.classList.remove('show'); });
return;
}
if (target.closest('a, button')) {
return;
}
const card = target.closest('[data-event-id]');
if (!card) {
return;
}
const eventId = Number(card.getAttribute('data-event-id'));
if (!Number.isFinite(eventId)) {
return;
}
window.location.href = `event_detail.html?id=${eventId}`;
}
// Sagt ein gehostetes Event ab (aus eigener Profilansicht entfernen).
let pendingCancelEventId = null;
function openCancelEventModal(eventId) {
pendingCancelEventId = eventId;
const modal = document.getElementById('cancelEventModal');
modal.classList.add('show');
}
window.closeCancelEventModal = function() {
pendingCancelEventId = null;
const modal = document.getElementById('cancelEventModal');
modal.classList.remove('show');
};
document.getElementById('confirmCancelEventBtn').addEventListener('click', function() {
if (pendingCancelEventId !== null && currentUser?.email) {
cancelHostedEvent(pendingCancelEventId, currentUser.email);
}
closeCancelEventModal();
const snackbar = document.getElementById('snackbar');
if (snackbar) {
snackbar.textContent = 'Dein Event wurde erfolgreich abgesagt.';
snackbar.classList.add('snackbar--danger', 'snackbar--visible');
setTimeout(() => {
snackbar.classList.remove('snackbar--visible');
setTimeout(() => snackbar.classList.remove('snackbar--danger'), 400);
}, 3000);
}
});
// Schliesst das Modal bei Klick ausserhalb des Inhalts.
document.getElementById('cancelEventModal').addEventListener('click', function(e) {
if (e.target === this) {
closeCancelEventModal();
}
});
function cancelHostedEvent(eventId, userEmail) {
// Lokal erstellte, eigene Events werden direkt aus dem Storage geloescht.
const storedEvents = getStoredEvents();
const nextStoredEvents = storedEvents.filter(event => {
const isTargetEvent = Number(event.id) === eventId;
const isOwnedByUser = normalizeText(event.hostEmail || '') === normalizeText(userEmail)
|| normalizeText(event.host?.name || '') === normalizeText(currentUser?.vorname || '');
return !(isTargetEvent && isOwnedByUser);
});
setStoredEvents(nextStoredEvents);
// Event-ID für alle Benutzer aus den Anmeldungen entfernen.
const registrationMap = getRegistrationMap();
Object.keys(registrationMap).forEach(email => {
const ids = Array.isArray(registrationMap[email])
? registrationMap[email].map(id => Number(id)).filter(Number.isFinite)
: [];
registrationMap[email] = ids.filter(id => id !== eventId);
});
setRegistrationMap(registrationMap);
allEvents = allEvents.filter(event => Number(event.id) !== eventId);
renderMyEvents(allEvents, currentUser);
renderMyRegistrations(allEvents, currentUser);
}
// Entfernt eine Event-ID aus der Benutzerliste und aktualisiert die UI sofort.
function unregisterFromEvent(eventId, userEmail) {
const registrationMap = getRegistrationMap();
const currentIds = Array.isArray(registrationMap[userEmail]) ? registrationMap[userEmail] : [];
const nextIds = currentIds
.map(id => Number(id))
.filter(id => Number.isFinite(id) && id !== eventId);
registrationMap[userEmail] = nextIds;
setRegistrationMap(registrationMap);
renderMyRegistrations(allEvents, currentUser);
}
// Validiert Profildaten konsistent und liefert true/false zur Submit-Steuerung.
function validateProfileForm() {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!vornameInput.value.trim()) {
vornameInput.parentElement.classList.add('has-error');
isValid = false;
}
if (!nachnameInput.value.trim()) {
nachnameInput.parentElement.classList.add('has-error');
isValid = false;
}
if (!emailRegex.test(emailInput.value.trim())) {
emailInput.parentElement.classList.add('has-error');
isValid = false;
}
if (passwortInput.value && passwortInput.value.length < 6) {
passwortInput.parentElement.classList.add('has-error');
isValid = false;
}
return isValid;
}
// Speichert Profiländerungen lokal und synchronisiert auch den Benutzerkatalog.
function handleProfileSubmit(event) {
event.preventDefault();
if (!validateProfileForm()) {
profileFeedback.textContent = 'Bitte prüfe die markierten Felder.';
return;
}
const previousEmail = currentUser.email;
const nextUser = {
...currentUser,
vorname: vornameInput.value.trim(),
nachname: nachnameInput.value.trim(),
email: emailInput.value.trim(),
passwort: passwortInput.value ? passwortInput.value : currentUser.passwort,
updatedAt: new Date().toISOString()
};
currentUser = nextUser;
localStorage.setItem(CURRENT_USER_KEY, JSON.stringify(nextUser));
syncUserInUserStore(previousEmail, nextUser);
// Falls sich die E-Mail geändert hat, verschieben wir bestehende Anmeldungen auf die neue E-Mail.
migrateRegistrationEmail(previousEmail, nextUser.email);
passwortInput.value = '';
profileHeadline.textContent = `Hallo ${nextUser.vorname}`;
profileFeedback.textContent = 'Profil erfolgreich gespeichert.';
}
// Synchronisiert einen Benutzer im zentralen User-Array.
function syncUserInUserStore(previousEmail, nextUser) {
let users = [];
try {
const raw = localStorage.getItem(USERS_STORAGE_KEY);
users = raw ? JSON.parse(raw) : [];
} catch (error) {
console.error('Benutzerdaten konnten nicht gelesen werden.', error);
}
const nextUsers = users.filter(user => user.email !== previousEmail && user.email !== nextUser.email);
nextUsers.unshift(nextUser);
localStorage.setItem(USERS_STORAGE_KEY, JSON.stringify(nextUsers));
}
// Migriert bestehende Registrierungen, falls die E-Mail aktualisiert wurde.
function migrateRegistrationEmail(previousEmail, nextEmail) {
if (!previousEmail || !nextEmail || previousEmail === nextEmail) {
return;
}
const map = getRegistrationMap();
const existingRegistrations = Array.isArray(map[previousEmail]) ? map[previousEmail] : [];
const alreadyPresent = Array.isArray(map[nextEmail]) ? map[nextEmail] : [];
map[nextEmail] = Array.from(new Set([...alreadyPresent, ...existingRegistrations]));
delete map[previousEmail];
localStorage.setItem(REGISTRATION_STORAGE_KEY, JSON.stringify(map));
}
// Ermittelt gehostete Events aus lokal erstellten Daten des aktuellen Benutzers.
function getMyHostedEvents(events, user) {
const userFirstName = normalizeText(user.vorname || '');
const userEmail = normalizeText(user.email || '');
return events.filter(event => {
if (event.source !== 'local') {
return false;
}
const hostEmail = normalizeText(event.hostEmail || '');
const hostName = normalizeText(event.host?.name || '');
if (hostEmail && hostEmail === userEmail) {
return true;
}
return userFirstName && hostName === userFirstName;
});
}
// Ermittelt angemeldete Events über die Registration-Map und participants-Liste.
function getMyRegisteredEvents(events, user) {
const registrationMap = getRegistrationMap();
const registeredIds = Array.isArray(registrationMap[user.email]) ? registrationMap[user.email] : [];
const idSet = new Set(registeredIds.map(id => Number(id)));
const userFirstName = String(user.vorname || '').trim().toLowerCase();
const userFullName = `${String(user.vorname || '').trim()} ${String(user.nachname || '').trim()}`.trim().toLowerCase();
return events.filter(event => {
if (idSet.has(Number(event.id))) {
return true;
}
if (Array.isArray(event.participants)) {
const participantSet = new Set(event.participants.map(name => String(name || '').trim().toLowerCase()).filter(Boolean));
if ((userFirstName && participantSet.has(userFirstName)) || (userFullName && participantSet.has(userFullName))) {
return true;
}
}
return false;
});
}
// Rendert angemeldete Events inkl. Zähler.
function renderMyEvents(events, user) {
const hostedEvents = getMyHostedEvents(events, user);
const count = hostedEvents.length;
myEventsCount.textContent = String(count);
if (myEventsBtnCount) myEventsBtnCount.textContent = String(count);
renderEventCards(myEventsList, hostedEvents, {
title: 'Noch kein eigenes Event',
text: 'Starte dein erstes Dinner und lade die Community an deinen Tisch ein.',
buttonLabel: 'Event erstellen',
href: 'event_create.html'
}, 'hosting');
}
function getSeenAddresses() {
try {
const raw = localStorage.getItem('socialCookingSeenAddresses');
return raw ? JSON.parse(raw) : [];
} catch (err) {
return [];
}
}
function markRegistrationsAsRead(events) {
const seen = getSeenAddresses();
let changed = false;
events.forEach(event => {
if (isAddressVisibleWindow(event) && !seen.includes(Number(event.id))) {
seen.push(Number(event.id));
changed = true;
}
});
if (changed) {
localStorage.setItem('socialCookingSeenAddresses', JSON.stringify(seen));
// Remove dots from UI
const tabDot = document.querySelector('[data-category-item="teilnehmen"] .notification-dot');
if (tabDot) tabDot.remove();
const navDot = document.querySelector('.profile-pill .notification-dot');
if (navDot) navDot.remove();
}
}
// Rendert angemeldete Events inkl. Zähler.
function renderMyRegistrations(events, user) {
const registeredEvents = getMyRegisteredEvents(events, user);
const count = registeredEvents.length;
myRegistrationsCount.textContent = String(count);
if (myRegistrationsBtnCount) myRegistrationsBtnCount.textContent = String(count);
const seenAddresses = getSeenAddresses();
const unreadEvents = registeredEvents.filter(e => isAddressVisibleWindow(e) && !seenAddresses.includes(Number(e.id)));
const hasNotifications = unreadEvents.length > 0;
const tabButton = document.querySelector('[data-category-item="teilnehmen"]');
if (tabButton) {
let dot = tabButton.querySelector('.notification-dot');
if (hasNotifications) {
if (!dot) {
dot = document.createElement('span');
dot.className = 'notification-dot';
tabButton.appendChild(dot);
}
} else if (dot) {
dot.remove();
}
}
renderEventCards(myRegistrationsList, registeredEvents, {
title: 'Noch keine Anmeldungen',
text: 'Entdecke spannende Dinner in deiner Naehe und melde dich direkt an.',
buttonLabel: 'Events entdecken',
href: 'event_overview.html'
}, 'registrations', seenAddresses);
// Falls wir bereits auf dem Tab sind, direkt als gelesen markieren
const activeTab = document.querySelector('[data-category-item="teilnehmen"].is-active');
if (activeTab && hasNotifications) {
// Kurze Verzögerung, damit UI sich erst aufbaut
setTimeout(() => markRegistrationsAsRead(registeredEvents), 500);
}
}
// Gibt true zurück, wenn die Abmeldung gesperrt ist (innerhalb von 24h oder in der Vergangenheit).
function isDeregistrationClosed(event) {
const eventDateTime = parseEventDateTime(event);
if (!eventDateTime || Number.isNaN(eventDateTime.getTime())) {
return false;
}
const msUntilStart = eventDateTime.getTime() - Date.now();
return msUntilStart <= 24 * 60 * 60 * 1000;
}
// Baut die Eventkarten für beide Listen in einheitlichem Markup.
function renderEventCards(container, events, emptyStateConfig, mode, seenAddresses = []) {
container.innerHTML = '';
if (events.length === 0) {
const emptyElement = document.createElement('div');
emptyElement.className = 'empty-state';
emptyElement.innerHTML = `
<div class="empty-state-kicker">Keine Treffer</div>
<h3>${emptyStateConfig.title}</h3>
<p>${emptyStateConfig.text}</p>
<a class="empty-state-link button-primary" href="${emptyStateConfig.href}">${emptyStateConfig.buttonLabel}</a>
`;
container.appendChild(emptyElement);
return;
}
events.forEach(event => {
const card = document.createElement('article');
card.className = 'profile-event-card profile-event-card-clickable';
card.setAttribute('data-event-id', String(event.id));
const isCanceled = event.status === 'canceled';
if (isCanceled) {
card.style.opacity = '0.6';
}
let addressMessage = 'Vielen Dank für die Anmeldung! Die Adresse für diesen Event wird 24 Stunden vorher genau hier sichtbar sein.';
if (isEventPastAddressWindow(event)) {
addressMessage = 'Vielen Dank, dass du an diesem Event teilgenommen hast.';
}
let addressMarkup = '';
if (mode === 'registrations' && event.address) {
if (isCanceled) {
addressMarkup = `
<div class="profile-event-address-block" aria-label="Hinweis zur Adresse">
<p class="profile-event-address-label">Adresse</p>
<p class="profile-event-address">Dieses Event wurde leider vom Gastgeber abgesagt.</p>
</div>
`;
} else if (isAddressVisibleWindow(event)) {
addressMarkup = `
<div class="profile-event-address-block" aria-label="Event Adresse">
<p class="profile-event-address-label">Adresse</p>
<p class="profile-event-address">${event.address}</p>
</div>
`;
} else {
addressMarkup = `
<div class="profile-event-address-block" aria-label="Hinweis zur Adresse">
<p class="profile-event-address-label">Adresse</p>
<p class="profile-event-address">${addressMessage}</p>
</div>
`;
}
}
const isDeregClosed = isDeregistrationClosed(event);
let actionMarkup = '';
if (mode === 'registrations') {
if (isCanceled) {
actionMarkup = `
<div class="event-side">
<button class="button-primary-abmelden" type="button" disabled>Abgesagt</button>
</div>
`;
} else if (isDeregClosed) {
actionMarkup = `
<div class="event-side">
<button class="button-primary-abmelden" type="button" disabled>Abmeldung geschlossen</button>
</div>
`;
} else {
actionMarkup = `
<div class="event-side">
<button class="button-primary-abmelden" type="button" data-unregister-id="${event.id}">Abmelden</button>
</div>
`;
}
} else {
if (isCanceled) {
actionMarkup = `
<div class="event-side">
<button class="button-primary-eigener-event" type="button" disabled>Abgesagt</button>
</div>
`;
} else {
actionMarkup = `
<div class="event-side">
<button class="button-primary-eigener-event" type="button" data-cancel-event-id="${event.id}">Event absagen</button>
</div>
`;
}
}
card.innerHTML = `
<div>
<h3 class="profile-event-title">${event.title}</h3>
<p class="profile-event-meta">${event.location} | ${formatEventDate(event.date)} | ${formatEventTime(event.time)}</p>
${addressMarkup}
</div>
${actionMarkup}
`;
container.appendChild(card);
});
}
// Gibt true zurück, wenn die Adresse sichtbar sein soll (24h vor bis 1h nach Start).
function isAddressVisibleWindow(event) {
if (event.status === 'canceled') return false;
const eventDateTime = parseEventDateTime(event);
if (!eventDateTime || Number.isNaN(eventDateTime.getTime())) return false;
const now = Date.now();
const start = eventDateTime.getTime();
const revealStart = start - (24 * 60 * 60 * 1000);
const revealEnd = start + (1 * 60 * 60 * 1000);
return now >= revealStart && now <= revealEnd;
}
// Gibt true zurück, wenn ein Event bereits vorbei ist (1h nach Start).
function isEventPastAddressWindow(event) {
const eventDateTime = parseEventDateTime(event);
if (!eventDateTime || Number.isNaN(eventDateTime.getTime())) return false;
const revealEnd = eventDateTime.getTime() + (1 * 60 * 60 * 1000);
return Date.now() > revealEnd;
}
// Parse für ISO- und lokalisierte Datumsformate aus den Eventdaten.
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);
}
// Formatiert ein Eventdatum konsistent für die Profilkarten.
function formatEventDate(dateString) {
if (!dateString) {
return 'Kein Datum';
}
// ISO Format: 2026-02-12
if (/^\d{4}-\d{2}-\d{2}$/.test(dateString)) {
const [year, month, day] = dateString.split('-');
const monthLabel = ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'][Number(month) - 1];
return `${Number(day)}. ${monthLabel} ${year}`;
}
// Format: 12. FEB. 2026
const match = dateString.match(/^(\d{1,2})\.\s*([A-ZÄÖÜ]{3})\.\s*(\d{4})$/);
if (match) {
const day = match[1];
const month = match[2];
const shortMonthMap = {
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'
};
return `${day}. ${shortMonthMap[month] || month} ${match[3]}`;
}
return dateString;
}
// Vereinheitlicht die Zeitanzeige für die Profilseite.
function formatEventTime(timeString) {
if (!timeString) {
return 'Keine Uhrzeit';
}
return timeString.includes('UHR') ? timeString.replace('UHR', 'Uhr').trim() : timeString;
}
// Normalisiert Vergleichswerte für robuste String-Matches.
function normalizeText(value) {
return String(value || '').trim().toLowerCase();
}
});