diff --git a/backend/routes/dogs.py b/backend/routes/dogs.py index 4bb9b97..069b09a 100644 --- a/backend/routes/dogs.py +++ b/backend/routes/dogs.py @@ -164,7 +164,11 @@ async def get_welcome_dashboard(dog_id: int, user=Depends(get_current_user)): day_num = (_dt.date.today() - _dt.date(2024, 1, 1)).days # Nur exercise_ids im JS-Format (starten mit bekanntem Tab-Namen) - _KNOWN_PREFIXES = ('grundkommandos_', 'tricks_', 'problemverhalten_', 'grundlagen_') + _KNOWN_PREFIXES = ( + 'grundkommandos_', 'tricks_', 'problemverhalten_', + 'mentale-auslastung_', 'hundesport_', 'koerperpflege_', 'welpe-basics_', + 'grundlagen_', + ) raw_progress = conn.execute( """SELECT exercise_id FROM exercise_progress WHERE user_id = ? AND status IN ('noch-nicht', 'manchmal', 'meistens') diff --git a/backend/routes/training.py b/backend/routes/training.py index b802a1b..a7c0951 100644 --- a/backend/routes/training.py +++ b/backend/routes/training.py @@ -10,6 +10,45 @@ from auth import get_current_user router = APIRouter() +# ------------------------------------------------------------------ +# Alle Übungen aus DB (öffentlich, kein Auth) +# ------------------------------------------------------------------ +@router.get("/exercises") +async def get_exercises(): + """Alle Übungen aus der DB, gruppiert nach Tab-ID.""" + import json as _json + CAT_TO_TAB = { + 'Grundkommando': 'grundkommandos', + 'Trick': 'tricks', + 'Problemverhalten': 'problemverhalten', + 'Mentale Auslastung': 'mentale-auslastung', + 'Hundesport': 'hundesport', + 'Koerperpflege': 'koerperpflege', + 'Körperpflege': 'koerperpflege', + 'Welpe Basics': 'welpe-basics', + } + with db() as conn: + rows = conn.execute(""" + SELECT exercise_id, name, kategorie, schwierigkeit, alter_ab, + dauer, beschreibung, schritte, tipp + FROM training_exercises ORDER BY kategorie, name + """).fetchall() + by_tab = {} + for r in rows: + tab = CAT_TO_TAB.get(r['kategorie'], r['kategorie'].lower().replace(' ', '-')) + by_tab.setdefault(tab, []).append({ + 'exercise_id': r['exercise_id'], + 'name': r['name'], + 'kategorie': tab, + 'schwierigkeit': r['schwierigkeit'] or 'mittel', + 'alter': r['alter_ab'], + 'dauer': r['dauer'], + 'beschreibung': r['beschreibung'], + 'schritte': _json.loads(r['schritte'] or '[]'), + 'tipp': r['tipp'], + }) + return by_tab + # ------------------------------------------------------------------ # Übungs-Status # ------------------------------------------------------------------ diff --git a/backend/static/js/app.js b/backend/static/js/app.js index 1f19eed..5924331 100644 --- a/backend/static/js/app.js +++ b/backend/static/js/app.js @@ -3,7 +3,7 @@ Router, State-Management, Navigation, Initialisierung. ============================================================ */ -const APP_VER = '469'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen +const APP_VER = '470'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen const App = (() => { diff --git a/backend/static/js/pages/uebungen.js b/backend/static/js/pages/uebungen.js index 4ad56fc..fa7ff50 100644 --- a/backend/static/js/pages/uebungen.js +++ b/backend/static/js/pages/uebungen.js @@ -36,17 +36,23 @@ window.Page_uebungen = (() => { // ---------------------------------------------------------- let _statsData = null; // cached stats from /api/training/stats let _badgesData = null; // cached badges from /api/achievements + let _exercisesByTab = {}; // aus API geladen + let _exercisesLoaded = false; // ---------------------------------------------------------- // DATEN // ---------------------------------------------------------- const TABS = [ - { id: 'grundkommandos', label: 'Grundkommandos' }, - { id: 'tricks', label: 'Tricks & Beschäftigung' }, - { id: 'problemverhalten', label: 'Problemverhalten' }, - { id: 'grundlagen', label: 'Trainingsgrundlagen' }, - { id: 'ki-trainer', label: 'KI-Trainer' }, + { id: 'grundkommandos', label: 'Grundkommandos' }, + { id: 'tricks', label: 'Tricks' }, + { id: 'problemverhalten', label: 'Problemverhalten' }, + { id: 'mentale-auslastung', label: 'Mentale Auslastung' }, + { id: 'hundesport', label: 'Hundesport' }, + { id: 'koerperpflege', label: 'Körperpflege' }, + { id: 'welpe-basics', label: 'Welpe Basics' }, + { id: 'grundlagen', label: 'Trainingsgrundlagen' }, + { id: 'ki-trainer', label: 'KI-Trainer' }, ]; // ---------------------------------------------------------- @@ -435,13 +441,20 @@ window.Page_uebungen = (() => { // ---------------------------------------------------------- // DB-Kategorien → Tab-IDs const _KAT_TO_TAB = { - 'grundkommando': 'grundkommandos', - 'grundkommandos': 'grundkommandos', - 'trick': 'tricks', - 'tricks': 'tricks', - 'problemverhalten':'problemverhalten', - 'grundlagen': 'grundlagen', - 'ki-trainer': 'ki-trainer', + 'grundkommando': 'grundkommandos', + 'grundkommandos': 'grundkommandos', + 'trick': 'tricks', + 'tricks': 'tricks', + 'problemverhalten': 'problemverhalten', + 'mentale auslastung': 'mentale-auslastung', + 'mentale-auslastung': 'mentale-auslastung', + 'hundesport': 'hundesport', + 'körperpflege': 'koerperpflege', + 'koerperpflege': 'koerperpflege', + 'welpe basics': 'welpe-basics', + 'welpe-basics': 'welpe-basics', + 'grundlagen': 'grundlagen', + 'ki-trainer': 'ki-trainer', }; const _VALID_TABS = new Set(TABS.map(t => t.id)); @@ -461,6 +474,16 @@ window.Page_uebungen = (() => { if (_VALID_TABS.has(mapped)) _activeTab = mapped; } _render(); + + // Übungen aus DB laden (parallel mit Progress) + if (!_exercisesLoaded) { + API.get('/training/exercises').then(data => { + _exercisesByTab = data || {}; + _exercisesLoaded = true; + _renderContent(); // neu rendern sobald Daten da + }).catch(() => {}); + } + if (params.exercise_id || params.name) { setTimeout(() => { // Erst per exercise_id suchen (zuverlässig), dann per Name als Fallback @@ -911,7 +934,8 @@ window.Page_uebungen = (() => { const el = _container.querySelector('#ueb-content'); if (!el) return; - const isExerciseTab = ['grundkommandos', 'tricks', 'problemverhalten'].includes(_activeTab); + const isExerciseTab = ['grundkommandos','tricks','problemverhalten', + 'mentale-auslastung','hundesport','koerperpflege','welpe-basics'].includes(_activeTab); const showIf = v => v ? '' : 'none'; const quickWrap = _container.querySelector('#ueb-quicksetup-btn')?.parentElement; @@ -924,11 +948,23 @@ window.Page_uebungen = (() => { if (bannerEl) bannerEl.style.display = showIf(isExerciseTab); switch (_activeTab) { - case 'grundkommandos': el.innerHTML = _renderUebungsList(GRUNDKOMMANDOS); break; - case 'tricks': el.innerHTML = _renderUebungsList(TRICKS); break; - case 'problemverhalten': el.innerHTML = _renderUebungsList(PROBLEMVERHALTEN); break; - case 'grundlagen': el.innerHTML = _renderGrundlagen(); break; - case 'ki-trainer': el.innerHTML = _renderKiTrainer(); break; + case 'grundkommandos': + case 'tricks': + case 'problemverhalten': + case 'mentale-auslastung': + case 'hundesport': + case 'koerperpflege': + case 'welpe-basics': { + const list = _exercisesByTab[_activeTab] || []; + el.innerHTML = list.length + ? _renderUebungsList(list) + : `
+ ${UI.icon('spinner')} Übungen werden geladen… +
`; + break; + } + case 'grundlagen': el.innerHTML = _renderGrundlagen(); break; + case 'ki-trainer': el.innerHTML = _renderKiTrainer(); break; } _bindAccordions(); _bindStatusButtons(); @@ -971,7 +1007,7 @@ window.Page_uebungen = (() => { const currentId = _getStatus(_activeTab, u.name); const sm = _statusMeta(currentId); - const hasBody = u.schritte.length > 0 || u.fehler.length > 0 || u.steigerung; + const hasBody = (u.schritte?.length > 0) || (u.fehler?.length > 0) || !!u.steigerung || !!u.tipp; return `
@@ -1078,7 +1114,7 @@ window.Page_uebungen = (() => { ` : ''} + ${u.tipp ? ` +
+ 💡 ${_esc(u.tipp)} +
` : ''}
` : ''} diff --git a/backend/static/sw.js b/backend/static/sw.js index 029415f..204f6a0 100644 --- a/backend/static/sw.js +++ b/backend/static/sw.js @@ -3,7 +3,7 @@ Offline-Cache + Push Notifications + Tile-Cache ============================================================ */ -const CACHE_VERSION = 'by-v492'; +const CACHE_VERSION = 'by-v493'; const CACHE_STATIC = `${CACHE_VERSION}-static`; const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten