Züchter-Editor: Wurfnamen sichtbar, 'undefined Medien' gefixt, Mitgliedschaften & Zertifikate

Rene: 'Züchter sollten mehr Einfluss haben — Wurfnamen (B-Wurf), Mitglied-
schaften und Zertifikate fürs Profil.'

- Wurfnamen: Infrastruktur existierte komplett (wurf_rang/wurf_name in DB,
  Backend, Wurfverwaltungs-Formular) — war nur im Editor und auf der
  öffentlichen Seite unsichtbar. Editor-Karten zeigen jetzt 'B-Wurf · Name'
  (Feldname-Bug geburtsdatum→geburt_datum), öffentliche Wurf-Karten bekommen
  den Rang/Namen als Badge, Hinweis im Editor verlinkt zur Vergabe.
- 'undefined Medien': my-editor lieferte kein foto_count (+photos fürs
  Profil-Grid) — ergänzt.
- NEU Mitgliedschaften & Zertifikate: entity_type 'certificate' im Foto-
  System (Ownership wie breeder), Editor-Sektion mit Upload (Bezeichnung als
  Caption) + Löschen, öffentliche Profilseite zeigt eigene Sektion mit
  Logos/Urkunden (klickbar, lazy). Public-Endpoint liefert result.zertifikate.

Tests: my-editor inkl. Wurfname/foto_count, Zertifikat-Roundtrip bis zur
öffentlichen Seite. Suite: 61 passed.
This commit is contained in:
rene 2026-06-07 21:00:14 +02:00
parent dfffd07a96
commit 5f01abc590
10 changed files with 186 additions and 28 deletions

View file

@ -160,6 +160,25 @@ window.Page_breeder = (() => {
</div>
</div>` : ''}
<!-- Mitgliedschaften & Zertifikate -->
${p.zertifikate?.length ? `
<div style="margin-bottom:var(--space-5)">
<h2 style="margin:0 0 var(--space-3);font-size:var(--text-base);font-weight:700;
display:flex;align-items:center;gap:var(--space-2)">
${UI.icon('seal-check')} Mitgliedschaften &amp; Zertifikate
</h2>
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:var(--space-3)">
${p.zertifikate.map(z => `
<a href="${UI.escape(z.url)}" target="_blank" rel="noopener"
style="display:block;background:var(--c-surface);border:1px solid var(--c-border);
border-radius:var(--radius-md);padding:var(--space-2);text-decoration:none;text-align:center">
<img src="${UI.escape(z.thumbnail_url || z.url)}" alt="${UI.escape(z.caption || 'Zertifikat')}"
loading="lazy" style="width:100%;aspect-ratio:1;object-fit:contain">
${z.caption ? `<div style="font-size:var(--text-xs);color:var(--c-text-secondary);margin-top:var(--space-1);overflow:hidden;text-overflow:ellipsis">${UI.escape(z.caption)}</div>` : ''}
</a>`).join('')}
</div>
</div>` : ''}
<!-- Gesundheits-Transparenz -->
${(p.hd_stats?.length || p.ed_stats?.length) ? `
<div style="margin-bottom:var(--space-5)">
@ -306,6 +325,8 @@ window.Page_breeder = (() => {
const _STATUS_COLOR = { geplant: '#6b7280', geboren: '#3b82f6', verfuegbar: '#16a34a', abgeschlossen: '#9ca3af' };
function _wurfCard(w) {
const wurfTitel = [w.wurf_rang ? `${w.wurf_rang}-Wurf` : null, w.wurf_name]
.filter(Boolean).join(' · ');
const eltern = [w.vater_name, w.mutter_name].filter(Boolean).join(' × ') || '—';
const datum = w.geburt_datum
? `Geburt: ${_fmtDate(w.geburt_datum)}`
@ -316,6 +337,7 @@ window.Page_breeder = (() => {
<div style="background:var(--c-surface);border:1px solid var(--c-border);border-radius:var(--radius-lg);
padding:var(--space-3) var(--space-4)">
<div style="display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap;margin-bottom:var(--space-1)">
${wurfTitel ? `<span style="background:var(--c-primary);color:#fff;border-radius:999px;padding:1px 10px;font-size:var(--text-xs);font-weight:700">${UI.escape(wurfTitel)}</span>` : ''}
<span style="font-weight:700;font-size:var(--text-sm)">${UI.escape(eltern)}</span>
<span style="background:${sc}1a;color:${sc};border:1px solid ${sc}40;
border-radius:999px;padding:1px 8px;font-size:var(--text-xs);font-weight:600">${sl}</span>