diff --git a/apps/ops/src/composables/useMap.js b/apps/ops/src/composables/useMap.js index be120df..417d2b6 100644 --- a/apps/ops/src/composables/useMap.js +++ b/apps/ops/src/composables/useMap.js @@ -8,7 +8,8 @@ export function useMap (deps) { currentView, periodStart, filteredResources, mapVisible, routeLegs, routeGeometry, getJobDate, jobColor, pushUndo, smartAssign, invalidateRoutes, - dragJob, dragIsAssist, rightPanel, openCtxMenu, + dragJob, dragIsAssist, rightPanel, openCtxMenu, openTechCtx, + saveTechHome, } = deps let map = null @@ -18,6 +19,7 @@ export function useMap (deps) { const mapMarkers = ref([]) const mapPanelW = ref(parseInt(localStorage.getItem('sbv2-mapW')) || 340) const geoFixJob = ref(null) + const geoFixTech = ref(null) // ← analog of geoFixJob, but for tech home base const mapDragJob = ref(null) let _mapGhost = null @@ -33,6 +35,20 @@ export function useMap (deps) { } watch(geoFixJob, v => { if (map) map.getCanvas().style.cursor = v ? 'crosshair' : '' }) + // Tech home-base "click on the map" picker. Same pattern as geoFixJob: + // user enters the mode, cursor turns to crosshair, next click on the + // map captures the lng/lat and persists it to ERPNext. + function startTechGeoFix (tech) { + geoFixTech.value = tech + if (!mapVisible.value) mapVisible.value = true + if (map) map.getCanvas().style.cursor = 'crosshair' + } + function cancelTechGeoFix () { + geoFixTech.value = null + if (map) map.getCanvas().style.cursor = '' + } + watch(geoFixTech, v => { if (map) map.getCanvas().style.cursor = v ? 'crosshair' : '' }) + // ── Panel resize ───────────────────────────────────────────────────────────── function startMapResize (e) { e.preventDefault() @@ -67,7 +83,10 @@ export function useMap (deps) { map = new mapboxgl.Map({ container: mapContainer.value, style: 'mapbox://styles/mapbox/dark-v11', - center: [-73.567, 45.502], zoom: 10, + // Default centered on Gigafibre HQ (1867 chemin de la Rivière, + // Sainte-Clotilde, QC). Zoom 10 shows Sainte-Clotilde + the + // surrounding service area (Châteauguay, Napierville, Hemmingford…). + center: [-73.6756177, 45.1599145], zoom: 10, }) if (mapResizeObs) mapResizeObs.disconnect() mapResizeObs = new ResizeObserver(() => { if (map) map.resize() }) @@ -112,8 +131,20 @@ export function useMap (deps) { map.on('mouseenter', 'sb-route-line', () => { if (mapDragJob.value) map.getCanvas().style.cursor = 'copy' }) map.on('mouseleave', 'sb-route-line', () => { if (!mapDragJob.value) map.getCanvas().style.cursor = '' }) - // Geo-fix click - map.on('click', e => { + // Geo-fix click — handles BOTH job geofix and tech home-base pick. + // Tech mode wins if both are somehow active simultaneously (defensive; + // shouldn't happen in practice). + map.on('click', async e => { + if (geoFixTech.value) { + const tech = geoFixTech.value + geoFixTech.value = null + map.getCanvas().style.cursor = '' + if (typeof saveTechHome === 'function') { + try { await saveTechHome(tech, e.lngLat.lng, e.lngLat.lat) } catch (_e) {} + } + nextTick(() => drawMapMarkers()) + return + } if (!geoFixJob.value) return const job = geoFixJob.value const saved = JSON.parse(localStorage.getItem('dispatch-job-coords') || '{}') @@ -255,6 +286,15 @@ export function useMap (deps) { el.appendChild(badge) } + // Right-click → open tech context menu (DispatchPage handles the + // q-menu). We pre-position by passing the original click event; + // useContextMenus reads e.clientX/Y to anchor the menu. + outer.addEventListener('contextmenu', e => { + e.preventDefault() + e.stopPropagation() + if (typeof openTechCtx === 'function') openTechCtx(e, tech) + }) + // Drag & drop handlers outer.addEventListener('dragover', e => { e.preventDefault(); el.style.transform = 'scale(1.25)' }) outer.addEventListener('dragleave', () => { el.style.transform = '' }) @@ -413,8 +453,9 @@ export function useMap (deps) { function getMap () { return map } return { - mapContainer, selectedTechId, mapMarkers, mapPanelW, geoFixJob, mapDragJob, - startGeoFix, cancelGeoFix, startMapResize, initMap, + mapContainer, selectedTechId, mapMarkers, mapPanelW, geoFixJob, geoFixTech, mapDragJob, + startGeoFix, cancelGeoFix, startTechGeoFix, cancelTechGeoFix, + startMapResize, initMap, drawMapMarkers, drawSelectedRoute, computeDayRoute, selectTechOnBoard, destroyMap, loadMapboxCss, getMap, } diff --git a/apps/ops/src/pages/DispatchPage.vue b/apps/ops/src/pages/DispatchPage.vue index e756995..1f3c4f9 100644 --- a/apps/ops/src/pages/DispatchPage.vue +++ b/apps/ops/src/pages/DispatchPage.vue @@ -318,10 +318,16 @@ const _map = useMap({ currentView, periodStart, filteredResources, mapVisible, routeLegs, routeGeometry, getJobDate, jobColor, pushUndo, smartAssign, invalidateRoutes, - dragJob, dragIsAssist, rightPanel, openCtxMenu, + dragJob, dragIsAssist, rightPanel, openCtxMenu, openTechCtx, + // Forward-binding through an arrow: `saveTechHome` is destructured + // from useTechManagement BELOW this call, so we can't pass the + // function value directly here (TDZ). The arrow defers the lookup + // to invocation time — by which point the const is defined. + saveTechHome: (tech, lng, lat) => saveTechHome(tech, lng, lat), }) -const { mapContainer, selectedTechId, mapMarkers, mapPanelW, geoFixJob, mapDragJob, - startGeoFix, cancelGeoFix, startMapResize, initMap, selectTechOnBoard, destroyMap, loadMapboxCss } = _map +const { mapContainer, selectedTechId, mapMarkers, mapPanelW, geoFixJob, geoFixTech, mapDragJob, + startGeoFix, cancelGeoFix, startTechGeoFix, cancelTechGeoFix, + startMapResize, initMap, selectTechOnBoard, destroyMap, loadMapboxCss } = _map computeDayRoute = _map.computeDayRoute drawMapMarkers = _map.drawMapMarkers drawSelectedRoute = _map.drawSelectedRoute @@ -411,27 +417,48 @@ const scheduleModalTech = ref(null) const scheduleForm = ref({}) const extraShiftsForm = ref([]) // On-call / garde shifts -// Edit a tech's home/departure coordinates. Two paths converge here: +// Edit a tech's home/departure coordinates. Three paths now converge: // • Type an address → free Nominatim geocode → confirm → save -// • Use the live GPS position (if Traccar device is online) as the -// new home base — handy after a tech moves -// • Or paste lat/lng directly (advanced) +// • Type "lat, lng" directly (advanced) +// • Pick the location by clicking on the map (geoFixTech mode) // We don't proxy the geocode through targo-hub on purpose: Nominatim // allows browser calls with a sane User-Agent and there's no secret -// involved. Hitting it from the SPA also keeps the hub free of the -// tile-usage policy responsibility. +// involved. async function openTechHomeDialog (tech) { const cur = tech.coords || [-73.6756177, 45.1599145] - const dialog = $q.dialog({ + // Step 1: ask the user how they want to set it. + $q.dialog({ title: `Position de départ — ${tech.fullName}`, - message: ` -
lat, lng.
- ${cur[1].toFixed(5)}, ${cur[0].toFixed(5)}
- ${cur[1].toFixed(5)}, ${cur[0].toFixed(5)}
+ lat, lng.