Planification: ne montrer que les presets nommés + coller multi-cases fiable + fix Cmd+C/V
- Nettoyage des modèles auto en double fait côté données (8h–16h→Jour, 7h–15h→Matinal, 9h–17h→Décalé, 8h–17h→Jour, 8h–22h→Soir) — restent 5 presets propres. - Barre d'assignation + légende = presetTemplates (modèles NOMMÉS seulement) → restent propres même si des modèles auto réapparaissent. - Fix copier-coller : le clic ouvrait le menu ET vidait la sélection → Cmd+C voyait 0 cellule. Maintenant on mémorise activeCell (dernière case cliquée) ; Cmd+C/V ferment le menu et marchent sans multi-sélection. Coller = vers la sélection si multi, sinon la case active. - Indicateur « N copié(s) » visible. Coller multi-cases via la barre (déjà) + Cmd+V sur sélection. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7f6d314cc0
commit
142ce45755
|
|
@ -81,7 +81,7 @@
|
|||
|
||||
<q-banner v-if="selection.length" dense rounded class="bg-teal-1 text-teal-9 q-mb-sm">
|
||||
{{ selection.length }} cellule(s) — assigner :
|
||||
<q-btn v-for="t in templatesRanked" :key="t.name" dense unelevated size="sm" class="q-mx-xs" :style="chip(t.color)" :label="code(t)" @click="assignBulk(t)"><q-tooltip>{{ t.template_name }}</q-tooltip></q-btn>
|
||||
<q-btn v-for="t in presetTemplates" :key="t.name" dense unelevated size="sm" class="q-mx-xs" :style="chip(t.color)" :label="code(t)" @click="assignBulk(t)"><q-tooltip>{{ t.template_name }}</q-tooltip></q-btn>
|
||||
<q-separator vertical class="q-mx-xs" />
|
||||
<q-btn dense flat size="sm" icon="content_copy" label="Copier" @click="copyCell" />
|
||||
<q-btn dense flat size="sm" icon="content_paste" :label="cellClipboard.length ? ('Coller (' + cellClipboard.length + ')') : 'Coller'" :disable="!cellClipboard.length" @click="pasteCells" />
|
||||
|
|
@ -90,8 +90,9 @@
|
|||
</q-banner>
|
||||
|
||||
<div class="row items-center q-gutter-xs q-mb-sm text-caption">
|
||||
<q-chip v-if="cellClipboard.length" dense size="sm" color="indigo" text-color="white" icon="content_paste">{{ cellClipboard.length }} copié(s)</q-chip>
|
||||
<span class="text-grey-7 q-mr-xs">Légende :</span>
|
||||
<template v-for="t in templates" :key="t.name"><span class="code-chip" :style="chip(t.color)">{{ code(t) }}</span><span class="text-grey-7 q-mr-sm">{{ t.template_name }}</span></template>
|
||||
<template v-for="t in presetTemplates" :key="t.name"><span class="code-chip" :style="chip(t.color)">{{ code(t) }}</span><span class="text-grey-7 q-mr-sm">{{ t.template_name }}</span></template>
|
||||
<span class="code-chip" style="background:#e0e0e0;color:#777">P</span><span class="text-grey-7 q-mr-sm">pause</span>
|
||||
<span class="free q-mr-xs">·</span><span class="text-grey-7 q-mr-sm">libre</span>
|
||||
<span class="cell-dirty-demo q-mr-xs">J</span><span class="text-grey-7 q-mr-sm">modifié (non publié)</span>
|
||||
|
|
@ -307,6 +308,7 @@ const showDemand = ref(false)
|
|||
const drag = reactive({ on: false, ti: 0, di: 0, moved: false, base: [] })
|
||||
const justDragged = ref(false)
|
||||
const selection = ref([])
|
||||
const activeCell = ref(null) // dernière case cliquée {id, name, iso} — pour copier/coller au clavier sans multi-sélection
|
||||
const anchor = ref(null)
|
||||
const demand = ref([]); const holidays = ref([]); const weekTemplates = ref([])
|
||||
const history = ref([]); const future = ref([])
|
||||
|
|
@ -345,6 +347,8 @@ const templatesRanked = computed(() => {
|
|||
const cnt = {}; for (const a of assignments.value) cnt[a.shift] = (cnt[a.shift] || 0) + 1
|
||||
return templates.value.slice().sort((x, y) => (cnt[y.name] || 0) - (cnt[x.name] || 0) || (x.template_name || '').localeCompare(y.template_name || ''))
|
||||
})
|
||||
// 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' } }
|
||||
|
|
@ -578,6 +582,7 @@ function removeShift (techId, iso, shift) { assignments.value = assignments.valu
|
|||
function clearLocal (techId, iso) { assignments.value = assignments.value.filter(x => !(x.tech === techId && x.date === iso)) }
|
||||
function onCellClick (t, d, ev, ti, di) {
|
||||
if (justDragged.value) { justDragged.value = false; return }
|
||||
activeCell.value = { id: t.id, name: t.name, iso: d.iso } // mémorise la case pour Cmd+C/V
|
||||
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
|
||||
|
|
@ -607,8 +612,19 @@ function assignBulk (tpl) { pushHistory(); for (const k of selection.value) { co
|
|||
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
|
||||
const cellClipboard = ref([])
|
||||
function copyCell () { const k = selection.value[0]; if (!k) { $q.notify({ type: 'warning', message: 'Sélectionne une case à copier' }); return } const [tid, iso] = k.split('|'); cellClipboard.value = cellsOf(tid, iso).map(a => a.shift); $q.notify({ type: 'positive', message: cellClipboard.value.length ? (cellClipboard.value.length + ' shift(s) copié(s) — sélectionne des cases puis Coller') : 'Case vide copiée (Coller videra les cases)' }) }
|
||||
function pasteCells () { if (!selection.value.length) { $q.notify({ type: 'warning', message: 'Sélectionne les cases cibles' }); return } pushHistory(); for (const k of selection.value) { const [tid, iso] = k.split('|'); const t = techs.value.find(x => x.id === tid); if (!cellClipboard.value.length) { clearLocal(tid, iso); continue } for (const name of cellClipboard.value) { const tpl = tplByName.value[name]; if (tpl) addShift(tid, t ? t.name : tid, iso, tpl) } } selection.value = [] }
|
||||
function copyCell () {
|
||||
const k = selection.value[0] || (activeCell.value && (activeCell.value.id + '|' + activeCell.value.iso))
|
||||
if (!k) { $q.notify({ type: 'warning', message: 'Clique ou sélectionne une case d\'abord' }); return }
|
||||
const [tid, iso] = k.split('|'); cellClipboard.value = cellsOf(tid, iso).map(a => a.shift)
|
||||
$q.notify({ type: 'positive', message: cellClipboard.value.length ? (cellClipboard.value.length + ' shift(s) copié(s) — sélectionne des cases puis Coller (ou Cmd+V)') : 'Case vide copiée (Coller videra les cases)' })
|
||||
}
|
||||
function pasteCells () {
|
||||
const targets = selection.value.length ? selection.value.slice() : (activeCell.value ? [activeCell.value.id + '|' + activeCell.value.iso] : [])
|
||||
if (!targets.length) { $q.notify({ type: 'warning', message: 'Sélectionne les cases cibles' }); return }
|
||||
pushHistory()
|
||||
for (const k of targets) { const [tid, iso] = k.split('|'); const t = techs.value.find(x => x.id === tid); if (!cellClipboard.value.length) { clearLocal(tid, iso); continue } for (const name of cellClipboard.value) { const tpl = tplByName.value[name]; if (tpl) addShift(tid, t ? t.name : tid, iso, tpl) } }
|
||||
if (selection.value.length) selection.value = []
|
||||
}
|
||||
|
||||
async function togglePause (t) { try { const paused = !isPaused(t); await roster.pauseTechnician(t.id, paused); t.status = paused ? 'En pause' : 'Disponible'; $q.notify({ type: 'info', message: t.name + (paused ? ' en pause' : ' réactivé') }) } catch (e) { err(e) } }
|
||||
function err (e) { $q.notify({ type: 'negative', message: '' + (e.message || e) }) }
|
||||
|
|
@ -616,8 +632,8 @@ function err (e) { $q.notify({ type: 'negative', message: '' + (e.message || e)
|
|||
function onKey (e) {
|
||||
const k = e.key.toLowerCase()
|
||||
if ((e.ctrlKey || e.metaKey) && k === 'z') { e.preventDefault(); if (e.shiftKey) redo(); else undo(); return }
|
||||
if ((e.ctrlKey || e.metaKey) && k === 'c' && selection.value.length) { e.preventDefault(); copyCell(); return }
|
||||
if ((e.ctrlKey || e.metaKey) && k === 'v' && selection.value.length) { e.preventDefault(); pasteCells() }
|
||||
if ((e.ctrlKey || e.metaKey) && k === 'c' && (selection.value.length || activeCell.value)) { e.preventDefault(); menu.show = false; copyCell(); return }
|
||||
if ((e.ctrlKey || e.metaKey) && k === 'v' && (selection.value.length || activeCell.value)) { e.preventDefault(); menu.show = false; pasteCells() }
|
||||
}
|
||||
function onUnload (e) { if (dirty.value) { e.preventDefault(); e.returnValue = '' } }
|
||||
onMounted(async () => { loadLS(); document.addEventListener('keydown', onKey); document.addEventListener('mouseup', onUp); window.addEventListener('beforeunload', onUnload); try { await loadBase() } catch (e) { err(e) } await loadWeek() })
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user