From f8af4235fbcb76706fe6c9253b847bad8a12daea Mon Sep 17 00:00:00 2001 From: louispaulb Date: Sun, 19 Jul 2026 16:34:28 -0400 Subject: [PATCH] =?UTF-8?q?improve(ops):=20accueil=20=E2=80=94=20d=C3=A9te?= =?UTF-8?q?cte=20les=20ARRIV=C3=89ES=20EN=20RETARD=20sur=20les=20intervent?= =?UTF-8?q?ions=20du=20jour?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enrichit la carte « Interventions aujourd'hui » (au-delà du simple découpage par statut) : croise l'heure de début prévue avec l'état géofence live → pastille ROUGE « N en retard » quand le début est passé de > 20 min mais le tech n'est PAS montré sur place (ni parti). Signal proactif de retard/no-show pour le répartiteur. Réutilise /roster/geofence-states. Propre (composant seul, aucun changement hub). Vérifié en dev : rendu 3 cartes, aucune erreur console (0 job aujourd'hui en dev → pastille masquée, correct). Build OK, leak-clean, déployé. Co-Authored-By: Claude Fable 5 --- .../src/components/shared/MonAujourdhui.vue | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/apps/ops/src/components/shared/MonAujourdhui.vue b/apps/ops/src/components/shared/MonAujourdhui.vue index 64289cc..57b8684 100644 --- a/apps/ops/src/components/shared/MonAujourdhui.vue +++ b/apps/ops/src/components/shared/MonAujourdhui.vue @@ -50,6 +50,10 @@ const jobBreakdown = computed(() => { return STATUS.map(s => ({ ...s, n: by[s.key] || 0 })).filter(s => s.n > 0) }) +// Heure courante (Québec) en minutes depuis minuit — pour détecter les arrivées EN RETARD sur les jobs du jour. +function nowEtMinutes () { + try { const p = new Date().toLocaleTimeString('en-GB', { timeZone: TZ, hour12: false }); const a = p.split(':'); return (Number(a[0]) || 0) * 60 + (Number(a[1]) || 0) } catch (e) { return 0 } +} async function hubGet (path) { try { const r = await fetch(HUB_URL + path, { headers: { 'Content-Type': 'application/json' } }); return r.ok ? await r.json() : null } catch { return null } } @@ -65,16 +69,24 @@ async function load () { } if (showJobs.value) { tasks.push((async () => { - const js = await listDocs('Dispatch Job', { filters: { scheduled_date: today }, fields: ['name', 'status'], limit: 300 }).catch(() => []) + const js = await listDocs('Dispatch Job', { filters: { scheduled_date: today }, fields: ['name', 'status', 'start_time'], limit: 300 }).catch(() => []) const by = {}; for (const j of (js || [])) by[j.status] = (by[j.status] || 0) + 1 - let enRoute = 0, onSite = 0 + let enRoute = 0, onSite = 0, atRisk = 0, st = {} const names = (js || []).map(j => j.name).slice(0, 120) if (names.length) { const g = await hubGet('/roster/geofence-states?jobs=' + encodeURIComponent(names.join(','))) - const st = (g && g.states) || {} + st = (g && g.states) || {} for (const k in st) { if (st[k] === 'en_route') enRoute++; else if (st[k] === 'on_site') onSite++ } } - jobs.value = { total: (js || []).length, by, enRoute, onSite } + // EN RETARD : début prévu passé de > 20 min mais le géofence ne montre PAS le tech arrivé (ni parti) → retard d'arrivée. + const nowMin = nowEtMinutes() + for (const j of (js || [])) { + if (j.status !== 'assigned' || !j.start_time) continue + const s = String(j.start_time); const sm = (Number(s.slice(0, 2)) || 0) * 60 + (Number(s.slice(3, 5)) || 0) + const state = st[j.name] + if (state !== 'on_site' && state !== 'departed' && (nowMin - sm) > 20) atRisk++ + } + jobs.value = { total: (js || []).length, by, enRoute, onSite, atRisk } })()) } try { await Promise.all(tasks) } catch (e) { /* dégradé */ } @@ -127,7 +139,8 @@ function allocate () { window.dispatchEvent(new CustomEvent('ops:assistant', { d
{{ dash(jobs && jobs.total) }}
-
+
+ {{ jobs.atRisk }} en retard {{ jobs.enRoute }} en route {{ jobs.onSite }} sur place