diff --git a/apps/ops/src/pages/PlanificationPage.vue b/apps/ops/src/pages/PlanificationPage.vue index 9557009..9af830b 100644 --- a/apps/ops/src/pages/PlanificationPage.vue +++ b/apps/ops/src/pages/PlanificationPage.vue @@ -256,14 +256,27 @@ {{ menu.tech && menu.tech.name }} — {{ menu.day && menu.day.dnum }} {{ cellCode(a) }} - {{ a.shift_name || a.shift }} {{ cellHours(a) }}h + {{ a.shift_name || a.shift }} {{ a.hours }}h Retirer Ajouter un shift {{ code(t) }}{{ t.template_name }} - Libérer tout + + Copier cette case + Coller{{ cellClipboard.length ? ' (' + cellClipboard.length + ')' : '' }} + + Ajuster l'horaire (glisser) +
+ +
+ {{ fmtH(menuRange.min) }}h–{{ fmtH(menuRange.max) }}h + + + +
+
@@ -549,6 +562,7 @@ function applyTemplate (tm) { // édition + sélection const menu = reactive({ show: false, target: null, tech: null, day: null }) +const menuRange = ref({ min: 8, max: 16 }); const menuOnCall = ref(0) function rect (sti, sdi, eti, edi) { const t0 = Math.min(sti, eti), t1 = Math.max(sti, eti), d0 = Math.min(sdi, edi), d1 = Math.max(sdi, edi) const out = [] @@ -566,7 +580,10 @@ function onCellClick (t, d, ev, ti, di) { if (justDragged.value) { justDragged.value = false; return } if (ev.shiftKey && anchor.value) { selectBlock(ti, di); return } if (ev.ctrlKey || ev.metaKey) { const k = t.id + '|' + d.iso; selection.value = selSet.value.has(k) ? selection.value.filter(x => x !== k) : [...selection.value, k]; anchor.value = { ti, di }; return } - selection.value = []; anchor.value = { ti, di }; menu.tech = t; menu.day = d; menu.target = ev.currentTarget; menu.show = true + selection.value = []; anchor.value = { ti, di }; menu.tech = t; menu.day = d; menu.target = ev.currentTarget + const wr = winOf(t.id, d.iso, false); const wg = winOf(t.id, d.iso, true); const w0 = wr || wg + menuRange.value = w0 ? { min: w0.s, max: w0.e } : { min: 8, max: 16 }; menuOnCall.value = wr ? 0 : (wg ? 1 : 0) + menu.show = true } function selectBlock (ti, di) { const a = anchor.value; const t0 = Math.min(a.ti, ti); const t1 = Math.max(a.ti, ti); const d0 = Math.min(a.di, di); const d1 = Math.max(a.di, di); const add = []; for (let i = t0; i <= t1; i++) for (let j = d0; j <= d1; j++) add.push(visibleTechs.value[i].id + '|' + dayList.value[j].iso); selection.value = [...new Set([...selection.value, ...add])] } function maybeSelectCol (di) { const ks = visibleTechs.value.map(t => t.id + '|' + dayList.value[di].iso); const all = ks.every(k => selSet.value.has(k)); selection.value = all ? selection.value.filter(k => !ks.includes(k)) : [...new Set([...selection.value, ...ks])] } @@ -575,6 +592,17 @@ const menuCellShifts = computed(() => (menu.tech && menu.day) ? cellsOf(menu.tec function addFromMenu (tpl) { if (menu.tech && menu.day) { pushHistory(); addShift(menu.tech.id, menu.tech.name, menu.day.iso, tpl) } } function removeShiftFromMenu (a) { pushHistory(); removeShift(a.tech, a.date, a.shift) } function clearOne () { if (menu.tech && menu.day) { pushHistory(); clearLocal(menu.tech.id, menu.day.iso); menu.show = false } } +// Menu : copier / coller (marche au clic, sans Cmd+clic) + ajuster l'horaire au slider +function copyFromMenu () { if (!menu.tech || !menu.day) return; cellClipboard.value = cellsOf(menu.tech.id, menu.day.iso).map(a => a.shift); $q.notify({ type: 'positive', message: cellClipboard.value.length ? (cellClipboard.value.length + ' shift(s) copié(s) — ouvre une autre case puis Coller') : 'Case vide copiée (Coller la videra)' }) } +function pasteFromMenu () { if (!menu.tech || !menu.day) return; pushHistory(); if (!cellClipboard.value.length) { clearLocal(menu.tech.id, menu.day.iso) } else { for (const name of cellClipboard.value) { const tpl = tplByName.value[name]; if (tpl) addShift(menu.tech.id, menu.tech.name, menu.day.iso, tpl) } } menu.show = false } +async function applyMenuRange () { + if (!menu.tech || !menu.day) return + const s = numToTime(menuRange.value.min); const e = numToTime(menuRange.value.max) + const nm = fmtH(menuRange.value.min) + 'h–' + fmtH(menuRange.value.max) + 'h' + (menuOnCall.value ? ' (garde)' : '') + let tpl = templates.value.find(t => t.template_name === nm) + if (!tpl) { try { await roster.createTemplate({ template_name: nm, start_time: s + ':00', end_time: e + ':00', hours: calcHours(s, e), color: menuOnCall.value ? '#6d4c41' : '#1976d2', default_required: 1, on_call: menuOnCall.value ? 1 : 0 }); await refreshTemplates(); tpl = templates.value.find(t => t.template_name === nm) } catch (e2) { err(e2); return } } + if (tpl) { pushHistory(); setCellReplace(menu.tech.id, menu.tech.name, menu.day.iso, tpl); menu.show = false } +} function assignBulk (tpl) { pushHistory(); for (const k of selection.value) { const [tid, iso] = k.split('|'); const t = techs.value.find(x => x.id === tid); addShift(tid, t ? t.name : tid, iso, tpl) } selection.value = [] } function clearBulk () { pushHistory(); for (const k of selection.value) { const [tid, iso] = k.split('|'); clearLocal(tid, iso) } selection.value = [] } // Copier-coller une case (bâtir l'horaire vite) : copie les shifts de la 1re case sélectionnée → colle dans les autres