From d04110c2aebb041f81797c556dec226029d204e8 Mon Sep 17 00:00:00 2001 From: rene Date: Fri, 1 May 2026 19:29:23 +0200 Subject: [PATCH] =?UTF-8?q?Feature:=20Admin=20Action-Items-Karte=20=C3=BCb?= =?UTF-8?q?er=20Tabs,=20SW=20by-v587?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/routes/admin.py | 34 ++++++++++++++++++ backend/static/js/app.js | 2 +- backend/static/js/pages/admin.js | 62 ++++++++++++++++++++++++++++++++ backend/static/sw.js | 2 +- 4 files changed, 98 insertions(+), 2 deletions(-) diff --git a/backend/routes/admin.py b/backend/routes/admin.py index 09a4127..cd3fee1 100644 --- a/backend/routes/admin.py +++ b/backend/routes/admin.py @@ -97,6 +97,40 @@ class ThreadAdminPatch(BaseModel): is_deleted: Optional[int] = None +# ------------------------------------------------------------------ +# GET /api/admin/action-items +# ------------------------------------------------------------------ +@router.get("/action-items") +async def action_items(user=Depends(require_mod)): + with db() as conn: + jobs = conn.execute( + "SELECT COUNT(*) FROM job_applications WHERE status IN ('pending','reviewing')" + ).fetchone()[0] + breeders = conn.execute( + "SELECT COUNT(*) FROM users WHERE breeder_status='pending'" + ).fetchone()[0] + reports = conn.execute( + "SELECT COUNT(*) FROM forum_reports WHERE resolved=0" + ).fetchone()[0] + fotos = conn.execute( + "SELECT COUNT(*) FROM wiki_foto_submissions WHERE status='pending'" + ).fetchone()[0] + poi_edits = conn.execute( + "SELECT COUNT(*) FROM osm_poi_edits WHERE status='pending'" + ).fetchone()[0] + users_today = conn.execute( + "SELECT COUNT(*) FROM users WHERE DATE(created_at)=DATE('now')" + ).fetchone()[0] + return { + "jobs_pending": jobs, + "breeder_pending": breeders, + "reports_open": reports, + "fotos_pending": fotos, + "poi_edits_pending": poi_edits, + "users_today": users_today, + } + + # ------------------------------------------------------------------ # GET /api/admin/stats # ------------------------------------------------------------------ diff --git a/backend/static/js/app.js b/backend/static/js/app.js index a4b6b3a..d060792 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 = '586'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen +const APP_VER = '587'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen const APP_VERSION = '1.2.1'; // ← semantische Version, wird bei make release gesetzt const IS_STAGING = location.hostname === 'staging.banyaro.app'; diff --git a/backend/static/js/pages/admin.js b/backend/static/js/pages/admin.js index 4734713..8a3b6c8 100644 --- a/backend/static/js/pages/admin.js +++ b/backend/static/js/pages/admin.js @@ -48,6 +48,9 @@ window.Page_admin = (() => { // ------------------------------------------------------------------ function _render() { _container.innerHTML = ` + +
+
${TABS.map(t => ` @@ -73,9 +76,68 @@ window.Page_admin = (() => { }); }); + _renderActionItems(); _renderTab(); } + async function _renderActionItems() { + const el = _container.querySelector('#adm-action-items'); + if (!el) return; + let d; + try { d = await API.get('/admin/action-items'); } catch { return; } + + const items = [ + { key: 'jobs_pending', label: 'Bewerbungen', tab: 'bewerbungen', icon: 'user-plus' }, + { key: 'breeder_pending', label: 'Züchter-Anträge', tab: 'zuchter', icon: 'certificate' }, + { key: 'reports_open', label: 'Meldungen', tab: 'moderation', icon: 'warning' }, + { key: 'fotos_pending', label: 'Foto-Einreichungen',tab: 'moderation', icon: 'image' }, + { key: 'poi_edits_pending', label: 'POI-Korrekturen', tab: 'moderation', icon: 'map-pin' }, + ]; + + const open = items.filter(i => d[i.key] > 0); + const usersToday = d.users_today || 0; + + el.innerHTML = ` +
+ + ${UI.icon('check-square')} Zu erledigen + + ${open.length === 0 + ? ` + ${UI.icon('check-circle')} Alles erledigt + ` + : open.map(i => ` + `).join('') + } + + ${UI.icon('user-plus')} ${usersToday} neue Nutzer heute + +
`; + + el.querySelectorAll('[data-action-tab]').forEach(btn => { + btn.addEventListener('click', () => { + _tab = btn.dataset.actionTab; + _container.querySelectorAll('#adm-tabs .by-tab').forEach(b => + b.classList.toggle('active', b.dataset.tab === _tab) + ); + _renderTab(); + }); + }); + } + async function _renderTab() { const el = _container.querySelector('#adm-content'); if (!el) return; diff --git a/backend/static/sw.js b/backend/static/sw.js index 2aa608e..362b77a 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-v586'; +const CACHE_VERSION = 'by-v587'; const CACHE_STATIC = `${CACHE_VERSION}-static`; const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten const CACHE_API = 'ban-yaro-api-v1'; // API-Response-Cache