Commit Graph

83 Commits

Author SHA1 Message Date
shawn cce5cf2b5f Add Disney park badges, filter pills, editable classification, and service entrances to asset detail view 2026-05-22 17:53:32 -04:00
shawn bb0f6b81e7 Add Excel import script for Cantaloupe machine list 2026-05-22 17:53:29 -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 2e2fd50f70 fix: retake admin screenshots in portrait with working emoji icons
- All admin screenshots retaken at 412×892 @2.625x (pixel 9a portrait)
- Fixed emoji rendering — ni-icon emojis now show colored icons (📈⚙️👥🏢📋📤📥)
- Fixed ADMIN_GUIDE.md TOC: 'Cantaloupe Data Sync' → 'Excel Import'
- Fixed sidebar nav list: '🔄 Sync' → '📥 Import'
2026-05-22 16:38:37 -04:00
shawn d92f2a0b2c docs: portrait admin screenshots + user guide admin section
- Retake all admin screenshots in portrait (412px narrow column)
- Add Admin Panel section (Section 9) to USER_GUIDE.md
- Update ADMIN_GUIDE.md references for portrait screenshots
2026-05-22 16:24:58 -04:00
shawn 1ef86ebfe0 docs(admin): retake all admin page screenshots for visual clarity
- Retake full set of p9a_admin_*.png screenshots (login, dashboard, settings, users, customers, activity, export, import, sidebar)
- Each screenshot inspected for layout errors
- Proper 1280px desktop viewport for all pages, clean empty states
- Save to docs/images directory
2026-05-22 16:17:57 -04:00
shawn d139abf923 docs(admin): new screenshots for Import page and hamburger sidebar
- Replace p9a_admin_sync.png with p9a_admin_import.png (new Import page)
- Add p9a_admin_sidebar.png (mobile sidebar with hamburger open)
- Update ADMIN_GUIDE.md with mobile navigation section
- Add screenshot reference in Import section
2026-05-22 16:12:15 -04:00
shawn 61928055af docs(admin): update guide for Excel import and hamburger sidebar
- Replace Cantaloupe Sync section with Excel Import section
- Update troubleshooting tips for import (not sync)
- Mention hamburger menu in mobile description
2026-05-22 16:08:32 -04:00
shawn bc0885ff28 Update user guides with Pixel 9a screenshots
- Updated USER_GUIDE.md with Pixel 9a mobile screenshots (login, dashboard,
  manual form, assets list, map, drawer)
- Added ADMIN_GUIDE.md with admin panel screenshots (login, dashboard,
  customers, sync page)
- 10 screenshots captured at 412x892 viewport @2.625 DPR
- Removed stale old image references, replaced with new p9a_* images
2026-05-22 16:00:53 -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 72003e2742 fix: GPS chip visibility and Leaflet Draw geofencing
- 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
2026-05-22 11:50:44 -04:00
shawn f9f6d9d5ce Fix manual photo UI: both gallery picker + camera button visible
- 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
2026-05-21 23:12:43 -04:00
shawn 90bd3f0e4b Remove capture=environment from manual photo input — plain file picker preserves EXIF
- 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
2026-05-21 23:06:49 -04:00
shawn e2025126a8 v3: GPS tap-to-enable + server-side EXIF bypass for manual creation
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
2026-05-21 22:53:14 -04:00
shawn bc3d27f3a2 chore: gitignore uploads/photos/, remove test images 2026-05-21 22:45:37 -04:00
shawn 02a38a8459 fix: EXIF data now visible in photo preview card
- extractExifData() now async — reads real EXIF tags (Make, Model,
  DateTimeOriginal, ISO, FNumber, ExposureTime, FocalLength, GPS)
  via exifr.parse() and displays them in the UI
- handlePickedPhoto() + reader.onload made async to support this
- GPS extraction already auto-triggers on photo pick (tryExtractGpsFromPicked)
- Server-side exif_data round-trip already functional (verified)
- DNG/RAW-02: .dng allowed, 20MB limit
2026-05-21 22:45:28 -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 4e3a58ca55 chore: remove test DNG file committed by accident 2026-05-21 22:23:34 -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 eaa2dab365 fix: createAssetFromPicked() now passes original File with EXIF
- Set manualPhotoFile = pickedPhotoFile directly (preserves EXIF)
- Set manualPhotoBlob = pickedPhotoFile (preserves EXIF)
- Keep FileReader for preview display only
- Trigger EXIF GPS extraction via extractGpsFromPhoto()
- Fill GPS coordinates into manual form (parking + map link)

