From cc841ef6d7edfd1a9b9e1b2077d877324c741fc4 Mon Sep 17 00:00:00 2001 From: rene Date: Tue, 19 May 2026 18:17:50 +0200 Subject: [PATCH] =?UTF-8?q?Feature:=20Trainingsprotokoll-Tab=20in=20=C3=9C?= =?UTF-8?q?bungen,=20kein=20Tagebuch-Spam?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Neuer Tab 'Protokoll' in der Übungen-Seite: zeigt alle Trainingseinheiten chronologisch nach Datum gruppiert (Heute/Gestern/Datum-Label) - Jede Einheit: Übungsname, Wdh., Erfolgs-Emoji, Stimmung, Sterne, Notiz, TOP-Badge - 'Weitere laden' Pagination (30 Einheiten pro Seite) - Backend: Training erstellt keine Tagebuch-Einträge mehr (weder bei ist_top noch manuell) - Frontend: 'Als Meilenstein ins Tagebuch' Checkbox komplett entfernt - onDogChange setzt Verlauf-State zurück --- backend/routes/training.py | 40 +----- backend/static/js/pages/uebungen.js | 209 ++++++++++++++++++++++------ 2 files changed, 167 insertions(+), 82 deletions(-) diff --git a/backend/routes/training.py b/backend/routes/training.py index 263d90d..291a81a 100644 --- a/backend/routes/training.py +++ b/backend/routes/training.py @@ -326,7 +326,7 @@ class SessionCreate(BaseModel): hund_stimmung: str = "aufmerksam" zufriedenheit: int = 3 notiz: Optional[str] = None - tagebuch_eintrag: bool = False + tagebuch_eintrag: bool = False # ignoriert — Training hat eigenes Protokoll @router.post("/sessions") @@ -363,42 +363,6 @@ async def log_session(body: SessionCreate, user=Depends(get_current_user)): # Badges prüfen new_badges = _check_badges(conn, uid, dog_name) - # Tagebucheintrag erstellen? - diary_entry_id = None - if body.tagebuch_eintrag or ist_top: - stimmung_label = STIMMUNGS_LABELS.get(body.hund_stimmung, body.hund_stimmung) - if ist_top: - titel = f"\U0001f3af {body.exercise_name} \u2014 Top-Training!" - else: - titel = f"\U0001f3af Training: {body.exercise_name}" - text_parts = [ - f"{body.wiederholungen} Wiederholungen \u00b7 " - f"Erfolgsquote: {body.erfolgsquote}% \u00b7 " - f"Stimmung: {stimmung_label}" - ] - if body.notiz: - text_parts.append(f"\n\n{body.notiz}") - eintrag_text = "".join(text_parts) - - diary_cur = conn.execute( - """ - INSERT INTO diary (dog_id, datum, typ, titel, text) - VALUES (?,?,?,?,?) - """, - (body.dog_id, datum, "training", titel, eintrag_text) - ) - diary_entry_id = diary_cur.lastrowid - - conn.execute( - "INSERT OR IGNORE INTO diary_dogs (diary_id, dog_id) VALUES (?,?)", - (diary_entry_id, body.dog_id) - ) - - conn.execute( - "UPDATE training_sessions SET diary_entry_id=? WHERE id=?", - (diary_entry_id, session_id) - ) - session = { "id": session_id, "user_id": uid, @@ -412,14 +376,12 @@ async def log_session(body: SessionCreate, user=Depends(get_current_user)): "zufriedenheit": body.zufriedenheit, "notiz": body.notiz, "ist_top": bool(ist_top), - "diary_entry_id": diary_entry_id, } return { "session": session, "ist_top": bool(ist_top), "badges": new_badges, - "diary_entry_id": diary_entry_id, } diff --git a/backend/static/js/pages/uebungen.js b/backend/static/js/pages/uebungen.js index c6ba308..c14e9f8 100644 --- a/backend/static/js/pages/uebungen.js +++ b/backend/static/js/pages/uebungen.js @@ -56,6 +56,7 @@ window.Page_uebungen = (() => { { id: 'welpe-basics', label: 'Welpe Basics' }, { id: 'grundlagen', label: 'Trainingsgrundlagen' }, { id: 'ki-trainer', label: 'KI-Trainer' }, + { id: 'verlauf', label: 'Protokoll' }, ]; // ---------------------------------------------------------- @@ -541,11 +542,13 @@ window.Page_uebungen = (() => { _renderContent(); } function onDogChange() { - _statsData = null; - _badgesData = null; - _progressCache = {}; + _statsData = null; + _badgesData = null; + _progressCache = {}; _progressLoaded = false; - _exerciseStats = {}; + _exerciseStats = {}; + _verlaufSessions = []; + _verlaufOffset = 0; _render(); _loadStatsAndBadges(); _loadVirtualTrainer(); @@ -980,6 +983,7 @@ window.Page_uebungen = (() => { const isExerciseTab = ['grundkommandos','tricks','problemverhalten', 'mentale-auslastung','hundesport','koerperpflege','welpe-basics'].includes(_activeTab); + const isVerlauf = _activeTab === 'verlauf'; const showIf = v => v ? '' : 'none'; const quickWrap = _container.querySelector('#ueb-quicksetup-btn')?.parentElement; @@ -990,6 +994,7 @@ window.Page_uebungen = (() => { if (trainerEl) trainerEl.style.display = showIf(isExerciseTab); if (suggestEl) suggestEl.style.display = showIf(isExerciseTab); if (bannerEl) bannerEl.style.display = showIf(isExerciseTab); + if (isVerlauf) _loadVerlauf(); switch (_activeTab) { case 'grundkommandos': @@ -1011,6 +1016,7 @@ window.Page_uebungen = (() => { break; } case 'grundlagen': el.innerHTML = _renderGrundlagen(); break; + case 'verlauf': el.innerHTML = _renderVerlaufShell(); break; case 'ki-trainer': if (!App.hasPro(_appState?.user)) { el.innerHTML = `
@@ -1647,18 +1653,6 @@ window.Page_uebungen = (() => { background:var(--c-surface);color:var(--c-text);line-height:1.5">
- - - @@ -1714,7 +1708,6 @@ window.Page_uebungen = (() => { btn.style.background = 'var(--c-primary-subtle)'; btn.style.borderColor = 'var(--c-primary)'; btn.style.transform = 'scale(1.15)'; - _checkMilestoneVisibility(); }); }); @@ -1738,17 +1731,9 @@ window.Page_uebungen = (() => { overlay.querySelectorAll('.ueb-stern-btn').forEach(b => { b.style.opacity = parseInt(b.dataset.val, 10) <= zufriedenheit ? '1' : '0.35'; }); - _checkMilestoneVisibility(); }); }); - function _checkMilestoneVisibility() { - const wrap = overlay.querySelector('#ueb-log-milestone-wrap'); - if (!wrap) return; - const show = erfolgsquote != null && erfolgsquote >= 75 && zufriedenheit != null && zufriedenheit >= 4; - wrap.hidden = !show; - } - // Save overlay.querySelector('#ueb-log-save').addEventListener('click', async () => { const dogId = _dogId(); @@ -1761,20 +1746,17 @@ window.Page_uebungen = (() => { const exerciseId = `${tab}_${exerciseName.replace(/[\s/]+/g, '_')}`; const today = new Date().toISOString().slice(0, 10); - const tagebuch = !overlay.querySelector('#ueb-log-milestone-wrap').hidden && - overlay.querySelector('#ueb-log-milestone').checked; const body = { - dog_id: dogId, - exercise_id: exerciseId, - exercise_name: exerciseName, - datum: today, - wiederholungen: wiederholungen, - erfolgsquote: erfolgsquote, - hund_stimmung: stimmung || null, - zufriedenheit: zufriedenheit || null, - notiz: overlay.querySelector('#ueb-log-notiz').value.trim() || null, - tagebuch_eintrag: tagebuch, + dog_id: dogId, + exercise_id: exerciseId, + exercise_name: exerciseName, + datum: today, + wiederholungen: wiederholungen, + erfolgsquote: erfolgsquote, + hund_stimmung: stimmung || null, + zufriedenheit: zufriedenheit || null, + notiz: overlay.querySelector('#ueb-log-notiz').value.trim() || null, }; try { @@ -1806,12 +1788,6 @@ window.Page_uebungen = (() => { }); } - if (resp.diary_entry_id) { - setTimeout(() => { - UI.toast.success('📖 Als Meilenstein im Tagebuch gespeichert.'); - }, resp.badges?.length ? (resp.badges.length + 1) * 1000 : 1000); - } - // Stats-Banner + Trainer aktualisieren _statsData = null; _loadStatsAndBadges(); @@ -1995,6 +1971,153 @@ window.Page_uebungen = (() => { }); } + // ---------------------------------------------------------- + // TRAININGSPROTOKOLL (Verlauf-Tab) + // ---------------------------------------------------------- + let _verlaufSessions = []; + let _verlaufOffset = 0; + let _verlaufHasMore = false; + const _VERLAUF_LIMIT = 30; + + const _ERFOLG_EMOJI = { 0: '😓', 25: '😐', 50: '🙂', 75: '😊', 100: '🎉' }; + const _STIMMUNG_EMOJI = { aufmerksam: '🎯', müde: '😴', abgelenkt: '🌪️', super: '⚡' }; + + function _renderVerlaufShell() { + const dogId = _dogId(); + if (!dogId) { + return `
+

