/* ============================================================ BAN YARO — Trainingspläne Seiten-Modul: Welpe, Junior, Erwachsener Hund Alle Inhalte hardcoded, Checkboxen via localStorage ============================================================ */ window.Page_trainingsplaene = (() => { let _container = null; let _appState = null; let _activePlan = 'welpe'; // welpe | junior | erwachsen let _activeAdultTab = 'grundkurs'; // grundkurs | aufbaukurs // ---------------------------------------------------------- // API HELPERS // ---------------------------------------------------------- function _dogId() { return _appState?.activeDog?.id || null; } async function _apiGet(url) { const r = await fetch(url, {credentials: 'include'}); return r.ok ? r.json() : null; } // ---------------------------------------------------------- // HELPER // ---------------------------------------------------------- function _esc(str) { if (!str) return ''; return String(str) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } function _icon(name) { return ``; } function _lsKey(planId, goalIdx) { return `tp_${planId}_${goalIdx}`; } function _saveGoal(key, checked) { localStorage.setItem(key, checked ? 'true' : 'false'); } function _loadGoal(key) { return localStorage.getItem(key) === 'true'; } // ---------------------------------------------------------- // RENDER HELPERS // ---------------------------------------------------------- function _renderTable(headers, rows) { const ths = headers.map(h => `${_esc(h)}`).join(''); const trs = rows.map(row => { const tds = row.map((cell, i) => `${_esc(cell)}`).join(''); return `${tds}`; }).join(''); return `
${ths}${trs}
`; } function _renderGoals(planId, goals) { const total = goals.length; let doneCount = 0; const items = goals.map((goal, idx) => { const key = _lsKey(planId, idx); const checked = _loadGoal(key); if (checked) doneCount++; return ` `; }).join(''); const progress = total > 0 ? Math.round((doneCount / total) * 100) : 0; return `
${_icon('check-circle')} Lernziele ${doneCount} von ${total} erreicht
${items}
`; } function _renderAccordionPhase(id, title, content) { return `
`; } function _renderHintBox(text) { return `
${_icon('info')} ${_esc(text)}
`; } // ---------------------------------------------------------- // PLAN SELECTOR // ---------------------------------------------------------- function _renderPlanSelector() { const plans = [ { id: 'welpe', label: 'Welpe', icon: 'dog', sub: '0–6 Monate' }, { id: 'junior', label: 'Junior', icon: 'dog', sub: '6–18 Monate' }, { id: 'erwachsen',label: 'Erwachsener Hund', icon: 'dog', sub: 'Grund- & Aufbaukurs' }, ]; const btns = plans.map(p => ` `).join(''); return `
${btns}
`; } // ---------------------------------------------------------- // WELPENPLAN // ---------------------------------------------------------- function _renderWelpe() { const intro = `

Voraussetzungen: Welpe ist eingezogen, Grundvertrauen wird aufgebaut.
Ziel am Ende: Sitz, Platz, Hier, Warte, Leine ohne Ziehen, Alleine bleiben bis 1 Stunde, keine Begrüßungssprünge.

${_renderHintBox('Welpen sind schnell überfordert. Lieber 3×5 Minuten täglich als einmal 20 Minuten. Sozialisation (Menschen, Geräusche, Orte) hat in dieser Phase genauso Priorität wie Training.')}`; const w12 = `

Fokus: Name, erste Bindung, Stubenreinheit, keine Übungen erzwingen.

${_renderTable( ['Tag', 'Einheit 1', 'Einheit 2', 'Einheit 3'], [['Mo–So', 'Name lernen', 'Markerwort einführen', 'Freies Erkunden + Beobachten']] )} `; const w34 = `

Fokus: Sitz, Warte (Futter), Nicht springen.

${_renderTable( ['Tag', 'Einheit 1', 'Einheit 2', 'Einheit 3'], [ ['Mo', 'Sitz einführen', 'Sitz wiederholen', 'Warte vor Futter'], ['Di', 'Sitz mit Handsignal', 'Nicht springen üben', 'Sitz aus verschiedenen Positionen'], ['Mi', 'Warte vor Futter', 'Sitz 5×', 'Freispiel + Name'], ['Do', 'Sitz festigen', 'Warte 5×', 'Nicht springen'], ['Fr', 'Sitz + Warte kombiniert', 'Freispiel', 'Leckerli-Suche im Gras'], ['Sa', 'Wiederholung Woche', 'Spaziergang mit Leine (ohne Ziel)', 'Entspannen auf Decke'], ['So', 'Ruhiger Tag', 'Kurze Sitz-Einheit', 'Sozialisation (Geräusche, Menschen)'], ] )} ${_renderGoals('welpe_w34', [ 'Sitz auf Handsignal (ohne Wort)', 'Sitz auf Wort "Sitz"', 'Wartet vor der Futterschüssel bis "Okay"', 'Springt nicht mehr bei Begrüßung (in Übung)', ])}`; const w56 = `

Fokus: Platz, Leinengewöhnung, Hier auf kurze Distanz.

${_renderTable( ['Tag', 'Einheit 1', 'Einheit 2', 'Einheit 3'], [ ['Mo', 'Platz einführen', 'Sitz wiederholen', 'Leine anlegen + Leckerli'], ['Di', 'Platz üben', 'Hier (2 Meter, innen)', 'Leine: erste Schritte'], ['Mi', 'Platz festigen', 'Sitz + Platz abwechselnd', 'Hier aus anderem Zimmer'], ['Do', 'Leine im Garten', 'Hier mit Freude', 'Platz 5×'], ['Fr', 'Sitz + Platz + Hier kombiniert', 'Spaziergang kurz', 'Nasenarbeit (Leckerli unter Becher)'], ['Sa', 'Wiederholungstag', 'Sozialisation (Markt, Park)', 'Freispiel'], ['So', 'Ruhiger Tag', 'Kurze Einheit nach Wahl', 'Entspannen'], ] )} ${_renderGoals('welpe_w56', [ 'Platz auf Handsignal', 'Platz auf Wort "Platz"', 'Kommt auf "Hier" aus 3 Metern', 'Läuft 5 Schritte an lockerer Leine', ])}`; const w78 = `

Fokus: Bleib (Dauer), Alleine bleiben aufbauen, Fuß vorbereiten.

${_renderTable( ['Tag', 'Einheit 1', 'Einheit 2', 'Einheit 3'], [ ['Mo', 'Bleib einführen (5 Sek)', 'Alleine bleiben (30 Sek)', 'Sitz + Platz Wiederholung'], ['Di', 'Bleib (10 Sek)', 'Alleine bleiben (1 Min)', 'Fuß: erste Schritte'], ['Mi', 'Bleib mit 1 Schritt Distanz', 'Hier aus Garten', 'Nasenarbeit'], ['Do', 'Bleib (20 Sek)', 'Alleine bleiben (2 Min)', 'Leine üben'], ['Fr', 'Sitz + Bleib + Hier kombiniert', 'Fuß 10 Schritte', 'Freispiel'], ['Sa', 'Ausflug (neue Umgebung)', 'Kurze Übungen unterwegs', 'Sozialisation'], ['So', 'Ruhiger Tag', 'Wiederholung nach Wahl', 'Entspannen'], ] )} ${_renderGoals('welpe_w78', [ 'Bleibt 30 Sekunden im Sitz', 'Bleibt alleine bis 5 Minuten ohne Stress', 'Kommt zuverlässig auf "Hier" (innen + Garten)', 'Fuß: 10 Schritte an lockerer Leine', ])}`; const w912 = `

Fokus: Alle Kommandos in Alltagssituationen, erste leichte Ablenkungen.

${_renderTable( ['Woche', 'Schwerpunkt'], [ ['9', 'Alle Kommandos im Garten mit leichter Ablenkung'], ['10', 'Sitz + Bleib auf der Straße, Fuß auf kurzen Spaziergängen'], ['11', 'Hier mit Schleppleine im Park, Alleine bleiben bis 30 Min'], ['12', 'Wiederholung + erste Tricks (Pfote, Dreh)'], ] )} ${_renderGoals('welpe_w912', [ 'Alle Grundkommandos auf Wort und Handsignal', 'Bleibt alleine bis 1 Stunde ohne Stress', 'Kommt zuverlässig auf "Hier" im Garten', 'Läuft entspannt an der Leine in ruhiger Umgebung', 'Erster Trick gelernt (Pfote oder Dreh)', ])}`; return ` ${intro}
${_renderAccordionPhase('welpe-w12', 'Woche 1–2: Ankommen & Vertrauen', w12)} ${_renderAccordionPhase('welpe-w34', 'Woche 3–4: Erste Kommandos', w34)} ${_renderAccordionPhase('welpe-w56', 'Woche 5–6: Platz & erste Leine', w56)} ${_renderAccordionPhase('welpe-w78', 'Woche 7–8: Bleib & Alleine bleiben', w78)} ${_renderAccordionPhase('welpe-w912', 'Woche 9–12: Festigung & Alltagsintegration', w912)}
`; } // ---------------------------------------------------------- // JUNIORPLAN // ---------------------------------------------------------- function _renderJunior() { const intro = `

Voraussetzungen: Grundkommandos bekannt, aber pubertätsbedingt unzuverlässig.
Ziel am Ende: Alle Grundkommandos auch bei Ablenkung, zuverlässiger Rückruf, Leinenführigkeit in der Stadt.

${_renderHintBox('Die Pubertät (ca. 6–12 Monate) ist normal. Der Hund "vergisst" scheinbar alles — er testet Grenzen und Reize sind überwältigend. Konsequenz und Geduld, kein Rückschritt im Denken.')}`; const m1 = `

Fokus: Alle bekannten Kommandos mit höherer Ablenkung neu festigen.

${_renderTable( ['Tag', 'Einheit 1 (5 Min)', 'Einheit 2 (5–10 Min)'], [ ['Mo', 'Sitz + Platz mit Ablenkung (Ball auf dem Boden)', 'Leine üben in neuer Umgebung'], ['Di', 'Hier mit Schleppleine im Park', 'Bleib mit Distanz (3–5 Meter)'], ['Mi', 'Fuß auf belebtem Gehweg', 'Nasenarbeit / Trick'], ['Do', 'Aus + Warte kombiniert', 'Alleine bleiben (bis 2 Stunden)'], ['Fr', 'Alle Kommandos — kurze Runde', 'Freispiel'], ['Sa', 'Ausflug + Training in neuer Umgebung', 'Sozialisation'], ['So', 'Ruhiger Tag — leichte Einheit', 'Entspannen'], ] )} ${_renderGoals('junior_m1', [ 'Sitz + Platz trotz Ball/anderen Hunden in der Nähe', 'Bleibt 1 Minute mit 5 Metern Distanz', 'Kommt auf "Hier" im Park (mit Schleppleine)', 'Fuß auf 50 Meter in ruhiger Straße', ])}`; const m2 = `

Fokus: Zuverlässiger Rückruf — das wichtigste Kommando in dieser Phase.

  1. Rückruf mit Schleppleine (10 Meter): Hund beschäftigt sich → "Hier!" → leichtes Anziehen wenn nötig → große Belohnung beim Ankommen
  2. Versteckspiel: Im Wald/Park verstecken → "Hier!" rufen → Hund sucht und findet → größte Belohnung
  3. Rückruf aus der Gruppe: Hund spielt mit anderen Hunden → "Hier!" → kommt er: Jackpot (5 Leckerlis + Streicheln)
  4. Notfallrückruf einführen: Anderes Wort (z.B. "Rapid!") nur für echte Notfälle — immer mit höchster Belohnung
${_renderGoals('junior_m2', [ 'Kommt auf "Hier" aus 20 Metern mit Schleppleine', 'Kommt aus Spielsituation mit anderen Hunden zurück', 'Notfallrückruf eingeführt', ])}`; const m3 = `

Fokus: Ruhiges Gehen auch bei Fahrrädern, anderen Hunden, Kinderwagen.

  1. Ruhige Straße → belohnen alle 10 Schritte bei locker hängender Leine
  2. Fahrrad kommt entgegen → Sitz → warten → Fahrrad vorbei → weitergehen + Leckerli
  3. Anderer Hund in Sichtweite → Fokus auf dich (Augenkontakt trainieren) → Leckerli
  4. Belebte Fußgängerzone: erst beobachten lassen, dann durch

Augenkontakt: "Schau" sagen → Hund schaut in deine Augen → sofort Markerwort + Leckerli. In Ablenkungssituationen: "Schau" → fokussiert → dann erst weitergehen.

${_renderGoals('junior_m3', [ 'Läuft 500 Meter an lockerer Leine in der Stadt', 'Bleibt bei Ablenkung (Fahrrad, Jogger) fokussiert', 'Augenkontakt auf Kommando "Schau"', ])}`; const m46 = `

Fokus: Festigung aller Kommandos, erste Aufbauübungen, Tricks für mentale Auslastung.

${_renderTable( ['Monat', 'Schwerpunkt'], [ ['4', 'Bleib auf Distanz (10 Meter), Freifolge (ohne Leine in sicherer Umgebung)'], ['5', 'Platz auf Distanz, Hier vom Freilauf, Trick-Erweiterung (Decke, Suchspiel)'], ['6', 'Alle Kommandos zuverlässig, erstes Hundesport-Schnuppern optional'], ] )} ${_renderGoals('junior_m46', [ 'Alle Grundkommandos bei mittlerer Ablenkung', 'Freifolge auf kurze Distanz (eingezäuntes Gelände)', '2–3 Tricks beherrscht', 'Rückruf vom Freilauf (Schleppleine)', ])}`; return ` ${intro}
${_renderAccordionPhase('junior-m1', 'Monat 1: Grundkommandos neu aufbauen', m1)} ${_renderAccordionPhase('junior-m2', 'Monat 2: Rückruf vertiefen', m2)} ${_renderAccordionPhase('junior-m3', 'Monat 3: Leinenführigkeit in der Stadt', m3)} ${_renderAccordionPhase('junior-m46', 'Monat 4–6: Aufbau & Tricks', m46)}
`; } // ---------------------------------------------------------- // ERWACHSENER HUND // ---------------------------------------------------------- function _renderErwachsenTabs() { return `
`; } function _renderGrundkurs() { const intro = `

Voraussetzungen: Keine oder wenige Vorkenntnisse.
Ziel: Alle Grundkommandos in 8 Wochen.

${_renderHintBox('Erwachsene Hunde lernen genauso gut wie Welpen — oft sogar konzentrierter. Alte Gewohnheiten brauchen länger zum Überschreiben, aber mit konsequentem Training klappt es.')}`; const gw12 = ` ${_renderTable( ['Tag', 'Einheit 1', 'Einheit 2'], [ ['Mo', 'Markerwort einführen', 'Sitz einführen'], ['Di', 'Sitz festigen', 'Warte vor Futter'], ['Mi', 'Sitz aus Bewegung', 'Markerwort in Alltagssituationen'], ['Do', 'Sitz + Warte kombiniert', 'Leine: erste Einschätzung'], ['Fr', 'Wiederholung', 'Freispiel'], ['Sa/So', 'Alltagsintegration', 'Kurze Einheit'], ] )}`; const gw34 = ` ${_renderTable( ['Tag', 'Einheit 1', 'Einheit 2'], [ ['Mo', 'Platz einführen', 'Hier (Wohnung, 3 Meter)'], ['Di', 'Platz festigen', 'Leinenführigkeit einschätzen + üben'], ['Mi', 'Sitz + Platz abwechselnd', 'Hier aus anderem Zimmer'], ['Do', 'Hier im Garten', 'Fuß vorbereiten'], ['Fr', 'Leine 10 Minuten üben', 'Platz + Hier kombiniert'], ['Sa/So', 'Spaziergang mit Training', 'Nasenarbeit'], ] )}`; const gw56 = ` ${_renderTable( ['Tag', 'Einheit 1', 'Einheit 2'], [ ['Mo', 'Bleib einführen (10 Sek)', 'Fuß einführen'], ['Di', 'Bleib (30 Sek, 1 Schritt)', 'Aus einführen'], ['Mi', 'Fuß 20 Schritte', 'Bleib mit Distanz'], ['Do', 'Aus mit Spielzeug', 'Fuß in der Straße'], ['Fr', 'Alle Kommandos kombiniert', 'Trick nach Wahl'], ['Sa/So', 'Ausflug mit Training', 'Sozialisation'], ] )}`; const gw78 = `

Alle Kommandos in Alltagssituationen:

${_renderGoals('erwachsen_gk', [ 'Sitz, Platz, Bleib (30 Sek), Hier, Fuß, Aus, Warte', 'Alle Kommandos im Alltag einsetzbar', 'Leine ohne starkes Ziehen', ])}`; return ` ${intro}
${_renderAccordionPhase('gk-w12', 'Woche 1–2: Markerwort + Sitz + Warte', gw12)} ${_renderAccordionPhase('gk-w34', 'Woche 3–4: Platz + Hier + Leine', gw34)} ${_renderAccordionPhase('gk-w56', 'Woche 5–6: Bleib + Fuß + Aus', gw56)} ${_renderAccordionPhase('gk-w78', 'Woche 7–8: Festigung + Alltagsintegration', gw78)}
`; } function _renderAufbaukurs() { const intro = `

Voraussetzungen: Grundkurs abgeschlossen oder Kommandos bekannt.
Dauer: 8 Wochen  |  Ziel: Kommandos bei Ablenkung, Distanzkommandos, Tricks, mentale Auslastung.

`; const aw12 = `

Alle bekannten Kommandos werden mit steigender Ablenkung trainiert. Immer bei der niedrigsten Stufe anfangen die noch klappt.

${_renderTable( ['Ablenkungsstufe', 'Beispiele'], [ ['Stufe 1', 'Leckerli auf dem Boden, Ball in Sichtweite'], ['Stufe 2', 'Anderer Mensch im Raum, Geräusche'], ['Stufe 3', 'Anderer Hund in Sichtweite'], ['Stufe 4', 'Belebter Park, Straße'], ['Stufe 5', 'Freilauf, Spielsituation unterbrechen'], ] )}`; const aw34 = `

Übung "Fernbedienung": Hund steht 5 Meter entfernt → "Sitz" → kurz warten → "Platz" → kurz warten → "Sitz" → "Hier". Wirkt beeindruckend, festigt Kommandos enorm.

`; const aw56 = ` ${_renderTable( ['Woche', 'Trick', 'Nasenarbeit'], [ ['5', 'Pfote + Dreh', 'Leckerli unter 3 Bechern suchen'], ['6', 'Decke/Matte', 'Leckerli im Gras suchen, Gegenstand apportieren'], ] )}`; const aw78 = ` ${_renderGoals('erwachsen_ak', [ 'Alle Kommandos bei Stufe 3–4 Ablenkung', 'Sitz + Platz auf 10 Meter Distanz', 'Hier vom Freilauf (Schleppleine)', '3–4 Tricks', 'Nasenarbeit als regelmäßige Beschäftigung', ])}`; return ` ${intro}
${_renderAccordionPhase('ak-w12', 'Woche 1–2: Ablenkungstraining', aw12)} ${_renderAccordionPhase('ak-w34', 'Woche 3–4: Distanzkommandos', aw34)} ${_renderAccordionPhase('ak-w56', 'Woche 5–6: Tricks & Nasenarbeit', aw56)} ${_renderAccordionPhase('ak-w78', 'Woche 7–8: Freie Gestaltung', aw78)}
`; } function _renderUebersicht() { return `

${_icon('clipboard-text')} Welcher Plan für welchen Hund?

${_renderTable( ['Situation', 'Empfohlener Plan'], [ ['Neuer Welpe (8–16 Wochen)', 'Welpenplan Woche 1–4'], ['Welpe 4–6 Monate', 'Welpenplan Woche 5–12'], ['Junghund 6–12 Monate (Pubertät)', 'Juniorplan Monat 1–3'], ['Junghund 12–18 Monate', 'Juniorplan Monat 4–6'], ['Erwachsener Hund ohne Training', 'Grundkurs Erwachsener'], ['Erwachsener Hund mit Grundwissen', 'Aufbaukurs Erwachsener'], ['Neu eingezogener Hund (unbekannte Vorgeschichte)', 'Grundkurs Erwachsener, Tempo anpassen'], ] )}`; } function _renderErwachsen() { const intro = `

Wähle deinen Kurs oder sieh dir die Schnellübersicht an.

`; let content = ''; if (_activeAdultTab === 'grundkurs') { content = _renderGrundkurs(); } else if (_activeAdultTab === 'aufbaukurs') { content = _renderAufbaukurs(); } else { content = `
${_renderUebersicht()}
`; } return `${intro}${_renderErwachsenTabs()}${content}`; } // ---------------------------------------------------------- // BIND EVENTS // ---------------------------------------------------------- function _bindEvents() { // Notiz-Button const dogId = _dogId(); _container.querySelector('#tp-note-btn')?.addEventListener('click', e => { e.stopPropagation(); const planLabel = _activePlan === 'welpe' ? 'Welpe 0–6 Monate' : _activePlan === 'junior' ? 'Junior 6–18 Monate' : `Erwachsener Hund – ${_activeAdultTab}`; _openNoteModal('trainingsplan', dogId, planLabel, null); }); // Plan selector _container.querySelectorAll('[data-plan]').forEach(btn => { btn.addEventListener('click', () => { _activePlan = btn.dataset.plan; _render(); }); }); // Adult sub-tabs _container.querySelectorAll('[data-tab]').forEach(btn => { btn.addEventListener('click', () => { _activeAdultTab = btn.dataset.tab; _render(); }); }); // Accordion _container.querySelectorAll('.tp-acc-head').forEach(btn => { btn.addEventListener('click', () => { const id = btn.dataset.acc; const body = document.getElementById(`tp-acc-body-${id}`); const arrow = btn.querySelector('.tp-acc-arrow'); if (!body) return; const isOpen = !body.hidden; body.hidden = isOpen; arrow.innerHTML = isOpen ? _icon('caret-down') : _icon('caret-up'); }); }); // Checkboxes _container.querySelectorAll('input[data-lskey]').forEach(cb => { cb.addEventListener('change', () => { const key = cb.dataset.lskey; _saveGoal(key, cb.checked); _updateProgress(cb); }); }); } function _updateProgress(cb) { const goalsEl = cb.closest('.tp-goals'); if (!goalsEl) return; const total = parseInt(goalsEl.dataset.total, 10) || 0; const done = goalsEl.querySelectorAll('input[type=checkbox]:checked').length; const label = goalsEl.querySelector('.tp-progress-label'); const bar = goalsEl.querySelector('.tp-progress-bar'); if (label) label.textContent = `${done} von ${total} erreicht`; if (bar) bar.style.width = total > 0 ? `${Math.round((done / total) * 100)}%` : '0%'; } // ---------------------------------------------------------- // MAIN RENDER // ---------------------------------------------------------- function _render() { let planContent = ''; if (_activePlan === 'welpe') planContent = _renderWelpe(); else if (_activePlan === 'junior') planContent = _renderJunior(); else planContent = _renderErwachsen(); const dogId = _dogId(); const planLabel = _activePlan === 'welpe' ? 'Welpe 0–6 Monate' : _activePlan === 'junior' ? 'Junior 6–18 Monate' : `Erwachsener Hund – ${_activeAdultTab}`; _container.innerHTML = `

${_icon('clipboard-text')} Trainingspläne

${dogId ? `` : ''}
${_renderPlanSelector()} ${planContent}

${_icon('calendar')} Trainingskalender

Lade Kalender…
`; _bindEvents(); _loadCalendar(); } // ---------------------------------------------------------- // TRAININGSKALENDER // ---------------------------------------------------------- async function _loadCalendar() { const dogId = _dogId(); const wrap = _container.querySelector('#tp-calendar-wrap'); if (!wrap) return; if (!dogId) { wrap.innerHTML = `

Kein Hund ausgewählt.

`; return; } const now = new Date(); const thisYear = now.getFullYear(); const thisMon = now.getMonth() + 1; // 1-based // Letzter Monat const prevDate = new Date(now.getFullYear(), now.getMonth() - 1, 1); const prevYear = prevDate.getFullYear(); const prevMon = prevDate.getMonth() + 1; const [dataCurr, dataPrev] = await Promise.all([ _apiGet(`/api/training/calendar?dog_id=${dogId}&year=${thisYear}&month=${thisMon}`), _apiGet(`/api/training/calendar?dog_id=${dogId}&year=${prevYear}&month=${prevMon}`), ]); wrap.innerHTML = `
${_renderCalendarMonth(prevYear, prevMon, dataPrev?.days || {})} ${_renderCalendarMonth(thisYear, thisMon, dataCurr?.days || {})}
kein Training Training Top-Training
`; } function _renderCalendarMonth(year, month, days) { const MONTHS = ['Januar','Februar','März','April','Mai','Juni', 'Juli','August','September','Oktober','November','Dezember']; const WDAYS = ['Mo','Di','Mi','Do','Fr','Sa','So']; const firstDay = new Date(year, month - 1, 1); // Monday = 0 offset: getDay() returns 0=Sun, so we shift let startOffset = firstDay.getDay(); // 0=Sun startOffset = startOffset === 0 ? 6 : startOffset - 1; // Mon=0 … Sun=6 const daysInMonth = new Date(year, month, 0).getDate(); // Header Wochentage const wdayHeader = WDAYS.map(d => `
${d}
` ).join(''); // Leere Slots am Anfang let cells = ''; for (let i = 0; i < startOffset; i++) { cells += `
`; } const today = new Date(); const todayStr = `${today.getFullYear()}-${String(today.getMonth()+1).padStart(2,'0')}-${String(today.getDate()).padStart(2,'0')}`; for (let d = 1; d <= daysInMonth; d++) { const dateStr = `${year}-${String(month).padStart(2,'0')}-${String(d).padStart(2,'0')}`; const dayData = days[dateStr]; const isToday = dateStr === todayStr; let bg = 'transparent'; let border = '1.5px solid var(--c-border)'; let color = 'var(--c-text-secondary)'; let title = ''; if (dayData) { if (dayData.top) { bg = 'var(--c-primary)'; border = '1.5px solid var(--c-primary)'; color = '#fff'; title = `Top-Training! ${dayData.count} Einheit${dayData.count !== 1 ? 'en' : ''}`; } else { bg = 'var(--c-primary-subtle)'; border = '1.5px solid var(--c-primary)'; color = 'var(--c-primary)'; title = `${dayData.count} Einheit${dayData.count !== 1 ? 'en' : ''}`; } } const todayRing = isToday ? 'box-shadow:0 0 0 2px var(--c-primary);' : ''; cells += `
${d}
`; } return `
${_esc(MONTHS[month - 1])} ${year}
${wdayHeader} ${cells}
`; } // ---------------------------------------------------------- // NOTIZ-MODAL // ---------------------------------------------------------- async function _openNoteModal(parentType, parentId, parentLabel, locationName) { // Vorhandenes Modal entfernen falls noch offen document.getElementById('by-note-modal')?.remove(); const overlay = document.createElement('div'); overlay.id = 'by-note-modal'; overlay.style.cssText = 'position:fixed;inset:0;z-index:2000;background:rgba(0,0,0,.55);display:flex;align-items:flex-end;justify-content:center'; overlay.innerHTML = `
Notiz
${_esc(parentLabel)}
`; document.body.appendChild(overlay); const textarea = document.getElementById('by-note-text'); const saveBtn = document.getElementById('by-note-save'); const cancelBtn = document.getElementById('by-note-cancel'); const closeBtn = document.getElementById('by-note-close'); let existingNoteId = null; try { const existing = await API.notes.get(parentType, String(parentId)); if (existing?.id) { existingNoteId = existing.id; textarea.value = existing.text || ''; } } catch (_) { /* keine Notiz vorhanden — ok */ } setTimeout(() => textarea.focus(), 100); const _close = () => overlay.remove(); closeBtn.addEventListener('click', _close); cancelBtn.addEventListener('click', _close); overlay.addEventListener('click', e => { if (e.target === overlay) _close(); }); document.getElementById('by-note-form').addEventListener('submit', async e => { e.preventDefault(); const text = textarea.value.trim(); UI.setLoading(saveBtn, true); try { const payload = { text, parent_label: parentLabel, location_name: locationName }; if (existingNoteId) { await API.notes.update(existingNoteId, payload); } else { await API.notes.create(parentType, String(parentId), payload); } UI.toast.success('Notiz gespeichert.'); _close(); } catch (err) { UI.toast.error(err.message || 'Fehler beim Speichern.'); UI.setLoading(saveBtn, false); } }); } // ---------------------------------------------------------- // PUBLIC API // ---------------------------------------------------------- async function init(container, appState) { _container = container; _appState = appState; _render(); } function refresh() {} function onDogChange() { _render(); } return { init, refresh, onDogChange }; })();