feat: dual progress bar on map markers (load + completion)
- Background bar (faded): planned hours / 8h capacity Color codes: green <4h, yellow 4-6h, orange 6-7h, red 7h+ - Foreground bar (solid green): completed hours / 8h Shows real-time job completion progress - Tooltip: "Name — X.Xh complété / X.Xh planifié" - Both bars stacked with absolute positioning for clean overlay Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
15813e6caf
commit
f7fea2b8e5
|
|
@ -181,13 +181,16 @@ export function useMap (deps) {
|
||||||
const initials = tech.fullName.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2)
|
const initials = tech.fullName.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2)
|
||||||
const color = TECH_COLORS[tech.colorIdx]
|
const color = TECH_COLORS[tech.colorIdx]
|
||||||
|
|
||||||
// Calculate daily workload (hours)
|
// Calculate daily workload + completion
|
||||||
const todayJobs = tech.queue.filter(j => jobSpansDate(j, dayStr))
|
const todayJobs = tech.queue.filter(j => jobSpansDate(j, dayStr))
|
||||||
const todayAssist = (tech.assistJobs || []).filter(j => jobSpansDate(j, dayStr))
|
const todayAssist = (tech.assistJobs || []).filter(j => jobSpansDate(j, dayStr))
|
||||||
const totalHours = todayJobs.reduce((s, j) => s + (j.duration || 1), 0)
|
const allToday = [...todayJobs, ...todayAssist]
|
||||||
+ todayAssist.reduce((s, j) => s + (j.duration || 1), 0)
|
const totalHours = allToday.reduce((s, j) => s + (j.duration || 1), 0)
|
||||||
const loadPct = Math.min(totalHours / 8, 1) // 0..1, capped at 8h
|
const doneHours = allToday.filter(j => (j.status || '').toLowerCase() === 'completed')
|
||||||
const barColor = loadPct < 0.5 ? '#10b981' : loadPct < 0.75 ? '#f59e0b' : loadPct < 0.9 ? '#f97316' : '#ef4444'
|
.reduce((s, j) => s + (j.duration || 1), 0)
|
||||||
|
const loadPct = Math.min(totalHours / 8, 1)
|
||||||
|
const donePct = totalHours > 0 ? Math.min(doneHours / 8, 1) : 0
|
||||||
|
const loadColor = loadPct < 0.5 ? '#10b981' : loadPct < 0.75 ? '#f59e0b' : loadPct < 0.9 ? '#f97316' : '#ef4444'
|
||||||
|
|
||||||
// Outer wrapper
|
// Outer wrapper
|
||||||
const outer = document.createElement('div')
|
const outer = document.createElement('div')
|
||||||
|
|
@ -199,7 +202,7 @@ export function useMap (deps) {
|
||||||
el.className = 'sb-map-tech-pin'
|
el.className = 'sb-map-tech-pin'
|
||||||
el.style.cssText = `background:${color};border-color:${color};`
|
el.style.cssText = `background:${color};border-color:${color};`
|
||||||
el.textContent = initials
|
el.textContent = initials
|
||||||
el.title = `${tech.fullName} — ${totalHours.toFixed(1)}h / 8h`
|
el.title = `${tech.fullName} — ${doneHours.toFixed(1)}h complété / ${totalHours.toFixed(1)}h planifié`
|
||||||
outer.appendChild(el)
|
outer.appendChild(el)
|
||||||
|
|
||||||
// Group badge (crew size)
|
// Group badge (crew size)
|
||||||
|
|
@ -212,14 +215,22 @@ export function useMap (deps) {
|
||||||
el.appendChild(badge)
|
el.appendChild(badge)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Workload progress bar (under avatar)
|
// Dual progress bar: load (background) + done (foreground)
|
||||||
if (totalHours > 0) {
|
if (totalHours > 0) {
|
||||||
const bar = document.createElement('div')
|
const bar = document.createElement('div')
|
||||||
bar.className = 'sb-map-load-bar'
|
bar.className = 'sb-map-load-bar'
|
||||||
const fill = document.createElement('div')
|
// Load bar (planned hours — full width = 8h)
|
||||||
fill.className = 'sb-map-load-fill'
|
const loadFill = document.createElement('div')
|
||||||
fill.style.cssText = `width:${Math.round(loadPct * 100)}%;background:${barColor};`
|
loadFill.className = 'sb-map-load-fill'
|
||||||
bar.appendChild(fill)
|
loadFill.style.cssText = `width:${Math.round(loadPct * 100)}%;background:${loadColor};opacity:0.35;`
|
||||||
|
bar.appendChild(loadFill)
|
||||||
|
// Done bar (completed hours — overlaid on top)
|
||||||
|
if (doneHours > 0) {
|
||||||
|
const doneFill = document.createElement('div')
|
||||||
|
doneFill.className = 'sb-map-done-fill'
|
||||||
|
doneFill.style.cssText = `width:${Math.round(donePct * 100)}%;background:#10b981;`
|
||||||
|
bar.appendChild(doneFill)
|
||||||
|
}
|
||||||
outer.appendChild(bar)
|
outer.appendChild(bar)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1498,8 +1498,9 @@ html, body { margin:0; padding:0; height:100%; overflow:hidden; }
|
||||||
.sb-map-tech-pin { width:36px; height:36px; border-radius:50%; display:flex; align-items:center; justify-content:center; font-size:0.65rem; font-weight:800; color:#fff; border:2.5px solid; box-shadow:0 2px 10px rgba(0,0,0,0.55); cursor:pointer; transition:transform 0.15s; }
|
.sb-map-tech-pin { width:36px; height:36px; border-radius:50%; display:flex; align-items:center; justify-content:center; font-size:0.65rem; font-weight:800; color:#fff; border:2.5px solid; box-shadow:0 2px 10px rgba(0,0,0,0.55); cursor:pointer; transition:transform 0.15s; }
|
||||||
.sb-map-tech-pin:hover { transform:scale(1.2); }
|
.sb-map-tech-pin:hover { transform:scale(1.2); }
|
||||||
.sb-map-tech-pin { position:relative; }
|
.sb-map-tech-pin { position:relative; }
|
||||||
.sb-map-load-bar { width:32px; height:4px; background:rgba(255,255,255,0.15); border-radius:2px; margin-top:3px; overflow:hidden; }
|
.sb-map-load-bar { width:32px; height:5px; background:rgba(255,255,255,0.12); border-radius:3px; margin-top:3px; overflow:hidden; position:relative; }
|
||||||
.sb-map-load-fill { height:100%; border-radius:2px; transition:width 0.3s; }
|
.sb-map-load-fill { position:absolute; top:0; left:0; height:100%; border-radius:3px; }
|
||||||
|
.sb-map-done-fill { position:absolute; top:0; left:0; height:100%; border-radius:3px; }
|
||||||
.sb-map-crew-badge { position:absolute; top:-4px; right:-6px; min-width:16px; height:16px; border-radius:8px; background:#6366f1; color:#fff; font-size:9px; font-weight:800; display:flex; align-items:center; justify-content:center; border:2px solid #0f172a; line-height:1; padding:0 3px; }
|
.sb-map-crew-badge { position:absolute; top:-4px; right:-6px; min-width:16px; height:16px; border-radius:8px; background:#6366f1; color:#fff; font-size:9px; font-weight:800; display:flex; align-items:center; justify-content:center; border:2px solid #0f172a; line-height:1; padding:0 3px; }
|
||||||
.sb-map-gps-active { box-shadow:0 0 0 3px rgba(16,185,129,0.5), 0 0 12px rgba(16,185,129,0.4), 0 2px 10px rgba(0,0,0,0.55); animation:gps-glow 2s infinite; }
|
.sb-map-gps-active { box-shadow:0 0 0 3px rgba(16,185,129,0.5), 0 0 12px rgba(16,185,129,0.4), 0 2px 10px rgba(0,0,0,0.55); animation:gps-glow 2s infinite; }
|
||||||
@keyframes gps-glow { 0%,100% { box-shadow:0 0 0 3px rgba(16,185,129,0.5), 0 0 12px rgba(16,185,129,0.4), 0 2px 10px rgba(0,0,0,0.55); } 50% { box-shadow:0 0 0 6px rgba(16,185,129,0.3), 0 0 20px rgba(16,185,129,0.3), 0 2px 10px rgba(0,0,0,0.55); } }
|
@keyframes gps-glow { 0%,100% { box-shadow:0 0 0 3px rgba(16,185,129,0.5), 0 0 12px rgba(16,185,129,0.4), 0 2px 10px rgba(0,0,0,0.55); } 50% { box-shadow:0 0 0 6px rgba(16,185,129,0.3), 0 0 20px rgba(16,185,129,0.3), 0 2px 10px rgba(0,0,0,0.55); } }
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user