Commit Graph

119 Commits

Author SHA1 Message Date
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
shawn 5cb8e8aa73 T1: Tesseract.js client-side OCR fallback 2026-05-21 11:08:16 -04:00
shawn c8973337c8 Clean up accidental db-shm/db-wal commit, add to gitignore 2026-05-21 07:45:35 -04:00
shawn 14687e7ba6 Fix Nav tab infinite recursion (pre-UX-sweep bug)
The reverted code had two bugs in the tab switching logic:
1. closeNavigate() called switchTab() -> infinite recursion since
   switchTab() calls closeNavigate() when leaving tabNavigate
2. currentTab was updated AFTER closeNavigate(), so the recursive
   call hit the same condition over and over

Fix: move currentTab assignment before cleanup, use a wasNavigating
flag to track the old tab state, and make closeNavigate() only reset
nav UI (caller handles tab transition).
2026-05-21 07:45:16 -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 bc8cc99280 T11a: Skeleton loading fade-out fixes
- Remove duplicate @keyframes shimmer (CSS conflict resolution)
- Remove dead .skeleton/.shimmer-stat/.shimmer-bar CSS classes
- Add hideSkeletons() fade-out to renderAssetList, renderCustList, renderActivity
- Dashboard and reports already had restore*Content() fade-out
2026-05-21 00:05:17 -04:00
shawn b398bc1469 T11b: Page transitions + micro-animations polish
- Remove duplicate FAB HTML element (was two #fabAdd buttons)
- Remove duplicate FAB CSS block (keep single definition with pulse class)
- Add .fab:hover to remaining FAB CSS block
- Convert all fab.style.display usage to hidden-fab CSS class for smooth transitions
- Add direction-aware tab transitions: forward slides from right, reverse (back) slides from left
- Fix FAB pulse persistence: restart animation on click instead of removing permanently
- Move FAB init code after FAB HTML element so hidden-fab applies on initial load
- Remove redundant style.display FAB visibility in switchTab (now handled by updateFabVisibility)

Tab transition animations already existed (slide+fade with cubic-bezier easing).
Button press/hover scale effects and .btn-pressed haptic class already existed.
Success toast slide-up already existed via translateY transition.
Consistent easing curve (cubic-bezier 0.4,0,0.2,1) already applied across transitions.
prefers-reduced-motion already respected (animation-duration: 0.01ms at line 1696).
2026-05-21 00:03:45 -04:00
shawn 111a412c3f T6 cleanup: remove scope-creep FAB, pull-to-refresh, skeleton loading — map features preserved 2026-05-20 23:49:23 -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 0aa9e1f0d8 T4b - Build Navigate Route-Finding UI
- Added GPS-aware empty state with location needed/gps off messaging
- Added GPS warning banner with 'enable location access' link in Navigate tab
- Show user's current location pin + accuracy circle on map when GPS available
- Enhanced destination search: GPS-aware filtering, badges for nav status
- Back button navigates to previous tab (or Map tab as fallback)
- Added navBackToPrevious() function with proper switchTab recursion avoidance
- Added CSS: .gps-warning, .gps-enable-link, .nav-route-info, .nav-route-item
- navigateToAsset() now goes to Nav tab even without GPS to show warning UI
- Improved search results: shows GPS on/off badge, disables non-navigable assets
- All navigation and smoke tests pass (5/5)
2026-05-20 23:23:01 -04:00
shawn b31f8e8a89 feat(navigate): GPS-aware empty state, route info CSS refactor, user location marker on map
- Add GPS warning banner with enable-location link when GPS unavailable
- Refactor route info cards from inline styles to CSS classes
- Update empty state messaging based on GPS availability
- Show user location marker with accuracy circle on nav map when GPS available
- Hide map container when no GPS (show only empty state + search)
2026-05-20 23:15:27 -04:00
shawn 845523c65b fix: toggle aria-hidden on modal open/close for screen reader accessibility 2026-05-20 23:10:19 -04:00
shawn 1a87427295 T1: Finalize empty states & onboarding — geofence point format fix
- Handle both [lat, lng] array and {lat, lng} object formats in geofence rendering
- All empty states verified: consistent icon + heading + description + CTA pattern
- Onboarding welcome overlay with localStorage flag
2026-05-20 23:00:07 -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 40fdc9dca6 T3: Search & Filter UX improvements
- Add missing assetCountBar div with count bar styling
- Add missing clearFiltersBtn with show/hide logic
- Add .search-highlight CSS for matching term highlighting
- Add .ai-location CSS for location context in asset items
- Add loading spinner overlay during search debounce
- Fix filter pills to use JS string escaping (not HTML escaping)
- Add 'make' filter parameter to API query
- Include 'make' in filter detection for count bar
2026-05-20 22:53:50 -04:00