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 @@
@@ -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