diff --git a/apps/ops/src/pages/PlanificationPage.vue b/apps/ops/src/pages/PlanificationPage.vue index 525a6d1..0d52db2 100644 --- a/apps/ops/src/pages/PlanificationPage.vue +++ b/apps/ops/src/pages/PlanificationPage.vue @@ -253,32 +253,36 @@ - - - {{ menu.tech && menu.tech.name }} — {{ menu.day && menu.day.dnum }} - - {{ cellCode(a) }} - {{ 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) -
+ + + {{ menu.tech && menu.tech.name }} — {{ menu.day && menu.day.dnum }} + +
+ + +
+ +
-
+
{{ fmtH(menuRange.min) }}h–{{ fmtH(menuRange.max) }}h
+ + + + {{ a.shift_name || a.shift }} {{ a.hours }}h + Retirer + +
+ Copier la case + Coller{{ cellClipboard.length ? ' (' + cellClipboard.length + ')' : '' }} + + +
@@ -340,8 +344,6 @@ function dowOf (iso) { const [y, m, d] = iso.split('-').map(Number); return new const tplOptions = computed(() => templates.value.map(t => ({ label: t.template_name, value: t.name }))) const techOptions = computed(() => techs.value.map(t => ({ label: t.name, value: t.id }))) function code (t) { return (t.template_name || t.name || '?').trim()[0].toUpperCase() } -const codeByShift = computed(() => Object.fromEntries(templates.value.map(t => [t.name, code(t)]))) -const colorByShift = computed(() => Object.fromEntries(templates.value.map(t => [t.name, t.color || '#1976d2']))) const tplByName = computed(() => Object.fromEntries(templates.value.map(t => [t.name, t]))) // Modèles triés par usage (les plus utilisés en premier) — pour l'assignation rapide const templatesRanked = computed(() => { @@ -350,8 +352,6 @@ const templatesRanked = computed(() => { }) // Presets « nommés » seulement (Jour/Soir/…) → barre d'assignation + légende propres, même si des modèles auto existent const presetTemplates = computed(() => templatesRanked.value.filter(t => /[a-gi-zA-GI-Z]/.test(t.template_name || ''))) -function cellCode (a) { return codeByShift.value[a.shift] || (a.shift_name || a.shift || '?')[0].toUpperCase() } -function cellColor (a) { return a.color || colorByShift.value[a.shift] || '#1976d2' } function chip (color) { return { background: color || '#1976d2', color: '#fff' } } // techs visibles (recherche + groupe + tri) @@ -609,20 +609,22 @@ function selectBlock (ti, di) { const a = anchor.value; const t0 = Math.min(a.ti 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])] } function maybeSelectRow (ti) { const ks = dayList.value.map(d => visibleTechs.value[ti].id + '|' + d.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])] } const menuCellShifts = computed(() => (menu.tech && menu.day) ? cellsOf(menu.tech.id, menu.day.iso) : []) -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)' : '') +// Applique une fenêtre [min,max] à la case du menu : trouve/crée un modèle auto-nommé puis remplace. +async function applyWindow (min, max, oncall) { + if (!menu.tech || !menu.day || max <= min) return + const s = numToTime(min); const e = numToTime(max) + const nm = fmtH(min) + 'h–' + fmtH(max) + 'h' + (oncall ? ' (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) { try { await roster.createTemplate({ template_name: nm, start_time: s + ':00', end_time: e + ':00', hours: calcHours(s, e), color: oncall ? '#f9a825' : '#1976d2', default_required: 1, on_call: oncall ? 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 quickShift (min, max) { return applyWindow(min, max, 0) } +async function applyMenuRange () { return applyWindow(menuRange.value.min, menuRange.value.max, menuOnCall.value) } 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