An den Verantaltungen und Locations gearbeitet
Some checks failed
Host-Based Deploy (Java 21 Fix) / build-and-run (push) Has been cancelled

This commit is contained in:
2026-04-05 23:04:09 +02:00
parent b81ad25c9f
commit 0f9f109067
65 changed files with 5260 additions and 3 deletions

View File

@@ -0,0 +1,177 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/img/icon.png" type="image/png">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Veranstaltung xXx Sphere</title>
<link rel="stylesheet" href="/css/variables.css">
<link rel="stylesheet" href="/css/style.css">
<style>
.back-link { display:inline-flex; align-items:center; gap:0.35rem; color:var(--color-muted); font-size:0.88rem; text-decoration:none; margin-bottom:1rem; }
.back-link:hover { color:var(--color-primary); }
.evt-header { display:flex; gap:1rem; align-items:flex-start; margin-bottom:1.25rem; flex-wrap:wrap; }
.evt-img { width:120px; height:120px; border-radius:12px; background:var(--color-secondary); object-fit:cover; flex-shrink:0; display:flex; align-items:center; justify-content:center; font-size:3rem; overflow:hidden; border:2px solid var(--color-secondary); }
.evt-img img { width:100%; height:100%; object-fit:cover; }
.evt-meta { flex:1; min-width:0; }
.evt-title { font-size:1.4rem; font-weight:700; margin:0 0 0.3rem; }
.evt-location { color:var(--color-muted); font-size:0.88rem; margin-bottom:0.2rem; }
.evt-date { font-size:0.88rem; color:var(--color-muted); margin-bottom:0.5rem; }
.evt-desc { font-size:0.93rem; line-height:1.55; white-space:pre-wrap; word-break:break-word; margin-top:0.5rem; }
.attend-btn { display:inline-flex; align-items:center; gap:0.4rem; margin-top:0.75rem; }
.section-title { font-size:1rem; font-weight:700; margin:1.5rem 0 0.75rem; }
.gender-group { margin-bottom:1.25rem; }
.gender-label { font-size:0.78rem; font-weight:700; text-transform:uppercase; letter-spacing:0.06em; color:var(--color-muted); margin-bottom:0.5rem; }
.attendee-list { display:flex; flex-wrap:wrap; gap:0.6rem; }
.attendee-chip { display:flex; align-items:center; gap:0.5rem; background:var(--color-card); border:1px solid var(--color-secondary); border-radius:20px; padding:0.3rem 0.6rem 0.3rem 0.3rem; text-decoration:none; color:inherit; transition:border-color 0.15s; font-size:0.85rem; }
.attendee-chip:hover { border-color:var(--color-primary); }
.attendee-avatar { width:28px; height:28px; border-radius:50%; background:var(--color-secondary); object-fit:cover; flex-shrink:0; overflow:hidden; display:flex; align-items:center; justify-content:center; font-size:0.8rem; }
.attendee-avatar img { width:100%; height:100%; object-fit:cover; }
.count-badge { background:var(--color-secondary); border-radius:12px; padding:0.15rem 0.6rem; font-size:0.78rem; color:var(--color-muted); margin-left:0.25rem; display:inline-block; }
</style>
</head>
<body>
<div class="main">
<a id="backLink" href="/community/events.html" class="back-link">← Veranstaltungen</a>
<div id="content">
<p style="color:var(--color-muted);">Wird geladen…</p>
</div>
</div>
<script src="/js/sidebar.js"></script>
<script src="/js/icons.js"></script>
<script>
const params = new URLSearchParams(location.search);
const eventId = params.get('id');
let myUserId = null;
function escHtml(s) {
return (s || '').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}
function formatDate(dt) {
if (!dt) return '';
const d = new Date(dt);
return d.toLocaleDateString('de-DE', { weekday:'long', day:'2-digit', month:'long', year:'numeric' })
+ ', ' + d.toLocaleTimeString('de-DE', { hour:'2-digit', minute:'2-digit' }) + ' Uhr';
}
const GENDER_LABELS = {
WEIBLICH: 'Frauen',
MAENNLICH: 'Männer',
DIVERS: 'Divers',
UNBEKANNT: 'Sonstiges'
};
async function loadPage() {
if (!eventId) { document.getElementById('content').innerHTML = '<p>Keine Event-ID angegeben.</p>'; return; }
const [meRes, evtRes] = await Promise.all([
fetch('/login/me'),
fetch(`/location-events/${eventId}`)
]);
if (!evtRes.ok) { document.getElementById('content').innerHTML = '<p>Veranstaltung nicht gefunden.</p>'; return; }
if (meRes.ok) { const me = await meRes.json(); myUserId = me.userId; }
const evt = await evtRes.json();
// Rücklink zur Location
const backLink = document.getElementById('backLink');
if (evt.locationId) {
backLink.href = `/community/location-detail.html?id=${evt.locationId}`;
backLink.textContent = `${escHtml(evt.locationName) || 'Location'}`;
}
document.title = `${evt.title} xXx Sphere`;
renderPage(evt);
}
function renderPage(evt) {
const imgHtml = evt.imageData
? `<img src="data:image/jpeg;base64,${evt.imageData}" alt="${escHtml(evt.title)}">`
: '🗓';
// Teilnehmende nach Geschlecht gruppieren
const byGender = {};
(evt.attendees || []).forEach(a => {
const g = a.geschlecht || 'UNBEKANNT';
if (!byGender[g]) byGender[g] = [];
byGender[g].push(a);
});
const genderOrder = ['WEIBLICH', 'MAENNLICH', 'DIVERS', 'UNBEKANNT'];
const attendeesHtml = genderOrder
.filter(g => byGender[g] && byGender[g].length > 0)
.map(g => {
const chips = byGender[g].map(a => {
const avatarHtml = a.profilePictureLq
? `<img src="data:image/jpeg;base64,${a.profilePictureLq}" alt="${escHtml(a.name)}">`
: a.name.charAt(0).toUpperCase();
return `<a class="attendee-chip" href="/community/benutzer.html?userId=${a.userId}">
<div class="attendee-avatar">${avatarHtml}</div>
${escHtml(a.name)}
</a>`;
}).join('');
return `<div class="gender-group">
<div class="gender-label">${GENDER_LABELS[g] || g} <span class="count-badge">${byGender[g].length}</span></div>
<div class="attendee-list">${chips}</div>
</div>`;
}).join('');
const totalAttendees = (evt.attendees || []).length;
const attending = evt.attendingMe;
document.getElementById('content').innerHTML = `
<div class="evt-header">
<div class="evt-img">${imgHtml}</div>
<div class="evt-meta">
<div class="evt-title">${escHtml(evt.title)}</div>
${evt.locationName ? `<div class="evt-location">📍 <a href="/community/location-detail.html?id=${evt.locationId}" style="color:inherit;text-decoration:none;">${escHtml(evt.locationName)}</a></div>` : ''}
<div class="evt-date">🗓 ${formatDate(evt.startAt)}</div>
${evt.description ? `<div class="evt-desc">${escHtml(evt.description)}</div>` : ''}
<div class="attend-btn">
<button class="btn" id="attendBtn"
style="${attending ? 'background:var(--color-secondary);color:var(--color-text);' : ''}"
onclick="toggleAttend()">
${attending ? '✓ Ich bin dabei' : '+ Ich bin dabei'}
</button>
<span style="color:var(--color-muted);font-size:0.85rem;" id="attendCount">${totalAttendees} Teilnehmer*in(nen)</span>
</div>
</div>
</div>
${totalAttendees > 0 ? `
<div class="section-title">Teilnehmende</div>
${attendeesHtml}
` : '<p style="color:var(--color-muted);font-size:0.9rem;margin-top:1rem;">Noch keine Teilnehmenden.</p>'}
`;
}
async function toggleAttend() {
const res = await fetch(`/location-events/${eventId}/attend`, { method: 'POST' });
if (!res.ok) { alert('Fehler beim Aktualisieren.'); return; }
const data = await res.json();
const btn = document.getElementById('attendBtn');
const countEl = document.getElementById('attendCount');
if (btn) {
btn.textContent = data.attending ? '✓ Ich bin dabei' : '+ Ich bin dabei';
btn.style.background = data.attending ? 'var(--color-secondary)' : '';
btn.style.color = data.attending ? 'var(--color-text)' : '';
}
if (countEl) countEl.textContent = `${data.attendeeCount} Teilnehmer*in(nen)`;
// Teilnehmendenliste neu laden
const evtRes = await fetch(`/location-events/${eventId}`);
if (evtRes.ok) { renderPage(await evtRes.json()); }
}
loadPage();
</script>
</body>
</html>