diff --git a/backend/main.py b/backend/main.py index ccf969b..212bcbf 100644 --- a/backend/main.py +++ b/backend/main.py @@ -410,7 +410,7 @@ async def serve_media(path: str, request: _Request): raise _HE(404, "Nicht gefunden.") return _media_response(filepath) -APP_VER = "991" # muss mit APP_VER in app.js übereinstimmen +APP_VER = "989" # muss mit APP_VER in app.js übereinstimmen @app.get("/.well-known/assetlinks.json") async def assetlinks(): diff --git a/backend/static/js/app.js b/backend/static/js/app.js index 7fdd413..4e8b66a 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 = '991'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen +const APP_VER = '989'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen const APP_VERSION = '1.6.0'; // ← semantische Version, wird bei make release gesetzt const IS_STAGING = location.hostname === 'staging.banyaro.app'; // Cache-Bust-Parameter nach Update-Reload sofort entfernen diff --git a/backend/static/js/pages/lost.js b/backend/static/js/pages/lost.js index 6f8fe0c..37daa9f 100644 --- a/backend/static/js/pages/lost.js +++ b/backend/static/js/pages/lost.js @@ -45,32 +45,14 @@ window.Page_lost = (() => { // ---------------------------------------------------------- // MODUL-STATE // ---------------------------------------------------------- - let _container = null; - let _appState = null; - let _map = null; - let _markers = []; - let _userMarker = null; - let _reports = []; - let _userPos = null; - let _leafletLoaded = false; - let _stylesInjected = false; - - function _injectStyles() { - if (_stylesInjected) return; - _stylesInjected = true; - const s = document.createElement('style'); - s.textContent = ` - @keyframes by-lost-pulse-r { - 0%,100% { box-shadow: 0 0 0 0 rgba(231,76,60,.55), 0 2px 6px rgba(0,0,0,.3); } - 50% { box-shadow: 0 0 0 11px rgba(231,76,60,0), 0 2px 6px rgba(0,0,0,.3); } - } - @keyframes by-lost-pulse-p { - 0%,100% { box-shadow: 0 0 0 0 rgba(217,119,6,.55), 0 2px 6px rgba(0,0,0,.3); } - 50% { box-shadow: 0 0 0 11px rgba(217,119,6,0), 0 2px 6px rgba(0,0,0,.3); } - } - `; - document.head.appendChild(s); - } + let _container = null; + let _appState = null; + let _map = null; + let _markers = []; + let _userMarker = null; + let _reports = []; + let _userPos = null; + let _leafletLoaded = false; // ---------------------------------------------------------- // INIT @@ -168,7 +150,6 @@ window.Page_lost = (() => { // KARTE INITIALISIEREN // ---------------------------------------------------------- function _initMap() { - _injectStyles(); const mapEl = document.getElementById('lost-map'); if (!mapEl || !window.L || _map) return; @@ -235,23 +216,13 @@ window.Page_lost = (() => { return; } + const pending = _getPending().map(p => ({ + ...p, + distanz_m: _haversine(_userPos.lat, _userPos.lon, p.lat, p.lon), + })); try { const fetched = await API.lost.list(_userPos.lat, _userPos.lon, 25); try { localStorage.setItem(_CACHE_KEY, JSON.stringify({ ts: Date.now(), data: fetched })); } catch {} - - // Remove pending items already on the server (race: sync completed during fetch) - const rawPending = _getPending(); - const dedupedPending = rawPending.filter(p => - !fetched.some(f => f.name === p.name && - Math.abs(f.lat - p.lat) < 0.0001 && - Math.abs(f.lon - p.lon) < 0.0001) - ); - if (dedupedPending.length < rawPending.length) _setPending(dedupedPending); - - const pending = dedupedPending.map(p => ({ - ...p, - distanz_m: _haversine(_userPos.lat, _userPos.lon, p.lat, p.lon), - })); _reports = [...pending, ...fetched]; _renderMarkers(); _renderHeld(); @@ -263,15 +234,10 @@ window.Page_lost = (() => { : 'Keine vermissten Hunde in deiner Nähe (25 km Radius). 🐾'; } } catch { - const offline_pending = _getPending().map(p => ({ - ...p, - distanz_m: _haversine(_userPos.lat, _userPos.lon, p.lat, p.lon), - })); try { const raw = localStorage.getItem(_CACHE_KEY); if (raw) { - const cached = JSON.parse(raw).data || []; - _reports = [...offline_pending, ...cached]; + _reports = [...pending, ...(JSON.parse(raw).data || [])]; _renderMarkers(); _renderHeld(); _renderList(); @@ -280,8 +246,8 @@ window.Page_lost = (() => { return; } } catch {} - _reports = offline_pending; - if (offline_pending.length) { + _reports = pending; + if (pending.length) { _renderMarkers(); _renderHeld(); _renderList(); @@ -301,21 +267,20 @@ window.Page_lost = (() => { _markers = []; _reports.forEach(r => { - const dotColor = r._isPending ? '#d97706' : '#e74c3c'; - const anim = r._isPending ? 'by-lost-pulse-p' : 'by-lost-pulse-r'; const icon = L.divIcon({ className : '', - html : `
🐕
`, + html : `
🐕
`, iconSize : [34, 34], iconAnchor : [17, 17], }); const distStr = r.distanz_m !== undefined - ? (r.distanz_m < 1000 ? `${Math.round(r.distanz_m)} m` : `${(r.distanz_m / 1000).toFixed(1)} km`) + ? (r.distanz_m < 1000 ? `${r.distanz_m} m` : `${(r.distanz_m / 1000).toFixed(1)} km`) : ''; const marker = L.marker([r.lat, r.lon], { icon }) @@ -324,11 +289,10 @@ window.Page_lost = (() => { 🔍 ${_escape(r.name)}
${r.rasse ? _escape(r.rasse) + '
' : ''} ${distStr ? `📍 ${distStr} entfernt
` : ''} - ${r._isPending ? '⏳ Sync ausstehend
' : ''} 📅 ${_fmtDate(r.created_at)} `); - if (!r._isPending) marker.on('click', () => _openDetail(r)); + marker.on('click', () => _openDetail(r)); _markers.push(marker); }); } @@ -370,19 +334,10 @@ window.Page_lost = (() => { listEl.innerHTML = _reports.map(r => _reportCard(r)).join(''); listEl.querySelectorAll('[data-lost-id]').forEach(card => { card.addEventListener('click', () => { - const id = card.dataset.lostId; - const r = _reports.find(x => String(x.id) === id && !x._isPending); + const r = _reports.find(x => x.id === parseInt(card.dataset.lostId)); if (r) _openDetail(r); }); }); - listEl.querySelectorAll('.lost-discard-btn').forEach(btn => { - btn.addEventListener('click', e => { - e.stopPropagation(); - const pid = btn.dataset.pendingId; - _setPending(_getPending().filter(x => x.id !== pid)); - _loadReports(); - }); - }); listEl.querySelectorAll('.lost-note-btn').forEach(btn => { btn.addEventListener('click', e => { e.stopPropagation(); @@ -440,24 +395,15 @@ window.Page_lost = (() => { Gemeldet ${_fmtDate(r.created_at)} ${r.melder_name ? '· ' + _escape(r.melder_name.split(' ')[0]) : ''} - ${r._isPending - ? `
- ⏳ Sync ausstehend - -
` - : (_appState.user ? `
- -
` : '')} + ${r._isPending ? `
⏳ Sync ausstehend
` : ''} + ${_appState.user ? `
+ +
` : ''} @@ -468,7 +414,6 @@ window.Page_lost = (() => { // DETAIL-MODAL // ---------------------------------------------------------- function _openDetail(r) { - if (r._isPending) return; // Pending-Einträge haben keine Server-ID const isOwn = _appState.user && _appState.user.id === r.user_id; const isAdmin = _appState.user?.rolle === 'admin'; const distStr = r.distanz_m !== undefined @@ -751,24 +696,19 @@ window.Page_lost = (() => { client_time : API.clientNow(), }; - let created; - try { - created = await API.lost.report(payload); - } catch (netErr) { - // Netzwerkfehler (TypeError = fetch failed) → offline speichern - if (netErr instanceof TypeError || !navigator.onLine) { - const pending = _addPending(payload); - pending.distanz_m = _userPos - ? Math.round(_haversine(_userPos.lat, _userPos.lon, pending.lat, pending.lon)) - : 0; - UI.modal.close(); - UI.toast.success('Offline gespeichert — wird synchronisiert sobald Verbindung besteht.'); - _loadReports(); - return; - } - throw netErr; // API-Fehler (z.B. 422) → weitergeben + if (!navigator.onLine) { + const pending = _addPending(payload); + pending.distanz_m = _userPos + ? Math.round(_haversine(_userPos.lat, _userPos.lon, pending.lat, pending.lon)) + : 0; + UI.modal.close(); + UI.toast.success('Offline gespeichert — wird synchronisiert sobald Verbindung besteht.'); + _loadReports(); + return; } + const created = await API.lost.report(payload); + // Foto hochladen if (photoInput?.files[0]) { try { diff --git a/backend/static/js/worlds.js b/backend/static/js/worlds.js index 35cb433..e343655 100644 --- a/backend/static/js/worlds.js +++ b/backend/static/js/worlds.js @@ -1691,7 +1691,7 @@ window.Worlds = (() => { const pos = await API.getLocation({ timeout: 4000, maximumAge: 600_000 }); const [p, l] = await Promise.allSettled([ API.get(`/poison/nearby?lat=${pos.lat}&lon=${pos.lon}&radius=5`).catch(() => []), - API.get(`/lost/nearby?lat=${pos.lat}&lon=${pos.lon}&radius=20`).catch(() => []), + API.get(`/lost/nearby?lat=${pos.lat}&lon=${pos.lon}&radius=5`).catch(() => []), ]); if (p.value?.length) out.push({ icon:'skull', color:'#EF4444', title:'Giftköder in der Nähe', sub:`${p.value.length} Meldung${p.value.length>1?'en':''}`, page:'poison' }); if (l.value?.length) out.push({ icon:'dog', color:'#3B82F6', title:'Verlorener Hund', sub:`${l.value.length} Meldung${l.value.length>1?'en':''}`, page:'lost' }); diff --git a/backend/static/sw.js b/backend/static/sw.js index d451c5b..e654eab 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-v991'; +const CACHE_VERSION = 'by-v989'; 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