Wähle einen Hund aus um das Protokoll zu sehen.

+
`; + } + return `
+
+
+ +
+
+
`; + } + + async function _loadVerlauf(append = false) { + const dogId = _dogId(); + if (!dogId) return; + const el = _container.querySelector('#verlauf-list'); + if (!el) return; + + if (!append) { + _verlaufSessions = []; + _verlaufOffset = 0; + } + + const data = await _apiGet( + `/api/training/sessions?dog_id=${dogId}&limit=${_VERLAUF_LIMIT + 1}&offset=${_verlaufOffset}` + ).catch(() => null); + + if (!data) { + if (!append) el.innerHTML = `
Fehler beim Laden.
`; + return; + } + + _verlaufHasMore = data.length > _VERLAUF_LIMIT; + const rows = data.slice(0, _VERLAUF_LIMIT); + _verlaufSessions = append ? [..._verlaufSessions, ...rows] : rows; + _verlaufOffset += rows.length; + + _renderVerlaufList(el); + } + + function _renderVerlaufList(el) { + if (!_verlaufSessions.length) { + el.innerHTML = ` +
+ +

Noch keine Trainingseinheiten geloggt.

+

+ Tippe in einer Übung auf "+ Einheit" um zu starten. +

+
`; + return; + } + + // Nach Datum gruppieren + const today = new Date().toISOString().slice(0, 10); + const yesterday = new Date(Date.now() - 86400000).toISOString().slice(0, 10); + const groups = {}; + _verlaufSessions.forEach(s => { + groups[s.datum] = groups[s.datum] || []; + groups[s.datum].push(s); + }); + + const html = Object.entries(groups).map(([datum, sessions]) => { + let label; + if (datum === today) label = 'Heute'; + else if (datum === yesterday) label = 'Gestern'; + else { + const d = new Date(datum + 'T00:00:00'); + label = d.toLocaleDateString('de-DE', { weekday: 'short', day: 'numeric', month: 'short' }); + } + + const rows = sessions.map(s => { + const erfolg = _ERFOLG_EMOJI[s.erfolgsquote] || '🙂'; + const stimmung = s.hund_stimmung ? (_STIMMUNG_EMOJI[s.hund_stimmung] || '') : ''; + const topBadge = s.ist_top + ? `TOP` + : ''; + const noteHtml = s.notiz + ? `
${_esc(s.notiz)}
` + : ''; + return ` +
+ ${erfolg} +
+
+ ${_esc(s.exercise_name)} + ${topBadge} +
+
+ ${s.wiederholungen}× Wdh. ${stimmung ? '· ' + stimmung : ''} + ${s.zufriedenheit ? '· ' + '⭐'.repeat(s.zufriedenheit) : ''} +
+ ${noteHtml} +
+
`; + }).join(''); + + return ` +
+
+ ${_esc(label)} +
+
${rows}
+
`; + }).join(''); + + const moreBtn = _verlaufHasMore + ? `` + : ''; + + el.innerHTML = html + moreBtn; + + el.querySelector('#verlauf-more')?.addEventListener('click', () => _loadVerlauf(true)); + } + // ---------------------------------------------------------- // TRAININGSGRUNDLAGEN // ----------------------------------------------------------