Feature: Übungs-Suche, fehlende Legacy-Übungen nachmigriert (110 gesamt) — SW by-v495, APP_VER 472

This commit is contained in:
rene 2026-04-29 12:26:32 +02:00
parent 4c3638c17c
commit 81ee1a063e
4 changed files with 63 additions and 4 deletions

View file

@ -1433,3 +1433,39 @@ def _migrate(conn_factory):
); );
""") """)
logger.info("Migration: ors_daily_total erstellt.") logger.info("Migration: ors_daily_total erstellt.")
# Fehlende Legacy-JS-Übungen nachträglich einfügen
import json as _json
_legacy = [
('trick_platz_decke', 'Platz auf Decke / Matte', 'Trick', 'leicht', '5-10 min',
'Der Hund geht auf Kommando auf seine Decke oder Matte und legt sich hin.',
_json.dumps(['Decke auslegen', 'Hund mit Leckerli auf die Matte locken', 'Markerwort + Leckerli wenn er drauf steht', 'Platz-Signal einbauen', 'Distanz schrittweise erhöhen']),
'Die Matte zum positiven Ort machen, nie erzwingen.'),
('trick_suchspiel', 'Suchspiel / Nasenarbeit', 'Trick', 'leicht', '10-15 min',
'Der Hund sucht versteckte Leckerlis per Nase — mentale Auslastung pur.',
_json.dumps(['Leckerli offen zeigen', 'Hund kurz festhalten, Leckerli verstecken', 'Freigabe-Wort und suchen lassen', 'Schwierigkeit langsam steigern']),
'Nasenarbeit erschöpft mehr als körperliche Aktivität.'),
('pb_nicht_springen2', 'Nicht springen / Begrüßung', 'Problemverhalten', 'leicht', '5-10 min',
'Der Hund begrüßt Menschen ohne hochzuspringen.',
_json.dumps(['Hund ignorieren wenn er springt (Rücken zudrehen)', 'Erst belohnen wenn alle vier Pfoten am Boden', 'Gäste in die Übung einbeziehen', 'Konsequent sein']),
'Alle Familienmitglieder müssen gleich reagieren.'),
('pb_leinenfuehrigkeit', 'Leinenführigkeit — Nicht ziehen', 'Problemverhalten', 'mittel', '10-20 min',
'Der Hund läuft locker an der Leine ohne zu ziehen.',
_json.dumps(['Bei Leinenzug stehen bleiben oder Richtung wechseln', 'Lockere Leine immer belohnen', 'Kurzstrecken üben, nicht lange Spaziergänge']),
'Dauert Wochen konsequentes Üben — Geduld.'),
('pb_bellen_klaffen', 'Bellen / Kläffen', 'Problemverhalten', 'mittel', '10 min',
'Übermäßiges Bellen auf Kommando reduzieren.',
_json.dumps(['Auslöser identifizieren', '"Ruhig"-Kommando einführen wenn Pause entsteht', 'Ruhige Pausen direkt belohnen']),
'Bellen nie durch Schreien lösen — das wirkt wie Mitmachen.'),
('pb_enttriggern', 'Enttriggern / Desensibilisierung', 'Problemverhalten', 'schwer', '15-30 min',
'Den Hund langsam an angstauslösende Reize gewöhnen — für reaktive Hunde.',
_json.dumps(['Angst-Auslöser in großer Distanz zeigen', 'Bei Ruhe belohnen (kein Stress sichtbar)', 'Distanz sehr langsam verringern', 'Niemals erzwingen']),
'Immer unterhalb der Stressschwelle bleiben.'),
]
for ex in _legacy:
if not conn.execute('SELECT 1 FROM training_exercises WHERE exercise_id=?', (ex[0],)).fetchone():
conn.execute(
'INSERT INTO training_exercises (exercise_id,name,kategorie,schwierigkeit,dauer,beschreibung,schritte,tipp) VALUES (?,?,?,?,?,?,?,?)',
ex
)
logger.info(f"Migration: Übung '{ex[1]}' eingefügt.")

View file

