Garde: la rotation avance par jour de garde (la séquence continue sur les jours affichés)
Avant: rotation par semaine → dans une vue d'1 semaine, un seul membre apparaissait. Maintenant la rotation avance par OCCURRENCE de garde (occurrenceIndex = nb de jours matching weekdays depuis l'époque) → chaque jour de garde prend le membre suivant de la séquence (A,A,B,C…), visible sur toute la vue. Sélecteur « Rotation : par jour de garde / par semaine » (+ N consécutifs). Défaut = par jour ; les règles existantes (sans unit) basculent en par-jour. Saut d'absent + doublons conservés. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d0ab57b1b5
commit
16325ed967
|
|
@ -270,7 +270,7 @@
|
|||
<q-item v-for="(r, i) in gardeRules" :key="r.id">
|
||||
<q-item-section>
|
||||
<q-item-label class="text-weight-medium">{{ r.dept }} · {{ shiftName(r.shift) }}</q-item-label>
|
||||
<q-item-label caption>{{ gardeDowLabel(r) }} · toutes les {{ r.periodWeeks }} sem. · rotation : {{ r.techs.map(id => (techs.find(t => t.id === id) || {}).name || id).join(' → ') }}</q-item-label>
|
||||
<q-item-label caption>{{ gardeDowLabel(r) }} · change tous les {{ r.periodWeeks }} {{ (r.unit || 'occ') === 'week' ? 'sem.' : 'jour(s) de garde' }} · rotation : {{ r.techs.map(id => (techs.find(t => t.id === id) || {}).name || id).join(' → ') }}</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section side class="row no-wrap">
|
||||
<q-btn flat dense round size="sm" icon="edit" color="primary" @click="editGardeRule(r)"><q-tooltip>Modifier (ordre, techs, période)</q-tooltip></q-btn>
|
||||
|
|
@ -282,7 +282,8 @@
|
|||
<div class="row q-col-gutter-sm items-end">
|
||||
<q-select dense outlined v-model="newGardeRule.dept" :options="groupNames" use-input fill-input hide-selected new-value-mode="add-unique" input-debounce="0" label="Département (optionnel)" style="width:180px" hint="existant ou tape un nom" />
|
||||
<q-select dense outlined v-model="newGardeRule.shift" :options="gardeTemplateOptions" emit-value map-options label="Shift de garde" style="width:190px" />
|
||||
<q-input dense outlined type="number" min="1" v-model.number="newGardeRule.periodWeeks" label="Sem. consécutives / tech" style="width:160px"><q-tooltip>2 = chaque tech fait 2 semaines de suite avant de passer au suivant</q-tooltip></q-input>
|
||||
<q-select dense outlined v-model="newGardeRule.unit" :options="[{ label: 'par jour de garde', value: 'occ' }, { label: 'par semaine', value: 'week' }]" emit-value map-options label="Rotation" style="width:150px" />
|
||||
<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>
|
||||
|
|
@ -378,7 +379,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, techs: [] })
|
||||
const newGardeRule = reactive({ dept: '', shift: '', weekdays: [], periodWeeks: 1, unit: 'occ', 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)
|
||||
|
|
@ -610,14 +611,24 @@ 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, 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 || 'occ', techs: [...r.techs] }); editingGardeId.value = r.id }
|
||||
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)) }
|
||||
// Tech de garde pour une date : tourne toutes les periodWeeks ; saute un tech absent au profit du suivant.
|
||||
// Nb de jours de garde (matching weekdays) depuis l'époque, AVANT cette date → fait avancer la séquence jour par jour.
|
||||
function occurrenceIndex (rule, iso) {
|
||||
const days = Math.round((d2ms(iso) - d2ms(GARDE_EPOCH)) / 86400000); if (days <= 0) return 0
|
||||
const wd = rule.weekdays || []; let cnt = Math.floor(days / 7) * wd.length
|
||||
const epochDow = dowOf(GARDE_EPOCH)
|
||||
for (let i = 0; i < days % 7; i++) if (wd.includes((epochDow + i) % 7)) cnt++
|
||||
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)) }
|
||||
// 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
|
||||
const base = Math.floor(weekIndex(iso) / (rule.periodWeeks || 1))
|
||||
const base = gardeIndex(rule, iso)
|
||||
for (let k = 0; k < rule.techs.length; k++) { const id = rule.techs[((base + k) % rule.techs.length + rule.techs.length) % rule.techs.length]; if (!isAbsent(id, iso)) return id }
|
||||
return rule.techs[((base % rule.techs.length) + rule.techs.length) % rule.techs.length]
|
||||
}
|
||||
|
|
@ -625,7 +636,7 @@ function toggleGardeDow (v) { const i = newGardeRule.weekdays.indexOf(v); if (i
|
|||
function saveGarde () { localStorage.setItem(LS_GARDE, JSON.stringify(gardeRules.value)) }
|
||||
function addGardeRule () {
|
||||
if (!newGardeRule.shift || !newGardeRule.techs.length || !newGardeRule.weekdays.length) { $q.notify({ type: 'warning', message: 'Shift, jours et techs requis (département optionnel)' }); return }
|
||||
const rule = { id: editingGardeId.value || Date.now(), dept: newGardeRule.dept || '—', shift: newGardeRule.shift, weekdays: [...newGardeRule.weekdays], periodWeeks: newGardeRule.periodWeeks || 1, techs: [...newGardeRule.techs] }
|
||||
const rule = { id: editingGardeId.value || Date.now(), dept: newGardeRule.dept || '—', shift: newGardeRule.shift, weekdays: [...newGardeRule.weekdays], periodWeeks: newGardeRule.periodWeeks || 1, unit: newGardeRule.unit || 'occ', techs: [...newGardeRule.techs] }
|
||||
if (editingGardeId.value) gardeRules.value = gardeRules.value.map(r => r.id === editingGardeId.value ? rule : r)
|
||||
else gardeRules.value = [...gardeRules.value, rule]
|
||||
saveGarde(); editingGardeId.value = null; newGardeRule.techs = []; newGardeRule.weekdays = []
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user