Sprint 10: OSM-POI-Cache, Karten-Clustering, Routen-Redesign

Karte (map.js):
- OSM Overpass API: Restaurants, Tierärzte, Parkplätze, Bänke, Wasserstellen
- Leaflet.markercluster für alle OSM-Layer
- Standort-Dot mit GPS-Genauigkeitskreis, Wake-Lock bei Aufzeichnung
- Community-Pins setzen/löschen, Meldungen, Crosshair-Placement
- Layer-Sichtbarkeit in localStorage (by_map_visible_v1)

Routen (routes.js + routen.py):
- Komoot-Stil: SVG-Track-Preview, Foto-Upload, Nearby-POIs im Detail-Modal
- Neue Felder: is_public, hunde_tauglichkeit, foto_urls
- Rate-Endpoint (POST /api/routes/{id}/rate)
- Foto-Upload (POST /api/routes/{id}/photo)
- Fix: json_extract $[-1] → $[#-1] (SQLite-kompatibler Pfad für letztes Element)

Backend (osm.py, database.py, scheduler.py):
- /api/osm/pois: OSM-Overpass-Cache mit Tile-Logik (14 Tage TTL)
- /api/osm/user-poi: Community-Marker CRUD
- /api/osm/report: Marker als ungültig melden
- Neue Tabellen: osm_pois, osm_tiles, user_map_pois, osm_reports
- Giftköder-Archiv-Job (täglich 03:00, soft-delete nach Ablauf)
- Giftköder-Archiv-Job als APScheduler-CronJob

UI: Orte-Menüpunkt entfernt (in Karte integriert), APP_VER auf 62
This commit is contained in:
rene 2026-04-15 16:30:10 +02:00
parent bf26e5faf4
commit ebe4ce20cf
16 changed files with 3020 additions and 737 deletions

View file

@ -3,6 +3,8 @@
Router, State-Management, Navigation, Initialisierung.
============================================================ */
const APP_VER = '62'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const App = (() => {
// ----------------------------------------------------------
@ -26,8 +28,7 @@ const App = (() => {
'dog-profile': { title: 'Mein Hund', module: null },
map: { title: 'Karte', module: null },
routes: { title: 'Routen', module: null },
places: { title: 'Orte', module: null },
events: { title: 'Events', module: null },
events: { title: 'Events', module: null },
poison: { title: 'Giftköder-Alarm', module: null },
walks: { title: 'Gassi-Treffen', module: null },
sitting: { title: 'Sitting', module: null },
@ -119,10 +120,12 @@ const App = (() => {
}
function _loadScript(src) {
// Versionierter URL damit SW/Browser-Cache bei jedem Deploy invalidiert wird
const versioned = `${src}?v=${APP_VER}`;
return new Promise((resolve, reject) => {
if (document.querySelector(`script[src="${src}"]`)) { resolve(); return; }
if (document.querySelector(`script[src="${versioned}"]`)) { resolve(); return; }
const s = document.createElement('script');
s.src = src;
s.src = versioned;
s.onload = resolve;
s.onerror = reject;
document.head.appendChild(s);
@ -193,11 +196,7 @@ const App = (() => {
}
function _openSidebar() {
const s = document.getElementById('sidebar');
if (!s) { UI.toast.error('sidebar: NULL'); return; }
s.classList.add('open');
const cs = getComputedStyle(s);
UI.toast.info(`display:${cs.display} | transform:${cs.transform} | z:${cs.zIndex}`);
document.getElementById('sidebar')?.classList.add('open');
document.getElementById('sidebar-backdrop')?.classList.add('visible');
}
@ -223,10 +222,7 @@ const App = (() => {
<button class="btn btn-danger w-full" data-quick="poison">
Giftköder melden
</button>
<button class="btn btn-secondary w-full" data-quick="place">
📍 Ort hinzufügen
</button>
<button class="btn btn-nature w-full" data-quick="walk">
<button class="btn btn-nature w-full" data-quick="walk">
🦮 Gassi-Treffen erstellen
</button>
</div>
@ -246,8 +242,7 @@ const App = (() => {
if (action === 'diary') { navigate('diary'); pages['diary'].module?.openNew?.(); }
if (action === 'health') { navigate('health'); pages['health'].module?.openNew?.(); }
if (action === 'poison') { navigate('poison'); pages['poison'].module?.openNew?.(); }
if (action === 'place') { navigate('places'); pages['places'].module?.openNew?.(); }
if (action === 'walk') { navigate('walks'); pages['walks'].module?.openNew?.(); }
if (action === 'walk') { navigate('walks'); pages['walks'].module?.openNew?.(); }
}, 350);
}, { once: true });
}