@ -3,7 +3,7 @@
Router, State-Management, Navigation, Initialisierung. Router, State-Management, Navigation, Initialisierung.
============================================================ */ ============================================================ */
const APP_VER = '471'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen const APP_VER = '472'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const App = (() => { const App = (() => {

View file

@ -39,6 +39,7 @@ window.Page_uebungen = (() => {
let _exercisesByTab = {}; // aus API geladen let _exercisesByTab = {}; // aus API geladen
let _exercisesLoaded = false; let _exercisesLoaded = false;
let _scrollTarget = null; // { exercise_id, name } — nach _renderContent() scrollen let _scrollTarget = null; // { exercise_id, name } — nach _renderContent() scrollen
let _searchQuery = ''; // aktuelle Sucheingabe
// ---------------------------------------------------------- // ----------------------------------------------------------
// DATEN // DATEN
@ -566,10 +567,21 @@ window.Page_uebungen = (() => {
<div id="ueb-stats-banner" style="padding:var(--space-2) var(--space-4) 0"></div> <div id="ueb-stats-banner" style="padding:var(--space-2) var(--space-4) 0"></div>
<div id="ueb-trainer" style="padding:0 var(--space-4);margin-bottom:var(--space-2)"></div> <div id="ueb-trainer" style="padding:0 var(--space-4);margin-bottom:var(--space-2)"></div>
<div id="ueb-suggestions" style="padding:0 var(--space-4);display:flex;flex-direction:column;gap:var(--space-2);margin-bottom:var(--space-2)"></div> <div id="ueb-suggestions" style="padding:0 var(--space-4);display:flex;flex-direction:column;gap:var(--space-2);margin-bottom:var(--space-2)"></div>
<div id="ueb-content"></div> <div style="padding:var(--space-3) var(--space-4) 0" id="ueb-search-wrap">
<input type="search" id="ueb-search" placeholder="Übung suchen…"
style="width:100%;padding:var(--space-2) var(--space-3);
border:1px solid var(--c-border);border-radius:var(--radius-md);
background:var(--c-surface);color:var(--c-text);font-size:var(--text-sm);
outline:none" value="${_esc(_searchQuery)}">
</div>
<div id="ueb-content"></div>
</div> </div>
`; `;
_container.querySelector('#ueb-quicksetup-btn').addEventListener('click', _openQuickSetupModal); _container.querySelector('#ueb-quicksetup-btn').addEventListener('click', _openQuickSetupModal);
_container.querySelector('#ueb-search')?.addEventListener('input', e => {
_searchQuery = e.target.value.trim().toLowerCase();
_renderContent();
});
_bindTabs(); _bindTabs();
_renderContent(); _renderContent();
_renderStatsBanner(); _renderStatsBanner();
@ -986,9 +998,20 @@ window.Page_uebungen = (() => {
// ÜBUNGS-CARDS // ÜBUNGS-CARDS
// ---------------------------------------------------------- // ----------------------------------------------------------
function _renderUebungsList(list) { function _renderUebungsList(list) {
const filtered = _searchQuery
? list.filter(u =>
u.name.toLowerCase().includes(_searchQuery) ||
(u.beschreibung || '').toLowerCase().includes(_searchQuery)
)
: list;
if (!filtered.length) {
return `<div style="padding:var(--space-8);text-align:center;color:var(--c-text-muted)">
${UI.icon('magnifying-glass')} Keine Übungen gefunden für ${_esc(_searchQuery)}"
</div>`;
}
return ` return `
<div style="padding:var(--space-4);display:flex;flex-direction:column;gap:var(--space-4)"> <div style="padding:var(--space-4);display:flex;flex-direction:column;gap:var(--space-4)">
${list.map((u, i) => _renderCard(u, i)).join('')} ${filtered.map((u, i) => _renderCard(u, i)).join('')}
</div> </div>
`; `;
} }

View file

@ -3,7 +3,7 @@
Offline-Cache + Push Notifications + Tile-Cache Offline-Cache + Push Notifications + Tile-Cache
============================================================ */ ============================================================ */
const CACHE_VERSION = 'by-v494'; const CACHE_VERSION = 'by-v495';
const CACHE_STATIC = `${CACHE_VERSION}-static`; const CACHE_STATIC = `${CACHE_VERSION}-static`;
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten