From 2665a6a2da8879a216c62a7b158f1ea998a2dbfa Mon Sep 17 00:00:00 2001 From: louispaulb Date: Sun, 7 Jun 2026 17:50:52 -0400 Subject: [PATCH] =?UTF-8?q?Planification=20=C3=A9diteur=20journ=C3=A9e=20:?= =?UTF-8?q?=20d=C3=A9placements=20en=20pointill=C3=A9s=20+=20clic=20pin=20?= =?UTF-8?q?=3D=20d=C3=A9tails=20+=20clic=20ligne=20=3D=20recentre=20carte?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Barre timeline : l'espace entre 2 jobs (le déplacement) est rendu en POINTILLÉS (au lieu du gris vide), tooltip « 🚗 déplacement ». dayTravelSegs() = gaps entre packedDay[i].end et [i+1].start. - Carte : clic sur un pin → POPUP avec détails du job (n°, sujet, heure, client) ; curseur main au survol. - Liste : clic sur une ligne → recentre la carte (easeTo zoom 14) sur ce job, en plus d'ouvrir le détail. Co-Authored-By: Claude Opus 4.8 (1M context) --- apps/ops/src/pages/PlanificationPage.vue | 25 +++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/apps/ops/src/pages/PlanificationPage.vue b/apps/ops/src/pages/PlanificationPage.vue index 07b3e9b..1000488 100644 --- a/apps/ops/src/pages/PlanificationPage.vue +++ b/apps/ops/src/pages/PlanificationPage.vue @@ -676,6 +676,7 @@
+
🚗 déplacement
{{ tk.h }}
@@ -701,7 +702,7 @@ {{ i + 1 }} -
{{ j.detail }} +
{{ j.detail }}
{{ j.subject }}
{{ fmtHM(packedDay[i].startMin) }}–{{ fmtHM(packedDay[i].endMin) }} · 🔒 RDV fixe · {{ j.customer }}
@@ -1115,9 +1116,17 @@ function ensureMapbox () { const iv = setInterval(() => { if (window.mapboxgl) { clearInterval(iv); resolve(window.mapboxgl) } }, 150) }) } -function dayStops () { // arrêts géolocalisés, dans l'ordre de tournée (packedDay) - return packedDay.value.filter(hasLL).map((j, i) => ({ lon: +j.lon, lat: +j.lat, label: String(i + 1), color: j.skill ? getTagColor(j.skill) : '#1976d2' })) +const _esc = (s) => String(s == null ? '' : s).replace(/[&<>"]/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c])) +function dayStops () { // arrêts géolocalisés, dans l'ordre de tournée (packedDay) + infos pour le popup + return packedDay.value.filter(hasLL).map((j, i) => ({ + lon: +j.lon, lat: +j.lat, label: String(i + 1), color: j.skill ? getTagColor(j.skill) : '#1976d2', + subject: j.subject || '', customer: j.customer || '', time: fmtHM(j.startMin) + '–' + fmtHM(j.endMin), + })) } +// Segments de DÉPLACEMENT (pointillés) = l'espace entre 2 jobs dans la barre timeline. +const dayTravelSegs = () => { const p = packedDay.value; const out = []; for (let i = 0; i < p.length - 1; i++) { const s = p[i].endMin, e = p[i + 1].startMin; if (e - s > 0.02) out.push({ s, e }) } return out } +// Centre la carte sur un job (clic sur la ligne de la liste). +function focusDayJob (j) { if (_dayMap && hasLL(j)) _dayMap.easeTo({ center: [+j.lon, +j.lat], zoom: 14, duration: 500 }) } async function initDayMap () { if (!MAPBOX_TOKEN || !dayMapEl.value || _dayMap) return const mapboxgl = await ensureMapbox(); if (!mapboxgl || !dayMapEl.value) return @@ -1133,6 +1142,15 @@ async function initDayMap () { _dayMap.addSource('day-stops', { type: 'geojson', data: { type: 'FeatureCollection', features: [] } }) _dayMap.addLayer({ id: 'day-stops-c', type: 'circle', source: 'day-stops', paint: { 'circle-radius': 13, 'circle-color': ['get', 'color'], 'circle-stroke-width': 2, 'circle-stroke-color': '#fff' } }) _dayMap.addLayer({ id: 'day-stops-l', type: 'symbol', source: 'day-stops', layout: { 'text-field': ['get', 'label'], 'text-font': ['DIN Offc Pro Bold', 'Arial Unicode MS Bold'], 'text-size': 12, 'text-allow-overlap': true }, paint: { 'text-color': '#fff' } }) + // Clic sur un pin → popup avec les détails du job ; curseur main au survol. + _dayMap.on('mouseenter', 'day-stops-c', () => { _dayMap.getCanvas().style.cursor = 'pointer' }) + _dayMap.on('mouseleave', 'day-stops-c', () => { _dayMap.getCanvas().style.cursor = '' }) + _dayMap.on('click', 'day-stops-c', (e) => { + const f = e.features[0]; const p = f.properties + new window.mapboxgl.Popup({ offset: 14 }).setLngLat(f.geometry.coordinates) + .setHTML(`
${_esc(p.label)}. ${_esc(p.subject)}
🕒 ${_esc(p.time)}${p.customer ? '
👤 ' + _esc(p.customer) : ''}
`) + .addTo(_dayMap) + }) refreshDayMap() }) } @@ -1925,6 +1943,7 @@ tr.res-hidden .hide-eye { opacity: 1; } .tl-shift.oncall { background: rgba(255,179,0,.14); border: 1px dashed #f9a825; } /* garde = sur appel hors heures (pointillé ambre) */ .tl-absent { position: absolute; inset: 0; border-radius: 2px; box-sizing: border-box; border: 1px solid #b0b0b0; background: repeating-linear-gradient(45deg, #cfcfcf 0, #cfcfcf 3px, #f0f0f0 3px, #f0f0f0 6px); } /* absent = hachuré gris */ .tl-blk { position: absolute; top: 0; bottom: 0; border-radius: 1px; } /* occupé = barre de statut opaque */ +.tl-travel { position: absolute; top: 0; bottom: 0; background-image: repeating-linear-gradient(90deg, #78909c 0 3px, transparent 3px 7px); background-size: 100% 3px; background-repeat: no-repeat; background-position: 0 center; opacity: .85; } /* déplacement = pointillés */ .tod-leg { display: inline-block; width: 46px; height: 9px; border-radius: 2px; vertical-align: middle; background: linear-gradient(to right, hsl(210,45%,91%), hsl(270,45%,83%)); } .occ-leg { display: inline-block; width: 46px; height: 9px; border-radius: 2px; vertical-align: middle; background: linear-gradient(to right, hsl(122,68%,44%), hsl(32,68%,44%)); } .leg-absent { display: inline-block; width: 24px; height: 9px; border-radius: 2px; vertical-align: middle; border: 1px solid #b0b0b0; background: repeating-linear-gradient(45deg,#cfcfcf 0,#cfcfcf 3px,#f0f0f0 3px,#f0f0f0 6px); }