feat(map): véhicules Traccar libellés par initiales du TECH, repli sur le device
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 <noreply@anthropic.com>
This commit is contained in:
parent
08a68016ec
commit
3547f4a680
|
|
@ -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 = '<span class="rm-live-pulse" style="background:' + color + '"></span>' +
|
||||
'<span class="rm-live-dot" style="background:' + color + '">' + initials(p.name || p.techName) + '</span>'
|
||||
'<span class="rm-live-dot" style="background:' + color + '">' + initials(p.techName || p.deviceName || p.name) + '</span>'
|
||||
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')
|
||||
|
|
|
|||
|
|
@ -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 })
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user