From 9b866ec3b9f13c51a90b041ea77bc3c9907bbead Mon Sep 17 00:00:00 2001 From: louispaulb Date: Sun, 5 Jul 2026 08:05:03 -0400 Subject: [PATCH] Revert "feat(ops): unassigned map jobs reuse assigned-stop pill + overlap fan" This reverts commit 80e09c87bace2b9aef7ca094bc94c0aa5aeee6a5. --- apps/ops/src/components/shared/RouteMap.vue | 39 +++++++-------------- apps/ops/src/pages/PlanificationPage.vue | 2 +- 2 files changed, 14 insertions(+), 27 deletions(-) diff --git a/apps/ops/src/components/shared/RouteMap.vue b/apps/ops/src/components/shared/RouteMap.vue index bf43976..0e22235 100644 --- a/apps/ops/src/components/shared/RouteMap.vue +++ b/apps/ops/src/components/shared/RouteMap.vue @@ -103,22 +103,13 @@ function renderPins (list) { for (const p of (list || [])) { if (!okLL(p.lon, p.lat)) continue const color = p.color || '#f97316' - // MÊME pastille round-rect que les arrêts assignés (icône de compétence), variante « non assigné » (bordure pointillée) ; - // participe au même éclatement en éventail au chevauchement (recluster/fan) que les arrêts. - const wrap = document.createElement('div'); wrap.className = 'rm-mk rm-mk-un' - const disc = document.createElement('div'); disc.className = 'rm-pill rm-pill-un'; disc.style.background = color - const ic = /^[a-z0-9_]+$/.test(String(p.icon || '')) ? p.icon : 'place' - disc.innerHTML = '' + ic + '' - wrap.appendChild(disc) + const wrap = document.createElement('div'); wrap.className = 'rm-pin' + wrap.innerHTML = '' wrap.title = (p.subject || 'Job') + (p.city ? ' · ' + p.city : '') + ' — non assigné' - const mk = new mapboxgl.Marker({ element: wrap, anchor: 'center' }).setLngLat([+p.lon, +p.lat]).addTo(map) - const rec = { mk, el: wrap, disc, rid: null, lngLat: [+p.lon, +p.lat], fan: null, members: null, pin: p } - wrap.addEventListener('mouseenter', () => onEnter(rec)) - wrap.addEventListener('mouseleave', () => onLeave(rec)) wrap.addEventListener('click', () => emit('pin-click', p)) - pinMk.push(rec) + const mk = new mapboxgl.Marker({ element: wrap, anchor: 'bottom' }).setLngLat([+p.lon, +p.lat]).addTo(map) + pinMk.push({ mk, el: wrap }) } - recluster() } function renderMarkers (routes) { clearMarkers() @@ -150,31 +141,29 @@ function renderMarkers (routes) { } // Regroupe les pastilles qui se CHEVAUCHENT à l'écran (distance pixel) → calcule pour chacune un décalage en éventail. function recluster () { - // Arrêts assignés ET jobs non assignés partagent le même éclatement en éventail au chevauchement. - const all = stopMk.concat(pinMk) - if (!map || !all.length) return - const px = all.map(s => map.project(s.lngLat)) - const parent = all.map((_, i) => i) + if (!map || !stopMk.length) return + const px = stopMk.map(s => map.project(s.lngLat)) + const parent = stopMk.map((_, i) => i) const find = (i) => { while (parent[i] !== i) { parent[i] = parent[parent[i]]; i = parent[i] } return i } const TH = 24 // pastille ø24 → chevauchement si centres < ~24 px - for (let i = 0; i < all.length; i++) for (let j = i + 1; j < all.length; j++) { + for (let i = 0; i < stopMk.length; i++) for (let j = i + 1; j < stopMk.length; j++) { const dx = px[i].x - px[j].x, dy = px[i].y - px[j].y if (dx * dx + dy * dy < TH * TH) parent[find(i)] = find(j) } const groups = {} - for (let i = 0; i < all.length; i++) { const r = find(i); (groups[r] = groups[r] || []).push(i) } - for (const s of all) { s.fan = null; s.members = null } + for (let i = 0; i < stopMk.length; i++) { const r = find(i); (groups[r] = groups[r] || []).push(i) } + for (const s of stopMk) { s.fan = null; s.members = null } for (const key in groups) { const ids = groups[key]; if (ids.length < 2) continue const cx = ids.reduce((a, i) => a + px[i].x, 0) / ids.length const cy = ids.reduce((a, i) => a + px[i].y, 0) / ids.length const R = 18 + ids.length * 6 // rayon d'éclatement croît avec le nombre - const members = ids.map(i => all[i]) + const members = ids.map(i => stopMk[i]) ids.forEach((i, k) => { const ang = (k / ids.length) * 2 * Math.PI - Math.PI / 2 const tx = cx + R * Math.cos(ang), ty = cy + R * Math.sin(ang) - all[i].fan = [tx - px[i].x, ty - px[i].y] // décalage (px) de la pastille par rapport à son point - all[i].members = members + stopMk[i].fan = [tx - px[i].x, ty - px[i].y] // décalage (px) de la pastille par rapport à son point + stopMk[i].members = members }) } } @@ -280,8 +269,6 @@ watch(() => props.pins, () => { renderPins(props.pins) }, { deep: true }) // job } .rm-mk .rm-pill .rm-ic { font-size: 14px; line-height: 1; opacity: .95; } .rm-mk .rm-pill .rm-num { font-size: 12px; font-weight: 800; line-height: 1; } -/* Non assigné : même pastille, bordure pointillée + halo pour se distinguer des arrêts planifiés. */ -.rm-mk-un .rm-pill { border-style: dashed; box-shadow: 0 0 0 2px rgba(249,115,22,.25), 0 1px 3px rgba(0,0,0,.45); } .rm-mk .rm-home { width: 15px; height: 15px; border-radius: 50%; box-sizing: border-box; background: #fff; border: 3px solid #888; box-shadow: 0 1px 3px rgba(0,0,0,.4); } .rm-mk.hot { z-index: 7; } .rm-mk.hot .rm-pill { transform: scale(1.15); box-shadow: 0 3px 9px rgba(0,0,0,.55); } diff --git a/apps/ops/src/pages/PlanificationPage.vue b/apps/ops/src/pages/PlanificationPage.vue index b716072..77a3b98 100644 --- a/apps/ops/src/pages/PlanificationPage.vue +++ b/apps/ops/src/pages/PlanificationPage.vue @@ -4786,7 +4786,7 @@ const routeDayUnassignedView = computed(() => { const _jlat = j => (j.latitude != null ? +j.latitude : (j.lat != null ? +j.lat : null)) const _jlon = j => (j.longitude != null ? +j.longitude : (j.lon != null ? +j.lon : null)) // Gouttes carte = non-assignés du jour (filtrés secteur) qui ont des coordonnées ; couleur = priorité -const routeUnassignedPins = computed(() => routeDayUnassignedView.value.filter(j => { const la = _jlat(j); const lo = _jlon(j); return la != null && lo != null && isFinite(la) && isFinite(lo) && Math.abs(la) > 0.01 }).map(j => ({ lat: _jlat(j), lon: _jlon(j), subject: j.subject || j.service_type || j.name, name: j.name, color: prioColor(j.priority), city: jobCity(j), icon: skillSym(j.required_skill || j.service_type), job: j }))) +const routeUnassignedPins = computed(() => routeDayUnassignedView.value.filter(j => { const la = _jlat(j); const lo = _jlon(j); return la != null && lo != null && isFinite(la) && isFinite(lo) && Math.abs(la) > 0.01 }).map(j => ({ lat: _jlat(j), lon: _jlon(j), subject: j.subject || j.service_type || j.name, name: j.name, color: prioColor(j.priority), city: jobCity(j), job: j }))) // ── Deep-link (?day=YYYY-MM-DD & ?skill=…) + action « planifier des quarts » du slot-finder ── const route = useRoute()