feat(planif): Tournées — filtre compétence sur la carte + jour ciblé + jobs non assignés en évidence
Batch B — le filtre de compétence pilote la carte : dayLivePositions (GPS techs) filtré par visibleTechs (les tournées l'étaient déjà via visibleTechs) → seuls les techs capables + leurs jobs sur la carte. Batch C — mise en évidence des non-assignés du jour : - Badge rouge « dus non assignés » par jour sur la bande (OccupancyStrip.os-unassigned), depuis unassignedByDay - routeDayUnassigned = jobs dus le jour sélectionné mais non assignés → bande « N à répartir » (chips cliquables → détail) + bouton « Suggérer » ; se met à jour au clic sur la strip Vérifié préview : positions filtrées par compétence ; badges [1,21,16,15,3,8] sur la bande ; clic 06/07 → bande « 21 à répartir » (14 chips réels). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
012f924683
commit
21724e65f8
|
|
@ -10,7 +10,8 @@
|
|||
@click="$emit('select', d.iso)"
|
||||
>
|
||||
<span v-if="d.holiday" class="os-hol">★</span>
|
||||
<span v-if="d.noShift" class="os-warn" title="Aucun quart planifié — capacité estimée">▲</span>
|
||||
<span v-if="d.noShift && !d.unassigned" class="os-warn" title="Aucun quart planifié — capacité estimée">▲</span>
|
||||
<span v-if="d.unassigned" class="os-unassigned" :title="d.unassigned + ' job(s) dû(s) ce jour, non assigné(s) — à répartir'">{{ d.unassigned }}</span>
|
||||
<span class="os-dow">{{ d.dow }}</span>
|
||||
<span class="os-num">{{ d.dnum }}</span>
|
||||
<span class="os-bar"><span class="os-fill" :style="{ height: fillH(d) + '%', background: heatColor(d.ratio) }"></span></span>
|
||||
|
|
@ -57,6 +58,8 @@ function dayTitle (d) {
|
|||
.os-day.holiday { background: #fff7ed; border-color: #fdba74; }
|
||||
.os-hol { position: absolute; top: 1px; left: 3px; color: #ea580c; font-size: 9px; line-height: 1; }
|
||||
.os-warn { position: absolute; top: 2px; right: 3px; color: #ef4444; font-size: 9px; line-height: 1; pointer-events: none; }
|
||||
/* Badge « dus non assignés ce jour » (flottant coin haut-droit) */
|
||||
.os-unassigned { position: absolute; top: -5px; right: -5px; min-width: 16px; height: 16px; padding: 0 3px; border-radius: 8px; background: #ef4444; color: #fff; font-size: 10px; font-weight: 700; line-height: 16px; text-align: center; box-shadow: 0 1px 3px rgba(2, 6, 23, .3); pointer-events: none; }
|
||||
.os-dow { font-size: 10px; color: var(--ops-text-secondary, #64748b); text-transform: capitalize; }
|
||||
.os-num { font-size: 13px; font-weight: 600; color: var(--ops-text, #1e293b); }
|
||||
.os-bar { width: 10px; height: 44px; background: #eef2f7; border-radius: 5px; overflow: hidden; display: flex; align-items: flex-end; margin: 3px 0; }
|
||||
|
|
|
|||
|
|
@ -495,6 +495,17 @@
|
|||
<q-tooltip>{{ hiddenRouteTechs.has(r.id) ? 'Cliquer pour RÉAFFICHER cette tournée' : 'Cliquer pour retirer cette tournée de la carte' }}</q-tooltip>
|
||||
</span>
|
||||
</div>
|
||||
<!-- Jobs DUS ce jour, NON assignés (mis en évidence) — clic = détail · « Suggérer » = dispatch auto du jour -->
|
||||
<div v-if="routeDayUnassigned.length" class="rt-unassigned">
|
||||
<span class="rt-ua-lbl"><q-icon name="inbox" size="15px" color="deep-orange-7" /> <b>{{ routeDayUnassigned.length }}</b> à répartir :</span>
|
||||
<span v-for="j in routeDayUnassigned.slice(0, 14)" :key="j.name" class="rt-ua-chip" :style="{ borderLeftColor: prioColor(j.priority) }" @click="openJobDetail(j)">
|
||||
<q-icon :name="jobIsOnsite(j) ? 'home_repair_service' : 'cloud'" size="12px" :color="jobIsOnsite(j) ? 'teal' : 'grey-5'" />{{ j.subject || j.service_type || j.name }}<template v-if="jobCity(j)"> · {{ jobCity(j) }}</template>
|
||||
<q-tooltip class="bg-grey-9">{{ j.required_skill || '—' }}<template v-if="j.customer_name"> · {{ j.customer_name }}</template><template v-if="j.address"><br>📍 {{ j.address }}</template></q-tooltip>
|
||||
</span>
|
||||
<span v-if="routeDayUnassigned.length > 14" class="text-caption text-grey-6 q-ml-xs">+{{ routeDayUnassigned.length - 14 }}</span>
|
||||
<q-space />
|
||||
<q-btn dense unelevated no-caps size="sm" color="primary" icon="auto_awesome" label="Suggérer" @click="openSuggest"><q-tooltip>Répartir automatiquement les jobs de ce jour</q-tooltip></q-btn>
|
||||
</div>
|
||||
<RouteMap ref="routesMapRef" :routes="dayRoutes" :live="showLivePos ? dayLivePositions : []" height="62vh" @metrics="m => Object.assign(dayRouteMetrics, m)" @stop-click="onRoutesStopClick" />
|
||||
<div class="suggest-legend q-mt-xs">
|
||||
<span v-for="r in dayRoutes" :key="r.id" class="suggest-leg" style="cursor:pointer" @click="routesMapRef && routesMapRef.fitTo(r.id)" @mouseenter="routesMapRef && routesMapRef.setActive(r.id)" @mouseleave="routesMapRef && routesMapRef.setActive(null)">
|
||||
|
|
@ -4693,10 +4704,12 @@ const routeStripDays = computed(() => {
|
|||
for (let i = 0; i < 14; i++) {
|
||||
const iso = addDaysISO(start.value, i); const c = routeCapacity.value[iso] || {}
|
||||
const h = +c.due_h || 0; const cap = +c.cap_h || 0; const dw = dowOf(iso)
|
||||
out.push({ iso, dow: FR_DOW[dw], dnum: iso.slice(8) + '/' + iso.slice(5, 7), weekend: dw === 0 || dw === 6, holiday: isHoliday(iso), h, cap, ratio: cap > 0 ? h / cap : (h > 0 ? 1 : 0), over: !!c.overbooked, today: iso === todayISO() })
|
||||
out.push({ iso, dow: FR_DOW[dw], dnum: iso.slice(8) + '/' + iso.slice(5, 7), weekend: dw === 0 || dw === 6, holiday: isHoliday(iso), h, cap, ratio: cap > 0 ? h / cap : (h > 0 ? 1 : 0), over: !!c.overbooked, today: iso === todayISO(), unassigned: (unassignedByDay.value[iso] || []).length })
|
||||
}
|
||||
return out
|
||||
})
|
||||
// Jobs DUS le jour sélectionné (Tournées) mais NON assignés — mis en évidence + « Suggérer ». Respecte le filtre de compétence (unassignedByDay).
|
||||
const routeDayUnassigned = computed(() => unassignedByDay.value[routesDay.value] || [])
|
||||
|
||||
// ── Deep-link (?day=YYYY-MM-DD & ?skill=…) + action « planifier des quarts » du slot-finder ──
|
||||
const route = useRoute()
|
||||
|
|
@ -4742,8 +4755,9 @@ function stopLivePositions () { if (_livePosTimer) { clearInterval(_livePosTimer
|
|||
// Positions pour la carte : masque les techs cachés (chips), colore selon leur tournée du jour (sinon gris neutre).
|
||||
const dayLivePositions = computed(() => {
|
||||
const colorByTech = {}; for (const r of (allDayRoutes.value || [])) colorByTech[r.id] = r.color
|
||||
const visIds = new Set((visibleTechs.value || []).map(t => t.id)) // filtre de compétence : seules les positions des techs capables (+ non masqués) sur la carte
|
||||
return (livePosRaw.value || [])
|
||||
.filter(p => !hiddenRouteTechs.value.has(p.techId))
|
||||
.filter(p => visIds.has(p.techId) && !hiddenRouteTechs.value.has(p.techId))
|
||||
.map(p => ({ techId: p.techId, name: p.techName, lat: p.lat, lon: p.lon, time: p.time, speed: p.speed, color: colorByTech[p.techId] || '#455a64' }))
|
||||
})
|
||||
watch(boardView, (v) => { if (v === 'routes') startLivePositions(); else stopLivePositions() }, { immediate: true })
|
||||
|
|
@ -5641,6 +5655,11 @@ onBeforeRouteLeave(() => { if (dirty.value && !window.confirm(DIRTY_MSG)) return
|
|||
.rt-chip.off { opacity: 0.55; border-style: dashed; }
|
||||
.rt-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
|
||||
.rt-n { display: inline-flex; align-items: center; justify-content: center; min-width: 17px; height: 17px; border-radius: 9px; padding: 0 4px; color: #fff; font-size: 10px; font-weight: 800; }
|
||||
/* Bande « jobs dus non assignés » (vue Tournées) : chips cliquables + Suggérer */
|
||||
.rt-unassigned { display: flex; align-items: center; flex-wrap: wrap; gap: 5px; margin-bottom: 6px; padding: 6px 8px; background: #fff7ed; border: 1px solid #fed7aa; border-radius: 8px; }
|
||||
.rt-ua-lbl { font-size: 12px; color: #9a3412; display: inline-flex; align-items: center; gap: 3px; }
|
||||
.rt-ua-chip { display: inline-flex; align-items: center; gap: 3px; max-width: 230px; padding: 2px 8px 2px 6px; border: 1px solid #e2e8f0; border-left: 3px solid #9e9e9e; border-radius: 6px; background: #fff; font-size: 11.5px; color: #334155; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.rt-ua-chip:hover { border-color: #6366f1; }
|
||||
.jt-chip { display: inline-flex; align-items: center; font-size: 9px; font-weight: 700; padding: 1px 4px; margin-right: 2px; border-radius: 6px; border: 1px solid #cbd5e1; color: #64748b; background: #fff; cursor: pointer; user-select: none; line-height: 1.4; }
|
||||
.jt-chip:hover { border-color: #6366f1; }
|
||||
.jt-chip.on { background: #6366f1; color: #fff; border-color: #6366f1; }
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user