Some checks failed
Build APK / build-apk (push) Failing after 3m21s
- build.yml: kotlin-android-Plugin (1.9.24) + JVM-Target 17 aktiviert — .kt-Dateien wurden vorher stillschweigend ignoriert (kein Kotlin-Compiler), NetDiagScannerPlugin fehlte im APK -> 'plugin not implemented' fuer alle nativen Scan-Methoden - build.yml: MainActivity.java durch .kt ersetzen (rm + cat), vermeidet Klassenkollision und registriert das Plugin korrekt - sync.svelte.ts: syncManual() -- gibt immer sichtbares Toast-Feedback (Fehler-Details, offline, nicht angemeldet, alles synchronisiert) - AppHeader.svelte: Sync-Button ruft syncManual() statt syncNow() Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
122 lines
3.5 KiB
TypeScript
122 lines
3.5 KiB
TypeScript
/**
|
|
* 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<SyncStatus>('idle');
|
|
pendingCount = $state(0);
|
|
lastError = $state('');
|
|
online = $state(true);
|
|
|
|
private timer: ReturnType<typeof setInterval> | null = null;
|
|
|
|
/** Sync-Dienst starten: Netz-Listener + periodischer Lauf */
|
|
async start(): Promise<void> {
|
|
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<void> {
|
|
this.pendingCount = (await getDirtyProtocols()).length;
|
|
}
|
|
|
|
/**
|
|
* Alle offenen Protokolle synchronisieren.
|
|
* Läuft still im Hintergrund; Fehler werden gemerkt, nicht geworfen.
|
|
*/
|
|
async syncNow(): Promise<void> {
|
|
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<void> {
|
|
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();
|