/* 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
? `
`
: `
`;
return `
`;
}
function _render() {
_container.innerHTML = `
${_privateHeader()}
${UI.icon('thermometer')} Läufigkeit & Trächtigkeit
`;
}
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: ``,
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 = `
| Datum |
Wert |
Labor |
|
${tests.map(t => `
| ${_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) : '—'} |
|
`).join('')}
`;
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 };
})();