- Added validate_roles() and roles_contain() helpers for comma-separated roles
- Updated create_user/update_user to accept role combinations (e.g. 'admin,technician')
- Changed all exact role queries (='technician', IN('technician','admin')) to LIKE
- Replaced single <select> with checkboxes in add/edit user modals
- Added renderRoleBadges() to display multiple role badges per user
- Added checkbox-group/checkbox-label CSS styles
- Updated tech-photo-upload technician query to use LIKE
- Updated db reset auth check to use roles_contain()
- Set shawn's role to 'admin,technician'
- Cleaned up unused imports (hashlib, io, etc.)
Applies to canteen-admin-server, canteen-admin-server-dev, and tech-photo-upload
Backend (cantaloupe_sync.py):
- GET /api/admin/cantaloupe/batches/{id}/field-changes — flat list of per-field diffs
- POST /api/admin/cantaloupe/batches/{id}/apply-field-changes — apply selected changes only
- PerFieldApprovalRequest/FieldChangeItem Pydantic models
- Activity log entries for apply-field-changes
Frontend (static/index.html):
- toggleFieldReview() — loads field changes via API, shows/hides review area
- renderFieldChanges() — geotagger-style cards with checkboxes per field, grouped by machine
- applyFieldChanges() — POST selected changes with confirmation modal
- Auto-select blank-fill changes (is_blank_fill flag)
- Sticky apply bar at bottom with live selected count
- Toggle all, toggle per-asset, toggle per-field helpers
- Review Changes button in pending review card
- /api/assets/search must be registered before /api/assets/{asset_id}
to prevent FastAPI path param from catching 'search' as an asset_id.
Returning a 422 with array detail caused frontend to show [object Object].
- Improve error message extraction in clearAssetGps catch block.
- DELETE /api/assets/{id}/gps: clears lat/lng to NULL
- GET /api/assets/{id}: asset lookup by ID
- GET /api/assets/search?machine_id=: asset lookup by machine ID
- Export & Admin page: Clear Asset GPS card with lookup + confirmation
- Tests: 204 success + 404 not found
- Replace broken icon-only sidebar collapse with off-canvas hamburger menu
that slides in from left (hidden by default on <768px)
- Add sidebar overlay backdrop, auto-close on nav/logout
- Rename 'Cantaloupe Sync' to 'Import' in sidebar and 'Excel Import' in page
- Remove 'Sync Now' button and backend subprocess auto-export entirely
- Keep manual Excel file upload as the only import path
- Update empty-state text and upload handler to work without Sync Now btn
- Changed subprocess from 'python' to 'sys.executable' (fixes FileNotFoundError
in systemd — venv python not in system PATH)
- Added '📤 Upload Excel' button to admin frontend sync page
- uploadSyncFile() sends file to the same /api/admin/cantaloupe/sync endpoint
(which already supported file upload — frontend just didn't expose it)
- Users can now use 'Sync Now' for auto-download or 'Upload Excel' for manual