- New disney_group query param on /api/assets (park, resort,
office, springs, other)
- Both list and map filter dropdowns show group options above
individual parks
- Backend uses IN queries for grouped park values
- Map view handles groups via client-side PARK_GROUP_MAP
Closes#69
Three fixes:
1. Barcode scanner: load ZXing locally with CDN fallback so it works
even when CDN is unreachable.
2. Scan result: add 'Register' button to create a new asset from a
scanned machine_id — works for both found and not-found results.
3. OCR serial search: when extracted machine_id isn't found, also try
searching all numeric strings from OCR text as serial numbers; add
'Register as New Asset' button on OCR not-found result.
showScannedAsset (called on the very next line) already triggers the
auto-checkin. handleBarcode was calling doAutoCheckin immediately before
showScannedAsset, resulting in two check-in records per barcode scan.
Root cause: AbortController signals don't properly propagate through
Service Worker fetch handlers (known Chrome/Safari bug), causing the
fetch to hang indefinitely on mobile browsers instead of timing out.
Fix: replace AbortController-based timeout in api() with Promise.race
against a simple setTimeout. Also:
- Stop all MediaStream tracks explicitly in stopScanning()
- Add 200ms yield point before API call for hardware release
- Bypass SW for API requests entirely (no caching needed for lookups)
- Bump SW cache to v11
- Add outer try/catch safety net in handleBarcode
- Stop camera scanner (stopScanning) BEFORE making the API call in
handleBarcode — active camera stream on mobile browsers can throttle
concurrent fetch requests, causing the lookup to hang indefinitely
- Add 15s AbortController timeout to the api() wrapper so any network
issue shows 'Request timed out — check your connection' instead of
hanging forever on 'looking up...'
- Both fixes together ensure the barcode lookup never hangs
Root cause #1: BrowserMultiFormatReader constructor takes 0 args so hints
and timing options (passed as args) were silently ignored. Hints are now
applied as reader.reader.hints, and timing as instance properties.
Root cause #2: ZXing per-frame 'no barcode' exceptions have message=undefined
causing .toLowerCase() to crash in old message-text filter. Switched to
instanceof checks against NotFoundException, ChecksumException, FormatException
which properly distinguishes normal 'no barcode' frames from real errors.
Tested in browser — hint setting and error filtering both verified working.
When OCR detects serial numbers matching assets in the DB but no
machine_id pattern (XXXXX-XXXXXX) is found, the result now shows:
- 'Asset found via serial/ID match' heading
- Matched assets with name, serial_number, machine_id, matched_on
- View Details button per matched asset
- Scan New + Retake buttons at the bottom
When machine_id IS found AND matched_assets also has entries, the
matched assets are shown as supplementary info below the main result.
The OCR vision prompt already asked for 'serial numbers' (no change
needed on server side).
- Add 'Scan New Asset' button after scanning/OCR'ing an existing asset
in both barcode and OCR modes (with resetBarcodeScanner/resetOcrScanner)
- Show scan results in-place instead of navigating away from Find Asset tab
- Fix barcode scanner: remove TRY_HARDER hint (too slow on mobile), reduce
from 13 formats to 7 focused 1D formats, add ZXing library load check
- Extend camera fail timeout from 5s to 10s for slow mobile browsers
- Show matched_assets from OCR serial number cross-reference even when
no machine_id pattern is detected (previously showed 'No machine ID')
- Display serial/ID DB matches alongside machine_id results when both exist
- Add null-safety to all new reset functions
- handleBarcode now shows result in-place via showScannedAsset
instead of navigating to Assets tab
- showScannedAsset gets a 'Scan New' button that calls
resetBarcodeScanner()
- resetBarcodeScanner(): clears scan result, restarts barcode camera
- lookupOcrAsset gets a 'Scan New' button that calls
resetOcrScanner()
- resetOcrScanner(): clears OCR result, restarts OCR camera
- Retake still works for re-capturing the same sticker
delayBetweenScanAttempts was set to 50ms (20 scans/sec) but with
TRY_HARDER enabled and 13 barcode formats configured, the ZXing decoder
couldn't keep up — frames were skipped and barcodes visible in the
viewfinder were never detected. 300ms gives each frame enough processing
time while still feeling real-time.
ZXing's decodeFromVideoDevice fires its callback on every video frame
(~30fps). When no barcode is in view, it passes `(null, err)` where err
is 'No MultiFormat Readers were able to detect the code' — this is
NORMAL, not a hardware failure.
The error branch now filters out 'multiformat' and 'unable to detect'
messages, only calling handleCameraError for real device-level issues
(permission denied, camera not found, stream broken).
- handleCameraError() now sets scanningActive=false and resets codeReader
so ZXing can't fire more callbacks that re-create the manual input and
re-focus it — mobile keyboard was popping repeatedly
- openDrawer() now checked loginOverlay.hidden before opening, so drawer
can't appear behind the login screen
- Fixed HTML initial state: OCR toggle button has 'active', OCR panel has 'active', barcode does not
- switchTab calls setAddAssetMode() which syncs visual panels AND starts the right camera
- Previously the JS variable was 'ocr' but HTML hardcoded barcode as active — visual mismatch
- Added max-height: 85vh to modal container
- Made modal-body scrollable (overflow-y: auto, flex shrink)
- Title and action buttons stay fixed while body scrolls
- Fixes 'too tall and not scrollable' when WO details have many items
- Default add-asset mode changed from barcode to OCR
- Auto-start OCR camera when switching to OCR mode or Add Asset tab
- Camera viewport hidden entirely after photo capture (no placeholder showing)
- Retake button added to all OCR result states (found, not-found, error)
- 'Retake' resets UI and re-starts camera for a new photo
- Added work order filter bar to Route tab matching the Work Orders tab UX:
status, priority, work type dropdowns + date range selector
- Filter params passed to /api/workorders/today endpoint (backend updated
with new query params: status, priority, work_type, date_range)
- Added Google Maps Navigate button on every route stop
- Added Mark Done & Next Stop button — tracks current stop index,
marks completed stops with green check, advances to next with
auto-prompt to open Google Maps navigation
- Animated stop progress: done stops dimmed with green ✓, current
stop highlighted with accent border
- Updated /api/workorders/today to support date_range (week/month/all)
and additional filter WHERE clauses
- Backend: create_checkin now auto-resolves user_id from auth token
when not provided in the request body
- Backend: list_checkins now JOINs users table to return username
alongside each check-in
- Frontend: all 4 check-in functions now pass user_id from
AppState.currentUser
- Frontend: check-in history shows 👤 username next to each entry
- Also made work order count clickable: shows WO detail modal
- Added work_orders list to MSFS asset response
Closes#62
Adds a '📍 My GPS' button to the GPS Map Editor card in the asset
detail screen that:
- Grabs device GPS via navigator.geolocation.getCurrentPosition
- Places map marker at current device position
- Auto-unlocks the GPS editor if locked, shows Save button
- Displays accuracy in status text
- Provides clear error messages for denied/timed-out/unavailable
Includes enableHighAccuracy: true, 15s timeout, 60s cache.
- Created SVG sprite with 49 game-icons symbols (58KB)
- Added .gi CSS class for icon sizing/fill
- Replaced 77 emoji in static HTML with <use href='#gi-{name}'>
- Skip <option> elements (can't render SVG there)
- Added JS gi() helper for 33 dynamic icon assignments
- Replaced 📍🔒🔓📷📋✅↕️⚠ in JS-driven content
- Zero network requests, zero console errors
- Pairs with existing CATEGORY_MAP game-icons SVGs
Add a GPS map editor card at the top of asset details, allowing users to
manually set an asset's GPS coordinates via an interactive Leaflet map.
- GPS card with Leaflet map and draggable marker
- 🔒 Lock/unlock toggle (locked by default when GPS exists)
- 💾 Save GPS button (visible only when unlocked)
- Saves via PUT /api/assets/{id} with lat/lng
- Click on map or drag marker to set position
- Fallback to Orlando coords (28.5383, -81.3792) when no GPS
- Auto-refresh detail view after save
Closes#61
- Cleaned CATEGORY_MAP: removed dead entries (Furniture, GF Bev,
Snack/bev, Snack/food), fixed duplicate 'Food' key, proper
distinct icons for all 6 real categories
- Added categoryInfo() helper: 🥤/🍿/🍔/🔧/🔌/📦 for known cats,
❓ (red) for unmapped categories, 📦 for null
- Updated all render sites (list, map pins, map list, detail view,
scan result, OCR result) to use categoryInfo()
- Added category icons to detail view and scan/OCR result headers
- Synced categories DB table: added Equipment/Appliances with icons,
removed unused Unknown
- Updated seed data in both server.py and admin_server.py to match
real asset categories
- Added category_health to /api/stats with unmapped category detection
- Added Category Health card to admin dashboard (shown only when
unmapped categories exist)
- Updated CSS backgrounds for all real categories + .cat-unmapped
- Add QR_CODE, DATA_MATRIX, PDF_417, AZTEC to barcode scanner hints
so serial number stickers with 2D barcodes can be scanned
- Update /api/assets/search to also match connect_id so full
Connect IDs (e.g. VM-05912-0000068454) work when scanned
- Bump SW cache v5→v6, footer v3.2→v3.3
- New script: scripts/populate_asset_company_place.py maps connect_id →
account name (company/customer_name) + city (place) + address via
extraction DB (msdyn_customerasset + account join)
- 5,743/7,488 assets now have company + customer_name populated
- 5,647/7,488 assets now have place (city) populated
- Fix: SELECT COALESCE(c.name, a.customer_name) so stored customer_name
isn't overridden to NULL when customer_id FK is missing
- Bump SW v4→v5, version v3.1→v3.2 to force cache refresh
The detail view's Asset Details card was missing location fields
(place, building_name, building_number, floor, room, trailer_number)
that were visible in the list cards. Added them alongside Model
so building/floor info appears in the main card rather than only
in the collapsed Directions & Access section below.
- 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)
- Added import_seed.py — reads Machine List(8).xlsx, matches by Asset ID → machine_id
- 51 new columns on assets table (route, barcode, sales, contact info, etc.)
- seed_data table with full 62-field JSON per asset for detail enrichment
- Seed data card in frontend asset detail view
- GET /api/assets/{id} now returns result.seed and result.seed_imported_at
- GPS columns from Seed ignored per request
- 1,654 matched, 199 unmatched (Seed-only assets not in MSFS)
Refs #47
OCR endpoint (/api/ocr) now auto-saves EXIF GPS from the photo
to the asset's latitude/longitude fields when they are blank,
so the first photo scan at a machine permanently records its location.
GET /api/assets/{id} now includes an 'msfs' block enriched from
the merged Dynamics 365 Field Service data (merged-assets.json)
loaded at server start (1,834 matched assets). Fields shown:
equipment_id, connect_id, compass_asset_number, manufacturer,
model_text, dex_model, install_date, software_version, etc.
Frontend updates:
- New 'Field Service Data' card in asset detail view when MSFS
data is available (manufacturer, Connect ID, install date, etc.)
- '📍 GPS saved to asset' confirmation shown after OCR auto-save
- AppState.ocrGpsSaved flag to track the auto-save event