Feature: Trauer-Feature, Futter-Verträglichkeit, Multi-Hund-Fixes, Wetter-Ort (Sprint 47)
- dog-profile.js: Verstorben-Button, Gedenkseite, KI-Abschiedstext - database.py: futter_eintraege/reaktionen, route_dogs, exercise_progress.dog_id - routes/ernaehrung.py: Futter-Verträglichkeit mit 20 Reaktionstypen + Analyse - routes/routen.py: route_dogs Many-to-Many, Routen editierbar - routes/training.py: exercise_progress per dog_id - routes/ki.py: /ki/abschied Trauer-KI - weather.py: Nominatim Ortsname parallel geladen - ui.js: dogChip/bindDogChip, visualViewport-Modal - api.js: gedenken, gedenkseite, futter-Methoden, route_dogs - worlds.js: Ortsname im Wetter-Chip - uebungen.js: _progressLoaded-Flag, dog-spezifischer Fortschritt - trainingsplaene.js: dog_id Unterstützung - diary.js/health.js: P-Badge Cleanup - map.js: Wetter-Ort-Anzeige entfernt - wetter.js: Ort in Wetter-Detail
This commit is contained in:
parent
1ce802c8dc
commit
bda61a0e40
16 changed files with 713 additions and 181 deletions
|
|
@ -75,6 +75,7 @@ window.Page_uebungen = (() => {
|
|||
|
||||
// In-memory cache (loaded from API on init)
|
||||
let _progressCache = {}; // key → statusId
|
||||
let _progressLoaded = false;
|
||||
let _exerciseStats = {}; // exercise_id → {recent_avg, session_count, trend}
|
||||
|
||||
function _progressKey(tab, name) {
|
||||
|
|
@ -83,17 +84,13 @@ window.Page_uebungen = (() => {
|
|||
|
||||
function _getStatus(tab, name) {
|
||||
const k = _progressKey(tab, name);
|
||||
// Fallback to localStorage while API loads
|
||||
return _progressCache[k] !== undefined
|
||||
? _progressCache[k]
|
||||
: localStorage.getItem(_statusKey(tab, name)) || null;
|
||||
return _progressCache[k] ?? null;
|
||||
}
|
||||
|
||||
function _setStatus(tab, name, statusId) {
|
||||
const k = _progressKey(tab, name);
|
||||
_progressCache[k] = statusId;
|
||||
localStorage.setItem(_statusKey(tab, name), statusId || ''); // keep localStorage in sync
|
||||
API.training.setProgress(k, statusId).catch(() => {});
|
||||
API.training.setProgress(k, statusId, _dogId()).catch(() => {});
|
||||
}
|
||||
|
||||
function _nextStatus(currentId) {
|
||||
|
|
@ -504,28 +501,19 @@ window.Page_uebungen = (() => {
|
|||
_scrollTarget = { exercise_id: params.exercise_id || '', name: params.name || '' };
|
||||
}
|
||||
|
||||
// Progress vom Server laden
|
||||
API.training.getProgress().then(rows => {
|
||||
rows.forEach(r => { _progressCache[r.exercise_id] = r.status; });
|
||||
// localStorage-Daten migrieren falls noch nicht im Backend
|
||||
Object.keys(localStorage).filter(k => k.startsWith('ub_status_')).forEach(lsKey => {
|
||||
const parts = lsKey.replace('ub_status_', '').split('_');
|
||||
const tab = parts[0];
|
||||
const name = parts.slice(1).join('_');
|
||||
const apiKey = `${tab}_${name}`;
|
||||
if (_progressCache[apiKey] === undefined) {
|
||||
const val = localStorage.getItem(lsKey);
|
||||
if (val) {
|
||||
_progressCache[apiKey] = val;
|
||||
API.training.setProgress(apiKey, val).catch(() => {});
|
||||
}
|
||||
}
|
||||
});
|
||||
_renderContent(); // Re-render with loaded progress
|
||||
}).catch(() => {});
|
||||
// Progress vom Server laden (hund-spezifisch)
|
||||
const _did = _dogId();
|
||||
_progressLoaded = false;
|
||||
API.training.getProgress(_did)
|
||||
.then(rows => {
|
||||
_progressCache = {};
|
||||
rows.forEach(r => { _progressCache[r.exercise_id] = r.status; });
|
||||
_progressLoaded = true;
|
||||
_renderContent();
|
||||
}).catch(() => { _progressLoaded = true; _renderContent(); });
|
||||
|
||||
// Empfehlungen laden
|
||||
API.training.getSuggestions().then(suggestions => {
|
||||
API.training.getSuggestions(_did).then(suggestions => {
|
||||
if (suggestions.length) _showSuggestions(suggestions);
|
||||
}).catch(() => {});
|
||||
|
||||
|
|
@ -556,6 +544,7 @@ window.Page_uebungen = (() => {
|
|||
_statsData = null;
|
||||
_badgesData = null;
|
||||
_progressCache = {};
|
||||
_progressLoaded = false;
|
||||
_exerciseStats = {};
|
||||
_render();
|
||||
_loadStatsAndBadges();
|
||||
|
|
@ -568,6 +557,7 @@ window.Page_uebungen = (() => {
|
|||
function _render() {
|
||||
_container.innerHTML = `
|
||||
<div id="ueb-wrap">
|
||||
<div style="padding:var(--space-3) var(--space-4) 0">${UI.dogChip(_appState)}</div>
|
||||
<div style="padding:var(--space-3) var(--space-4) var(--space-2)">
|
||||
<table style="width:100%;border-collapse:collapse">
|
||||
<tr>
|
||||
|
|
@ -604,6 +594,7 @@ window.Page_uebungen = (() => {
|
|||
<div id="ueb-content"></div>
|
||||
</div>
|
||||
`;
|
||||
UI.bindDogChip(_container, _appState);
|
||||
_container.querySelector('#ueb-quicksetup-btn').addEventListener('click', _openQuickSetupModal);
|
||||
_container.querySelector('#ueb-tabs')?.style.setProperty('--ueb-tab-cols', Math.ceil(TABS.length / 2));
|
||||
_container.querySelector('#ueb-search')?.addEventListener('input', e => {
|
||||
|
|
@ -613,7 +604,12 @@ window.Page_uebungen = (() => {
|
|||
_renderContent();
|
||||
});
|
||||
_bindTabs();
|
||||
_renderContent();
|
||||
if (_progressLoaded) {
|
||||
_renderContent();
|
||||
} else {
|
||||
const el = _container.querySelector('#ueb-content');
|
||||
if (el) el.innerHTML = `<div style="padding:var(--space-6);text-align:center;color:var(--c-text-muted)"><svg class="ph-icon" style="width:24px;height:24px;animation:spin 1s linear infinite" aria-hidden="true"><use href="/icons/phosphor.svg#spinner"></use></svg></div>`;
|
||||
}
|
||||
_renderStatsBanner();
|
||||
}
|
||||
|
||||
|
|
@ -782,7 +778,18 @@ window.Page_uebungen = (() => {
|
|||
// ----------------------------------------------------------
|
||||
// SCHNELL-SETUP: Stand aller Übungen erfassen
|
||||
// ----------------------------------------------------------
|
||||
function _openQuickSetupModal() {
|
||||
async function _openQuickSetupModal() {
|
||||
// Sicherstellen dass Progress geladen ist bevor das Modal öffnet
|
||||
if (!_progressLoaded) {
|
||||
const did = _dogId();
|
||||
try {
|
||||
const rows = await API.training.getProgress(did);
|
||||
_progressCache = {};
|
||||
rows.forEach(r => { _progressCache[r.exercise_id] = r.status; });
|
||||
_progressLoaded = true;
|
||||
_renderContent();
|
||||
} catch { _progressLoaded = true; }
|
||||
}
|
||||
const ALL = [
|
||||
{ group: 'Grundkommandos', tab: 'grundkommandos', items: GRUNDKOMMANDOS },
|
||||
{ group: 'Tricks', tab: 'tricks', items: TRICKS },
|
||||
|
|
@ -883,11 +890,8 @@ window.Page_uebungen = (() => {
|
|||
|
||||
// Alle geänderten Status speichern
|
||||
const parts = Object.entries(pending).map(([key, val]) => {
|
||||
const [tab, ...rest] = key.split('_');
|
||||
const name = rest.join('_').replace(/_/g, ' ');
|
||||
_progressCache[key] = val || null;
|
||||
localStorage.setItem(`ub_status_${key}`, val || '');
|
||||
return API.training.setProgress(key, val || null);
|
||||
return API.training.setProgress(key, val || null, _dogId());
|
||||
});
|
||||
await Promise.allSettled(parts);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue