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 })
}