From 9b0c286aa3b4ddda3b5bf944e71112392d2b54b7 Mon Sep 17 00:00:00 2001 From: rene Date: Sat, 30 May 2026 12:31:59 +0200 Subject: [PATCH 1/5] Expenses: GET /api/expenses/categories liefert die Kategorien MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit KATEGORIE_META als Dict mit id → {label, color} (preserved Order). KATEGORIEN bleibt als Set für Validierung. Neuer Endpunkt liefert [{id, label, color}, …] als Single Source of Truth für PWA und mobile Clients — bisher war die Liste in JS und Python dupliziert. Mobile-Client (banyaro-ios) holt die Liste jetzt dynamisch. --- backend/routes/expenses.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/backend/routes/expenses.py b/backend/routes/expenses.py index 9a34f27..9e32e43 100644 --- a/backend/routes/expenses.py +++ b/backend/routes/expenses.py @@ -12,7 +12,15 @@ from auth import get_current_user router = APIRouter() logger = logging.getLogger(__name__) -KATEGORIEN = {"tierarzt", "futter", "zubehoer", "versicherung", "sitter", "sonstiges"} +KATEGORIE_META = { + "futter": {"label": "Futter", "color": "#f59e0b"}, + "tierarzt": {"label": "Tierarzt", "color": "#ef4444"}, + "zubehoer": {"label": "Zubehör", "color": "#8b5cf6"}, + "versicherung": {"label": "Versicherung", "color": "#3b82f6"}, + "sitter": {"label": "Sitter", "color": "#10b981"}, + "sonstiges": {"label": "Sonstiges", "color": "#6b7280"}, +} +KATEGORIEN = set(KATEGORIE_META.keys()) # ------------------------------------------------------------------ @@ -75,6 +83,18 @@ def _serialize(row) -> dict: return dict(row) +# ------------------------------------------------------------------ +# GET /api/expenses/categories — gültige Kategorien (id + label + color) +# Single source of truth für PWA und mobile Clients. +# ------------------------------------------------------------------ +@router.get("/categories") +async def list_categories(): + return [ + {"id": key, **meta} + for key, meta in KATEGORIE_META.items() + ] + + # ------------------------------------------------------------------ # GET /api/expenses/summary — Monats- und Jahressummen # WICHTIG: Diese Route muss VOR /{id} stehen! From 0685f3b8ef47648af84cb4d7b612fe6d171d532e Mon Sep 17 00:00:00 2001 From: rene Date: Sat, 30 May 2026 12:55:48 +0200 Subject: [PATCH 2/5] Welcome-Dashboard: Tagesfoto wird serverseitig pro Tag gecacht MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Neue Tabelle daily_photo_cache(dog_id, datum, photo_url) hält die Wahl fest, sobald irgendein Client (PWA oder iOS) das erste Mal heute fragt. Alle nachfolgenden Aufrufe liefern dieselbe URL — kein Kippen mehr, wenn zwischendrin neue Fotos hochgeladen werden und die tick%len-Rotation auf einen anderen Index zeigt. CREATE TABLE IF NOT EXISTS inline, daher kein Migrations-Eingriff nötig. --- backend/routes/dogs.py | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/backend/routes/dogs.py b/backend/routes/dogs.py index 1c37b99..9c414d8 100644 --- a/backend/routes/dogs.py +++ b/backend/routes/dogs.py @@ -232,15 +232,42 @@ async def get_welcome_dashboard(dog_id: int, user=Depends(get_current_user)): ORDER BY d.datum DESC, d.id DESC, dm.id ASC""", (dog_id,) ).fetchall() + # Cross-Plattform-stabiles Tagesfoto: einmal pro Tag pro Hund festgelegt + # und in daily_photo_cache persistiert. Sobald ein Client (PWA oder + # iOS) zum ersten Mal heute zugreift, wird die Wahl gespeichert; alle + # weiteren Clients liefern dasselbe Bild. + import datetime as _dt2 + conn.execute(""" + CREATE TABLE IF NOT EXISTS daily_photo_cache ( + dog_id INTEGER NOT NULL, + datum TEXT NOT NULL, + photo_url TEXT NOT NULL, + PRIMARY KEY (dog_id, datum) + ) + """) + today_iso = _dt2.date.today().isoformat() + cached = conn.execute( + "SELECT photo_url FROM daily_photo_cache WHERE dog_id=? AND datum=?", + (dog_id, today_iso) + ).fetchone() + random_photo = None - if photos: - import datetime as _dt2 + if cached and cached["photo_url"]: + random_photo = { + "url": cached["photo_url"], + "preview_url": preview_url_from(cached["photo_url"]), + } + elif photos: tick = (_dt2.date.today() - _dt2.date(2024, 1, 1)).days chosen_url = photos[tick % len(photos)]["url"] random_photo = { "url": chosen_url, "preview_url": preview_url_from(chosen_url), } + conn.execute( + "INSERT OR IGNORE INTO daily_photo_cache (dog_id, datum, photo_url) VALUES (?, ?, ?)", + (dog_id, today_iso, chosen_url) + ) # Neuester Tagebucheintrag last_diary_row = conn.execute( From 2d43618dc8c3e1beacee4daa159693703e728b2c Mon Sep 17 00:00:00 2001 From: rene Date: Sat, 30 May 2026 17:07:24 +0200 Subject: [PATCH 3/5] Onboarding-Wizard: kein Stuck-State mehr bei vorhandenem Hund --- backend/static/js/pages/onboarding.js | 40 +++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/backend/static/js/pages/onboarding.js b/backend/static/js/pages/onboarding.js index c292f03..746dc63 100644 --- a/backend/static/js/pages/onboarding.js +++ b/backend/static/js/pages/onboarding.js @@ -15,6 +15,28 @@ window.Page_onboarding = (() => { async function init(container, appState) { _container = container; _appState = appState; + + // Hunde frisch laden — der appState kann veraltet sein (z.B. nach + // Anlage in mobiler App). Wenn schon ein Hund da ist, Wizard + // komplett überspringen. + try { + const dogs = await API.dogs.list(); + _appState.dogs = dogs; + if (dogs.length > 0) { + _appState.activeDog = dogs[0]; + localStorage.setItem('by_active_dog', String(dogs[0].id)); + localStorage.setItem('by_onboarding_done', '1'); + App.renderDogSwitcher?.(); + if (window.Worlds) window.Worlds.init(_appState); + else App.navigate('diary'); + return; + } + } catch (e) { + // Lieber Wizard zeigen als komplett blockieren — wenn API down ist, + // landet der User halt im Standard-Flow. + console.warn('Onboarding: dog-list refresh failed', e); + } + _step = 1; _render(); } @@ -422,6 +444,24 @@ window.Page_onboarding = (() => { _render(); } catch (err) { + // 403 „schon einen Hund" → kein Stuck-State, weiter zu Schritt 3 + const isAlreadyHas = err?.status === 403 + || /Pro-Feature|schon|already|maximal/i.test(err?.message || ''); + if (isAlreadyHas) { + try { + const dogs = await API.dogs.list(); + _appState.dogs = dogs; + if (dogs.length > 0) { + _appState.activeDog = dogs[0]; + localStorage.setItem('by_active_dog', String(dogs[0].id)); + App.renderDogSwitcher?.(); + } + } catch {} + UI.toast.info?.('Du hast bereits einen Hund — geht direkt weiter.'); + _step = 3; + _render(); + return; + } UI.toast.error(err.message || 'Hund konnte nicht angelegt werden.'); } finally { if (saveBtn) { From fb9620fbcb0c0c852ab432382ae3f66f020faf91 Mon Sep 17 00:00:00 2001 From: rene Date: Sat, 30 May 2026 17:19:11 +0200 Subject: [PATCH 4/5] Onboarding: 'Los geht's' navigiert direkt zum vollen Hunde-Profil-Formular statt Mini-Wizard. Welcome-'Hund anlegen' nutzt echten Event-Listener statt inline onclick. --- backend/static/js/pages/onboarding.js | 8 +++++--- backend/static/js/worlds.js | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/backend/static/js/pages/onboarding.js b/backend/static/js/pages/onboarding.js index 746dc63..5ba7638 100644 --- a/backend/static/js/pages/onboarding.js +++ b/backend/static/js/pages/onboarding.js @@ -337,10 +337,12 @@ window.Page_onboarding = (() => { // EVENTS // ---------------------------------------------------------- function _bindEvents() { - // Weiter-Button (Schritt 1) + // Weiter-Button (Schritt 1) — direkt ins richtige Hunde-Profil, + // statt eines doppelten Mini-Formulars im Wizard. Onboarding gilt + // damit als erledigt, sobald der User dort angekommen ist. _container.querySelector('#ob-next-btn')?.addEventListener('click', () => { - _step = 2; - _render(); + localStorage.setItem('by_onboarding_done', '1'); + App.navigate('dog-profile'); }); // Zurück-Button (Schritt 2) diff --git a/backend/static/js/worlds.js b/backend/static/js/worlds.js index c6e05eb..52f3baa 100644 --- a/backend/static/js/worlds.js +++ b/backend/static/js/worlds.js @@ -1425,7 +1425,7 @@ window.Worlds = (() => {
Lege ein Profil an und schalte alle Features frei
- @@ -1444,6 +1444,7 @@ window.Worlds = (() => { `; el.querySelectorAll('[data-wnav]').forEach(e => e.addEventListener('click', () => navigateTo(e.dataset.wnav))); + el.querySelector('#welcome-add-dog-btn')?.addEventListener('click', () => navigateTo('dog-profile')); return; } From 7a10db2da447e94c5a8aa301dad97a58d5b7fc9f Mon Sep 17 00:00:00 2001 From: rene Date: Sat, 30 May 2026 17:28:29 +0200 Subject: [PATCH 5/5] =?UTF-8?q?Bump=20APP=5FVER=201133=20=E2=86=92=201134?= =?UTF-8?q?=20(SW-Cache-Bust=20f=C3=BCr=20Onboarding/Welcome-Fix)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VERSION | 2 +- backend/static/index.html | 24 ++++++++++++------------ backend/static/js/app.js | 2 +- backend/static/landing.html | 2 +- backend/static/sw.js | 2 +- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/VERSION b/VERSION index a624bd7..ff7be53 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1133 \ No newline at end of file +1134 \ No newline at end of file diff --git a/backend/static/index.html b/backend/static/index.html index 1e0a5d0..cd373a2 100644 --- a/backend/static/index.html +++ b/backend/static/index.html @@ -86,14 +86,14 @@ Ban Yaro - + - - - - - + + + + + @@ -617,11 +617,11 @@ - - - - - + + + + + @@ -631,7 +631,7 @@ - + diff --git a/backend/static/js/app.js b/backend/static/js/app.js index a7b5e90..e116fa3 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 = '1133'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen +const APP_VER = '1134'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen const APP_VERSION = '1.6.0'; // ← semantische Version, wird bei make release gesetzt window.APP_VER = APP_VER; // global verfügbar für andere Module (z.B. offline-indicator) window.APP_VERSION = APP_VERSION; diff --git a/backend/static/landing.html b/backend/static/landing.html index 06ddcf5..a04db75 100644 --- a/backend/static/landing.html +++ b/backend/static/landing.html @@ -4,7 +4,7 @@ - + Ban Yaro — Die Hunde-App für Deutschland, Österreich & Schweiz diff --git a/backend/static/sw.js b/backend/static/sw.js index b11d249..f92b73f 100644 --- a/backend/static/sw.js +++ b/backend/static/sw.js @@ -4,7 +4,7 @@ ============================================================ */ // ← EINZIGE Stelle für die Version — STATIC_ASSETS und CACHE_VERSION leiten sich ab -const VER = '1133'; +const VER = '1134'; const CACHE_VERSION = `by-v${VER}`; const CACHE_STATIC = `${CACHE_VERSION}-static`; const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten