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
// ----------------------------------------------------------