feat: Enhance profile page with tab navigation and event management features
Fix Event anmeldung und abmeldung Button. 12h vor Event beginn wird Adresse angezeigt.
This commit is contained in:
parent
1efa4dcd39
commit
c3bea2817c
@ -266,7 +266,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
background: var(--olive);
|
|
||||||
color: #fffde8;
|
color: #fffde8;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: var(--radius-pill);
|
border-radius: var(--radius-pill);
|
||||||
@ -275,10 +274,44 @@
|
|||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
transition: background-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary:hover {
|
.btn-primary-register {
|
||||||
filter: brightness(0.95);
|
background: var(--olive);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary-register:hover,
|
||||||
|
.btn-primary-register:focus-visible {
|
||||||
|
background: #575704;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 10px rgba(107, 107, 5, 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary-register:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
box-shadow: 0 2px 6px rgba(107, 107, 5, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary-danger {
|
||||||
|
background: var(--tomato);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary-danger:hover,
|
||||||
|
.btn-primary-danger:focus-visible {
|
||||||
|
background: var(--tomato-dark);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 10px rgba(188, 74, 52, 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary-danger:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
box-shadow: 0 2px 6px rgba(188, 74, 52, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary-own,
|
.btn-primary-own,
|
||||||
@ -688,12 +721,42 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.detail-primary-btn {
|
.detail-primary-btn {
|
||||||
border: 2px solid var(--tomato);
|
|
||||||
border-radius: var(--radius-pill);
|
border-radius: var(--radius-pill);
|
||||||
background: var(--tomato);
|
|
||||||
color: var(--white);
|
color: var(--white);
|
||||||
padding: 10px 22px;
|
padding: 10px 22px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s ease, border-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-primary-btn-register {
|
||||||
|
border: 2px solid var(--olive);
|
||||||
|
background: var(--olive);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-primary-btn-register:not(:disabled):hover,
|
||||||
|
.detail-primary-btn-register:not(:disabled):focus-visible {
|
||||||
|
background: #575704;
|
||||||
|
border-color: #575704;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 10px rgba(107, 107, 5, 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-primary-btn-danger {
|
||||||
|
border: 2px solid var(--tomato);
|
||||||
|
background: var(--tomato);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-primary-btn-danger:not(:disabled):hover,
|
||||||
|
.detail-primary-btn-danger:not(:disabled):focus-visible {
|
||||||
|
background: var(--tomato-dark);
|
||||||
|
border-color: var(--tomato-dark);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 10px rgba(188, 74, 52, 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-primary-btn:not(:disabled):active {
|
||||||
|
transform: translateY(0);
|
||||||
|
box-shadow: 0 2px 6px rgba(102, 52, 13, 0.22);
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-primary-btn:disabled {
|
.detail-primary-btn:disabled {
|
||||||
|
|||||||
@ -44,6 +44,38 @@
|
|||||||
gap: var(--space-4);
|
gap: var(--space-4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.profile-tabs {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: var(--space-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-tab {
|
||||||
|
border: 2px solid var(--olive);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: var(--butter);
|
||||||
|
color: var(--black);
|
||||||
|
padding: 0.45rem 1rem;
|
||||||
|
min-height: 2.5rem;
|
||||||
|
font-family: "Jost", sans-serif;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: var(--ls-ui);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-tab:hover,
|
||||||
|
.profile-tab:focus-visible {
|
||||||
|
background: #faf8e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-tab.is-active {
|
||||||
|
border-color: transparent;
|
||||||
|
background: var(--olive);
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
/* Konsistentes Karten-Layout fuer alle Profilsektionen. */
|
/* Konsistentes Karten-Layout fuer alle Profilsektionen. */
|
||||||
.profile-panel {
|
.profile-panel {
|
||||||
background: rgba(255, 255, 255, 0.88);
|
background: rgba(255, 255, 255, 0.88);
|
||||||
@ -92,6 +124,16 @@
|
|||||||
gap: var(--space-3);
|
gap: var(--space-3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.profile-event-card-clickable {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: box-shadow 0.2s ease, transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-event-card-clickable:hover {
|
||||||
|
box-shadow: 0 6px 16px rgba(102, 52, 13, 0.14);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
.profile-event-title {
|
.profile-event-title {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: var(--black);
|
color: var(--black);
|
||||||
@ -106,6 +148,31 @@
|
|||||||
color: var(--olive);
|
color: var(--olive);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.profile-event-address-block {
|
||||||
|
margin-top: 0.55rem;
|
||||||
|
padding: 0.6rem 0.75rem;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
border-left: 4px solid var(--tomato);
|
||||||
|
background: rgba(232, 237, 209, 0.65);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-event-address-label {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--olive);
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: var(--ls-label);
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-event-address {
|
||||||
|
margin: 0.2rem 0 0;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--black);
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
|
||||||
.profile-event-link {
|
.profile-event-link {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
color: var(--blue);
|
color: var(--blue);
|
||||||
@ -135,11 +202,44 @@
|
|||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-cancel-btn {
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: var(--tomato);
|
||||||
|
color: var(--butter-light);
|
||||||
|
padding: 0.45rem 0.95rem;
|
||||||
|
font-family: "Jost", sans-serif;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-cancel-btn:hover,
|
||||||
|
.profile-cancel-btn:focus-visible {
|
||||||
|
background: var(--tomato-dark);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 10px rgba(188, 74, 52, 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-cancel-btn:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
box-shadow: 0 2px 6px rgba(188, 74, 52, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-unregister-btn:hover,
|
.profile-unregister-btn:hover,
|
||||||
.profile-unregister-btn:focus-visible {
|
.profile-unregister-btn:focus-visible {
|
||||||
background: var(--tomato-dark);
|
background: var(--tomato-dark);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 10px rgba(188, 74, 52, 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-unregister-btn:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
box-shadow: 0 2px 6px rgba(188, 74, 52, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-empty {
|
.profile-empty {
|
||||||
@ -147,6 +247,35 @@
|
|||||||
color: var(--black);
|
color: var(--black);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.profile-empty-state {
|
||||||
|
text-align: center;
|
||||||
|
padding: 2.4rem 1.3rem;
|
||||||
|
border: 2px solid var(--olive-light);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
background: rgba(255, 255, 255, 0.92);
|
||||||
|
box-shadow: 0 3px 12px rgba(102, 52, 13, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-empty-kicker {
|
||||||
|
margin: 0 0 0.5rem;
|
||||||
|
color: var(--olive);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: var(--ls-label);
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-empty-state h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: var(--brown);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-empty-state p {
|
||||||
|
margin: 0.65rem auto 1rem;
|
||||||
|
max-width: 36rem;
|
||||||
|
}
|
||||||
|
|
||||||
.form-grid {
|
.form-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
|
|||||||
@ -3,8 +3,9 @@
|
|||||||
"id": 1,
|
"id": 1,
|
||||||
"title": "Italienische Tavolata",
|
"title": "Italienische Tavolata",
|
||||||
"location": "Luzern",
|
"location": "Luzern",
|
||||||
"date": "19. MÄR. 2026",
|
"address": "Pilatusstrasse 18, 6003 Luzern",
|
||||||
"time": "18:30 UHR",
|
"date": "11. APR. 2026",
|
||||||
|
"time": "3:30 UHR",
|
||||||
"category": "DINNER",
|
"category": "DINNER",
|
||||||
"diet": "VEGGIE",
|
"diet": "VEGGIE",
|
||||||
"spots": 6,
|
"spots": 6,
|
||||||
@ -43,6 +44,7 @@
|
|||||||
"id": 2,
|
"id": 2,
|
||||||
"title": "Noche Peruana",
|
"title": "Noche Peruana",
|
||||||
"location": "Chur",
|
"location": "Chur",
|
||||||
|
"address": "Obere Gasse 41, 7000 Chur",
|
||||||
"date": "11. APR. 2026",
|
"date": "11. APR. 2026",
|
||||||
"time": "19:00 UHR",
|
"time": "19:00 UHR",
|
||||||
"category": "DINNER",
|
"category": "DINNER",
|
||||||
@ -84,6 +86,7 @@
|
|||||||
"id": 3,
|
"id": 3,
|
||||||
"title": "Japanese Delight",
|
"title": "Japanese Delight",
|
||||||
"location": "ZÜRICH",
|
"location": "ZÜRICH",
|
||||||
|
"address": "Limmatquai 92, 8001 Zürich",
|
||||||
"date": "02. MAI. 2026",
|
"date": "02. MAI. 2026",
|
||||||
"time": "12:30 UHR",
|
"time": "12:30 UHR",
|
||||||
"category": "LUNCH",
|
"category": "LUNCH",
|
||||||
|
|||||||
@ -429,7 +429,8 @@ function buildStoredEvent() {
|
|||||||
? []
|
? []
|
||||||
: getCheckboxValues("allergies").split(", ").filter(Boolean),
|
: getCheckboxValues("allergies").split(", ").filter(Boolean),
|
||||||
allergiesNote: form.elements.allergiesOther.value.trim(),
|
allergiesNote: form.elements.allergiesOther.value.trim(),
|
||||||
participants: [usernameElement.textContent.trim() || "Host"],
|
// Host wird separat gefuehrt und nicht als angemeldeter Gast gezaehlt.
|
||||||
|
participants: [],
|
||||||
gallery: [],
|
gallery: [],
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
source: "local"
|
source: "local"
|
||||||
|
|||||||
@ -52,6 +52,80 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
localStorage.setItem(REGISTRATION_STORAGE_KEY, JSON.stringify(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
// Ermittelt, ob das Event vom aktuell eingeloggten Benutzer erstellt wurde.
|
||||||
function isEventOwnedByCurrentUser(event, user) {
|
function isEventOwnedByCurrentUser(event, user) {
|
||||||
if (!event || !user) {
|
if (!event || !user) {
|
||||||
@ -71,6 +145,29 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
return Boolean(userFirstName && hostName && userFirstName === hostName);
|
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.
|
// Fetch data source and resolve the matching event record.
|
||||||
try {
|
try {
|
||||||
const response = await fetch('data/events.json');
|
const response = await fetch('data/events.json');
|
||||||
@ -157,19 +254,47 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
? event.gallery
|
? event.gallery
|
||||||
: [event.image, event.image, event.image];
|
: [event.image, event.image, event.image];
|
||||||
const visibleParticipants = participants.slice(0, 6);
|
const visibleParticipants = participants.slice(0, 6);
|
||||||
const remainingParticipants = Math.max(0, participants.length - visibleParticipants.length);
|
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 totalGuests = Number.isFinite(event.spots) ? event.spots : 0;
|
||||||
const confirmedGuests = participants.length;
|
const confirmedGuests = participants.length + extraRegistrations;
|
||||||
const freePlaces = Math.max(0, totalGuests - confirmedGuests);
|
const freePlaces = Math.max(0, totalGuests - confirmedGuests);
|
||||||
const isFull = freePlaces === 0;
|
const isFull = freePlaces === 0;
|
||||||
|
const isRegistrationClosed = isRegistrationClosedForEvent(event);
|
||||||
const isOwnEvent = isEventOwnedByCurrentUser(event, currentUser);
|
const isOwnEvent = isEventOwnedByCurrentUser(event, currentUser);
|
||||||
const registrationMap = getRegistrationMap();
|
|
||||||
const userRegistrations = currentUser?.email && Array.isArray(registrationMap[currentUser.email])
|
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 isRegistered = userRegistrations.includes(Number(event.id));
|
||||||
const actionButtonLabel = isOwnEvent ? 'Dein Event' : !currentUser ? 'Einloggen' : isRegistered ? 'Abmelden' : 'Anmelden';
|
const isListedParticipant = isUserListedInEventParticipants(event, currentUser);
|
||||||
const actionButtonDisabled = isOwnEvent || (!isRegistered && isFull);
|
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) && isRegistrationClosed && hasAddressAccess;
|
||||||
|
const addressPanelMarkup = shouldRevealAddress
|
||||||
|
? `
|
||||||
|
<article class="detail-panel detail-panel-compact">
|
||||||
|
<h2 class="detail-section-title">Adresse</h2>
|
||||||
|
<p>${event.address}</p>
|
||||||
|
</article>
|
||||||
|
`
|
||||||
|
: '';
|
||||||
const detailChips = [
|
const detailChips = [
|
||||||
`<span class="event-tag">${eventCategory}</span>`,
|
`<span class="event-tag">${eventCategory}</span>`,
|
||||||
`<span class="event-tag">${dietLabel}</span>`,
|
`<span class="event-tag">${dietLabel}</span>`,
|
||||||
@ -227,6 +352,8 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
${remainingParticipants > 0 ? `<span class="participant-more">+${remainingParticipants}</span>` : ''}
|
${remainingParticipants > 0 ? `<span class="participant-more">+${remainingParticipants}</span>` : ''}
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
|
${addressPanelMarkup}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="detail-gallery detail-gallery-large">
|
<div class="detail-gallery detail-gallery-large">
|
||||||
@ -254,7 +381,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
<span class="detail-spots-pill${isFull ? ' detail-spots-pill-full' : ''}">
|
<span class="detail-spots-pill${isFull ? ' detail-spots-pill-full' : ''}">
|
||||||
${isFull ? 'AUSGEBUCHT' : `${freePlaces} Plaetze frei`}
|
${isFull ? 'AUSGEBUCHT' : `${freePlaces} Plaetze frei`}
|
||||||
</span>
|
</span>
|
||||||
<button class="detail-primary-btn${isOwnEvent ? ' detail-primary-btn-own' : ''}" type="button" data-register-button ${actionButtonDisabled ? 'disabled' : ''}>
|
<button class="detail-primary-btn${actionButtonVariantClass}" type="button" data-register-button ${actionButtonDisabled ? 'disabled' : ''}>
|
||||||
${actionButtonLabel}
|
${actionButtonLabel}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -280,6 +407,13 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
const galleryButtons = detailContainer.querySelectorAll('.detail-gallery-item');
|
const galleryButtons = detailContainer.querySelectorAll('.detail-gallery-item');
|
||||||
const registerButton = detailContainer.querySelector('[data-register-button]');
|
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.
|
// Anmeldung toggeln und im lokalen Registrierungs-Store persistieren.
|
||||||
if (registerButton) {
|
if (registerButton) {
|
||||||
registerButton.addEventListener('click', () => {
|
registerButton.addEventListener('click', () => {
|
||||||
@ -300,7 +434,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
|
|
||||||
if (registrationSet.has(Number(event.id))) {
|
if (registrationSet.has(Number(event.id))) {
|
||||||
registrationSet.delete(Number(event.id));
|
registrationSet.delete(Number(event.id));
|
||||||
} else if (!isFull) {
|
} else if (!isFull && !isRegistrationClosed) {
|
||||||
registrationSet.add(Number(event.id));
|
registrationSet.add(Number(event.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const EVENTS_STORAGE_KEY = 'socialCookingEvents';
|
const EVENTS_STORAGE_KEY = 'socialCookingEvents';
|
||||||
const CURRENT_USER_KEY = 'socialCookingCurrentUser';
|
const CURRENT_USER_KEY = 'socialCookingCurrentUser';
|
||||||
|
const REGISTRATION_STORAGE_KEY = 'socialCookingRegistrations';
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
// DOM references used throughout the page lifecycle.
|
// DOM references used throughout the page lifecycle.
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
@ -55,6 +56,20 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
// Initial data bootstrap:
|
// Initial data bootstrap:
|
||||||
// 1) fetch JSON,
|
// 1) fetch JSON,
|
||||||
@ -183,6 +198,83 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
: `${timeString} Uhr`;
|
: `${timeString} Uhr`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Baut aus Eventdatum/-zeit ein Date-Objekt fuer Fristlogik und Vergleiche.
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zaehlt eindeutige Registrierungen eines Events ueber alle Benutzer.
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schliesst neue Anmeldungen ab 12h vor Start (inkl. bereits gestarteter Events).
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
// Safely verify whether a value exists in the given select element.
|
// Safely verify whether a value exists in the given select element.
|
||||||
function hasOption(selectElement, value) {
|
function hasOption(selectElement, value) {
|
||||||
return Array.from(selectElement.options).some(option => option.value === value);
|
return Array.from(selectElement.options).some(option => option.value === value);
|
||||||
@ -202,6 +294,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const filtered = allEvents.filter(event => {
|
const filtered = allEvents.filter(event => {
|
||||||
|
// Lokal erstellte Events werden nicht in der allgemeinen Event-Uebersicht angezeigt.
|
||||||
|
if (event.source === 'local') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const categoryMatch = activeCategory === 'ALLE' || event.category === activeCategory;
|
const categoryMatch = activeCategory === 'ALLE' || event.category === activeCategory;
|
||||||
const locationMatch = selectedLocation === 'ALLE_ORTE' || event.location === selectedLocation;
|
const locationMatch = selectedLocation === 'ALLE_ORTE' || event.location === selectedLocation;
|
||||||
const eventDateIso = parseEventDateToIso(event.date);
|
const eventDateIso = parseEventDateToIso(event.date);
|
||||||
@ -222,6 +319,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
// - or event cards with status and metadata.
|
// - or event cards with status and metadata.
|
||||||
function renderEvents(events) {
|
function renderEvents(events) {
|
||||||
eventGrid.innerHTML = '';
|
eventGrid.innerHTML = '';
|
||||||
|
const registrationMap = getRegistrationMap();
|
||||||
|
const userRegistrationSet = currentUser?.email && Array.isArray(registrationMap[currentUser.email])
|
||||||
|
? new Set(registrationMap[currentUser.email].map(id => Number(id)))
|
||||||
|
: new Set();
|
||||||
|
|
||||||
if (events.length === 0) {
|
if (events.length === 0) {
|
||||||
eventGrid.innerHTML = `
|
eventGrid.innerHTML = `
|
||||||
@ -242,20 +343,28 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const card = document.createElement('article');
|
const card = document.createElement('article');
|
||||||
card.className = 'event-card';
|
card.className = 'event-card';
|
||||||
card.style.cursor = 'pointer';
|
card.style.cursor = 'pointer';
|
||||||
card.onclick = () => {
|
card.addEventListener('click', clickedEvent => {
|
||||||
|
if (clickedEvent.target instanceof HTMLElement && clickedEvent.target.closest('button')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
window.location.href = `event_detail.html?id=${event.id}`;
|
window.location.href = `event_detail.html?id=${event.id}`;
|
||||||
};
|
});
|
||||||
|
|
||||||
const displayDate = formatEventDate(event.date);
|
const displayDate = formatEventDate(event.date);
|
||||||
const displayTime = formatEventTime(event.time);
|
const displayTime = formatEventTime(event.time);
|
||||||
|
|
||||||
// Capacity logic:
|
// Capacity logic:
|
||||||
// spots = total capacity, participants.length = booked seats.
|
// spots = total capacity, participants.length = booked seats.
|
||||||
const bookedSeats = event.participants ? event.participants.length : 0;
|
const baseParticipants = Array.isArray(event.participants) ? event.participants.length : 0;
|
||||||
|
const extraRegistrations = countRegistrationsForEvent(registrationMap, event.id);
|
||||||
|
const bookedSeats = baseParticipants + extraRegistrations;
|
||||||
const totalCapacity = event.spots;
|
const totalCapacity = event.spots;
|
||||||
const freePlaces = Math.max(0, totalCapacity - bookedSeats);
|
const freePlaces = Math.max(0, totalCapacity - bookedSeats);
|
||||||
const isFull = freePlaces === 0;
|
const isFull = freePlaces === 0;
|
||||||
const isOwnEvent = isEventOwnedByCurrentUser(event, currentUser);
|
const isOwnEvent = isEventOwnedByCurrentUser(event, currentUser);
|
||||||
|
const isRegistered = userRegistrationSet.has(Number(event.id));
|
||||||
|
const isRegistrationClosed = isRegistrationClosedForEvent(event);
|
||||||
|
|
||||||
// Build optional specification chips only when data exists.
|
// Build optional specification chips only when data exists.
|
||||||
const specsChips = event.specifications && event.specifications.length > 0
|
const specsChips = event.specifications && event.specifications.length > 0
|
||||||
@ -263,10 +372,16 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
: '';
|
: '';
|
||||||
|
|
||||||
const actionMarkup = isOwnEvent
|
const actionMarkup = isOwnEvent
|
||||||
? '<button class="btn-primary btn-primary-own" type="button" disabled>Dein Event</button>'
|
? '<button class="btn-primary btn-primary-own" type="button" data-registration-action="own" disabled>Dein Event!</button>'
|
||||||
: isFull
|
: isRegistered
|
||||||
? ''
|
? '<button class="btn-primary btn-primary-danger" type="button" data-registration-action="unregister">Abmelden</button>'
|
||||||
: '<button class="btn-primary" type="button">Anmelden</button>';
|
: isRegistrationClosed
|
||||||
|
? '<button class="btn-primary btn-primary-danger" type="button" data-registration-action="closed" disabled>Anmeldung geschlossen</button>'
|
||||||
|
: isFull
|
||||||
|
? ''
|
||||||
|
: !currentUser
|
||||||
|
? '<button class="btn-primary btn-primary-register" type="button" data-registration-action="login">Anmelden</button>'
|
||||||
|
: '<button class="btn-primary btn-primary-register" type="button" data-registration-action="register">Anmelden</button>';
|
||||||
|
|
||||||
card.innerHTML = `
|
card.innerHTML = `
|
||||||
<div class="event-main">
|
<div class="event-main">
|
||||||
@ -290,6 +405,50 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const actionButton = card.querySelector('[data-registration-action]');
|
||||||
|
if (actionButton) {
|
||||||
|
actionButton.addEventListener('click', clickEvent => {
|
||||||
|
clickEvent.stopPropagation();
|
||||||
|
|
||||||
|
const action = actionButton.getAttribute('data-registration-action');
|
||||||
|
if (action === 'own') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === 'closed') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === 'login') {
|
||||||
|
window.location.href = 'login.html';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!currentUser?.email) {
|
||||||
|
window.location.href = 'login.html';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextRegistrationMap = getRegistrationMap();
|
||||||
|
const currentIds = Array.isArray(nextRegistrationMap[currentUser.email])
|
||||||
|
? nextRegistrationMap[currentUser.email].map(id => Number(id))
|
||||||
|
: [];
|
||||||
|
const idSet = new Set(currentIds);
|
||||||
|
|
||||||
|
if (action === 'unregister') {
|
||||||
|
idSet.delete(Number(event.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === 'register' && !isFull && !isRegistrationClosed) {
|
||||||
|
idSet.add(Number(event.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
nextRegistrationMap[currentUser.email] = Array.from(idSet);
|
||||||
|
setRegistrationMap(nextRegistrationMap);
|
||||||
|
applyFilters();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
eventGrid.appendChild(card);
|
eventGrid.appendChild(card);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
182
js/my_profil.js
182
js/my_profil.js
@ -10,6 +10,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const profileHeadline = document.getElementById('profile-headline');
|
const profileHeadline = document.getElementById('profile-headline');
|
||||||
const profileSubline = document.getElementById('profile-subline');
|
const profileSubline = document.getElementById('profile-subline');
|
||||||
const logoutButton = document.getElementById('logout-button');
|
const logoutButton = document.getElementById('logout-button');
|
||||||
|
const profileTabButtons = Array.from(document.querySelectorAll('[data-profile-tab]'));
|
||||||
|
const profileTabPanels = Array.from(document.querySelectorAll('[data-profile-panel]'));
|
||||||
|
|
||||||
const myEventsCount = document.getElementById('my-events-count');
|
const myEventsCount = document.getElementById('my-events-count');
|
||||||
const myRegistrationsCount = document.getElementById('my-registrations-count');
|
const myRegistrationsCount = document.getElementById('my-registrations-count');
|
||||||
@ -36,6 +38,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
renderLoggedInState(currentUser);
|
renderLoggedInState(currentUser);
|
||||||
bindFormHandlers();
|
bindFormHandlers();
|
||||||
|
activateProfileTab('hosting');
|
||||||
|
|
||||||
allEvents = await loadAllEvents();
|
allEvents = await loadAllEvents();
|
||||||
renderMyEvents(allEvents, currentUser);
|
renderMyEvents(allEvents, currentUser);
|
||||||
@ -80,6 +83,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
localStorage.setItem(REGISTRATION_STORAGE_KEY, JSON.stringify(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.
|
// Fuehrt JSON-Daten und lokal erstellte Events in einer Liste zusammen.
|
||||||
async function loadAllEvents() {
|
async function loadAllEvents() {
|
||||||
try {
|
try {
|
||||||
@ -119,6 +127,18 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
function bindFormHandlers() {
|
function bindFormHandlers() {
|
||||||
profileForm.addEventListener('submit', handleProfileSubmit);
|
profileForm.addEventListener('submit', handleProfileSubmit);
|
||||||
myRegistrationsList.addEventListener('click', handleRegistrationListClick);
|
myRegistrationsList.addEventListener('click', handleRegistrationListClick);
|
||||||
|
myEventsList.addEventListener('click', handleHostedListClick);
|
||||||
|
|
||||||
|
profileTabButtons.forEach(button => {
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
const tabName = button.getAttribute('data-profile-tab');
|
||||||
|
if (!tabName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
activateProfileTab(tabName);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
[vornameInput, nachnameInput, emailInput, passwortInput].forEach(input => {
|
[vornameInput, nachnameInput, emailInput, passwortInput].forEach(input => {
|
||||||
input.addEventListener('input', () => {
|
input.addEventListener('input', () => {
|
||||||
@ -133,6 +153,53 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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)) {
|
||||||
|
cancelHostedEvent(eventId, currentUser.email);
|
||||||
|
}
|
||||||
|
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-profile-tab') === 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Reagiert auf Aktionen in der Liste "Meine Anmeldungen" per Event Delegation.
|
// Reagiert auf Aktionen in der Liste "Meine Anmeldungen" per Event Delegation.
|
||||||
function handleRegistrationListClick(event) {
|
function handleRegistrationListClick(event) {
|
||||||
const target = event.target;
|
const target = event.target;
|
||||||
@ -141,18 +208,68 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const unregisterButton = target.closest('[data-unregister-id]');
|
const unregisterButton = target.closest('[data-unregister-id]');
|
||||||
if (!unregisterButton || !currentUser?.email) {
|
if (unregisterButton) {
|
||||||
|
if (!currentUser?.email) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventId = Number(unregisterButton.getAttribute('data-unregister-id'));
|
||||||
|
if (!Number.isFinite(eventId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
unregisterFromEvent(eventId, currentUser.email);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const eventId = Number(unregisterButton.getAttribute('data-unregister-id'));
|
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)) {
|
if (!Number.isFinite(eventId)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
unregisterFromEvent(eventId, currentUser.email);
|
window.location.href = `event_detail.html?id=${eventId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Sagt ein gehostetes Event ab (aus eigener Profilansicht entfernen).
|
||||||
|
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 fuer 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);
|
||||||
|
profileFeedback.textContent = 'Event wurde abgesagt und aus deinem Hosting entfernt.';
|
||||||
|
}
|
||||||
// Entfernt eine Event-ID aus der Benutzerliste und aktualisiert die UI sofort.
|
// Entfernt eine Event-ID aus der Benutzerliste und aktualisiert die UI sofort.
|
||||||
function unregisterFromEvent(eventId, userEmail) {
|
function unregisterFromEvent(eventId, userEmail) {
|
||||||
const registrationMap = getRegistrationMap();
|
const registrationMap = getRegistrationMap();
|
||||||
@ -260,15 +377,20 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
localStorage.setItem(REGISTRATION_STORAGE_KEY, JSON.stringify(map));
|
localStorage.setItem(REGISTRATION_STORAGE_KEY, JSON.stringify(map));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ermittelt gehostete Events anhand Host-E-Mail oder Host-Vorname.
|
// Ermittelt gehostete Events aus lokal erstellten Daten des aktuellen Benutzers.
|
||||||
function getMyHostedEvents(events, user) {
|
function getMyHostedEvents(events, user) {
|
||||||
const userFirstName = normalizeText(user.vorname);
|
const userFirstName = normalizeText(user.vorname || '');
|
||||||
|
const userEmail = normalizeText(user.email || '');
|
||||||
|
|
||||||
return events.filter(event => {
|
return events.filter(event => {
|
||||||
|
if (event.source !== 'local') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const hostEmail = normalizeText(event.hostEmail || '');
|
const hostEmail = normalizeText(event.hostEmail || '');
|
||||||
const hostName = normalizeText(event.host?.name || '');
|
const hostName = normalizeText(event.host?.name || '');
|
||||||
|
|
||||||
if (hostEmail && hostEmail === normalizeText(user.email)) {
|
if (hostEmail && hostEmail === userEmail) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,45 +411,73 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
function renderMyEvents(events, user) {
|
function renderMyEvents(events, user) {
|
||||||
const hostedEvents = getMyHostedEvents(events, user);
|
const hostedEvents = getMyHostedEvents(events, user);
|
||||||
myEventsCount.textContent = String(hostedEvents.length);
|
myEventsCount.textContent = String(hostedEvents.length);
|
||||||
renderEventCards(myEventsList, hostedEvents, 'Du hast noch kein eigenes Event erstellt.', false);
|
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');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rendert angemeldete Events inkl. Zaehler.
|
// Rendert angemeldete Events inkl. Zaehler.
|
||||||
function renderMyRegistrations(events, user) {
|
function renderMyRegistrations(events, user) {
|
||||||
const registeredEvents = getMyRegisteredEvents(events, user);
|
const registeredEvents = getMyRegisteredEvents(events, user);
|
||||||
myRegistrationsCount.textContent = String(registeredEvents.length);
|
myRegistrationsCount.textContent = String(registeredEvents.length);
|
||||||
renderEventCards(myRegistrationsList, registeredEvents, 'Du bist aktuell bei keinem Event angemeldet.', true);
|
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');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Baut die Eventkarten fuer beide Listen in einheitlichem Markup.
|
// Baut die Eventkarten fuer beide Listen in einheitlichem Markup.
|
||||||
function renderEventCards(container, events, emptyText, withUnregisterButton) {
|
function renderEventCards(container, events, emptyStateConfig, mode) {
|
||||||
container.innerHTML = '';
|
container.innerHTML = '';
|
||||||
|
|
||||||
if (events.length === 0) {
|
if (events.length === 0) {
|
||||||
const emptyElement = document.createElement('p');
|
const emptyElement = document.createElement('div');
|
||||||
emptyElement.className = 'profile-empty';
|
emptyElement.className = 'profile-empty-state';
|
||||||
emptyElement.textContent = emptyText;
|
emptyElement.innerHTML = `
|
||||||
|
<p class="profile-empty-kicker">Keine Treffer</p>
|
||||||
|
<h3>${emptyStateConfig.title}</h3>
|
||||||
|
<p>${emptyStateConfig.text}</p>
|
||||||
|
<a class="button" href="${emptyStateConfig.href}">${emptyStateConfig.buttonLabel}</a>
|
||||||
|
`;
|
||||||
container.appendChild(emptyElement);
|
container.appendChild(emptyElement);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
events.forEach(event => {
|
events.forEach(event => {
|
||||||
const card = document.createElement('article');
|
const card = document.createElement('article');
|
||||||
card.className = 'profile-event-card';
|
card.className = 'profile-event-card profile-event-card-clickable';
|
||||||
|
card.setAttribute('data-event-id', String(event.id));
|
||||||
|
const addressMarkup = mode === 'registrations' && event.address
|
||||||
|
? `
|
||||||
|
<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>
|
||||||
|
`
|
||||||
|
: '';
|
||||||
|
|
||||||
const actionMarkup = withUnregisterButton
|
const actionMarkup = mode === 'registrations'
|
||||||
? `
|
? `
|
||||||
<div class="profile-event-actions">
|
<div class="profile-event-actions">
|
||||||
<a class="profile-event-link" href="event_detail.html?id=${event.id}">Zum Event</a>
|
|
||||||
<button class="profile-unregister-btn" type="button" data-unregister-id="${event.id}">Abmelden</button>
|
<button class="profile-unregister-btn" type="button" data-unregister-id="${event.id}">Abmelden</button>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: `<a class="profile-event-link" href="event_detail.html?id=${event.id}">Zum Event</a>`;
|
: `
|
||||||
|
<div class="profile-event-actions">
|
||||||
|
<button class="profile-cancel-btn" type="button" data-cancel-event-id="${event.id}">Event absagen</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
card.innerHTML = `
|
card.innerHTML = `
|
||||||
<div>
|
<div>
|
||||||
<h3 class="profile-event-title">${event.title}</h3>
|
<h3 class="profile-event-title">${event.title}</h3>
|
||||||
<p class="profile-event-meta">${event.location} | ${formatEventDate(event.date)} | ${formatEventTime(event.time)}</p>
|
<p class="profile-event-meta">${event.location} | ${formatEventDate(event.date)} | ${formatEventTime(event.time)}</p>
|
||||||
|
${addressMarkup}
|
||||||
</div>
|
</div>
|
||||||
${actionMarkup}
|
${actionMarkup}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -44,7 +44,13 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="logged-in-content" class="profile-grid">
|
<section id="logged-in-content" class="profile-grid">
|
||||||
<article class="profile-panel">
|
<nav class="profile-tabs" aria-label="Profilbereiche">
|
||||||
|
<button type="button" class="profile-tab is-active" data-profile-tab="hosting">Hosting</button>
|
||||||
|
<button type="button" class="profile-tab" data-profile-tab="teilnehmen">Teilnehmen</button>
|
||||||
|
<button type="button" class="profile-tab" data-profile-tab="einstellungen">Einstellungen</button>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<article class="profile-panel" data-profile-panel="hosting">
|
||||||
<div class="panel-head">
|
<div class="panel-head">
|
||||||
<h2 class="panel-title">Meine Events</h2>
|
<h2 class="panel-title">Meine Events</h2>
|
||||||
<span id="my-events-count" class="panel-count">0</span>
|
<span id="my-events-count" class="panel-count">0</span>
|
||||||
@ -52,7 +58,7 @@
|
|||||||
<div id="my-events-list" class="profile-card-list"></div>
|
<div id="my-events-list" class="profile-card-list"></div>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article class="profile-panel">
|
<article class="profile-panel hidden" data-profile-panel="teilnehmen">
|
||||||
<div class="panel-head">
|
<div class="panel-head">
|
||||||
<h2 class="panel-title">Meine Anmeldungen</h2>
|
<h2 class="panel-title">Meine Anmeldungen</h2>
|
||||||
<span id="my-registrations-count" class="panel-count">0</span>
|
<span id="my-registrations-count" class="panel-count">0</span>
|
||||||
@ -60,7 +66,7 @@
|
|||||||
<div id="my-registrations-list" class="profile-card-list"></div>
|
<div id="my-registrations-list" class="profile-card-list"></div>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article class="profile-panel profile-panel-form">
|
<article class="profile-panel profile-panel-form hidden" data-profile-panel="einstellungen">
|
||||||
<h2 class="panel-title">Profil verwalten</h2>
|
<h2 class="panel-title">Profil verwalten</h2>
|
||||||
<form id="profile-form" novalidate>
|
<form id="profile-form" novalidate>
|
||||||
<div class="form-grid">
|
<div class="form-grid">
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user