diff --git a/backend/main.py b/backend/main.py index 212bcbf..bc3f106 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 = "989" # muss mit APP_VER in app.js übereinstimmen +APP_VER = "990" # 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 4e8b66a..4cd3b61 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 = '989'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen +const APP_VER = '990'; // ← 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 37daa9f..e4be5d8 100644 --- a/backend/static/js/pages/lost.js +++ b/backend/static/js/pages/lost.js @@ -45,14 +45,29 @@ 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 _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-ping { + 0% { transform: scale(0.9); opacity: 0.7; } + 70% { transform: scale(2.2); opacity: 0; } + 100% { transform: scale(2.2); opacity: 0; } + } + `; + document.head.appendChild(s); + } // ---------------------------------------------------------- // INIT @@ -216,13 +231,23 @@ 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(); @@ -234,10 +259,15 @@ 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) { - _reports = [...pending, ...(JSON.parse(raw).data || [])]; + const cached = JSON.parse(raw).data || []; + _reports = [...offline_pending, ...cached]; _renderMarkers(); _renderHeld(); _renderList(); @@ -246,8 +276,8 @@ window.Page_lost = (() => { return; } } catch {} - _reports = pending; - if (pending.length) { + _reports = offline_pending; + if (offline_pending.length) { _renderMarkers(); _renderHeld(); _renderList(); @@ -263,24 +293,33 @@ window.Page_lost = (() => { // ---------------------------------------------------------- function _renderMarkers() { if (!_map || !window.L) return; + _injectStyles(); _markers.forEach(m => _map.removeLayer(m)); _markers = []; _reports.forEach(r => { + const dotColor = r._isPending ? '#d97706' : '#e74c3c'; + const ringColor = r._isPending ? 'rgba(217,119,6,0.35)' : 'rgba(231,76,60,0.35)'; const icon = L.divIcon({ className : '', - html : `