Install app
Add to Home Screen
In Safari, tap the Share button, then choose “Add to Home Screen.” JobDoctor opens full-screen, just like a native app.
Got it
{{ njHeader }}
A project belongs to a customer. Its project type sets the checklist, files & pages; the crew you pick is added as project users.
No customers yet. Add a customer first, then create a project for them.
PROJECT TYPE{{ njTypeAster }}
{{ njTypePicker.label }}{{ njTypePicker.caret }}
{{ o.name }}{{ o.sub }}
{{ njTypePicker.emptyText }}
Pick a project type to carry on — it decides the checklists, the crew and how this job bills.
CUSTOMER{{ njCustAster }}+ New customer
{{ c.initial }}
{{ c.name }}
{{ c.sub }}
No matching customers.
Now pick the customer this project is for.
HOW MANY ARRAYS?{{ njPanelsAster }}
{{ a.label }}
{{ a.name }}
PANELS
TILT
ROOF
{{ njTotalLine }}
DOES THIS NEED A PERMIT?{{ njPermitAster }}
Yes{{ njPermitAutoNote }}
{{ o.label }}
Fine to leave — any checklist that needs approval waits as “Pending permit requirements” until someone answers on the Permits tab. Work that can proceed without a permit is unaffected.
RECORD PANEL WATTAGE?{{ njWattsAster }}
{{ w.label }}
PANEL WATTAGE{{ njWattsFieldAster }}
W
{{ njKwMath }}{{ njKwLabel }}
Answer this to carry on — it sets the system size on the project.
WHO'S ON IT
{{ p.initial }}
{{ p.name }}×
Assign
{{ c.initial }}
{{ c.name }}
Everyone's already assigned.
BILL TO
The customer
A partner
Invoices go to the customer on the project.
Pick a partner — invoices have nowhere to go without one.
Invoices & "Send to customer" go to the partner, not the homeowner. Homeowner stays the site contact.
No partners yet — add one in Partners →
HOW IT BILLS
{{ m.label }}
{{ njModeHint }}
CAN WORK BE BILLED BEFORE THE INSPECTION PASSES?
Yes — bill on completion
No — wait for the inspection
{{ njHoldHint }}
{{ njHoldSource }}
{{ njAmountLabel }}
$
{{ njAmountHint }}
{{ njPartnerDrawNote }}
DRAWS
{{ njDrawPctTotal }}
%{{ dw.amount }}×
No draws yet — start with your usual split or add your own.
{{ njDrawPresetLabel }}
+ Add draw
{{ njDrawHint }}
CHECKLIST AMOUNTS
{{ njChecklistTotal }}
{{ ck.name }}$
Each checklist invoices at its own amount as it is completed, and together they are the contract value.
REVIEWER
{{ njBillMissingText }}
Cancel
Create project
New customer
FIRST NAME
LAST NAME
PHONE
EMAIL
STREET ADDRESS{{ ncAddrAsterisk }}Verified location
{{ s.label }}
{{ s.hint }}
CITY
STATE
ZIP
NOTES
{{ cf.labelUpper }}
ADDITIONAL CONTACTS
Remove
+ Add additional contact
Cancel
Add customer
Edit customer
FIRST NAME
LAST NAME
PHONE
EMAIL
STREET ADDRESS{{ ncAddrAsterisk }}Verified location
{{ s.label }}
{{ s.hint }}
CITY
STATE
ZIP
NOTES
ADDITIONAL CONTACTS
Remove
+ Add additional contact
Cancel
Save changes
{{ pcInitial }}
{{ pcName }}
{{ pcKind }}
×
{{ pcPhone }}{{ pcEmail }}
{{ pcAddress }}
{{ pcJobs }}
Projects
{{ pcBilled }}
Billed
{{ pcTerms }}
Terms
CONTACTS
{{ c.name }}{{ c.title }}
Manage in Partners →
{{ mdInitial }}
{{ mdName }}
{{ mdRole }} · {{ mdActive }}
×
Name
Email{{ mdEmail }}
Phone{{ mdPhone }}
Job title{{ mdPosition }}
Crews{{ mdCrews }}
Projects involved{{ mdJobCount }}
Reassign work
Deactivate and reassign
{{ raTitle }}
{{ raSub }}
From
{{ raFromInitial }}
{{ raFromName }}
{{ raFromPick.label }}
{{ rf.initials }}
{{ rf.name }}
{{ rf.sub }}
NOW
Nobody matches that.
To
{{ raToPick.label }}
{{ rt.initials }}
{{ rt.name }}
{{ rt.sub }}
NOW
Nobody matches that.
Nobody is assigned on the projects you picked — no project users, no crew on upcoming work orders, no permit or task owners. Pick anyone above to move whatever they do have, or cancel.
What moves
{{ m.label }} {{ m.count }}
{{ m.hint }}
Which work orders
{{ c.label }}
Completed, partial and cancelled work orders never move — they are the record of who actually did the work.
{{ raSummary }}
They keep their login until the move finishes, then go inactive. Anything left unassigned stays on the project with nobody on it.
{{ raConfirmLabel }}
Cancel
Without a map location
×
{{ u.name }}
{{ u.addr }}
Invite user
They’ll get access when they sign up with this email.
WORKSPACE JOIN CODE
{{ orgJoinCode }}
Testers can sign up in either app and enter this to join instantly.
NAME
POSITION
EMAIL
PHONE
PERMISSIONS
{{ p.label }}
+{{ invSeatPrice }}{{ invSeatNote }}
Cancel
Send invite
{{ termsModalTitle }}
{{ termsModalSub }}
Worth knowing before you agree
JobDoctor is a record-keeping tool, not a guaranteed archive — keep your own copies of anything you can't afford to lose.
The Service is provided as-is, with no uptime guarantee, and our liability is capped.
Seat changes, proration and refund rules are set out in full in the Terms.
Agree & continue
Cancel
Find a date and time
{{ sfSub }}
{{ sfDurLabel }} {{ sfDurHours }}
×
Force the date
{{ sfForceNote }}
WHEN
{{ c.label }}
TIME OF DAY
{{ c.label }}
DAYS THAT WORK
{{ d.label }}
NOT BEFORE
CREW
{{ s.name }}{{ s.why }}
Pick a crew to search their week.
OPEN SLOTS {{ sfSummary }}
{{ sfRunNote }}
{{ r.title }}
{{ r.sub }}
Book
Nothing fits these constraints. Widen the range or shorten the job.
{{ sfBadCount }} {{ sfBadToggleLabel }}
{{ r.title }}
{{ r.sub }}
{{ sfFootNote }} Widen the range
Booking sets the date, window and crew, and moves the status to Scheduled.
Enter manually
Cancel
!
{{ obTitle }}
{{ obSub }}
Booked {{ obTotalLabel }}
{{ obRunNote }}
{{ obLegendBooked }}
{{ obLegendThis }}
{{ obLegendOver }}
{{ obLegendDrive }}
Already on that day
{{ r.title }} {{ r.when }} {{ r.hours }}
Why this is flagged
{{ rs.label }}
Next days with room
{{ sg.label }}
{{ sg.free }}
{{ obConfirmLabel }}
Cancel
{{ moveTitle }}
{{ moveSub }}
WAS
{{ moveWasDate }}
{{ moveWasWindow }}
NOW
{{ moveNowDate }}
NEW ARRIVAL WINDOW
Carried over from the old booking. Change it or keep it.
{{ moveToldTick }}
The customer knows about the new date
Cancel
Move it
Stays Scheduled · no reschedule recorded · the crew get a note
{{ reschedTitle }}
{{ reschedSub }}
REASON
REASON
Cancel
Confirm
Material
×
ATTACH TO CHECKLIST
VENDOR / STORE
Add
Cancel
PURCHASE DATE
CATEGORY
ALLOCATION
Needs classification — pick Direct or Shared.
QTY
COST
$
PO NUMBER · auto
{{ matPo }}
VENDOR RECEIPT #
Bill back to customer
Show as a billable line item on the invoice
RECEIPT
View receipt
+Upload / replace
NOTES
{{ matLoggedBy }}
{{ matDoneLabel }}
Change order
×
ATTACH TO CHECKLIST
Line items
+Add line item
No line items yet
$
{{ it.lineTotal }}
Change order total{{ coModalTotal }}
NOTES
STATUS
HISTORY
{{ h.name }}{{ h.dateLabel }}
Approved change orders flow into the invoice as a billable line item.
{{ coDoneLabel }}
{{ invDNumber }}
{{ invDCustomer }}
×
{{ invDStatusLabel }}{{ invDPaidPct }}Issued {{ invDIssued }}Due {{ invDDue }}
BILLED ITEMS
No line items
{{ li.numLabel }}
{{ li.label }}
{{ li.sub }}
{{ li.amount }}{{ li.kindLabel }}Send✓ Sent✓ PaidMark paid
Total{{ invDAmount }}
⬇ PDF
Send
Mark paid
Mark unpaid
New invoice
CUSTOMER
BILLABLE ITEMS · {{ invSelectedCount }} of {{ invCandidateCount }} selected
{{ li.label }}
{{ li.sub }}
{{ li.heldWhy }}
{{ li.amount }}
Invoice total{{ invAmountLabel }}
{{ invInspNote }}
{{ invHeldNote }}
AMOUNT
ISSUED
DUE
NOTES
Email to customer on send
Off = saved in-system only, not emailed
Cancel
Save draft
Save & send
New jurisdiction
{{ ahjModalNote }}
Name
City
State
{{ s.label }}
No state matches that.
Jurisdiction type
Portal name
Portal URL
Phone
Typical review days
Permit must be picked up in person
Notes
Save jurisdiction
Cancel
Which permits and inspections this jurisdiction requires is set in Customize → Jurisdictions. Without that, a project on it gets one Building permit and no inspections.
Project type
TYPE NAME
SALESFORCE RECORD TYPEhow the import knows a project is this type
Matched on this value, not on the name — so “Reinstall Only” in Salesforce lands on this type even after you rename it here. Leave it blank and imported projects of that record type arrive with no type and wait in Needs setup.
ORDER OF WORKdrag to reorder · tap the number to pair
No checklists yet — build them on the Checklists page.
⚑ PERMIT MUST BE APPROVED FROM HERE ON
{{ st.num }}
{{ st.name }}
{{ st.subLabel }}
{{ st.optLabel }} {{ st.waitLabel }} ⚑ PERMIT LINE Remove
NEEDS INSPECTING BY {{ ir.name }} WITH VISIT AFTER WE APPROVE × nothing + add
{{ o.name }}
Nothing matches that — add it in Customize → Inspection types
Close
What each control means
Optional — not on every job. The pipeline stops waiting for it instead of hanging on a step nobody will do.
Waits for step N — a hard dependency. Off by default: you often book the reinstall before the removal has happened, and an app that refuses that gets worked around.
Tap a number — pairs that step with the one above it, so they run in either order.
⚑ The permit line — {{ ptPermitNote }}
Needs inspecting by — the inspections this checklist owes; every project of this type gets them. With visit = booked the same day as the work, so it is ready as soon as the checklist has a date. After we approve = ready once the office signs the finished checklist off. Either way every permit on the project must be approved first.
DEFAULT FILES
Add file
No default files.
DEFAULT PAGES
Add page
No default pages.
Cancel
Save type
Add project
Pick a project type — its checklist, files & pages attach automatically.
No project types yet. Create one under Project Types first.
PROJECT TYPE
{{ apTypePicker.label }}{{ apTypePicker.caret }}
{{ o.name }}{{ o.sub }}
{{ apTypePicker.emptyText }}
Pick a project type to carry on — it decides the checklists, the crew and how this job bills.
PROJECT TITLE{{ apTitleAsterisk }}
Bill to someone other than the customer
Partner, GC, or property manager
HOW IT BILLS
{{ m.label }}
{{ apModeHint }}
CAN WORK BE BILLED BEFORE THE INSPECTION PASSES?
Yes — bill on completion
No — wait for the inspection
{{ apHoldHint }}
{{ apHoldSource }}
{{ apAmountLabel }}
$
{{ apAmountHint }}
DRAWS
{{ apDrawPctTotal }}
%{{ dw.amount }}×
No draws yet — start with your usual split or add your own.
{{ apDrawPresetLabel }}
+ Add draw
{{ apDrawHint }}
CHECKLIST AMOUNTS
{{ apChecklistTotal }}
{{ ck.name }}$
Each checklist invoices at its own amount as it is completed, and together they are the contract value.
ESTIMATED DURATION
{{ d.label }}
REVIEWER
Cancel
Add project
Why was this rejected?
{{ rjInvoice }} will be voided and a service ticket opened. No work-order status changes — finished work stays finished.
{{ r.label }}
Notes — required
Service type for the ticket
Prefilled from the reason.
Reject & open ticket
Cancel
{{ inspAskTitle }}
{{ inspAskBody }}
Set it once on the jurisdiction and every permit opened against it from now on creates its inspection for you.
Choose the inspections
Later
{{ inspRemoveTitle }}
{{ inspRemoveBody }}
Why
{{ inspRemoveConfirmLabel }}
Keep it
Why is this moving?
Recorded on the inspection's timeline, so a date that slips three times shows why each time.
Cancel
Pick a new time
{{ seqTitle }}
{{ seqBody }}
Book it anyway
{{ seqFirstLabel }}
{{ dropStatusTitle }}
This {{ dropStatusKind }} is one the app reads by name.
{{ dropStatusWhy }}
Records already sitting on it keep it — it just stops being offered, and nothing sets it automatically any more. Add a status with the same name back to restore it.
Keep it
Remove anyway
Lift which hold?
{{ liftWhat }} Clearing one leaves the others in place — the work stays held until the last one goes.
{{ r.label }}
{{ r.note }}
Lift
Cancel
Lift all of it
Why is this on hold?
It shows beside the status so nobody has to ask, and the clock stops while it is held.
WHAT STOPS
{{ holdScopeWoLabel }}
The whole project
REASON — PICK EVERY ONE THAT APPLIES
{{ o.tick }}{{ o.label }}
{{ holdPickedNote }}
WAITING ON
CHECK BACK ON *
The stage clock pauses. Permits and inspections keep counting — the jurisdiction doesn't care that you paused. On the check-back date it comes back with a "hold expired" flag.
Cancel
{{ holdConfirmLabel }}
Reject work order
Sent back to the crew to fix in the same checklist.
REJECTION NOTES
Cancel
Reject
PhotoMarkup saves as rejected
Download
Delete
Draw
Text
COLOR
Undo
Clear
Cancel
Save rejected markup
Turn billing off?
You have {{ boLine }}
They’ll be hidden, not deleted. Export them first if you’re moving to another system.
Invoices, payments and money chips disappear across the app. Approved work goes straight to Complete. Turning billing back on restores everything exactly as it was.
Cancel
Turn billing off
Can’t get this one?
Tell the office why. This does not tick the item off — it still needs the photo, or a manager has to waive it.
WHAT’S IN THE WAY?
Cancel
Send to office
Waive this requirement
Closes {{ wvFieldTitle }} without the photo. Logged with your name and reason, and it shows on the work order permanently.
CREW SAID
{{ wvCantNote }}
REASON — REQUIRED
Cancel
Waive it
Reject this item
Sends this checklist item back to the crew to fix.
NOTE (optional)
Cancel
Reject item
Back
Page
Reference images
{{ refViewerImgs }}
No reference images for this step.
Name this filter
Cancel
Save filter
ESC
No matches.
{{ grp.kind }}
{{ r.label }}{{ r.sub }}
RECENTLY VIEWED
{{ r.label }}{{ r.sub }}
Type to search across your workspace
Crews
New crew
No crews yet — tap New crew, then add members below.
Members · {{ g.count }}
{{ m.initial }}
{{ m.name }}
Done
Report a problem
{{ irJobName }}
WHAT KIND
{{ k.name }}
WHICH VISIT (OPTIONAL)
WHAT'S AFFECTED — PICK ALL THAT APPLY
{{ p.label }}
IN A FEW WORDS
WHAT HAPPENED
PHOTOS
+
{{ irWarnText }}
Cancel
File it
Assign to this project
Nobody matches that
{{ m.initial }}
{{ m.name }}
{{ m.role }}
Done
Set a new password
NEW PASSWORD
{{ pwShowLabel }}
CONFIRM PASSWORD
At least 8 characters, including a number or special character.
{{ pwErr }}
Update password
JD
JobDoctor
Set a new password
Choose a new password for your account.
NEW PASSWORD
{{ pwShowLabel }}
CONFIRM PASSWORD
At least 8 characters, including a number or special character.
{{ pwErr }}
Update password
Connecting to your workspace…
This is taking longer than it should. Your connection may have dropped part-way.
Try again
Sign out
JD
JobDoctor
{{ authTitle }}
{{ authSub }}
EMAIL
PASSWORD
CONFIRM PASSWORD
{{ authErr }}
Forgot password?
{{ authBtnLabel }}
{{ authSwapLabel }} {{ authSwapCta }}
JD
JobDoctor
Two-factor code
Enter the 6-digit code from your authenticator app.
{{ authErr }}
Verify
Cancel
Set up your company
Start a free trial for your company, or join a team you were invited to.
START YOUR COMPANY — FREE TRIAL
Create workspace
14-day trial · No card required
or join an existing team
HAVE AN INVITE CODE?
Join workspace
{{ joinErr }}
Sign out
JD
My Deals
{{ salesWho }}
Refresh
Sign out
Couldn't load your deals.
{{ salesErr }}
Try again
0
No deals yet
Deals you close in the sales tool will appear here, with their progress as the office works them.
{{ d.customer }}
{{ d.address }}
{{ d.stage }}
{{ d.value }}
{{ d.holdText }}
Next up ·{{ d.next }}{{ d.progress }}
← My deals
{{ sdCustomer }}
{{ sdAddress }}
{{ sdValue }}
{{ sdStage }}
{{ sdHoldText }}
Sold {{ sdSold }}{{ sdProgress }}
WORK ON THIS DEAL
Couldn't load the steps. {{ sdErr }}
Try again
No work scheduled yet.
{{ s.dotGlyph }}
{{ s.name }}
{{ s.sub }}
{{ s.hold }}
JD
JD
JobDoctor
Search⌘K
{{ n.label }}{{ n.badge }}
Setup
{{ u.label }}
{{ helpNavLabel }}
{{ devNavLabel }}
{{ userInitial }}
{{ userInitial }}
{{ userEmail }}
{{ roleLabel }}
JD
JOBDOCTOR FIELD
{{ pageTitle }}
{{ userInitial }}
{{ homeGreeting }}
{{ homeToday }}
×
No matches.
{{ g.kind }}
{{ it.label }}{{ it.sub }}
{{ c.label }}{{ c.count }}
Service — came back
{{ homeSvcCount }}
Open Service
{{ s.title }}
{{ s.sub }}
My permitting & Inspections
{{ homePqActionCount }}
Open queue
My permits
{{ homePqPermitCount }}
{{ h.title }}
{{ h.sub }}
My inspections
{{ homePqInspCount }}
{{ h.title }}
{{ h.sub }}
Unassigned — {{ homePqUnassigned }}
Claim in queue
Texts & calls to file💬 {{ homeSmsUnCount }} texts📞 {{ homeCallUnCount }} calls
Texts from a number we couldn’t place on an open project. The router refuses to guess — file them and they land on the project’s conversation.
{{ su.from }}{{ su.preview }}{{ su.when }}
{{ su.fullBody }}
{{ su.pick.label }}{{ su.pick.caret }}
{{ sr2.name }}
{{ sr2.sub }}
{{ su.pick.emptyText }}
Ignore
Calls from a number we don’t recognise. They still happened — often they’re the interesting ones.
{{ cu.who }}
{{ cu.meta }}
{{ cu.pick.label }}{{ cu.pick.caret }}
{{ cr.name }}
Customer email✉ {{ homeWaitingCount }} waiting on a reply{{ homeUnmatchedCount }} unmatched
{{ hw.title }}{{ hw.sub }}
Mail we couldn’t match to a project — somebody emailed a bare address. File it and it lands on the project’s conversation, attachments and all.
{{ um.from }}{{ um.subject }}{{ um.when }}
{{ um.fullBody }}
{{ um.pick.label }}{{ um.pick.caret }}
{{ ur.name }}
{{ ur.sub }}
{{ um.pick.emptyText }}
Ignore
{{ um.preview }} Filing it…
Needs you{{ homeWhoNote }} {{ homeQueueCount }}
{{ o.label }}
{{ p.initials }}
{{ p.name }}
{{ p.sub }}
NOW
Nobody matches that.
Nothing needs you
{{ homeEmptyNote }}
{{ q.tag }} {{ q.title }}
{{ q.detail }}
{{ b.text }}
{{ q.cta }}
OUT OF SLA {{ homeSlaDue }} due today
{{ homeSlaOver }}
Everything inside its target
{{ r.stage }} {{ r.meta }} {{ r.n }}
THIS WEEK
Completed{{ homeDoneWeek }}
Rescheduled{{ homeReschedWeek }}
Photos in{{ homePhotosWeek }}
Permits & Inspections
Everything waiting on a jurisdiction
Dashboard
Where every job is, and how healthy your pipeline looks
Customize
to
Show on this dashboard
{{ t.label }}
{{ opsHealth }}HEALTH
Pipeline health · {{ opsHealthLabel }}
Blends on-time completion, reschedule rate & collection rate for the selected period, state & crew.
{{ opsHeroOnTime }}
On-time
The four numbers
{{ mxPeriodNote }}
{{ t.label }}
{{ t.value }}
{{ t.unit }}
{{ t.sub }}
{{ d.k }}
{{ d.v }}
Avg job cycle
{{ opsCycle }}
{{ opsCycleSub }}
Active work
{{ opsActiveCount }}
{{ opsActiveSub }}
Completed
{{ opsCompletedN }}
{{ opsCompletedSub }}
Your bottleneck is {{ opsBottleneckName }} — jobs wait {{ opsBottleneckDays }} there, {{ opsBottleneckShare }} of all waiting time.
See those jobs
Projects by status
{{ b.name }}
{{ b.count }}
Flow — where the days goWidth = share of total waiting time
{{ f.name }}
{{ f.days }}
{{ f.meta }}
Stage
Jobs
Value
Avg days
Median
Age spread
Oldest
{{ r.name }}
{{ r.count }}
{{ r.value }}
{{ r.avg }}
{{ r.med }}
{{ r.oldest }}
0–3 days
4–7 days
8+ days — stuck
Speed & quality
{{ c.value }}
{{ c.label }}
Budget & profit {{ bgtSub }}
{{ bgtEmptyNote }}
On budget
{{ bgtOnBudget }}{{ bgtOfTotal }}
{{ bgtOnBudgetSub }}
{{ bgtRateLabel }}
Over budget
{{ bgtOverCount }}
{{ bgtOverSub }}
Close to the line
{{ bgtCloseCount }}
{{ bgtCloseSub }}
Headroom
{{ bgtHeadroom }}
{{ bgtHeadroomSub }}
Finished vs still running
An unfinished project hasn’t booked all its cost yet — read the finished number.
{{ r.name }} · {{ r.count }}
{{ r.pct }}
{{ r.note }}
On-budget rate by project type
{{ bgtTypeNote }}
{{ r.name }}
{{ r.val }}
Over budget
Worst first. A running project can still be saved; a finished one is a lesson.
Project
Value
Budget
Actual
Over by
Margin
{{ r.title }}
{{ r.meta }}
{{ r.stateLabel }}
{{ r.value }}
{{ r.budget }}
{{ r.actual }}
{{ r.over }}
{{ r.margin }}
No project value set
{{ bgtNoValNote }}
{{ r.title }}
{{ r.meta }}
{{ r.status }}
{{ r.cost }}
{{ bgtFootnote }}
Collected
{{ opsCollected }}
{{ opsCollectedSub }}
Open A/R
{{ opsOpenAR }}
{{ opsOpenARSub }}
In the pipeline
{{ opsPipelineValue }}
{{ opsPipelineSub }}
Value by stageUnbilled work sitting in the pipeline
{{ r.name }}
{{ r.value }}
{{ r.meta }}
Invoiced → collected
Invoiced
{{ opsInvoiced }}
Collected
{{ opsCollectedBig }}
Outstanding
{{ opsOutstanding }}
{{ opsDaysToPay }}
Avg days to get paid
{{ opsCollectRate }}
Collection rate
{{ opsAvgJobValue }}
Avg job value
{{ opsPastDue }}
Invoices past 30 days
A/R by age
{{ b.label }}
{{ b.amount }}
Chase these first
{{ c.who }}
{{ c.meta }}
{{ c.amount }}
Billing
{{ c.value }}
{{ c.label }}
Jobs completedLast 8 weeks
{{ t.count }}
{{ t.label }}
Decisions {{ pqTabDecCount }}
Unassigned {{ pqTabUnCount }}
Permits {{ pqTabPermitsCount }}
Inspections {{ pqTabInspCount }}
Dash
Timelines
Mine
Everyone
Refresh
{{ sc.label }}
{{ pqDashWhoPick.label }}
{{ dw.initials }}
{{ dw.name }}
Nobody matches that.
{{ pqDashStatePick.label }}{{ pqDashStatePick.caret }}
{{ ds.name }}
{{ ds.sub }}
{{ pqDashStatePick.emptyText }}
{{ pqDashAhjPick.label }}{{ pqDashAhjPick.caret }}
{{ da.name }}
{{ da.sub }}
{{ pqDashAhjPick.emptyText }}
{{ pqDashCoordPick.label }}
{{ dc.initials }}
{{ dc.name }}
{{ dc.sub }}
Nobody matches that.
Reset
DATES
{{ dr.label }}
{{ pqDashScopeNote }}
{{ t.label }} {{ t.value }} {{ t.sub }}
Backlog by status {{ pqDashBacklogTotal }}
Nothing open in this slice.
{{ bk.label }}
{{ bk.count }}
Results by week {{ pqDashPassRate }}
{{ wk.label }}
Passed Failed
Slowest jurisdictions
No request-to-date pairs stamped in this range yet — this fills in as inspections are requested and dated inside the app.
{{ aj.name }}
{{ aj.sub }}
{{ aj.days }}
Request to the date they gave. Sets the lead time to promise a customer.
{{ pqDashListTitle }} {{ pqDashListCount }}
Export CSV
Nothing sitting in this one.
{{ dl.title }}
{{ dl.type }}
{{ dl.why }}
{{ dl.age }} {{ dl.owner }}
{{ pqDashListMore }} — Export CSV gives you all of them.
Ready → scheduled → passed median days at each hop · {{ pqTlSample }} {{ pqTlTotal }}
No inspection reached a result inside this range, so there is nothing to average. Widen the dates or clear the filters.
{{ hp.days }}
{{ hp.label }}
{{ hp.sub }}
{{ hp.n }}
{{ pqTlOursNote }}
What a fail costs {{ pqTlCount }}
GROUP BY
{{ tg.label }}
Export CSV
No inspection failed inside this range.
{{ pqTlHead }} FAILS FAIL → RE-BOOKED FAIL → PASSED STILL OPEN
{{ fr.name }} {{ fr.n }} {{ fr.rb }} {{ fr.ps }} {{ fr.openN }}
Read it as two problems A label or a paperwork miss gets re-booked in a day or two. Anything that needs a crew back on the roof sits far longer before anyone re-books — that gap, not the inspector, is where the weeks go.
First-time pass rate
{{ pqTlFirstRate }} {{ pqTlFirstSub }}
Counted from the fails stamped before each pass — no new data needed, and it is the one number that says whether the crews or the paperwork are the problem.
Every number here is read off status stamps, and stamping only exists from the day the app started doing it — so this fills in from now, not from history. Imported inspections arrive with no stage timestamps. Treat a hop measured on a handful of records as a hint, not a fact: each hop says how many it measured.
Does this project need a permit?
Nothing downstream can be planned until this is answered — every gated checklist waits on it. Answer here and the project moves on. A Yes is only half the answer: the project stays here, marked Needs AHJ selected, until a jurisdiction is picked — that is what turns it into a real permit.
{{ pqDecCount }}
{{ pqDecWaitLabel }}
Every project has an answer. Nothing is waiting on a permit decision.
{{ d.title }}
{{ d.sub }}
{{ d.age }}
Yes
No
NEEDS AHJ SELECTED
Which jurisdiction issues it?
{{ d.needsAhjNote }}
{{ d.ahjPick.label }}
{{ aj.name }}
{{ aj.sub }}
No jurisdiction matches that — add it under Customize.
← Back
Permit needed — who chases it?
Ownership is per project. The person you pick here owns every permit on it, and the inspection owner owns every inspection — change one later and all of them move with it.
Permit coordinator
{{ d.pcPick.label }}
{{ pc.initials }}
{{ pc.name }}
{{ pc.sub }}
NOW
Nobody matches that.
Inspection coordinator
{{ d.icPick.label }}
{{ ic.initials }}
{{ ic.name }}
{{ ic.sub }}
NOW
Nobody matches that.
← Back
{{ d.assignNote }}
{{ d.doneLabel }}
Answered yes — no permit set up yet {{ pqSetupCount }}
A jurisdiction is set but no permit rows exist yet — until they do, the work still waits. Change the jurisdiction or the owners here; open the project to add the permits. One owner takes every permit on a project, another every inspection.
{{ u.title }}
{{ u.sub }}
{{ u.age }}
Set it up →
Jurisdiction
{{ u.ahjPick.label }}
{{ aj.name }}
{{ aj.sub }}
NOW
No jurisdiction matches that — add it under Customize.
Permit coordinator
{{ u.pcPick.label }}
{{ pc.initials }}
{{ pc.name }}
{{ pc.sub }}
NOW
Nobody matches that.
Inspection coordinator
{{ u.icPick.label }}
{{ ic.initials }}
{{ ic.name }}
{{ ic.sub }}
NOW
Nobody matches that.
Nobody owns these yet
Permits and inspections with no coordinator, oldest first. Picking someone here makes them the coordinator for that whole project — every open item on it moves, and ones added later start with them.
{{ pqUnCount }}
TO HAND OUT
Everything has a coordinator. Nothing to hand out.
{{ u.kindLabel }}
{{ u.title }}
{{ u.sub }}
{{ u.where }}
{{ u.age }}
{{ u.pick.label }}
{{ up.initials }}
{{ up.name }}
{{ up.sub }}
NOW
Nobody matches that.
{{ s.label }}
{{ s.value }}
{{ s.sub }}
{{ pqEmptyNote }}
{{ r.gTitle }}
{{ r.gWhere }}
{{ r.gChecklist }}
{{ r.gCount }}
{{ r.title }} {{ r.job }} {{ r.clLabel }} {{ r.pushLabel }}
{{ r.meta }}
{{ r.statusLabel }}
{{ r.ageLabel }}
{{ r.clock }}
Request it
{{ r.assigneeLabel }}
ASSIGN TO
{{ pp.initials }}
{{ pp.name }}
{{ pp.sub }}
NOW
{{ r.askReadyText }}
Ready — request it
{{ r.askReadyNoLabel }}
Approved
Rejected
It arrived
Open project
Coordinator
{{ r.coordPick.label }}
{{ cp.initials }}
{{ cp.name }}
{{ cp.sub }}
NOW
Nobody matches that.
Follow up on
What the reviewer asked for (required to log a rejection)
{{ r.blockLabel }} — reason, owner and a date are all required
{{ pqOwnerPick.label }}
{{ bo.initials }}
{{ bo.name }}
{{ bo.sub }}
NOW
Nobody matches that.
{{ r.blockLabel }}
Blocking keeps it in the queue and the clock keeps running. Each push is recorded and shows on the card.
Forecast
Waiting on the work — each says what has to happen first, and none of it ages. A green button means nothing is left to wait on.
{{ f.title }} {{ f.job }}
{{ f.meta }}
{{ f.waitLabel }}
Crew stats · this period
{{ cc.name }}
Completed{{ cc.completedCount }}
Reschedules{{ cc.reschedCount }}
Add crews under Setup → Manage Crews to see per-crew stats here.
Panels handled — every period, every work type
{{ mxPanelFootnote }}
Work type
{{ h.name }}
{{ r.name }}
{{ c.v }}
No approved visits yet in these periods. Panels appear here as soon as a panel-crediting checklist is approved.
Panels closed per week
Last 12 weeks, Monday-first.
{{ w.v }}
{{ w.label }}
By crew · month to date
{{ c.name }}
{{ c.sub }}
{{ c.v }} · {{ c.pct }}
{{ mxMissing }} approved visits have no panel count
They add zero, so every total above is at least this short.
{{ m.name }}
{{ m.what }}
{{ t.label }}
{{ t.value }}
{{ t.sub }}
Why they moved · month to date
{{ r.name }}
{{ r.v }} · {{ r.pct }}
Which crew
{{ c.name }}
{{ c.v }}
Reschedule rate per week
{{ w.v }}
{{ w.label }}
Which day it happens on
{{ d.v }}
{{ d.label }}
{{ t.label }}
{{ t.value }}
{{ t.sub }}
Rate per week
{{ w.v }}
{{ w.label }}
Cause · ticket type
{{ r.name }}
{{ r.v }} · {{ r.pct }}
Service visits by crew
{{ c.name }}
{{ c.v }}
{{ d.name }}
{{ d.desc }}
Creation to close-out — every hop, timed
{{ mxSlaNote }}
Hop
Median
p90
Worst
Target (d)
Hit
Waiting
Oldest
{{ r.name }}
{{ r.sampleNote }}
{{ r.med }}
{{ r.p90 }}
{{ r.max }}
{{ r.pctIn }}
{{ r.wip }}
{{ r.oldest }}
Open work by age
{{ mxOpenJobs }} open projects, aged from creation.
{{ a.name }}
{{ a.v }}
The two gates
{{ g.label }}
{{ g.value }}
{{ g.sub }}
{{ t.label }}
{{ t.value }}
{{ t.sub }}
Assumptions
Observed panels per crew-day: {{ mxCapObs }} — {{ mxCapObsNote }}. Leave the box empty to use it.
Panels per crew-day
Field days per week
Target backlog (weeks)
Crews running
{{ mxCapCrewsNote }}
{{ mxCapPerCrewWeek }}
How many people we need
{{ a.v }}
{{ a.name }}
Booked ahead — next 8 weeks
{{ mxCapCeiling }}
{{ w.pct }}
{{ w.label }}
!Our Terms of Service have been updated. Please review and accept them to keep using JobDoctor.Review & accept
{{ billTrialBanner }}
View plans
{{ greeting }}
{{ orgName }} workspace
{{ s.value }}
{{ s.label }}
New customer
New project
New checklist
Project type
Today & upcoming
{{ j.mon }}{{ j.day }}
{{ j.title }}
{{ j.address }}
{{ j.lateNote }}
{{ j.statusLabel }}
Nothing scheduled yet — create a job and set its date.
My schedule
Your assigned appointments
{{ d.dow }}
{{ d.dnum }}
{{ voSelLabel }}
Nothing scheduled
Pick another day above.
{{ r.when }}
{{ r.custName }}
{{ r.address }}
Schedule
Calendar by crew · dot = status
CalendarNeeds scheduling {{ schedQueueCount }}Scheduling Rules
Capacity
Optimizer
The queue
Analytics
PERSON
{{ anWhoPick.label }}
{{ p.initials }}
{{ p.name }}
{{ p.sub }}
NOW
Nobody matches that.
{{ anWhoLabel }}
{{ anD1Pct }}
{{ anD1Cap }}
{{ anD1Sub }}
{{ anD2Pct }}
{{ anD2Cap }}
{{ anD2Sub }}
{{ anD3Pct }}
{{ anD3Cap }}
{{ anD3Sub }}
{{ anQueue }}
waiting to be booked
{{ anScopeQueue }}
{{ anMed }}
median wait
red past 14 days
{{ anOldest }}
the oldest one
{{ anOldestSub }}
{{ anBlocked }}
blocked
waiting on a permit answer or a hold
{{ anTouches }}
calls logged
{{ anScopeCalls }}
{{ anRecent }}
in the last 7 days
{{ anScopeRecent }}
{{ anBooked }}
ended in a booking
the calls that did their job
{{ anNever }}
never called
queued and nobody has tried
How the calls went
Every attempt on work still waiting for a date.
Nobody has logged a call yet.
{{ o.label }}{{ o.n }}
Who is on the phone
Attempts logged, by person.
No attempts logged yet.
{{ p.name }}{{ p.n }}
Gone quiet
{{ anStale }}
jobs with no contact in a week — the ones that quietly turn into 60-day waits.
Scheduling rules
What counts as a full day, and when to warn you. Crew beats season, season beats the default.
The standard working day
STARTS
CUTOFF
MAX HOURS / DAY
MAX JOBS / DAY
GAP BETWEEN JOBS (MIN)
INSPECTION VISIT (HRS)
PAST DUE AFTER (DAYS)
LUNCH & SLACK / DAY (MIN)
PANELS / CREW / DAY
Count drive time between stops
atmph \u2014 used only until we can route the day for real, then the real distance takes over. A project with a panel count and no hand-set duration is estimated from the panels-per-day rate.
Works on
{{ d.label }}
When someone books past a limit
{{ e.label }}
{{ e.desc }}
Seasons
{{ srActiveSeasonLabel }}
{{ sn.badge }}
No seasons — every day uses the default above. Add one if winter and summer days differ.
+ Add a season
Crew overrides
Leave a box empty to follow the default or season.
HOURS/DAY DAY STARTS JOBS/DAY MAX MILES WORKS ON
{{ c.name }}
{{ c.seasonNote }}
{{ d.label }}
No crews yet — add one under Setup → Crews.
{{ backToLabel }}
Mine · {{ qMineCount }}
Everyone · {{ qAllCount }}
Oldest first
{{ qScopeLabel }}
{{ qReadyLabel }}
{{ qBlockedLabel }}
Nothing waiting here.
{{ r.title }}
{{ r.where }}
{{ r.age }}YOURS
{{ r.callChip }}{{ r.bestLine }}
Open
{{ r.callChip }}{{ r.callLine }}
Log a call
HOW DID IT GO
Log every attempt, answered or not. Booking a date logs itself.
{{ o.label }}
Save the call
Cancel
{{ r.blockedWhy }}
Answer it →
OFFER THEM ONE OF THESE{{ r.durChip }}
{{ r.durAskLine }}{{ d.label }}
{{ o.day }}
{{ o.window }}
{{ o.sub }}
{{ r.bookLabel }}
Pick another day
{{ r.noSlotLine }}
{{ cwTitle }}
Real free hours, drive time already taken out.
{{ d.label }}{{ d.offLabel }}
{{ d.free }}
{{ fillTitle }} — a call list, not an auto-booker
{{ fillSub }}
{{ p.n }}
{{ p.name }} · {{ p.miles }}
{{ p.call }}
{{ p.window }}
Start calling
This week, by crew
Hours still free, drive time already taken out.
{{ col.dow }} {{ col.dnum }}
{{ c.label }}
{{ homeDayLabel }} {{ homeDaySub }}
{{ c.name }} {{ c.hours }}
{{ c.sub }}
This week {{ homeWeekLabel }}
{{ d.label }}
{{ d.hours }}
{{ woTypeLabel }}
Select all
Clear all
{{ o.name }}{{ o.count }}
SAVED
{{ f.name }}
Save filter
{{ crewBtnLabel }}
Select all
Clear all
No crews match “{{ crewMenuQ }}”
{{ c.name }}{{ c.count }}
{{ c.name }}×
{{ schedHeaderLabel }}
{{ m.label }}
{{ m.label }}
{{ ss.label }} {{ ss.value }} {{ ss.sub }}
{{ w }}
{{ d.day }}
{{ c.title }}{{ c.resched }}
{{ d.more }}
{{ d.label }}{{ d.sub }}
WEEK LOAD
{{ row.name }}
{{ w.shortTitle }}{{ w.statusLabel }}{{ w.runLabel }}{{ w.finishBadge }}{{ w.svcLabel }}
{{ w.typeLine }}
{{ w.metaLine }}
{{ w.whyLabel }}
OPEN
OFF
{{ row.load }}{{ row.panelLabel }}
Nothing booked for any crew this week.
{{ crewWeekIdleLabel }}{{ crewWeekToggleLabel }}
{{ col.dow }} {{ col.dnum }}
{{ cg.crewName }}{{ cg.count }}
{{ w.title }}
{{ w.sub }}
{{ w.runLabel }}{{ w.runNote }}
{{ w.finishBadge }}{{ w.whyLabel }}
{{ w.wxLabel }}
{{ w.wxWhy }}
{{ w.statusLabel }}{{ w.svcLabel }}{{ w.timeLabel }}{{ w.resched }}
{{ w.crewName }}
{{ c.initial }}
No work orders
{{ row.name }}{{ row.count }}
{{ j.title }}
{{ j.address }}
{{ j.finishBadge }}{{ j.reschedBadge }}{{ j.svcLabel }}
No work orders for this crew today.
Projects
{{ jobsCountLabel }}
Status: {{ jobsFilterLabel }} ✕
Search: {{ jobsSearchLabel }} ✕
Pipeline
Board
List
New
{{ boardStatePicker.label }}
{{ o.tick }}
{{ o.name }}
{{ boardStatePicker.emptyText }}
{{ boardStatePicker.clearLabel }}
{{ boardCkPicker.label }}
{{ o.tick }}
{{ o.name }}
{{ boardCkPicker.emptyText }}
{{ boardCkPicker.clearLabel }}
{{ bfLabel }}
STAGES ON THE BOARD Select all Deselect all
Nothing on the board — tick the stages you want.
{{ st.label }} {{ st.count }}
SAVED VIEWS
{{ v.name }} {{ v.sub }} ×
{{ bfSaveLabel }}
A view saves the whole bar — states, checklists, dates and stages.
Clear all filters
to
{{ col.label }}{{ col.lateLabel }}{{ col.count }}
{{ col.targetLabel }}
{{ j.title }}
{{ j.address }}
{{ j.sla.label }}
{{ j.pastDueLabel }}
{{ j.issueLabel }}
{{ j.ckName }}{{ j.ckStatus }}
{{ j.moneyLabel }}{{ j.moneyAmount }}
{{ j.dateLabel }}{{ j.crewName }}
{{ col.moreLabel }}
Nothing here
{{ plCountLine }}
{{ plSkipped }}
{{ plMineLabel }}
{{ plStagePicker.label }}
{{ o.tick }}
{{ o.name }}
{{ plStagePicker.emptyText }}
{{ plStagePicker.clearLabel }}
{{ plAhjPicker.label }}
{{ o.tick }}
{{ o.name }}
{{ plAhjPicker.emptyText }}
{{ plAhjPicker.clearLabel }}
{{ plWaitPicker.label }}
{{ o.tick }}
{{ o.name }}
{{ plWaitPicker.emptyText }}
{{ plWaitPicker.clearLabel }}
{{ plFlagPicker.label }}
{{ o.tick }}
{{ o.name }}
{{ plFlagPicker.emptyText }}
{{ plFlagPicker.clearLabel }}
{{ plSortLabel }}
Clear filters
{{ c.label }}
{{ c.count }}
{{ c.targetLabel }}
{{ j.title }}
{{ j.days }}
{{ j.address }}
{{ j.nextText }}
{{ j.nextSub }}
{{ j.alsoLabel }}
{{ ch.label }}
{{ c.moreLabel }}
{{ plEmptyLine }}
{{ jobSelLabel }}
Reassign
Freeze sync
Unfreeze
Clear
{{ pgCountNote }}
ASSIGNED
{{ c.label }}
{{ pgPersonLabel }}
{{ p.initials }}
{{ p.name }}
Nobody matches that
PROJECTCHECKLISTSTAGECREWPEOPLEWHEN
No projects match your filters.
{{ r.title }}
💬📞FROZEN
{{ r.address }}
{{ r.ckName }}
{{ r.stageName }}
{{ r.peopleLabel }}
ON THIS PROJECT
Nobody is on this project yet.
{{ p.initials }}
{{ p.name }} Reassign
{{ r.dateLabel }}
{{ pgShowingLabel }}
{{ p.label }}
PER PAGE
{{ s.label }}
{{ backFromJobLabel }}
{{ jobAddress }}
{{ jobStatusLabel }}{{ stageDiag }}{{ jobHoldReason }} · edit{{ jobHoldReason }}lift{{ jobAutoHoldLabel }}Put on holdMark NTPSaving…{{ ntpLabel }}M{{ msIdx }}{{ msText }}Open change order{{ jobStatusAge }} in current status · {{ jobSinceCreation }} since creation{{ jobSlaLabel }}· Created by {{ jobCreatedBy }}
Mark complete →
{{ nextHint }}
Rescheduled — {{ jobRescheduleReason }}
{{ jobIssueHead }}
{{ i.tag }}{{ i.title }}
{{ i.sub }}
PROJECT TYPE
{{ jtName }}
{{ jtSfLabel }}
{{ o.name }} {{ o.sf }} ✓ now
Nothing matches that
Close
{{ c.name }}{{ c.frac }} · {{ c.pct }}
Project
Permits {{ permitsTabCount2 }}
Work Orders {{ checklistTabCount }}
Inspections {{ inspectionsTabCount }}
Service {{ svcJobCount }}
Issues {{ issuesTabCount }}
Costing {{ costingTabCount }}
Notes {{ notesTabCount }}
Files {{ filesTabCount }}
Photos {{ photoTabCount }}
Pages {{ pagesTabCount }}
{{ galleryDownloadZipLabel }}
{{ galleryDownloadPdfLabel }}
{{ projectSelAllLabel }}
Clear selection
{{ g.label }}
{{ p.initial }}
{{ p.cap }}
No photos yet — open the checklist and capture some.
ORDER OF WORKthis project only — drag to reorder · tap the number to pair
⚑ PERMIT MUST BE APPROVED FROM HERE ON
{{ ck.stepNum }}
{{ ck.stepOptLabel }} {{ ck.stepWaitLabel }} ⚑ PERMIT LINE
{{ ck.name }}
{{ ck.onHoldReason }}
{{ ck.progressCount }}
{{ ck.woStatusLabel }}
{{ ck.woWhen }}
{{ ck.woDoneLabel }}↻ {{ ck.woReschedCount }}
{{ cm.initial }}
{{ ck.photoCountLabel }} · {{ ck.toggleLabel }}
ZIP
PDF
{{ g.label }}
No checklist attached to this project.
+ Add a checklist to this project
{{ t.name }}
{{ t.sub }}
{{ addCkEmptyText }}
Permit required?
{{ permitReqHint }}
Yes
No
Nobody has answered this yet.
Any checklist that needs permit approval is on hold as “Pending permit requirements” until you do.
No permit required on this project.
Switch this to Yes if that changes — nothing already entered is deleted.
Authority having jurisdiction
Picking the AHJ builds the permit and inspection checklists from its requirements.
{{ ahjName }}
{{ ahjSub }}
Edit jurisdiction
Change
{{ ahjPicker.label }}{{ ahjPicker.caret }}
{{ o.name }}{{ o.sub }}
{{ ahjPicker.emptyText }}
{{ ahjEmptyNote }}
+ Add a jurisdiction
Permits
{{ permitSummary }}
Coordinator
{{ permitOwnerPick.label }}
{{ pp.initials }}
{{ pp.name }}
{{ pp.sub }}
NOW
Nobody matches that.
Rebuild from jurisdiction
{{ addPermitLabel }}
No permits yet — pick the jurisdiction above and its required permits are created for you.
{{ p.name }} {{ p.statusLabel }} {{ p.redesignLabel }}
{{ p.sub }}
{{ p.toggleLabel }}
Permit type
Permit number
Status
Fee
Follow up on
{{ p.followHint }}
{{ p.approveBlock }}
Submission checklist {{ p.itemsLabel }}
{{ i.name }}
{{ i.hint }}
yours
Nothing required yet.
+ Add your own check
Timeline
{{ tl.label }} — {{ tl.reason }} {{ tl.when }} +{{ tl.gap }}
Submission history
{{ c.title }}
{{ c.submittedLabel }} {{ c.decidedLabel }} {{ c.elapsedLabel }}
{{ c.note }}
Not submitted yet.
Every rejection is kept as its own dated cycle, so redesign counts stay reportable.
Record the decision
Approved
Rejected — redesign #{{ p.nextRedesign }}
Submit to AHJ
Resubmit as Rev {{ p.nextRevision }}
Saving…
Delete
Redesign tracking
{{ c.value }}
{{ c.label }}
Service
Rework on this project — from a rejected bill, a failed inspection, or raised here. Schedules on the same calendar and crews as work orders.
+ New ticket
Nothing has come back on this project.
{{ t.typeLabel }} {{ t.number }} {{ t.sourceLabel }} {{ t.heldLabel }}
{{ t.reasonLabel }}
{{ t.notes }}
{{ t.statusLabel }}
{{ t.whenLabel }}
{{ t.whenSub }}
{{ t.toggleLabel }}
What has to happen
{{ it.name }}
Remove
{{ it.before.label }}
{{ it.before.reqLabel }}
{{ it.after.label }}
{{ it.after.reqLabel }}
{{ it.missingLabel }}
Add
Notes
Service ticket details
Service type
Hours
Schedule
When
{{ t.whenLabel }}
Not bookedSchedule…
Arrival window
{{ t.winDetail }}
Crew
Crew
{{ t.crewDetail }}
Owner
{{ t.ownerPick.label }}
{{ pp.initials }}
{{ pp.name }}NOW
Nobody matches that
Does this one bill?
No charge
Billable
$
Asked every time, never assumed.
{{ t.scheduleLabel }}
Unschedule
Crew finished
{{ t.closeLabel }}
Re-open
Delete ticket
Complete is the crew’s word; Closed is the office’s — and only Closed releases the invoice and lets the project reach Complete.
Inspections
{{ inspSummary }}
Coordinator
{{ inspOwnerPick.label }}
{{ pp.initials }}
{{ pp.name }}
{{ pp.sub }}
NOW
Nobody matches that.
+ Add inspection
No inspections yet — the jurisdiction's required inspections are created when you pick it on the Permits tab.
Waiting on the jurisdiction
{{ inspGapLine }} Only a jurisdiction can open an inspection — add these to its permit types if they are really owed.
{{ i.name }} {{ i.statusLabel }} attempt {{ i.attemptLabel }} {{ i.originLabel }}
{{ i.sub }}
Cancelled — {{ i.voidReason }}
{{ i.removeLabel }}
{{ i.toggleLabel }}
Inspection type
Status
{{ i.statusHint }}
Requested on
AHJ date
{{ i.ahjDateLabel }}
{{ i.bookedHint }}
Not bookedSchedule…
Set through Schedule — it books the crew and the calendar together.
Arrival window
{{ i.arrLabel }}
Only half a window \u2014 tap to fix
{{ i.bookedHint }}
Crew
{{ i.crewLabel }}
Inspector name
{{ i.arrWindowLabel }}
{{ i.dayLoadLabel }}
{{ i.calendarNote }}
{{ i.scheduleLabel }}
Unschedule
This has a date but no arrival window, so the crew day shows no hours. Set both times.
Reported passed — not confirmed yet
{{ i.reportedLine }}
{{ i.reportedNotes }}
Confirm the pass
Send it back
Until this is confirmed the permit stays open and the work stays gated.
Failed — {{ i.failReason }}
{{ i.failNotes }}
On-site checklist {{ i.itemsLabel }}
{{ it.name }}
{{ it.hint }}
yours
Nothing on the list yet.
+ Add your own check
Timeline
{{ tl.label }} — {{ tl.reason }} {{ tl.when }} +{{ tl.gap }}
Attempts
{{ a.title }} {{ a.when }}
{{ a.note }}
No attempt recorded yet.
Record attempt {{ i.nextAttempt }}
{{ i.removeLabel }} this inspection
Passed
Failed
Pick the outcome and the right fields appear.
{{ i.recDateLabel }}
Fail reason
Re-inspection fee
Reschedule reason
Notes
{{ i.recAttachLabel }}
+ Attach photo
{{ f.name }} ×
Nothing attached yet.
{{ i.submitLabel }}
Cancel
A fee entered here also posts to Costing as a permit / fees line.
{{ i.fixNote }}
Still work orders underneath
Own tab, own fields, own lifecycle — but an inspection is still scheduled through the work order engine, so it takes a real crew day on the calendar.
! Report a problem on this project
{{ i.tag }} {{ i.title }} {{ i.faultLabel }}
{{ i.sub }}
Work this issue →
Nothing reported on this project.
Upload a file
{{ f.name }}
{{ f.dateLabel }}
NEEDED · UPLOAD{{ f.typeLabel }}
No files yet — upload one, or they’ll appear from the project type.
{{ c.label }}
Post
{{ notesFeedEmptyText }}
{{ n.init }}
{{ n.area }} {{ n.attached }} {{ n.who }} Reply Resolve Delete
{{ n.body }}
{{ n.pill }}
{{ r.body }}
{{ r.who }}
Send
Budget & profit
The project value is the budget. Every cost logged below eats into it.
Target margin {{ budTargetPct }}
Project value
{{ budValue }}
{{ budCo }}
Cost budget
{{ budBudgetCost }}
value less the {{ budTargetPct }} target
Actual cost
{{ budActual }}
{{ budRemaining }} {{ budRemainingLabel }}
Profit if it ends here
{{ budProfit }}
{{ budMargin }} — {{ budMarginNote }}
{{ budPctLabel }} budget {{ budBudgetCost }}
!No project value yet — the PM sets it in Billing on the Project tab. Until then there's no budget to measure spend against.
Direct cost per panel
{{ budPerPanel }}
{{ budDirectTotal }} direct ÷ {{ budPanels }} panels · permits & shared costs excluded
Budget per panel
{{ budBudgetPerPanel }}
Value per panel
{{ budValuePerPanel }}
No panel count on this project — it's left out of every cost-per-panel figure.
Panels
Where the money went
No costs logged yet — materials, labour and permit fees show up here as they're entered.
{{ c.name }}
{{ c.share }} {{ c.amount }} {{ c.perPanel }}
Cost lines logged
{{ materialsTotal }}
Change orders
{{ changeOrdersTotal }}
Project value
{{ budValue }}
Materials & receipts
Edit any line here — changes save as you type.
Detailed entry
Add purchase
Permitting
{{ permCostLine }}
Add permit cost
No costs logged yet — click to add the first purchase.
Add
Cancel
{{ m.total }}
Add
Cancel
$
Add category line
{{ m.lineCount }}
Bill back
Receipt
Attach
More…
{{ matLineCount }}{{ materialsTotal }}
Add another purchase
Change orders
Add change order
No change orders.
{{ c.description }}
{{ c.checklistName }}{{ c.dateLabel }}
{{ c.statusLabel }}{{ c.amount }}
New page
{{ p.title }}
{{ p.snippet }}
No pages yet — create one, or they’ll appear from the project type.
Project users
Assign
PROJECT MANAGER
{{ pmPick.label }}
{{ pp.initials }}
{{ pp.name }}
{{ pp.sub }}
NOW
Nobody matches that
{{ pmSfName }}
{{ m.initial }}
{{ m.name }}
{{ m.role }}
No one assigned yet.
Customer
{{ jobCustomerName }}
{{ jobPhone }}
{{ jobEmail }}
No contact details yet.
SYSTEM
{{ sysEditLabel }}
{{ sysTotalLine }}
{{ a.name }}{{ a.detail }}
No arrays recorded — press Edit to add them.
{{ c.label }}
{{ a.name }}
PANELS
TILT
ROOF
PANEL WATTAGE
{{ sysDraftTotal }}
Cancel
Save
ADDITIONAL CONTACTS
{{ ct.name }}
{{ ct.role }}
{{ ct.phone }}{{ ct.email }}
Checklist status
{{ jobCreatedMeta }}
{{ s.name }}{{ s.status }}
{{ s.detail }}
No checklists on this project yet.
RELATED
{{ r.glyph }}
{{ r.label }}{{ r.count }}
Salesforce sync
Frozen — Salesforce changes are not being applied
{{ sfFrozenSince }}. This project will drift from Salesforce until you unfreeze it.
Unfreeze — resume syncing
{{ sfLastSynced }}. Freezing keeps this project exactly as verified: the sync stops updating it and an undo leaves it and its work orders alone.
Freeze — keep on undo, stop updates
BILLING MODE
BILL TO
BILLING TERMS
CONTRACT TOTAL
$
RETAINAGE %
%
Held: {{ retainageHeld }}
Billable extras (materials + approved COs): {{ billableExtrasLabel }}Contract + extras: {{ trueContractLabel }}
Checklist amounts
Each checklist bills its own amount when sent to billing
{{ sfValueSeededNote }}
{{ wa.name }}{{ wa.stateLabel }}{{ wa.invNo }}${{ wa.amount }}
{{ wa.holdWhy }}
{{ wa.needsAmountNote }}
{{ a.label }}
Draw schedule
Billed {{ drawsBilled }} of {{ drawsScheduled }} scheduled
⚠ Draws exceed 100% of contract ({{ drawTotalPctLabel }}) — reduce before sending
{{ drawPresetLabel }}
+Draw
No draws yet — add {{ drawPresetLabel }} or a custom draw
{{ d.amount }}✓ SentSend to billing →
Billable extras
Materials & approved change orders — send each to billing on top of the draws
{{ extraSelCount }} selectedSend selected as one invoice →
{{ x.name }}
{{ x.sub }}
{{ x.amount }}✓ SentSend to billing →
CONTRACT AMOUNT
$
Final at end — billed as one invoice once every checklist is complete.
Description
Links
+ Add link
{{ l.label }}
×
{{ f.label }}{{ f.value }}
Save
Cancel
+ Link
Sold by sales{{ sdSubmitted }}
REP
{{ sdRep }}
{{ sdRepEmail }}
DEAL CODE
{{ sdDealCode }}Copy
DISCOUNT APPROVED · {{ sdDiscountPct }}
{{ sdDiscountWho }}
{{ sdDiscountWhen }}
{{ g.title }}
{{ r.label }}{{ r.value }}
{{ sec.name }}
{{ cf.labelUpper }}
Add field
Add section
Tasks{{ tasksDoneLabel }}
{{ t.title }}
Conversation
Refresh
{{ emailCheckLabel }}
{{ cfc.label }}
{{ emailCheckNote }}
Synced with Slack — messages here and in {{ slackChannelName }} stay in step.Open in Slack This project has no Slack channel yet.
Create channel
{{ m.initial }}
{{ m.body }}
{{ a.name }}{{ a.sizeLabel }}
{{ m.who }} · {{ m.time }} · Reply
📧 {{ m.who }}{{ m.time }}{{ m.statusLabel }}
Matched to this project by sender address — check it belongs here.
{{ m.movePick.label }}{{ m.movePick.caret }}
{{ mr.name }}
{{ mr.sub }}
{{ m.movePick.emptyText }}
{{ m.toLine }}
{{ m.ccLine }}
{{ m.subject }}
{{ m.body }}
› {{ m.quotedLabel }}
{{ m.quotedNode }}
{{ ea.name }}{{ ea.sizeLabel }}
{{ m.errText }}
Reply
Reply all
Retry
{{ m.retryNote }}
{{ m.body }}
{{ ta.name }}
{{ m.who }} · {{ m.time }} · {{ m.tick }}
{{ m.errText }}
Retry
Matched to this project by phone number — check it belongs here.
{{ m.movePick.label }}{{ m.movePick.caret }}
{{ smr.name }}
{{ m.movePick.emptyText }}
{{ m.icon }} {{ m.headline }}
{{ m.meta }}
Recording processing…
The recording didn’t make it across — the call itself is on the record.
{{ rc.label }}
{{ m.transcript }}
DIALPAD AI SUMMARY — NOT A TRANSCRIPT, AND SOMETIMES WRONG{{ m.recap }}Action: {{ m.actions }}
Call back {{ m.numberLabel }}
No messages yet — start the conversation.
New messages ↓
Replying to {{ replyToWho }} — {{ replyToSnippet }}
{{ p.initial }}
{{ p.name }}
On this project
No Slack ping
Nobody matches that
{{ a.name }}{{ a.sizeLabel }}uploading…failed
{{ cm.label }}
{{ callBtnLabel }}
{{ emailOffNote }}Connect your Google accountSet it up
{{ callNote }}
{{ dpOffNote }}See My settings → Phone
TEXTING THE CUSTOMER — SENDS IMMEDIATELY{{ smsThreadNote }}
To
{{ sr.label }}
{{ smsNoNumbersNote }}
{{ smsOptedOutNote }}
{{ smsA2pWarn }}
{{ smsLen }}
📎 Add an image
{{ smsImgName }}
{{ cmpErr }}
{{ smsSendLabel }}
{{ smsFromNote }}
GOING TO THE CUSTOMER{{ cmpThreadNote }}Start a new thread
{{ sendsAsNote }}
{{ sendsAsNote }}
Connect your Google account
To
{{ tc.addr }}
+ Add recipient
Cc
{{ cmpAddCustNote }}
Cc
{{ cc.addr }}
+ Add
{{ pg.label }}
{{ pr.name }}{{ pr.email }}{{ pr.role }}
Add
Done
{{ qg.label }}
{{ qr.name }}{{ qr.email }}
Add
Done
Use a template
{{ tplNote }}
{{ tp.name }}{{ tp.sub }}
B
I
🔗
Attach
{{ cmpLinkHint }}
Add link
Cancel
SIGNATURE — added when it sends
{{ sigNode }}
Attachments are {{ cmpAttSize }} — anything over about 7 MB will be turned down.
{{ cmpErr }}
Connect Google
Retry
{{ cmpSendLabel }}
Clear
{{ cancelJobLabel }}
Delete project
{{ railNoteHead }}
{{ railNoteBadge }}
{{ notesRailToggleLabel }}
{{ railNoteEmptyText }}
{{ n.init }}
{{ n.body }}
{{ n.attached }}
{{ n.who }} Reply Resolve Delete
{{ n.pill }}
{{ r.body }}
{{ r.who }}
Send
Post
Send to crew
Notes {{ notesTabCount }}
BILLABLE TOTAL{{ billTotal }}
{{ billTotalBilled }}
Billed
{{ billTotalCollected }}
Collected
{{ billTotalUninvoiced }}
Uninvoiced
CONTRACT{{ railContract }}
{{ railBilled }}
Billed
{{ railCollected }}
Collected
{{ railDue }}
Due
CHANGE ORDERS{{ coCount }}
{{ coTotal }}to contract
{{ coApprovedN }}{{ coPendingN }}
Billed {{ coBilled }}Collected {{ coCollected }}
MATERIALS & RECEIPTS{{ matCount }}
{{ matTotal }}logged
{{ matReceiptsN }}{{ matMissingN }}
Billed {{ matBilled }}Collected {{ matCollected }}
{{ railNextName }}
{{ railNextWhen }}
Activity
{{ a.text }}
{{ a.meta }}
Map
{{ mapSubtitle }}
{{ c.label }}
STATE
{{ mapStateBtnLabel }}
{{ s.label }}
{{ mapMatchCount }}{{ mapMatchLabel }}
{{ b.label }}
{{ mapUnplacedLabel }} ›
{{ mapRadiusBtnLabel }}
{{ mapRadiusHint }}
Crew → job distance
PROJECT / CUSTOMER
{{ o.name }}
{{ o.sub }}
CREW
{{ o.name }}
{{ o.sub }}
STACKABLE SETTINGS
Max distancemi
Only part-day jobs
Calculating…
{{ mapCalcErrorText }}
{{ mapCalcMiles }} mi
{{ mapCalcMode }}
{{ mapCalcTime }}
Estimated drive
Finding the best crew…
Suggested: {{ mapBestName }}
{{ mapBestMiles }} mi away · next open {{ mapBestFree }}
ALL CREWS BY DISTANCE
{{ r.name }}{{ r.milesLabel }} mi· {{ r.freeLabel }}
STACKABLE NEARBY JOBS
{{ s.name }}{{ s.milesLabel }} mi· {{ s.dateLabel }}
Scheduled
In progress
Complete
Calls & Texts
{{ pt.label }}
Everyone in the workspace can read this — it is the shared record of what we told a customer.
{{ phPersonPick.label }}
{{ pp.name }}
Nobody matches that
{{ pg.label }}
Clear filters
{{ phErr }}
WHEN
DIR
CUSTOMER
GOODCO
PROJECT
LENGTH
OUTCOME
REC
{{ r.when }}
{{ r.dirIcon }}
{{ r.who }}
{{ r.person }}
{{ r.jobLabel }}
{{ r.dur }}
{{ r.outcome }}
{{ r.card.icon }} {{ r.card.headline }}
{{ r.card.meta }}
Recording processing…
The recording didn’t make it across — the call itself is on the record.
{{ rc.label }}
{{ r.card.transcript }}
DIALPAD AI SUMMARY — NOT A TRANSCRIPT, AND SOMETIMES WRONG{{ r.card.recap }}
{{ r.card.assignPick.label }}{{ r.card.assignPick.caret }}
{{ ar.name }}
WHEN
DIR
NUMBER
MESSAGE
PROJECT
{{ t.when }}
{{ t.dir }}
{{ t.who }}
{{ t.body }}
{{ t.jobLabel }}
{{ phEmptyNote }}
{{ phCountLabel }}
‹ Newer
{{ phPageLabel }}
Older ›
Reports
{{ repSubtitle }}
{{ t.label }}
{{ c.label }}
Panels touched
{{ panelTotal }}
{{ panelVisits }}
{{ panelMissingNote }}
No visits were approved in this period.
By visit type
{{ r.name }} {{ r.visits }} {{ r.panels }}
By crew
{{ r.name }} {{ r.visits }} {{ r.panels }}
{{ panelFootnote }}
Saved reports need one setup step — run migration_v45_saved_reports.sql in Supabase. The builder still works; you just can't save yet.
Start from one of these
+ New report
{{ p.label }}
{{ p.desc }}
{{ p.tag }}
Saved by your team
{{ s.star }}
{{ s.name }}
{{ s.src }}
{{ s.desc }}
{{ s.sched }}
{{ rbSummary }}
Export CSV
{{ rbSubLabel }}
Save report
Report on
{{ s.label }}
Filters
Stage
{{ c.label }}
Status
{{ c.label }}
Age
{{ c.label }}
Crew
{{ c.label }}
Group by
{{ c.label }}
Columns
{{ c.label }}
Show as
{{ v.label }}
{{ t.label }}
{{ t.value }}
{{ rbChartTitle }}
No rows match these filters.
{{ r.label }}
{{ r.value }}
{{ h.label }}
{{ g.label }}
{{ g.meta }}
{{ c.text }}
No rows match these filters.
Billed
{{ repBilled }}
Collected
{{ repCollected }}
Outstanding
{{ repOutstanding }}
Overdue
{{ repOverdue }}
Accounts receivable aging⬇ CSV
Current{{ repArCur }}
31–60 days{{ repAr3160 }}
60+ days{{ repAr60 }}
Crew productivity⬇ CSV
No crew data yet.
{{ c.name }}{{ c.jobs }}{{ c.pass }}
Project packet
Pick a project and pull its photos and checklist status into one document you can save as a PDF or hand to the customer.
PROJECT
{{ o.name }}
No projects match.
Generate & preview
Save as PDF
✕ Close preview
{{ repTitle }}
{{ repAddr }}
{{ repOrg }}
{{ repDate }}
Checklist progress
{{ c.name }}
{{ c.pct }}%
Photos ({{ repPhotoCount }})
No photos captured yet.
Photos
{{ photosCountLabel }}
No photos yet — capture some from a project checklist.
{{ feedDownloadZipLabel }}
{{ feedDownloadPdfLabel }}
{{ globalSelAllLabel }}
Clear selection
{{ g.label }}
{{ p.initial }}
{{ p.cap }}
Checklists
Each one says whether that work can proceed without permit approval. A removal can go out while the reinstall waits on the same permit.
Templates your crews complete on site
Board
List
New
{{ t.name }}
{{ t.meta }}
{{ t.gateLabel }}
Edit
{{ t.name }}
{{ t.meta }}
{{ t.gateLabel }}
No checklists yet — tap “New” to build one.
All checklists
{{ tplNameLabel }}
{{ fieldCountLabel }}
SALESFORCE RECORD TYPE
{{ tplSfNote }}
COUNTS TOWARD PANELS
{{ tplPanelsNote }}
Off for surveys, permits and service calls.
DEFAULT LENGTH
hours
{{ tplHoursNote }}
PERMIT
Can proceed without permit approval
{{ tplGateNote }}
{{ tplSaveLabel }}
{{ tplAutosaveNote }}
{{ sec.count }}
{{ f.title }}
{{ f.hint }}
{{ f.typeLabel }}{{ f.minLabel }}{{ f.reqLabel }}
Add field
Add section
Field settings
{{ selTitle }}
FIELD NAME
FIELD TYPE
{{ t.label }}
Required
Blocks job completion
Minimum photos
{{ selMin }}
Options
Add option
Hint for the crew
Reference images
Add
Example photos the crew can view while completing this step.
Up
Down
{{ jobTitle }}
{{ jobAddress }}
⚠ Rejected — fix & resubmit
{{ runRejectNotes }}
{{ progressLabel }}{{ reqLeftLabel }}
{{ sec.upper }}
{{ f.numLabel }}
{{ f.title }}
{{ f.sub }}
Rejected: {{ f.rejNote }}
{{ f.waiveLine }}
CREW BLOCKED{{ f.cantNote }}
Can’t get
{{ f.waiveLabel }}
{{ f.rejectLabel }}
REJECTEDWAIVED
{{ progressLabel }}{{ progressPct }}
{{ blockLabel }}
{{ completeLabel }}
Save rejections — send back ({{ runReviewCount }})
{{ dvTitle }}
{{ jobTitle }}
{{ dvTypeLabel }}Required
{{ dvRejectLabel }}
Rejected: {{ dvRejNote }}
What to capture
{{ dvHint }}
View reference images{{ dvRefCount }}
Your photos{{ dvCountLabel }}
REJECTED
Tap a photo to mark it up & flag rejected.
Take photo
Choose from library
Import from CompanyCam
WAIVED — NO PHOTO REQUIRED
{{ dvWaiveLine }}
Undo the waiver
CREW FLAGGED A BLOCKER
{{ dvCantNote }}
Still open until it’s done or waived.
Waive this requirement
Clip uploaded
walkthrough.mp4
Tap to upload a clip
Choose from library
{{ o.text }}
Done
Work order details
WORK ORDER
{{ riChecklistName }}
Created {{ riCreatedLabel }}
On hold — {{ riHoldReason }}
{{ riHoldNote }}
STATUS
Complete all photos to finish this work order.
{{ riOpenInsp }}on this project
REJECTED — NEEDS REWORK
{{ riRejectNotes }}
REVIEW
✓ Approved
Approve all
Reviewing — press Reject on any checklist item to send it back to the crew.
No review required — ready to bill
Schedule
ESTIMATED DURATION · locked
{{ riDurLockedLabel }}{{ riDurLockedHours }}
The crew’s day is built on this. Unschedule to change it.
{{ d.label }}
hours
{{ riSchedDayLabel }}
{{ riSchedTimeLabel }}
{{ riSchedLoadLabel }}
Change time
Unschedule
Find a date and time
{{ riManualToggleLabel }}
SCHEDULED DATE *
ARRIVAL WINDOW *
to
{{ riArrWarnText }}
Crew
{{ cm.initial }}
{{ cm.name }}
{{ cm.role }}
×
{{ h.initial }}
{{ h.name }}
Helper (this WO)
×
{{ riAddPerson.label }}
{{ pp.initials }}
{{ pp.name }}
{{ pp.sub }}
Nobody matches that
{{ riBillableLabel }}
REVIEW NEEDED
REVIEWER
Gets the review request when the tech completes.
Status history{{ riHistLabel }}
{{ h.name }}{{ h.dateLabel }}
CANCELLED
{{ riCancelReason }}
Rescheduled {{ riReschedCount }} time(s)
Reason: {{ riReschedReason }}
Was scheduled {{ riReschedWhen }}
Save
Customers
People and companies you work for
Board
List
New customer
STATE
PROJECT
No customers found.
{{ c.initial }}
{{ c.name }}
{{ c.jobCount }}
{{ c.projBadgeLabel }}
{{ c.phone }}
{{ c.email }}
{{ c.address }}
{{ custMoreLabel }}
{{ c.initial }}
{{ c.name }}
{{ c.phone }} {{ c.email }}
{{ c.projBadgeLabel }}{{ c.jobCount }}
{{ custMoreLabel }}
All customers
{{ cdInitial }}
{{ cdName }}
{{ cdProjCount }}
Edit
Contact
{{ cdPhone }}
{{ cdEmail }}
{{ cdAddress }}
{{ cdNotes }}
ADDITIONAL CONTACTS
{{ ct.name }}
{{ ct.role }}
{{ ct.phone }}{{ ct.email }}
{{ sec.name }}
{{ cf.labelUpper }}
Add field
Add section
Estimates
{{ e.number }}
{{ e.amountLabel }}
{{ e.statusLabel }}
Projects
Add project
No projects yet — click Add project and choose a project type.
{{ p.title }}
{{ p.dateLabel }}
{{ p.statusLabel }}
Delete customer
Project types
Each type sets the checklist, files & pages a new project gets
New type
No project types yet — create one to define its checklist, files, and pages.
{{ t.name }}
{{ t.checklist }}
{{ t.meta }}
Edit
{{ settingsName }}
Customer email
{{ gGrantNote }}
{{ gEmail }}
{{ gWhen }} — customers see your name and your address, and it lands in your own Gmail Sent folder.
Disconnect
No Google account connected
{{ gSharedNote }}
Connect your Google account
Phone — texts & calls
{{ dpStatusNote }}
{{ dpLine }}
{{ dpEmail }} · {{ dpWhen }} — a customer who texts this number reaches you, not the office.
No Dialpad line linked
{{ dpNotLinkedNote }}
{{ dpBrowserNote }}
Workspace
Refresh
{{ dpRecordOn }}{{ dpA2p }}{{ dpHealth }}
{{ dpCtiNote }}
{{ dpGapNote }}
{{ dpLinkedCount }}
{{ dr.name }}{{ dr.line }}{{ dr.statusLabel }}
Test as a role
See the app the way each role sees it. This changes what the interface shows you — menus, buttons, permissions — and nothing in the database. Operator tools stay available so you can always switch back.
{{ r.label }}
Viewing as {{ testRoleName }} — your real access is unchanged.
Back to my role
Database rules still run as your real account, so a simulated role may still be able to save something it could not in real life. This shows you the interface, not the security.
Account Info
Change Email Address or Password
1
Enter your current password to edit your login info
Current Password
Don't know your password? Log out and reset it
2
Update your login info
Email Address
New Password
Leave blank to keep your current password. New passwords need 8+ characters with a letter and a number or symbol.
{{ acctErr }}
{{ acctMsg }}
{{ acctSaveLabel }}
Signed in as {{ settingsEmail }}
Security
Two-factor authentication
Require a code from your authenticator app at sign-in.
On · turn off
Enable
Set up two-factor
1. Scan this QR in Google Authenticator, Authy, or 1Password. 2. Enter the 6-digit code it shows.
{{ mfaQrEl }}
Manual key: {{ mfaEnrollSecret }}
{{ mfaErr }}
Cancel
Verify & enable
Profile Info
FIRST NAME
LAST NAME
PHONE NUMBER
JOB TITLE
{{ settingsJobTitleLabel }}
Set by an admin on the Team page
Company branding
Shows on invoices and customer-facing pages.
BUSINESS NAME
BRAND COLOR
PHONE
BILLING EMAIL
STREET ADDRESS
CITY
STATE
ZIP
Upload logo
PNG/JPG, shown on invoices
Sign out
Subscription
Current plan: {{ subPlanLabel }}
Upgrade plan
Plans & billing
Cancel subscription
Cancelling stops future charges at the end of your billing period. Data is kept 30 days, then deleted.
Danger zone
Permanently delete this workspace and everything in it — jobs, photos, work orders, invoices, and customers. This cannot be undone.
Delete workspace & all data
Estimates
{{ estNumber }}
Estimate / quote
STATUS
Customer
Existing customer
New customer
SELECT CUSTOMER
{{ c.name }}
{{ c.sub }}
No matches — switch to “New customer” to add one.
CUSTOMER EMAIL
PHONE
SERVICE ADDRESS
{{ s.label }}
FIRST NAME
LAST NAME
PHONE
EMAIL
STREET ADDRESS
{{ s.label }}
CITY
STATE
{{ s.label }}
ZIP
{{ cf.labelUpper }}
A new customer will be created and linked when you save this estimate.
CUSTOMER
CUSTOMER EMAIL
PHONE
SERVICE ADDRESS
{{ s.label }}
ASSIGNED TO *
{{ estAssignPick.label }}
{{ ea.initials }}
{{ ea.name }}
{{ ea.sub }}
NOW
Nobody matches that.
ESTIMATE DATE
VALID UNTIL
Start from a scope templateFills line items & terms
{{ t.name }}
{{ t.meta }} · Apply
Line items
+Add line
Add line items to build the quote
$
{{ l.lineTotal }}
Subtotal{{ estSubtotal }}
Discount
$
Tax
%{{ estTaxAmt }}
Total{{ estTotal }}
SCOPE / TERMS (on quote)
NOTES (internal)
Save estimate
⬇ PDF
Mark sent
Mark accepted
Declined
Delete
Duplicate
Save as template
Convert to project →
✓ Converted to project
Estimates
Bid a job, send a quote, convert accepted quotes to a project.
+New estimate
Estimates
Price book
{{ c.k }}
{{ c.v }}
Start from a scope templateSave any estimate as a reusable scope
+Blank estimate
Start from scratch
{{ t.name }}
{{ t.meta }}
Use scope
No estimates yet — create your first quote.
ESTIMATECUSTOMERAMOUNTSTATUS
{{ e.number }}{{ e.name }}{{ e.amountLabel }}{{ e.statusLabel }}
PDFDeleteConvert →✓ Project
Price book
Reusable items & labor rates you can drop into any estimate.
+Item
+Labor rate
No saved items yet.
{{ p.kindLabel }}
{{ co.label }}{{ co.note }}
No saved categories — type one to use it here, or add it under Customize → Estimate categories.
$
%
$
Sales tax by state
Estimates auto-apply the rate for the customer's service-address state.
{{ t.st }}
%
+Add state
Billing
Invoices & accounts receivable
New invoice
Outstanding
{{ billOutstanding }}
Overdue
{{ billOverdue }}
Paid this month
{{ billPaidMonth }}
Drafts
{{ billDraftCount }}
Progress billing
{{ p.title }}
{{ p.address }}
Contract {{ p.contractLabel }}Contract billed {{ p.contractBilledLabel }}Remaining {{ p.remainingLabel }}Pending contract {{ p.contractPendingLabel }}Pending extras {{ p.extrasPendingLabel }}
Extras {{ p.extrasTotalLabel }}Extras billed {{ p.extrasBilledLabel }}Extras remaining {{ p.extrasRemainingLabel }}{{ p.extrasPctLabel }}
{{ p.pctLabel }}{{ p.paidPctLabel }}
Ready to invoice
{{ b.title }}
{{ b.address }}
{{ n.label }}
{{ b.finalLabel }}
⚠ Not billed: {{ b.nonBillNames }}
{{ b.billHoldLabel }}
{{ b.billableCount }} billable
Open job
Create invoice
{{ t.label }}
Mine
{{ billPmLabel }}
{{ p.name }}
{{ p.sub }}
Nobody matches that
No invoices here yet.
INVOICECUSTOMERTYPEISSUEDDUEAMOUNTSTATUS
{{ r.number }}
{{ r.title }}
{{ r.sub }}
Progress billing{{ ck.name }}
{{ r.issued }}{{ r.due }}{{ r.amount }}{{ r.statusLabel }}
Send
Undo send
{{ r.progPaidBtnLabel }}
Mark paid
Rejected
Customize
Workflow & workspace defaults
{{ t.label }}
Stage targets
How long a job should sit in each stage before someone should look at it. Blank means no target — nothing is flagged.
STAGE TARGET CLOCK
{{ r.name }}
{{ r.why }}
{{ r.unit }}
{{ r.clock }}
Count business days only
Warn day(s) before the target
A job that’s on track shows no badge at all — a tag on every card is a tag nobody reads. Add a stage in Work order statuses and it appears here automatically.
{{ g.label }}
{{ it.label }} {{ it.state }}
{{ czSecTitle }}
{{ czSecWhat }}
{{ czSecLive }}
Where you'll see it
{{ w.text }}
Billing & invoicing
Create invoices, track what’s owed and mark payments here. Turn it off if you invoice from QuickBooks or anywhere else — approved work goes straight to Complete and no money is shown anywhere in the app.
Nothing is deleted. Turning it back on restores every invoice exactly as it was.
Track contract value
Keeps the job’s value and extras on the project even with invoicing off — it’s how you judge whether a job was worth doing, not just how you bill it.
Always on while billing is enabled.
Weather warnings
Marks scheduled work the weather can actually stop, using the free National Weather Service forecast for each job’s own address — not one forecast for the whole day, because you run New Hampshire and Massachusetts on the same Thursday.
{{ czWxStatus }}
What counts as bad weather
Your numbers, and only for roof work — carrying panels. Surveys and permit visits ignore rain, wind and cold; nobody minds drizzle while they meet an inspector. Lightning is the one thing that stops all of them. Only the hours a crew is on site are checked, so a 7–9 AM job on a wet afternoon is never flagged.
RAIN CHANCE
% or more
RAIN AMOUNT
in or more
WIND GUSTS
mph or more
COLD
°F or below
{{ czWxPeekHead }}
{{ p.k }} {{ p.v }} {{ p.when }}
{{ p.verdict }}
{{ czWxPeekNote }}
Lightning stops everything
Including surveys. There is no threshold to tune — any forecast thunderstorm during the visit is flagged.
Photo waivers
Lets a manager close a required item without the photo, with a mandatory reason logged against their name. Turn it off if you bill a GC or an insurer who audits the photo file — then required means required, with no exceptions.
Crews can never waive — waiving is a manager decision, made here.
Crews can flag a blocker
Adds a Blocked button to required items in the field app. The crew types what’s in the way and it lands here on the item — it never ticks anything off. Turn it off and the crew just can’t submit until the item is done.
Work order statuses
Add status
“Follow-up” statuses close the work order and open a new one in Ready to schedule (partials & reschedules).
Project stages
Add stage
The board columns on Projects — a project moves through these automatically from its work orders.
Stage automation
When a work order reaches a status, move its project to this stage. The project follows the least-advanced open work order.
WO: {{ m.woName }}
Inspection fail reasons
Tick a reason to say the failure was ours: it opens a service ticket with the tech’s photos and notes, holds the invoice, and comes back through review. Unticked, the inspection is simply re-booked.
{{ r.label }} {{ r.note }} ×
Add
Reschedule reasons
Shown when a work order is marked partial or rescheduled. Tick a reason to make it count against the crew's grade — leave weather and customer changes unticked.
{{ r.label }} {{ r.note }} ×
Add
Cancel reasons
Shown when a work order is cancelled. Cancelling clears its date and frees the crew’s day — the work order stays on the project as a record.
{{ c.label }}×
Add
Hold reasons
Every hold needs a reason — it shows next to the status so nobody has to ask why. “Pending permit” is set and cleared automatically by permit-gated checklists; leave it in the list.
{{ c.label }}×
Add
What's affected — damage reports
The tick-list a crew or a PM picks from when they report damage or a site problem. More than one can be picked on a report, so keep each one a single thing.
{{ c.label }}×
Add
Inspection reschedule reasons
Shown when a booked inspection moves. Kept apart from work-order reschedule reasons because an AHJ moving the date is a different problem from a crew being unavailable — and you want them counted separately.
{{ r.label }} {{ r.note }} ×
+ Add
Tick the ones where a truck went out — those count as an attempt, because the trip cost you either way.
Estimated durations
The quick-pick chips under a work order's scheduled date. A Custom option always lets you enter an exact time.
hrs
+Add duration
Customer email
Email a customer from the project. It goes out as the person sending it, and the reply files itself back onto that project.
Customer email isn’t switched on for this workspace yet — nothing to configure here until it is.
FROM NAME
{{ ceFromName }}
REPLIES MAILBOX
{{ ceReplyAddr }}
{{ ceMailboxNote }}
TEMPLATES
A starting point, not locked wording — whoever sends it can edit before it goes.
+ New template
The templates table isn’t in the database yet — run migrations/2026-08-19-email-templates.sql in Supabase and reload.
No templates yet.
{{ ct.name }}
{{ ct.sub }}
{{ ct.editLabel }}
{{ cv.tag }}
Anything with no value on the project takes its sentence with it, so a customer never sees an empty placeholder.
Save
Delete
WHO HAS CONNECTED GOOGLE
{{ ceConnectedCount }} — anyone unconnected still sends, but from the shared mailbox with their name on it.
Refresh
{{ cr.name }}{{ cr.permission }}{{ cr.email }}{{ cr.statusLabel }}{{ cr.when }}
Slack
Every new project gets its own channel, and the project Conversation stays in step with it both ways.
{{ slackTeamName }}
{{ slackChannelCount }} project channels
{{ slackBackfillLabel }}
Disconnect
!Slack isn’t installed on the server yet. The database migration and the two Edge Functions in the slack-handoff package have to be run first — until then Connect can’t do anything.
SLACK DIRECTORY
Matches teammates to their Slack accounts by email — @mentions only ping matched people.
{{ slackDirBtnLabel }}
{{ slackDirSummary }}
{{ u.name }}{{ u.email }}JobDoctor email doesn't match a Slack login — fix either side, then re-run
CHANNEL SETTINGS
Create a channel for every new project
Off — new projects don’t get Slack channels
Mirror messages both ways
Archive the channel when the project closes
Channel name prefix
Who gets invited
Always invite
Slack member IDs, comma-separated
No workspace connected.
Connect Slack
POST TO THE PROJECT CHANNEL WHEN
{{ ev.label }}
Permit & inspection types
The dropdowns used across permitting. Individual jurisdictions and their checklists live under Setup → Jurisdictions.
Permit types
The dropdown on every permit, and the rows in each jurisdiction's matrix.
{{ c.label }}×
Inspection types
The dropdown on every inspection. Used across all jurisdictions.
{{ c.label }}×
Drag to reorder — the order here is the order every permit and inspection dropdown uses.
Permit & inspection statuses
Permit statuses
What a permit moves through, from first draft to issued.
{{ s.lockNote }} ×
+ Add
Inspection statuses
What an inspection moves through, from request to result.
{{ s.lockNote }} ×
+ Add
Reset to the built-in statuses
Vendors
The vendor picklist on cost lines. New ones can also be added straight from a project's Costing tab.
Import from existing cost lines
No vendors yet — add them below, or import the ones already typed onto cost lines.
{{ c.label }}×
Add
Draw schedule
Shown as the {{ czDrawPreview }} button wherever draws are set up.
{{ czDrawTotal }}
% ×
+ Add draw
Reset to 30/30/40
Budget target
The margin every project is budgeted to hit. A project's cost budget is its value less this percentage.
Target margin
%
e.g. a $20,000 project budgets {{ targetBudgetExample }} of cost
Material categories
Shown when logging a material on a project's Costing tab.
{{ c.label }}×
Add
Estimate categories
Shown as the category dropdown on estimate line items.
{{ c.label }}×
Add
Roof types
Roof is chosen per array, beside tilt, on a new project and on the SYSTEM card.
{{ c.label }}×
Add
Partner types
Shown as the type dropdown on each partner (GCs, property managers, contractors you sub for).
{{ c.label }}×
Add
Company positions
Shown as the position dropdown when inviting a team member.
{{ c.label }}×
Add
Contact roles
Role chips available when adding an additional contact to a customer.
{{ c.label }}×
Add
ROLE
{{ czRoleDesc }}
WORKSPACE PLAN
{{ czPlanLabel }}
Locked areas need a higher plan.
PAGES THEY CAN OPEN
Reset {{ czRoleLabel }} to defaults
{{ p.num }}
{{ p.label }}{{ p.planTag }}
ACTIONS THEY CAN TAKE
{{ a.label }}{{ a.planTag }}
WHICH PROJECTS THEY SEE
See all projects — not just the ones they're assigned to
All roles at a glance
Default access per role on your plan.
{{ c }}
{{ r.label }}
{{ r.label }}
{{ cell.mark }}
Saved to this workspace only — changes auto-save and stay isolated to your account. Scope (all vs. assigned projects) comes next.
Social
Compose once, publish everywhere you switch on
{{ socInboxCount }} need replies
Compose
Library
Calendar
Inbox
Insights
Accounts
PUBLISH TO
{{ socAllLabel }}
Instagram
Facebook
X
TikTok
Snapchat
{{ socMediaEl }}
{{ socMediaStatus }}
📷
Add photo or video
from your library or computer
{{ socCounterText }}
⚠️Over 280 characters — X will reject this. Trim to publish everywhere at once.
📷Instagram needs a photo or video — add media or it'll be skipped for IG.
Post now
Add to queue
Schedule
{{ socAddEvergreenTick }}
Save as evergreen — reusable when the queue runs dry
{{ socPublishLabel }}{{ socSelCount }}
✓ {{ socPostedMsg }}
LIVE PREVIEW · {{ socSelCount }} SELECTED
jobdoctorapp
···
♡ 💬 ✈
jobdoctorapp {{ socText }}
JobDoctor
Sponsored · 🌐
{{ socText }}
👍 Like   💬 Comment   ↗ Share
JobDoctor @jobdoctorapp · now
{{ socText }}
💬 ⇄ ♡ 📊

