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.
433 lines
23 KiB
JavaScript
433 lines
23 KiB
JavaScript
/* ============================================================
|
||
BAN YARO — Öffentliches Züchter-Profil (Visitenkarte)
|
||
============================================================ */
|
||
|
||
window.Page_breeder = (() => {
|
||
|
||
let _container = null;
|
||
let _appState = null;
|
||
|
||
|
||
// ----------------------------------------------------------
|
||
// INIT
|
||
// ----------------------------------------------------------
|
||
async function init(container, appState, params = {}) {
|
||
_container = container;
|
||
_appState = appState;
|
||
|
||
const zwingername = params?.zwingername
|
||
|| decodeURIComponent((window.location.pathname.split('/breeder/')[1] || '').replace(/\/$/, ''));
|
||
|
||
if (!zwingername) {
|
||
container.innerHTML = '<div style="padding:var(--space-6)">Kein Zwingername angegeben.</div>';
|
||
return;
|
||
}
|
||
|
||
container.innerHTML = `
|
||
<div id="breeder-profile-body" style="padding-bottom:calc(var(--space-16) + 24px)">
|
||
${UI.skeleton(5)}
|
||
</div>`;
|
||
|
||
// FAB an document.body hängen damit position:fixed zuverlässig funktioniert
|
||
// und destroy() der einzige Lifecycle-Kontrollpunkt bleibt
|
||
const fab = document.createElement('button');
|
||
fab.id = 'breeder-back-fab';
|
||
fab.setAttribute('aria-label', 'Zurück zur Wurfbörse');
|
||
fab.style.cssText = 'position:fixed;bottom:calc(var(--safe-bottom,0px) + 20px);right:20px;' +
|
||
'width:54px;height:54px;border-radius:50%;background:var(--c-primary);' +
|
||
'border:none;color:#fff;cursor:pointer;z-index:200;' +
|
||
'display:flex;align-items:center;justify-content:center;' +
|
||
'box-shadow:0 4px 18px rgba(196,132,58,.45);transition:transform .12s,box-shadow .12s;' +
|
||
'-webkit-tap-highlight-color:transparent';
|
||
fab.innerHTML = '<svg style="width:22px;height:22px" viewBox="0 0 256 256"><use href="/icons/phosphor.svg#arrow-left"></use></svg>';
|
||
fab.addEventListener('click', () => App.navigate('wurfboerse'));
|
||
document.body.appendChild(fab);
|
||
|
||
try {
|
||
const p = await API.breeder.profile(zwingername);
|
||
_render(p);
|
||
} catch (e) {
|
||
document.getElementById('breeder-profile-body').innerHTML =
|
||
`<div style="padding:var(--space-8);text-align:center;color:var(--c-text-secondary)">
|
||
${UI.icon('magnifying-glass')} ${UI.escape(e.message || 'Züchter nicht gefunden.')}
|
||
</div>`;
|
||
}
|
||
}
|
||
|
||
// ----------------------------------------------------------
|
||
// RENDER
|
||
// ----------------------------------------------------------
|
||
function _render(p) {
|
||
const body = document.getElementById('breeder-profile-body') || _container;
|
||
|
||
const seit = p.verified_at
|
||
? new Date(p.verified_at).toLocaleDateString('de-DE', { month: 'long', year: 'numeric' })
|
||
: null;
|
||
|
||
const isLoggedIn = !!_appState?.user;
|
||
const isOwnProfile = _appState?.user?.id === p.zuechter_user_id;
|
||
|
||
body.innerHTML = `
|
||
<!-- ═══ HERO ═══ -->
|
||
<div style="background:linear-gradient(135deg,var(--c-primary-dark,#a86e2e),var(--c-primary,#C4843A));
|
||
padding:var(--space-6) var(--space-4) var(--space-8);color:white;position:relative">
|
||
<div style="max-width:640px;margin:0 auto">
|
||
<div style="display:flex;align-items:flex-start;gap:var(--space-3);flex-wrap:wrap">
|
||
<div class="flex-1-min">
|
||
<p style="margin:0 0 var(--space-1);font-size:var(--text-xs);opacity:.7;text-transform:uppercase;letter-spacing:.1em">
|
||
${UI.icon('seal-check')} Verifizierter Züchter
|
||
</p>
|
||
<h1 style="margin:0 0 var(--space-2);font-size:clamp(1.3rem,4vw,1.9rem);font-weight:800;line-height:1.2;word-break:break-word">
|
||
${UI.escape(p.zwingername)}
|
||
</h1>
|
||
<div style="display:flex;flex-wrap:wrap;gap:var(--space-2);align-items:center">
|
||
${p.rasse_text ? `<span style="background:rgba(255,255,255,.2);border-radius:999px;padding:2px 10px;font-size:var(--text-xs);font-weight:600">${UI.escape(p.rasse_text)}</span>` : ''}
|
||
${p.vdh_mitglied ? `<span style="background:rgba(255,255,255,.2);border-radius:999px;padding:2px 10px;font-size:var(--text-xs);font-weight:600">${UI.icon('certificate')} VDH</span>` : ''}
|
||
${p.stadt ? `<span style="opacity:.8;font-size:var(--text-xs)">${UI.icon('map-pin')} ${UI.escape(p.stadt)}</span>` : ''}
|
||
${seit ? `<span style="opacity:.7;font-size:var(--text-xs)">Züchter seit ${UI.escape(seit)}</span>` : ''}
|
||
</div>
|
||
</div>
|
||
${p.logo_url
|
||
? `<img src="${UI.escape(p.logo_url)}" alt="Zwinger-Logo"
|
||
style="width:72px;height:72px;border-radius:50%;object-fit:cover;
|
||
border:3px solid rgba(255,255,255,.5);flex-shrink:0;box-shadow:0 2px 12px rgba(0,0,0,.25)"
|
||
data-fb="hide">`
|
||
: `<div style="background:rgba(255,255,255,.15);border-radius:50%;width:64px;height:64px;
|
||
display:flex;align-items:center;justify-content:center;flex-shrink:0">
|
||
<svg style="width:32px;height:32px" viewBox="0 0 256 256"><use href="/icons/phosphor.svg#paw-print"></use></svg>
|
||
</div>`
|
||
}
|
||
</div>
|
||
|
||
${!isOwnProfile ? `
|
||
<div style="margin-top:var(--space-5);display:flex;gap:var(--space-3);flex-wrap:wrap">
|
||
${isLoggedIn
|
||
? `<button class="breeder-chat-btn"
|
||
style="background:white;color:var(--c-primary-dark,#a86e2e);border:none;
|
||
border-radius:999px;padding:var(--space-2) var(--space-5);
|
||
font-weight:700;cursor:pointer;display:flex;align-items:center;gap:6px">
|
||
${UI.icon('chat-circle-dots')} Nachricht senden
|
||
</button>`
|
||
: `<button class="breeder-login-btn"
|
||
style="background:white;color:var(--c-primary-dark,#a86e2e);border:none;
|
||
border-radius:999px;padding:var(--space-2) var(--space-5);
|
||
font-weight:700;cursor:pointer">
|
||
Anmelden um zu schreiben
|
||
</button>`
|
||
}
|
||
${p.website ? `<a href="${UI.escape(p.website)}" target="_blank" rel="noopener noreferrer"
|
||
style="background:rgba(255,255,255,.2);color:white;border:1px solid rgba(255,255,255,.4);
|
||
border-radius:999px;padding:var(--space-2) var(--space-5);
|
||
font-weight:600;font-size:var(--text-sm);text-decoration:none;
|
||
display:flex;align-items:center;gap:6px">
|
||
${UI.icon('arrow-square-out')} Website
|
||
</a>` : ''}
|
||
</div>` : ''}
|
||
</div>
|
||
</div>
|
||
|
||
<div style="max-width:640px;margin:0 auto;padding:var(--space-4)">
|
||
|
||
<!-- Beschreibung -->
|
||
${p.beschreibung ? `
|
||
<div style="background:var(--c-bg-secondary);border:1px solid var(--c-border);border-radius:var(--radius-lg);
|
||
padding:var(--space-4);margin-bottom:var(--space-4)">
|
||
<p style="margin:0;line-height:1.7;color:var(--c-text-secondary);white-space:pre-line">${UI.escape(p.beschreibung)}</p>
|
||
</div>` : ''}
|
||
|
||
<!-- Zuchthunde -->
|
||
${p.hunde?.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('dog')} Unsere Zuchthunde
|
||
<span style="font-size:var(--text-xs);color:var(--c-text-muted);font-weight:400">${p.hunde.length} Hunde</span>
|
||
</h2>
|
||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:var(--space-3)">
|
||
${p.hunde.map(h => _hundCard(h)).join('')}
|
||
</div>
|
||
</div>` : ''}
|
||
|
||
<!-- Aktuelle Würfe -->
|
||
${p.wuerfe?.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('baby')} Aktuelle Würfe
|
||
</h2>
|
||
<div class="flex-col-gap-3">
|
||
${p.wuerfe.map(w => _wurfCard(w)).join('')}
|
||
</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 & 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)">
|
||
<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('heartbeat')} Gesundheits-Transparenz
|
||
</h2>
|
||
<div style="background:var(--c-bg-secondary);border:1px solid var(--c-border);
|
||
border-radius:var(--radius-lg);padding:var(--space-4)">
|
||
${_statsSection('HD-Ergebnisse', p.hd_stats)}
|
||
${p.hd_stats?.length && p.ed_stats?.length ? '<hr style="border:none;border-top:1px solid var(--c-border);margin:var(--space-3) 0">' : ''}
|
||
${_statsSection('ED-Ergebnisse', p.ed_stats)}
|
||
</div>
|
||
</div>` : ''}
|
||
|
||
<!-- Kontakt/Details -->
|
||
<div style="background:var(--c-bg-secondary);border:1px solid var(--c-border);
|
||
border-radius:var(--radius-lg);padding:var(--space-4);margin-bottom:var(--space-4)">
|
||
<h2 style="margin:0 0 var(--space-3);font-size:var(--text-base);font-weight:700">
|
||
${UI.icon('info')} Über den Züchter
|
||
</h2>
|
||
<dl style="margin:0;display:flex;flex-direction:column;gap:var(--space-2)">
|
||
${_dl('Züchter', p.zuechter_name)}
|
||
${_dl('Rasse(n)', p.rasse_text)}
|
||
${_dl('Verein', p.verein)}
|
||
${_dl('VDH-Mitglied', p.vdh_mitglied ? '✓ Ja' : 'Nein')}
|
||
${_dl('Stadt', p.stadt)}
|
||
${p.website ? `
|
||
<div style="display:flex;gap:var(--space-2);align-items:baseline">
|
||
<dt style="color:var(--c-text-secondary);min-width:110px;font-size:var(--text-sm);flex-shrink:0">Website</dt>
|
||
<dd style="margin:0"><a href="${UI.escape(p.website)}" target="_blank" rel="noopener noreferrer"
|
||
style="color:var(--c-primary);word-break:break-all">${UI.escape(p.website)}</a></dd>
|
||
</div>` : ''}
|
||
${seit ? _dl('Züchter seit', seit) : ''}
|
||
</dl>
|
||
</div>
|
||
|
||
<!-- Fotos / Gallery -->
|
||
${p.fotos?.length ? `
|
||
<div class="mb-4">
|
||
<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('images')} Galerie
|
||
<span style="font-size:var(--text-xs);color:var(--c-text-muted);font-weight:400">${p.fotos.length} Fotos</span>
|
||
</h2>
|
||
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:var(--space-2)">
|
||
${p.fotos.map((ph, i) => `
|
||
<a href="${UI.escape(ph.url)}" target="_blank" rel="noopener noreferrer"
|
||
style="display:block;border-radius:var(--radius-md);overflow:hidden;
|
||
border:${ph.primary ? '2px solid var(--c-primary)' : '1px solid var(--c-border)'};
|
||
aspect-ratio:1;position:relative">
|
||
<img src="${UI.escape(ph.thumb)}" alt="${UI.escape(ph.caption)}"
|
||
loading="${i < 6 ? 'eager' : 'lazy'}"
|
||
style="width:100%;height:100%;object-fit:cover;display:block"
|
||
data-fb="hide-parent">
|
||
${ph.primary ? `<span style="position:absolute;top:4px;left:4px;background:var(--c-primary);
|
||
color:white;font-size:9px;font-weight:700;border-radius:999px;padding:1px 6px">Logo</span>` : ''}
|
||
${ph.caption ? `<div style="position:absolute;bottom:0;left:0;right:0;
|
||
background:linear-gradient(transparent,rgba(0,0,0,.6));
|
||
color:white;font-size:10px;padding:12px 6px 4px;line-height:1.3">${UI.escape(ph.caption)}</div>` : ''}
|
||
</a>`).join('')}
|
||
</div>
|
||
</div>` : ''}
|
||
|
||
<div id="breeder-photos-section" class="hidden"></div>
|
||
|
||
</div>`;
|
||
|
||
// Events
|
||
body.querySelector('.breeder-chat-btn')?.addEventListener('click', () => _contactBreeder(p.zuechter_user_id));
|
||
body.querySelector('.breeder-login-btn')?.addEventListener('click', () => App.navigate('settings'));
|
||
|
||
_loadBreederPhotos(p.id);
|
||
}
|
||
|
||
// ----------------------------------------------------------
|
||
// Hund-Karte
|
||
// ----------------------------------------------------------
|
||
function _hundCard(h) {
|
||
const alter = h.geburtsdatum
|
||
? Math.floor((Date.now() - new Date(h.geburtsdatum)) / 31557600000)
|
||
: null;
|
||
const gIcon = h.geschlecht === 'maennlich' ? UI.icon('gender-male') : UI.icon('gender-female');
|
||
|
||
const hdTest = h.health_tests?.find(t => t.test_typ === 'HD');
|
||
const edTest = h.health_tests?.find(t => t.test_typ === 'ED');
|
||
const augeTest = h.health_tests?.find(t => t.test_typ === 'augen');
|
||
|
||
const testPills = [
|
||
hdTest ? `<span style="${_testPillStyle(hdTest.ergebnis,'HD')}">HD ${UI.escape(hdTest.ergebnis)}</span>` : '',
|
||
edTest ? `<span style="${_testPillStyle(edTest.ergebnis,'ED')}">ED ${UI.escape(edTest.ergebnis)}</span>` : '',
|
||
augeTest ? `<span style="${_testPillStyle('clear','augen')}">Augen ✓</span>` : '',
|
||
].filter(Boolean).join('');
|
||
|
||
const titlePills = (h.titel || []).map(t =>
|
||
`<span style="background:var(--c-primary-light,#f5e6d3);color:var(--c-primary-dark,#a86e2e);
|
||
border-radius:999px;padding:1px 8px;font-size:10px;font-weight:700">${UI.escape(t)}</span>`
|
||
).join('');
|
||
|
||
const genBadge = h.gentests_total > 0
|
||
? `<span class="text-xs-muted">
|
||
${h.gentests_clear}/${h.gentests_total} Gentests frei
|
||
</span>`
|
||
: '';
|
||
|
||
return `
|
||
<div style="background:var(--c-surface);border:1px solid var(--c-border);border-radius:var(--radius-lg);
|
||
padding:var(--space-3);display:flex;flex-direction:column;gap:var(--space-2)">
|
||
<div style="display:flex;align-items:center;gap:var(--space-2)">
|
||
<span class="text-primary">${gIcon}</span>
|
||
<span style="font-weight:700;font-size:var(--text-sm)">${UI.escape(h.name)}</span>
|
||
${h.rufname ? `<span style="color:var(--c-text-muted);font-size:var(--text-xs)">"${UI.escape(h.rufname)}"</span>` : ''}
|
||
${alter !== null ? `<span style="color:var(--c-text-muted);font-size:var(--text-xs);margin-left:auto">${alter} J.</span>` : ''}
|
||
</div>
|
||
${h.farbe ? `<p style="margin:0;font-size:var(--text-xs);color:var(--c-text-secondary)">${UI.escape(h.farbe)}</p>` : ''}
|
||
${testPills ? `<div style="display:flex;flex-wrap:wrap;gap:4px">${testPills}</div>` : ''}
|
||
${titlePills ? `<div style="display:flex;flex-wrap:wrap;gap:4px">${titlePills}</div>` : ''}
|
||
${genBadge}
|
||
</div>`;
|
||
}
|
||
|
||
function _testPillStyle(ergebnis, typ) {
|
||
const e = (ergebnis || '').toUpperCase();
|
||
let bg = '#6b72801a', color = '#6b7280', border = '#6b728040';
|
||
if (typ === 'HD') {
|
||
if (['A','A1','A2'].includes(e)) { bg='#16a34a1a';color='#16a34a';border='#16a34a40'; }
|
||
else if (e === 'B' || e === 'B1' || e === 'B2') { bg='#86efac1a';color='#15803d';border='#86efac40'; }
|
||
else if (e === 'C') { bg='#eab3081a';color='#a16207';border='#eab30840'; }
|
||
else if (e === 'D' || e === 'E') { bg='#ef44441a';color='#dc2626';border='#ef444440'; }
|
||
} else if (typ === 'ED') {
|
||
if (e === '0' || e === 'ED 0') { bg='#16a34a1a';color='#16a34a';border='#16a34a40'; }
|
||
else if (e === '1') { bg='#eab3081a';color='#a16207';border='#eab30840'; }
|
||
else if (e === '2' || e === '3') { bg='#ef44441a';color='#dc2626';border='#ef444440'; }
|
||
} else if (typ === 'augen' || ergebnis === 'clear') {
|
||
bg='#16a34a1a';color='#16a34a';border='#16a34a40';
|
||
}
|
||
return `background:${bg};color:${color};border:1px solid ${border};border-radius:999px;padding:1px 8px;font-size:11px;font-weight:600`;
|
||
}
|
||
|
||
// ----------------------------------------------------------
|
||
// Wurf-Karte
|
||
// ----------------------------------------------------------
|
||
const _STATUS_LABEL = { geplant: 'Geplant', geboren: 'Geboren', verfuegbar: 'Verfügbar', abgeschlossen: 'Abgeschlossen' };
|
||
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)}`
|
||
: w.erwartetes_datum ? `Erwartet: ${_fmtDate(w.erwartetes_datum)}` : '';
|
||
const sc = _STATUS_COLOR[w.status] || '#6b7280';
|
||
const sl = _STATUS_LABEL[w.status] || w.status;
|
||
return `
|
||
<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>
|
||
</div>
|
||
<div style="display:flex;gap:var(--space-4);flex-wrap:wrap;font-size:var(--text-xs);color:var(--c-text-secondary)">
|
||
${datum ? `<span>${UI.icon('calendar-dots')} ${UI.escape(datum)}</span>` : ''}
|
||
${w.welpen_gesamt ? `<span>${UI.icon('dog')} ${w.welpen_verfuegbar ?? '?'}/${w.welpen_gesamt} verfügbar</span>` : ''}
|
||
${w.preis_spanne ? `<span>${UI.icon('currency-eur')} ${UI.escape(w.preis_spanne)}</span>` : ''}
|
||
</div>
|
||
${w.beschreibung ? `<p style="margin:var(--space-2) 0 0;font-size:var(--text-xs);color:var(--c-text-secondary);line-height:1.5">${UI.escape(w.beschreibung)}</p>` : ''}
|
||
</div>`;
|
||
}
|
||
|
||
// ----------------------------------------------------------
|
||
// Statistik-Sektion
|
||
// ----------------------------------------------------------
|
||
function _statsSection(label, stats) {
|
||
if (!stats?.length) return '';
|
||
const total = stats.reduce((s, r) => s + r.cnt, 0);
|
||
return `
|
||
<div>
|
||
<p style="margin:0 0 var(--space-2);font-size:var(--text-xs);font-weight:700;
|
||
color:var(--c-text-muted);text-transform:uppercase;letter-spacing:.06em">${UI.escape(label)}</p>
|
||
<div style="display:flex;flex-wrap:wrap;gap:var(--space-2)">
|
||
${stats.map(r => `
|
||
<div style="display:flex;align-items:center;gap:6px;font-size:var(--text-sm)">
|
||
<span style="font-weight:700">${UI.escape(r.ergebnis || '—')}</span>
|
||
<span class="text-muted">${r.cnt}×</span>
|
||
<span style="background:var(--c-border);border-radius:999px;height:6px;
|
||
width:${Math.round(r.cnt/total*80)+16}px;display:inline-block"></span>
|
||
</div>`).join('')}
|
||
</div>
|
||
</div>`;
|
||
}
|
||
|
||
// ----------------------------------------------------------
|
||
// Hilfsfunktionen
|
||
// ----------------------------------------------------------
|
||
function _dl(label, value) {
|
||
if (!value) return '';
|
||
return `<div style="display:flex;gap:var(--space-2);align-items:baseline">
|
||
<dt style="color:var(--c-text-secondary);min-width:110px;font-size:var(--text-sm);flex-shrink:0">${UI.escape(label)}</dt>
|
||
<dd style="margin:0;font-size:var(--text-sm)">${UI.escape(String(value))}</dd>
|
||
</div>`;
|
||
}
|
||
|
||
function _fmtDate(iso) {
|
||
if (!iso) return '—';
|
||
const [y,m,d] = iso.slice(0,10).split('-');
|
||
return `${d}.${m}.${y}`;
|
||
}
|
||
|
||
async function _loadBreederPhotos(breederId) {
|
||
const section = document.getElementById('breeder-photos-section');
|
||
if (!section) return;
|
||
try {
|
||
const photos = await API.breederPhotos.list('breeder', breederId);
|
||
if (!photos?.length) return;
|
||
section.innerHTML = `
|
||
<div class="mb-4">
|
||
<h2 style="margin:0 0 var(--space-3);font-size:var(--text-base);font-weight:700">
|
||
${UI.icon('images')} Fotos
|
||
</h2>
|
||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(110px,1fr));gap:var(--space-2)">
|
||
${photos.map(ph => `
|
||
<a href="${UI.escape(ph.url||'')}" target="_blank" rel="noopener noreferrer"
|
||
style="display:block;border-radius:var(--radius-md);overflow:hidden;
|
||
border:1px solid var(--c-border);aspect-ratio:1">
|
||
<img src="${UI.escape(ph.thumbnail_url||ph.url||'')}" alt="${UI.escape(ph.caption||'')}"
|
||
loading="lazy" style="width:100%;height:100%;object-fit:cover;display:block"
|
||
data-fb="hide-parent">
|
||
</a>`).join('')}
|
||
</div>
|
||
</div>`;
|
||
section.classList.remove('hidden'); // Template startet mit .hidden (!important)
|
||
} catch (_) {}
|
||
}
|
||
|
||
async function _contactBreeder(userId) {
|
||
if (!_appState?.user) { App.navigate('settings'); return; }
|
||
try {
|
||
await API.chat.start(userId);
|
||
App.navigate('chat');
|
||
} catch (e) { UI.toast.error(e.message || 'Chat konnte nicht geöffnet werden.'); }
|
||
}
|
||
|
||
function refresh() {}
|
||
function onDogChange() {}
|
||
function destroy() { document.getElementById('breeder-back-fab')?.remove(); }
|
||
|
||
return { init, refresh, onDogChange, destroy };
|
||
|
||
})();
|