Fix: Lost — Puls-Animation (box-shadow), false-offline, Pending-Guard

- Pulsierender Marker: Wechsel von position:absolute-Ring auf box-shadow-Animation
  (by-lost-pulse-r/p), kein Overflow-Problem mit Leaflet divIcon, iOS-kompatibel
- navigator.onLine iOS-Falsch-Positiv: Formular-Submit versucht API zuerst,
  fällt nur bei TypeError (fetch failed) auf Pending-Modus zurück
- _openDetail(): früher Return für Pending-Einträge (verhindert delete mit
  string-ID "pending_..." → Backend-Fehler "unable to parse integer")
- SW by-v991, APP_VER 991
This commit is contained in:
rene 2026-05-15 17:44:59 +02:00
parent be12550df1
commit f2856b8acb
4 changed files with 35 additions and 33 deletions

View file

@ -410,7 +410,7 @@ async def serve_media(path: str, request: _Request):
raise _HE(404, "Nicht gefunden.")
return _media_response(filepath)
APP_VER = "990" # muss mit APP_VER in app.js übereinstimmen
APP_VER = "991" # muss mit APP_VER in app.js übereinstimmen
@app.get("/.well-known/assetlinks.json")
async def assetlinks():

View file

@ -3,7 +3,7 @@
Router, State-Management, Navigation, Initialisierung.
============================================================ */
const APP_VER = '990'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const APP_VER = '991'; // ← 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

View file

@ -60,10 +60,13 @@ window.Page_lost = (() => {
_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; }
@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);
@ -165,6 +168,7 @@ window.Page_lost = (() => {
// KARTE INITIALISIEREN
// ----------------------------------------------------------
function _initMap() {
_injectStyles();
const mapEl = document.getElementById('lost-map');
if (!mapEl || !window.L || _map) return;
@ -293,29 +297,21 @@ 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 dotColor = r._isPending ? '#d97706' : '#e74c3c';
const anim = r._isPending ? 'by-lost-pulse-p' : 'by-lost-pulse-r';
const icon = L.divIcon({
className : '',
html : `
<div style="position:relative;width:44px;height:44px">
<div style="position:absolute;inset:0;border-radius:50%;
background:${ringColor};
animation:by-lost-ping 1.5s ease-out infinite"></div>
<div style="position:absolute;top:5px;left:5px;
html : `<div style="background:${dotColor};color:#fff;border-radius:50%;
width:34px;height:34px;
background:${dotColor};color:#fff;border-radius:50%;
display:flex;align-items:center;justify-content:center;
font-size:17px;box-shadow:0 2px 6px rgba(0,0,0,.35);
border:2px solid #fff">🐕</div>
</div>`,
iconSize : [44, 44],
iconAnchor : [22, 22],
font-size:17px;border:2px solid #fff;
animation:${anim} 1.8s ease-in-out infinite">🐕</div>`,
iconSize : [34, 34],
iconAnchor : [17, 17],
});
const distStr = r.distanz_m !== undefined
@ -472,6 +468,7 @@ 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
@ -754,19 +751,24 @@ window.Page_lost = (() => {
client_time : API.clientNow(),
};
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;
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
}
const created = await API.lost.report(payload);
// Foto hochladen
if (photoInput?.files[0]) {
try {

View file

@ -3,7 +3,7 @@
Offline-Cache + Push Notifications + Tile-Cache
============================================================ */
const CACHE_VERSION = 'by-v990';
const CACHE_VERSION = 'by-v991';
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