Planification : minimap du territoire (pins+tracé) dans l'éditeur de journée + retrait du sélecteur priorité
- Minimap Mapbox Static ajoutée sous la timeline : pins numérotés dans l'ordre de tournée + tracé reliant les arrêts, ajustée auto au territoire des jobs → on VÉRIFIE d'un coup d'œil que chaque arrêt tombe à la bonne adresse (un pin mal placé = coord à corriger via Conformité adresses). Clic → carte interactive (Dispatch). Indique « N sans coords (absent de la carte) » le cas échéant. Helper encodePolyline (precision 5) pour le tracé. - Sélecteur de priorité retiré de chaque ligne (défaut « Moyenne » conservé en donnée, géré au Dispatch) → gain de place. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
48c2f53d18
commit
50d877b49f
|
|
@ -679,6 +679,11 @@
|
|||
<div v-for="(b, bi) in dayBlocks()" :key="'k' + bi" class="tl-blk" :style="blockStyle(b, dayOcc() && dayOcc().pct)"></div>
|
||||
<span v-for="tk in axisTicks" :key="'t' + tk.h" class="tldlg-tick" :style="{ left: tk.left }">{{ tk.h }}</span>
|
||||
</div>
|
||||
<!-- minimap : territoire des jobs (pins numérotés dans l'ordre de tournée + tracé) → vérifier les adresses d'un coup d'œil -->
|
||||
<div v-if="dayMapUrl" class="de-map-wrap" @click="gotoDispatch(dayEditor.tech, dayEditor.day && dayEditor.day.iso)">
|
||||
<img :src="dayMapUrl" class="de-map" alt="Territoire du jour" loading="lazy" />
|
||||
<div class="de-map-cap">🗺 Pins = arrêts dans l'ordre · clic = carte interactive (Dispatch)<span v-if="dayNoCoord" class="text-deep-orange-7"> · ⚠ {{ dayNoCoord }} sans coords (absent de la carte)</span></div>
|
||||
</div>
|
||||
<div v-if="!dayEditor.list.length" class="text-grey-6 q-pa-md text-center">Aucun job ce jour.</div>
|
||||
<!-- liste éditable : flèches/glisser pour réordonner · durée en minutes · ✕ pour retirer -->
|
||||
<template v-for="(j, i) in dayEditor.list" :key="j.name">
|
||||
|
|
@ -701,9 +706,7 @@
|
|||
<div class="ellipsis text-grey-6" style="font-size:11px">{{ fmtHM(packedDay[i].startMin) }}–{{ fmtHM(packedDay[i].endMin) }}<span v-if="j.locked" class="text-deep-orange-7"> · 🔒 RDV fixe</span><span v-if="j.customer"> · {{ j.customer }}</span></div>
|
||||
</div>
|
||||
<div class="de-dur"><input type="number" min="5" step="5" :value="jobMinutes(j)" @change="setJobMinutes(j, $event.target.value)" @click.stop @mousedown.stop /><span>min</span></div>
|
||||
<select :value="j.priority" @change="j.priority = $event.target.value" class="de-prio" :style="{ borderColor: prioColor(j.priority) }">
|
||||
<option value="urgent">Urgent</option><option value="high">Élevée</option><option value="medium">Moyenne</option><option value="low">Basse</option>
|
||||
</select>
|
||||
<!-- sélecteur de priorité retiré (défaut « Moyenne » conservé en donnée) → gain de place ; priorité gérée au Dispatch -->
|
||||
<q-btn flat dense round size="sm" :icon="j.locked ? 'lock' : 'lock_open'" :color="j.locked ? 'deep-orange' : 'grey-5'" @click="j.locked = !j.locked"><q-tooltip>{{ j.locked ? 'Heure FIXE (RDV) — verrouillée, non replanifiée' : 'Heure flexible — replanifiée par la tournée' }}</q-tooltip></q-btn>
|
||||
<q-btn flat dense round size="sm" icon="close" color="negative" @click="removeFromDay(j)"><q-tooltip>Retirer du tech (retour au pool)</q-tooltip></q-btn>
|
||||
</div>
|
||||
|
|
@ -1095,6 +1098,31 @@ const packedDay = computed(() => {
|
|||
}
|
||||
return out
|
||||
})
|
||||
// Encodage polyline (precision 5, deltas entiers) pour le tracé de la minimap Mapbox Static.
|
||||
function encodePolyline (coords) {
|
||||
let prevLat = 0, prevLon = 0, out = ''
|
||||
const enc = (cur, prev) => { let v = cur - prev; v = v < 0 ? ~(v << 1) : (v << 1); let s = ''; while (v >= 0x20) { s += String.fromCharCode((0x20 | (v & 0x1f)) + 63); v >>= 5 } s += String.fromCharCode(v + 63); return s }
|
||||
for (const [lat, lon] of coords) { const la = Math.round(lat * 1e5); const lo = Math.round(lon * 1e5); out += enc(la, prevLat) + enc(lo, prevLon); prevLat = la; prevLon = lo }
|
||||
return out
|
||||
}
|
||||
// Minimap du jour (Mapbox Static) : pins numérotés dans l'ordre de tournée + tracé, ajustée au territoire (auto).
|
||||
// → permet de VÉRIFIER d'un coup d'œil que chaque arrêt tombe à la bonne adresse (un pin mal placé = coord à corriger).
|
||||
const hasLL = (j) => j && j.lat != null && j.lon != null && isFinite(+j.lat) && isFinite(+j.lon)
|
||||
const dayMapUrl = computed(() => {
|
||||
if (!MAPBOX_TOKEN) return null
|
||||
const pts = packedDay.value.filter(hasLL).slice(0, 24)
|
||||
if (!pts.length) return null
|
||||
const markers = pts.map((j, idx) => {
|
||||
const hex = (j.skill ? getTagColor(j.skill) : '#1976d2').replace('#', '')
|
||||
const lbl = idx < 9 ? '-' + (idx + 1) : '' // pin-s : label = 1 caractère (1..9) ; au-delà = pin sans numéro
|
||||
return `pin-s${lbl}+${hex}(${(+j.lon).toFixed(5)},${(+j.lat).toFixed(5)})`
|
||||
})
|
||||
const overlays = []
|
||||
if (pts.length > 1) overlays.push(`path-2+3b5bdb-0.55(${encodeURIComponent(encodePolyline(pts.map(j => [+j.lat, +j.lon])))})`) // tracé (ordre)
|
||||
overlays.push(...markers) // marqueurs au-dessus du tracé
|
||||
return `https://api.mapbox.com/styles/v1/mapbox/streets-v12/static/${overlays.join(',')}/auto/520x200@2x?padding=35&access_token=${MAPBOX_TOKEN}`
|
||||
})
|
||||
const dayNoCoord = computed(() => dayEditor.list.filter(j => !hasLL(j)).length)
|
||||
const dayTotalH = () => Math.round(dayEditor.list.reduce((s, j) => s + (Number(j.dur) || 0), 0) * 10) / 10
|
||||
async function removeFromDay (j) {
|
||||
try { await roster.unassignJobRoster(j.name); dayEditor.list = dayEditor.list.filter(x => x.name !== j.name); await loadWeek(); $q.notify({ type: 'info', message: 'Retiré du tech (retour au pool « à assigner »)', timeout: 2200 }) } catch (e) { err(e) }
|
||||
|
|
@ -1838,6 +1866,11 @@ tr.res-hidden .hide-eye { opacity: 1; }
|
|||
.de-row:hover { background: #f7f5fc; }
|
||||
.de-ord { font-size: 12px; font-weight: 700; color: #607d8b; min-width: 16px; text-align: center; }
|
||||
.de-dot { width: 11px; height: 11px; border-radius: 3px; flex: 0 0 auto; }
|
||||
/* minimap du jour (territoire des arrêts) */
|
||||
.de-map-wrap { margin: 8px 0 4px; cursor: pointer; border-radius: 8px; overflow: hidden; border: 1px solid #e0e0e0; }
|
||||
.de-map-wrap:hover { border-color: #3b5bdb; }
|
||||
.de-map { display: block; width: 100%; height: auto; }
|
||||
.de-map-cap { font-size: 10px; color: #777; padding: 3px 6px; background: #fafafa; border-top: 1px solid #eee; }
|
||||
.de-prio { font-size: 11px; border: 1px solid #ccc; border-left-width: 4px; border-radius: 4px; padding: 2px 4px; background: #fff; }
|
||||
.de-dur { display: flex; align-items: center; gap: 2px; font-size: 10px; color: #888; }
|
||||
.de-dur input { width: 46px; font-size: 11px; text-align: right; border: 1px solid #cfc4e8; border-radius: 4px; padding: 2px 3px; }
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user