Journée :
-
+
Positions GPS des techs (Traccar), rafraîchies ~25 s. Cliquer pour {{ showLivePos ? 'masquer' : 'afficher' }}.
{{ dayRoutes.length }} tournée(s) · clic tech = zoom · clic arrêt = détail
@@ -1794,7 +1795,7 @@ import * as roster from 'src/api/roster'
import * as addressApi from 'src/api/address' // recherche d'adresse RQA (coords) pour le sélecteur d'emplacement
import { useSSE, sendSmsViaHub } from 'src/composables/useSSE'
import { MAPBOX_TOKEN } from 'src/config/erpnext' // routage routier réel (API Mapbox Matrix), déjà utilisé par le Dispatch
-import { legacyDeptColor } from 'src/composables/useHelpers' // coloriage par type « comme legacy » (partagé avec le board Dispatch)
+import { legacyDeptColor, heatColor } from 'src/composables/useHelpers' // coloriage par type « comme legacy » (partagé) + heatColor (source unique, aussi )
import { useUserPrefs } from 'src/composables/useUserPrefs' // préférences d'affichage par utilisateur (serveur)
import { relTime } from 'src/composables/useFormatters' // temps relatif COHÉRENT (même formateur que la Boîte)
import { messageIdentity, priorityMeta, PRIORITY_LEVELS } from 'src/composables/useConversationDisplay' // resolvers partagés + priorité (drapeau) réutilisée des conversations
@@ -1804,6 +1805,7 @@ import TechSelect from 'src/components/shared/TechSelect.vue'
import SkillSelect from 'src/components/shared/SkillSelect.vue'
import TagEditor from 'src/components/shared/TagEditor.vue' // module de tags partagé (Dispatch) : condensé, création à la volée, couleurs
import RouteMap from 'src/components/shared/RouteMap.vue' // carte de tournées réutilisable (revue dispatch auto + onglet Tournées) : OSRM réel, arrêts numérotés, fitTo
+import OccupancyStrip from 'src/components/shared/OccupancyStrip.vue' // bande d'occupation réutilisable (sélecteur de jour Tournées + tableau de bord)
import UnifiedCreateModal from 'src/components/shared/UnifiedCreateModal.vue' // création de job NATIVE OPS (work-order) — Dispatch déprécié, tout ici
import SuggestSlotsDialog from 'src/modules/dispatch/components/SuggestSlotsDialog.vue' // « Trouver un créneau » → pré-remplit la création (tech+date+heure+adresse)
import WeeklyScheduleEditor from 'src/components/shared/WeeklyScheduleEditor.vue' // génération de quarts hebdo par tech (modèles) — repris/amélioré depuis Dispatch
@@ -1874,6 +1876,9 @@ const estLoadByDay = computed(() => {
})
function estLoad (iso) { return estLoadByDay.value[iso] || 0 }
function loadClass (iso) { const est = estLoad(iso), dispo = visStat(iso).hours || 0; if (!dispo) return est > 0 ? 'cap-full' : 'cap-none'; const r = est / dispo; return r > 1 ? 'cap-full' : (r > 0.85 ? 'cap-low' : 'cap-ok') } // vert/orange/rouge selon est/dispo
+// Barre verticale d'occupation de l'en-tête desktop (miroir de la bande mobile) : ratio charge estimée / heures de quart.
+function hdrRatio (iso) { const cap = visStat(iso).hours || 0, load = estLoad(iso) || 0; return cap > 0 ? load / cap : (load > 0 ? 1 : 0) }
+function hdrFillH (iso) { return (estLoad(iso) || 0) > 0 ? Math.max(12, Math.min(100, Math.round(hdrRatio(iso) * 100))) : 0 }
// Vue « jobs prévus en en-tête de jour » : jobs NON assignés ayant une date, groupés par jour, glissables sur une case
// tech×jour (réutilise onJobDragStart/onCellDrop → assigne + replanifie à la date de la case). Suit le filtre compétence.
const showDayJobs = ref(false)
@@ -3981,7 +3986,7 @@ function hasShiftDay (techId, iso) {
// ── Vue mobile « bande de jours + heat » (téléphone) : charge agrégée par jour + par tech, lecture rapide. ──
const todayIso = computed(() => (nowET.value && nowET.value.iso) || new Date().toISOString().slice(0, 10))
-function heatColor (ratio) { const r = +ratio || 0; if (r >= 1) return '#ef4444'; if (r >= 0.75) return '#f59e0b'; if (r >= 0.45) return '#84cc16'; return '#22c55e' }
+// heatColor → composables/useHelpers.js (source unique, partagé avec + tableau de bord)
const _CAP_H = 8 // capacité indicative par tech-jour (h) pour normaliser les barres
const mobileSelIso = ref(null)
// Bande de jours mobile = fenêtre LONGUE (4 semaines) → carrousel horizontal CONTINU (sans flèches).
@@ -4676,6 +4681,11 @@ const suggestRoutes = computed(() => {
watch(() => suggestDlg.view, (v) => { if (v === 'map' && !suggestMapDay.value) suggestMapDay.value = suggestWindow.value[0] || '' })
// ── P3 — Onglet « Tournées » : tournées RÉELLES (jobs assignés, occupation) de la journée choisie, 1 couleur/tech, tracés OSRM (RouteMap). ──
const routesDay = ref(null)
+// Sélecteur de jour (Tournées) enrichi : bande d'occupation (barres de charge) au lieu de simples chips « lun 29/06 ».
+const routeStripDays = computed(() => dayList.value.slice(0, 7).map(d => {
+ const load = estLoad(d.iso) || 0; const cap = visStat(d.iso).hours || 0
+ return { iso: d.iso, dow: d.dow, dnum: d.dnum, weekend: d.weekend, holiday: isHoliday(d.iso), h: load, cap, ratio: hdrRatio(d.iso), over: cap > 0 && load > cap, today: d.iso === todayISO() }
+}))
const routesMapRef = ref(null)
const dayRouteMetrics = reactive({})
const hiddenRouteTechs = ref(new Set()) // chips : techs RETIRÉS de la carte (couleur/badge conservés, réactivables d'un clic)
@@ -5505,7 +5515,10 @@ onBeforeRouteLeave(() => { if (dirty.value && !window.confirm(DIRTY_MSG)) return
.loc-results { max-height: 190px; overflow: auto; border-radius: 6px; }
.loc-pick-link { cursor: pointer; text-decoration: underline; font-weight: 600; }
/* Bande de capacité restante par jour (AM/PM/Soir) dans l'en-tête */
-.cap-strip { display: flex; gap: 2px; justify-content: center; align-items: center; margin-top: 2px; }
+.cap-strip { display: flex; gap: 3px; justify-content: center; align-items: center; margin-top: 2px; }
+/* Barre verticale d'occupation (miroir de la bande mobile) dans l'en-tête desktop */
+.hdr-vbar { width: 7px; height: 22px; background: #eef2f7; border-radius: 4px; overflow: hidden; display: inline-flex; align-items: flex-end; flex: 0 0 auto; }
+.hdr-vfill { width: 100%; border-radius: 4px; transition: height .3s; }
.cap-seg { font-size: 9px; font-weight: 700; line-height: 1; min-width: 15px; padding: 2px 2px; border-radius: 3px; text-align: center; cursor: default; }
.cap-ok { background: #c8e6c9; color: #1b5e20; }
.cap-low { background: #ffe0b2; color: #e65100; }