Garde: presets Soirs de semaine/Fin de semaine (combinables) + rotation par semaine (défaut) + aperçu
- Plages combinables: « Soirs de semaine » (L-V) + « Fin de semaine » (S-D) en toggles (+ chips fins). - Rotation par défaut = PAR SEMAINE (garde = bloc hebdo) → corrige la séquence brisée (par jour, un week-end pouvait avoir 2 personnes). Index de semaine continu dans le temps → ordre respecté en naviguant. Séquence [A,A,B] = A 2 semaines consécutives, puis B. (Sélecteur jour/semaine conservé.) - APERÇU live : qui est de garde sur les 8 prochaines semaines, reflète la file en cours d'édition → on voit l'ordre respecté + l'effet des modifs avant d'appliquer. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b9d4d46d1c
commit
2b71d1c78c
|
|
@ -286,11 +286,13 @@
|
|||
<q-input dense outlined type="number" min="1" v-model.number="newGardeRule.periodWeeks" :label="newGardeRule.unit === 'week' ? 'Semaines / tech' : 'Jours de garde / tech'" style="width:150px"><q-tooltip>Occurrences consécutives avant de passer au tech suivant (1 = change à chaque {{ newGardeRule.unit === 'week' ? 'semaine' : 'jour de garde' }})</q-tooltip></q-input>
|
||||
</div>
|
||||
<div class="q-mt-sm row items-center q-gutter-xs">
|
||||
<span class="text-caption text-grey-7 q-mr-xs">Jours :</span>
|
||||
<q-chip v-for="dw in GARDE_DOW" :key="dw.v" clickable dense :color="newGardeRule.weekdays.includes(dw.v) ? 'brown' : 'grey-4'" :text-color="newGardeRule.weekdays.includes(dw.v) ? 'white' : 'grey-8'" @click="toggleGardeDow(dw.v)">{{ dw.l }}</q-chip>
|
||||
<q-btn flat dense size="sm" no-caps label="Tous les soirs" @click="newGardeRule.weekdays = [1,2,3,4,5,6,0]" />
|
||||
<q-btn flat dense size="sm" no-caps label="Week-ends" @click="newGardeRule.weekdays = [5,6,0]" />
|
||||
<span class="text-caption text-grey-7 q-mr-xs">Plages (hors bureau) :</span>
|
||||
<q-btn :outline="!isSetActive(WD_SEMAINE)" :unelevated="isSetActive(WD_SEMAINE)" dense size="sm" color="brown" no-caps label="Soirs de semaine" @click="toggleWeekdaysSet(WD_SEMAINE)" />
|
||||
<q-btn :outline="!isSetActive(WD_FINSEM)" :unelevated="isSetActive(WD_FINSEM)" dense size="sm" color="brown" no-caps label="Fin de semaine" @click="toggleWeekdaysSet(WD_FINSEM)" />
|
||||
<span class="text-grey-5 q-mx-xs">·</span>
|
||||
<q-chip v-for="dw in GARDE_DOW" :key="dw.v" clickable dense size="sm" :color="newGardeRule.weekdays.includes(dw.v) ? 'brown' : 'grey-4'" :text-color="newGardeRule.weekdays.includes(dw.v) ? 'white' : 'grey-8'" @click="toggleGardeDow(dw.v)">{{ dw.l }}</q-chip>
|
||||
</div>
|
||||
<div class="text-caption text-grey-6 q-mt-xs">Combinables. La garde couvre les heures du shift choisi (hors bureau) ; pour des heures différentes semaine/week-end, crée 2 règles.</div>
|
||||
<div class="row items-end q-gutter-sm q-mt-sm">
|
||||
<q-select dense outlined v-model="gardePick" :options="techOptions" emit-value map-options label="Ajouter un tech à la suite" style="min-width:210px" />
|
||||
<q-btn dense unelevated color="grey-7" icon="add" label="Ajouter" :disable="!gardePick" @click="addTechToSeq" />
|
||||
|
|
@ -305,6 +307,12 @@
|
|||
<q-btn flat dense round size="xs" icon="close" color="grey-6" @click="newGardeRule.techs.splice(i, 1)"><q-tooltip>Retirer</q-tooltip></q-btn>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="gardePreview.length" class="q-mt-sm bg-grey-1 rounded-borders q-pa-sm">
|
||||
<div class="text-caption text-weight-medium">Aperçu (qui est de garde, prochaines semaines) :</div>
|
||||
<div class="row q-gutter-xs q-mt-xs">
|
||||
<q-chip v-for="(p, i) in gardePreview" :key="i" dense size="sm" color="brown-1" text-color="brown-9">sem. {{ p.week.slice(8) }}/{{ p.week.slice(5, 7) }} → {{ p.name }}</q-chip>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row items-center q-mt-md">
|
||||
<q-btn dense unelevated color="brown" :icon="editingGardeId ? 'save' : 'add'" :label="editingGardeId ? 'Mettre à jour' : 'Ajouter la règle'" @click="addGardeRule" />
|
||||
<q-btn v-if="editingGardeId" flat dense class="q-ml-xs" label="Annuler" @click="editingGardeId = null; newGardeRule.techs = []; newGardeRule.weekdays = []" />
|
||||
|
|
@ -379,7 +387,7 @@ const activeCell = ref(null) // dernière case cliquée {id, name, iso} — pour
|
|||
const anchor = ref(null)
|
||||
const demand = ref([]); const holidays = ref([]); const weekTemplates = ref([])
|
||||
const gardeRules = ref([]); const showGarde = ref(false)
|
||||
const newGardeRule = reactive({ dept: '', shift: '', weekdays: [], periodWeeks: 1, unit: 'occ', techs: [] })
|
||||
const newGardeRule = reactive({ dept: '', shift: '', weekdays: [], periodWeeks: 1, unit: 'week', techs: [] })
|
||||
const GARDE_DOW = [{ v: 1, l: 'L' }, { v: 2, l: 'M' }, { v: 3, l: 'M' }, { v: 4, l: 'J' }, { v: 5, l: 'V' }, { v: 6, l: 'S' }, { v: 0, l: 'D' }]
|
||||
const history = ref([]); const future = ref([])
|
||||
const search = ref(''); const groupFilter = ref(null); const maxHours = ref(40)
|
||||
|
|
@ -611,7 +619,10 @@ const groupNames = computed(() => [...new Set(techs.value.map(t => t.group).filt
|
|||
const editingGardeId = ref(null); const gardePick = ref(null)
|
||||
function addTechToSeq () { if (gardePick.value) { newGardeRule.techs.push(gardePick.value); gardePick.value = null } } // doublons permis (tours inégaux)
|
||||
function moveTech (i, dir) { const a = newGardeRule.techs; const j = i + dir; if (j < 0 || j >= a.length) return; const x = a[i]; a.splice(i, 1); a.splice(j, 0, x) }
|
||||
function editGardeRule (r) { Object.assign(newGardeRule, { dept: r.dept || '', shift: r.shift, weekdays: [...r.weekdays], periodWeeks: r.periodWeeks || 1, unit: r.unit || 'occ', techs: [...r.techs] }); editingGardeId.value = r.id }
|
||||
function editGardeRule (r) { Object.assign(newGardeRule, { dept: r.dept || '', shift: r.shift, weekdays: [...r.weekdays], periodWeeks: r.periodWeeks || 1, unit: r.unit || 'week', techs: [...r.techs] }); editingGardeId.value = r.id }
|
||||
const WD_SEMAINE = [1, 2, 3, 4, 5]; const WD_FINSEM = [6, 0]
|
||||
function isSetActive (set) { return set.length && set.every(v => newGardeRule.weekdays.includes(v)) }
|
||||
function toggleWeekdaysSet (set) { if (isSetActive(set)) newGardeRule.weekdays = newGardeRule.weekdays.filter(v => !set.includes(v)); else newGardeRule.weekdays = [...new Set([...newGardeRule.weekdays, ...set])] }
|
||||
function d2ms (iso) { const a = iso.split('-').map(Number); return Date.UTC(a[0], a[1] - 1, a[2]) }
|
||||
function mondayISO (iso) { return addDaysISO(iso, -((dowOf(iso) + 6) % 7)) }
|
||||
function weekIndex (iso) { return Math.round((d2ms(mondayISO(iso)) - d2ms(GARDE_EPOCH)) / (7 * 86400000)) }
|
||||
|
|
@ -624,7 +635,20 @@ function occurrenceIndex (rule, iso) {
|
|||
return cnt
|
||||
}
|
||||
// Index de rotation : par OCCURRENCE de garde (défaut → la séquence continue chaque jour de garde) ou par SEMAINE (bloc).
|
||||
function gardeIndex (rule, iso) { return Math.floor(((rule.unit || 'occ') === 'week' ? weekIndex(iso) : occurrenceIndex(rule, iso)) / (rule.periodWeeks || 1)) }
|
||||
function gardeIndex (rule, iso) { return Math.floor(((rule.unit || 'week') === 'week' ? weekIndex(iso) : occurrenceIndex(rule, iso)) / (rule.periodWeeks || 1)) }
|
||||
// Aperçu : qui est de garde sur les prochaines semaines (depuis la semaine affichée) — reflète la file en cours d'édition
|
||||
const gardePreview = computed(() => {
|
||||
const rule = newGardeRule; if (!rule.techs.length || !rule.weekdays.length) return []
|
||||
const out = []; const wk0 = mondayISO(start.value)
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const ws = addDaysISO(wk0, i * 7); let iso = null
|
||||
for (let k = 0; k < 7; k++) { const d = addDaysISO(ws, k); if (rule.weekdays.includes(dowOf(d))) { iso = d; break } }
|
||||
if (!iso) continue
|
||||
const base = gardeIndex(rule, iso); const id = rule.techs[((base % rule.techs.length) + rule.techs.length) % rule.techs.length]
|
||||
out.push({ week: ws, name: (techs.value.find(t => t.id === id) || {}).name || id })
|
||||
}
|
||||
return out
|
||||
})
|
||||
// Tech de garde pour une date ; saute un tech absent au profit du suivant.
|
||||
function rotationTech (rule, iso) {
|
||||
if (!rule.techs || !rule.techs.length) return null
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user