All checks were successful
Deploy baustelle-pwa / deploy (push) Successful in 1s
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>
74 lines
2.5 KiB
JavaScript
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 };
|
|
})();
|