planif: fix figement carte des tournées suggérées (boucle de rendu) + tracés routiers réels

Cause : :routes="suggestRoutes.map(...)" inline créait un NOUVEAU tableau à chaque rendu → watch deep
de RouteMap → draw → emit('metrics') → onRouteMetrics mutait realRoutes → re-rendu → nouveau tableau →
BOUCLE INFINIE (onglet figé ; le remplacement asynchrone OSRM ne finissait jamais → lignes à vol d'oiseau).
Fix : computed CACHÉ suggestMapRoutes (réf stable) + onRouteMetrics idempotent. La géométrie routière OSRM
(vérifiée côté serveur : 200, ~405 pts) s'affiche maintenant.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
louispaulb 2026-07-07 10:57:39 -04:00
parent 9142d6dd26
commit 746a8de6df

View File

@ -1141,7 +1141,7 @@
</template> </template>
</div> </div>
<div v-if="suggestDlg.view === 'map'"> <div v-if="suggestDlg.view === 'map'">
<RouteMap :routes="suggestRoutes.map(r => ({ id: r.techId, name: r.techName, color: r.color, home: r.home, stops: r.stops }))" height="440px" @metrics="onRouteMetrics" @stop-click="onReviewStopClick" /> <RouteMap :routes="suggestMapRoutes" height="440px" @metrics="onRouteMetrics" @stop-click="onReviewStopClick" />
<div class="suggest-legend"> <div class="suggest-legend">
<span v-for="r in suggestRoutes" :key="r.techId" class="suggest-leg"><span class="suggest-leg-dot" :style="{ background: r.color }"></span>{{ r.techName }} · {{ r.stops.length }} arrêt(s)<span v-if="realRoutes[r.techId]"> · 🚗 {{ realRoutes[r.techId].km }} km / {{ realRoutes[r.techId].mins }} min</span><span v-else-if="r.km"> · 🚗 {{ r.km }} km</span></span> <span v-for="r in suggestRoutes" :key="r.techId" class="suggest-leg"><span class="suggest-leg-dot" :style="{ background: r.color }"></span>{{ r.techName }} · {{ r.stops.length }} arrêt(s)<span v-if="realRoutes[r.techId]"> · 🚗 {{ realRoutes[r.techId].km }} km / {{ realRoutes[r.techId].mins }} min</span><span v-else-if="r.km"> · 🚗 {{ r.km }} km</span></span>
<span v-if="!suggestRoutes.length" class="text-grey-6" style="font-size:12px">Aucun job géolocalisé ce jour (utilise « Localiser » sur le pool pour les placer sur la carte).</span> <span v-if="!suggestRoutes.length" class="text-grey-6" style="font-size:12px">Aucun job géolocalisé ce jour (utilise « Localiser » sur le pool pour les placer sur la carte).</span>
@ -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 } } 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 domicilearrêts par tech, 1 couleur/tech, 1 jour // Grande carte des TOURNÉES proposées (revue Suggérer) : ligne domicilearrêts par tech, 1 couleur/tech, 1 jour
// Carte de la revue = composant partagé <RouteMap> (P2). Les métriques réelles (OSRM) remontent ici pour la légende. // Carte de la revue = composant partagé <RouteMap> (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 (domicilearrêts) géométrie routière + distance/durée réelles. Cache par signature. // D v2 routes RÉELLES : Mapbox Directions par tech (domicilearrêts) géométrie routière + distance/durée réelles. Cache par signature.
const _routeCache = new Map() const _routeCache = new Map()
const realRoutes = reactive({}) // techId { km, mins } (la légende préfère ces valeurs réelles quand présentes) 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 return out
}) })
// RÉFÉRENCE STABLE pour <RouteMap :routes> 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 = <RouteMap> (réactif sur suggestRoutes) il ne reste qu'à défauter le jour affiché à l'ouverture de la vue carte. // Carte de la revue = <RouteMap> (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] || '' }) 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). // P3 Onglet « Tournées » : tournées RÉELLES (jobs assignés, occupation) de la journée choisie, 1 couleur/tech, tracés OSRM (RouteMap).