2.4k 💬
88 ↗
@jobdoctor
{{ socText }}
JobDoctor
{{ socText }}
Switch on a platform to preview the post.
LIBRARY
{{ nav.label }}{{ nav.count }}
TAGS
{{ socTagManageLabel }}
{{ tag.label }}
Common tags — quick-pick on every photo
{{ t.label }}
STATUS
{{ st.label }}
🔍
{{ socLibHeading }}
{{ socLibShown }}
⬆ Upload off-job media
Auto-filled from job checklist photos. Tag & mark “Ready” to curate what posts. Upload is only for off-job media (logos, promos).
{{ a.statusLabel }}
{{ a.job }}
{{ a.meta }}
{{ t }}+ add tags
No media matches those filters.
{{ socLibSelJob }}
{{ socLibSelMeta }} · {{ socLibSelBy }}
STATUS
{{ o.label }}
TAGS
{{ c.label }}
Extra — this photo only
{{ t.label }}
Use in a post →
Autopilot
{{ socAutoSummary }}
{{ d.letter }}
×
+ Add time
{{ socEvergreenTick }}
When the queue runs dry, re-post an evergreen instead of going quiet
Queue
{{ socQueueSummary }}
+ Starter pack
Nothing queued
Write a post in Compose and choose “Add to queue”, or drop in the starter pack.
{{ q.slotLabel }}
{{ q.preview }}
{{ q.targetLabel }}◼ media♻ evergreen
×
Evergreen pool
Reused when the queue is empty — least recently posted goes first.
{{ e.preview }}
{{ e.meta }}
×
Scheduled
{{ sp.when }}
{{ sp.preview }}
×
{{ socCronNote }}
Comments, mentions & DMs from every platform — questions flagged first.
{{ msg.initials }}
{{ msg.name }}{{ msg.platform }}QUESTION{{ msg.time }}
{{ msg.text }}
Reply
✨ AI draft
Done
Sample inbox — live comment sync arrives with connected accounts.
Reach
64.2k
▲ 22%
Engagement
5.8%
▲ 1.1pt
New followers
1,204
▲ 18%
Link clicks
3,417
▼ 4%
Reach by platform
{{ bar.name }}
{{ bar.val }}
Link each network once. You approve on their site — JobDoctor never sees your password.
⚙️
Setup isn't finished yet
Connecting accounts needs a one-time Meta app link, done by a JobDoctor admin in Supabase (App ID + Secret). There's nothing to paste on this screen — the Connect buttons start working once that's set. See “JD Social go-live checklist”.
🔒
Secure sign-in (OAuth)
Connect takes you to the platform's own login. You approve access, they hand JobDoctor a token — stored encrypted in Supabase. Revoke anytime, here or from the platform.
YOUR NETWORKS
{{ acct.glyph }}
{{ acct.name }}{{ acct.statusLabel }}
{{ acct.detail }}
{{ acct.btnLabel }}
Snapchat has no public posting API — profile only. X posting requires a paid API tier. TikTok is pending approval.
Add media
Pick a job photo, or upload from this device
⬆ Upload from computer
🔍
{{ c.label }}
FROM YOUR JOBS
{{ p.caption }}
{{ p.meta }}
No job photos yet — crews' photos will show here. Use “Upload from computer” above.
Issues
Damage and site problems reported from the field. Worst and oldest first — nothing waits for someone to open the right project.
The issues table isn't in the database yet
Run migrations/2026-08-11-job-issues.sql in Supabase, then reload. Until then nothing can be reported or saved.
{{ issErrText }}
{{ f.label }} {{ f.n }}
{{ o.label }}
{{ p.initials }}
{{ p.name }}
{{ p.sub }}
NOW
Nobody matches that.
{{ issWhoNote }}
{{ i.title }}
{{ i.sub }}
{{ i.tag }}
{{ issNoneLabel }}
{{ issSelTag }}{{ issSelLevel }}
{{ issSelTitle }}
{{ issSelMeta }}
{{ issSelJobName }} →
{{ issSelDesc }}
THE ONE DECISION THAT SETS EVERYTHING ELSE
At fault
Cost against us
Not at fault
Change order
{{ issFaultNote }}
REPAIR ESTIMATE
+ Add estimate or invoice
Photo or PDF. Several allowed.
CREW ESTIMATED
{{ issSelEstCost }}
ACTUAL COST
WHO ABSORBS IT
{{ a.label }}
WHERE IT'S UP TO
{{ a.label }}
HOW DOES IT GET FIXED?
{{ a.label }}
{{ issRepairNote }}
WHO IS DOING THE REPAIR
Open the repair ticket
{{ issTicketLabel }}Open →
{{ issRepairPartsNote }}
{{ issNotifyTitle }}
{{ issNotifyNote }}
{{ a.label }}
This posts to the project's Costing tab as a cost line under "Damage — at fault", so it eats into the budget and the margin. It is never billed to the customer.
COST AGAINST US — THIS JOB
{{ issJobCost }}
THIS MONTH, ALL JOBS
{{ issMonthCost }}
Charged to {{ issChargedNames }}
Snapshotted when fault was set, so a later roster change can't rewrite who was on the roof.
{{ issCoTitle }}
{{ issCoNote }}
Raise a change order from this report
Open the change order on the project →
WHAT IS IT WAITING ON?
{{ w.label }}
{{ issWaitSummary }}
A new design doesn't always need a permit revision — add that one only if this AHJ wants to see it again.
Send the permit back so the permit manager sees it
{{ issPermitNoneNote }}
{{ p.name }}
{{ p.state }}
Needs revision
Open Permits & Inspections →
Acknowledge
Close it out
Reopen
Service
Rework, callbacks and anything a rejected bill or a failed inspection sent back. Same calendar and crews as work orders.
Refresh
{{ s.label }}
{{ s.value }}
{{ s.sub }}
{{ c.label }}
{{ svcEmptyNote }}
{{ t.typeLabel }} {{ t.number }} {{ t.sourceLabel }} {{ t.heldLabel }}
{{ t.jobTitle }}
{{ t.address }}
{{ t.notes }}
{{ t.statusLabel }}
{{ t.whenLabel }}
{{ t.whenSub }}
{{ t.ownerName }}
owner
Open project
Scheduling, the billing question and the item list all live on the project's Service tab — open the project to work a ticket. Crews only see a ticket once it is on their schedule.
{{ ahjReturnLabel }}
Jurisdictions
Every permit authority you submit to — what it requires, its checklists and its portals.
+ Jurisdiction
{{ ahjCountLabel }}
{{ ahjLibEmptyNote }}
Add your first jurisdiction
The permits matrix and submittal checklists live inside a jurisdiction — add one and they open here.
{{ a.name }} Salesforce inactive
{{ a.sub }}
{{ a.countLabel }} {{ a.toggleLabel }}
Name
City
State
{{ s.label }}
No state matches that.
Jurisdiction type
Phone
Typical review days
Permit must be picked up in person
Active
Notes
Permits & inspections required
Ticking a permit creates its submittal checklist. Ticking an inspection creates the inspection when that permit is approved.
TYPE
PERMIT
INSPECTION
ITEMS P/I
{{ m.type }} remove
{{ m.items }}
+ Add
Requirements — {{ a.reqTypeLabel }}
Kept apart: the application's documents, and what gets inspected on site.
{{ t.label }}
Tick a permit or inspection above first — requirements hang off a type.
Submittal requirements — what goes in the application
Becomes the permit's checklist on every project in this jurisdiction.
{{ r.reqLabel }}
Remove
+ Add
Nothing required yet.
Inspections this permit owes
Naming them here is what creates them on a project. A permit can owe several — Building → Rough, Building final.
{{ n.name }} ×
{{ a.inspNamesFallback }}
+ Add
On-site inspection checklist — what the inspector looks at
Becomes the checklist on every {{ a.reqTypeLabel }} inspection.
{{ r.reqLabel }}
Remove
+ Add
Nothing on the list yet.
Portals & logins
A jurisdiction often has several — plan review, inspection scheduling, fee payment.
+ Add portal
Portal
URL
Username
Password
{{ p.pwToggleLabel }}
Open
Remove
No portals yet.
{{ a.portalPwNote }}
Delete jurisdiction
Partners
Bill-to companies you work for — GCs, property managers, contractors you sub for.
Partner
{{ p.initial }}
{{ p.name }}
{{ p.meta }}
Open
← All partners
No partners yet — add the GCs or property managers you bill instead of the homeowner.
{{ p.initial }}
Delete
TYPE
BILLING EMAIL
PHONE
TERMS
BILLING MODE
Draw schedule
{{ p.drawSourceNote }}
{{ p.drawTotal }}
% ×
+ Add draw
Set under Customize → Draw schedule.
Use the workspace default
New projects billing to this partner start from this split. Projects already created keep the draws they have.
Can their work be billed before the inspection passes?
Yes — bill on completion
No — wait for the inspection
{{ p.holdNote }}
New projects billing to this partner start from this answer and can override it.
COMPANY ADDRESS
Portal / login
WEBSITE
USERNAME
PASSWORD
Contacts
Contact
Remove
{{ p.jobs }}
Projects
{{ p.billed }}
Billed
{{ p.collected }}
Collected
Crew capacity
Open spots this week by crew
CalendarNeeds schedulingCapacityOptimizer
CREWS
{{ capCrewBtnLabel }}
Select all
Back to all crews
{{ c.tick }}{{ c.name }}
Nobody matches that
{{ capFilterNote }}
Settings
{{ capRulesSummary }}
Change in Scheduling Rules
{{ capHeadOpen }}h open of {{ capHeadCap }}
Mon–Fri, drive time already deducted
{{ capHeadPct }}
of the week is spoken for
{{ capHeadBooked }}h
{{ capBookedNote }}
{{ capOverH }}h over
{{ capOverLabel }}
{{ capWeekendH }}h
booked on days off
{{ capPanelsLabel }}
this week
Where the room is
Crews with whole days still open, most first.
No fully open days left this week.
{{ c.label }}
{{ c.sub }}
Fill it
HALF DAYS — GOOD FOR A SERVICE CALL
{{ h.crew }}
{{ h.left }}
{{ capOffTitle }}
{{ capOffSub }}
{{ j.name }}
{{ j.sub }}
Sending it back counts as a reschedule and records the reason — the crew’s grade is untouched, because this is an office decision.
Day off — send the work back to Needs scheduling
Day off — leave the work booked
Never mind
The week, crew by crew
Click a bar to give that crew the day off — booked work is never dropped silently.
No crews yet — add one under Manage Crews (Setup).
{{ h.label }} WEEK {{ r.name }}
{{ d.txt }}
{{ r.freeLabel }}
wide openpart booked — hours left shownfullday off
Schedule Optimizer
Flags jobs closer to another crew's starting location · 14-day window
CalendarNeeds schedulingCapacityOptimizer
{{ optGrade }}Fleet health · {{ optHealth }}
Jobs analyzed
{{ optJobs }}
Reassign (>1.5hr)
{{ optReassign }}
Minutes reclaimable
{{ optMinutes }}
Some crews have no starting location set — add one under Manage Crews so the optimizer can measure drive distance.
Parameters
Reassign overhr drive
Suboptimal if a closer crew saves+ min
OK if withinmin of the day's other job
OK · closest crewSuboptimal · closer saves 20+ minReassign · over threshold + closer existsRegional · far but no closer crewNo address
Analyze jobs
{{ optAnalyzeNote }}
STATUSPROJECTADDRESSCURRENTSUGGESTEDDRIVESAVED
{{ j.status }}{{ j.title }}{{ j.addr }}{{ j.curCrew }}{{ j.sugCrew }}{{ j.drive }}{{ j.saved }}
CREWSTARTING LOCATIONJOBSOPTIMALREASSIGNGRADE
No crews yet.
{{ o.crew }}{{ o.loc }}{{ o.jobs }}{{ o.optimal }}{{ o.reassign }}{{ o.grade }}
Manage Crews
{{ crewsCount }}
New crew
Board
List
{{ c.label }}{{ c.count }}
No crews yet — tap New crew to create your first one.
No crews match your search.
{{ g.count }}
{{ g.activeLabel }}
Delete
STARTING LOCATION *
{{ sg.label }}
Required — used for drive distance & the optimizer
{{ g.memEmptyLabel }}
{{ s.initial }}
{{ s.name }}
{{ m.initial }}
{{ m.name }}
No members yet — search above to add.
Support
We usually reply within one business day · support@goodcosolar.com
New request
Send request
Your requests
No requests yet.
{{ t.subject }}
{{ t.body }}
{{ t.when }}
{{ t.status }}
This workspace's trial has ended
Ask your workspace owner to choose a plan to restore access. Your data is safe and will return as soon as the workspace is active.
Plan & billing
{{ billCurLine }}
{{ billSaveLabel }}
Monthly
CURRENT PLAN
{{ t.eyebrow }}
{{ t.headline }}
{{ t.tagline }}
{{ t.price }} {{ t.per }}
{{ t.sub }}
{{ ft.label }}
{{ t.ctaLabel }}
{{ pricingSeatLine }}
Get in touch
Add-ons · either plan can buy these
{{ ad.label }}
{{ ad.desc }}
{{ ad.price }}
{{ ad.statusLabel }}
Subscriptions are billed monthly and can be canceled anytime. Prices in USD. Taxes may apply. See our Terms & Privacy Policy.
Cancel subscription
Cancelling stops future charges at the end of your current billing period. Your workspace data is kept for 30 days, then permanently deleted.
Developer Mode
EXPERT
Reshape JobDoctor to fit how your company runs — fields, layouts, automations, branding, and integrations.
Editing
{{ devEditLabel }}
Setup
Design
Salesforce
Connection Field map Schedule Runs Claude sync
Claude sync
Claude reads both schemas and decides the map — fields, record types, transforms. The sync engine applies it row by row. Rows import on their own; anything NEW waits for you below.
{{ cLearnLabel }}
Dry run
Run now
{{ cNote }}
{{ cErr }}
LAST RUN {{ cLastWhen }} {{ cLastState }}
{{ c.n }}{{ c.label }}
{{ cLastSummary }}
WHAT SKIPPED, AND WHY
{{ cLastSkip }}
{{ cLastErr }}
Waiting on you
Mappings Claude wants to make. Nothing is written until you approve — approving applies it straight away and the next run uses it.
{{ cApproveAllLabel }}
Nothing pending. Ask Claude to learn the schemas, or run a sync — anything it can't resolve turns up here.
{{ p.kindLabel }} {{ p.title }} {{ p.conf }}
{{ p.reason }}
{{ p.sub }}
Approve
No
RECENTLY DECIDED
{{ d.state }}{{ d.title }}{{ d.err }}
Run history
{{ r.state }} {{ r.when }} {{ r.counts }} {{ r.line }}
{{ sfStatusTitle }}
{{ sfStatusNote }}
Connection
Credentials live in Supabase secrets, never in the browser.
{{ sfEnabledLabel }}
INSTANCE URL
SYNC EVERY (MIN)
{{ sfDryLabel }}
{{ sfRunLabel }}
{{ sfFullRunNote }}
{{ sfResult }}
Test import — one project
Pull a single Salesforce project, see exactly what would be written before anything is, then import it for a real look. Never moves the change bookmark, so the full import later is unaffected. Needs sf-sync v8 deployed — the buttons check and refuse on an older build.
SALESFORCE PROJECT NUMBER OR RECORD ID
{{ sfTestPrevLabel }}
{{ sfTestImpLabel }}
Remove test project
{{ sfTestNote }}
Imported — open {{ sfImpTitle }} →
WHAT A REAL IMPORT WOULD WRITE — NOTHING WAS
Clear ×
COULD NOT RESOLVE
{{ u.label }} — Salesforce value {{ u.raw }}
{{ u.why }}
PROJECT → jobs {{ sfPrevTitle }}
SALESFORCE FIELD
JOBDOCTOR
VALUE
{{ f.sf }}
{{ f.jd }} {{ f.tx }}
{{ f.val }}
CUSTOMER → customers {{ sfPrevCustName }}
{{ f.sf }}
{{ f.jd }} {{ f.tx }}
{{ f.val }}
{{ w.title }}WOULD SKIP — {{ w.skipped }}
{{ f.sf }}
{{ f.jd }} {{ f.tx }}
{{ f.val }}
Undo import
Deletes every record the sync created (rows stamped with a Salesforce id) and resets the change bookmark so the next sync starts fresh. Records made by hand in JobDoctor are never touched.
{{ sfUndoScanLabel }}
SCOPE
Everything imported
Just the last run
{{ sfUndoScopeNote }}
{{ sfFrozenLabel }}
{{ c.n }}{{ c.label }}
Nothing to undo — no imported records found.
Type UNDO to confirm. Edits made in JobDoctor to imported records are deleted with them.
{{ sfUndoBtnLabel }}
Record type mapping
Salesforce record types to JobDoctor project types & checklists. A work order can only import if its record type has a checklist here.
{{ sfRtBtnLabel }}
{{ sfRtGapNote }}
{{ sfRtErr }}
WORK ORDER RECORD TYPES → CHECKLISTS
No WorkOrder record types came back.
{{ r.name }}{{ r.dev }}
{{ r.state }}
{{ r.btnLabel }}
{{ o.name }}NOW
Nothing matches that
Clear this mapping
PROJECT RECORD TYPES → PROJECT TYPES
No Project__c record types came back.
{{ r.name }}{{ r.dev }}
{{ r.state }}
{{ r.btnLabel }}
{{ o.name }}NOW
Nothing matches that
Clear this mapping
JOBDOCTOR SIDE
{{ r.name }}{{ r.kind }}{{ r.state }}
{{ s.n }}
{{ s.label }}
This IS the mapping the sync runs on — every pick writes a row to sf_field_map straight away. Values shown are from P-1000003 (Jones Household - Test Project 1), read from Salesforce.
{{ sfAutoLabel }}
{{ o.title }} {{ o.count }}
{{ o.sfObj }} → {{ o.jdTable }}
{{ o.nIn }} {{ o.nBuild }} {{ o.nLost }} not in sync yet
{{ sfCurTitle }}
{{ sfCurSub }}
{{ sfCurKeyLabel }} {{ sfCurKey }}
Clear filter
Mapping here is recorded, but the sync function does not import this object yet — it currently handles Contact, Project__c, WorkOrder and Account.
OBJECT SALESFORCE FIELD · VALUE ON P-1000003 LANDS IN JOBDOCTOR STATUS
{{ f.objName }}
{{ f.sf }} {{ f.example }}
{{ f.target }} {{ f.caret }}
{{ f.transform }}
{{ op.label }} {{ op.tag }}
Nothing matches that.
{{ f.badge }} {{ f.rule }}
Gone the day Salesforce is off {{ sfLostLabel }}
{{ sfReadyLine }}
{{ l.obj }}
{{ l.field }}
{{ l.why }}
{{ sfSchedNote }}
{{ g.title }} {{ g.sub }}
{{ op.label }}
{{ op.desc }}
IN PLAIN ENGLISH {{ sfSchedSummary }} {{ sfSchedCaveat }}
What a pull can and cannot make identical
{{ p.title }}
{{ p.desc }}
Raw mapping rows
The same sf_field_map rows the Field map tab writes — edit here for anything the catalogue does not cover.
Add field
{{ m.onLabel }}
Recent runs
No sync has run yet.
{{ r.object }}{{ r.counts }}{{ r.when }}
With editing on, add/reorder custom fields & sections directly on any Project or Customer. Changes apply to your whole workspace instantly.
Form Builder
Custom fields & sections shown on every record.
Projects
Customers
No custom sections yet — add one to start building.
+Field
No fields yet.
Req
SHOWS ON
+Add section
Terminology
Rename what things are called across the whole app.
{{ t.def }}
Layout Editor
Show or hide the optional tabs on every project.
{{ t.label }}
Workflow Automation
When a work order hits a status, run an action automatically.
+ Add rule
No automation rules yet.
WHENTHEN
Sidebar order
Reorder the left navigation for your whole workspace.
{{ n.label }}
Statuses & Stages
Define your own work-order statuses and project stages with colors & automation.
Workflow Automation
Stage automation rules — when a work order changes, move the project and notify the right people.
Branding & PDF Templates
Your logo & colors live in Settings and flow onto invoices & estimates. Open →
API & Webhooks
Generate an API key and push job/WO changes to your tools.
{{ devApiKey }}
Revoke
Generate API key
Webhooks
+ Add
No endpoints yet.
Project detail sections
Reorder the cards shown on every project's Overview tab. Applies to your whole workspace.
{{ s.label }}
Super Admin
Not visible to anyone else — role permissions, storage/cost estimate, and error log.
Metrics
Projection
Workspaces
Storage & cost
Role permissions
Plans & promos
Error log
Beta
Break-even
Recomputed from your live plan mix and current infrastructure cost — not a fixed number.
Break-even at
{{ pjBreakeven }}customers
Paying today
{{ pjPayingNow }}{{ pjGap }}
Blended ARPU
{{ pjArpu }}/mo
Fixed costs
{{ pjFixed }}/mo
{{ pjArpuNote }}
What if I had…
Drag to model a customer count at your current mix.
{{ pjN }}customers
{{ p.label }}
MRR
{{ pjMrr }}
{{ pjArr }} / yr
Infrastructure
{{ pjInfra }}
{{ pjStorage }} media storage
Stripe fees
{{ pjStripe }}
2.9% + 30¢ monthly
Net
{{ pjNet }}
{{ pjMargin }} margin · {{ pjNetYr }}/yr
{{ pjSupport }}
Milestones
What each customer count actually means for you.
{{ m.n }}
{{ m.title }}
{{ m.note }}
{{ m.money }}
Read this monthly, not daily
These are arithmetic, not forecasts — there's no growth-rate prediction here on purpose. A trend line drawn through a handful of workspaces is noise dressed up as insight. The numbers that decide anything are on the Metrics tab.
MRR
{{ mxMrr }}
{{ mxMrrSub }}
ARR pace
{{ mxArr }}
MRR × 12
Paying orgs
{{ mxPaying }}
{{ mxPlanSplit }}
In trial
{{ mxTrials }}
{{ mxTrialSub }}
Net margin
{{ mxMargin }}
{{ mxMarginSub }}
Active workspaces
the number that matters
An org that pays and never opens the app has already churned — it just hasn't told you yet.
{{ mxWau }} {{ mxWauSub }}
{{ mxLegend7 }}
{{ mxLegend30 }}
{{ mxLegendDark }}
Activation funnel
Last 30 days of sign-ups. Where they stop is your product bug.
{{ fn.label }}{{ fn.count }}
{{ mxFunnelNote }}
New sign-ups
Day
Week
Month
{{ mxSignupCaption }}
{{ b.label }}
{{ mxSuAName }}
{{ mxSuA }}
Last 30 days
{{ mxSu30 }}
Trial → paid
{{ mxConvRate }}
All time
{{ mxSuAll }}
Needs attention
Your to-do list, generated. Work this before anything else in Super Admin.
{{ a.badge }}
{{ a.name }}
{{ a.detail }}
Open
Nothing needs attention — every workspace is active and no trial is expiring.
Workspace usage
last 30 days · sorted by health
Workspace
Plan
Seats
MRR
Jobs
Photos
Storage
Last active
{{ u.name }}
{{ u.plan }}
{{ u.seats }}
{{ u.mrr }}
{{ u.jobs30 }}
{{ u.photos30 }}
{{ u.storage }}
{{ u.last }}
No workspaces yet.
{{ opOrgCount }}
Opening a workspace is logged. Read-only — for support & debugging.
{{ o.name }}
{{ o.users }} users · {{ o.jobs }} jobs
{{ o.plan }}
No workspaces found — run migration v27.
{{ opvName }}
{{ opvStatusLabel }}
×
{{ opvJobCount }}
Jobs
{{ opvCustCount }}
Customers
{{ opvMemberCount }}
Members
{{ opvRevenue }}
Collected
Members ({{ opvMemberCount }})
{{ m.name }}
{{ m.position }}
{{ m.email }}
{{ m.phone }}
Joined {{ m.joined }}
Recent jobs ({{ opvJobLabel }})
{{ j.title }}
{{ j.status }}
Current plan
{{ opvPlanLabel }}{{ opvPlanPrice }}
{{ ft.label }}
{{ opvIncludedNote }}
Add-ons — sold separately, any tier
{{ ad.label }}
{{ ad.desc }}
{{ ad.price }}
Testing — set plan
{{ b.label }}
Switches this workspace's plan instantly (clears any comp/free override) so you can sign in as its users and test each tier. Use the Role dropdown on each member above to test roles.
Commercial — discounts & free access
DISCOUNT %
PROMO NOTE
FREE UNTIL
COMP PLAN (force)
Free-until or a comp plan grants full access without payment (early-adopter program). Clear them when the customer converts to paid.
Actual usage
{{ opPlanLabel }}
Refresh
Reading usage from Postgres…
{{ opUsageErrMsg }}
Database
{{ opDbUsed }}
{{ opDbPct }} · {{ opRemainDb }}
File storage
{{ opStUsed }}
{{ opStPct }} · {{ opRemainSt }}
{{ opFileCount }} files
Monthly active users
{{ opMauUsed }}
{{ opMauPct }}
{{ opAuthUsers }}
Live DB connections
{{ opConnUsed }}
{{ opConnPct }}
Storage overage
{{ opOverageCost }}
{{ opOverageNote }}
Not measurable here
Egress (bandwidth) and your plan tier live in Supabase billing, not in the database — nothing inside Postgres can read them.
You get {{ opEgressAllowance }}. Check it under Project Settings → Usage.
Buckets
{{ b.name }} {{ b.detail }}
Biggest tables
{{ t.name }} {{ t.size }}
Role permissions
{{ rr.role }}
Subscription tiers
Toggle which features each paid tier unlocks. Applies to every workspace.
{{ t.name }}
{{ f.label }}
Per-workspace discounts, promos, and free/early-adopter access are set inside each workspace (open one from the Workspaces tab).
Error log (last 50)
Refresh
{{ e.message }}
{{ e.when }} · {{ e.url }}
No errors logged yet.
Beta features
Hidden, in-progress features. Turn one on to reveal it in this workspace.
Schedule Optimizer
Route-density analysis & crew reassignment suggestions. Geocoding accuracy still being tuned.
Team
{{ orgName }}
Your profile
{{ userInitial }}
{{ userEmail }} · {{ roleLabel }}
Your name is set by an owner, admin or manager, on the Team page.
Users{{ seatSummary }}{{ memberCount }}
Invite Users
{{ m.initial }}
{{ m.name }}
{{ m.email }}
{{ m.phone }}
{{ m.activityMeta }}
{{ m.role }}
{{ m.toggleLabel }}
{{ m.operatorLabel }}
Pending invites
{{ i.email }}
Invited · pending
{{ i.permission }}
Sign out
Dialpad{{ dpRingingNote }}
{{ dpWrongUserNote }}
{{ dialerLoginNote }}
📞 Dialer
{{ n.label }}
{{ a.label }}
{{ toast }}
Import from CompanyCam
{{ ccSub }}
Close
{{ ccBusyLabel }}
{{ ccErr }}
Which CompanyCam project is this?
Nothing in CompanyCam matched this address. Search for it by name or street instead.
Search
{{ m.name }}
{{ m.address }}
{{ m.scoreLabel }}
{{ ccPhotoHeading }}
Different project
{{ ccSelectAllLabel }}
This CompanyCam project has no photos yet.
ALREADY IMPORTED
{{ ccFootNote }}
{{ ccImportLabel }}