Commit Graph

29 Commits

Author SHA1 Message Date
shawn e8a918fc7b feat: add is_disney column for reliable Disney/Non-Disney filtering
- 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)
2026-05-22 18:34:13 -04:00
shawn 60dfd3434a feat: add Disney/Non-Disney quick filter to dropdown panel
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
2026-05-22 18:25:54 -04:00
shawn e0412acbe3 feat: add Category, Make, Disney Park to filter panel dropdowns with backend endpoints 2026-05-22 18:19:32 -04:00
shawn d98a791c96 feat: improved asset filter UX - collapsible filter panel, customer/location cascade, status dropdown, assigned_to filter, active filter tags, result count
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
2026-05-22 18:15:35 -04:00
shawn 35ccebb0f0 Add serial_number to search bar query 2026-05-22 18:07:42 -04:00
shawn 7394d44caf Load all assets at once (no pagination), add server-side disney_park filter 2026-05-22 17:59:33 -04:00
shawn 95b5bd26eb Add pagination (Prev/Next with counter) and X-Total-Count header 2026-05-22 17:57:24 -04:00
shawn 27f372ed49 DB schema: add disney_park column to assets, create service_entrances table
- Added disney_park TEXT DEFAULT NULL to assets table in _create_v2_tables
- Created service_entrances table with id, location_id, name, latitude,
  longitude, notes, created_at, updated_at; location_id FK to locations
  with ON DELETE CASCADE; name/lat/lon are NOT NULL
- Added v4 migration in init_db: ALTER TABLE for disney_park on existing
  DBs, schema-aware recreation of service_entrances if name column lacks
  NOT NULL constraint
- 44/45 tests pass (1 pre-existing 404 failure on stats endpoint)
2026-05-22 17:45:47 -04:00
shawn 6aa5d3d35c Add Disney park classification + service entrance API
- Auto-classifies 1,155 Disney assets into parks/resorts/offices
- New columns/table: assets.disney_park, service_entrances
- API: /api/disney/stats, /api/disney/classify, /api/assets/{id}/disney-park
- API: /api/service-entrances CRUD, /api/locations/{id}/service-entrances
- OCR strategy: ConnectID last-5, resort keyword detection, address patterns
2026-05-22 17:41:11 -04:00
shawn 25da436bba Fix: seed admin/changeme user for frontend E2E tests
The test suite login as 'admin'/'changeme' but only 'shawn' was seeded.
Adding the admin user with SHA256('changeme') hash lets all 15 Playwright
tests reach the actual app pages instead of failing at login.
2026-05-22 15:08:39 -04:00
shawn 497d1104c1 T5: Expand DNG/RAW-02 photo upload support
- Added .dng to PHOTO_ALLOWED_EXTS
- Bumped PHOTO_MAX_SIZE from 10MB to 20MB
- DNG files (~13MB from Pixel 9a) now accepted by all upload paths:
  /api/upload/photo, /api/ocr, connect_labels
- Server validates extension+size only; DNG EXIF preserved naturally
  by write_bytes(); _re_embed_exif() correctly skips non-JPEG;
  _extract_gps_from_bytes() opens DNG as TIFF via PIL (already works)

Verified: .dng upload via /api/upload/photo and /api/ocr both succeed,
file saved byte-identical.
2026-05-21 22:35:47 -04:00
shawn 43bdedde61 T1: Clean up duplicate function declarations
- Removed duplicate clearPickedPhoto() at old line ~1772
  (kept newer version at line ~3857 with pickedPhotoGps clearing)
- Removed duplicate extractGpsFromPicked() at old line ~1831
  (kept newer async version at line ~3766 that uses exifr)
- Both ocrPickedPhoto() and createAssetFromPicked() were already
  singular — they were refactored in a prior edit

Verified: no duplicate function names, JS syntax OK, no regressions
2026-05-21 22:23:30 -04:00
shawn 78dbdfd5c5 EXIF round-trip: read before upload, re-embed server-side
- Client: exifr.parse() reads full EXIF before upload, sent as exif_data
- Server: piexif re-embeds EXIF into saved JPEG after upload
- Auto-extract GPS from picked gallery photos on selection
- Removed Google Photos button (needs OAuth, not feasible)
2026-05-21 21:29:55 -04:00
shawn 58f8b67b28 auth: full session-based auth with remember-me support
- Seed user: shawn / Brett85!@ (SHA-256)
- Sessions table with expires_at column
- login endpoint supports remember_me flag (30d vs 1d expiry)
- logout endpoint invalidates server-side token
- auth middleware + /api/auth/me check session expiry
- Frontend sends remember_me in login request body
- Frontend calls /api/auth/logout on client logout
- Migration function for existing databases
2026-05-21 20:17:21 -04:00
shawn 0824075048 t_68f4f6a2: Strip admin API routes from main server.py 2026-05-21 17:27:06 -04:00
shawn 7cf566ad6d t_2f17b1b4: Add EXIF GPS extraction to upload/photo and OCR endpoints
- _extract_gps_from_bytes helper reads GPSInfo IFD via PIL get_ifd()
- Converts DMS to decimal degrees, returns {lat, lng} or None
- /api/upload/photo: reads bytes first, extracts GPS before saving
- /api/ocr: extracts GPS from raw bytes before OCR processing
- exif_gps returned alongside path/machine_id in both endpoints
- Photos without EXIF GPS omit the exif_gps key entirely
- Raw file bytes preserved (no re-compression, no EXIF stripping)
2026-05-21 17:17:38 -04:00
shawn 09d83da4e2 server OCR: accept any file extension, detect format from bytes
- Removed extension whitelist — no more 'unsupported format' errors
- Falls back to .jpg temp file for unknown/no extension
- PIL detects actual image format from file bytes (handles DNG as TIFF)
- Android browsers often strip DNG extensions when picking from gallery
- Tested: works with .dng, .jpg, no extension, and .bogus
2026-05-21 15:05:39 -04:00
shawn 0cf5dddf83 DNG/RAW photo support for gallery upload
- Server: accept .dng in OCR endpoint (PIL opens as TIFF)
- Server: bump OCR size limit to 20MB (DNG files can be large)
- Client: gallery upload tries server OCR first (with 6s timeout),
  then falls back to client-side Tesseract.js — matches 📸 capture flow
