diff --git a/backend/main.py b/backend/main.py index 8e75ede..9f5a2c4 100644 --- a/backend/main.py +++ b/backend/main.py @@ -406,7 +406,7 @@ async def serve_media(path: str, request: _Request): raise _HE(404, "Nicht gefunden.") return _media_response(filepath) -APP_VER = "920" # muss mit APP_VER in app.js übereinstimmen +APP_VER = "921" # muss mit APP_VER in app.js übereinstimmen @app.get("/.well-known/assetlinks.json") async def assetlinks(): diff --git a/backend/routes/breeder.py b/backend/routes/breeder.py index 1afe535..3728277 100644 --- a/backend/routes/breeder.py +++ b/backend/routes/breeder.py @@ -183,6 +183,27 @@ async def admin_pending_breeders(admin=Depends(require_admin)): return [dict(r) for r in rows] +# ------------------------------------------------------------------ +# GET /api/admin/breeders — alle aktiven Züchter +# ------------------------------------------------------------------ +@router.get("/admin/breeders") +async def admin_all_breeders(admin=Depends(require_admin)): + with db() as conn: + rows = conn.execute(""" + SELECT u.id, u.name, u.email, u.created_at, u.subscription_tier, + u.breeder_status, u.last_login, + bp.zwingername, bp.rasse_text, bp.verein, bp.vdh_mitglied, + bp.stadt, bp.website, bp.verified_at, + (SELECT COUNT(*) FROM litters WHERE user_id=u.id) AS wuerfe_count, + (SELECT COUNT(*) FROM dogs WHERE user_id=u.id AND is_zucht_hund=1) AS zuchthunde_count + FROM users u + LEFT JOIN breeder_profiles bp ON bp.user_id = u.id + WHERE u.rolle = 'breeder' OR u.breeder_status = 'approved' + ORDER BY bp.verified_at DESC NULLS LAST, u.created_at DESC + """).fetchall() + return [dict(r) for r in rows] + + # ------------------------------------------------------------------ # GET /api/admin/breeder/{user_id}/documents — Dokumente eines Antrags # ------------------------------------------------------------------ diff --git a/backend/static/js/api.js b/backend/static/js/api.js index 62b0056..f75b2cf 100644 --- a/backend/static/js/api.js +++ b/backend/static/js/api.js @@ -691,6 +691,7 @@ const API = (() => { updateProfile(data) { return put('/breeder/profile', data); }, adminCreateProfile() { return post('/admin/breeder/create-profile', {}); }, pendingList() { return get('/admin/breeders/pending'); }, + allList() { return get('/admin/breeders'); }, documents(userId) { return get(`/admin/breeder/${userId}/documents`); }, documentUrl(userId, docId) { return `/api/admin/breeder/${userId}/document/${docId}`; }, approve(userId) { return post(`/admin/breeder/${userId}/approve`, {}); }, diff --git a/backend/static/js/app.js b/backend/static/js/app.js index 2860ab3..1aa2c36 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 = '920'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen +const APP_VER = '921'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen const APP_VERSION = '1.5.1'; // ← semantische Version, wird bei make release gesetzt const IS_STAGING = location.hostname === 'staging.banyaro.app'; // Cache-Bust-Parameter nach Update-Reload sofort entfernen diff --git a/backend/static/js/pages/admin.js b/backend/static/js/pages/admin.js index e95aa34..0f03bb4 100644 --- a/backend/static/js/pages/admin.js +++ b/backend/static/js/pages/admin.js @@ -1893,12 +1893,17 @@ window.Page_admin = (() => { ${UI.icon('arrows-clockwise')} Aktualisieren -
Lade…
+
Lade…
+
Lade…
`; - el.querySelector('#adm-zuchter-refresh').addEventListener('click', () => - _loadZuechterAntraege(el.querySelector('#adm-zuchter-list')) - ); - await _loadZuechterAntraege(el.querySelector('#adm-zuchter-list')); + el.querySelector('#adm-zuchter-refresh').addEventListener('click', () => { + _loadZuechterAntraege(el.querySelector('#adm-zuchter-antraege')); + _loadZuechterListe(el.querySelector('#adm-zuchter-liste')); + }); + await Promise.all([ + _loadZuechterAntraege(el.querySelector('#adm-zuchter-antraege')), + _loadZuechterListe(el.querySelector('#adm-zuchter-liste')), + ]); } async function _loadZuechterAntraege(el) { @@ -1912,12 +1917,20 @@ window.Page_admin = (() => { } if (!antraege.length) { - el.innerHTML = _emptyState('certificate', 'Keine offenen Anträge', 'Aktuell liegen keine Züchter-Anträge zur Prüfung vor.'); + el.innerHTML = `
+
Offene Anträge
+
+ ${UI.icon('check-circle')} Keine offenen Anträge +
+
`; return; } el.innerHTML = ` -
+
+
Offene Anträge (${antraege.length})
+
+
${antraege.map(a => `
@@ -2072,6 +2085,74 @@ window.Page_admin = (() => { }); } + async function _loadZuechterListe(el) { + el.innerHTML = `
Lade…
`; + let breeders; + try { + breeders = await API.breeder.allList(); + } catch (e) { + el.innerHTML = _emptyState('warning', 'Fehler', e.message); + return; + } + + const tierBadge = t => { + if (t === 'breeder') return `Züchter-Abo`; + if (t === 'breeder_test') return `Test`; + return `Standard`; + }; + + const rows = breeders.map(b => ` + + +
${_esc(b.name)}
+
${_esc(b.email)}
+ + ${_esc(b.zwingername || '—')} + ${_esc(b.rasse_text || '—')} + ${_esc(b.stadt || '—')} + + ${b.wuerfe_count || 0} Würfe
+ ${b.zuchthunde_count || 0} Zuchthunde + + ${tierBadge(b.subscription_tier)} + + ${b.verified_at ? new Date(b.verified_at).toLocaleDateString('de-DE') : '—'} + + + + + `).join(''); + + el.innerHTML = ` +
+
Alle Züchter (${breeders.length})
+
+ + + ${['Nutzer','Zwingername','Rasse','Stadt','Aktivität','Abo','Seit',''].map(h => + `` + ).join('')} + + + ${rows || ``} + +
${h}
Noch keine Züchter
+
+
`; + + el.querySelectorAll('.adm-breeder-tier-btn').forEach(btn => { + btn.addEventListener('click', () => + _changeTier(btn.dataset.uid, btn.dataset.name, btn.dataset.tier) + ); + }); + } + // ------------------------------------------------------------------ async function _renderJobs(el) { el.innerHTML = ` diff --git a/backend/static/sw.js b/backend/static/sw.js index 7fe3d87..a33205f 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-v920'; +const CACHE_VERSION = 'by-v921'; 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