Compare commits

..

No commits in common. "8ad3ca8a74d9903dc04df6aafc0bc3b4909076d5" and "0c0daaad6b9db979897d148b7de0870ce18f31b6" have entirely different histories.

8 changed files with 17 additions and 225 deletions

View file

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

View file

@ -3,7 +3,7 @@
Router, State-Management, Navigation, Initialisierung. Router, State-Management, Navigation, Initialisierung.
============================================================ */ ============================================================ */
const APP_VER = '988'; // ← 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 APP_VERSION = '1.6.0'; // ← semantische Version, wird bei make release gesetzt
const IS_STAGING = location.hostname === 'staging.banyaro.app'; const IS_STAGING = location.hostname === 'staging.banyaro.app';
// Cache-Bust-Parameter nach Update-Reload sofort entfernen // Cache-Bust-Parameter nach Update-Reload sofort entfernen

View file

@ -6,8 +6,6 @@
window.Page_diary = (() => { window.Page_diary = (() => {
const _CACHE_KEY = 'by_diary_cache';
// ---------------------------------------------------------- // ----------------------------------------------------------
// MODUL-STATE // MODUL-STATE
// ---------------------------------------------------------- // ----------------------------------------------------------
@ -326,7 +324,6 @@ window.Page_diary = (() => {
async function _load() { async function _load() {
const dog = _appState.activeDog; const dog = _appState.activeDog;
if (!dog) return; if (!dog) return;
const cacheKey = _CACHE_KEY + '_' + dog.id;
try { try {
const params = { limit: LIMIT, offset: _offset }; const params = { limit: LIMIT, offset: _offset };
if (_searchQuery) params.q = _searchQuery; if (_searchQuery) params.q = _searchQuery;
@ -334,10 +331,6 @@ window.Page_diary = (() => {
const batch = await API.diary.list(dog.id, params); const batch = await API.diary.list(dog.id, params);
_entries = _entries.concat(batch); _entries = _entries.concat(batch);
if (_offset === 0 && !_searchQuery && !_filterMilestone) {
try { localStorage.setItem(cacheKey, JSON.stringify({ ts: Date.now(), data: batch })); } catch {}
}
// "Mehr laden" anzeigen wenn volle Page geladen wurde // "Mehr laden" anzeigen wenn volle Page geladen wurde
const loadMore = _container.querySelector('#diary-load-more'); const loadMore = _container.querySelector('#diary-load-more');
if (loadMore) { if (loadMore) {
@ -346,17 +339,7 @@ window.Page_diary = (() => {
// Stats-Bar befüllen // Stats-Bar befüllen
_renderStatsBar(); _renderStatsBar();
} catch { } catch (err) {
try {
const raw = localStorage.getItem(cacheKey);
if (raw) {
const cached = JSON.parse(raw).data || [];
_entries = cached;
_renderStatsBar();
UI.toast.info('Offline — zeige zuletzt geladene Einträge.');
return;
}
} catch {}
UI.toast.error('Einträge konnten nicht geladen werden.'); UI.toast.error('Einträge konnten nicht geladen werden.');
} }
} }

View file

@ -5,43 +5,6 @@
window.Page_lost = (() => { 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 // MODUL-STATE
// ---------------------------------------------------------- // ----------------------------------------------------------
@ -216,14 +179,8 @@ window.Page_lost = (() => {
return; return;
} }
const pending = _getPending().map(p => ({
...p,
distanz_m: _haversine(_userPos.lat, _userPos.lon, p.lat, p.lon),
}));
try { try {
const fetched = await API.lost.list(_userPos.lat, _userPos.lon, 25); _reports = 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(); _renderMarkers();
_renderHeld(); _renderHeld();
_renderList(); _renderList();
@ -234,26 +191,6 @@ window.Page_lost = (() => {
: 'Keine vermissten Hunde in deiner Nähe (25 km Radius). 🐾'; : 'Keine vermissten Hunde in deiner Nähe (25 km Radius). 🐾';
} }
} catch { } 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.'); UI.toast.error('Meldungen konnten nicht geladen werden.');
} }
} }
@ -395,7 +332,6 @@ window.Page_lost = (() => {
Gemeldet ${_fmtDate(r.created_at)} Gemeldet ${_fmtDate(r.created_at)}
${r.melder_name ? '· ' + _escape(r.melder_name.split(' ')[0]) : ''} ${r.melder_name ? '· ' + _escape(r.melder_name.split(' ')[0]) : ''}
</div> </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)"> ${_appState.user ? `<div style="margin-top:var(--space-2)">
<button class="btn btn-ghost btn-xs lost-note-btn" <button class="btn btn-ghost btn-xs lost-note-btn"
data-lost-note-id="${r.id}" data-lost-note-id="${r.id}"
@ -696,17 +632,6 @@ window.Page_lost = (() => {
client_time : API.clientNow(), 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); const created = await API.lost.report(payload);
// Foto hochladen // Foto hochladen

View file

@ -58,8 +58,7 @@ window.Page_map = (() => {
zuechter: [], zuechter: [],
}; };
const VISIBLE_KEY = 'by_map_visible_v1'; const VISIBLE_KEY = 'by_map_visible_v1';
const _MAP_POI_KEY = 'by_map_pois_cache';
let _visible = {}; let _visible = {};
// Gespeicherten Zustand laden, Fallback: alles sichtbar // Gespeicherten Zustand laden, Fallback: alles sichtbar
@ -1218,41 +1217,9 @@ window.Page_map = (() => {
API.breeder.mapMarkers(), API.breeder.mapMarkers(),
]); ]);
const allFailed = [places, poisonList, breederList].every(r => r.status === 'rejected'); if (places.status === 'fulfilled') _addPlaces(places.value);
if (allFailed) { if (poisonList.status === 'fulfilled') _addPoison(poisonList.value);
try { if (breederList.status === 'fulfilled') _addBreeders(breederList.value);
const raw = localStorage.getItem(_MAP_POI_KEY);
if (raw) {
const cached = JSON.parse(raw);
_addPlaces(cached.places || []);
_addPoison(cached.poison || []);
_addBreeders(cached.breeders || []);
UI.toast.info('Offline — Karte zeigt gecachte Kacheln. POI-Daten eventuell veraltet.');
_scheduleOsmLoad();
return;
}
} catch {}
}
const placesVal = places.status === 'fulfilled' ? places.value : [];
const poisonVal = poisonList.status === 'fulfilled' ? poisonList.value : [];
const breederVal = breederList.status === 'fulfilled' ? breederList.value : [];
if (places.status === 'fulfilled') _addPlaces(placesVal);
if (poisonList.status === 'fulfilled') _addPoison(poisonVal);
if (breederList.status === 'fulfilled') _addBreeders(breederVal);
if (places.status === 'fulfilled' || poisonList.status === 'fulfilled' || breederList.status === 'fulfilled') {
try {
localStorage.setItem(_MAP_POI_KEY, JSON.stringify({
ts: Date.now(),
places: placesVal,
poison: poisonVal,
breeders: breederVal,
}));
} catch {}
}
_scheduleOsmLoad(); _scheduleOsmLoad();
} }

View file

@ -5,8 +5,6 @@
window.Page_poison = (() => { window.Page_poison = (() => {
const _CACHE_KEY = 'by_poison_cache';
// ---------------------------------------------------------- // ----------------------------------------------------------
// MODUL-STATE // MODUL-STATE
// ---------------------------------------------------------- // ----------------------------------------------------------
@ -173,7 +171,6 @@ window.Page_poison = (() => {
try { try {
_reports = await API.poison.listNearby(_userPos.lat, _userPos.lon, 10000); _reports = await API.poison.listNearby(_userPos.lat, _userPos.lon, 10000);
try { localStorage.setItem(_CACHE_KEY, JSON.stringify({ ts: Date.now(), data: _reports })); } catch {}
_renderMarkers(); _renderMarkers();
_renderList(); _renderList();
_updateBadge(_reports.length); _updateBadge(_reports.length);
@ -183,18 +180,6 @@ window.Page_poison = (() => {
: 'Keine aktiven Giftköder-Meldungen in deiner Nähe (10 km Radius).'; : 'Keine aktiven Giftköder-Meldungen in deiner Nähe (10 km Radius).';
} }
} catch { } catch {
try {
const raw = localStorage.getItem(_CACHE_KEY);
if (raw) {
_reports = JSON.parse(raw).data || [];
_renderMarkers();
_renderList();
_updateBadge(_reports.length);
if (infoEl) infoEl.textContent = `${_reports.length} gecachte Meldung${_reports.length !== 1 ? 'en' : ''} (Offline)`;
UI.toast.info('Offline — zeige zuletzt geladene Daten.');
return;
}
} catch {}
UI.toast.error('Meldungen konnten nicht geladen werden.'); UI.toast.error('Meldungen konnten nicht geladen werden.');
} }
} }

View file

@ -5,45 +5,6 @@
window.Page_walks = (() => { window.Page_walks = (() => {
// ----------------------------------------------------------
// OFFLINE-CACHE
// ----------------------------------------------------------
const _CACHE_KEY = 'by_walks_cache';
const _PENDING_KEY = 'by_walks_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(),
teilnehmer_count: 1, max_teilnehmer: data.max_teilnehmer || 10,
status: 'open' };
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, created_at: _ca, teilnehmer_count: _tc, status: _st, ...payload } = item;
await API.walks.create(payload);
_setPending(_getPending().filter(x => x.id !== item.id));
ok++;
} catch {}
}
if (ok > 0) { UI.toast.success(`${ok} Treffen synchronisiert.`); _loadData(); }
}
window.addEventListener('online', _syncPending);
let _container = null; let _container = null;
let _appState = null; let _appState = null;
let _data = []; let _data = [];
@ -234,16 +195,14 @@ window.Page_walks = (() => {
// Daten laden // Daten laden
// ---------------------------------------------------------- // ----------------------------------------------------------
async function _loadData() { async function _loadData() {
const pending = _getPending();
try { try {
const fetched = await API.walks.list( _data = await API.walks.list(
_userPos?.lat ?? null, _userPos?.lat ?? null,
_userPos?.lon ?? null _userPos?.lon ?? null
); );
try { localStorage.setItem(_CACHE_KEY, JSON.stringify({ ts: Date.now(), data: fetched })); } catch {}
_data = [...pending, ...fetched];
_renderList(); _renderList();
_renderMarkers(); _renderMarkers();
// Desktop: Karte direkt initialisieren (beide Panels sichtbar)
if (window.innerWidth >= 1024) { if (window.innerWidth >= 1024) {
UI.loadLeaflet().then(() => { UI.loadLeaflet().then(() => {
_initMap(); _initMap();
@ -251,20 +210,8 @@ window.Page_walks = (() => {
setTimeout(() => _map?.invalidateSize(), 400); setTimeout(() => _map?.invalidateSize(), 400);
}); });
} }
} catch { } catch (err) {
try { UI.toast.error(err.message || 'Fehler beim Laden.');
const raw = localStorage.getItem(_CACHE_KEY);
if (raw) {
_data = [...pending, ...(JSON.parse(raw).data || [])];
_renderList();
_renderMarkers();
UI.toast.info('Offline — zeige zuletzt geladene Treffen.');
return;
}
} catch {}
_data = pending;
if (pending.length) { _renderList(); _renderMarkers(); return; }
UI.toast.error('Treffen konnten nicht geladen werden.');
} }
} }
@ -344,7 +291,6 @@ window.Page_walks = (() => {
</span> </span>
<span class="walks-badge">${UI.icon('paw-print')} ${w.teilnehmer_count}/${w.max_teilnehmer}</span> <span class="walks-badge">${UI.icon('paw-print')} ${w.teilnehmer_count}/${w.max_teilnehmer}</span>
${isOwn ? '<span class="walks-badge walks-badge--own">Mein Treffen</span>' : ''} ${isOwn ? '<span class="walks-badge walks-badge--own">Mein Treffen</span>' : ''}
${w._isPending ? `<span style="font-size:10px;color:var(--c-warning,#d97706);font-weight:600">⏳ Sync ausstehend</span>` : ''}
</div> </div>
</div> </div>
<div style="display:flex;flex-direction:column;align-items:flex-end;gap:var(--space-1)"> <div style="display:flex;flex-direction:column;align-items:flex-end;gap:var(--space-1)">
@ -1182,24 +1128,15 @@ window.Page_walks = (() => {
const idx = _data.findIndex(w => w.id === walk.id); const idx = _data.findIndex(w => w.id === walk.id);
if (idx !== -1) _data[idx] = { ..._data[idx], ...updated }; if (idx !== -1) _data[idx] = { ..._data[idx], ...updated };
UI.toast.success('Treffen aktualisiert.'); UI.toast.success('Treffen aktualisiert.');
UI.modal.close();
_renderList();
_renderMarkers();
} else { } else {
if (!navigator.onLine) {
_addPending(payload);
UI.modal.close();
UI.toast.success('Offline gespeichert — wird synchronisiert sobald Verbindung besteht.');
_loadData();
return;
}
const created = await API.walks.create(payload); const created = await API.walks.create(payload);
_data.unshift({ ...created, teilnehmer_count: 0 }); _data.unshift({ ...created, teilnehmer_count: 0 });
UI.toast.success('Treffen geplant! 🎉'); UI.toast.success('Treffen geplant! 🎉');
UI.modal.close();
_renderList();
_renderMarkers();
} }
UI.modal.close();
_renderList();
_renderMarkers();
}); });
}); });
} }

View file

@ -3,7 +3,7 @@
Offline-Cache + Push Notifications + Tile-Cache Offline-Cache + Push Notifications + Tile-Cache
============================================================ */ ============================================================ */
const CACHE_VERSION = 'by-v988'; const CACHE_VERSION = 'by-v987';
const CACHE_STATIC = `${CACHE_VERSION}-static`; const CACHE_STATIC = `${CACHE_VERSION}-static`;
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten
const CACHE_API = 'ban-yaro-api-v1'; // API-Response-Cache const CACHE_API = 'ban-yaro-api-v1'; // API-Response-Cache
@ -119,8 +119,6 @@ const _QUEUEABLE = [
{ re: /^\/api\/training\/sessions$/, methods: ['POST'] }, { re: /^\/api\/training\/sessions$/, methods: ['POST'] },
{ re: /^\/api\/training\/progress$/, methods: ['POST'] }, { re: /^\/api\/training\/progress$/, methods: ['POST'] },
{ re: /^\/api\/poison$/, methods: ['POST'] }, { re: /^\/api\/poison$/, methods: ['POST'] },
{ re: /^\/api\/lost\/report$/, methods: ['POST'] },
{ re: /^\/api\/walks$/, methods: ['POST'] },
]; ];
function _isQueueable(pathname, method) { function _isQueueable(pathname, method) {
return _QUEUEABLE.some(q => q.methods.includes(method) && q.re.test(pathname)); return _QUEUEABLE.some(q => q.methods.includes(method) && q.re.test(pathname));
@ -141,9 +139,6 @@ const _CACHEABLE_GET = [
/^\/api\/wiki\/rassen/, /^\/api\/wiki\/rassen/,
/^\/api\/dogs\/\d+\/diary\/stats/, /^\/api\/dogs\/\d+\/diary\/stats/,
/^\/api\/routes$/, /^\/api\/routes$/,
/^\/api\/places$/,
/^\/api\/breeder\/map-markers$/,
/^\/api\/gassi-zeiten/,
// Drei Welten — offline-fähig // Drei Welten — offline-fähig
/^\/api\/streak\/\d+/, /^\/api\/streak\/\d+/,
/^\/api\/forum\/threads/, /^\/api\/forum\/threads/,