Job Photos
Field photo documentation, annotation, and before/after comparisons — attached to a job site, not a person.
On this page
What it is, and where to find it
Job Photos is a paid add-on (job_photos in the billing catalog) that gives crews and office staff a place to capture, tag, and mark up photos (and other files) tied to a job site. It's the same underlying feature everywhere it shows up — one component, JobPhotosPage, is rendered at two different routes:
- Its own app shell at
/photos/jobs, with a dedicated dark sidebar — this is what crew accounts land on. The Home dashboard surfaces it as a "Job Photos" tile ("Field photo documentation & field forms") whenever the org has the add-on, both for crew accounts and everyone else. - Inside Landscapt/CRM, under Scheduling → Job Photos (route at
/crm/scheduling/job-photos).
A Photo Job can optionally link to an Equipt Project (for cost tracking) or a Landscapt Client — both links are visible and editable from the job detail pane. It is not tied to a specific CMMS Work Order, PM Schedule, or CRM Job/visit — see Known limitations below for what that means in practice.
Who can access it
Access is role-based, computed client-side in usePhotoAccess(). Admins and Crew accounts always have access; everyone else needs an explicit photo_module_access flag on their profile. This is not a crew-only tool — managers, admins, and technicians can all be granted access, and in practice the full gallery/detail view (edit, archive, delete, project/client linking) is only shown to non-crew roles, while crew gets a simplified mobile-first view.
| Role | Can access | Can upload | Can annotate | Can delete |
|---|---|---|---|---|
| Admin | Yes | Yes | Yes | Yes |
| Manager (“Sales”) | Only with the access flag | Yes | Yes | Yes |
| Crew | Yes — auto-granted | Yes | No | No |
| Technician | Only with the access flag | Yes | No | No |
| Viewer / Purchaser | Only with the access flag | No | No | No |
CrewPhotoView instead of the full PhotoGallery: a two-column grid, Before/After/All filters, a big sticky "Upload Progress Photo" button, and a read-only lightbox. Crew cannot open the annotation editor — the route itself redirects them away if they land on it directly.Photo Jobs
Photos aren't uploaded loose — every photo belongs to a Photo Job, a lightweight record (name, customer name, address, notes, status) that exists specifically for organizing photo documentation by job site. Photo Jobs are distinct from CMMS Work Orders and CRM Jobs — they're their own object.
- Status —
active,complete, orpending, changed with one-click action buttons on the detail pane. - Archive — a separate axis from status; archived jobs drop out of the default list but can still be found under the "Archived" view filter.
- Optional Project link — ties the Photo Job to an Equipt/PO Project for cost tracking; the linked project's status badge and full detail sheet are reachable directly from the Photo Job.
- Optional Client link — ties the Photo Job to a Landscapt Client record. If left unlinked, the job is only associated with a customer by free-text name — see Known limitations.
Both List and Table view modes are available for browsing Photo Jobs, and each job has its own Comments and Audit Trail tabs, same as other record types in the app.
Uploading photos, videos, and files
The uploader offers four entry points: Camera (opens the device's rear camera directly via capture="environment" on mobile), Library (photo picker), Videos, and Files (PDF, Word, Excel, PowerPoint, text, CSV). Multiple files can be queued at once.
- Image compression — any image over 500 KB is compressed client-side to a 500 KB target (max dimension 2048px, JPEG/PNG, quality 0.85) before upload, explicitly to keep uploads workable on poor cell coverage in the field. EXIF is preserved through compression. If compression fails for any reason, the original file is uploaded unmodified rather than blocking the upload.
- Location tagging — GPS is captured from the device's live Geolocation API at the moment of upload, not parsed from the photo's EXIF data. The code is explicit about why: mobile browsers routinely strip EXIF GPS tags before JavaScript can read them, so "where the device is right now" is the more reliable signal for a photo being taken live in the field. If location permission is denied, the photo still uploads — it's just untagged.
- iCloud proxy warning — the uploader flags any image under 100 KB with a visible warning that it may be an undownloaded iCloud thumbnail rather than the real photo, and suggests opening it in the Photos app first.
- Each queued file can be given a display name, a Before/During/After/Neither flag, one or more tags, and a note before submitting — set individually per file or as a default applied to the whole batch.
job-photos-original Supabase bucket, annotated composites to job-photos-annotated. Only the storage path is stored in the job_photos table — signed URLs (1-hour TTL) are generated fresh at read time, never persisted, matching how attachments work elsewhere in the app. Deleting a photo soft-deletes the database row only; the underlying storage object is left in place intentionally, with storage cleanup treated as a separate admin task.Tagging, before/after, and comparisons
Every photo (or video) can carry a Before/During/After/Neither flag and any number of the fixed tag set:
Tree RemovalTrimmingMulchLawnDrainageHardscapeCleanupOther
The gallery (office/full view) can filter by file type (Photos / Videos / Docs) and by the Before/During/After/Annotated tabs. A "Select" mode lets you multi-select photos to bulk-apply a Before/After/During tag, or to clear tags.
Before/After comparisons are a separate, explicit pairing — not just two photos that happen to share the Before/After tag. Select exactly two photos in Select mode and choose "Pair as Before/After"; a dialog lets you preview each photo, swap which one is "before" vs. "after", and give the pair an optional label (e.g. "Front bed", "Retaining wall"). A job can have several such pairs. Comparisons render with a slider view (BeforeAfterSlider), and any comparison can be deleted independently of the photos it references.
Annotating a photo
The annotation editor is a Fabric.js canvas layered over the original photo, reachable from any photo's "Annotate" button (Admin/Manager only). It exists to mark up areas of interest for the crew to see — the page's own subheading says exactly that: "Mark up areas of interest for the crew".
| Tool | What it does |
|---|---|
| Select | Click an existing shape or text label to select, move, resize, or rotate it. Delete key (or the toolbar trash icon) removes what's selected. |
| Arrow | Click and drag to draw a straight arrow with a filled head — the standard “this, right here” marker. |
| Circle | Click to drop a fixed 40px-radius outlined circle centered on the click point. |
| Text | Click to place an editable label (“Label” by default) with a semi-transparent black background so it reads over any photo. Double-click to rename it. |
| Draw (freehand) | Switches the canvas into freehand pencil mode — draw any freeform line, 4px wide. |
Four colors are available — Red, Yellow, Green, White — applied to whichever tool is active. Undo removes the most recently added shape; Delete removes whatever is currently selected.
Saving renders the canvas (photo + all markup) to a flattened PNG, uploads it to the job-photos-annotated bucket, and stores the raw Fabric.js JSON alongside it so the annotation can be reopened and edited later — not just viewed as a flat image. Once a photo has annotations, both the office gallery and the crew view display the annotated composite instead of the plain original by default, with a small pencil-icon badge marking which photos have markup.
Worked example: documenting a mulch job
- Office creates a Photo Job — "123 Main St — Lawn & Mulch" — optionally linking it to the Client and/or the Equipt Project for cost tracking.
- Before the crew starts, someone (crew or office) uploads a "Before" photo of the bed from the Camera picker. It's automatically compressed and GPS- tagged from the device's current location.
- A manager opens that Before photo and annotates it — a red circle around a low spot that needs extra mulch depth, plus a text label reading "2″ here" — and saves. The circle and label are now baked into a composite PNG the crew sees when they open the photo, without needing to be in the office to hear the instruction.
- After the crew finishes, they upload an "After" photo of the same bed from the field — a two-tap flow (Camera → Upload) that doesn't require touching a keyboard.
- Office selects both photos in Select mode, chooses "Pair as Before/After", confirms which is which, labels the pair "Front bed", and saves. The comparison now appears with a slider anyone reviewing the job can drag between before and after.
Known limitations
- Client linking is manual and optional. A Photo Job stores a free-text
customerNameand an optionalclientIdFK. If no client is explicitly linked, the job is associated with a customer by name string only — there's no automatic matching against the Clients table. PerCLAUDE.md, this kind of auto-match/client-linking work for photo jobs and damage cases is intentionally deferred until after the Landscapt dev/prod split. - No link to a specific CRM Job, visit, or CMMS Work Order. A Photo Job can only be linked to a Project (Equipt/PO) or a Client — not to an individual scheduled visit or work order. Photos document a job site, not a specific dispatch.
- Deleted storage objects are not cleaned up automatically. Soft-deleting a photo removes the database row but intentionally leaves the file sitting in Supabase Storage; a real cleanup pass is a separate, unbuilt admin task.
- The Draw and Circle annotation tools are fixed-size/simple. Circles are always a 40px radius regardless of drag distance, and there's no rectangle, highlighter, or measurement tool — only Arrow, Circle, Text, and freehand Draw.