Planification: barre de temps pâle + barre de statut opaque vert→orange (occupation)

- Barre de TEMPS (dispo) = pâle (bleu très pâle matin → violet pâle soir) : repère discret du quand.
- Barre de STATUT (occupé) = OPAQUE vert (peu) → orange (plein) → rouge (surbooké), positionnée
  aux vraies heures des jobs → ressort nettement sur le fond pâle ; les trous pâles = offrables.
- Légende: dispo (matin→soir) pâle + occupation (vert→orange) + garde.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
louispaulb 2026-06-04 19:22:05 -04:00
parent d7867e2a62
commit 94c7566dd3

View File

@ -92,7 +92,8 @@
<div class="row items-center q-gutter-xs q-mb-sm text-caption"> <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> <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> <span class="text-grey-7 q-mr-xs">Légende :</span>
<span class="tod-leg"></span><span class="text-grey-7 q-mr-sm">matin soir</span> <span class="tod-leg"></span><span class="text-grey-7 q-mr-sm">dispo (matin soir)</span>
<span class="occ-leg"></span><span class="text-grey-7 q-mr-sm">occupation</span>
<span class="tod-garde"></span><span class="text-grey-7 q-mr-sm">garde</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="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="free q-mr-xs">·</span><span class="text-grey-7 q-mr-sm">libre</span>
@ -402,8 +403,8 @@ const axisTicks = computed(() => {
return out return out
}) })
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) + '%' } } 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) + '%' } }
// Couleur selon l'heure : bleu pâle le matin violet le soir // Barre de temps PÂLE : bleu très pâle le matin violet pâle le soir (repère discret du « quand »)
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 todColor (h) { const t = Math.max(0, Math.min(1, (h - 6) / 15)); return 'hsl(' + Math.round(210 + t * 60) + ',45%,' + Math.round(91 - t * 8) + '%)' }
function bandGradient (s, e) { return 'linear-gradient(to right, ' + todColor(s) + ', ' + todColor(e) + ')' } function bandGradient (s, e) { return 'linear-gradient(to right, ' + todColor(s) + ', ' + todColor(e) + ')' }
// Bandes = chaque shift. Régulier = dégradé matinsoir ; garde (on_call) = hachuré (réserve). // Bandes = chaque shift. Régulier = dégradé matinsoir ; garde (on_call) = hachuré (réserve).
function cellBands (techId, iso) { function cellBands (techId, iso) {
@ -417,8 +418,9 @@ function cellBands (techId, iso) {
} }
return out return out
} }
// Occupé = assombrit la dispo (clair = libre/offrable) ; rouge si surbooké. // Barre de statut OPAQUE selon l'occupation : vert (peu) orange (plein) rouge (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)' } } function occColor (pct) { if (pct == null) return '#9e9e9e'; if (pct >= 100) return '#e53935'; const t = Math.max(0, Math.min(1, pct / 100)); return 'hsl(' + Math.round(122 - t * 90) + ',68%,44%)' }
function blockStyle (blk, pct) { return { ...pos(blk.s, Math.min(blk.e, 24)), background: occColor(pct) } }
// Fenêtre des shifts (garde=true seulement les quarts de garde ; garde=false réguliers) // 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 } 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 }
const occCells = computed(() => { const occCells = computed(() => {
@ -681,7 +683,8 @@ th.clk, td.clk { cursor: pointer; }
.tl-shift { position: absolute; top: 0; bottom: 0; background: #ccd2d8; border-radius: 1px; } /* fenêtre dispo = neutre */ .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-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; } /* occupé = assombrit la dispo */ .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-leg { display: inline-block; width: 46px; height: 9px; border-radius: 2px; vertical-align: middle; background: linear-gradient(to right, hsl(210,45%,91%), hsl(270,45%,83%)); }
.occ-leg { display: inline-block; width: 46px; height: 9px; border-radius: 2px; vertical-align: middle; background: linear-gradient(to right, hsl(122,68%,44%), hsl(32,68%,44%)); }
.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; } .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; } tr.paused .tech-col { color: #aaa; }
tfoot .sum td { background: #fafafa; font-size: 11px; color: #555; font-weight: 600; } tfoot .sum td { background: #fafafa; font-size: 11px; color: #555; font-weight: 600; }