/* BAN YARO — Läufigkeit & Trächtigkeit (Züchter) */ window.Page_laeufi = (() => { let _container, _appState; let _hunde = []; let _openHundId = null; let _breederInfo = null; // ---------------------------------------------------------- // Init / Refresh // ---------------------------------------------------------- async function init(container, appState) { _container = container; _appState = appState; if (!appState.user || !['breeder','admin'].includes(appState.user.rolle)) { _container.innerHTML = `

Nur für verifizierte Züchter.

`; return; } API.breeder.status().then(s => { _breederInfo = s?.profile ? { zwingername: s.profile.zwingername, logo_url: s.profile.logo_url } : null; const headerEl = _container.querySelector('#breeder-private-header'); if (headerEl) headerEl.outerHTML = _privateHeader(); }).catch(() => {}); _render(); await _loadHunde(); } function refresh() { _loadHunde(); } function onDogChange() {} // ---------------------------------------------------------- // Grundstruktur // ---------------------------------------------------------- function _privateHeader() { const zwinger = _breederInfo?.zwingername || 'Mein Zwinger'; const logoUrl = _breederInfo?.logo_url || null; const logoHtml = logoUrl ? `Logo` : `
`; return `
${logoHtml}

${UI.escape(zwinger)}

Privater Bereich · Nur du siehst das
`; } function _render() { _container.innerHTML = `
${_privateHeader()}

${UI.icon('thermometer')} Läufigkeit & Trächtigkeit

Lädt…

`; } async function _loadHunde() { try { const alle = await API.zuchthunde.list(); _hunde = alle.filter(h => h.geschlecht === 'weiblich'); _renderHundeList(); } catch (err) { document.getElementById('laeufi-list').innerHTML = `

${UI.escape(err.message || 'Fehler')}

`; } } function _renderHundeList() { const el = document.getElementById('laeufi-list'); if (!_hunde.length) { el.innerHTML = `
${UI.icon('gender-female')}

Keine Hündinnen in der Zuchtkartei

Lege zuerst weibliche Hunde in der Zuchtkartei an.

`; return; } el.innerHTML = _hunde.map(h => _hundCardHTML(h)).join(''); _hunde.forEach(h => { document.getElementById(`laeufi-toggle-${h.id}`) ?.addEventListener('click', () => _toggleHund(h.id)); }); if (_openHundId) _toggleHund(_openHundId, true); } // ---------------------------------------------------------- // Hund-Karte // ---------------------------------------------------------- function _hundCardHTML(h) { const alter = h.geburtsdatum ? Math.floor((Date.now() - new Date(h.geburtsdatum)) / 31557600000) + ' J' : ''; return `
${UI.escape(h.name)} ${h.rufname ? `"${UI.escape(h.rufname)}"` : ''} ${alter ? `${alter}` : ''}
${h.rasse_text || h.farbe ? `
${[h.rasse_text, h.farbe].filter(Boolean).map(s => UI.escape(s)).join(' · ')}
` : ''}
${UI.icon('caret-down')}
`; } async function _toggleHund(hundId, forceOpen = false) { const detail = document.getElementById(`laeufi-detail-${hundId}`); if (!detail) return; const isOpen = detail.style.display !== 'none'; if (isOpen && !forceOpen) { detail.style.display = 'none'; _openHundId = null; return; } detail.style.display = ''; _openHundId = hundId; await _loadHundContent(hundId); } // ---------------------------------------------------------- // Inhalt pro Hündin laden // ---------------------------------------------------------- async function _loadHundContent(hundId) { const el = document.getElementById(`laeufi-content-${hundId}`); if (!el) return; try { const [laeufiList, deckList] = await Promise.all([ API.laeufi.list(hundId), API.laeufi.listDeck(hundId), ]); _renderHundContent(el, hundId, laeufiList, deckList); } catch (err) { el.innerHTML = `

${UI.escape(err.message || 'Fehler')}

`; } } function _renderHundContent(container, hundId, laeufiList, deckList) { container.innerHTML = `

${UI.icon('drop')} Läufigkeiten

${_renderLaeufiEntries(hundId, laeufiList)}

${UI.icon('heart')} Deckdaten & Trächtigkeit

${_renderDeckEntries(hundId, deckList)}
`; document.getElementById(`laeufi-add-btn-${hundId}`) ?.addEventListener('click', () => _showLaeufiForm(hundId, null, laeufiList)); document.getElementById(`deck-add-btn-${hundId}`) ?.addEventListener('click', () => _showDeckForm(hundId, null, laeufiList)); // Edit/Delete Events für Läufigkeiten container.querySelectorAll('.laeufi-edit-btn').forEach(btn => { const id = parseInt(btn.dataset.id); const entry = laeufiList.find(l => l.id === id); if (entry) btn.addEventListener('click', () => _showLaeufiForm(hundId, entry, laeufiList)); }); container.querySelectorAll('.laeufi-delete-btn').forEach(btn => { const id = parseInt(btn.dataset.id); btn.addEventListener('click', async () => { if (!window.confirm('Läufigkeit und alle Progesterontests löschen?')) return; try { await API.laeufi.remove(id); await _loadHundContent(hundId); } catch (err) { UI.toast.error(err.message); } }); }); container.querySelectorAll('.laeufi-prog-btn').forEach(btn => { const id = parseInt(btn.dataset.id); const entry = laeufiList.find(l => l.id === id); if (entry) btn.addEventListener('click', () => _showProgModal(hundId, entry)); }); // Edit/Delete Events für Deckdaten container.querySelectorAll('.deck-edit-btn').forEach(btn => { const id = parseInt(btn.dataset.id); const entry = deckList.find(d => d.id === id); if (entry) btn.addEventListener('click', () => _showDeckForm(hundId, entry, laeufiList)); }); container.querySelectorAll('.deck-delete-btn').forEach(btn => { const id = parseInt(btn.dataset.id); btn.addEventListener('click', async () => { if (!window.confirm('Deckdaten löschen?')) return; try { await API.laeufi.removeDeck(id); await _loadHundContent(hundId); } catch (err) { UI.toast.error(err.message); } }); }); } // ---------------------------------------------------------- // Läufigkeits-Einträge rendern // ---------------------------------------------------------- function _renderLaeufiEntries(hundId, list) { if (!list.length) return `
Noch keine Läufigkeit eingetragen.
`; return list.map(l => `
${_fmtDate(l.beginn)} ${l.ende ? `→ ${_fmtDate(l.ende)} ${_daysDiff(l.beginn, l.ende)} Tage` : ''}
${l.notiz ? `

${UI.escape(l.notiz)}

` : ''}
`).join(''); } // ---------------------------------------------------------- // Deckdaten + Meilensteine rendern // ---------------------------------------------------------- const _TRAECHTIG = { 0: { label: 'Unbekannt', color: '#6b7280' }, 1: { label: 'Trächtig ✓', color: '#16a34a' }, [-1]: { label: 'Nicht trächtig', color: '#dc2626' } }; const _DECKART = { natuerlich: 'Natürlich', ki_frisch: 'KI frisch', ki_tiefgekuehlt: 'KI tiefgekühlt', ki_gefroren: 'KI gefroren' }; function _renderDeckEntries(hundId, list) { if (!list.length) return `
Noch keine Deckung eingetragen.
`; return list.map(d => { const tc = _TRAECHTIG[d.traechtig] || _TRAECHTIG[0]; const heute = d.meilensteine?.find(m => !m.vorbei); const naechster = heute || d.meilensteine?.[d.meilensteine.length - 1]; return `
${UI.icon('heart')} Deckung ${_fmtDate(d.deckdatum)} ${tc.label}
${d.ruede_name ? `${UI.icon('dog')} Rüde: ${UI.escape(d.ruede_name)}` : ''} ${UI.icon('arrows-clockwise')} ${_DECKART[d.deckart] || d.deckart} ${d.ultraschall_datum ? `${UI.icon('heartbeat')} Ultraschall: ${_fmtDate(d.ultraschall_datum)}` : ''}
${naechster && d.traechtig === 1 ? `
${UI.icon('calendar-dots')} ${naechster.vorbei ? 'Letzter' : 'Nächster'} Meilenstein: ${naechster.label} · Tag ${naechster.tag} · ${_fmtDate(naechster.datum)}
` : ''}
${d.traechtig === 1 && d.meilensteine?.length ? _renderMeilensteine(d.meilensteine) : ''}
`; }).join(''); } function _renderMeilensteine(meilensteine) { return `

${UI.icon('calendar-check')} Trächtigkeits-Meilensteine

${meilensteine.map(m => `
${m.vorbei ? '✓' : m.tag} ${_fmtDate(m.datum)} ${UI.escape(m.label)}
`).join('')}
`; } // ---------------------------------------------------------- // Formulare // ---------------------------------------------------------- function _showLaeufiForm(hundId, entry, _allLaeufi) { const isEdit = !!entry; const v = entry || {}; const today = new Date().toISOString().slice(0, 10); UI.modal.open({ title: isEdit ? 'Läufigkeit bearbeiten' : 'Läufigkeit eintragen', body: `
`, footer: ` `, }); document.getElementById('laeufi-form').addEventListener('submit', async e => { e.preventDefault(); const fd = new FormData(e.target); const data = { beginn: fd.get('beginn'), ende: fd.get('ende') || null, notiz: fd.get('notiz') || null }; try { if (isEdit) await API.laeufi.update(entry.id, data); else await API.laeufi.add(hundId, data); UI.modal.close(); await _loadHundContent(hundId); UI.toast.success(isEdit ? 'Gespeichert.' : 'Läufigkeit eingetragen.'); } catch (err) { UI.toast.error(err.message); } }); } function _showDeckForm(hundId, entry, laeufiList) { const isEdit = !!entry; const v = entry || {}; const today = new Date().toISOString().slice(0, 10); const laeufiOpts = laeufiList.map(l => `` ).join(''); UI.modal.open({ title: isEdit ? 'Deckung bearbeiten' : 'Deckung eintragen', body: `
`, footer: ` `, }); document.getElementById('deck-form').addEventListener('submit', async e => { e.preventDefault(); const fd = new FormData(e.target); const data = { deckdatum: fd.get('deckdatum'), laeufi_id: fd.get('laeufi_id') ? parseInt(fd.get('laeufi_id')) : null, ruede_name: fd.get('ruede_name') || null, deckart: fd.get('deckart'), traechtig: parseInt(fd.get('traechtig')), ultraschall_datum: fd.get('ultraschall_datum') || null, notiz: fd.get('notiz') || null, }; try { if (isEdit) await API.laeufi.updateDeck(entry.id, data); else await API.laeufi.addDeck(hundId, data); UI.modal.close(); await _loadHundContent(hundId); UI.toast.success(isEdit ? 'Gespeichert.' : 'Deckung eingetragen.'); } catch (err) { UI.toast.error(err.message); } }); } // ---------------------------------------------------------- // Progesterontests-Modal // ---------------------------------------------------------- async function _showProgModal(hundId, laeufi) { UI.modal.open({ title: `Progesterontests — ${_fmtDate(laeufi.beginn)}`, body: `

Lädt…

`, footer: ` `, }); await _loadProgContent(laeufi.id); document.getElementById('prog-add-btn') ?.addEventListener('click', () => _showProgForm(hundId, laeufi.id, null)); } async function _loadProgContent(laeufiId) { const el = document.getElementById('prog-modal-content'); if (!el) return; const tests = await API.laeufi.listProg(laeufiId); if (!tests.length) { el.innerHTML = `

Noch keine Tests eingetragen.

`; return; } el.innerHTML = ` ${tests.map(t => ` `).join('')}
Datum Wert Labor
${_fmtDate(t.datum)} ${t.wert != null ? `${t.wert} ${UI.escape(t.einheit)}` : '—'} ${t.wert != null ? `${_progEinschaetzung(t.wert, t.einheit)}` : ''} ${t.labor ? UI.escape(t.labor) : '—'}
`; el.querySelectorAll('.prog-delete-btn').forEach(btn => { btn.addEventListener('click', async () => { try { await API.laeufi.removeProg(parseInt(btn.dataset.id)); await _loadProgContent(laeufiId); } catch (err) { UI.toast.error(err.message); } }); }); } function _progEinschaetzung(wert, einheit) { if (einheit !== 'ng/ml') return ''; if (wert < 2) return '(Basiswert)'; if (wert < 5) return '(Anstieg)'; if (wert < 10) return '(LH-Peak Nähe)'; if (wert < 15) return '(Ovulation)'; return '(Post-Ovulation)'; } function _showProgForm(hundId, laeufiId, _entry) { const today = new Date().toISOString().slice(0, 10); UI.modal.open({ title: 'Progesterontest eintragen', body: `
`, footer: ` `, }); document.getElementById('prog-form').addEventListener('submit', async e => { e.preventDefault(); const fd = new FormData(e.target); const wertRaw = fd.get('wert'); const data = { datum: fd.get('datum'), wert: wertRaw ? parseFloat(wertRaw) : null, einheit: fd.get('einheit'), labor: fd.get('labor') || null, notiz: fd.get('notiz') || null, }; try { await API.laeufi.addProg(laeufiId, data); UI.modal.close(); // Prog-Modal neu öffnen const laeufi = { id: laeufiId, beginn: '' }; await _showProgModal(hundId, laeufi); await _loadHundContent(hundId); UI.toast.success('Test eingetragen.'); } catch (err) { UI.toast.error(err.message); } }); } // ---------------------------------------------------------- // Hilfsfunktionen // ---------------------------------------------------------- function _fmtDate(iso) { if (!iso) return '—'; const [y, m, d] = iso.slice(0, 10).split('-'); return `${d}.${m}.${y}`; } function _daysDiff(a, b) { if (!a || !b) return ''; return Math.round((new Date(b) - new Date(a)) / 86400000); } return { init, refresh, onDogChange }; })();