Previously the function used readAsDataURL -> atob -> Blob
which stripped all EXIF metadata. Now the original File
flows through to submitManualAsset where exifr.parse()
re-embeds EXIF on the server.
2026-05-21 22:19:32 -04:00
shawn df90ebf5eb t_63e94899: Remove duplicate ocrPickedPhoto/createAssetFromPicked stubs — originals at 1783/1797 now resolve correctly 2026-05-21 22:15:20 -04:00
shawn e9f6af0590 fix: Map tab initialization (H1, H2)
- Added global let map/heatLayer/heatVisible declarations
- Leaflet map initializes on first visit to Map tab (OSM tiles)
- Implemented loadAssetPins() - fetches assets with coords from API,
  creates Leaflet markers with popups, auto-fits bounds
- Guard toggleHeatmap() with proper declared globals
- Closes #31 H1, #32 H2

Also confirmed M1 (empty asset list) is not a bug: API returns [] because
DB has no assets yet. Empty state renders correctly.
2026-05-21 21:46:59 -04:00
shawn 5822dfc1ab fix: guard stopManualPhoto() call — mode switching threw ReferenceError
setAddAssetMode() called stopManualPhoto() which was removed during
the canvas→file-input refactor. Every mode switch failed silently.
2026-05-21 21:42:01 -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 906b61143a fix: EXIF GPS extraction now works with native camera capture
- Replaced getUserMedia() + canvas.toBlob() with <input type=file capture=environment>
  → native camera app preserves full EXIF data including GPS
- Defined missing extractGpsFromPhoto() using exifr.gps() library
- Manual mode now uploads the original file (with EXIF) instead of a canvas-encoded blob
- Server-side exif_gps fallback if client-side extraction fails
- Renamed manualPhotoBlob → manualPhotoFile for clarity
2026-05-21 20:51:09 -04:00
shawn 62146c5ce7 db: auto-backup before server start + daily cron
- backup-db.sh: copies assets.db to /home/oplabs/backups/canteen-db/
- start.sh: runs backup automatically before every launch
- Daily cron at 3am for unattended backups
- Keeps last 30 backups, prunes oldest
2026-05-21 20:21:16 -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 921a40a560 Fix: call initAuth() on DOMContentLoaded so login overlay shows on page load. Also includes v3 admin separation changes that weren't committed. 2026-05-21 20:07:52 -04:00
shawn b5381fde69 t_4a71eab5: Strip admin UI from main index.html
- Removed empty comment blocks for removed admin tabs (Customers, Dashboard, Activity, Reports, Settings)
- Removed geofence Create/Edit/Delete UI from map tab (chip, color picker, geofence panel)
- Removed leaflet-draw dependency (no longer needed)
- Removed CustState and showCustView (admin-only)
- Removed all geofence JS functions (toggleGeofenceDraw, saveDrawnGeofence, loadGeofences, renderGeofenceList, editGeofence, deleteGeofence, assignGeofenceUsers)
- Removed loadTechnicianOptions() and its /api/users call
- Fixed loadManualLocations() to not call removed /api/locations endpoint
- Removed initMap orphan call and drawingGeofence cleanup in switchTab
- Removed unused geofence CSS styles and color-picker-row styles
- Kept Admin Panel link in drawer footer pointing to admin app
2026-05-21 17:56:13 -04:00
shawn ba6921eade t_4a71eab5: Strip admin UI from main index.html
- Replaced loadSettingsCache() with hardcoded seed data (categories, makes, keys, badges)
- Removed tabActivity lifecycle hook (activity log moved to admin app)
- Updated version to v3.0 + Admin Panel link in drawer footer
- Updated PROJECT.md with v3 project structure (3 repos, admin server docs)
- Removed backup files (.bak, .orig)
- Main index.html reduced from 7645 to 4228 lines
2026-05-21 17:47:22 -04:00
shawn 0824075048 t_68f4f6a2: Strip admin API routes from main server.py 2026-05-21 17:27:06 -04:00
shawn 9a1a5d9caa docs: add admin.canteen.ourpad.casa proxy config to PROJECT.md
NPMPlus proxy ID 20, cert ID 21, backend 192.168.0.127:8901
Cloudflare DNS A record grey cloud, Let's Encrypt SSL

Closes gitea issue #26
2026-05-21 17:18:21 -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 5d2d169885 fix: rename photoPicker to cameraInput/galleryInput broke JS IIFE
Two places referenced old photoPicker element ID that was removed:
1. connectFromGallery() — switched to window._connectMode flag + galleryInput.click()
2. Connect mode hook IIFE — now hooks both cameraInput and galleryInput via
   shared hookPicker() helper using window._connectMode instead of DOM attr

