Feature: Offline-Stufen 1+2+3 — Timeout, API-Cache, Write-Queue (IndexedDB + BackgroundSync) — SW by-v509, APP_VER 486

This commit is contained in:
rene 2026-04-29 19:13:04 +02:00
parent ad3b73d687
commit 2411151b17
4 changed files with 268 additions and 31 deletions

View file

@ -95,7 +95,18 @@
<body>
<!-- Offline-Banner -->
<div id="offline-banner">Kein Internet — du bist offline</div>
<div id="offline-banner" aria-live="polite"
style="display:none;position:fixed;top:0;left:0;right:0;z-index:9999;
background:#1f2937;color:#f3f4f6;font-size:0.78rem;font-weight:500;
padding:7px 16px;align-items:center;justify-content:center;gap:8px;
box-shadow:0 2px 8px rgba(0,0,0,.3)">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" viewBox="0 0 256 256">
<path d="M213.92,210.62l-160-176A8,8,0,1,0,42.08,45.38L81.06,88.86A152.34,152.34,0,0,0,26.49,130a8,8,0,0,0,11,11.61,136.36,136.36,0,0,1,52-37.29l19.2,21.12A96.09,96.09,0,0,0,67.6,160.59,8,8,0,1,0,79,172.2a80.12,80.12,0,0,1,33.5-23.89L128,165.37V224a8,8,0,0,0,16,0V183.94l69.92,76.92a8,8,0,1,0,11.84-10.76ZM128,141.46,108.42,120A80.38,80.38,0,0,1,128,116a79.91,79.91,0,0,1,19.59,2.43l-19.59,23Zm0-85.46a167.9,167.9,0,0,1,101.51,34.17,8,8,0,1,0,9.72-12.72A183.82,183.82,0,0,0,128,40a183.5,183.5,0,0,0-48.55,6.55L95,64.18A168.23,168.23,0,0,1,128,56Zm57.09,72.41a8,8,0,0,0,11.22-1.36,8,8,0,0,0-1.36-11.22,136.72,136.72,0,0,0-31.62-18.23L178,114.26A120.52,120.52,0,0,1,185.09,128.41Z"/>
</svg>
<span id="offline-banner-text">Offline — Änderungen werden gespeichert und synchronisiert sobald Verbindung besteht</span>
<span id="offline-queue-badge" style="display:none;background:rgba(239,68,68,.8);
border-radius:999px;padding:1px 7px;font-size:11px;font-weight:700"></span>
</div>
<!-- Backdrop + Sidebar direkt im body (kein Ancestor-Stacking-Context) -->
<div id="sidebar-backdrop" class="sidebar-backdrop"></div>
@ -456,30 +467,33 @@
<!-- Offline-Banner Logik -->
<script>
(function() {
var _wasOffline = false;
var banner = document.getElementById('offline-banner');
function setOffline() {
_wasOffline = true;
if (banner) banner.style.display = 'block';
function _updateBanner() {
var banner = document.getElementById('offline-banner');
if (!banner) return;
banner.style.display = navigator.onLine ? 'none' : 'flex';
}
function setOnline() {
if (banner) banner.style.display = 'none';
if (_wasOffline) {
_wasOffline = false;
// UI.toast ist verfügbar sobald ui.js geladen ist
if (window.UI && UI.toast) {
UI.toast.success('Wieder online');
}
window.addEventListener('offline', function() {
_updateBanner();
// Queue-Count abfragen
if (navigator.serviceWorker) {
navigator.serviceWorker.ready.then(function(reg) {
if (reg.active) reg.active.postMessage({ type: 'QUEUE_COUNT' });
});
}
}
window.addEventListener('offline', setOffline);
window.addEventListener('online', setOnline);
// Initialzustand prüfen
if (!navigator.onLine) setOffline();
});
window.addEventListener('online', function() {
_updateBanner();
var badge = document.getElementById('offline-queue-badge');
if (badge) badge.style.display = 'none';
// Queue abarbeiten
if (navigator.serviceWorker) {
navigator.serviceWorker.ready.then(function(reg) {
if (reg.active) reg.active.postMessage({ type: 'PROCESS_QUEUE' });
});
}
});
// Initial prüfen
_updateBanner();
})();
</script>
@ -508,6 +522,35 @@
});
navigator.serviceWorker.addEventListener('message', e => {
if (e.data?.type === 'QUEUE_PROCESSED') {
const { synced, failed, total } = e.data;
if (total === 0) return;
if (synced > 0 && window.UI?.toast) {
window.UI.toast.success(
synced === 1
? '1 offline gespeicherter Eintrag synchronisiert'
: `${synced} offline gespeicherte Einträge synchronisiert`
);
// Aktuelle Seite neu laden
window.App?.state && window.pages?.[window.App.state.page]?.module?.refresh?.();
}
if (failed > 0 && window.UI?.toast) {
window.UI.toast.warning(`${failed} Eintrag${failed > 1 ? 'e' : ''} noch nicht synchronisiert — kein Netz`);
}
return;
}
if (e.data?.type === 'QUEUE_COUNT') {
const badge = document.getElementById('offline-queue-badge');
if (badge) {
if (e.data.count > 0) {
badge.textContent = e.data.count;
badge.style.display = '';
} else {
badge.style.display = 'none';
}
}
return;
}
if (e.data?.type === 'CHECK_NEARBY_ALERTS') {
window.App?._checkNearbyAlerts?.();
}