fix: map markers zoom drift — fixed-size container + center anchor
- All elements (SVG ring + avatar + badge) inside a fixed-size container (45x45px) with absolute positioning - Avatar centered with calculated offset, ring fills container - Mapbox marker anchor changed from 'bottom' to 'center' - No more variable margins causing offset drift on zoom Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6f901f911c
commit
6d8339fa16
|
|
@ -192,24 +192,23 @@ export function useMap (deps) {
|
||||||
const donePct = totalHours > 0 ? Math.min(doneHours / 8, 1) : 0
|
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'
|
const loadColor = loadPct < 0.5 ? '#10b981' : loadPct < 0.75 ? '#f59e0b' : loadPct < 0.9 ? '#f97316' : '#ef4444'
|
||||||
|
|
||||||
// SVG ring dimensions
|
// Ring + avatar in a fixed-size container so Mapbox anchor stays consistent
|
||||||
const SIZE = 46, STROKE = 3.5, R = (SIZE - STROKE) / 2
|
const PIN = 36, STROKE = 3.5, SIZE = PIN + STROKE * 2 + 2 // ~45px
|
||||||
const CIRC = 2 * Math.PI * R
|
const R = (SIZE - STROKE) / 2, CIRC = 2 * Math.PI * R
|
||||||
const completedJobs = allToday.filter(j => (j.status || '').toLowerCase() === 'completed').length
|
const completedJobs = allToday.filter(j => (j.status || '').toLowerCase() === 'completed').length
|
||||||
const totalJobs = allToday.length
|
const totalJobs = allToday.length
|
||||||
const completionPct = totalJobs > 0 ? completedJobs / totalJobs : 0
|
const completionPct = totalJobs > 0 ? completedJobs / totalJobs : 0
|
||||||
|
|
||||||
// Outer wrapper
|
// Fixed-size outer wrapper — Mapbox anchors to this
|
||||||
const outer = document.createElement('div')
|
const outer = document.createElement('div')
|
||||||
outer.style.cssText = 'cursor:pointer;position:relative;'
|
outer.style.cssText = `cursor:pointer;width:${SIZE}px;height:${SIZE}px;position:relative;`
|
||||||
outer.dataset.techId = tech.id
|
outer.dataset.techId = tech.id
|
||||||
|
|
||||||
// SVG ring (load arc + completion arc)
|
// SVG ring (load arc + completion arc) — fills entire container
|
||||||
if (totalHours > 0) {
|
if (totalHours > 0) {
|
||||||
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
|
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
|
||||||
svg.setAttribute('width', SIZE); svg.setAttribute('height', SIZE)
|
svg.setAttribute('width', SIZE); svg.setAttribute('height', SIZE)
|
||||||
svg.style.cssText = 'position:absolute;top:0;left:0;transform:rotate(-90deg);pointer-events:none;'
|
svg.style.cssText = 'position:absolute;top:0;left:0;transform:rotate(-90deg);pointer-events:none;'
|
||||||
// Load arc (how full the day is — faded)
|
|
||||||
const loadArc = document.createElementNS('http://www.w3.org/2000/svg', 'circle')
|
const loadArc = document.createElementNS('http://www.w3.org/2000/svg', 'circle')
|
||||||
loadArc.setAttribute('cx', SIZE/2); loadArc.setAttribute('cy', SIZE/2); loadArc.setAttribute('r', R)
|
loadArc.setAttribute('cx', SIZE/2); loadArc.setAttribute('cy', SIZE/2); loadArc.setAttribute('r', R)
|
||||||
loadArc.setAttribute('fill', 'none'); loadArc.setAttribute('stroke', loadColor)
|
loadArc.setAttribute('fill', 'none'); loadArc.setAttribute('stroke', loadColor)
|
||||||
|
|
@ -217,7 +216,6 @@ export function useMap (deps) {
|
||||||
loadArc.setAttribute('stroke-dasharray', `${CIRC * loadPct} ${CIRC}`)
|
loadArc.setAttribute('stroke-dasharray', `${CIRC * loadPct} ${CIRC}`)
|
||||||
loadArc.setAttribute('stroke-linecap', 'round')
|
loadArc.setAttribute('stroke-linecap', 'round')
|
||||||
svg.appendChild(loadArc)
|
svg.appendChild(loadArc)
|
||||||
// Completion arc (jobs done — solid bright)
|
|
||||||
if (completionPct > 0) {
|
if (completionPct > 0) {
|
||||||
const doneArc = document.createElementNS('http://www.w3.org/2000/svg', 'circle')
|
const doneArc = document.createElementNS('http://www.w3.org/2000/svg', 'circle')
|
||||||
doneArc.setAttribute('cx', SIZE/2); doneArc.setAttribute('cy', SIZE/2); doneArc.setAttribute('r', R)
|
doneArc.setAttribute('cx', SIZE/2); doneArc.setAttribute('cy', SIZE/2); doneArc.setAttribute('r', R)
|
||||||
|
|
@ -230,11 +228,11 @@ export function useMap (deps) {
|
||||||
outer.appendChild(svg)
|
outer.appendChild(svg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avatar circle (centered inside SVG ring)
|
// Avatar circle — absolutely centered in container
|
||||||
const el = document.createElement('div')
|
const el = document.createElement('div')
|
||||||
el.className = 'sb-map-tech-pin'
|
el.className = 'sb-map-tech-pin'
|
||||||
const pad = totalHours > 0 ? STROKE + 1 : 0
|
const offset = (SIZE - PIN) / 2
|
||||||
el.style.cssText = `background:${color};border-color:${color};margin:${pad}px;`
|
el.style.cssText = `background:${color};border-color:${color};position:absolute;top:${offset}px;left:${offset}px;width:${PIN}px;height:${PIN}px;`
|
||||||
el.textContent = initials
|
el.textContent = initials
|
||||||
el.title = `${tech.fullName} — ${completedJobs}/${totalJobs} jobs (${doneHours.toFixed(1)}h / ${totalHours.toFixed(1)}h)`
|
el.title = `${tech.fullName} — ${completedJobs}/${totalJobs} jobs (${doneHours.toFixed(1)}h / ${totalHours.toFixed(1)}h)`
|
||||||
outer.appendChild(el)
|
outer.appendChild(el)
|
||||||
|
|
@ -269,7 +267,7 @@ export function useMap (deps) {
|
||||||
el.classList.add('sb-map-gps-active')
|
el.classList.add('sb-map-gps-active')
|
||||||
el.title += ' (GPS)'
|
el.title += ' (GPS)'
|
||||||
}
|
}
|
||||||
const m = new mbgl.Marker({ element: outer, anchor: 'bottom' }).setLngLat(pos).addTo(map)
|
const m = new mbgl.Marker({ element: outer, anchor: 'center' }).setLngLat(pos).addTo(map)
|
||||||
mapMarkers.value.push(m)
|
mapMarkers.value.push(m)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user