diff --git a/apps/ops/src/pages/PlanificationPage.vue b/apps/ops/src/pages/PlanificationPage.vue
index 0dccaa3..fa779af 100644
--- a/apps/ops/src/pages/PlanificationPage.vue
+++ b/apps/ops/src/pages/PlanificationPage.vue
@@ -1141,7 +1141,7 @@
-
+
{{ r.techName }} · {{ r.stops.length }} arrêt(s) · 🚗 {{ realRoutes[r.techId].km }} km / {{ realRoutes[r.techId].mins }} min · 🚗 ≈{{ r.km }} km
Aucun job géolocalisé ce jour (utilise « Localiser » sur le pool pour les placer sur la carte).
@@ -3452,7 +3452,15 @@ function refreshAssignMap () {
function destroyAssignMap () { assignLasso.value = false; if (_assignMapRO) { try { _assignMapRO.disconnect() } catch (e) {} _assignMapRO = null } if (_assignMap) { try { _assignMap.remove() } catch (e) {} _assignMap = null } }
// ── Grande carte des TOURNÉES proposées (revue Suggérer) : ligne domicile→arrêts par tech, 1 couleur/tech, 1 jour ──
// Carte de la revue = composant partagé (P2). Les métriques réelles (OSRM) remontent ici pour la légende.
-function onRouteMetrics (m) { for (const k in realRoutes) delete realRoutes[k]; Object.assign(realRoutes, m) }
+function onRouteMetrics (m) {
+ // Idempotent : ne muter realRoutes QUE si les valeurs changent (sinon re-rendu inutile → risque de boucle avec RouteMap)
+ const keys = Object.keys(m || {})
+ let same = keys.length === Object.keys(realRoutes).length
+ if (same) for (const k of keys) { const a = realRoutes[k], b = m[k]; if (!a || a.km !== b.km || a.mins !== b.mins) { same = false; break } }
+ if (same) return
+ for (const k in realRoutes) delete realRoutes[k]
+ Object.assign(realRoutes, m)
+}
// D v2 — routes RÉELLES : Mapbox Directions par tech (domicile→arrêts) → géométrie routière + distance/durée réelles. Cache par signature.
const _routeCache = new Map()
const realRoutes = reactive({}) // techId → { km, mins } (la légende préfère ces valeurs réelles quand présentes)
@@ -5019,6 +5027,10 @@ const suggestRoutes = computed(() => {
}
return out
})
+// RÉFÉRENCE STABLE pour — computed CACHÉ (ne change QUE si suggestRoutes change).
+// AVANT : `:routes="suggestRoutes.map(...)"` inline créait un NOUVEAU tableau à CHAQUE rendu → le watch deep de RouteMap
+// redessinait → emit('metrics') → onRouteMetrics muta realRoutes → re-rendu → nouveau tableau → BOUCLE INFINIE (figeait la carte).
+const suggestMapRoutes = computed(() => suggestRoutes.value.map(r => ({ id: r.techId, name: r.techName, color: r.color, home: r.home, stops: r.stops })))
// Carte de la revue = (réactif sur suggestRoutes) — il ne reste qu'à défauter le jour affiché à l'ouverture de la vue carte.
watch(() => suggestDlg.view, (v) => { if (v === 'map' && !suggestMapDay.value) suggestMapDay.value = suggestWindow.value[0] || '' })
// ── P3 — Onglet « Tournées » : tournées RÉELLES (jobs assignés, occupation) de la journée choisie, 1 couleur/tech, tracés OSRM (RouteMap). ──