/* ============================================================
BAN YARO — Ernährung
Tabs: Kalorien-Rechner | Futter-Guide | Giftliste | KI-Berater
============================================================ */
window.Page_ernaehrung = (() => {
let _container = null;
let _appState = null;
let _activeTab = 'rechner';
let _profil = {};
const TABS = [
{ key: 'rechner', label: 'Kalorien', icon: ' ' },
{ key: 'guide', label: 'Futter-Guide', icon: ' ' },
{ key: 'gift', label: 'Giftliste', icon: ' ' },
{ key: 'ki', label: 'KI-Berater', icon: ' ' },
{ key: 'vertraeglichkeit', label: 'Verträglichkeit', icon: ' ' },
];
// ------------------------------------------------------------------
// Escape helper
// ------------------------------------------------------------------
// ------------------------------------------------------------------
// LIFECYCLE
// ------------------------------------------------------------------
async function init(container, appState, params) {
_container = container;
_appState = appState;
if (params?.tab && TABS.some(t => t.key === params.tab)) {
_activeTab = params.tab;
}
await _render();
}
async function refresh() {
await _render();
}
async function onDogChange() {
_profil = {};
_activeTab = 'rechner'; // Tab zurücksetzen damit neuer Hund frisch startet
await _render();
}
// ------------------------------------------------------------------
// RENDER
// ------------------------------------------------------------------
async function _render() {
if (!_appState.activeDog) {
_container.innerHTML = UI.emptyState({
icon: ' ',
title: 'Noch kein Hund angelegt',
text: 'Erstelle zuerst ein Hundeprofil.',
action: `Profil erstellen `,
});
return;
}
// Profil laden
const dog = _appState.activeDog;
try {
_profil = await API.get(`/dogs/${dog.id}/ernaehrung`);
} catch (_) {
_profil = {};
}
_container.innerHTML = `
${UI.dogChip(_appState)}
`;
UI.bindDogChip(_container, _appState);
_renderTabBar();
_renderTab();
}
// ------------------------------------------------------------------
// TAB-BAR
// ------------------------------------------------------------------
function _renderTabBar() {
const el = _container.querySelector('#ern-tabs');
if (!el) return;
el.innerHTML = TABS.map(t => `
${t.icon} ${t.label}
`).join('');
el.querySelectorAll('.by-tab').forEach(btn => {
btn.addEventListener('click', () => {
_activeTab = btn.dataset.tab;
el.querySelectorAll('.by-tab').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
_renderTab();
});
});
}
function _renderTab() {
const el = _container.querySelector('#ern-tab-content');
if (!el) return;
switch (_activeTab) {
case 'rechner': _renderRechner(el); break;
case 'guide': _renderGuide(el); break;
case 'gift': _renderGift(el); break;
case 'ki': _renderKi(el); break;
case 'vertraeglichkeit': _renderVertraeglichkeit(el); break;
}
}
// ------------------------------------------------------------------
// TAB 1: KALORIEN-RECHNER
// ------------------------------------------------------------------
function _renderRechner(el) {
const dog = _appState.activeDog;
// Auto-Werte aus Hundeprofil
const gewichtDefault = dog?.gewicht || '';
const alterDefault = dog?.alter || '';
el.innerHTML = `
🏃 Aktivität
🛋️ Gemütlich
🚶 Normal
🏃 Aktiv
🏅 Sportlich
✂️ Kastriert / Sterilisiert
Nein
Ja
Kalorienbedarf berechnen
`;
// Aktivität Pills
el.querySelectorAll('[data-akt]').forEach(btn => {
btn.addEventListener('click', () => {
el.querySelectorAll('[data-akt]').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
});
});
// Kastriert Pills
el.querySelectorAll('[data-kas]').forEach(btn => {
btn.addEventListener('click', () => {
el.querySelectorAll('[data-kas]').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
});
});
el.querySelector('#ern-rechner-btn').addEventListener('click', () => _berechne(el));
}
function _berechne(el) {
const gewicht = parseFloat(el.querySelector('#ern-gewicht').value);
const aktivitaet = el.querySelector('[data-akt].active')?.dataset.akt || 'normal';
const kastriert = el.querySelector('[data-kas].active')?.dataset.kas === 'ja';
if (!gewicht || gewicht < 0.5) {
UI.toast.warning('Bitte ein gültiges Gewicht eingeben.');
return;
}
const rer = 70 * Math.pow(gewicht, 0.75);
const faktoren = {
gering: { intakt: 1.2, kastriert: 1.0 },
normal: { intakt: 1.6, kastriert: 1.4 },
aktiv: { intakt: 1.8, kastriert: 1.6 },
sport: { intakt: 2.1, kastriert: 1.9 },
};
const kcal = Math.round(rer * faktoren[aktivitaet][kastriert ? 'kastriert' : 'intakt']);
// Umrechnung in Futtermengen
const trocken = Math.round(kcal / 3.5); // ~350 kcal/100g
const nass = Math.round(kcal / 0.85); // ~85 kcal/100g
const barf = Math.round(kcal / 1.5); // ~150 kcal/100g
const kcalFormatted = kcal.toLocaleString('de-DE');
const resultEl = el.querySelector('#ern-rechner-result');
resultEl.style.display = '';
resultEl.innerHTML = `
ca. ${kcalFormatted} kcal
pro Tag
🌾 Trockenfutter
(~350 kcal/100g)
${trocken} g / Tag
= ${Math.round(trocken/2)} g morgens + ${Math.round(trocken/2)} g abends
🥫 Nassfutter
(~85 kcal/100g)
${nass} g / Tag
= ${Math.round(nass/2)} g morgens + ${Math.round(nass/2)} g abends
🥩 BARF
(~150 kcal/100g)
${barf} g / Tag
= ${Math.round(barf/2)} g morgens + ${Math.round(barf/2)} g abends
Richtwert nach Nationaler Forschungsratsformel (NRC). Immer den Körperzustand beobachten.
`;
// Profil-Speichern einblenden und kcal vorbelegen
const profilSection = el.querySelector('#ern-profil-speichern');
profilSection.style.display = '';
// kcal für Speichern merken
profilSection.dataset.kcal = kcal;
el.querySelector('#ern-prof-save-btn').onclick = () => _speichereProfil(el, kcal);
}
async function _speichereProfil(el, kcal) {
const dog = _appState.activeDog;
const futter_typ = el.querySelector('#ern-prof-typ').value || null;
const marke = el.querySelector('#ern-prof-marke').value.trim() || null;
const portionen = parseInt(el.querySelector('#ern-prof-portionen').value) || 2;
const notizen = el.querySelector('#ern-prof-notizen').value.trim() || null;
const btn = el.querySelector('#ern-prof-save-btn');
await UI.asyncButton(btn, async () => {
try {
_profil = await API.put(`/dogs/${dog.id}/ernaehrung`, {
futter_typ, marke, kcal_tag: kcal, portionen, notizen,
});
UI.toast.success('Profil gespeichert.');
} catch (err) {
UI.toast.error(err.message || 'Fehler beim Speichern.');
}
});
}
// ------------------------------------------------------------------
// TAB 2: FUTTER-GUIDE
// ------------------------------------------------------------------
function _renderGuide(el) {
const cards = [
{
id: 'barf',
emoji: '🥩',
titel: 'BARF (Rohfütterung)',
inhalt: `
Zusammensetzung: 70 % Muskelfleisch, 10 % rohe Knochen, 10 % Organe, 10 % Gemüse & Obst
Vorteile: Naturnahste Ernährungsform, glänzendes Fell, weniger Kot, keine Zusatzstoffe
Risiken: Keimbelastung durch rohes Fleisch, Calcium-Phosphor-Balance muss stimmen, zeitaufwändig und teurer
Tipp: Niemals BARF und Trockenfutter in derselben Mahlzeit mischen — unterschiedliche Verdauungszeiten können zu Problemen führen.
`,
},
{
id: 'nass',
emoji: '🥫',
titel: 'Nassfutter',
inhalt: `
Zusammensetzung: 70–80 % Wasseranteil, meist höherer Fleischanteil als Trockenfutter
Vorteile: Hunde trinken automatisch mehr (gut für die Niere), schmackhafter, gut für wählerische Hunde
Worauf achten: Erste Zutat auf der Liste = Fleisch (nicht „Tierische Nebenerzeugnisse"), kein Zucker, kein Karamell
Zähne: Schlechter für die Zahngesundheit als Trockenfutter — öfter Zähne putzen oder Kauartikel geben.
`,
},
{
id: 'trocken',
emoji: '🌾',
titel: 'Trockenfutter',
inhalt: `
Zusammensetzung: 6–10 % Wasser, ca. 350–400 kcal/100 g, konzentrierte Nährstoffe
Gute Zutaten: Benanntes Fleisch an erster Stelle (Huhn, Lachs), mind. 40 % Tierprotein, kein Getreide als Hauptzutat
Schlechte Zutaten: „Getreide" als erste Zutat, Zucker, Karamell, Konservierungsstoffe E320 / E321
Wichtig: Immer frisches Wasser bereitstellen — Trockenfutter enthält kaum Feuchtigkeit.
`,
},
];
el.innerHTML = `
Klicke auf eine Karte für Details.
${cards.map(c => `
${c.emoji} ${c.titel}
${c.inhalt}
`).join('')}
`;
el.querySelectorAll('.ern-guide-card').forEach(card => {
card.querySelector('.ern-guide-head').addEventListener('click', () => {
const body = card.querySelector('.ern-guide-body');
const chevron = card.querySelector('.ern-guide-chevron');
const open = body.style.display !== 'none';
body.style.display = open ? 'none' : '';
chevron.style.transform = open ? '' : 'rotate(180deg)';
});
});
}
// ------------------------------------------------------------------
// TAB 3: GIFTLISTE
// ------------------------------------------------------------------
function _renderGift(el) {
const items = [
{ emoji: '🍫', name: 'Schokolade', grund: 'Theobromin → Herzrasen, Krämpfe, kann tödlich sein' },
{ emoji: '🍇', name: 'Trauben & Rosinen', grund: 'Nierenversagen — auch kleinste Mengen gefährlich' },
{ emoji: '🧅', name: 'Zwiebeln & Knoblauch', grund: 'Zerstören rote Blutkörperchen → Anämie' },
{ emoji: '🥑', name: 'Avocado', grund: 'Persin → Erbrechen, Durchfall, Atemnot' },
{ emoji: '🌰', name: 'Macadamia-Nüsse', grund: 'Lähmungserscheinungen, Zittern, Erbrechen' },
{ emoji: '🍬', name: 'Xylitol (Süßstoff)', grund: 'Schwere Leberschäden, Unterzucker — oft in Kaugummi' },
{ emoji: '🥛', name: 'Milch & Milchprodukte', grund: 'Laktose-Intoleranz bei vielen Hunden → Durchfall' },
{ emoji: '🦴', name: 'Gekochte Knochen', grund: 'Splitter → innere Verletzungen, Darmverschluss' },
{ emoji: '☕', name: 'Koffein (Kaffee, Tee)', grund: 'Herzrasen, Zittern, Nervensystem' },
{ emoji: '🧂', name: 'Salz', grund: 'Natriumvergiftung → Erbrechen, Krämpfe' },
];
el.innerHTML = `
⚠️ Notfall-Tierarzt: Bei Verdacht auf Vergiftung sofort zum Tierarzt.
Nicht abwarten, auch wenn noch keine Symptome sichtbar sind.
${items.map(item => `
${item.emoji}
${UI.escape(item.name)}
${UI.escape(item.grund)}
`).join('')}
Diese Liste ist nicht vollständig. Im Zweifel gilt: lieber weglassen.
`;
}
// ------------------------------------------------------------------
// TAB 4: KI-FUTTERBERATER
// ------------------------------------------------------------------
function _renderKi(el) {
const dog = _appState.activeDog;
el.innerHTML = `
Der KI-Futterberater beantwortet Ernährungsfragen für
${UI.escape(dog?.name || 'deinen Hund')} .
Bei Gesundheitsfragen immer den Tierarzt zurate ziehen.
${[
'Welches Futter empfiehlst du für meine Rasse?',
'Wie oft soll ich meinen Hund füttern?',
'Ist Getreide im Futter schlecht?',
'Welche Leckerlis sind gesund?',
].map(q => `
${UI.escape(q)}
`).join('')}
`;
// Vorschläge
el.querySelectorAll('.ern-ki-vorschlag').forEach(btn => {
btn.addEventListener('click', () => {
el.querySelector('#ern-ki-frage').value = btn.dataset.q;
el.querySelector('#ern-ki-frage').focus();
});
});
// Senden
el.querySelector('#ern-ki-send-btn').addEventListener('click', () => _kiSenden(el));
el.querySelector('#ern-ki-frage').addEventListener('keydown', e => {
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) _kiSenden(el);
});
}
async function _kiSenden(el) {
const dog = _appState.activeDog;
const frageEl = el.querySelector('#ern-ki-frage');
const frage = frageEl.value.trim();
if (!frage) {
UI.toast.warning('Bitte eine Frage eingeben.');
return;
}
const chatEl = el.querySelector('#ern-ki-chat');
const sendBtn = el.querySelector('#ern-ki-send-btn');
// Userfrage anzeigen
chatEl.insertAdjacentHTML('beforeend', `
`);
frageEl.value = '';
// KI-Antwort Placeholder
const placeholderId = `ern-ki-placeholder-${Date.now()}`;
chatEl.insertAdjacentHTML('beforeend', `
`);
chatEl.scrollTop = chatEl.scrollHeight;
await UI.asyncButton(sendBtn, async () => {
let antwort = '';
try {
const result = await API.post(`/dogs/${dog.id}/ernaehrung/ki-beratung`, {
frage,
dog_name: dog?.name || null,
rasse: dog?.rasse || null,
alter: dog?.alter != null ? String(dog.alter) : null,
gewicht: dog?.gewicht || null,
aktiv: false,
});
antwort = result.antwort || 'Keine Antwort erhalten.';
} catch (err) {
if (err.status === 503) {
antwort = 'Die KI ist momentan nicht verfügbar. Bitte später versuchen.';
} else {
antwort = 'Fehler bei der KI-Anfrage. Bitte später erneut versuchen.';
}
}
const antwortHtml = UI.escape(antwort)
.replace(/\n\n/g, '')
.replace(/\n/g, ' ');
const placeholder = document.getElementById(placeholderId);
if (placeholder) {
placeholder.innerHTML = `
`;
}
chatEl.scrollTop = chatEl.scrollHeight;
});
}
// ------------------------------------------------------------------
// TAB 5: VERTRÄGLICHKEIT
// ------------------------------------------------------------------
async function _renderVertraeglichkeit(el) {
const dog = _appState?.activeDog;
if (!dog) { el.innerHTML = ''; return; }
el.innerHTML = `
Futter erfassen
Reaktion erfassen
Haut- & Fellsymptome zeigen sich erst nach Wochen — trage regelmäßig ein um Muster zu erkennen.
`;
el.querySelector('#vert-btn-futter').addEventListener('click', () => _openFutterModal(el, dog));
el.querySelector('#vert-btn-reaktion').addEventListener('click', () => _openReaktionModal(el, dog));
await _loadAnalyse(el, dog);
await _loadVerlauf(el, dog);
}
function _todayStr() {
return new Date().toISOString().slice(0, 10);
}
function _nowTimeStr() {
const d = new Date();
return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
}
function _openFutterModal(el, dog) {
const id = `fm-${Date.now()}`;
const body = `
`;
const footer = `
Abbrechen
Speichern
`;
UI.modal.open({ title: 'Futter erfassen', body, footer });
// Datalist mit bekannten Futter-Namen füllen
API.dogs.futterList(dog.id).then(list => {
const dl = document.getElementById('vert-futter-datalist');
if (!dl) return;
const names = [...new Set((list || []).map(e => e.futter_name))];
dl.innerHTML = names.map(n => ``).join('');
}).catch(() => {});
setTimeout(() => {
const saveBtn = document.getElementById('vert-futter-save-btn');
if (!saveBtn) return;
saveBtn.addEventListener('click', async (ev) => {
ev.preventDefault();
const form = document.getElementById(id);
if (!form) return;
const fd = new FormData(form);
const data = {
datum: fd.get('datum'),
uhrzeit: fd.get('uhrzeit'),
futter_name: (fd.get('futter_name') || '').trim(),
futter_typ: fd.get('futter_typ') || 'trockenfutter',
menge_g: fd.get('menge_g') ? parseInt(fd.get('menge_g')) : null,
notiz: (fd.get('notiz') || '').trim() || null,
};
if (!data.futter_name) { UI.toast.warning('Bitte Futter-Name angeben.'); return; }
await UI.asyncButton(saveBtn, async () => {
try {
await API.dogs.futterCreate(dog.id, data);
UI.modal.close();
UI.toast.success('Futter gespeichert.');
const tabEl = _container.querySelector('#ern-tab-content');
if (tabEl) await _loadAnalyse(tabEl, dog), await _loadVerlauf(tabEl, dog);
} catch (err) {
UI.toast.error(err.message || 'Fehler beim Speichern.');
}
});
});
}, 50);
}
function _openReaktionModal(el, dog) {
const id = `rm-${Date.now()}`;
const body = `
Datum
Uhrzeit
Reaktion
Glänzendes Fell
Gute Verdauung
Viel Energie
Erbrechen
Durchfall
Blähungen
Weicher Stuhl
Appetitlosigkeit
Juckreiz / Kratzen
Haarausfall
Stumpfes Fell
Schuppenbildung
Hautrötungen / Entzündung
Pfoten lecken (chronisch)
Ohrentzündung
Fettiges Fell / Seborrhö
Schlappheit / Apathie
Nervosität / Unruhe
Ungewöhnlich viel trinken
Sonstiges
Notiz (optional)
`;
const footer = `
Abbrechen
Speichern
`;
UI.modal.open({ title: 'Reaktion erfassen', body, footer });
setTimeout(() => {
// Stern-Buttons
document.querySelectorAll('.vert-stern').forEach(btn => {
btn.addEventListener('click', () => {
const val = parseInt(btn.dataset.val);
const form = document.getElementById(id);
if (form) form.querySelector('[name=intensitaet]').value = val;
document.querySelectorAll('.vert-stern').forEach(b => {
const v = parseInt(b.dataset.val);
b.style.background = v <= val ? 'var(--c-primary)' : 'var(--c-bg-card)';
b.style.color = v <= val ? '#fff' : 'var(--c-text-secondary)';
});
});
});
const saveBtn = document.getElementById('vert-reaktion-save-btn');
if (!saveBtn) return;
saveBtn.addEventListener('click', async (ev) => {
ev.preventDefault();
const form = document.getElementById(id);
if (!form) return;
const fd = new FormData(form);
const data = {
datum: fd.get('datum'),
uhrzeit: fd.get('uhrzeit'),
reaktion_typ: fd.get('reaktion_typ'),
intensitaet: parseInt(fd.get('intensitaet')) || 3,
notiz: (fd.get('notiz') || '').trim() || null,
};
await UI.asyncButton(saveBtn, async () => {
try {
await API.dogs.reaktionCreate(dog.id, data);
UI.modal.close();
UI.toast.success('Reaktion gespeichert.');
const tabEl = _container.querySelector('#ern-tab-content');
if (tabEl) await _loadAnalyse(tabEl, dog), await _loadVerlauf(tabEl, dog);
} catch (err) {
UI.toast.error(err.message || 'Fehler beim Speichern.');
}
});
});
}, 50);
}
async function _loadAnalyse(el, dog) {
const analyseEl = el.querySelector('#vert-analyse');
if (!analyseEl) return;
let data;
try {
data = await API.dogs.futterAnalyse(dog.id);
} catch (_) {
analyseEl.innerHTML = `Analyse nicht verfügbar.
`;
return;
}
if (!data.futter || data.futter.length === 0) {
analyseEl.innerHTML = `
Noch keine Einträge. Erfasse Futter und Reaktionen um die Analyse zu sehen.
`;
return;
}
const STATUS_CFG = {
gut: { label: 'Gut verträglich', color: 'var(--c-success,#22c55e)', bg: 'rgba(34,197,94,0.12)' },
neutral: { label: 'Neutral', color: 'var(--c-warning,#f59e0b)', bg: 'rgba(245,158,11,0.12)' },
problematisch:{ label: 'Problematisch', color: 'var(--c-danger,#ef4444)', bg: 'rgba(239,68,68,0.12)' },
neu: { label: 'Zu wenig Daten', color: 'var(--c-text-muted)', bg: 'var(--c-surface)' },
};
const TYP_LABELS = {
trockenfutter: 'Trockenfutter', nassfutter: 'Nassfutter',
barf: 'BARF', snack: 'Snack', sonstiges: 'Sonstiges',
};
const KAT_LABELS = {
gastro_negativ: 'Magen & Darm',
haut_negativ: 'Haut & Fell',
allgemein_negativ: 'Allgemein',
positiv: 'Positiv',
sonstiges: 'Sonstiges',
};
const hinweisHtml = data.hinweis ? `
${UI.escape(data.hinweis)}
` : '';
analyseEl.innerHTML = `
Verträglichkeits-Analyse
(${data.eintraege_count} Mahlzeiten, ${data.reaktionen_count} Reaktionen)
${hinweisHtml}
${data.futter.map(f => {
const cfg = STATUS_CFG[f.status] || STATUS_CFG.neu;
// Symptom-Kategorien des Futters als Chips
const katChips = Object.entries(f.kategorien || {})
.filter(([kat]) => kat !== 'positiv')
.map(([kat, cnt]) => {
const isHaut = kat === 'haut_negativ';
const isGastro = kat === 'gastro_negativ';
const chipColor = isHaut ? 'var(--c-warning,#f59e0b)' :
isGastro ? 'var(--c-danger,#ef4444)' :
'var(--c-text-muted)';
return `
${UI.escape(KAT_LABELS[kat] || kat)} ×${cnt}
`;
}).join('');
return `
${UI.escape(f.name)}
${UI.escape(TYP_LABELS[f.typ] || f.typ)} · ${f.mahlzeiten} Mahlzeit${f.mahlzeiten !== 1 ? 'en' : ''}
${f.status !== 'neu' ? `· +${f.positiv} / -${f.negativ} ` : ''}
${katChips ? `
${katChips}
` : ''}
${UI.escape(cfg.label)}
`;
}).join('')}
`;
}
async function _loadVerlauf(el, dog) {
const verlaufEl = el.querySelector('#vert-verlauf');
if (!verlaufEl) return;
let eintraege = [], reaktionen = [];
try {
[eintraege, reaktionen] = await Promise.all([
API.dogs.futterList(dog.id),
API.dogs.reaktionList(dog.id),
]);
} catch (_) { return; }
// Letzten 10 Futter + 5 Reaktionen, gemischt chronologisch
const items = [
...(eintraege || []).slice(0, 10).map(e => ({ ...e, _art: 'futter' })),
...(reaktionen || []).slice(0, 5).map(r => ({ ...r, _art: 'reaktion' })),
].sort((a, b) => {
const ta = `${a.datum}T${a.uhrzeit}`;
const tb = `${b.datum}T${b.uhrzeit}`;
return tb.localeCompare(ta);
});
if (items.length === 0) {
verlaufEl.innerHTML = '';
return;
}
const REAK_LABELS = {
// Positiv
verdauung_gut: 'Gute Verdauung',
energie_hoch: 'Viel Energie',
fell_glaenzend: 'Glänzendes Fell',
// Gastro
erbrechen: 'Erbrechen',
durchfall: 'Durchfall',
blaehungen: 'Blähungen',
weicher_stuhl: 'Weicher Stuhl',
appetitlosigkeit: 'Appetitlosigkeit',
// Haut & Fell
juckreiz: 'Juckreiz / Kratzen',
haarausfall: 'Haarausfall',
stumpfes_fell: 'Stumpfes Fell',
schuppenbildung: 'Schuppenbildung',
roetungen: 'Hautrötungen / Entzündung',
pfotenlecken: 'Pfoten lecken (chronisch)',
ohrentzuendung: 'Ohrentzündung',
fettiges_fell: 'Fettiges Fell / Seborrhö',
// Allgemein
schlappheit: 'Schlappheit / Apathie',
nervositaet: 'Nervosität / Unruhe',
viel_trinken: 'Ungewöhnlich viel trinken',
sonstiges: 'Sonstiges',
};
const NEGATIV_TYPEN = new Set([
'erbrechen','durchfall','blaehungen','weicher_stuhl','appetitlosigkeit',
'juckreiz','haarausfall','stumpfes_fell','schuppenbildung','roetungen',
'pfotenlecken','ohrentzuendung','fettiges_fell',
'schlappheit','nervositaet','viel_trinken',
]);
const POSITIV_TYPEN = new Set(['verdauung_gut','energie_hoch','fell_glaenzend']);
verlaufEl.innerHTML = `
Verlauf
${items.map(item => {
if (item._art === 'futter') {
return `
${UI.escape(item.futter_name)}
${UI.escape(item.datum)} ${UI.escape(item.uhrzeit)}
${item.menge_g ? ` · ${item.menge_g} g` : ''}
`;
} else {
const isNeg = NEGATIV_TYPEN.has(item.reaktion_typ);
const isPos = POSITIV_TYPEN.has(item.reaktion_typ);
const col = isNeg ? 'var(--c-danger,#ef4444)' : isPos ? 'var(--c-success,#22c55e)' : 'var(--c-text-muted)';
return `
${UI.escape(REAK_LABELS[item.reaktion_typ] || item.reaktion_typ)}
(${item.intensitaet}/5)
${UI.escape(item.datum)} ${UI.escape(item.uhrzeit)}
`;
}
}).join('')}
`;
// Löschen-Buttons
verlaufEl.querySelectorAll('.vert-del-futter').forEach(btn => {
btn.addEventListener('click', async () => {
if (!window.confirm('Eintrag löschen?')) return;
try {
await API.dogs.futterDelete(dog.id, parseInt(btn.dataset.id));
UI.toast.success('Eintrag gelöscht.');
const tabEl = _container.querySelector('#ern-tab-content');
if (tabEl) await _loadAnalyse(tabEl, dog), await _loadVerlauf(tabEl, dog);
} catch (err) {
UI.toast.error(err.message || 'Fehler.');
}
});
});
verlaufEl.querySelectorAll('.vert-del-reaktion').forEach(btn => {
btn.addEventListener('click', async () => {
if (!window.confirm('Reaktion löschen?')) return;
try {
await API.dogs.reaktionDelete(dog.id, parseInt(btn.dataset.id));
UI.toast.success('Reaktion gelöscht.');
const tabEl = _container.querySelector('#ern-tab-content');
if (tabEl) await _loadAnalyse(tabEl, dog), await _loadVerlauf(tabEl, dog);
} catch (err) {
UI.toast.error(err.message || 'Fehler.');
}
});
});
}
// ------------------------------------------------------------------
// PUBLIC API
// ------------------------------------------------------------------
return { init, refresh, onDogChange };
})();