/* ============================================================ BAN YARO — Moderations-Panel Nur für Moderatoren und Admins. ============================================================ */ window.Page_moderation = (() => { let _container = null; let _appState = null; let _tab = 'uebersicht'; const TABS = [ { id: 'uebersicht', label: 'Übersicht', icon: 'house-line' }, { id: 'fotos', label: 'Fotos', icon: 'image' }, { id: 'user', label: 'User', icon: 'users' }, { id: 'forum', label: 'Forum', icon: 'chat-circle-dots' }, ]; // ------------------------------------------------------------------ async function init(container, appState) { _container = container; _appState = appState; const u = appState.user; const isMod = u?.rolle === 'admin' || u?.rolle === 'moderator' || u?.is_moderator; if (!isMod) { container.innerHTML = _emptyState('shield', 'Kein Zugriff', 'Dieser Bereich ist nur für Moderatoren und Admins.'); return; } _render(); } function refresh() { _renderTab(); } function onDogChange() {} // ------------------------------------------------------------------ // SHELL // ------------------------------------------------------------------ function _render() { _container.innerHTML = `
${TABS.map(t => ` `).join('')}
`; _container.querySelector('#mod-tabs') ?.style.setProperty('--adm-tab-cols', Math.ceil(TABS.length / 2)); _container.querySelectorAll('#mod-tabs .by-tab').forEach(btn => { btn.addEventListener('click', () => { _tab = btn.dataset.tab; _container.querySelectorAll('#mod-tabs .by-tab').forEach(b => b.classList.toggle('active', b.dataset.tab === _tab) ); _renderTab(); }); }); _renderTab(); } async function _renderTab() { const el = _container.querySelector('#mod-content'); if (!el) return; el.innerHTML = `
Lade…
`; try { switch (_tab) { case 'uebersicht': await _renderStats(el); break; case 'fotos': await _renderFotos(el); break; case 'user': await _renderUsers(el); break; case 'forum': await _renderForum(el); break; } } catch (e) { el.innerHTML = _emptyState('warning', 'Fehler', e.message || 'Unbekannter Fehler.'); } } // ------------------------------------------------------------------ // TAB: ÜBERSICHT // ------------------------------------------------------------------ async function _renderStats(el) { const s = await API.get('/moderation/stats'); el.innerHTML = `
${_statCard('warning', 'Offene Meldungen', s.open_reports, s.open_reports > 0 ? 'var(--c-danger)' : 'var(--c-text-muted)')} ${_statCard('image', 'Fotos ausstehend', s.pending_fotos, s.pending_fotos > 0 ? 'var(--c-warning)' : 'var(--c-text-muted)')} ${_statCard('skull', 'Gesperrte User', s.banned_users, s.banned_users > 0 ? '#f59e0b' : 'var(--c-text-muted)')} ${_statCard('storefront', 'Züchter ausstehend', s.pending_zuchter, s.pending_zuchter > 0 ? 'var(--c-warning)' : 'var(--c-text-muted)')}

${UI.icon('info')} Das Moderations-Panel zeigt dir alle ausstehenden Aufgaben auf einen Blick. Verwende die Tabs oben für Details zu Fotos, Usern und Forum-Meldungen.

`; } function _statCard(icon, label, value, color) { return `
${value ?? '—'}
${label}
`; } // ------------------------------------------------------------------ // TAB: FOTOS // ------------------------------------------------------------------ async function _renderFotos(el) { el.innerHTML = `
Lade…
`; el.querySelector('#mod-fotos-refresh').addEventListener('click', () => _loadFotos(el.querySelector('#mod-fotos-list')) ); await _loadFotos(el.querySelector('#mod-fotos-list')); } async function _loadFotos(el) { el.innerHTML = `
Lade…
`; const fotos = await API.get('/moderation/fotos'); if (!fotos.length) { el.innerHTML = _emptyState('check-circle', 'Keine ausstehenden Fotos', 'Alle Foto-Einreichungen wurden bearbeitet.'); return; } el.innerHTML = `
${fotos.map(f => `
${_esc(f.rasse_name || f.rasse_slug)}
von ${_esc(f.user_name)}
${f.rights_confirmed ? `✓ Bildrechte bestätigt` : `⚠ Keine Bestätigung`}
${f.aktuell_foto ? ` Aktuell
aktuelles Foto
` : ''}
`).join('')}
`; el.querySelectorAll('.mod-foto-approve').forEach(btn => { btn.addEventListener('click', async () => { btn.disabled = true; try { await API.patch(`/moderation/fotos/${btn.dataset.id}`, { action: 'approve' }); UI.toast.success('Foto freigegeben.'); await _loadFotos(el); } catch (e) { UI.toast.error(e.message); btn.disabled = false; } }); }); el.querySelectorAll('.mod-foto-reject').forEach(btn => { btn.addEventListener('click', async () => { btn.disabled = true; try { await API.patch(`/moderation/fotos/${btn.dataset.id}`, { action: 'reject', reject_reason: 'Nicht geeignet.' }); UI.toast.success('Foto abgelehnt.'); await _loadFotos(el); } catch (e) { UI.toast.error(e.message); btn.disabled = false; } }); }); } // ------------------------------------------------------------------ // TAB: USER // ------------------------------------------------------------------ async function _renderUsers(el) { el.innerHTML = `
Lade…
`; const load = async () => { const q = el.querySelector('#mod-user-q').value; const banned = el.querySelector('#mod-only-banned').checked ? 1 : 0; const data = await API.get( `/moderation/users?q=${encodeURIComponent(q)}&banned=${banned}` ); _renderUserList(el.querySelector('#mod-user-list'), data.users, data.total, el); }; let timer; el.querySelector('#mod-user-q').addEventListener('input', () => { clearTimeout(timer); timer = setTimeout(load, 350); }); el.querySelector('#mod-only-banned').addEventListener('change', load); await load(); } function _renderUserList(el, users, total, parentEl) { if (!users.length) { el.innerHTML = _emptyState('users', 'Keine Nutzer gefunden', ''); return; } el.innerHTML = `
${total} Nutzer gefunden
${users.map(u => `
${_esc(u.name[0].toUpperCase())}
${_esc(u.name)} ${u.is_banned ? `GESPERRT` : ''}
${_esc(u.email)} · ${_esc(u.rolle)}
${u.is_banned ? `` : `` }
`).join('')}
`; el.querySelectorAll('.mod-ban').forEach(btn => { btn.addEventListener('click', () => _banUser(btn.dataset.uid, btn.dataset.name, true, parentEl)); }); el.querySelectorAll('.mod-unban').forEach(btn => { btn.addEventListener('click', () => _banUser(btn.dataset.uid, btn.dataset.name, false, parentEl)); }); } async function _banUser(uid, name, ban, parentEl) { if (ban) { const reason = window.prompt(`${name} sperren — Grund (optional):`); if (reason === null) return; try { await API.patch(`/moderation/users/${uid}`, { is_banned: 1, ban_reason: reason || 'Kein Grund angegeben.' }); UI.toast.success(`${name} gesperrt.`); _renderTab(); } catch (e) { UI.toast.error(e.message); } } else { try { await API.patch(`/moderation/users/${uid}`, { is_banned: 0, ban_reason: null }); UI.toast.success(`Sperre für ${name} aufgehoben.`); _renderTab(); } catch (e) { UI.toast.error(e.message); } } } // ------------------------------------------------------------------ // TAB: FORUM // ------------------------------------------------------------------ async function _renderForum(el) { el.innerHTML = `
Lade…
`; el.querySelector('#mod-forum-refresh').addEventListener('click', () => _loadReports(el.querySelector('#mod-forum-list')) ); await _loadReports(el.querySelector('#mod-forum-list')); } async function _loadReports(el) { el.innerHTML = `
Lade…
`; const reports = await API.get('/moderation/reports'); if (!reports.length) { el.innerHTML = _emptyState('check-circle', 'Keine offenen Meldungen', 'Alles sauber.'); return; } el.innerHTML = `
${reports.map(r => `
${_esc(r.target_type)} #${r.target_id} · Gemeldet von ${_esc(r.melder_name)}
Grund: ${_esc(r.grund)}
${r.content_preview ? `
${_esc(r.content_preview)}
` : ''}
`).join('')}
`; el.querySelectorAll('.mod-resolve-btn').forEach(btn => { btn.addEventListener('click', async () => { btn.disabled = true; try { await API.patch(`/moderation/reports/${btn.dataset.rid}`, {}); UI.toast.success('Meldung als erledigt markiert.'); await _loadReports(el); } catch (e) { UI.toast.error(e.message); btn.disabled = false; } }); }); } // ------------------------------------------------------------------ // HELPERS // ------------------------------------------------------------------ function _emptyState(icon, title, text) { return `

${title}

${text ? `

${text}

` : ''}
`; } function _esc(s) { if (!s) return ''; return String(s) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } // ------------------------------------------------------------------ return { init, refresh, onDogChange }; })();