Two related issues, one PR:
1. **Bad coords** on customer C-LPB4's "Wifi buggy" job (DJ-MNP8WIKT).
Address on file is `691 rue des Hirondelles, Saint-Michel J0L2J0`,
but the saved lat/lng (-73.677086, 45.159206) reverse-geocodes to
`2336 rue René-Vinet, Sainte-Clotilde J0L1W0` — ~9 km away. The
delta matches the Gigafibre HQ default fallback (-73.6756, 45.1599)
pretty closely, suggesting the geocoder either failed silently at
Service Location creation time or got pinned to the HQ centroid.
Fixed live in DB (UPDATE on tabService Location LOC-0000000004 +
tabDispatch Job DJ-MNP8WIKT to lng=-73.5792377, lat=45.2408452,
verified via Nominatim against the typed address). The job pin
should now show on the correct house.
2. **No way to jump from a job to the client** — the dispatcher had
to memorize/type the customer ID. Now both the RightPanel and the
job context-menu surface clickable shortcuts:
• Client → `#/clients/<id>` (opens ClientDetailPage in-app)
• Lieu → `/desk/Service Location/<id>` (opens ERPNext in a new
tab; the ops SPA doesn't have a dedicated SL detail page)
Required wiring `customer` + `serviceLocation` into the job map in
`stores/dispatch.js` — the API (`fetchJobsFast` uses `["*"]`) was
already returning the fields, the store just wasn't surfacing them.
Note on the deeper bug: the SL lat/lng is the source of truth and the
job currently *copies* it at creation time (rather than reading from
the SL link dynamically). If a Service Location's coords are corrected
after a job exists, the job retains stale coords. A follow-up could
either (a) re-read on render, or (b) trigger a backfill when SL coords
change. Out of scope for this fix — for now, the dispatcher who fixes
an SL must also update any open jobs at that location.
Two issues conflated in the same PR because they touch the same pixels:
1. **Resource filter no longer treats techs and materials as equals.**
Was a 3-button inline toggle [Tous][👤 45][🔧 6] with all three
visually similar — and the wrench glyph clashed with the wrench
used for the filter-settings button. Now:
• Default = 'human' (techs only). Materials are secondary
resources; they don't deserve front-of-bar real estate.
• Single chip [👥 45 ▾] in the toolbar. Click → dropdown:
· Techs (45) ← active by default
· Matériel (6) (only shown if materialCount > 0)
· Tous (51) (only shown if materialCount > 0)
• Defaults to localStorage 'sbv2-filterResType' if previously
persisted, otherwise 'human' instead of '' (was '').
2. **Mixed-style icons (emoji + Lucide SVG) replaced with consistent
single-color Lucide-style strokes.**
Each is a stroke-only inline <svg> with stroke="currentColor", so
they inherit the surrounding text color (no green/red/yellow
tinting). Added to the existing ICON set in useHelpers.js:
user, users, package, sliders, chevDown, map, clipboard,
sparkles, signal, rotateCw, alertTri, moreH, pause, play,
externalLink, target, calendar
Replaced in the dispatch top toolbar:
⚠️ → ICON.alertTri (overload alert)
📋 → ICON.clipboard (unscheduled jobs)
🗺 → ICON.map (map toggle)
🗓 → ICON.calendar (planning toggle)
👥 → ICON.users (team-jobs button + Ressources menu)
🔧 → ICON.sliders (filter-settings — was wrench, which
collided with the materials filter)
👤/🔧 → ICON.users / .package (resource type dropdown)
↻ → ICON.rotateCw (refresh in ⋯ menu)
✨ → ICON.sparkles (AI in ⋯ menu)
📡 → ICON.signal (offers in ⋯ menu)
↗ → ICON.externalLink (ERPNext link in ⋯ menu)
⋯ → ICON.moreH (the ⋯ button itself)
.sb-icon-svg gives them consistent sizing (14px in buttons, 15px
in dropdown items, 16px in the ⋯ trigger) so they're crisp at all
the spots they appear.
Emojis still in place elsewhere (job-tile chips, status badges, etc.)
will be migrated incrementally — out of scope for this pass which
only targeted the user's visible header.
Four fixes around the dispatch header following dispatcher feedback:
1. **⋯ overflow menu was invisible**: .sb-header had `overflow:hidden`,
which clipped the absolutely-positioned dropdown right at the
header's bottom edge. Switched the header to `overflow:visible`
(children all have flex-shrink:0 + a flex:1 center, so the layout
doesn't actually overflow horizontally). Bumped z-index to 5000
for safety on top of map/calendar layers.
2. **NLP/Assistant IA bar hidden by default**: was eagerly rendering
on every page load, with the long French placeholder polluting
the header below the toolbar. The user just wanted the icon. Now
`nlpVisible` defaults to false, persisted in localStorage so power
users who flip it on keep it open across sessions. Toggle still
lives in the ⋯ menu.
3. **Click a tech in the resource list now flies the map to them**:
selectTechOnBoard previously only opened the map panel. Now it
also `map.flyTo({ center })` using `tech.gpsCoords ?? tech.coords`
— live Traccar position wins when the tech is online; falls back
to the saved home base. Animated, deferred a tick so map.resize()
happens first, otherwise flyTo can land on garbage coords during
the panel's open transition.
4. **Board view tabs collapsed into a "Vue principale ▾" dropdown**:
was [Vue principale][Par région][+] inline. Now a single button
showing the active view; click reveals the others + the future
"+ Nouvelle vue" entry. Same dropdown component as the ⋯ menu
(shared CSS, click-outside + ESC close).
The header right-side was getting noisy — 8 buttons + 2 indicators
all competing for screen width, with two visually-similar 📡 icons
(offer pool vs GPS settings) that confused dispatchers. On narrower
laptops the bar wrapped or icons overflowed.
New layout:
[⚠ overload] [📋 unassigned + count] [🗺 Carte] [Publier + count] [+ WO] [⋯]
Everything else dropped into the ⋯ dropdown:
• ↻ Actualiser
• ✨ Assistant IA
• 📡 Offres aux techs (with green count badge)
• 👥 Ressources & GPS ← was 📡 in the bar; this is also where
the tech-management UI (rename, deactivate,
home location, Traccar device link) lives
• ↗ Ouvrir ERPNext (with the inline status dot)
The ⋯ menu closes on Escape, on click outside, and after picking an
item. Same close-handler chain that already serves the job/tech
context menus.
The kept-up-front buttons all have either a status badge (counts,
overload alert) or are the primary CTAs (Publier, + WO) — so the
dispatcher's eye stays on workflow signal, not on chrome.
Three connected UX changes:
1. **Map centered on Gigafibre HQ on first load** —
Sainte-Clotilde (lng=-73.6756, lat=45.1599), zoom 10 — covers the
service area (Sainte-Clotilde + Châteauguay + Napierville +
Hemmingford). Was downtown Montréal.
2. **Right-click on a tech pin** opens the existing techCtx menu
(already used from the calendar via @ctx-tech). New entries:
• 📍 Adresse de départ… → openTechHomeDialog
• 🎯 Choisir sur la carte → startTechGeoFix (mirrors the existing
geoFixJob flow used for jobs)
3. **The 📍 button in the GPS sidebar** now offers a 2-option chooser
first: "Saisir une adresse" or "Cliquer sur la carte". Picking the
map option drops the user into geoFixTech mode.
Implementation:
• useMap.js: new geoFixTech ref + startTechGeoFix/cancelTechGeoFix
+ a contextmenu listener on each tech outer wrapper that calls
openTechCtx(e, tech). The map's main click handler now branches:
if geoFixTech is set, persist the lng/lat via saveTechHome (passed
in via deps as a forward-bound arrow because saveTechHome is
destructured below the useMap call in DispatchPage).
• DispatchPage.vue: new banner shown while in pick mode (animated
indigo bar at top, "Cliquez sur la carte pour {tech}", with a
cancel button); ESC also cancels.
• dispatch-styles.scss: .sb-geofix-banner styles + reusing the
existing pulse keyframe.
Two changes around tech "departure point" coords (used for route
optimization when the tech has no live GPS yet):
1. New default fallback = 1867 chemin de la Rivière, Sainte-Clotilde
(Gigafibre HQ, lng=-73.6756, lat=45.1599). Was downtown Montréal,
which never made sense — every tech started the day with a 70 km
imaginary commute.
2. Per-tech editable home base via a 📍 button on each row of the
tech sidebar. Clicking it opens a dialog that accepts either:
• a free-text address — geocoded via OpenStreetMap Nominatim
(browser-side, sane User-Agent, no hub proxy needed)
• or a literal "lat, lng" pair pasted directly
On confirm: PUT to ERPNext (Dispatch Technician.latitude /
.longitude), patch the local store row, and trigger a route
recompute since the start point changed.
The geocode hits Nominatim public — fine for a low-volume
internal tool. If we ever exceed their fair-use limits, swap to
the existing /address-search hub route which already has the
AQ + RQA pipeline.
Tech mobile view (erp.gigafibre.ca/ops/#/j):
- TechLayout with bottom nav tabs (tasks, scanner, diagnostic, more)
- TechTasksPage: rich header with tech name/stats, job cards with
priority dots, time, location, duration badges, bottom sheet detail
with En route/Terminer buttons + scanner/detail access
- TechJobDetailPage: editable fields, equipment list, GPS navigation
- TechScanPage: device lookup by SN/MAC, create/link to job
- TechDiagnosticPage: speed test + host reachability checks
- Route /j replaces legacy dispatch-app tech view
Dispatch unassign confirmation:
- Dialog appears when unassigning published or in-progress jobs
- Warns that tech has already received the task
- Cancel/Confirm flow prevents accidental removal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>