Offline-Fallbacks für diary, poison, map + SW-Erweiterung

- sw.js: /api/places, /api/breeder/map-markers, /api/gassi-zeiten in _CACHEABLE_GET; /api/lost/report und /api/walks in _QUEUEABLE
- diary.js: localStorage-Cache pro Hund, Fallback bei Offline mit Toast
- poison.js: localStorage-Cache, Fallback bei Offline mit Toast (sicherheitsrelevant)
- map.js: POI-Cache (places/poison/breeders) in localStorage, Offline-Toast + Fallback auf gecachte Daten
This commit is contained in:
rene 2026-05-15 17:02:26 +02:00
parent 0c0daaad6b
commit 53fcb61933
6 changed files with 222 additions and 14 deletions

View file

@ -5,6 +5,43 @@
window.Page_lost = (() => {
// ----------------------------------------------------------
// OFFLINE-CACHE
// ----------------------------------------------------------
const _CACHE_KEY = 'by_lost_cache';
const _PENDING_KEY = 'by_lost_pending';
function _getPending() {
try { return JSON.parse(localStorage.getItem(_PENDING_KEY) || '[]'); } catch { return []; }
}
function _setPending(list) {
try { localStorage.setItem(_PENDING_KEY, JSON.stringify(list)); } catch {}
}
function _addPending(data) {
const list = _getPending();
const entry = { ...data, id: `pending_${Date.now()}`, _isPending: true,
created_at: new Date().toISOString() };
list.push(entry);
_setPending(list);
return entry;
}
async function _syncPending() {
if (!navigator.onLine) return;
const list = _getPending();
if (!list.length) return;
let ok = 0;
for (const item of [...list]) {
try {
const { id: _pid, _isPending, ...payload } = item;
await API.lost.report(payload);
_setPending(_getPending().filter(x => x.id !== item.id));
ok++;
} catch {}
}
if (ok > 0) { UI.toast.success(`${ok} Meldung(en) synchronisiert.`); _loadReports(); }
}
window.addEventListener('online', _syncPending);
// ----------------------------------------------------------
// MODUL-STATE
// ----------------------------------------------------------
@ -179,8 +216,14 @@ window.Page_lost = (() => {
return;
}
const pending = _getPending().map(p => ({
...p,
distanz_m: _haversine(_userPos.lat, _userPos.lon, p.lat, p.lon),
}));
try {
_reports = await API.lost.list(_userPos.lat, _userPos.lon, 25);
const fetched = await API.lost.list(_userPos.lat, _userPos.lon, 25);
try { localStorage.setItem(_CACHE_KEY, JSON.stringify({ ts: Date.now(), data: fetched })); } catch {}
_reports = [...pending, ...fetched];
_renderMarkers();
_renderHeld();
_renderList();
@ -191,6 +234,26 @@ window.Page_lost = (() => {
: 'Keine vermissten Hunde in deiner Nähe (25 km Radius). 🐾';
}
} catch {
try {
const raw = localStorage.getItem(_CACHE_KEY);
if (raw) {
_reports = [...pending, ...(JSON.parse(raw).data || [])];
_renderMarkers();
_renderHeld();
_renderList();
_updateBadge(_reports.length);
if (infoEl) infoEl.textContent = 'Offline — zeige zuletzt geladene Meldungen.';
return;
}
} catch {}
_reports = pending;
if (pending.length) {
_renderMarkers();
_renderHeld();
_renderList();
_updateBadge(_reports.length);
return;
}
UI.toast.error('Meldungen konnten nicht geladen werden.');
}
}
@ -332,6 +395,7 @@ window.Page_lost = (() => {
Gemeldet ${_fmtDate(r.created_at)}
${r.melder_name ? '· ' + _escape(r.melder_name.split(' ')[0]) : ''}
</div>
${r._isPending ? `<div style="font-size:10px;color:var(--c-warning,#d97706);font-weight:600">⏳ Sync ausstehend</div>` : ''}
${_appState.user ? `<div style="margin-top:var(--space-2)">
<button class="btn btn-ghost btn-xs lost-note-btn"
data-lost-note-id="${r.id}"
@ -632,6 +696,17 @@ 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;
}
const created = await API.lost.report(payload);
// Foto hochladen