docs: add Plan 4 (lifecycle state machine + inspections + estimate versions)
This commit is contained in:
@@ -509,7 +509,8 @@ Each plan = one phase, produces working/verifiable software on its own, and is e
|
||||
### Plan 4 — Project lifecycle + inspections + estimates
|
||||
- **What:** make the existing PROJECT PROGRESSION bar an **interactive state machine** with progress % tied to the stage (§9); the two-touchpoint **inspection model** with an issues/rework loop + session photo/note uploads (§11, A.5); the **claimed-vs-verified scope-variance** badge; and the **estimate-versions** section (v1 claim → v2 verified → signed; full document view).
|
||||
- **Why:** the static progression bar already disagrees with completion % (a visible defect); interactivity + the inspection loop + scope-variance is the core sales narrative (a professional inspection turns a $200 self-report into an insurance-backed job).
|
||||
- **How:** add the lifecycle/inspection/estimate fields to projects in `mockStore.jsx` (sequential) per Appendix A; add store mutators (advance stage, add inspection/issue, upload photo→object URL, add estimate version); build the UI in `OwnerProjectDetail.jsx` (progression control, inspection panel + issues list, scope-variance badge, estimate-versions tab) — these UI pieces in the one file run sequentially, but are independent of the data tasks. Verify: advancing a stage updates %, logging an issue loops to rework then re-inspection, estimate versions render.
|
||||
- **How:** add the lifecycle/inspection/estimate fields to projects in `mockStore.jsx` (sequential) per Appendix A; add store mutators (advance stage, add inspection/issue, upload photo→object URL, add estimate version); build the UI in `OwnerProjectDetail.jsx` (progression control, inspection panel + issues list, scope-variance badge, estimate-versions tab) — these UI pieces in the one file run sequentially, but are independent of the data tasks. A new pure `src/data/lifecycle.js` (stage set, stage→% map, next/prev helpers) is the one parallel-safe carve-out; everything else is concentrated in `mockStore.jsx` + `OwnerProjectDetail.jsx`, so Plan 4 is mostly sequential. Verify: advancing a stage updates %, logging an issue loops to rework then re-inspection, estimate versions render.
|
||||
- **Plan file:** `docs/superpowers/plans/2026-05-29-plan4-lifecycle-inspections-estimates.md`.
|
||||
|
||||
### Plan 5 — Subcontractor task state machine
|
||||
- **What:** the §10 stage machine (Not Assigned → Assigned → Pre-Work Inspection → Work In Progress → On Hold ↔ → Post-Work Review → Pass→Completed / Fail→Rework), per-transition notes + actor + timestamp, pre/post-inspection attribution; owner table gains Task Category + Assigned Date; richer task modal (stage timeline + issues with photos).
|
||||
|
||||
@@ -0,0 +1,257 @@
|
||||
# Plan 4 — Project Lifecycle State Machine + Inspections + Estimate Versions
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: superpowers:subagent-driven-development. Steps use checkbox (`- [ ]`). Follow the execution protocol (`2026-05-29-execution-protocol.md`) — sequential on shared files, parallel only where lanes are disjoint.
|
||||
|
||||
**Goal:** Turn the static project-progression bar into an **interactive lifecycle state machine** (stage → progress %, with advance/regress + note + actor), add the **two-touchpoint inspection model** (damage verification + external final inspection, with an issues→rework→re-inspection loop and photo uploads), the **claimed-vs-verified scope-variance badge**, and an **estimate-versions** tab (v1 claim → v2 verified → signed; opens the full estimate document). This makes the Plan-3-tagged scenarios #2 (insurance up-scope), #5 (revisions), #8 (final inspection), and #10 (dispute) fully playable.
|
||||
|
||||
**Architecture:** A new pure module `src/data/lifecycle.js` defines the stage set, the stage→% map, and next/prev helpers (reused by both the store mutator and the UI). `mockStore.jsx` gains `lifecycleStage` + `stageHistory` on every project, `inspections[]` / `estimateVersions[]` / `verifiedScope` on the scenario projects, and new mutators (advance stage, add inspection, add/resolve issue, add estimate version) following the existing `updateProject`/`addJobTeamMember` pattern. `OwnerProjectDetail.jsx` gets an interactive progression control, an Inspections tab, an Estimate Versions tab, and a scope-variance badge — reusing the existing `EstimateDetailModal` and the `URL.createObjectURL` photo pattern.
|
||||
|
||||
**Tech stack:** React 18, Vite 7, mock React-context store, pnpm. No test harness → verify via `npm run build`, throwaway `node` checks for pure logic, and dev-run of each interaction. Nothing test-related is committed.
|
||||
|
||||
**Spec/master:** `docs/superpowers/2026-05-29-MASTER-PLAN.md` §9 (lifecycle), §11 (estimate/inspection/scope-variance), Appendix A.5 (inspection), A.6 (estimate version), Appendix G Plan 4. Builds on Plan 3's `demoScenario` tags.
|
||||
|
||||
---
|
||||
|
||||
## Current state (verified)
|
||||
|
||||
- `OwnerProjectDetail.jsx` has **10 tabs** (`overview, budget, team, changeOrders, rfis, milestones, invoices, risks, activity, docs`, array at lines 67-78). **No Inspections or Estimate Versions tab.**
|
||||
- The **"Project Progression"** block (lines 555-592, inside `overview`) is **static**: five hardcoded steps `["Lead","Inspection Scheduled","Damage Verified","Scope Approved","Project Completed"]` whose active state is `(i*25) <= project.completionPercentage`. No `onClick`, no advance control. **No `lifecycleStage` field exists** on any project.
|
||||
- Store mutator pattern (`mockStore.jsx`): `const [projects, setProjects] = useState(MOCK_PROJECTS)` (line ~7438); mutators are inline arrows on the context `value` object (~line 8036+): `updateProject(projectId, data)` (shallow-merge, line ~8181), `addJobTeamMember(projectId, member)` (nested-append with `id: \`jtm_${Date.now()}\``, ~8184). New mutators follow this exactly.
|
||||
- Estimate document renderer: **`EstimateDetailModal`** (`src/components/estimates/EstimateDetailModal.jsx`), props `{ isOpen, onClose, estimate }`. `estimate` = `{ id, clientName, address, date, status, total, templateId, templateName, estimatedBy, roofArea, notes }`. Line items are **computed** via `computeLineItems(template, roofArea)` (`src/utils/estimateExport.js`); a `!template` fallback (lines ~389-394) renders a stored-total-only view. Versions can therefore reuse this modal with either a real `templateId` or just a `total`.
|
||||
- Photo upload: copy **Pattern A** — `URL.createObjectURL(file)` into a local state array (`PropertyDetailDrawer.jsx` lines 235-246) — the only pattern that yields a displayable image URL. (The `CreateItemModal` `type:'file'` path captures a `File` but its submit is a stub.)
|
||||
- 29 projects (`PRJ-2026-001…029`); Plan 3 tagged 10 with `demoScenario:1..10`.
|
||||
|
||||
## Scope & parallelism note (read first)
|
||||
|
||||
Plan 4 is **concentrated in two files** — `src/data/mockStore.jsx` (data + mutators) and `src/pages/owner/OwnerProjectDetail.jsx` (all UI). The only parallel-safe carve-out is the new pure `src/data/lifecycle.js`. Therefore **Phase A (data/store, mockStore.jsx) and Phase B (UI, OwnerProjectDetail.jsx) are each internally sequential**; Phase B depends on Phase A (it consumes the new fields + mutators). Don't fabricate parallelism here — run it sequentially and commit between tasks.
|
||||
|
||||
---
|
||||
|
||||
## Lane / ownership map
|
||||
|
||||
| Task | Lane (exclusive files) | Phase |
|
||||
|------|------------------------|-------|
|
||||
| T1 `lifecycle.js` (new) | `src/data/lifecycle.js` | A (first; independent) |
|
||||
| T2 lifecycleStage + stageHistory on all 29 projects | `src/data/mockStore.jsx` | A (seq) |
|
||||
| T3 inspections / estimateVersions / verifiedScope on scenario projects | `src/data/mockStore.jsx` | A (seq, after T2) |
|
||||
| T4 store mutators | `src/data/mockStore.jsx` | A (seq, after T3) |
|
||||
| T5 interactive progression control | `src/pages/owner/OwnerProjectDetail.jsx` | B (seq) |
|
||||
| T6 Inspections tab | `src/pages/owner/OwnerProjectDetail.jsx` | B (seq) |
|
||||
| T7 Estimate Versions tab | `src/pages/owner/OwnerProjectDetail.jsx` | B (seq) |
|
||||
| T8 Scope-variance badge | `src/pages/owner/OwnerProjectDetail.jsx` | B (seq) |
|
||||
| T9 Verify | (read-only) | C |
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Create `src/data/lifecycle.js` (stage model — pure)
|
||||
|
||||
**Files:** Create `src/data/lifecycle.js`. Independent; do first.
|
||||
|
||||
- [ ] **Step 1:** Create the file:
|
||||
|
||||
```js
|
||||
// src/data/lifecycle.js
|
||||
// The project lifecycle state machine (Master §9). Pure constants + helpers; no React.
|
||||
|
||||
// Ordered linear stages. 'Rework In Progress' is an off-line regression state (entered on a failed
|
||||
// final inspection), so it is NOT in the linear order array — handle it explicitly.
|
||||
export const LIFECYCLE_STAGES = [
|
||||
'Lead',
|
||||
'Damage Inspection Scheduled',
|
||||
'Damage Verified',
|
||||
'Scope Approved',
|
||||
'Contract Signed',
|
||||
'Work In Progress',
|
||||
'Work Complete — Awaiting Inspection',
|
||||
'Final Inspection Scheduled',
|
||||
'Final Inspection In Progress',
|
||||
'Project Completed',
|
||||
'Final Payment Received — Closed',
|
||||
];
|
||||
|
||||
export const REWORK_STAGE = 'Rework In Progress';
|
||||
|
||||
// Stage → canonical progress %. Advancing to a stage sets the project's completionPercentage to this.
|
||||
export const STAGE_PROGRESS = {
|
||||
'Lead': 5,
|
||||
'Damage Inspection Scheduled': 10,
|
||||
'Damage Verified': 20,
|
||||
'Scope Approved': 30,
|
||||
'Contract Signed': 40,
|
||||
'Work In Progress': 55,
|
||||
'Work Complete — Awaiting Inspection': 80,
|
||||
'Final Inspection Scheduled': 85,
|
||||
'Final Inspection In Progress': 90,
|
||||
'Rework In Progress': 75,
|
||||
'Project Completed': 95,
|
||||
'Final Payment Received — Closed': 100,
|
||||
};
|
||||
|
||||
export const stageToPct = (stage) => STAGE_PROGRESS[stage] ?? 0;
|
||||
|
||||
export const nextStage = (stage) => {
|
||||
if (stage === REWORK_STAGE) return 'Final Inspection Scheduled'; // rework re-enters inspection
|
||||
const i = LIFECYCLE_STAGES.indexOf(stage);
|
||||
return i >= 0 && i < LIFECYCLE_STAGES.length - 1 ? LIFECYCLE_STAGES[i + 1] : stage;
|
||||
};
|
||||
|
||||
export const prevStage = (stage) => {
|
||||
const i = LIFECYCLE_STAGES.indexOf(stage);
|
||||
return i > 0 ? LIFECYCLE_STAGES[i - 1] : stage;
|
||||
};
|
||||
|
||||
// Is this stage at/after a reference stage? (for gating UI like "can schedule final inspection")
|
||||
export const isAtOrAfter = (stage, ref) =>
|
||||
LIFECYCLE_STAGES.indexOf(stage) >= LIFECYCLE_STAGES.indexOf(ref);
|
||||
```
|
||||
|
||||
- [ ] **Step 2 (verify):** throwaway `node` check (temp dir, deleted): assert `nextStage('Damage Verified')==='Scope Approved'`, `nextStage(REWORK_STAGE)==='Final Inspection Scheduled'`, `stageToPct('Contract Signed')===40`, `prevStage('Lead')==='Lead'`, `isAtOrAfter('Project Completed','Contract Signed')===true`. Then `node -e "import('./src/data/lifecycle.js').then(m=>console.log(Object.keys(m)))"` proves it loads.
|
||||
- [ ] **Step 3:** commit `git commit -m "feat(lifecycle): add pure project lifecycle stage model (stages, progress map, helpers)"` (after `git add src/data/lifecycle.js`).
|
||||
|
||||
## Task 2: Add `lifecycleStage` + `stageHistory` to all 29 projects
|
||||
|
||||
**Files:** `src/data/mockStore.jsx` only.
|
||||
|
||||
- [ ] **Step 1:** For EACH of the 29 projects, add a `lifecycleStage` (a value from `LIFECYCLE_STAGES`/`REWORK_STAGE`) derived from its current `status`/`phase`/`completionPercentage`:
|
||||
- `status:'completed'` & all paid → `'Final Payment Received — Closed'`; completed but not all paid → `'Project Completed'`.
|
||||
- active near-done (~80–90%) → `'Work Complete — Awaiting Inspection'` or `'Final Inspection Scheduled'`.
|
||||
- active mid (40–70%) → `'Work In Progress'`.
|
||||
- signed/pre-construction (the converted signed leads, ~0–30%) → `'Contract Signed'` or `'Scope Approved'`.
|
||||
- the disputed/on-hold scenario (#10) → keep its existing stage (e.g. `'Work In Progress'`) — On-Hold is represented by `status:'disputed'` + the dispute data; do NOT add an on-hold lifecycle stage (the §10 sub-task machine has On Hold; the project lifecycle does not).
|
||||
- Set `completionPercentage` to remain consistent with `stageToPct(lifecycleStage)` for discrete stages; for `'Work In Progress'` keep the project's existing milestone-driven % (40–70).
|
||||
- [ ] **Step 2:** Add a `stageHistory: [{ stage, date, by, note }]` to each project — at minimum the current stage as the last entry; for the scenario projects, author 2–4 prior transitions with real dates/actors (owner or rep names) telling the job's story.
|
||||
- [ ] **Step 3 (verify):** `npm run build` → `✓ built`. `git grep -c "lifecycleStage:" -- src/data/mockStore.jsx` → 29. Spot-check 3 projects that `completionPercentage` is consistent with the stage.
|
||||
- [ ] **Step 4:** commit `git commit -am "feat(data): add lifecycleStage + stageHistory to all projects"`.
|
||||
|
||||
## Task 3: Add inspections / estimateVersions / verifiedScope to scenario projects
|
||||
|
||||
**Files:** `src/data/mockStore.jsx` only. After T2.
|
||||
|
||||
Author rich data on the tagged scenarios (find them via `demoScenario`), per schemas A.5 / A.6 and §11.
|
||||
|
||||
- [ ] **Step 1 — Damage inspection + scope variance (scenario #2, the hero up-scope).** On the `demoScenario:2` project add:
|
||||
- `reportedScope: { summary: 'Small roof leak near chimney', estTotal: 200, estDays: 1 }` (the customer's claim — also mirror onto its `sourceLeadId` lead as `reportedScope`).
|
||||
- `verifiedScope: { summary: 'Widespread hail bruising across north + west slopes; decking damage', estTotal: 15400, estDays: 5 }`.
|
||||
- `scopeVariance: true` (drives the badge).
|
||||
- `inspections: [{ id:'insp_<n>_1', kind:'damage', round:1, inspectorName:<a field agent or rep>, inspectorRole:'Field Agent', inspectionDate:<date>, result:'pass', feedbackNote:'Verified major hail damage far exceeding reported leak; photos attached.', issues:[] }]`.
|
||||
- `estimateVersions: [ { version:1, date:<d1>, total:200, note:'Initial — homeowner-reported leak', status:'sent' }, { version:2, date:<d2>, total:15400, note:'Revised after damage verification (insurance supplement)', status:'revised' }, { version:3, date:<d3>, total:15400, note:'Approved + signed after adjuster supplement', status:'signed', templateId:<an existing template id if one fits, else omit> } ]`.
|
||||
- [ ] **Step 2 — Estimate revisions (scenario #5).** On `demoScenario:5` add `estimateVersions` of 3–4 versions showing a negotiation (v1 sent → v2 revised → v3 revised → v4 signed), each with a `note` and changing `total`; set `verifiedScope` if relevant.
|
||||
- [ ] **Step 3 — Final inspection + rework loop (scenario #8, near-complete).** On `demoScenario:8` add a final inspection round per A.5: `{ id, kind:'final', round:1, inspectorName:<external examiner name>, inspectorContact:{ phone:<realistic>, email:<realistic> }, inspectionDate:<d>, result:'fail', feedbackNote:'Flashing detail at the valley needs rework.', issues:[{ id, description:'Valley flashing not sealed to spec', note:'Re-seal and re-inspect', photos:[], status:'open' }] }`. Set its `lifecycleStage` to `'Final Inspection In Progress'` (or `REWORK_STAGE` to demo the regression). This gives the demo a live issues→rework loop.
|
||||
- [ ] **Step 4 — Closed, all-passed (scenario #9).** On `demoScenario:9` add a passing final inspection: `{ kind:'final', round:1, inspectorName, inspectorContact, inspectionDate, result:'pass', feedbackNote:'Passed — no punch items.', issues:[] }` and a signed `estimateVersions` entry, so a fully-closed job shows the complete paper trail.
|
||||
- [ ] **Step 5 — Dispute context (scenario #10).** Ensure the disputed project's `verifiedScope`/`estimateVersions` reflect the change-order dispute (a `status:'revised'` version the customer is contesting). (The paused payments + dispute timeline already exist from Plan 3.)
|
||||
- [ ] **Step 6 (verify):** `npm run build` → `✓ built`. `git grep -c "inspections:" -- src/data/mockStore.jsx` ≥ 4; `git grep -c "estimateVersions:" ` ≥ 4; `git grep -c "scopeVariance: true"` ≥ 1. Confirm any `templateId` used exists in the templates store (else omit it — the modal's no-template fallback handles it).
|
||||
- [ ] **Step 7:** commit `git commit -am "feat(data): add inspections, estimate versions, and scope variance to demo scenarios"`.
|
||||
|
||||
## Task 4: Store mutators
|
||||
|
||||
**Files:** `src/data/mockStore.jsx` only. After T3. Add these inline arrows to the context `value` object, next to `updateProject` (follow that exact pattern):
|
||||
|
||||
- [ ] **Step 1:** Add:
|
||||
|
||||
```js
|
||||
// Lifecycle: set stage, log the transition, sync completionPercentage from the stage map.
|
||||
advanceLifecycleStage: (projectId, stage, note, by) => {
|
||||
setProjects(prev => prev.map(p => p.id === projectId ? {
|
||||
...p,
|
||||
lifecycleStage: stage,
|
||||
completionPercentage: stage === 'Work In Progress'
|
||||
? Math.max(p.completionPercentage || 0, 55) // WIP is milestone-driven; never regress within it
|
||||
: STAGE_PROGRESS[stage] ?? p.completionPercentage,
|
||||
stageHistory: [...(p.stageHistory || []), { stage, date: new Date().toISOString().slice(0, 10), by: by || 'Owner', note: note || '' }],
|
||||
} : p));
|
||||
},
|
||||
addInspection: (projectId, inspection) => {
|
||||
setProjects(prev => prev.map(p => p.id === projectId
|
||||
? { ...p, inspections: [...(p.inspections || []), { ...inspection, id: inspection.id || `insp_${Date.now()}` }] }
|
||||
: p));
|
||||
},
|
||||
addInspectionIssue: (projectId, inspectionId, issue) => {
|
||||
setProjects(prev => prev.map(p => p.id === projectId ? {
|
||||
...p,
|
||||
inspections: (p.inspections || []).map(ins => ins.id === inspectionId
|
||||
? { ...ins, issues: [...(ins.issues || []), { ...issue, id: issue.id || `iss_${Date.now()}`, status: 'open' }] }
|
||||
: ins),
|
||||
} : p));
|
||||
},
|
||||
resolveInspectionIssue: (projectId, inspectionId, issueId) => {
|
||||
setProjects(prev => prev.map(p => p.id === projectId ? {
|
||||
...p,
|
||||
inspections: (p.inspections || []).map(ins => ins.id === inspectionId
|
||||
? { ...ins, issues: (ins.issues || []).map(iss => iss.id === issueId ? { ...iss, status: 'fixed' } : iss) }
|
||||
: ins),
|
||||
} : p));
|
||||
},
|
||||
addEstimateVersion: (projectId, version) => {
|
||||
setProjects(prev => prev.map(p => p.id === projectId
|
||||
? { ...p, estimateVersions: [...(p.estimateVersions || []), version] }
|
||||
: p));
|
||||
},
|
||||
```
|
||||
|
||||
- [ ] **Step 2:** At the top of `mockStore.jsx`, import the progress map used by the mutator: `import { STAGE_PROGRESS } from './lifecycle';` (place with the other imports).
|
||||
- [ ] **Step 3 (verify):** `npm run build` → `✓ built`. `git grep -n "advanceLifecycleStage\|addInspection\|addEstimateVersion" -- src/data/mockStore.jsx` shows the new keys on the context value.
|
||||
- [ ] **Step 4:** commit `git commit -am "feat(store): add lifecycle/inspection/estimate-version mutators"`.
|
||||
|
||||
## Task 5: Interactive progression control
|
||||
|
||||
**Files:** `src/pages/owner/OwnerProjectDetail.jsx` only. Phase B.
|
||||
|
||||
- [ ] **Step 1:** Import the lifecycle model + mutator: `import { LIFECYCLE_STAGES, REWORK_STAGE, nextStage, prevStage, stageToPct } from '../../data/lifecycle';` and pull `advanceLifecycleStage` from `useMockStore()`.
|
||||
- [ ] **Step 2:** Replace the static progression block (lines ~555-592) so it renders from `project.lifecycleStage` (fall back to deriving from `completionPercentage` if a project somehow lacks the field). Render the stage label + the progress bar driven by `project.completionPercentage` (which now tracks the stage).
|
||||
- [ ] **Step 3:** Add an owner/admin-only **Advance Stage** control: a button "Advance to: {nextStage(project.lifecycleStage)}" that opens the existing `CreateItemModal` (or a small inline prompt) capturing an optional note, then calls `advanceLifecycleStage(project.id, nextStage(project.lifecycleStage), note, user.name)`. Add a secondary "Regress / Send to Rework" affordance for the failed-inspection case (advance to `REWORK_STAGE` when at a Final Inspection stage). Gate the controls with `['OWNER','ADMIN'].includes(user?.role)`.
|
||||
- [ ] **Step 4:** Show the `stageHistory` as a compact timeline beneath the bar (stage · date · by · note).
|
||||
- [ ] **Step 5 (verify):** `npm run build` → `✓ built`. Dev-run: advancing a stage updates the bar + % live and appends to the history.
|
||||
- [ ] **Step 6:** commit `git commit -am "feat(projects): interactive lifecycle progression control with stage history"`.
|
||||
|
||||
## Task 6: Inspections tab
|
||||
|
||||
**Files:** `src/pages/owner/OwnerProjectDetail.jsx` only. After T5.
|
||||
|
||||
- [ ] **Step 1:** Add `{ id: 'inspections', label: 'Inspections', icon: <an existing lucide icon, e.g. ShieldCheck or ClipboardCheck> }` to the `tabs` array.
|
||||
- [ ] **Step 2:** Add the `activeTab === 'inspections'` render block (follow the existing `NeoCard`/table card patterns). Render `project.inspections` grouped by `kind` (Damage Verification vs Final Inspection). For each inspection show: kind, round, inspector name (+ role for damage, + contact phone/email for final), date, a pass/fail badge, and the `feedbackNote`. Below each, list its `issues[]` with an open/fixed badge.
|
||||
- [ ] **Step 3:** Interactions (owner/admin): **Add Issue** to an inspection (calls `addInspectionIssue`), **Mark Fixed** on an open issue (calls `resolveInspectionIssue`), and **Attach Photo** to an issue using Pattern A — a hidden `<input type="file" accept="image/*">` whose `onChange` does `URL.createObjectURL(file)` and stores `{ id, url, caption }` into the issue's `photos[]` (local component state mirror is fine for session display; persist via a small `updateProject` if you want it sticky). Render thumbnails from `issue.photos[].url`.
|
||||
- [ ] **Step 4:** Add a **"Schedule Re-inspection"** action that appends a new final-inspection round (`round: prevRound+1`, `result` unset/pending) via `addInspection` — demonstrating the rework→re-inspection loop; older rounds stay visible.
|
||||
- [ ] **Step 5 (verify):** `npm run build` → `✓ built`. Dev-run on scenario #8: the failed final inspection shows its open issue; marking it fixed + scheduling re-inspection works; photo thumbnail appears.
|
||||
- [ ] **Step 6:** commit `git commit -am "feat(projects): Inspections tab (damage + final, issues, rework loop, photo upload)"`.
|
||||
|
||||
## Task 7: Estimate Versions tab
|
||||
|
||||
**Files:** `src/pages/owner/OwnerProjectDetail.jsx` only. After T6.
|
||||
|
||||
- [ ] **Step 1:** Add `{ id: 'estimates', label: 'Estimates', icon: FileText }` to the `tabs` array.
|
||||
- [ ] **Step 2:** Import the estimate modal: `import EstimateDetailModal from '../../components/estimates/EstimateDetailModal';` and add a `const [selectedVersion, setSelectedVersion] = useState(null);`.
|
||||
- [ ] **Step 3:** Add the `activeTab === 'estimates'` block: a list of `project.estimateVersions` (version, date, `formatCurrency(total)`, note, and a status badge — green "Signed" for `status:'signed'`). Each row is clickable.
|
||||
- [ ] **Step 4:** On click, open `EstimateDetailModal` with an estimate object built from the version + project: `{ id: \`${project.id}-v${v.version}\`, clientName: project.customerName, address: project.address, date: v.date, status: v.status, total: v.total, templateId: v.templateId, templateName: v.templateName, estimatedBy: <rep name>, roofArea: v.roofArea, notes: v.note }`. If the version has no `templateId`, the modal's no-template fallback renders the stored total — that's expected.
|
||||
- [ ] **Step 5 (verify):** `npm run build` → `✓ built`. Dev-run on scenario #2 and #5: the version history renders with a Signed badge on the final version; clicking opens the full estimate document.
|
||||
- [ ] **Step 6:** commit `git commit -am "feat(projects): Estimate Versions tab (history + full document view)"`.
|
||||
|
||||
## Task 8: Scope-variance badge
|
||||
|
||||
**Files:** `src/pages/owner/OwnerProjectDetail.jsx` only. After T7.
|
||||
|
||||
- [ ] **Step 1:** In the `overview` tab header area (near the project title/metrics), when `project.scopeVariance` is truthy and both `reportedScope` and `verifiedScope` exist, render a visible badge, e.g.:
|
||||
`Reported ~${formatCurrency(project.reportedScope.estTotal)} / ${project.reportedScope.estDays}d → Verified ${formatCurrency(project.verifiedScope.estTotal)} / ${project.verifiedScope.estDays}d`
|
||||
styled as an attention badge (amber), with a short tooltip/subtext summarizing the discrepancy story.
|
||||
- [ ] **Step 2 (verify):** `npm run build` → `✓ built`. Dev-run scenario #2: the badge shows the "~$200 → $15,400" jump.
|
||||
- [ ] **Step 3:** commit `git commit -am "feat(projects): claimed-vs-verified scope-variance badge"`.
|
||||
|
||||
## Task 9: Verify (read-only)
|
||||
|
||||
- [ ] `npm run build` clean.
|
||||
- [ ] `git grep -c "lifecycleStage:" -- src/data/mockStore.jsx` → 29; every project's `completionPercentage` is consistent with `stageToPct(lifecycleStage)` for discrete stages (WIP exempt).
|
||||
- [ ] Scenario coverage: #2 has reportedScope+verifiedScope+scopeVariance+3 estimate versions+a damage inspection; #5 has multi-version estimate history; #8 has a failed final inspection with an open issue; #9 has a passing final inspection; #10's dispute reflected in a contested estimate version.
|
||||
- [ ] Dev-run the four interaction loops: **advance stage** (bar + % + history update); **log + resolve an inspection issue**; **schedule a re-inspection round**; **open an estimate version document**; **scope-variance badge** visible on #2.
|
||||
- [ ] No financial invariants broken (this plan adds lifecycle/inspection/estimate data; it must not change budgetBreakdown/committedCost/actualCost — confirm Plan 1/2 invariants still hold on any project touched).
|
||||
|
||||
---
|
||||
|
||||
## Self-review checklist
|
||||
- [ ] `lifecycle.js` pure, exports stages/progress/helpers; fixture-verified.
|
||||
- [ ] All 29 projects have `lifecycleStage` + `stageHistory`; % consistent with stage.
|
||||
- [ ] Scenario projects carry inspections / estimateVersions / scope-variance per §11.
|
||||
- [ ] 5 mutators added following the established `setProjects` pattern; `STAGE_PROGRESS` imported.
|
||||
- [ ] OwnerProjectDetail: progression control interactive (advance/regress + note + history); Inspections tab (issues + rework + photos); Estimates tab (versions + modal); scope-variance badge.
|
||||
- [ ] Owner/admin gate on mutating controls; build clean; no financial invariant regressions.
|
||||
|
||||
## Notes for Plan 5
|
||||
Plan 5 (subcontractor task state machine, Master §10) is independent of these project-level changes and operates on `MOCK_SUBCONTRACTOR_TASKS` + `SubcontractorTasksPage.jsx` + `TaskViewModal.jsx`. The inspection/rework UI patterns built here (issues list, photo upload, status badges, re-review rounds) are the model to reuse for the sub-task Pre-Work/Post-Work review loop.
|
||||
Reference in New Issue
Block a user