feat(ops): filter chips scope Suggérer + skill-matched techs (Tournées)
Suite au carrousel : les chips de filtre de la bande « à répartir » pilotent maintenant AUSSI « Suggérer » (« ce que je filtre = ce que je répartis »). - Chips PRIORITÉ (normalisée high/medium/low, via POOL_PRIOS) ajoutés à côté des secteurs ; filtrent la liste + la carte (routeDayUnassignedView = jour + compétence + secteur + priorité). Affichés seulement si >1 priorité présente (comme les secteurs). - suggestJobs() : en vue Tournées, le périmètre = routeDayUnassignedView EXACTEMENT (corrige l'incohérence : avant, les chips secteur étaient ignorés par Suggérer alors que l'infobulle disait « du secteur »). - openSuggest() : pré-sélection des techs par COMPÉTENCE — parmi les disponibles (quart ce jour), ceux dont t.skills couvre un required_skill des jobs du périmètre ; repli sur tous les dispos si aucun match. Toujours modifiable (le dialogue liste les 56 techs, cochables). Vérifié live (06/07) : secteur Huntingdon → vue 4 jobs → Suggérer affiche « 4 job » (avant : ignorait le secteur) + 13/56 techs pré-cochés (compétents+dispos, reste modifiable). 0 nouvelle erreur console. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
b7b1647ece
commit
cef6507f5f
|
|
@ -503,6 +503,12 @@
|
|||
<button v-for="s in routeSectors" :key="s.sector" type="button" class="rt-sect" :class="{ on: routeSectorSel.has(s.sector) }" @click="toggleRouteSector(s.sector)"><q-icon name="place" size="11px" />{{ s.sector }}<span class="rt-sect-n">{{ s.n }}</span></button>
|
||||
<button v-if="routeSectorSel.size" type="button" class="rt-sect rt-sect-x" @click="routeSectorSel = new Set()">✕ tout</button>
|
||||
</template>
|
||||
<template v-if="routePrios.length > 1">
|
||||
<span class="rt-chip-sep" />
|
||||
<button v-for="p in routePrios" :key="p.value" type="button" class="rt-sect rt-prio" :class="{ on: routePrioSel.has(p.value) }"
|
||||
:style="routePrioSel.has(p.value) ? { background: p.color, borderColor: p.color, color: '#fff' } : { borderColor: p.color, color: p.color }"
|
||||
@click="toggleRoutePrio(p.value)"><q-icon name="flag" size="11px" />{{ p.label }}<span class="rt-sect-n">{{ p.n }}</span></button>
|
||||
</template>
|
||||
<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 {{ routeSectorSel.size ? 'du secteur' : 'de ce jour' }}</q-tooltip></q-btn>
|
||||
</div>
|
||||
|
|
@ -4424,7 +4430,13 @@ function openSuggest () {
|
|||
const win = suggestWindow.value // fenêtre = chips de date cochés, sinon le jour du sélecteur
|
||||
const techs = visibleTechs.value || []
|
||||
const withShift = techs.filter(t => win.some(iso => hasShiftDay(t.id, iso)))
|
||||
const base = withShift.length ? withShift : techs
|
||||
const availPool = withShift.length ? withShift : techs
|
||||
// Pré-sélection par COMPÉTENCE : parmi les dispos, ceux dont les compétences couvrent au moins un skill requis
|
||||
// par les jobs du périmètre (jour + filtres). Toujours modifiable (le dialogue liste tous les techs, cochables) ;
|
||||
// repli sur tous les dispos si aucun skill requis connu ou aucun match.
|
||||
const needed = new Set((suggestJobs() || []).map(j => j.required_skill).filter(Boolean))
|
||||
const skilled = needed.size ? availPool.filter(t => (t.skills || []).some(s => needed.has(s))) : []
|
||||
const base = skilled.length ? skilled : availPool
|
||||
const sel = {}; base.forEach(t => { sel[t.id] = true }); suggestDlg.techSel = sel
|
||||
suggestDlg.plan = []; suggestDlg.mode = 'config'; suggestDlg.open = true
|
||||
}
|
||||
|
|
@ -4443,6 +4455,8 @@ const suggestWindow = computed(() => {
|
|||
// par les CHIPS de TYPE **et** de DATE (assignJobsFiltered). Ex. décocher « téléphonie » → non réparti aux techs terrain.
|
||||
const suggestJobs = () => {
|
||||
if (selectedNames.value.length) return (assignPanel.jobs || []).filter(j => j.status !== 'On Hold' && selectedJobs[j.name]) // sélection lasso explicite
|
||||
// Vue Tournées : périmètre = EXACTEMENT la bande filtrée (jour sélectionné + compétence + secteur + priorité) → « ce que je filtre = ce que Suggérer répartit ».
|
||||
if (boardView.value === 'routes') return (routeDayUnassignedView.value || []).filter(j => j.status !== 'On Hold')
|
||||
let jobs = (assignJobsFiltered.value || []).filter(j => j.status !== 'On Hold') // pool tel qu'affiché (type + date)
|
||||
// Par DÉFAUT (aucun chip de date coché) : ne répartir QUE les jobs DATÉS sur le(s) jour(s) de la fenêtre — jamais les
|
||||
// jobs en retard/sans date/d'autres jours (souvent faits mais pas encore fermés) qui pollueraient la simulation.
|
||||
|
|
@ -4729,10 +4743,20 @@ const routeStripDays = computed(() => {
|
|||
const routeDayUnassigned = computed(() => unassignedByDay.value[routesDay.value] || [])
|
||||
// Chips SECTEUR (par ville) des non-assignés du jour → filtre la liste ET la carte. Reset au changement de jour.
|
||||
const routeSectorSel = ref(new Set())
|
||||
watch(routesDay, () => { routeSectorSel.value = new Set() })
|
||||
const routePrioSel = ref(new Set())
|
||||
watch(routesDay, () => { routeSectorSel.value = new Set(); routePrioSel.value = new Set() })
|
||||
const routeSectors = computed(() => { const m = {}; for (const j of routeDayUnassigned.value) { const c = jobCity(j) || 'Sans secteur'; m[c] = (m[c] || 0) + 1 } return Object.entries(m).map(([sector, n]) => ({ sector, n })).sort((a, b) => b.n - a.n) })
|
||||
function toggleRouteSector (s) { const set = new Set(routeSectorSel.value); if (set.has(s)) set.delete(s); else set.add(s); routeSectorSel.value = set }
|
||||
const routeDayUnassignedView = computed(() => { const sel = routeSectorSel.value; return sel.size ? routeDayUnassigned.value.filter(j => sel.has(jobCity(j) || 'Sans secteur')) : routeDayUnassigned.value })
|
||||
// Chips PRIORITÉ (normalisée high/medium/low) — filtrent la liste + la carte comme les secteurs, et (avec eux) le périmètre de « Suggérer ».
|
||||
const NORM_PRIO = { high: 'high', urgent: 'high', 'élevée': 'high', elevee: 'high', medium: 'medium', moyenne: 'medium', normal: 'medium', low: 'low', basse: 'low' }
|
||||
const normPrio = j => NORM_PRIO[String(j.priority || 'medium').toLowerCase()] || 'medium'
|
||||
const routePrios = computed(() => { const m = {}; for (const j of routeDayUnassigned.value) { const p = normPrio(j); m[p] = (m[p] || 0) + 1 } return POOL_PRIOS.filter(pp => m[pp.value]).map(pp => ({ ...pp, n: m[pp.value] })) })
|
||||
function toggleRoutePrio (p) { const set = new Set(routePrioSel.value); if (set.has(p)) set.delete(p); else set.add(p); routePrioSel.value = set }
|
||||
const routeDayUnassignedView = computed(() => {
|
||||
const sel = routeSectorSel.value; const ps = routePrioSel.value
|
||||
if (!sel.size && !ps.size) return routeDayUnassigned.value
|
||||
return routeDayUnassigned.value.filter(j => (!sel.size || sel.has(jobCity(j) || 'Sans secteur')) && (!ps.size || ps.has(normPrio(j))))
|
||||
})
|
||||
const _jlat = j => (j.latitude != null ? +j.latitude : (j.lat != null ? +j.lat : null))
|
||||
const _jlon = j => (j.longitude != null ? +j.longitude : (j.lon != null ? +j.lon : null))
|
||||
// Gouttes carte = non-assignés du jour (filtrés secteur) qui ont des coordonnées ; couleur = priorité
|
||||
|
|
@ -5695,6 +5719,7 @@ onBeforeRouteLeave(() => { if (dirty.value && !window.confirm(DIRTY_MSG)) return
|
|||
.rt-sect.on { background: #6366f1; border-color: #6366f1; color: #fff; }
|
||||
.rt-sect-n { font-weight: 800; margin-left: 2px; opacity: .85; }
|
||||
.rt-sect-x { border-style: dashed; color: #94a3b8; }
|
||||
.rt-chip-sep { width: 1px; align-self: stretch; min-height: 16px; background: #fed7aa; margin: 0 2px; }
|
||||
.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; }
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user