This was crashing the entire inline script (TypeError on null) ~line 4040,
preventing initAuth()/checkAuthGate() from ever running — no login screen.
2026-05-21 16:16:53 -04:00
shawn e16f2ec90f gallery: split Take Photo vs Pick from Gallery inputs
- Two dedicated <input> elements: cameraInput (capture=environment) for
  direct camera, galleryInput (no capture) for gallery picker
- Both wired to handlePickedPhoto() via a shared wireInput helper
- refreshGPS() fires on button click (before picker opens) for a fresh
  high-accuracy GPS fix — no maximumAge, so it pulls current location
  instead of relying on cached values from page-load initGPS()
- Workaround for browser EXIF GPS stripping: camera captures may preserve
  EXIF GPS, gallery picks fall through to device geolocation API
2026-05-21 15:29:03 -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 28f1a33a2d gallery: increase server OCR timeout to 20s
- 6s was too short for 13MB DNG upload on mobile
- Bumped to 20s to handle large RAW photos
- Added 'Uploading to server OCR...' status message during upload
- Server OCR handles DNG correctly (tested via curl), so the
  timeout was the root cause of gallery upload failures
2026-05-21 14:55:13 -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 97699a95ea gallery upload: better OCR error messages
- Catches 'unknown error' from Tesseract.js (unsupported
  image formats like RAW/DNG) and shows helpful guidance:
  'Use the 📸 live camera instead'
- Falls through to actual error message for other failures
- Better null-safety on error object
2026-05-21 14:47:34 -04:00
shawn 78c3c19ce4 gallery upload: add FileReader + img error handlers
- reader.onerror: shows error message on file read failure
- img.onerror: detects unsupported image formats (HEIC, RAW)
- Shows clear feedback: 'unsupported format' or 'format not supported'
- Helps diagnose gallery upload issues
2026-05-21 14:44:02 -04:00
shawn 6403699486 fix: login lag + 📸 overlay visibility + status text
- Login lag: camera/GPS/assets no longer start before login.
  Only initAuth() + initOfflineSupport() run on pageload.
  Camera/GPS/assets start after successful login (or auto-login
  from stored session).
- 📸 capture overlay: was defined in HTML but never shown.
  Now shown when camera starts, hidden when scanning stops.
- Status text: updated from 'Point camera at a barcode' to
  'Point camera at a barcode, or tap 📸 for text stickers'
2026-05-21 14:16:24 -04:00
shawn d55cc4aa44 gallery upload: GPS fallback + auto-lookup
- GPS: when EXIF stripped by mobile browser, falls back to
  navigator.geolocation.getCurrentPosition() for device location
- OCR result now populates scanMachineId hidden field so Create works
- createFromScanPhoto also checks AppState.gpsLat as fallback
- Gallery OCR now auto-lookups via handleBarcode() — routes to confirm
  card (existing asset) or new asset form (not found)
- Manual entry button also calls handleBarcode() after prompt
2026-05-21 14:09:14 -04:00
shawn 7331b4e1aa barcode scan: add confirmation step before auto check-in
- Existing assets now show a confirm card with machine ID + ✓ Confirm & Check In / ✏️ Edit buttons
- Confirm triggers auto check-in then shows the result card
- Edit opens inline machine ID editor with Re-lookup
- Cancel button returns to original confirmation
- New asset flow unchanged (form already editable)
2026-05-21 14:03:59 -04:00
shawn 04544603ea Fix extra closing brace causing JS parse error 2026-05-21 12:26:07 -04:00
shawn 07a0212b6b Clean up orphaned functions causing JS errors 2026-05-21 12:23:29 -04:00
shawn 8480a6d364 Fix mode structure: Scan (barcode+OCR+gallery) + Manual only
- Removed separate OCR and Connect mode toggles — merged into Scan
- Renamed Barcode mode to Scan with OCR fallback + gallery upload
- Added captureScanOcr() — tap to capture frame for OCR when barcode fails
- handleBarcode() now compares barcode vs OCR result, picks valid ID
- Gallery photo picker integrated into Scan mode (OCR + EXIF GPS)
- Removed old photoPreviewCard and connect-mode HTML sections
- Removed orphaned functions referencing removed elements
2026-05-21 12:20:14 -04:00
shawn 37ba54b750 T5: Offline queue + background sync — fix missing offline banner, add API GET caching to SW
- Added missing #offlineBanner HTML element with queueCountBadge
  (showOfflineBanner/hideOfflineBanner were referencing it but it didn't exist)
- Enhanced SW: API GET calls now use network-first with cache fallback
  (POST/PUT/DELETE still pass through — main thread handles offline queuing)
- Bumped SW cache from canteen-v1 to canteen-v2
2026-05-21 12:01:05 -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 f895bf99de T2: Photo gallery picker with preview 2026-05-21 11:20:20 -04:00