From 2b442ebd98053b01b570130401c9bceac92d9623 Mon Sep 17 00:00:00 2001 From: rene Date: Tue, 21 Apr 2026 19:44:06 +0200 Subject: [PATCH] =?UTF-8?q?Admin:=20Moderation-Tab=20f=C3=BCr=20Z=C3=BCcht?= =?UTF-8?q?er=20+=20Wiki-Fotos=20(SW=20by-v280)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/static/js/app.js | 2 +- backend/static/js/pages/admin.js | 157 ++++++++++++++++++++++++++++--- backend/static/sw.js | 2 +- 3 files changed, 145 insertions(+), 16 deletions(-) diff --git a/backend/static/js/app.js b/backend/static/js/app.js index 3c028aa..a20c012 100644 --- a/backend/static/js/app.js +++ b/backend/static/js/app.js @@ -3,7 +3,7 @@ Router, State-Management, Navigation, Initialisierung. ============================================================ */ -const APP_VER = '267'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen +const APP_VER = '268'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen const App = (() => { diff --git a/backend/static/js/pages/admin.js b/backend/static/js/pages/admin.js index ecd08ca..0dbd689 100644 --- a/backend/static/js/pages/admin.js +++ b/backend/static/js/pages/admin.js @@ -10,13 +10,14 @@ window.Page_admin = (() => { let _tab = 'uebersicht'; const TABS = [ - { id: 'uebersicht', label: 'Übersicht', icon: 'house-line' }, - { id: 'nutzer', label: 'Nutzer', icon: 'users' }, - { id: 'forum', label: 'Forum & Meldungen', icon: 'chat-circle-dots' }, - { id: 'analytics', label: 'Analytics', icon: 'target' }, - { id: 'system', label: 'System', icon: 'gear' }, - { id: 'jobs', label: 'Jobs', icon: 'clock' }, - { id: 'audit', label: 'Audit-Log', icon: 'clipboard-text' }, + { id: 'uebersicht', label: 'Übersicht', icon: 'house-line' }, + { id: 'nutzer', label: 'Nutzer', icon: 'users' }, + { id: 'moderation', label: 'Moderation', icon: 'shield-check' }, + { id: 'forum', label: 'Forum & Meldungen', icon: 'chat-circle-dots' }, + { id: 'analytics', label: 'Analytics', icon: 'target' }, + { id: 'system', label: 'System', icon: 'gear' }, + { id: 'jobs', label: 'Jobs', icon: 'clock' }, + { id: 'audit', label: 'Audit-Log', icon: 'clipboard-text' }, ]; // ------------------------------------------------------------------ @@ -74,13 +75,14 @@ window.Page_admin = (() => { el.innerHTML = `
Lade…
`; try { switch (_tab) { - case 'uebersicht': await _renderStats(el); break; - case 'nutzer': await _renderUsers(el); break; - case 'forum': await _renderForum(el); break; - case 'analytics': await _renderAnalytics(el); break; - case 'system': await _renderSystem(el); break; - case 'jobs': await _renderJobs(el); break; - case 'audit': await _renderAudit(el); break; + case 'uebersicht': await _renderStats(el); break; + case 'nutzer': await _renderUsers(el); break; + case 'moderation': await _renderModeration(el); break; + case 'forum': await _renderForum(el); break; + case 'analytics': await _renderAnalytics(el); break; + case 'system': await _renderSystem(el); break; + case 'jobs': await _renderJobs(el); break; + case 'audit': await _renderAudit(el); break; } } catch (e) { el.innerHTML = _emptyState('warning', 'Fehler', e.message || 'Unbekannter Fehler.'); @@ -737,6 +739,133 @@ window.Page_admin = (() => { // ------------------------------------------------------------------ // TAB: JOBS + // ------------------------------------------------------------------ + // TAB: MODERATION + // ------------------------------------------------------------------ + async function _renderModeration(el) { + el.innerHTML = ` +
+ +
+
Lade…
+ `; + el.querySelector('#adm-mod-refresh').addEventListener('click', () => _loadModeration(el.querySelector('#adm-mod-content'))); + await _loadModeration(el.querySelector('#adm-mod-content')); + } + + async function _loadModeration(el) { + el.innerHTML = `
Lade…
`; + + const [zuchter, fotos] = await Promise.all([ + API.get('/wiki/zuchter/pending').catch(() => []), + API.get('/wiki/foto-submissions').catch(() => []), + ]); + + let html = ''; + + // --- Züchter-Einreichungen --- + html += `

+ Züchter-Einreichungen + ${zuchter.length} +

`; + + if (!zuchter.length) { + html += `

Keine ausstehenden Einreichungen.

`; + } else { + html += `
+ + + + + ${zuchter.map((z, i) => ` + + + + + + + + `).join('')} +
RasseName / ZwingernameOrtVDHWebsite
${_esc(z.rasse_slug)}${_esc(z.name)}${z.zwingername ? `
${_esc(z.zwingername)}` : ''}
${_esc([z.plz, z.ort, z.bundesland].filter(Boolean).join(' '))}${z.vdh_mitglied ? '✓ VDH' : '—'}${z.website ? `Link` : '—'} + + +
`; + } + + // --- Wiki-Foto-Einreichungen --- + html += `

+ Wiki-Foto-Einreichungen + ${fotos.length} +

`; + + if (!fotos.length) { + html += `

Keine ausstehenden Foto-Einreichungen.

`; + } else { + html += `
+ ${fotos.map(f => ` +
+ +
${_esc(f.rasse_name)}
+
von ${_esc(f.user_name)}
+ ${f.aktuell_foto ? `Aktuell +
↑ aktuelles Foto
` : ''} +
+ + +
+
`).join('')} +
`; + } + + el.innerHTML = html; + + // Züchter freigeben + el.querySelectorAll('.adm-zuchter-approve').forEach(btn => { + btn.addEventListener('click', async () => { + btn.disabled = true; + await API.patch(`/wiki/zuchter/${btn.dataset.id}/verify`, {}); + await _loadModeration(el); + }); + }); + + // Züchter löschen + el.querySelectorAll('.adm-zuchter-delete').forEach(btn => { + btn.addEventListener('click', async () => { + if (!window.confirm('Eintrag löschen?')) return; + btn.disabled = true; + await API.delete(`/admin/wiki/zuchter/${btn.dataset.id}`); + await _loadModeration(el); + }); + }); + + // Foto freigeben + el.querySelectorAll('.adm-foto-approve').forEach(btn => { + btn.addEventListener('click', async () => { + btn.disabled = true; + await API.patch(`/wiki/foto-submissions/${btn.dataset.id}`, {action: 'approve'}); + await _loadModeration(el); + }); + }); + + // Foto ablehnen + el.querySelectorAll('.adm-foto-reject').forEach(btn => { + btn.addEventListener('click', async () => { + btn.disabled = true; + await API.patch(`/wiki/foto-submissions/${btn.dataset.id}`, {action: 'reject', reject_reason: 'Nicht geeignet.'}); + await _loadModeration(el); + }); + }); + } + // ------------------------------------------------------------------ async function _renderJobs(el) { el.innerHTML = ` diff --git a/backend/static/sw.js b/backend/static/sw.js index e59302e..b38cdaf 100644 --- a/backend/static/sw.js +++ b/backend/static/sw.js @@ -3,7 +3,7 @@ Offline-Cache + Push Notifications + Tile-Cache ============================================================ */ -const CACHE_VERSION = 'by-v279'; +const CACHE_VERSION = 'by-v280'; const CACHE_STATIC = `${CACHE_VERSION}-static`; const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten