/** * Offline-Sync der Diagnose-Protokolle (Svelte 5 Runes). * * Auf der Baustelle ist oft keine Verbindung zum Dolibarr-Server. Protokolle * werden lokal gespeichert (db.ts) und hier zum Server geschoben, sobald * wieder Netz da ist. Der Server-Endpunkt ist idempotent (clientUuid), ein * doppelter Sync schadet also nicht. */ import { Network } from '@capacitor/network'; import { ApiError, isLoggedIn, syncProtocol } from './api'; import { getDirtyProtocols, saveProtocol } from './db'; import { toast } from './toast.svelte'; type SyncStatus = 'idle' | 'syncing' | 'offline' | 'error'; const SYNC_INTERVAL_MS = 30_000; class SyncState { status = $state('idle'); pendingCount = $state(0); lastError = $state(''); online = $state(true); private timer: ReturnType | null = null; /** Sync-Dienst starten: Netz-Listener + periodischer Lauf */ async start(): Promise { const st = await Network.getStatus(); this.online = st.connected; Network.addListener('networkStatusChange', (s) => { this.online = s.connected; if (s.connected) void this.syncNow(); else this.status = 'offline'; }); this.timer = setInterval(() => void this.syncNow(), SYNC_INTERVAL_MS); await this.refreshPending(); void this.syncNow(); } /** Sync-Dienst stoppen */ stop(): void { if (this.timer) clearInterval(this.timer); this.timer = null; } /** Anzahl offener (dirty) Protokolle neu zählen */ async refreshPending(): Promise { this.pendingCount = (await getDirtyProtocols()).length; } /** * Alle offenen Protokolle synchronisieren. * Läuft still im Hintergrund; Fehler werden gemerkt, nicht geworfen. */ async syncNow(): Promise { if (this.status === 'syncing' || !this.online || !isLoggedIn()) return; const dirty = await getDirtyProtocols(); this.pendingCount = dirty.length; if (dirty.length === 0) { this.status = 'idle'; return; } this.status = 'syncing'; this.lastError = ''; for (const p of dirty) { try { const res = await syncProtocol(p); p.serverId = res.protocolId; p.ref = res.ref; p.dirty = false; await saveProtocol(p); } catch (e) { this.lastError = e instanceof ApiError ? e.message : 'Sync-Fehler'; this.status = 'error'; await this.refreshPending(); return; // beim nächsten Lauf erneut versuchen } } await this.refreshPending(); this.status = 'idle'; } /** * Manueller Sync per Tap auf die Sync-Ampel im Header. * Gibt IMMER sichtbares Feedback (Toast) — der `syncNow()`-Hintergrundlauf * bricht still ab, dadurch wirkte der Button vorher "tot". */ async syncManual(): Promise { if (!this.online) { toast.show('Offline — keine Verbindung zum Server', 'info'); return; } if (!isLoggedIn()) { toast.show('Nicht angemeldet — bitte erst einloggen', 'info'); return; } if (this.status === 'syncing') { toast.show('Synchronisierung läuft bereits …', 'info'); return; } await this.refreshPending(); if (this.pendingCount === 0) { toast.show('Alles synchronisiert', 'success'); return; } await this.syncNow(); if (this.status === 'error') { toast.show(this.lastError || 'Sync fehlgeschlagen', 'error', 5000); } else { toast.show('Protokolle übertragen', 'success'); } } } export const sync = new SyncState();