From 3547f4a680daee15ce378d300f45869bfc3e84a0 Mon Sep 17 00:00:00 2001 From: louispaulb Date: Mon, 20 Jul 2026 11:44:04 -0400 Subject: [PATCH] =?UTF-8?q?feat(map):=20v=C3=A9hicules=20Traccar=20libell?= =?UTF-8?q?=C3=A9s=20par=20initiales=20du=20TECH,=20repli=20sur=20le=20dev?= =?UTF-8?q?ice?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Demande user : afficher les véhicules Traccar avec les initiales du TECHNICIEN assigné, et ne retomber sur les initiales du device Traccar QUE si aucun tech n'est assigné au véhicule. - hub /roster/tech-positions : part désormais de TOUS les devices Traccar (pas seulement ceux liés à un tech). Pour chaque device localisé : techName si un tech y est assigné (via traccar_device_id), sinon deviceName ; renvoie techName + deviceName + name résolu (tech>device). Exclut l'île nulle (0,0 = jamais localisé). Live : 23 positions (14 tech, 9 device-only). - RouteMap : libellé marqueur = initials(techName || deviceName || name) (tech prioritaire). - PlanificationPage dayLivePositions : les véhicules SANS tech s'affichent aussi (gris), filtrés par visibilité seulement s'ils ont un tech. PlanificationPage.vue = co-édité → DÉPLOYÉ (build) mais NON commité (hunk à isoler). Vérifié : endpoint live OK ; build spa OK, md5 match. Co-Authored-By: Claude Opus 4.8 --- apps/ops/src/components/shared/RouteMap.vue | 4 +-- services/targo-hub/lib/roster.js | 32 +++++++++++++++------ 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/apps/ops/src/components/shared/RouteMap.vue b/apps/ops/src/components/shared/RouteMap.vue index d8501e9..acf344c 100644 --- a/apps/ops/src/components/shared/RouteMap.vue +++ b/apps/ops/src/components/shared/RouteMap.vue @@ -16,7 +16,7 @@ import { initials } from 'src/composables/useFormatters' // initiales (source un const props = defineProps({ routes: { type: Array, default: () => [] }, - live: { type: Array, default: () => [] }, // positions GPS LIVE : [{ techId, name|techName, color, lat, lon, time, speed(nœuds) }] + live: { type: Array, default: () => [] }, // positions GPS LIVE : [{ techId, techName, deviceName, name, color, lat, lon, time, speed(nœuds) }] — libellé = techName > deviceName pins: { type: Array, default: () => [] }, // jobs NON assignés à situer : [{ lat, lon, subject, name, color(priorité), city }] track: { type: Object, default: null }, // tracé GPS RÉEL (Traccar) d'UN tech sur la journée : { coords:[[lon,lat]…] } — couche orange au 1er plan (vs tournées prévues) height: { type: String, default: '440px' }, @@ -88,7 +88,7 @@ function renderLive (list) { // pas `wrap` (comme les arrêts translatent .rm-pill, enfant de .rm-mk), sinon on casserait le placement GPS. const inner = document.createElement('div'); inner.className = 'rm-live-in' inner.innerHTML = '' + - '' + initials(p.name || p.techName) + '' + '' + initials(p.techName || p.deviceName || p.name) + '' wrap.appendChild(inner) const spd = (p.speed != null && p.speed > 1) ? Math.round(p.speed * 1.852) + ' km/h' : 'arrêté' // Traccar speed = nœuds → km/h const when = ageMin == null ? 'position' : (ageMin <= 1 ? "à l'instant" : 'il y a ' + ageMin + ' min') diff --git a/services/targo-hub/lib/roster.js b/services/targo-hub/lib/roster.js index 44153c5..ead41f7 100644 --- a/services/targo-hub/lib/roster.js +++ b/services/targo-hub/lib/roster.js @@ -2431,17 +2431,33 @@ async function handle (req, res, method, path, url) { // Positions GPS LIVE de TOUS les techs ayant un appareil Traccar → marqueurs « live » sur la carte des tournées // (rafraîchi périodiquement côté client). Léger : une requête position par appareil (getPositions parallèle). if (path === '/roster/tech-positions' && method === 'GET') { - const techs = (await fetchTechnicians()).filter(t => t.traccar_device_id) - if (!techs.length) return json(res, 200, { positions: [] }) - const { getPositions } = require('./traccar') - const ids = [...new Set(techs.map(t => parseInt(t.traccar_device_id)).filter(Boolean))] + // Affiche TOUS les véhicules Traccar. Libellé = initiales du TECH assigné au véhicule ; à défaut (aucun tech + // assigné à ce device) → nom du device Traccar. On renvoie techName + deviceName + `name` résolu (tech > device). + const { getPositions, getDevices } = require('./traccar') + let devices = [] + try { devices = await getDevices() } catch (e) { /* Traccar injoignable → devices vide */ } + const techs = await fetchTechnicians() + const techByDev = {}; for (const t of techs) { const d = parseInt(t.traccar_device_id); if (d) techByDev[d] = t } // device → tech assigné + const devById = {}; for (const d of (devices || [])) { const id = parseInt(d.id); if (id) devById[id] = d } + const ids = Object.keys(devById).map(Number) + if (!ids.length) return json(res, 200, { positions: [] }) let positions = [] try { positions = await getPositions(ids) } catch (e) { return json(res, 200, { positions: [], error: e.message }) } - const byDev = {}; for (const p of positions) if (p && p.deviceId != null) byDev[p.deviceId] = p const out = [] - for (const t of techs) { - const p = byDev[parseInt(t.traccar_device_id)] - if (p && p.latitude != null) out.push({ techId: t.id, techName: t.name, lat: p.latitude, lon: p.longitude, time: p.fixTime || p.deviceTime || p.serverTime || null, speed: p.speed || 0 }) + for (const p of positions) { + if (!p || p.deviceId == null || p.latitude == null) continue + if (Math.abs(+p.latitude) < 0.01 && Math.abs(+p.longitude) < 0.01) continue // île nulle (0,0) = position invalide (device jamais localisé) → exclu + const t = techByDev[parseInt(p.deviceId)] + const dev = devById[parseInt(p.deviceId)] + const deviceName = (dev && dev.name) || '' + out.push({ + deviceId: p.deviceId, + techId: t ? t.id : null, + techName: t ? t.name : null, // initiales du tech si un tech est assigné au véhicule + deviceName, // repli : nom du device Traccar (aucun tech assigné) + name: t ? t.name : deviceName, // libellé résolu : tech > device + lat: p.latitude, lon: p.longitude, time: p.fixTime || p.deviceTime || p.serverTime || null, speed: p.speed || 0, + }) } return json(res, 200, { positions: out }) }