Root cause: rpLoadTechs() and all other route planner API calls used
raw fetch() instead of the api() wrapper, so no Authorization header
was attached. Auth middleware returned 401, technician list never
loaded, users saw 'Select at least one technician'.
Fixed by replacing all 6 fetch('/api/...') calls in the route planner
with api('/api/...') and removing redundant .json() parsing (api()
returns parsed data directly).
Tested in browser: technician checkboxes populate, Load Today's Route
returns 3 optimized stops with 12.5km route, map renders correctly,
search tab queries return Disney WO results via auth.
Closes: #issue-to-be-created
- Removed onclick=openDrawer() from user badge
- Removed '⋯ More' bottom tab (also opened drawer)
- Header hamburger ☰ remains the single drawer trigger
- Removed startVisitTracking / stopVisitTracking functions
- Removed checkProximityToAssets and haversineM
- Removed logAutoVisit and all visit timer logic
- Removed visitTracker UI div on Map tab
- Removed GPS watchPosition call (was polling at ~1-5s intervals)
- Kept server-side /api/visits endpoints (used by heatmap) and one-shot GPS for scan check-in
Reason: GPS auto-tracker can't distinguish machines above/below each other
on different floors.
When a user scans a barcode that matches an existing asset:
- Redirect directly to the asset detail view (Assets tab)
- Auto-checkin still runs in background if GPS available
- Show a toast notification with the asset name
When scanning a barcode not in database:
- Switch back to Add Asset tab and show the create form
- Deferred status update survives async startScanning
No backend changes needed.
Closes#39
- Add .ai-address and .ai-location CSS classes
- Increase .asset-item padding to 14px, align-items to flex-start
- Reduce .ai-name font to 14px, allow wrapping
- Render address line (📍 icon) when present
- Render floor · building · room location line when any field present
- Keep existing icon and meta line unchanged
- Add .filter-toggle.warn CSS styles (amber border/text with active bg)
- Add Data Health row with toggle button in filter panel HTML
- Add noDexFilterActive state variable and toggleNoDexFilter() function
- Wire no_dex_days=5 into API URL builder when toggle active
- Integrate into getActiveFilterCount, renderActiveFilterTags, removeFilter, clearAllFilters
- Magic Kingdom: accurate 60-point polygon from OSM way 297428432
- All parks: adjusted boundaries for better alignment
- Increased stroke weight (3px) and opacity for visibility
- Higher fill opacity (0.15)
- Polygons for Magic Kingdom, Epcot, Hollywood Studios, Animal Kingdom, Disney Springs
- Resort area outline (dashed) for Pop Century / Art of Animation
- Color-coded to match existing park badge colors
- Labels with park icons at polygon centers
- Toggle chip (🏰 Parks) in map controls to show/hide
- Outlines visible by default on map load
Replaced the Snack category emoji with an actual chips illustration
served from static/icons/chips.png. The icon renders in the asset
list category badges as a 28x28 object-fit:contain image within
the 42x42 circular badge.
- Added is_disney INTEGER DEFAULT 0 column to assets table
- Backfilled 996 Disney assets based on D- customer prefix
- Server: is_disney in AssetCreate/AssetUpdate models, INSERT/UPDATE
- Server: disney_filter query param now uses is_disney column (no JOIN needed)
- import_cantaloupe.py: sets is_disney=1 when customer starts with D-
- import_machines.py: sets is_disney=1 when customer starts with D-
- disney_classify.py: sets is_disney=1 when classifying
- Frontend: new Location Type dropdown with Disney/Non-Disney toggle
(replaces the __disney__/__non_disney__ park dropdown pseudo-options)
Backend:
- disney_filter query param on /api/assets (values: 'disney' | 'non_disney')
- Detects Disney by checking disney_park IS NOT NULL OR customer name LIKE 'D-%'
- Catches ESPN, Wide World of Sports, all Disney resorts, etc.
- Adds LEFT JOIN to customers table when disney_filter is active
Frontend:
- '🏰 Disney only' and '🏢 Non-Disney' options in Disney Park dropdown
- Active filter tag shows which is selected
- Works together with other dropdown filters
Backend:
- GET /api/customers — lists all customers for filter dropdown
- GET /api/locations?customer_id=X — cascading locations per customer
- GET /api/users — lists users for assigned_to filter
Frontend:
- Collapsible 'Filters' panel with Customer→Location cascade, Status, Assigned To
- Active filter tags showing what's currently filtering, removable individually
- 'Clear all filters' button to reset all at once
- Result count display ('X assets found')
- Better search placeholder showing searchable fields
- Filter count badge on the toggle button
- Removed hardcoded display:none from GPS center chip on map tab
- Chip now shows when GPS position is obtained in startVisitTracking
- Added leaflet-draw CSS and JS to HTML head
- Added draw control (polygon, rectangle, circle) to map init
- Emits geofenceDrawn custom event for external hooks
- Manual photo area now shows two clear choices:
📁 Choose from Gallery — plain accept=image/* (EXIF preserved)
📸 Take Photo — calls openManualCamera() for quick capture
- Each has a label explaining EXIF behavior
- openManualCamera() now appends input to DOM before click()
(required by mobile Chrome for programmatic file input clicks)
- Dynamically created camera input is cleaned up after use
- camera-area overflow:visible to not clip the taller placeholder
- manPhotoInput now uses accept=image/* without capture attribute
-> opens gallery/file picker which gives raw file bytes (EXIF preserved)
- Camera shortcut preserved via openManualCamera() creates a temp
capture=environment input for quick field capture (no EXIF but available)
- Label updated: 'Tap to take photo' -> 'Tap to choose photo'
- Manual photo capture row references openManualCamera() instead
GPS:
- initGPS() now shows tappable GPS badge instead of failing silently
- Badge pulses amber with '📍 Tap for GPS' — click triggers permission prompt
- fillGpsParking() auto-triggers badge click if GPS not yet available
- requestLocation() extracted as reusable Promise
EXIF bypass:
- submitManualAsset() now uses server-extracted GPS from raw upload bytes
- Server returns exif_gps in /api/upload/photo response (was already working)
- Client was ignoring it — now fills parking + map link fields automatically
- Bypasses Android content provider EXIF stripping since server reads raw bytes
Admin separation:
- admin.canteen.ourpad.casa now served by systemd service (canteen-admin.service)
- Main server also systemd (canteen-main.service) — both auto-restart on crash/boot