refactor(planif): clic progressbar → timeline éditable Dispatch (tech+jour) au lieu du popup maison

- réutilisation max + cohérence : le clic sur le progressbar ouvre le tableau Dispatch focalisé sur
  le tech + le jour cliqué (gotoDispatch(t, d.iso)) = LE timeline éditable (drag-drop réordonner, supprimer/désaffecter)
- retire le popup cellJobsMenu (réordonner/priorité) → règle aussi le chevauchement avec l'infobulle mouseover
- (endpoint /roster/reorder-jobs conservé, réutilisable ; le réordonnancement se fait désormais côté Dispatch)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
louispaulb 2026-06-06 13:19:59 -04:00
parent b7b7da783b
commit 455a66aeb9

View File

@ -183,7 +183,7 @@
<div class="tl"><div class="tl-absent"></div><q-tooltip class="bg-grey-9">Absent · {{ absenceLabel(t.id, d.iso) }}</q-tooltip></div>
</template>
<template v-else-if="hasReg(t.id, d.iso) || onGarde(t.id, d.iso)">
<div class="tl tl-click" @mousedown.stop @click.stop="openCellJobs(t, d, $event)">
<div class="tl tl-click" @mousedown.stop @click.stop="gotoDispatch(t, d.iso)">
<div v-for="(b, bi) in cellBands(t.id, d.iso)" :key="'b' + bi" class="tl-shift" :class="{ oncall: b.oncall }" :style="{ left: b.left, width: b.width, background: b.bg || undefined }"></div>
<div v-for="(b, bi) in cellBlocks(t.id, d.iso)" :key="'j' + bi" class="tl-blk" :style="blockStyle(b, cellPct(t.id, d.iso))"></div>
<!-- Aperçu d'occupation projetée pendant le drag : barre fantôme + delta -->
@ -658,36 +658,6 @@
</div>
</q-list>
</q-menu>
<!-- Menu « jobs de la cellule » : clic sur le progressbar détail + réordonner / re-prioriser -->
<q-menu v-model="cellJobsMenu.show" :target="cellJobsMenu.target" anchor="bottom left" self="top left" max-height="80vh">
<div style="width:340px;padding:6px 8px" @click.stop @mousedown.stop>
<div class="row items-center q-mb-xs">
<div class="text-weight-bold ellipsis">{{ cellJobsMenu.tech && cellJobsMenu.tech.name }} {{ cellJobsMenu.day && cellJobsMenu.day.dnum }}</div>
<q-space /><span class="text-caption text-grey-6">{{ cellJobsMenu.list.length }} job(s)</span>
</div>
<div v-if="!cellJobsMenu.list.length" class="text-grey-6 q-pa-sm text-center">Aucun job assigné ce jour.</div>
<div v-for="(j, i) in cellJobsMenu.list" :key="j.name" class="cjm-row">
<div class="column" style="gap:0">
<q-btn flat dense round size="8px" icon="keyboard_arrow_up" :disable="i === 0" @click="moveCellJob(i, -1)" />
<q-btn flat dense round size="8px" icon="keyboard_arrow_down" :disable="i === cellJobsMenu.list.length - 1" @click="moveCellJob(i, 1)" />
</div>
<span class="cjm-ord">{{ i + 1 }}</span>
<span class="cjm-dot" :style="{ background: j.skill ? getTagColor(j.skill) : prioColor(j.priority) }"></span>
<div class="col" style="min-width:0">
<div class="ellipsis" style="font-size:12px;font-weight:500">{{ j.subject }}</div>
<div class="ellipsis" style="font-size:10px;color:#888">{{ j.start || '—' }} · {{ j.dur }}h<span v-if="j.customer"> · {{ j.customer }}</span><span v-if="j.skill"> · {{ j.skill }}</span></div>
</div>
<select :value="j.priority" @change="j.priority = $event.target.value" @click.stop class="cjm-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>
</div>
<div v-if="cellJobsMenu.list.length" class="row items-center q-mt-xs">
<span class="text-caption text-grey-6">Flèches = ordre du tech · menu = priorité</span><q-space />
<q-btn dense unelevated size="sm" color="primary" :loading="cellJobsMenu.saving" label="Enregistrer" @click="saveCellOrder" />
</div>
</div>
</q-menu>
</q-page>
</template>
@ -986,25 +956,13 @@ function panelUp () { _panelDrag = null; document.removeEventListener('mousemove
// Réutilise les helpers de cellule (cellBands/cellBlocks/cellJobs/cellPct) 0 nouvel appel réseau.
const timelineDlg = reactive({ open: false, tech: null })
function openTimeline (t) { timelineDlg.tech = t; timelineDlg.open = true }
// Menu « jobs de la cellule » : détail + réordonner / re-prioriser (clic sur le progressbar)
const cellJobsMenu = reactive({ show: false, target: null, tech: null, day: null, list: [], saving: false })
function openCellJobs (t, d, ev) {
cellJobsMenu.tech = t; cellJobsMenu.day = d
cellJobsMenu.list = cellJobs(t.id, d.iso).map(j => ({ ...j })) // copie ordonnée (route_order priorité heure)
cellJobsMenu.target = (ev && ev.currentTarget) || null
cellJobsMenu.show = true
}
function moveCellJob (i, dir) { const l = cellJobsMenu.list; const j = i + dir; if (j < 0 || j >= l.length) return; const [x] = l.splice(i, 1); l.splice(j, 0, x) }
async function saveCellOrder () {
const updates = cellJobsMenu.list.map((j, i) => ({ job: j.name, route_order: i + 1, priority: j.priority }))
cellJobsMenu.saving = true
try { const r = await roster.reorderJobs(updates); cellJobsMenu.show = false; await loadWeek(); $q.notify({ type: 'positive', message: (r.updated || 0) + ' job(s) réordonné(s)', timeout: 2000 }) } catch (e) { err(e) } finally { cellJobsMenu.saving = false }
}
// Deep-link vers le tableau Dispatch focalisé sur la ressource + son 1er jour avec jobs (sinon début de semaine).
function gotoDispatch (t) {
// (Clic sur le progressbar gotoDispatch : on ouvre le timeline ÉDITABLE du tableau Dispatch, drag-drop + suppression,
// plutôt qu'un popup maison réutilisation max + cohérence. Le réordonnancement/priorité se fait là-bas.)
// Deep-link vers le tableau Dispatch focalisé sur la ressource + le jour cliqué (sinon 1er jour de la semaine).
function gotoDispatch (t, dateIso) {
const q = {}
if (t) q.tech = t.id
q.date = (timelineDays.value[0] && timelineDays.value[0].iso) || start.value
q.date = dateIso || (timelineDays.value[0] && timelineDays.value[0].iso) || start.value
router.push({ path: '/dispatch', query: q })
}
const timelineDays = computed(() => {