feat(planif): « Suggérer » respecte aussi le filtre de TYPE du pool

Étend le filtre du dispatch auto au type affiché (« ce que je vois = ce que
je répartis ») : suggestJobs part désormais de assignJobsFiltered (type + date)
au lieu des seules dates. Défaut inchangé (aucun filtre = tout le pool part).
Levier concret : décocher « téléphonie » → non réparti aux techs terrain.

- suggestJobs : base = assignJobsFiltered (type ∩ date) hors On Hold ; lasso prioritaire.
- suggestFiltered + suggestScope : bouton « Suggérer (N) » et dialogue
  « N job(s) des jours + types cochés → jours » quels que soient les filtres actifs.
- Aucun changement hub : optimize-plan filtre déjà par job_names (déployé).

Vérifié preview : réparation seul → 18 ; réparation + 07-06 → 5 arrêts,
tous réparation, tous le 07-06 (heuristique locale).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
louispaulb 2026-07-03 12:09:18 -04:00
parent 6c5ed0060f
commit 8d80b44a79

View File

@ -880,7 +880,7 @@
<div class="assign-hdr" @mousedown="panelHeaderDown"> <div class="assign-hdr" @mousedown="panelHeaderDown">
<q-icon name="drag_indicator" size="18px" /><span>Jobs à assigner ({{ assignTypeFilter.length ? assignJobsFiltered.length + '/' + assignPanel.jobs.length : assignPanel.jobs.length }})</span><q-space /> <q-icon name="drag_indicator" size="18px" /><span>Jobs à assigner ({{ assignTypeFilter.length ? assignJobsFiltered.length + '/' + assignPanel.jobs.length : assignPanel.jobs.length }})</span><q-space />
<q-btn v-if="assignNoCoord" flat dense no-caps size="sm" color="warning" icon="wrong_location" :label="String(assignNoCoord)" :loading="assignPanel.locating" class="q-mr-xs" @click="batchLocatePool"><q-tooltip>Localiser les {{ assignNoCoord }} job(s) sans coordonnées : adresse base RQA + GPS (ils apparaissent ensuite sur la carte)</q-tooltip></q-btn> <q-btn v-if="assignNoCoord" flat dense no-caps size="sm" color="warning" icon="wrong_location" :label="String(assignNoCoord)" :loading="assignPanel.locating" class="q-mr-xs" @click="batchLocatePool"><q-tooltip>Localiser les {{ assignNoCoord }} job(s) sans coordonnées : adresse base RQA + GPS (ils apparaissent ensuite sur la carte)</q-tooltip></q-btn>
<q-btn flat dense no-caps size="sm" color="amber-4" icon="auto_awesome" :label="(selectedNames.length || assignDateFilter.length) ? 'Suggérer (' + suggestJobCount + ')' : 'Suggérer'" :loading="suggestDlg.building" class="q-mr-xs" @click="openSuggest"><q-tooltip>Proposer une répartition optimisée (proximité · charge · compétence){{ selectedNames.length ? ' pour la sélection lasso' : (assignDateFilter.length ? ' pour les jours cochés' : '') }} tu revois et ajustes avant d'appliquer</q-tooltip></q-btn> <q-btn flat dense no-caps size="sm" color="amber-4" icon="auto_awesome" :label="suggestFiltered ? 'Suggérer (' + suggestJobCount + ')' : 'Suggérer'" :loading="suggestDlg.building" class="q-mr-xs" @click="openSuggest"><q-tooltip>Proposer une répartition optimisée (proximité · charge · compétence){{ suggestFiltered ? ' — ' + suggestJobCount + ' job(s) ' + suggestScope : '' }} tu revois et ajustes avant d'appliquer</q-tooltip></q-btn>
<q-btn flat dense round size="sm" icon="map" :color="assignPanel.showMap ? 'amber-4' : 'white'" @click="toggleAssignMap"><q-tooltip>Carte des jobs (regrouper par région)</q-tooltip></q-btn> <q-btn flat dense round size="sm" icon="map" :color="assignPanel.showMap ? 'amber-4' : 'white'" @click="toggleAssignMap"><q-tooltip>Carte des jobs (regrouper par région)</q-tooltip></q-btn>
<q-btn flat dense round size="sm" icon="refresh" color="white" :loading="assignPanel.loading" @click="openAssignPanel" /> <q-btn flat dense round size="sm" icon="refresh" color="white" :loading="assignPanel.loading" @click="openAssignPanel" />
<q-btn flat dense round size="sm" icon="close" color="white" @click="assignPanel.open = false" /> <q-btn flat dense round size="sm" icon="close" color="white" @click="assignPanel.open = false" />
@ -1023,8 +1023,8 @@
<!-- FENÊTRE de répartition = les CHIPS de date cochés dans le pool (ou la sélection lasso). Sinon un jour choisi ici. --> <!-- FENÊTRE de répartition = les CHIPS de date cochés dans le pool (ou la sélection lasso). Sinon un jour choisi ici. -->
<div class="row items-center q-mb-sm" style="gap:6px;color:#475569;background:#eef2ff;border-radius:6px;padding:5px 8px"> <div class="row items-center q-mb-sm" style="gap:6px;color:#475569;background:#eef2ff;border-radius:6px;padding:5px 8px">
<q-icon name="event" size="16px" color="primary" /><span class="text-caption text-weight-medium">Répartir :</span> <q-icon name="event" size="16px" color="primary" /><span class="text-caption text-weight-medium">Répartir :</span>
<template v-if="selectedNames.length || assignDateFilter.length"> <template v-if="suggestFiltered">
<span class="text-caption text-weight-medium text-indigo-8">{{ suggestJobCount }} job(s) {{ selectedNames.length ? 'sélectionnés' : 'des jours cochés' }}</span> <span class="text-caption text-weight-medium text-indigo-8">{{ suggestJobCount }} job(s) {{ suggestScope }}</span>
<q-icon name="arrow_forward" size="13px" color="grey-6" /><span class="text-caption text-grey-7">{{ suggestWindow.map(windowDayLabel).join(', ') }}</span> <q-icon name="arrow_forward" size="13px" color="grey-6" /><span class="text-caption text-grey-7">{{ suggestWindow.map(windowDayLabel).join(', ') }}</span>
<q-space /><span class="text-caption text-grey-6">{{ selectedNames.length ? 'sélection lasso' : 'ajuste les chips sous le pool' }}</span> <q-space /><span class="text-caption text-grey-6">{{ selectedNames.length ? 'sélection lasso' : 'ajuste les chips sous le pool' }}</span>
</template> </template>
@ -4383,14 +4383,23 @@ const suggestWindow = computed(() => {
if (f.includes('__overdue__') || f.includes('Sans date') || !days.size) days.add(todayISO()) // en retard / sans date placés aujourd'hui if (f.includes('__overdue__') || f.includes('Sans date') || !days.size) days.add(todayISO()) // en retard / sans date placés aujourd'hui
return [...days].sort() return [...days].sort()
}) })
// Jobs que « Suggérer » doit répartir : la sélection lasso si active, sinon le pool filtré par les CHIPS de date. // Jobs que « Suggérer » doit répartir : la sélection lasso si active, sinon « ce que je vois » = le pool filtré
// par les CHIPS de TYPE **et** de DATE (assignJobsFiltered). Ex. décocher « téléphonie » non réparti aux techs terrain.
const suggestJobs = () => { const suggestJobs = () => {
const useSel = selectedNames.value.length > 0 if (selectedNames.value.length) return (assignPanel.jobs || []).filter(j => j.status !== 'On Hold' && selectedJobs[j.name]) // sélection lasso explicite
let jobs = (assignPanel.jobs || []).filter(j => j.status !== 'On Hold') return (assignJobsFiltered.value || []).filter(j => j.status !== 'On Hold') // pool tel qu'affiché (type + date)
jobs = useSel ? jobs.filter(j => selectedJobs[j.name]) : jobs.filter(jobMatchesDateChips)
return jobs
} }
const suggestJobCount = computed(() => suggestJobs().length) const suggestJobCount = computed(() => suggestJobs().length)
// Y a-t-il un filtre restreignant le dispatch ? (lasso, chips de date ou de type)
const suggestFiltered = computed(() => !!(selectedNames.value.length || assignDateFilter.value.length || assignTypeFilter.value.length))
// Libellé du périmètre : « sélectionnés » (lasso) · « des jours/types cochés » (chips) décrit ce qui limite le jeu de jobs.
const suggestScope = computed(() => {
if (selectedNames.value.length) return 'sélectionnés'
const parts = []
if (assignDateFilter.value.length) parts.push('jours')
if (assignTypeFilter.value.length) parts.push('types')
return parts.length ? ('des ' + parts.join(' + ') + ' cochés') : 'du pool'
})
const windowDayLabel = iso => iso === todayISO() ? "Aujourd'hui" : (iso === tomorrowISO() ? 'Demain' : (fmtDueLabel(iso) || iso)) const windowDayLabel = iso => iso === todayISO() ? "Aujourd'hui" : (iso === tomorrowISO() ? 'Demain' : (fmtDueLabel(iso) || iso))
const suggestWindowLabel = computed(() => { const suggestWindowLabel = computed(() => {
const w = suggestWindow.value; if (!w.length) return '—' const w = suggestWindow.value; if (!w.length) return '—'