baustelle-pwa/lib/offline.js
Eduard Wisch 7c0aefa793
All checks were successful
Deploy baustelle-pwa / deploy (push) Successful in 1s
feat: Initiales Release Baustelle PWA v1.0.0 [deploy]
Mobile Progressive Web App für Baustellen-Doku, spricht die REST-API
des Dolibarr-Bericht-Moduls.

MVP-Features:
- Vanilla JavaScript, kein Build-Step nötig
- Login mit Dolibarr-Credentials → JWT (7 Tage)
- Auftragsliste mit Suche und Multi-User-Filter
- Auftragsdetail mit Kunde, Adresse, Click-to-Call
- Foto-Aufnahme via Kamera oder Galerie (multiple)
- Clientseitige Bildverkleinerung (max 2000px, JPEG q=0.85)
- Offline-Queue in IndexedDB für Uploads ohne Netz
- Auto-Sync bei Online-Event mit Status-Badge
- Service Worker für App-Shell-Cache
- PWA-installierbar (Manifest, Icons, Theme-Color)

Hosting: awl.data-it-solution.de/baustelle/ via Apache-Alias

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 22:50:01 +02:00

74 lines
2.5 KiB
JavaScript

/* Offline-Queue für Foto-Uploads.
* Wenn der Upload fehlschlägt (offline), wird er in IndexedDB abgelegt
* und beim nächsten Online-Event automatisch nachgesendet.
*/
(function () {
let syncing = false;
async function enqueuePhoto(orderId, fileBlob, filename) {
// Blob → ArrayBuffer für IndexedDB-Speicherung
const buf = await fileBlob.arrayBuffer();
const id = await idb.queuePush({
type: 'photo',
order_id: orderId,
filename,
mime: fileBlob.type || 'image/jpeg',
data: buf,
created: Date.now(),
});
updateBadge();
return id;
}
async function syncQueue() {
if (syncing) return;
if (!navigator.onLine) return;
syncing = true;
try {
const items = await idb.queueAll();
for (const it of items) {
try {
if (it.type === 'photo') {
const blob = new Blob([it.data], { type: it.mime });
await api.uploadOrderPhoto(it.order_id, blob, it.filename);
}
await idb.queueDelete(it.id);
} catch (e) {
console.warn('Sync failed for item', it.id, e);
// Nicht weiter versuchen wenn ein Item failed
break;
}
}
} finally {
syncing = false;
updateBadge();
}
}
async function updateBadge() {
const items = await idb.queueAll();
const badge = document.getElementById('status-badge');
if (!badge) return;
if (!navigator.onLine) {
badge.textContent = items.length ? '🔴 ' + items.length : '🔴';
badge.title = items.length + ' Uploads warten';
} else if (items.length) {
badge.textContent = '🟡 ' + items.length;
badge.title = items.length + ' werden synchronisiert';
} else {
badge.textContent = '🟢';
badge.title = 'Online';
}
}
window.addEventListener('online', () => { updateBadge(); syncQueue(); });
window.addEventListener('offline', updateBadge);
document.addEventListener('DOMContentLoaded', () => {
updateBadge();
if (navigator.onLine) syncQueue();
// Periodischer Sync alle 30s
setInterval(() => { if (navigator.onLine) syncQueue(); }, 30000);
});
window.offline = { enqueuePhoto, syncQueue, updateBadge };
})();