From 9ee8de85781d62ff1692de6d284f81ff50ee1085 Mon Sep 17 00:00:00 2001 From: Shawn Date: Fri, 29 May 2026 00:10:30 -0400 Subject: [PATCH] fix: add/find tab now lookup-only, nav fix, route techs fix - Add/Find tab: removed check-in card and add-asset form, now pure lookup - OCR: stop camera after capture, added GPS capture at photo time - OCR: increased timeout from 8s to 15s to reduce 500 errors - Nav tab: added GPS prompt + asset search when navigating directly - Route: include all 26 techs from bookableresource table (not just Shawn) --- server.py | 12 ++- static/index.html | 186 +++++++++++++++++++++++++++++++++++++++------- 2 files changed, 166 insertions(+), 32 deletions(-) diff --git a/server.py b/server.py index 4812811..a901ea7 100644 --- a/server.py +++ b/server.py @@ -2972,13 +2972,11 @@ async def workorders_technicians(): try: cur = conn.cursor() cur.execute( - """ - SELECT DISTINCT b."resource!name" AS technician - FROM bookableresourcebooking b - WHERE b."resource!name" IS NOT NULL - AND b."resource!name" != '' - ORDER BY technician - """ + """SELECT DISTINCT name AS technician FROM bookableresource WHERE name IS NOT NULL AND name != '' + UNION + SELECT DISTINCT b."resource!name" AS technician FROM bookableresourcebooking b + WHERE b."resource!name" IS NOT NULL AND b."resource!name" != '' + ORDER BY technician""" ) techs = [r["technician"] for r in cur.fetchall()] return {"technicians": techs, "total": len(techs)} diff --git a/static/index.html b/static/index.html index 296bb40..7342719 100644 --- a/static/index.html +++ b/static/index.html @@ -1124,22 +1124,6 @@ - - - - - @@ -1166,8 +1150,6 @@ - - + +
@@ -2118,6 +2114,11 @@ document.dispatchEvent(new CustomEvent('rpTabActivated')); } + // Navigate tab โ€” check GPS and asset setup + if (tabId === 'tabNavigate') { + updateNavSetup(); + } + // Dispatch event so child tab implementations can hook in document.dispatchEvent(new CustomEvent('tabChange', { detail: { tabId } })); } @@ -2507,9 +2508,6 @@ ${hasGps ? 'โœ“ GPS captured' : '๐Ÿ“ GPS needed'} -
`; @@ -2803,11 +2801,41 @@
${esc(catLabel(asset.category))} ยท ${esc(asset.status)}
`; setOcrStatus('Asset found!', 'success'); - // Auto-checkin if GPS available + // Try to capture GPS if not available AppState.currentAssetId = asset.id; + if (AppState.gpsLat == null) { + try { + const pos = await new Promise((resolve, reject) => { + navigator.geolocation.getCurrentPosition(resolve, reject, + { enableHighAccuracy: true, timeout: 8000 }); + }); + AppState.gpsLat = pos.coords.latitude; + AppState.gpsLng = pos.coords.longitude; + AppState.gpsAcc = pos.coords.accuracy; + } catch (_) { /* GPS not available */ } + } + + // Auto-checkin if GPS available if (AppState.gpsLat != null) { doAutoCheckin(asset.id); el.innerHTML += `
โœ“ Auto check-in recorded
`; + + // Save GPS to asset if it's missing GPS coords + if (!asset.latitude || !asset.longitude) { + try { + await api('/api/assets/' + asset.id, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + latitude: AppState.gpsLat, + longitude: AppState.gpsLng, + }), + }); + el.innerHTML += `
๐Ÿ“ GPS location saved to asset
`; + } catch (_) {} + } + } else { + el.innerHTML += `
๐Ÿ“ GPS not available โ€” check-in skipped
`; } if (AppState.ocrGpsSaved) { el.innerHTML += `
๐Ÿ“ GPS saved to asset
`; @@ -2870,6 +2898,9 @@ canvas.getContext('2d').drawImage(video, 0, 0); const blob = await new Promise(r => canvas.toBlob(r, 'image/jpeg', 0.85)); + // Stop camera after capturing photo + stopOcrCamera(); + setOcrStatus('Processing OCR...', 'working'); document.getElementById('ocrResult').style.display = 'none'; @@ -2879,7 +2910,7 @@ // Try server-side OCR first with 8s timeout try { const controller = new AbortController(); - const serverTimeout = setTimeout(() => controller.abort(), 8000); + const serverTimeout = setTimeout(() => controller.abort(), 15000); const formData = new FormData(); formData.append('file', blob, 'sticker.jpg'); const res = await fetch('/api/ocr', { @@ -2920,6 +2951,19 @@ setOcrStatus('Processing OCR...', 'working'); } + // Capture device GPS for this photo (fires async โ€” saved to AppState) + if (AppState.gpsLat == null) { + navigator.geolocation.getCurrentPosition( + (pos) => { + AppState.gpsLat = pos.coords.latitude; + AppState.gpsLng = pos.coords.longitude; + AppState.gpsAcc = pos.coords.accuracy; + }, + () => {}, + { enableHighAccuracy: true, timeout: 10000 } + ); + } + displayOcrResult(data); } @@ -3940,9 +3984,14 @@ async function navigateToAsset(mode) { mode = mode || 'driving'; - if (!AppState.currentAssetId) return; + if (!AppState.currentAssetId) { + updateNavSetup(); + showToast('Select an asset first', true); + return; + } if (!AppState.gpsLat || !AppState.gpsLng) { - showToast('GPS location not available. Tap โ—Ž My Location first.', true); + updateNavSetup(); + showToast('GPS location not available. Tap ๐Ÿ“ Get My Location.', true); return; } try { @@ -4090,6 +4139,93 @@ } } + function updateNavSetup() { + const hasGps = AppState.gpsLat != null; + const hasAsset = AppState.currentAssetId != null; + const setupEl = document.getElementById('navSetup'); + const gpsEl = document.getElementById('navSetupGps'); + const assetEl = document.getElementById('navSetupAsset'); + if (!setupEl) return; + + if (!hasGps || !hasAsset) { + setupEl.style.display = 'block'; + gpsEl.style.display = hasGps ? 'none' : 'block'; + assetEl.style.display = hasAsset ? 'none' : 'block'; + } else { + setupEl.style.display = 'none'; + } + } + + function captureNavGps() { + const btn = document.querySelector('#navSetupGps button'); + if (btn) { btn.textContent = 'โณ Acquiring...'; btn.disabled = true; } + navigator.geolocation.getCurrentPosition( + (pos) => { + AppState.gpsLat = pos.coords.latitude; + AppState.gpsLng = pos.coords.longitude; + AppState.gpsAcc = pos.coords.accuracy; + if (btn) { btn.textContent = '๐Ÿ“ Got location! โœ“'; btn.disabled = false; } + setTimeout(updateNavSetup, 500); + showToast('๐Ÿ“ Location acquired'); + }, + () => { + if (btn) { btn.textContent = '๐Ÿ“ Get My Location'; btn.disabled = false; } + showToast('GPS failed โ€” check permissions', true); + }, + { enableHighAccuracy: true, timeout: 15000 } + ); + } + + let _navSearchTimeout = null; + + function searchNavAssets() { + clearTimeout(_navSearchTimeout); + const q = document.getElementById('navAssetSearch')?.value.trim(); + if (!q || q.length < 2) { + document.getElementById('navAssetResults').innerHTML = ''; + return; + } + _navSearchTimeout = setTimeout(async () => { + try { + const res = await api('/api/assets?search=' + encodeURIComponent(q)); + const results = Array.isArray(res) ? res : (res.assets || res.data || []); + const container = document.getElementById('navAssetResults'); + if (results.length === 0) { + container.innerHTML = '
No assets found
'; + return; + } + container.innerHTML = results.slice(0, 10).map(a => + `
+ ${esc(a.name)} + ${esc(a.machine_id)} +
` + ).join(''); + } catch (e) { + document.getElementById('navAssetResults').innerHTML = + '
Search failed
'; + } + }, 300); + } + + function selectNavAsset(id) { + AppState.currentAssetId = id; + document.getElementById('navDestName').textContent = 'โ†’ Asset #' + id; + document.getElementById('navAssetSearch').value = ''; + document.getElementById('navAssetResults').innerHTML = ''; + // Fetch the asset name + api('/api/assets/' + id).then(a => { + document.getElementById('navDestName').textContent = 'โ†’ ' + a.name; + }).catch(() => {}); + updateNavSetup(); + // If GPS is also set, navigate immediately + if (AppState.gpsLat != null) { + navigateToAsset('driving'); + } else { + showToast('Asset selected. Now get your location.'); + } + } + function closeNavigate() { if (navMap) { navMap.remove(); navMap = null; } AppState._navData = null;