- DNG photos now work with 'Pick from Gallery'
2026-05-21 14:51:57 -04:00
shawn 622996624a T4: Connect Label upload workflow — unified photo + OCR + GPS
- Added 'Connect' mode toggle on Add Asset tab with Camera/Gallery options
- processConnectLabelPhoto(file): parallel OCR + GPS extraction
- Shows results card with editable machine_id, lat/lng, map preview
- POST /api/connect-label endpoint with photo upload + GPS + reverse geocode
- Connect Label section in Assets list (localStorage-backed recent scans)
- Handles OCR failure (manual entry) and missing EXIF GPS gracefully
2026-05-21 11:50:09 -04:00
shawn 62016bae83 Revert all UX sweep changes (T5-T11, 16 commits)
This reverts all commits from 828fd5c..bc8cc99, undoing the entire
canteen-ux-sweep board changes: Dashboard Fixes, Map Improvements,
Modal System, Navigate fixes, Empty States, Search & Filter, Reports,
Navigate Route-Finding UI, T6 cleanup, Page Transitions, Skeleton Loading.

Restores codebase to pre-UX-sweep state at d844429.
2026-05-21 07:36:58 -04:00
shawn 88694e6b8e T6 Map Page Improvements: geolocation fallback, location coordinates, friendly error messages
- Improved geolocation error messages (maps error codes to friendly text: Location blocked, GPS unavailable, GPS timed out)
- Added location data to list_assets API (location_latitude, longitude, address, building_name, building_number, name) via LEFT JOIN
- Map pins now use location coordinates as direct fallback when asset lacks own lat/lng
- GeocodeAndPin now falls back to location address fields when asset address is empty
- All 96 map-related tests pass (66 API + 30 frontend)
2026-05-20 23:33:47 -04:00
shawn 51f9bcfacd fix: reports page — add asset lat/lng to /api/visits for map button, fix null field guard in form error handler 2026-05-20 22:58:50 -04:00
shawn 828fd5c482 T5 - Dashboard Fixes: active technicians, interactive bars, quick actions, caching
- Fix Active Technicians: query users WHERE role='technician' instead of counting from visit data
- Interactive bar charts: hover tooltips show label+count, click drills down to filtered asset list
- Quick action icons: coin for Assets, Sites, Check-in replaces CSV export buttons
- No visit data: suggestion card with 'Add a Check-in' button
- No manufacturer data: hide section entirely when empty
- 30-second TTL cache on both backend (/api/stats) and frontend (loadDashboard)
- Add dashboard cache module with time-based invalidation
2026-05-20 22:25:24 -04:00
shawn d7f2e07c68 feat: 6 UX improvements for field technicians
F1: Scan existing asset → auto check-in (handleBarcode now autoCheckins)
F2: Auto-generate address from GPS reverse geocode (Nominatim, all 3 modes)
F3: Larger camera viewport (3:4 aspect ratio, full-width)
F4: Building # already syncs to Address/Trailer field (existing)
F5: Parking location GPS button already exists (fillGpsParking)
F6: Navigation tool with route line on map (existing navigateToAsset)

Backend: Added GET /api/geocode endpoint for reverse geocoding
2026-05-20 18:01:39 -04:00
shawn b69bffe44a feat: auto check-in on barcode scan of existing assets
- handleBarcode() now calls autoCheckin() after finding asset via barcode
- showScannedAsset() no longer shows manual checkin card
- Replace 'Check In' button with 'Auto checked in' indicator
- Add CSS for sr-checkin-done span
2026-05-20 17:57:04 -04:00
shawn 02b3ba665e feat: oninput handler copies building # to trailer number
- Manual asset form: manBuildingNumber → manAddress (address/trailer field)
- Location form: locFormBldgNum → locFormTrailer
2026-05-20 17:55:00 -04:00
shawn fc0a3ee9be fix: auto-update asset lat/lng on check-in so map shows pins
Previously assets never had coordinates populated - only checkins did.
Now when a checkin is created with GPS data, the asset's latitude/longitude
fields are auto-updated. Also backfilled existing assets from their latest
checkin history. This fixes empty maps — loadAssetPins() filters by
a.latitude != null.
2026-05-20 15:43:43 -04:00
shawn b20998e8e3 fix: OCR last-5-digits extraction, barcode 1D-only scanning, auto-checkin on create
- OCR: Extract only last 5 digits from Connect ID (XXXXX-XXXXXX → last 5)
- Barcode: Switch to decodeFromVideoDevice with 1D-only format hints
  (Code 128/39/EAN/UPC/ITF/Codabar), TRY_HARDER, 50ms scan interval
- Auto check-in: New assets auto-checkin with GPS on creation via
  all three entry modes (barcode, OCR, manual)
- Tests: Updated e2e tests for login wait and API flow
2026-05-20 15:03:20 -04:00
shawn 7da3f28c6a Initial commit: Canteen Asset Geolocation Tool v2 2026-05-17 18:55:28 -04:00