Planification: barre de dispo dégradée par l'heure (bleu matin → violet soir), fini les lettres
- Cellules: plus de chip-lettre (J/S/M/D). La bande de la timeline est colorée par l'HEURE : dégradé bleu pâle le matin → violet le soir (hsl 210→270). On lit 'quand' d'un coup d'œil. - Occupé = assombrit la bande (clair = libre/offrable, foncé = pris) ; rouge si surbooké. (Remplace le feu vert/orange/rouge — plus sobre.) Garde reste hachurée. - Légende: échantillon dégradé « matin → soir » + « garde » (hachuré) au lieu de la liste de lettres. - Intervalle (8–16) gardé en texte. occColor retiré. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
142ce45755
commit
738d315785
|
|
@ -92,7 +92,8 @@
|
|||
<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 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="tod-leg"></span><span class="text-grey-7 q-mr-sm">matin → soir</span>
|
||||
<span class="tod-garde"></span><span class="text-grey-7 q-mr-sm">garde</span>
|
||||
<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>
|
||||
|
|
@ -123,11 +124,10 @@
|
|||
<td v-for="(d, di) in dayList" :key="d.iso" class="cell" :class="{ weekend: d.weekend, holiday: isHoliday(d.iso), sel: isSelected(t.id, d.iso), dirty: isCellDirty(t.id, d.iso) }" @mousedown="onDown(ti, di, $event)" @mouseenter="onEnter(ti, di)" @click="onCellClick(t, d, $event, ti, di)">
|
||||
<template v-if="cellsOf(t.id, d.iso).length">
|
||||
<div class="cell-chips">
|
||||
<span v-for="(a, ai) in cellsOf(t.id, d.iso)" :key="ai" class="code-chip" :style="chip(cellColor(a))">{{ cellCode(a) }}</span>
|
||||
<span v-if="cellOcc(t.id, d.iso)" class="cell-int">{{ cellLabel(t.id, d.iso) }}</span>
|
||||
</div>
|
||||
<div v-if="cellOcc(t.id, d.iso)" class="tl">
|
||||
<div v-for="(b, bi) in cellBands(t.id, d.iso)" :key="'b' + bi" class="tl-shift" :class="{ oncall: b.oncall }" :style="{ left: b.left, width: b.width }"></div>
|
||||
<div v-for="(b, bi) in cellBands(t.id, d.iso)" :key="'b' + bi" class="tl-shift" :class="{ oncall: b.oncall }" :style="{ left: b.left, width: b.width, background: b.bg || undefined }"></div>
|
||||
<div v-for="(b, bi) in cellOcc(t.id, d.iso).blocks" :key="'j' + bi" class="tl-blk" :style="blockStyle(b, cellOcc(t.id, d.iso).pct)"></div>
|
||||
<q-tooltip class="bg-grey-9">{{ cellInterval(t.id, d.iso) }}<template v-if="cellOcc(t.id, d.iso).bookableH"> · {{ cellOcc(t.id, d.iso).usedH }} h occupé / {{ cellOcc(t.id, d.iso).bookableH }} h offrable ({{ cellOcc(t.id, d.iso).pct }} %)</template></q-tooltip>
|
||||
</div>
|
||||
|
|
@ -397,18 +397,23 @@ const axisBounds = computed(() => {
|
|||
return { min: lo, max: hi }
|
||||
})
|
||||
function pos (s, e) { const b = axisBounds.value; const span = (b.max - b.min) || 24; const L = Math.max(0, (s - b.min) / span * 100); const R = Math.min(100, (e - b.min) / span * 100); return { left: L + '%', width: Math.max(1.5, R - L) + '%' } }
|
||||
// Bandes = chaque shift du jour. Garde (on_call) = bande hachurée (réserve, non offrable).
|
||||
// Couleur selon l'heure : bleu pâle le matin → violet le soir
|
||||
function todColor (h) { const t = Math.max(0, Math.min(1, (h - 6) / 15)); return 'hsl(' + Math.round(210 + t * 60) + ',' + Math.round(62 - t * 6) + '%,' + Math.round(83 - t * 22) + '%)' }
|
||||
function bandGradient (s, e) { return 'linear-gradient(to right, ' + todColor(s) + ', ' + todColor(e) + ')' }
|
||||
// Bandes = chaque shift. Régulier = dégradé matin→soir ; garde (on_call) = hachuré (réserve).
|
||||
function cellBands (techId, iso) {
|
||||
const out = []
|
||||
for (const a of cellsOf(techId, iso)) {
|
||||
const t = tplByName.value[a.shift]; if (!t) continue
|
||||
const oc = !!t.on_call
|
||||
const s = hToNum(t.start_time); const e = hToNum(t.end_time); if (s == null || e == null) continue
|
||||
if (e <= s) { out.push({ ...pos(s, 24), oncall: oc }); out.push({ ...pos(0, e), oncall: oc }) } else out.push({ ...pos(s, e), oncall: oc }) // chevauche minuit
|
||||
if (e <= s) { out.push({ ...pos(s, 24), oncall: oc, bg: oc ? null : bandGradient(s, 24) }); out.push({ ...pos(0, e), oncall: oc, bg: oc ? null : bandGradient(0, e) }) }
|
||||
else out.push({ ...pos(s, e), oncall: oc, bg: oc ? null : bandGradient(s, e) }) // chevauche minuit
|
||||
}
|
||||
return out
|
||||
}
|
||||
function blockStyle (blk, pct) { return { ...pos(blk.s, Math.min(blk.e, 24)), background: pct == null ? '#6d4c41' : occColor(pct) } }
|
||||
// Occupé = assombrit la dispo (clair = libre/offrable) ; rouge si surbooké.
|
||||
function blockStyle (blk, pct) { return { ...pos(blk.s, Math.min(blk.e, 24)), background: (pct != null && pct >= 100) ? 'rgba(229,57,53,.6)' : 'rgba(0,0,0,.28)' } }
|
||||
// Fenêtre des shifts (garde=true → seulement les quarts de garde ; garde=false → réguliers)
|
||||
function winOf (techId, iso, garde) { let s = Infinity; let e = -Infinity; for (const a of cellsOf(techId, iso)) { const t = tplByName.value[a.shift]; if (!t || (!!t.on_call) !== garde) continue; const st = hToNum(t.start_time); const en = hToNum(t.end_time); if (st != null) s = Math.min(s, st); if (en != null) e = Math.max(e, en) } return isFinite(s) ? { s, e } : null }
|
||||
// Libellé inline = intervalle début–fin seulement (pas d'icône : le timeline + l'heure suffisent)
|
||||
|
|
@ -425,7 +430,6 @@ const occCells = computed(() => {
|
|||
return m
|
||||
})
|
||||
function cellOcc (techId, iso) { return occCells.value[techId + '|' + iso] || null }
|
||||
function occColor (pct) { return pct >= 100 ? '#e53935' : pct >= 70 ? '#fb8c00' : '#43a047' }
|
||||
function cellInterval (techId, iso) {
|
||||
return cellsOf(techId, iso).map(a => { const t = tplByName.value[a.shift]; const nm = (t && t.template_name) ? t.template_name.split(' ')[0] + ' ' : ''; const tag = (t && t.on_call) ? ' (garde)' : ''; return (t && t.start_time) ? (nm + t.start_time.slice(0, 5) + '–' + (t.end_time || '').slice(0, 5) + tag) : (a.shift_name || a.shift) }).join(' + ')
|
||||
}
|
||||
|
|
@ -672,7 +676,9 @@ th.clk, td.clk { cursor: pointer; }
|
|||
.tl { position: relative; height: 8px; min-width: 58px; background: #f1f3f5; border-radius: 2px; margin-top: 3px; overflow: hidden; }
|
||||
.tl-shift { position: absolute; top: 0; bottom: 0; background: #ccd2d8; border-radius: 1px; } /* fenêtre dispo = neutre */
|
||||
.tl-shift.oncall { background: repeating-linear-gradient(45deg, #d7ccc8 0, #d7ccc8 2px, transparent 2px, transparent 4px); box-shadow: inset 0 0 0 1px #a1887f; } /* garde = hachuré (réserve, non offrable) */
|
||||
.tl-blk { position: absolute; top: 0; bottom: 0; border-radius: 1px; opacity: .95; } /* occupé = trait coloré */
|
||||
.tl-blk { position: absolute; top: 0; bottom: 0; border-radius: 1px; } /* occupé = assombrit la dispo */
|
||||
.tod-leg { display: inline-block; width: 46px; height: 9px; border-radius: 2px; vertical-align: middle; background: linear-gradient(to right, hsl(210,62%,83%), hsl(270,56%,61%)); }
|
||||
.tod-garde { display: inline-block; width: 24px; height: 9px; border-radius: 2px; vertical-align: middle; background: repeating-linear-gradient(45deg,#d7ccc8 0,#d7ccc8 2px,transparent 2px,transparent 4px); box-shadow: inset 0 0 0 1px #a1887f; }
|
||||
tr.paused .tech-col { color: #aaa; }
|
||||
tfoot .sum td { background: #fafafa; font-size: 11px; color: #555; font-weight: 600; }
|
||||
tfoot .sum .tech-col { background: #fafafa; }
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user