Hashtags eingeführt
Some checks failed
Host-Based Deploy (Java 21 Fix) / build-and-run (push) Has been cancelled

This commit is contained in:
2026-04-11 01:14:33 +02:00
parent ec1409820b
commit e2a71ab096
57 changed files with 2365 additions and 740 deletions

View File

@@ -22,16 +22,9 @@
.gruppe-card-body { padding:0.85rem; flex:1; display:flex; flex-direction:column; gap:0.4rem; }
.gruppe-card-name { font-weight:700; font-size:1rem; }
.gruppe-card-meta { font-size:0.78rem; color:var(--color-muted); }
.gruppe-card-actions { display:flex; gap:0.5rem; flex-wrap:wrap; margin-top:auto; padding-top:0.5rem; }
.gruppe-card-actions button, .gruppe-card-actions a.btn { margin-top:0; padding:0.3rem 0.7rem; font-size:0.8rem; width:auto; }
.role-badge { font-size:0.7rem; font-weight:700; padding:0.1rem 0.4rem; border-radius:4px; background:var(--color-primary); color:#fff; display:inline-block; }
.role-badge.mitglied { background:var(--color-secondary); color:var(--color-text); }
.search-row { display:flex; gap:0.75rem; margin-bottom:1rem; }
.search-row input { flex:1; }
.search-row button { white-space:nowrap; width:auto; margin-top:0; }
.anfrage-list { list-style:none; margin:0; padding:0; }
.anfrage-item { display:flex; align-items:center; justify-content:space-between; gap:1rem; padding:0.75rem 0; border-bottom:1px solid var(--color-secondary); }
.anfrage-item:last-child { border-bottom:none; }
@@ -64,12 +57,14 @@
<div class="content">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:1.25rem;">
<h1 style="margin:0;">Gruppen</h1>
<button onclick="openCreateDialog()" style="width:auto; padding:0.4rem 1rem; font-size:0.9rem; margin:0;">+ Erstellen</button>
<div style="display:flex; gap:0.5rem;">
<button onclick="location.href='/search.html?tab=gruppen'" style="width:auto; padding:0.4rem 1rem; font-size:0.9rem; margin:0;">🔍 Suchen</button>
<button onclick="openCreateDialog()" style="width:auto; padding:0.4rem 1rem; font-size:0.9rem; margin:0;">+ Erstellen</button>
</div>
</div>
<div class="tabs">
<button class="tab-btn active" data-tab="mine" onclick="switchTab('mine', this)">Meine Gruppen</button>
<button class="tab-btn" data-tab="discover" onclick="switchTab('discover', this)">Entdecken</button>
<button class="tab-btn" data-tab="requests" onclick="switchTab('requests', this)">Meine Anfragen</button>
</div>
@@ -79,16 +74,6 @@
<p class="empty-hint" id="mineEmpty" style="display:none;">Du bist noch in keiner Gruppe.</p>
</div>
<!-- Entdecken -->
<div class="tab-panel" id="tab-discover">
<div class="search-row">
<input type="text" id="searchInput" placeholder="Gruppenname suchen…" onkeydown="if(event.key==='Enter')doSearch()">
<button onclick="doSearch()">Suchen</button>
</div>
<div class="gruppe-grid" id="discoverGrid"></div>
<p class="empty-hint" id="discoverHint">Gib einen Suchbegriff ein.</p>
</div>
<!-- Meine Anfragen -->
<div class="tab-panel" id="tab-requests">
<ul class="anfrage-list" id="requestsList"></ul>
@@ -121,20 +106,6 @@
</div>
</div>
<!-- Beitrittsanfrage Dialog -->
<div class="dialog-backdrop" id="joinDialog">
<div class="dialog">
<h3>Beitrittsanfrage senden</h3>
<p id="joinDialogGroupName" style="font-weight:600; margin-bottom:0.5rem;"></p>
<label>Nachricht (optional)</label>
<textarea id="joinNachricht" placeholder="Warum möchtest du beitreten?"></textarea>
<p class="message error" id="joinError" style="display:none; margin-top:0.75rem;"></p>
<div class="dialog-actions">
<button class="secondary" onclick="closeJoinDialog()">Abbrechen</button>
<button onclick="sendJoinRequest()">Anfrage senden</button>
</div>
</div>
</div>
<script src="/js/icons.js"></script>
<script src="/js/nav.js"></script>
@@ -157,7 +128,7 @@
function esc(s) { const d = document.createElement('div'); d.textContent = s ?? ''; return d.innerHTML; }
function gruppeCard(g, showJoin = false) {
function gruppeCard(g) {
const img = g.bild
? `<div class="gruppe-card-img"><img src="data:image/jpeg;base64,${g.bild}" alt=""></div>`
: `<div class="gruppe-card-img">👥</div>`;
@@ -165,16 +136,6 @@
? `<span class="role-badge ${g.myRole === 'ADMIN' ? '' : 'mitglied'}">${g.myRole === 'ADMIN' ? 'Admin' : 'Mitglied'}</span>`
: '';
const privBadge = g.isPrivate ? ' 🔒' : '';
let actions = '';
if (showJoin && !g.myRole) {
if (g.myRequestStatus === 'AUSSTEHEND') {
actions = `<button disabled style="opacity:0.6;" onclick="event.stopPropagation()">Anfrage ausstehend</button>`;
} else if (g.isPrivate) {
actions = `<button onclick="event.stopPropagation(); openJoinDialog('${g.gruppeId}','${esc(g.name)}')">Anfrage senden</button>`;
} else {
actions = `<button onclick="event.stopPropagation(); joinGruppe('${g.gruppeId}', this)">Beitreten</button>`;
}
}
return `
<div class="gruppe-card" id="gc-${g.gruppeId}" onclick="location.href='/community/gruppe.html?gruppeId=${g.gruppeId}'" style="cursor:pointer;">
${img}
@@ -183,7 +144,6 @@
<div class="gruppe-card-meta">${g.memberCount} Mitglied${g.memberCount !== 1 ? 'er' : ''} · ${g.postCount} Beiträge</div>
${g.beschreibung ? `<div style="font-size:0.82rem;color:var(--color-muted);">${esc(g.beschreibung.substring(0,80))}${g.beschreibung.length>80?'…':''}</div>` : ''}
<div class="card-notif" id="notif-${g.gruppeId}"></div>
${actions ? `<div class="gruppe-card-actions">${actions}</div>` : ''}
</div>
</div>`;
}
@@ -223,37 +183,6 @@
}));
}
async function doSearch() {
const q = document.getElementById('searchInput').value.trim();
if (!q) return;
try {
const res = await fetch('/gruppen/search?q=' + encodeURIComponent(q));
if (!res.ok) return;
const data = await res.json();
const grid = document.getElementById('discoverGrid');
const hint = document.getElementById('discoverHint');
grid.innerHTML = '';
if (data.length === 0) { hint.textContent = 'Keine Gruppen gefunden.'; hint.style.display = ''; return; }
hint.style.display = 'none';
data.forEach(g => grid.insertAdjacentHTML('beforeend', gruppeCard(g, true)));
} catch(e) { console.error(e); }
}
async function joinGruppe(gruppeId, btn) {
btn.disabled = true;
btn.textContent = '…';
try {
const res = await fetch('/gruppen/' + gruppeId + '/join', { method: 'POST', headers:{'Content-Type':'application/json'}, body:'{}' });
if (res.ok || res.status === 201) {
btn.textContent = 'Beigetreten ✓';
setTimeout(loadMine, 500);
} else {
btn.disabled = false;
btn.textContent = 'Beitreten';
}
} catch(e) { btn.disabled = false; btn.textContent = 'Beitreten'; }
}
async function loadRequests() {
try {
const reqRes = await fetch('/gruppen/requests/mine');
@@ -333,42 +262,6 @@
} catch(e) { showCreateError('Fehler: ' + e.message); }
}
// ── Join dialog ──
let pendingJoinGruppeId = null;
function openJoinDialog(gruppeId, name) {
pendingJoinGruppeId = gruppeId;
document.getElementById('joinDialogGroupName').textContent = name;
document.getElementById('joinNachricht').value = '';
document.getElementById('joinDialog').classList.add('visible');
}
function closeJoinDialog() { document.getElementById('joinDialog').classList.remove('visible'); pendingJoinGruppeId = null; }
async function sendJoinRequest() {
if (!pendingJoinGruppeId) return;
document.getElementById('joinError').style.display = 'none';
const nachricht = document.getElementById('joinNachricht').value.trim() || null;
try {
const res = await fetch('/gruppen/' + pendingJoinGruppeId + '/join', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({ nachricht })
});
if (res.ok || res.status === 201) {
closeJoinDialog();
doSearch();
} else {
const el = document.getElementById('joinError');
el.textContent = 'Fehler beim Senden der Anfrage.';
el.style.display = 'block';
}
} catch(e) {
const el = document.getElementById('joinError');
el.textContent = 'Fehler: ' + e.message;
el.style.display = 'block';
}
}
// ── Image preview ──
function previewBild(input, previewId, dataId) {
@@ -401,9 +294,6 @@
document.getElementById('createDialog').addEventListener('click', e => {
if (e.target === document.getElementById('createDialog')) closeCreateDialog();
});
document.getElementById('joinDialog').addEventListener('click', e => {
if (e.target === document.getElementById('joinDialog')) closeJoinDialog();
});
loadMine();
</script>