From 1fdba57365d53da8d3e0439f648476164f49f26b Mon Sep 17 00:00:00 2001 From: rene Date: Sun, 3 May 2026 19:50:04 +0200 Subject: [PATCH] =?UTF-8?q?Feature:=20UX-Fixes=20=E2=80=94=20Zahnrad=20weg?= =?UTF-8?q?,=20POI-Kombi-Typen,=20exp-fab-Position,=20Welten-Config=20in?= =?UTF-8?q?=20DB=20(SW=20by-v653)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - worlds-settings Zahnrad komplett entfernt (war auf Mobile sichtbar, auf Desktop schon hidden) - exp-fab: bottom jetzt calc(--nav-bottom-height + --safe-bottom + --space-2) — kein Overlap mit worlds-back auf iPhone - Karte POI: neue Typen bank, bank_kotbeutel, bank_kotbeutel_abfall, kotbeutel_abfall (Backend + Frontend) - Welten-Chip-Config: GET/PUT /profile/world-config, Spalte users.world_config TEXT (Migration), Sync bei Init + Speichern --- backend/database.py | 5 +++++ backend/routes/osm.py | 14 +++++++++----- backend/routes/profile.py | 25 +++++++++++++++++++++++++ backend/static/css/components.css | 2 +- backend/static/index.html | 11 ++++------- backend/static/js/app.js | 2 +- backend/static/js/pages/map.js | 20 ++++++++++++-------- backend/static/js/worlds.js | 30 ++++++++++++++++++++++++++---- backend/static/sw.js | 2 +- 9 files changed, 84 insertions(+), 27 deletions(-) diff --git a/backend/database.py b/backend/database.py index eeb1add..078e7ae 100644 --- a/backend/database.py +++ b/backend/database.py @@ -1923,6 +1923,11 @@ def _migrate(conn_factory): ) """) + # Welten-Chip-Konfiguration pro User + existing_u = [row[1] for row in conn.execute("PRAGMA table_info(users)").fetchall()] + if 'world_config' not in existing_u: + conn.execute("ALTER TABLE users ADD COLUMN world_config TEXT") + # Wiederkehrende Ausgaben (Daueraufträge) conn.executescript(""" CREATE TABLE IF NOT EXISTS recurring_expenses ( diff --git a/backend/routes/osm.py b/backend/routes/osm.py index e08742b..1f73f76 100644 --- a/backend/routes/osm.py +++ b/backend/routes/osm.py @@ -279,11 +279,15 @@ class UserPoiIn(BaseModel): ALLOWED_TYPES = { 'waste_basket', 'drinking_water', 'dog_park', - 'giftkoeder', # Giftköder-Meldung (Community-Pin mit Radius) - 'kotbeutel', # Kotbeutelspender - 'gefahr', # Allgemeine Gefahr / Hinweis - 'parkplatz', # Hundefreundlicher Parkplatz - 'treffpunkt', # Treffpunkt für Hundehalter + 'giftkoeder', # Giftköder-Meldung (Community-Pin mit Radius) + 'kotbeutel', # Kotbeutelspender + 'kotbeutel_abfall', # Kotbeutelspender + Mülleimer Kombi + 'bank', # Sitzbank + 'bank_kotbeutel', # Sitzbank + Kotbeutelspender + 'bank_kotbeutel_abfall', # Sitzbank + Kotbeutelspender + Mülleimer + 'gefahr', # Allgemeine Gefahr / Hinweis + 'parkplatz', # Hundefreundlicher Parkplatz + 'treffpunkt', # Treffpunkt für Hundehalter 'sonstiges', } diff --git a/backend/routes/profile.py b/backend/routes/profile.py index 08f403a..5c07b99 100644 --- a/backend/routes/profile.py +++ b/backend/routes/profile.py @@ -113,3 +113,28 @@ async def upload_avatar( ) return {"avatar_url": avatar_url} + + +# ---------------------------------------------------------- +# GET /profile/world-config — Welten-Chip-Konfiguration laden +# PUT /profile/world-config — Welten-Chip-Konfiguration speichern +# ---------------------------------------------------------- +import json as _json + +@router.get('/profile/world-config') +async def get_world_config(user=Depends(get_current_user)): + with db() as conn: + row = conn.execute("SELECT world_config FROM users WHERE id=?", (user['id'],)).fetchone() + cfg = row['world_config'] if row and row['world_config'] else None + return {"config": _json.loads(cfg) if cfg else None} + + +class WorldConfigIn(BaseModel): + config: dict + +@router.put('/profile/world-config') +async def put_world_config(body: WorldConfigIn, user=Depends(get_current_user)): + with db() as conn: + conn.execute("UPDATE users SET world_config=? WHERE id=?", + (_json.dumps(body.config), user['id'])) + return {"status": "ok"} diff --git a/backend/static/css/components.css b/backend/static/css/components.css index 3302268..564b011 100644 --- a/backend/static/css/components.css +++ b/backend/static/css/components.css @@ -6954,7 +6954,7 @@ svg.empty-state-icon { /* FAB */ .exp-fab { position: fixed; - bottom: calc(var(--nav-height, 64px) + var(--space-4)); + bottom: calc(var(--nav-bottom-height) + var(--safe-bottom) + var(--space-2)); right: var(--space-4); z-index: 100; width: 52px; diff --git a/backend/static/index.html b/backend/static/index.html index 6086cbb..e052694 100644 --- a/backend/static/index.html +++ b/backend/static/index.html @@ -93,9 +93,9 @@ - - - + + + @@ -539,9 +539,6 @@ HUND WELT -
@@ -565,7 +562,7 @@ - + diff --git a/backend/static/js/app.js b/backend/static/js/app.js index 8d5a013..99cabf8 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 = '652'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen +const APP_VER = '653'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen const APP_VERSION = '1.3.0'; // ← semantische Version, wird bei make release gesetzt const IS_STAGING = location.hostname === 'staging.banyaro.app'; diff --git a/backend/static/js/pages/map.js b/backend/static/js/pages/map.js index ded9a7d..594951e 100644 --- a/backend/static/js/pages/map.js +++ b/backend/static/js/pages/map.js @@ -839,14 +839,18 @@ window.Page_map = (() => { } const PIN_TYPES = [ - { type: 'giftkoeder', icon: '', label: 'Giftköder', color: '#DC2626' }, // ← wichtigster Typ, immer oben - { type: 'waste_basket', icon: '', label: 'Mülleimer', color: '#6B7280' }, - { type: 'kotbeutel', icon: '', label: 'Kotbeutel', color: '#84A98C' }, - { type: 'drinking_water', icon: '', label: 'Wasserstelle', color: '#0EA5E9' }, - { type: 'dog_park', icon: '', label: 'Hundewiese', color: '#15803D' }, - { type: 'parkplatz', icon: '', label: 'Parkplatz', color: '#2563EB' }, - { type: 'treffpunkt', icon: '', label: 'Treffpunkt', color: '#7C3AED' }, - { type: 'sonstiges', icon: '', label: 'Sonstiges', color: '#F59E0B' }, + { type: 'giftkoeder', icon: '', label: 'Giftköder', color: '#DC2626' }, + { type: 'waste_basket', icon: '', label: 'Mülleimer', color: '#6B7280' }, + { type: 'kotbeutel', icon: '', label: 'Kotbeutel', color: '#84A98C' }, + { type: 'kotbeutel_abfall', icon: '', label: 'Kotbeutel + Mülleimer', color: '#5a8a6a' }, + { type: 'bank', icon: '', label: 'Sitzbank', color: '#92400E' }, + { type: 'bank_kotbeutel', icon: '', label: 'Bank + Kotbeutel', color: '#7a6030' }, + { type: 'bank_kotbeutel_abfall', icon: '', label: 'Bank + Kotbeutel + Mülleimer', color: '#4a5a2a' }, + { type: 'drinking_water', icon: '', label: 'Wasserstelle', color: '#0EA5E9' }, + { type: 'dog_park', icon: '', label: 'Hundewiese', color: '#15803D' }, + { type: 'parkplatz', icon: '', label: 'Parkplatz', color: '#2563EB' }, + { type: 'treffpunkt', icon: '', label: 'Treffpunkt', color: '#7C3AED' }, + { type: 'sonstiges', icon: '', label: 'Sonstiges', color: '#F59E0B' }, ]; function _confirmPlacement(latlng) { diff --git a/backend/static/js/worlds.js b/backend/static/js/worlds.js index 0fc7723..4c925f2 100644 --- a/backend/static/js/worlds.js +++ b/backend/static/js/worlds.js @@ -49,6 +49,8 @@ window.Worlds = (() => { _setupButtons(); _goTo(_cur, false); show(); + // Config aus DB laden (async, dann neu rendern wenn nötig) + await _loadConfigFromServer(); // Welten parallel rendern _renderJetzt(); _renderHund(); @@ -159,7 +161,6 @@ window.Worlds = (() => { function _setupButtons() { document.getElementById('worlds-fab')?.addEventListener('click', _openFab); - document.getElementById('worlds-settings')?.addEventListener('click', () => navigateTo('settings')); document.getElementById('worlds-back')?.addEventListener('click', () => show()); document.querySelectorAll('.wdot').forEach((dot, i) => { dot.style.pointerEvents = 'auto'; @@ -296,12 +297,33 @@ window.Worlds = (() => { welt: ['map','forum','friends','walks','poison','recalls','lost','routes','events','jobs','knigge','movies'], }; - function _getConfig() { - try { return JSON.parse(localStorage.getItem('world_chips') || 'null') || _DEFAULT_CONFIG; } - catch { return _DEFAULT_CONFIG; } + // _cfgCache: wird beim Init aus DB geladen, Fallback localStorage → Default + let _cfgCache = null; + + async function _loadConfigFromServer() { + try { + const res = await API.get('/profile/world-config'); + if (res?.config) { + _cfgCache = res.config; + try { localStorage.setItem('world_chips', JSON.stringify(_cfgCache)); } catch {} + return; + } + } catch {} + // Fallback: localStorage + try { _cfgCache = JSON.parse(localStorage.getItem('world_chips') || 'null') || _DEFAULT_CONFIG; } + catch { _cfgCache = _DEFAULT_CONFIG; } } + + function _getConfig() { + return _cfgCache || _DEFAULT_CONFIG; + } + function _saveConfig(cfg) { + _cfgCache = cfg; try { localStorage.setItem('world_chips', JSON.stringify(cfg)); } catch {} + if (_state?.user) { + API.put('/profile/world-config', { config: cfg }).catch(() => {}); + } } function _chipMeta(page) { return _ALL_CHIPS.find(c => c.page === page) || null; diff --git a/backend/static/sw.js b/backend/static/sw.js index 67907d2..71594d7 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-v652'; +const CACHE_VERSION = 'by-v653'; const CACHE_STATIC = `${CACHE_VERSION}-static`; const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten const CACHE_API = 'ban-yaro-api-v1'; // API-Response-Cache