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

@ -138,6 +138,34 @@ window.Page_breeder_editor = (() => {
</label>
</div>
<!-- Mitgliedschaften & Zertifikate -->
<div class="card" style="padding:var(--space-4);margin-bottom:var(--space-3)">
<div style="font-size:var(--text-xs);font-weight:700;text-transform:uppercase;
letter-spacing:.06em;color:var(--c-text-muted);margin-bottom:var(--space-2)">
Mitgliedschaften &amp; Zertifikate
</div>
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-bottom:var(--space-2)">
Vereins-Logos, VDH-Mitgliedschaft, Urkunden werden auf deiner öffentlichen
Profilseite in einer eigenen Sektion gezeigt.
</div>
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:var(--space-2);margin:var(--space-3) 0">
${(p.certificates || []).map(c => `
<div style="position:relative;border:1px solid var(--c-border);border-radius:var(--radius-md);overflow:hidden;background:var(--c-surface-2)">
<img src="${UI.escape(c.thumbnail_url || c.url)}" style="width:100%;aspect-ratio:1;object-fit:contain;padding:6px;box-sizing:border-box">
<div style="font-size:10px;text-align:center;padding:2px 4px 5px;color:var(--c-text-secondary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${UI.escape(c.caption || '—')}</div>
<button class="be-cert-del" data-id="${c.id}"
style="position:absolute;top:4px;right:4px;background:rgba(0,0,0,.6);
border:none;border-radius:50%;width:24px;height:24px;cursor:pointer;
color:#fff;font-size:14px;display:flex;align-items:center;justify-content:center">×</button>
</div>`).join('')}
</div>
<label class="btn btn-secondary btn-sm" style="cursor:pointer;display:inline-flex;align-items:center;gap:6px">
<svg class="ph-icon" style="width:16px;height:16px"><use href="/icons/phosphor.svg#plus"></use></svg>
Logo / Urkunde hinzufügen
<input type="file" id="be-cert-input" accept="image/*" class="hidden">
</label>
</div>
<!-- Würfe Schnellupload -->
${litters.length ? `
<div class="card" style="padding:var(--space-4);margin-bottom:var(--space-3)">
@ -148,6 +176,10 @@ window.Page_breeder_editor = (() => {
<div class="flex-col-gap-3">
${litters.map(l => _renderLitterCard(l)).join('')}
</div>
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:var(--space-2)">
${UI.icon('info')} Wurf-Rang (A-, B-Wurf ) und Wurfnamen vergibst du in der
<a href="#litters" class="text-primary">Wurfverwaltung</a>.
</div>
</div>` : ''}
</div>
@ -179,12 +211,14 @@ window.Page_breeder_editor = (() => {
}
function _renderLitterCard(l) {
const label = l.geburtsdatum
? `Wurf vom ${new Date(l.geburtsdatum).toLocaleDateString('de-DE')}`
: `Wurf #${l.id}`;
// Wurf-Rang/-Name (aus der Wurfverwaltung) zuerst, dann Datum, dann #id
const name = [l.wurf_rang ? `${l.wurf_rang}-Wurf` : null, l.wurf_name]
.filter(Boolean).join(' · ');
const label = name
|| (l.geburt_datum ? `Wurf vom ${new Date(l.geburt_datum).toLocaleDateString('de-DE')}` : `Wurf #${l.id}`);
const info = [
l.welpen_gesamt ? `${l.welpen_gesamt} Welpen` : null,
`${l.foto_count} Medien`,
`${l.foto_count ?? 0} Medien`,
].filter(Boolean).join(' · ');
return `
<div style="border:1px solid var(--c-border);border-radius:var(--radius-md);padding:var(--space-3)">
@ -287,6 +321,36 @@ window.Page_breeder_editor = (() => {
});
// Wurf-Upload
// Zertifikat/Mitgliedschaft hochladen — Bezeichnung wird als Caption gespeichert
el.querySelector('#be-cert-input')?.addEventListener('change', async e => {
const file = e.target.files[0];
if (!file) return;
const caption = (window.prompt('Bezeichnung (z. B. „VDH-Mitglied", „Club für Britische Hütehunde"):') || '').trim();
const fd = new FormData();
fd.append('file', file);
fd.append('entity_type', 'certificate');
fd.append('entity_id', String(_data.profile.id));
fd.append('visibility', 'public');
if (caption) fd.append('caption', caption);
try {
const ph = await API.breederPhotos.upload(fd);
(_data.profile.certificates ||= []).push(ph);
UI.toast.success('Zertifikat hinzugefügt — erscheint auf deiner Profilseite.');
_render();
} catch (err) { UI.toast.error(err.message); }
});
// Zertifikat löschen
el.querySelectorAll('.be-cert-del').forEach(btn => {
btn.addEventListener('click', async () => {
try {
await API.breederPhotos.remove(parseInt(btn.dataset.id));
_data.profile.certificates = (_data.profile.certificates || []).filter(c => String(c.id) !== btn.dataset.id);
_render();
} catch (err) { UI.toast.error(err.message); }
});
});
el.querySelectorAll('.be-litter-input').forEach(input => {
input.addEventListener('change', async e => {
const file = e.target.files[0];