// Targo Tech — app native (Capacitor + Vite). // Appairage 1× (QR → token tech persistant) → géorepérage natif Transistorsoft (arrière-plan, app fermée) // → POST auto des événements geofence au hub (/field/ts) → UI réutilisée depuis le hub (/field). import BackgroundGeolocation from '@transistorsoft/capacitor-background-geolocation' import { BarcodeScanner } from '@capacitor-mlkit/barcode-scanning' import { Preferences } from '@capacitor/preferences' const HUB = 'https://msg.gigafibre.ca' const $ = (id) => document.getElementById(id) const getToken = async () => (await Preferences.get({ key: 'tech_token' })).value const setToken = async (t) => Preferences.set({ key: 'tech_token', value: t }) function tokenFromUrl (u) { try { return new URL(u).searchParams.get('t') } catch (e) { const m = /[?&]t=([^&\s]+)/.exec(u || ''); return m ? decodeURIComponent(m[1]) : (u && u.includes('.') ? u : null) } } window.pairScan = async () => { try { const { barcodes } = await BarcodeScanner.scan() const v = barcodes && barcodes[0] && (barcodes[0].rawValue || barcodes[0].displayValue) const t = v && tokenFromUrl(v) if (t) { await setToken(t); boot() } else $('msg').textContent = 'QR non reconnu' } catch (e) { $('msg').textContent = 'Scan annulé' } } window.pairPaste = async () => { const t = tokenFromUrl($('lnk').value.trim()); if (t) { await setToken(t); boot() } else $('msg').textContent = 'Lien invalide' } window.unpair = async () => { await Preferences.remove({ key: 'tech_token' }); location.reload() } // Géorepérage natif : enregistre les jobs du jour comme geofences ; Transistorsoft POSTe enter/exit au hub (autoSync), // même app fermée. L'identifier de chaque geofence = le token signé du job → le hub mappe + écrit le checkpoint. async function initGeo (token) { await BackgroundGeolocation.ready({ desiredAccuracy: BackgroundGeolocation.DESIRED_ACCURACY_HIGH, distanceFilter: 50, stopOnTerminate: false, startOnBoot: true, geofenceModeHighAccuracy: true, locationAuthorizationRequest: 'Always', backgroundPermissionRationale: { title: 'Suivi des interventions', message: 'Targo Tech a besoin de la localisation « toujours » pour détecter automatiquement vos arrivées/départs.' }, url: HUB + '/field/ts', autoSync: true, batchSync: false, notification: { title: 'Targo Tech', text: 'Suivi des interventions actif' }, }) try { const jobs = await fetch(HUB + '/field/tech?t=' + encodeURIComponent(token)).then(r => r.json()).then(j => j.jobs || []) await BackgroundGeolocation.removeGeofences() const fences = jobs.filter(j => j.lat && Math.abs(+j.lat) > 0.01).map(j => ({ identifier: j.token, latitude: +j.lat, longitude: +j.lon, radius: 200, notifyOnEntry: true, notifyOnExit: true })) if (fences.length) await BackgroundGeolocation.addGeofences(fences) } catch (e) { console.warn('geofences', e) } const st = await BackgroundGeolocation.getState() if (!st.enabled) await BackgroundGeolocation.start() } async function boot () { const token = await getToken() if (!token) { $('splash').style.display = 'none'; $('pair').style.display = 'block'; return } try { await initGeo(token) } catch (e) { console.warn('initGeo', e) } // Charge l'UI complète hébergée (liste/détail/carte/photo/scan IA + MLKit injecté par Capacitor) location.replace(HUB + '/field?t=' + encodeURIComponent(token)) } boot()