gigafibre-fsm/apps/ops/src/components/shared/OccupancyStrip.vue
louispaulb b9df6d720a fix(ops/mobile): bandes d'occupation ≤7 jours remplissent la largeur (fini le micro-défilement)
Les cellules de jour étaient à largeur fixe (52px) → une bande de 7 jours (~400px)
débordait de ~57px sur mobile et défilait légèrement (6,5 jours visibles).

Override mobile `.os-day { flex:1 0 40px; width:auto }` : jusqu'à 7 jours, les
cellules s'étirent pour remplir la largeur (tableau de bord « cette/prochaine
semaine » = 7 tiennent pile). Au-delà (tournées = bande 14 j) elles gardent leur
plancher 40px et la bande défile comme avant.

Vérifié 375px : tableau de bord 7 j = ne défile plus (jour 41px) ; Planif 14 j =
défile toujours (jour 40px). Portée mobile uniquement.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 20:58:43 -04:00

75 lines
4.5 KiB
Vue

<template>
<!-- Bande d'occupation réutilisable : 1 cellule par jour, barre verticale « chaleur » (vert → rouge).
Extraite de la bande mobile Planif (.pm-strip). Utilisée : tableau de bord (charge de la semaine),
et partout où un aperçu de charge par jour est utile. Clic → @select(iso). -->
<div class="os-strip">
<button
v-for="d in days" :key="d.iso" type="button" class="os-day"
:class="{ active: d.iso === selected, today: d.today, weekend: d.weekend, holiday: d.holiday }"
:title="dayTitle(d)"
@click="$emit('select', d.iso)"
>
<span v-if="d.holiday" class="os-hol">★</span>
<span v-if="d.noShift && !d.unassigned" class="os-warn" title="Aucun quart planifié — capacité estimée">▲</span>
<span v-if="d.unassigned" class="os-unassigned" :title="d.unassigned + ' job(s) (s) ce jour, non assigné(s) à répartir'">{{ d.unassigned }}</span>
<span class="os-dow">{{ d.dow }}</span>
<span class="os-num">{{ d.dnum }}</span>
<span class="os-bar"><span class="os-fill" :style="{ height: fillH(d) + '%', background: heatColor(d.ratio) }"></span></span>
<span class="os-h">{{ d.cap > 0 ? Math.round(d.h) + '/' + Math.round(d.cap) : (Math.round(d.h) + 'h') }}<span v-if="d.over" class="os-over">⚠</span></span>
</button>
</div>
</template>
<script setup>
import { heatColor } from 'src/composables/useHelpers'
defineProps({
days: { type: Array, default: () => [] }, // [{ iso, dow, dnum, h, cap, ratio, over, weekend, holiday, noShift, today }]
selected: { type: String, default: '' },
})
defineEmits(['select'])
// Barre : min 12 % quand il y a de la charge (visible), 0 si rien ; plafonnée à 100 %.
function fillH (d) { return d.h > 0 ? Math.max(12, Math.min(100, Math.round((d.ratio || 0) * 100))) : 0 }
function dayTitle (d) {
const parts = [Math.round(d.h) + 'h à faire']
if (d.cap > 0) parts.push('/ ' + Math.round(d.cap) + 'h de capacité')
if (d.over) parts.push('· surchargé ')
if (d.noShift) parts.push('· aucun quart (estimé)')
if (d.holiday) parts.unshift(' férié ')
return parts.join(' ')
}
</script>
<style scoped>
.os-strip { display: flex; gap: 6px; overflow-x: auto; padding-bottom: 4px; }
.os-strip::-webkit-scrollbar { height: 0; }
.os-day {
position: relative; flex: 0 0 auto; width: 52px; border: 1px solid var(--ops-border, #e2e8f0);
border-radius: 10px; background: #fff; display: flex; flex-direction: column; align-items: center;
gap: 2px; padding: 7px 2px; cursor: pointer; transition: border-color .15s, box-shadow .15s;
}
.os-day:hover { border-color: #c7d2fe; }
.os-day.active { border-color: #6366f1; box-shadow: 0 0 0 2px rgba(99, 102, 241, .18); }
.os-day.today .os-num { color: #6366f1; font-weight: 700; }
/* Différenciateur « jour courant » : liseré indigo en haut de la cellule (n'écrase pas la bague .active) */
.os-day.today::before { content: ''; position: absolute; top: -1px; left: 22%; right: 22%; height: 3px; border-radius: 0 0 3px 3px; background: #6366f1; }
.os-day.weekend { background: #f8fafc; }
.os-day.holiday { background: #fff7ed; border-color: #fdba74; }
.os-hol { position: absolute; top: 1px; left: 3px; color: #ea580c; font-size: 9px; line-height: 1; }
.os-warn { position: absolute; top: 2px; right: 3px; color: #ef4444; font-size: 9px; line-height: 1; pointer-events: none; }
/* Badge « dus non assignés ce jour » (flottant coin haut-droit) */
.os-unassigned { position: absolute; top: -5px; right: -5px; min-width: 16px; height: 16px; padding: 0 3px; border-radius: 8px; background: #ef4444; color: #fff; font-size: 10px; font-weight: 700; line-height: 16px; text-align: center; box-shadow: 0 1px 3px rgba(2, 6, 23, .3); pointer-events: none; }
.os-dow { font-size: 10px; color: var(--ops-text-secondary, #64748b); text-transform: capitalize; }
.os-num { font-size: 13px; font-weight: 600; color: var(--ops-text, #1e293b); }
.os-bar { width: 10px; height: 44px; background: #eef2f7; border-radius: 5px; overflow: hidden; display: flex; align-items: flex-end; margin: 3px 0; }
.os-fill { width: 100%; border-radius: 5px; transition: height .3s; }
.os-h { font-size: 10px; color: var(--ops-text-secondary, #64748b); white-space: nowrap; }
.os-over { color: #ef4444; margin-left: 1px; }
/* Mobile : ≤ 7 jours (bandes hebdo du tableau de bord) → les cellules s'étirent pour remplir la largeur
(fini le micro-défilement) ; au-delà (bande 14 j des tournées) elles gardent leur plancher et défilent. */
@media (max-width: 599px) {
.os-day { flex: 1 0 40px; width: auto; }
}
</style>