diff --git a/backend/main.py b/backend/main.py
index 9ab4a1e..8beb2ba 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 = "899" # muss mit APP_VER in app.js übereinstimmen
+APP_VER = "900" # 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 22e6fde..2f3dac4 100644
--- a/backend/routes/breeder.py
+++ b/backend/routes/breeder.py
@@ -301,7 +301,7 @@ async def admin_reject_breeder(user_id: int, body: RejectBody, admin=Depends(req
# ------------------------------------------------------------------
-# GET /api/breeder/profil/{zwingername} — öffentliches Profil
+# GET /api/breeder/profil/{zwingername} — öffentliches Profil (angereichert)
# ------------------------------------------------------------------
@router.get("/breeder/profil/{zwingername}")
async def breeder_public_profile(zwingername: str):
@@ -318,9 +318,84 @@ async def breeder_public_profile(zwingername: str):
AND u.rolle IN ('breeder', 'admin')
AND (u.breeder_status = 'approved' OR u.rolle = 'admin')
""", (zwingername,)).fetchone()
- if not row:
- raise HTTPException(404, "Züchter nicht gefunden.")
- return dict(row)
+ if not row:
+ raise HTTPException(404, "Züchter nicht gefunden.")
+
+ breeder_id = row["id"]
+ result = dict(row)
+
+ # Öffentliche Zuchthunde + ihre wichtigsten Gesundheitstests + Titel
+ hunde_rows = conn.execute("""
+ SELECT id, name, rufname, geschlecht, geburtsdatum, farbe, zuchtbuchnummer, foto_url
+ FROM zucht_hunde
+ WHERE breeder_id=? AND is_public=1 AND (sterbedatum IS NULL OR sterbedatum='')
+ ORDER BY geschlecht, name
+ """, (breeder_id,)).fetchall()
+
+ hunde = []
+ for h in hunde_rows:
+ hund = dict(h)
+ # Gesundheitstests (nur öffentliche, nur HD/ED/Augen/Herz)
+ tests = conn.execute("""
+ SELECT test_typ, ergebnis, test_name, untersuch_am
+ FROM dog_health_tests
+ WHERE hund_id=? AND is_public=1
+ AND test_typ IN ('HD','ED','augen','herz','OCD','patella','ZTP')
+ ORDER BY test_typ, untersuch_am DESC
+ """, (h["id"],)).fetchall()
+ seen = set()
+ hund["health_tests"] = []
+ for t in tests:
+ if t["test_typ"] not in seen:
+ seen.add(t["test_typ"])
+ hund["health_tests"].append(dict(t))
+ # Gentests (nur öffentliche, Zusammenfassung)
+ gentests = conn.execute("""
+ SELECT COUNT(*) as total,
+ SUM(CASE WHEN ergebnis_klasse='clear' THEN 1 ELSE 0 END) as clear_cnt
+ FROM dog_genetic_tests WHERE hund_id=? AND is_public=1
+ """, (h["id"],)).fetchone()
+ hund["gentests_total"] = gentests["total"] or 0
+ hund["gentests_clear"] = gentests["clear_cnt"] or 0
+ # Auszeichnungen (nur Zucht/Champion)
+ titles = conn.execute("""
+ SELECT titel_name FROM dog_titles
+ WHERE hund_id=? AND titel_typ IN ('champion','zucht','ausstellung')
+ ORDER BY verliehen_am DESC LIMIT 3
+ """, (h["id"],)).fetchall()
+ hund["titel"] = [t["titel_name"] for t in titles]
+ hunde.append(hund)
+
+ result["hunde"] = hunde
+
+ # Sichtbare Würfe
+ wuerfe = conn.execute("""
+ SELECT id, vater_name, mutter_name, geburt_datum, erwartetes_datum,
+ status, welpen_gesamt, welpen_verfuegbar, preis_spanne, beschreibung
+ FROM litters
+ WHERE breeder_id=? AND sichtbar=1 AND status != 'abgeschlossen'
+ ORDER BY COALESCE(geburt_datum, erwartetes_datum) DESC
+ """, (breeder_id,)).fetchall()
+ result["wuerfe"] = [dict(w) for w in wuerfe]
+
+ # Gesundheits-Statistik (aggregiert über alle öffentlichen Hunde)
+ hd_stats = conn.execute("""
+ SELECT ergebnis, COUNT(*) as cnt FROM dog_health_tests
+ WHERE hund_id IN (SELECT id FROM zucht_hunde WHERE breeder_id=? AND is_public=1)
+ AND test_typ='HD' AND is_public=1
+ GROUP BY ergebnis
+ """, (breeder_id,)).fetchall()
+ result["hd_stats"] = [dict(r) for r in hd_stats]
+
+ ed_stats = conn.execute("""
+ SELECT ergebnis, COUNT(*) as cnt FROM dog_health_tests
+ WHERE hund_id IN (SELECT id FROM zucht_hunde WHERE breeder_id=? AND is_public=1)
+ AND test_typ='ED' AND is_public=1
+ GROUP BY ergebnis
+ """, (breeder_id,)).fetchall()
+ result["ed_stats"] = [dict(r) for r in ed_stats]
+
+ return result
# ------------------------------------------------------------------
diff --git a/backend/static/index.html b/backend/static/index.html
index 07cacba..d45b667 100644
--- a/backend/static/index.html
+++ b/backend/static/index.html
@@ -591,10 +591,10 @@
-
-
-
-
+
+
+
+
diff --git a/backend/static/js/app.js b/backend/static/js/app.js
index 7b5af08..0219d30 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 = '899'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
+const APP_VER = '900'; // ← 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/breeder.js b/backend/static/js/pages/breeder.js
index 5c50ec4..907469f 100644
--- a/backend/static/js/pages/breeder.js
+++ b/backend/static/js/pages/breeder.js
@@ -1,6 +1,5 @@
/* ============================================================
- BAN YARO — Öffentliches Züchter-Profil
- Seiten-Modul: Zeigt das verifizierte Profil eines Züchters.
+ BAN YARO — Öffentliches Züchter-Profil (Visitenkarte)
============================================================ */
window.Page_breeder = (() => {
@@ -8,7 +7,8 @@ window.Page_breeder = (() => {
let _container = null;
let _appState = null;
- const _esc = s => UI.esc ? UI.esc(s) : String(s ?? '').replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
+ const _esc = s => UI.esc ? UI.esc(s) : String(s ?? '').replace(/[&<>"']/g,
+ c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
// ----------------------------------------------------------
// INIT
@@ -17,7 +17,6 @@ window.Page_breeder = (() => {
_container = container;
_appState = appState;
- // Zwingername aus params oder URL-Pfad (/breeder/vom-sonnenfeld)
const zwingername = params?.zwingername
|| decodeURIComponent((window.location.pathname.split('/breeder/')[1] || '').replace(/\/$/, ''));
@@ -27,8 +26,8 @@ window.Page_breeder = (() => {
}
container.innerHTML = `
-