/* ============================================================ BAN YARO — Zucht-Profil Vollständiges Profil eines Zuchthundes: Basisdaten + Stammbaum (4 Generationen) + Gesundheitstests + Gentests + Titel. ============================================================ */ window.Page_zucht_profil = (() => { let _container = null; let _appState = null; let _hundId = null; let _hund = null; // ---------------------------------------------------------- // Hilfsfunktionen // ---------------------------------------------------------- function _esc(s) { if (!s) return ''; return String(s) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function _fmtDate(iso) { if (!iso) return '—'; const d = new Date(iso + 'T12:00:00'); return d.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' }); } // ---------------------------------------------------------- // Badge-Farben // ---------------------------------------------------------- function _healthBadge(testTyp, ergebnis) { const e = (ergebnis || '').trim().toUpperCase(); let color = '#6B7280'; if (testTyp === 'HD') { if (['A1', 'A2', 'A'].includes(e)) color = '#22C55E'; else if (['B1', 'B2', 'B'].includes(e)) color = '#86EFAC'; else if (e === 'C') color = '#EAB308'; else if (e === 'D') color = '#F97316'; else if (e === 'E') color = '#EF4444'; } else if (testTyp === 'ED') { if (e === '0' || e === 'ED 0') color = '#22C55E'; else if (e === '1' || e === 'ED 1') color = '#EAB308'; else if (e === '2' || e === 'ED 2') color = '#F97316'; else if (e === '3' || e === 'ED 3') color = '#EF4444'; } else { const el = e.toLowerCase(); if (el === 'clear') color = '#22C55E'; if (el === 'carrier') color = '#EAB308'; if (el === 'affected') color = '#EF4444'; } return `${_esc(ergebnis || '—')}`; } function _geneticBadge(ergebnis) { const e = (ergebnis || '').toLowerCase().trim(); let color = '#6B7280'; if (e === 'clear') color = '#22C55E'; if (e === 'carrier') color = '#F59E0B'; if (e === 'affected') color = '#EF4444'; return `${_esc(ergebnis || '—')}`; } function _titleTypBadge(typ) { const t = (typ || '').toLowerCase(); const colors = { ausstellung: '#8B5CF6', arbeit: '#F59E0B', champion: '#EF4444', sport: '#3B82F6', zucht: '#10B981', }; const color = colors[t] || '#6B7280'; return `${_esc(typ || '—')}`; } // ---------------------------------------------------------- // INIT / LIFECYCLE // ---------------------------------------------------------- async function init(container, appState, params) { _container = container; _appState = appState; _hundId = params?.id ? parseInt(params.id) : null; if (!_hundId) { _container.innerHTML = `
${UI.icon('warning')}

Kein Hund angegeben.

`; return; } _renderSkeleton(); await _load(); } function refresh() { if (_hundId) _load(); } function onDogChange() {} // ---------------------------------------------------------- // Daten laden // ---------------------------------------------------------- async function _load() { try { const [hund, tree, health, genetic, titles] = await Promise.all([ API.zuchthunde.get(_hundId), API.zuchthunde.pedigree(_hundId, 4), API.zuchthunde.healthTests(_hundId), API.zuchthunde.geneticTests(_hundId), API.zuchthunde.titles(_hundId), ]); _hund = hund; _renderAll(hund, tree, health, genetic, titles); } catch (err) { _container.innerHTML = `
${UI.icon('warning')}

${_esc(err.message || 'Fehler beim Laden.')}

`; } } // ---------------------------------------------------------- // Skeleton während des Ladens // ---------------------------------------------------------- function _renderSkeleton() { _container.innerHTML = `
${UI.skeleton(6)}
`; _container.querySelector('.zp-back-btn')?.addEventListener('click', () => { if (window.history.length > 1) history.back(); else App.navigate('zuchthunde'); }); } // ---------------------------------------------------------- // Vollständige Seite rendern // ---------------------------------------------------------- function _renderAll(hund, tree, health, genetic, titles) { _container.innerHTML = `
${_renderHeader(hund)}

${UI.icon('tree-structure')} Stammbaum

${_renderPedigree(tree, 4)}

${UI.icon('heart')} Gesundheitstests

${_renderHealthTable(health)}

${UI.icon('dna')} Gentests

${_renderGeneticTable(genetic)}

${UI.icon('trophy')} Titel & Auszeichnungen

${_renderTitlesList(titles)}
`; // Zurück-Button verdrahten _container.querySelector('.zp-back-btn')?.addEventListener('click', () => { if (window.history.length > 1) history.back(); else App.navigate('zuchthunde'); }); // Link teilen _container.querySelector('.zp-share-btn')?.addEventListener('click', () => { const url = window.location.origin + '#zucht-profil&id=' + _hundId; navigator.clipboard.writeText(url).then(() => { UI.toast.success('Link kopiert!'); }).catch(() => { UI.toast.error('Kopieren nicht möglich.'); }); }); // Stammbaum-Klicks verdrahten (außer Gen 1 = Proband selbst) _container.querySelectorAll('.pedigree-cell[data-hund-id]').forEach(cell => { const nodeId = parseInt(cell.dataset.hundId); const gen = parseInt(cell.dataset.gen || '1'); if (gen === 1) return; // Proband — kein Klick nötig cell.style.cursor = 'pointer'; cell.addEventListener('click', () => { App.navigate('zucht-profil', true, { id: nodeId }); }); }); } // ---------------------------------------------------------- // Header // ---------------------------------------------------------- function _renderHeader(h) { const gIcon = h.geschlecht === 'maennlich' ? UI.icon('gender-male') : h.geschlecht === 'weiblich' ? UI.icon('gender-female') : UI.icon('dog'); const geburtsjahrLabel = h.geburtsdatum ? `*${new Date(h.geburtsdatum + 'T12:00:00').toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' })}` : null; const geschlechtLabel = h.geschlecht === 'maennlich' ? 'Rüde' : h.geschlecht === 'weiblich' ? 'Hündin' : null; const metaItems = [ h.rasse ? `${UI.icon('paw-print')} ${_esc(h.rasse)}` : null, geschlechtLabel ? `${gIcon} ${geschlechtLabel}` : null, geburtsjahrLabel ? `${UI.icon('calendar-dots')} ${geburtsjahrLabel}` : null, ].filter(Boolean); const identItems = [ h.chip_nr ? `${UI.icon('barcode')} Chip: ${_esc(h.chip_nr)}` : null, h.zuchtbuchnummer ? `${UI.icon('book-open')} ZB-Nr.: ${_esc(h.zuchtbuchnummer)}` : null, h.taetowier_nr ? `${UI.icon('pencil-simple')} Tätowierung: ${_esc(h.taetowier_nr)}` : null, h.farbe ? `${UI.icon('palette')} ${_esc(h.farbe)}` : null, ].filter(Boolean); const elternItems = [ h.vater_name ? `Vater: ${_esc(h.vater_name)}` : null, h.mutter_name ? `Mutter: ${_esc(h.mutter_name)}` : null, ].filter(Boolean); return `
${gIcon}

${_esc(h.name)} ${h.rufname ? `(${_esc(h.rufname)})` : ''}

${metaItems.length ? `
${metaItems.map(m => `${m}`).join('·')}
` : ''} ${identItems.length ? `
${identItems.map(m => `${m}`).join('')}
` : ''} ${elternItems.length ? `
${elternItems.join('  ·  ')}
` : ''} ${h.notiz ? `
${_esc(h.notiz)}
` : ''}
`; } // ---------------------------------------------------------- // Stammbaum // ---------------------------------------------------------- function _renderPedigree(tree, generations) { const totalRows = Math.pow(2, generations - 1); // 8 für 4 Generationen // Alle Knoten rekursiv einsammeln function collect(node, gen, rowStart, rowSpan) { if (gen > generations) return []; const items = [{ node: node || null, gen, rowStart, rowSpan }]; if (gen < generations) { const half = rowSpan / 2; items.push(...collect(node?.vater || null, gen + 1, rowStart, half)); items.push(...collect(node?.mutter || null, gen + 1, rowStart + half, half)); } return items; } const items = collect(tree, 1, 1, totalRows); const cells = items.map(({ node, gen, rowStart, rowSpan }) => { const isEmpty = !node; return `
${node ? _pedigreeNodeHTML(node, gen) : `
${UI.icon('question')}
`}
`; }).join(''); return `
${cells}
`; } function _pedigreeNodeHTML(node, gen) { const gIcon = node.geschlecht === 'maennlich' ? UI.icon('gender-male') : node.geschlecht === 'weiblich' ? UI.icon('gender-female') : ''; const dob = node.geburtsdatum ? `*${new Date(node.geburtsdatum + 'T12:00:00').getFullYear()}` : ''; const isProband = gen === 1; const bgColor = isProband ? 'var(--c-primary)' : 'var(--c-surface-2, var(--c-surface))'; const textColor = isProband ? '#fff' : 'var(--c-text)'; const borderColor = isProband ? 'var(--c-primary)' : 'var(--c-border)'; return `
${gIcon} ${_esc(node.name)}
${node.rufname ? `
${_esc(node.rufname)}
` : ''} ${dob ? `
${dob}
` : ''} ${node.zuchtbuchnummer ? `
${_esc(node.zuchtbuchnummer)}
` : ''}
`; } // ---------------------------------------------------------- // Gesundheitstests-Tabelle // ---------------------------------------------------------- function _renderHealthTable(tests) { if (!tests || !tests.length) { return `

Noch keine Gesundheitstests eingetragen.

`; } const rows = tests.map(t => ` ${_esc(t.test_typ || 'Sonstiges')} ${t.test_name ? `
${_esc(t.test_name)}` : ''} ${_healthBadge(t.test_typ || '', t.ergebnis)} ${t.untersuch_am ? _fmtDate(t.untersuch_am) : '—'} ${t.labor ? _esc(t.labor) : '—'} `).join(''); return `
${rows}
Test Ergebnis Datum Labor / Institut
`; } // ---------------------------------------------------------- // Gentests-Tabelle // ---------------------------------------------------------- function _renderGeneticTable(tests) { if (!tests || !tests.length) { return `

Noch keine Gentests eingetragen.

`; } const rows = tests.map(t => ` ${_esc(t.marker_name || '—')} ${_geneticBadge(t.ergebnis_klasse)} ${t.getestet_am ? _fmtDate(t.getestet_am) : '—'} ${t.labor ? _esc(t.labor) : '—'} `).join(''); return `
${rows}
Marker / Gen Ergebnis Datum Labor
`; } // ---------------------------------------------------------- // Titel-Liste // ---------------------------------------------------------- function _renderTitlesList(titles) { if (!titles || !titles.length) { return `

Noch keine Titel eingetragen.

`; } // Chronologisch sortieren (neuestes zuerst) const sorted = [...titles].sort((a, b) => { const da = a.verliehen_am || '0000'; const db = b.verliehen_am || '0000'; return db.localeCompare(da); }); const items = sorted.map(t => `
${_titleTypBadge(t.titel_typ)} ${t.formwert ? `${_esc(t.formwert)}` : ''}
${_esc(t.titel_name || '—')}
${t.verliehen_am ? `${UI.icon('calendar-dots')} ${_fmtDate(t.verliehen_am)}` : ''} ${t.ort ? ` ·  ${UI.icon('map-pin')} ${_esc(t.ort)}` : ''} ${t.richter ? ` ·  ${UI.icon('user')} ${_esc(t.richter)}` : ''} ${t.ausstellung ? `
${UI.icon('ticket')} ${_esc(t.ausstellung)}` : ''}
`).join(''); return `
${items}
`; } // ---------------------------------------------------------- // CSS (einmalig injizieren) // ---------------------------------------------------------- (function _injectStyles() { if (document.getElementById('zp-styles')) return; const s = document.createElement('style'); s.id = 'zp-styles'; s.textContent = ` /* Layout */ .zp-layout { padding: var(--space-4) 0; } /* Header */ .zp-header { display: flex; align-items: flex-start; gap: var(--space-4); background: var(--c-surface); border: 1px solid var(--c-border); border-radius: var(--radius-lg); padding: var(--space-4); margin-bottom: var(--space-5); } .zp-header-icon { font-size: 2rem; flex-shrink: 0; line-height: 1; margin-top: 2px; } .zp-header-body { flex: 1; min-width: 0; } .zp-header-name { font-size: var(--text-xl); font-weight: var(--weight-bold); margin: 0 0 var(--space-1); display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--space-2); } .zp-header-rufname { font-size: var(--text-base); font-weight: var(--weight-normal); color: var(--c-text-secondary); } .zp-header-meta { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-2); font-size: var(--text-sm); color: var(--c-text-secondary); margin-top: var(--space-1); } .zp-header-ident { flex-direction: column; align-items: flex-start; gap: var(--space-1); font-size: var(--text-xs); margin-top: var(--space-2); } .zp-meta-sep { opacity: .4; } .zp-header-notiz { font-size: var(--text-xs); color: var(--c-text-secondary); font-style: italic; margin-top: var(--space-2); } /* Sektion */ .zp-section { background: var(--c-surface); border: 1px solid var(--c-border); border-radius: var(--radius-lg); padding: var(--space-4); margin-bottom: var(--space-4); } .zp-section-title { font-size: var(--text-base); font-weight: var(--weight-semibold); margin: 0 0 var(--space-4); display: flex; align-items: center; gap: var(--space-2); } /* Stammbaum-Wrapper: horizontal scrollbar auf Mobile */ .zp-pedigree-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; padding-bottom: var(--space-2); } /* Stammbaum-Zellen */ .pedigree-cell { box-sizing: border-box; padding: 2px; min-height: 56px; } .pedigree-cell:not(.pedigree-empty):hover .pedigree-node { opacity: .85; } .pedigree-empty { display: flex; align-items: center; justify-content: center; } .pedigree-unknown { width: 100%; min-height: 52px; border: 1px dashed var(--c-border); border-radius: var(--radius-md); display: flex; align-items: center; justify-content: center; color: var(--c-text-muted); font-size: var(--text-lg); opacity: .5; } /* Tabellen */ .zp-table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; border-radius: var(--radius-md); border: 1px solid var(--c-border); } .zp-table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); white-space: nowrap; } .zp-th { padding: var(--space-2) var(--space-3); text-align: left; font-size: var(--text-xs); font-weight: var(--weight-semibold); text-transform: uppercase; letter-spacing: .04em; color: var(--c-text-secondary); background: var(--c-surface-2, var(--c-bg)); border-bottom: 1px solid var(--c-border); } .zp-td { padding: var(--space-2) var(--space-3); border-bottom: 1px solid var(--c-border); vertical-align: middle; } .zp-table tbody tr:last-child .zp-td { border-bottom: none; } .zp-table tbody tr:hover { background: var(--c-surface-2, var(--c-bg)); } .zp-td-muted { color: var(--c-text-secondary); font-size: var(--text-xs); } /* Badge */ .zp-badge { display: inline-block; padding: 2px 8px; border-radius: 99px; font-size: var(--text-xs); font-weight: var(--weight-semibold); color: #fff; white-space: nowrap; } /* Titel-Liste */ .zp-titles-list { display: flex; flex-direction: column; gap: var(--space-2); } .zp-title-item { display: flex; align-items: flex-start; gap: var(--space-3); padding: var(--space-3); border: 1px solid var(--c-border); border-radius: var(--radius-md); background: var(--c-surface-2, var(--c-bg)); } .zp-title-badges { display: flex; flex-direction: column; gap: var(--space-1); flex-shrink: 0; } .zp-title-name { font-weight: var(--weight-semibold); font-size: var(--text-sm); flex: 1; min-width: 0; } .zp-title-meta { font-size: var(--text-xs); color: var(--c-text-secondary); margin-top: 2px; } /* Leer-Zustand */ .zp-empty { color: var(--c-text-muted); font-size: var(--text-sm); margin: 0; font-style: italic; } `; document.head.appendChild(s); })(); return { init, refresh, onDogChange }; })();