/* ============================================================ 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: ``, }); 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 => ` `).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 // ------------------------------------------------------------------ // Alter in Jahren (1 Nachkommastelle) aus Geburtstag, '' wenn unbekannt/ungültig. function _alterJahre(geburtstag) { if (!geburtstag) return ''; const birth = new Date(geburtstag + 'T00:00:00'); if (isNaN(birth.getTime())) return ''; const years = (Date.now() - birth.getTime()) / (365.25 * 24 * 3600 * 1000); return (years > 0 && years < 30) ? Math.round(years * 10) / 10 : ''; } // Lebensphase aus Alter (Jahre): Welpen/Junghunde brauchen mehr (Wachstum), // Senioren weniger. `growth` (absoluter RER-Faktor) überschreibt den // Aktivitäts-Faktor; `mult` skaliert den Erwachsenen-Faktor. function _lifeStage(alter) { if (!alter || alter <= 0) return { label: '', mult: 1, growth: null }; if (alter < 0.34) return { label: '🍼 Welpe (< 4 Mon.) — hoher Wachstumsbedarf', mult: 1, growth: 3.0 }; if (alter < 1) return { label: '🐶 Junghund (4–12 Mon.) — erhöhter Bedarf', mult: 1, growth: 2.0 }; if (alter >= 11) return { label: '🐕 Hochbetagt (11+ J.) — reduzierter Bedarf', mult: 0.85, growth: null }; if (alter >= 7) return { label: '🐕 Senior (7+ J.) — leicht reduzierter Bedarf', mult: 0.90, growth: null }; return { label: '', mult: 1, growth: null }; } function _renderRechner(el) { const dog = _appState.activeDog; // Auto-Werte aus Hundeprofil. Feldnamen: gewicht_kg (nicht gewicht); Alter gibt // es nicht als Feld → aus geburtstag berechnen. const gewichtDefault = dog?.gewicht_kg ?? ''; const alterDefault = _alterJahre(dog?.geburtstag); el.innerHTML = `
`; // 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)); // Bereits gespeichertes Futter-Profil beim Öffnen direkt anzeigen — sonst war // es „nicht auffindbar" (Formular lag versteckt hinter der Berechnung). const hasProfil = !!(_profil && (_profil.futter_typ || _profil.marke || _profil.notizen || _profil.kcal_tag)); if (hasProfil) { if (_profil.kcal_tag) { // Gespeicherten Tagesbedarf 1:1 wieder anzeigen (kein Neu-Rechnen → keine // abweichende Zahl, da Aktivität/Kastration nicht persistiert werden). _showResult(el, _profil.kcal_tag); } else { const ps = el.querySelector('#ern-profil-speichern'); if (ps) { ps.style.display = ''; el.querySelector('#ern-prof-save-btn').onclick = () => _speichereProfil(el, null); } } } } function _berechne(el) { const gewicht = parseFloat(el.querySelector('#ern-gewicht').value); const alter = parseFloat(el.querySelector('#ern-alter').value) || 0; 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 }, }; // Lebensphase einrechnen: Welpe/Junghund = Wachstumsfaktor (überschreibt // Aktivität), Senior = reduzierter Faktor. const baseFactor = faktoren[aktivitaet][kastriert ? 'kastriert' : 'intakt']; const stage = _lifeStage(alter); const factor = stage.growth != null ? stage.growth : baseFactor * stage.mult; const kcal = Math.round(rer * factor); _showResult(el, kcal); } // Tagesbedarf-Ergebnis + Profil-Formular rendern (genutzt von Berechnung UND // beim Öffnen mit gespeichertem kcal_tag). function _showResult(el, kcal) { // 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 stageLabel = _lifeStage(parseFloat(el.querySelector('#ern-alter')?.value) || 0).label; const resultEl = el.querySelector('#ern-rechner-result'); resultEl.style.display = ''; resultEl.innerHTML = `
ca. ${kcalFormatted} kcal
pro Tag
${stageLabel ? `
${stageLabel}
` : ''}
🌾 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}
`).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 => ` `).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', `
${UI.escape(frage)}
`); frageEl.value = ''; // KI-Antwort Placeholder const placeholderId = `ern-ki-placeholder-${Date.now()}`; chatEl.insertAdjacentHTML('beforeend', `
Denke nach…
`); 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 = `

${antwortHtml}

`; } chatEl.scrollTop = chatEl.scrollHeight; }); } // ------------------------------------------------------------------ // TAB 5: VERTRÄGLICHKEIT // ------------------------------------------------------------------ async function _renderVertraeglichkeit(el) { const dog = _appState?.activeDog; if (!dog) { el.innerHTML = ''; return; } el.innerHTML = `
Haut- & Fellsymptome zeigen sich erst nach Wochen — trage regelmäßig ein um Muster zu erkennen.
Lade Analyse…
`; 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 = ` `; 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 => `