Fix: Verlauf-Tab kein Endlos-Spinner + Save-422 bei null-Feldern

- Verlauf: _verlaufLoading-Flag verhindert parallele Loads
- Verlauf: el nach await neu holen (stale DOM-Referenz nach Re-Render)
- Verlauf: bei _renderContent() Shell nur rendern wenn keine Sessions im Cache
- Backend: hund_stimmung/zufriedenheit als Optional[str/int] → akzeptiert null
This commit is contained in:
rene 2026-05-19 18:28:56 +02:00
parent cc841ef6d7
commit 738571d958
2 changed files with 21 additions and 6 deletions

View file

@ -323,8 +323,8 @@ class SessionCreate(BaseModel):
datum: Optional[str] = None
wiederholungen: int = 1
erfolgsquote: int = 50
hund_stimmung: str = "aufmerksam"
zufriedenheit: int = 3
hund_stimmung: Optional[str] = "aufmerksam"
zufriedenheit: Optional[int] = 3
notiz: Optional[str] = None
tagebuch_eintrag: bool = False # ignoriert — Training hat eigenes Protokoll

View file

@ -549,6 +549,7 @@ window.Page_uebungen = (() => {
_exerciseStats = {};
_verlaufSessions = [];
_verlaufOffset = 0;
_verlaufLoading = false;
_render();
_loadStatsAndBadges();
_loadVirtualTrainer();
@ -994,7 +995,6 @@ 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':
@ -1016,7 +1016,16 @@ window.Page_uebungen = (() => {
break;
}
case 'grundlagen': el.innerHTML = _renderGrundlagen(); break;
case 'verlauf': el.innerHTML = _renderVerlaufShell(); break;
case 'verlauf': {
if (_verlaufSessions.length > 0) {
el.innerHTML = `<div id="verlauf-wrap" style="padding:var(--space-4);display:flex;flex-direction:column;gap:var(--space-3)"><div id="verlauf-list"></div></div>`;
_renderVerlaufList(el.querySelector('#verlauf-list'));
} else {
el.innerHTML = _renderVerlaufShell();
_loadVerlauf();
}
break;
}
case 'ki-trainer':
if (!App.hasPro(_appState?.user)) {
el.innerHTML = `<div style="padding:var(--space-6);text-align:center;color:var(--c-text-muted)">
@ -1977,6 +1986,7 @@ window.Page_uebungen = (() => {
let _verlaufSessions = [];
let _verlaufOffset = 0;
let _verlaufHasMore = false;
let _verlaufLoading = false;
const _VERLAUF_LIMIT = 30;
const _ERFOLG_EMOJI = { 0: '😓', 25: '😐', 50: '🙂', 75: '😊', 100: '🎉' };
@ -2001,19 +2011,24 @@ window.Page_uebungen = (() => {
}
async function _loadVerlauf(append = false) {
if (_verlaufLoading) return;
const dogId = _dogId();
if (!dogId) return;
const el = _container.querySelector('#verlauf-list');
if (!el) return;
if (!append) {
_verlaufSessions = [];
_verlaufOffset = 0;
}
_verlaufLoading = true;
const data = await _apiGet(
`/api/training/sessions?dog_id=${dogId}&limit=${_VERLAUF_LIMIT + 1}&offset=${_verlaufOffset}`
).catch(() => null);
_verlaufLoading = false;
// Element nach await neu holen — könnte durch Re-Render veraltet sein
const el = _container?.querySelector('#verlauf-list');
if (!el) return;
if (!data) {
if (!append) el.innerHTML = `<div style="padding:var(--space-6);text-align:center;color:var(--c-text-muted);font-size:var(--text-sm)">Fehler beim Laden.</div>`;