Feat: Routen offline aufzeichnen — LocalStorage-Queue, Cache-Fallback, Auto-Sync (SW by-v987)
This commit is contained in:
parent
3fae57a0e2
commit
0c0daaad6b
4 changed files with 79 additions and 17 deletions
|
|
@ -3,7 +3,7 @@
|
|||
Router, State-Management, Navigation, Initialisierung.
|
||||
============================================================ */
|
||||
|
||||
const APP_VER = '986'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
|
||||
const APP_VER = '987'; // ← 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
|
||||
|
|
|
|||
|
|
@ -5,6 +5,40 @@
|
|||
|
||||
window.Page_routes = (() => {
|
||||
|
||||
const _CACHE_KEY = 'by_routes_cache';
|
||||
const _PENDING_KEY = 'by_routes_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(), user_id: null };
|
||||
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 r of [...list]) {
|
||||
try {
|
||||
const { id: _pid, _isPending, ...payload } = r;
|
||||
await API.routes.create(payload);
|
||||
_setPending(_getPending().filter(x => x.id !== r.id));
|
||||
ok++;
|
||||
} catch {}
|
||||
}
|
||||
if (ok > 0) { UI.toast.success(`${ok} Route(n) synchronisiert.`); _loadData(); }
|
||||
}
|
||||
window.addEventListener('online', _syncPending);
|
||||
|
||||
let _container = null;
|
||||
let _appState = null;
|
||||
let _data = [];
|
||||
|
|
@ -1011,7 +1045,7 @@ window.Page_routes = (() => {
|
|||
const btn = document.querySelector('[form="rk-rms-form"][type="submit"]');
|
||||
const fd = UI.formData(e.target);
|
||||
await UI.asyncButton(btn, async () => {
|
||||
const saved = await API.routes.create({
|
||||
const payload = {
|
||||
name: fd.name?.trim(),
|
||||
beschreibung: fd.beschreibung || null,
|
||||
gps_track: track,
|
||||
|
|
@ -1024,7 +1058,15 @@ window.Page_routes = (() => {
|
|||
is_public: 'is_public' in fd,
|
||||
hunde_tauglichkeit: fd.hunde_tauglichkeit || 'sehr_gut',
|
||||
client_time: API.clientNow(),
|
||||
});
|
||||
};
|
||||
if (!navigator.onLine) {
|
||||
_addPending(payload);
|
||||
UI.modal.close();
|
||||
UI.toast.success(`Route offline gespeichert — wird synchronisiert sobald Verbindung besteht.`);
|
||||
_loadData();
|
||||
return;
|
||||
}
|
||||
const saved = await API.routes.create(payload);
|
||||
UI.modal.close();
|
||||
UI.toast.success(`Route „${saved.name}" gespeichert!`);
|
||||
_loadData();
|
||||
|
|
@ -1209,20 +1251,36 @@ window.Page_routes = (() => {
|
|||
// Daten
|
||||
// ----------------------------------------------------------
|
||||
async function _loadData() {
|
||||
const _merge = (online) => {
|
||||
const pending = _getPending();
|
||||
if (pending.length) _data = [...pending, ..._data];
|
||||
if (_appState.user && _browseMode === 'mine')
|
||||
document.getElementById('rk-mine-group')?.style.setProperty('display', '');
|
||||
if (_browseMode === 'discover' && _userPos)
|
||||
document.getElementById('rk-nearby-group')?.style.setProperty('display', '');
|
||||
if (!online && pending.length)
|
||||
UI.toast.info('Offline — ' + pending.length + ' Route(n) warten auf Sync.');
|
||||
_applyFilter();
|
||||
};
|
||||
try {
|
||||
_data = await API.routes.list();
|
||||
// "Meine Routen"-Filter nur zeigen wenn eingeloggt und im Mine-Modus
|
||||
if (_appState.user && _browseMode === 'mine') {
|
||||
document.getElementById('rk-mine-group')?.style.setProperty('display', '');
|
||||
}
|
||||
// Standort-abhängiger Filter im Entdecken-Modus
|
||||
if (_browseMode === 'discover' && _userPos) {
|
||||
document.getElementById('rk-nearby-group')?.style.setProperty('display', '');
|
||||
}
|
||||
_applyFilter();
|
||||
} catch (err) {
|
||||
try { localStorage.setItem(_CACHE_KEY, JSON.stringify({ ts: Date.now(), data: _data })); } catch {}
|
||||
_merge(true);
|
||||
} catch {
|
||||
try {
|
||||
const raw = localStorage.getItem(_CACHE_KEY);
|
||||
if (raw) {
|
||||
_data = JSON.parse(raw).data || [];
|
||||
UI.toast.info('Offline — zeige zuletzt geladene Routen.');
|
||||
_merge(false);
|
||||
return;
|
||||
}
|
||||
} catch {}
|
||||
// Nur Pending-Routen zeigen wenn gar kein Cache
|
||||
_data = _getPending();
|
||||
if (_data.length) { _merge(false); return; }
|
||||
document.getElementById('rk-grid').innerHTML =
|
||||
`<p style="color:var(--c-danger);padding:var(--space-6)">Fehler: ${UI.escape(err.message)}</p>`;
|
||||
`<p style="color:var(--c-danger);padding:var(--space-6)">Offline — noch keine Routen gecacht.</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1369,10 +1427,13 @@ window.Page_routes = (() => {
|
|||
: '';
|
||||
|
||||
return `
|
||||
<div class="rk-card" data-id="${r.id}">
|
||||
<div class="rk-card" data-id="${r.id}" ${r._isPending ? 'data-pending="1"' : ''}>
|
||||
<div class="rk-card-preview">${previewContent}</div>
|
||||
<div class="rk-card-body">
|
||||
${authorLine}
|
||||
${r._isPending ? `<div style="font-size:10px;font-weight:700;color:var(--c-warning,#d97706);
|
||||
margin-bottom:3px;display:flex;align-items:center;gap:4px">
|
||||
${UI.icon('cloud-arrow-up')} Sync ausstehend</div>` : ''}
|
||||
<div class="rk-card-name">${UI.escape(r.name)}</div>
|
||||
<div style="display:flex;flex-wrap:wrap;gap:5px;margin:var(--space-2) 0">
|
||||
${dist ? _pill(dist, 'rgba(107,114,128,0.10)','#9ca3af','rgba(107,114,128,0.30)') : ''}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
Offline-Cache + Push Notifications + Tile-Cache
|
||||
============================================================ */
|
||||
|
||||
const CACHE_VERSION = 'by-v986';
|
||||
const CACHE_VERSION = 'by-v987';
|
||||
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
|
||||
|
|
@ -138,6 +138,7 @@ const _CACHEABLE_GET = [
|
|||
/^\/api\/training\/plan-progress/,
|
||||
/^\/api\/wiki\/rassen/,
|
||||
/^\/api\/dogs\/\d+\/diary\/stats/,
|
||||
/^\/api\/routes$/,
|
||||
// Drei Welten — offline-fähig
|
||||
/^\/api\/streak\/\d+/,
|
||||
/^\/api\/forum\/threads/,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue