docs: reframe access control to ABAC; align Plan 2 to unified /projects/:id; add Appendix G plan-by-plan roadmap (what/why/how)
This commit is contained in:
@@ -33,6 +33,7 @@
|
|||||||
- Appendix D — Demo walkthrough clickstreams (sales + investor)
|
- Appendix D — Demo walkthrough clickstreams (sales + investor)
|
||||||
- Appendix E — Naming conventions
|
- Appendix E — Naming conventions
|
||||||
- Appendix F — Verification & QA detail
|
- Appendix F — Verification & QA detail
|
||||||
|
- Appendix G — Plan-by-plan roadmap (what · why · how)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -167,13 +168,19 @@ Field normalization map (kanban → project): `estimatedAmount`→`budget`/`appr
|
|||||||
- **Target:** a two-layer profile per person + a self-service User Details page + an owner Employee/Subcontractor Details view + a shared org skill catalog, all feeding LynkDispatch (see §13).
|
- **Target:** a two-layer profile per person + a self-service User Details page + an owner Employee/Subcontractor Details view + a shared org skill catalog, all feeding LynkDispatch (see §13).
|
||||||
- **Why:** richer structured context yields more believable AI dispatch recommendations (investor wow), and the profile depth is what a prospect expects from a mature CRM.
|
- **Why:** richer structured context yields more believable AI dispatch recommendations (investor wow), and the profile depth is what a prospect expects from a mature CRM.
|
||||||
|
|
||||||
### Phase 8 — Access control + unified permission-aware routing *(plan)*
|
### Phase 8 — ABAC access control + unified routing *(plan)*
|
||||||
- **Current:** a real permission framework already exists — `orgPermissions` (role × module → actions), `userPermissionOverrides` (per-person grant/deny), the `usePermissions().can(module, action)` hook (Owner→all; person-override wins; else role default), and `PermissionGate` for component-level gating — all editable in Org Settings. But the **routing layer ignores it**, and routing is **triplicated**: the same feature components are mounted under `/owner/…`, `/admin/…`, and `/emp/fa/…` with only `<ProtectedRoute allowedRoles={[...]}>` (a coarse role check), plus a `basePath` helper recomputing the role prefix everywhere to build links. `PERMISSION_MODULES` covers only 7 features; denied users bounce to `/`; feature segments are terse (`maps`, `kanban`).
|
- **Current:** the existing framework is effectively **RBAC with per-person exceptions** — `orgPermissions` (role × module → actions) + `userPermissionOverrides` (per-person grant/deny) + `usePermissions().can(module, action)` + `PermissionGate`. The **routing layer ignores it** and is **triplicated**: feature components are mounted under `/owner/…`, `/admin/…`, `/emp/fa/…` with only `<ProtectedRoute allowedRoles={[...]}>` and a `basePath` helper; denied users bounce to `/`; segments are terse (`maps`, `kanban`).
|
||||||
- **Target (Option B — unified, permission-guarded):** **one route per feature, no role prefix**, with human-readable names: `/territory-map`, `/pipeline`, `/projects/:id`, `/leads/:id`, `/dispatch`, `/storm-intel`, `/estimates`, `/leaderboard`, `/pro-canvas`, `/org-settings`, `/subcontractor-tasks`, etc. A **permission-aware route guard** decides access: `can(module, 'view')` honoring per-person overrides (role is just one input via the role default). Role-specific **dashboards/landing stay distinct** (`/owner`, `/admin`, `/agent`, `/contractor`, `/vendor`, `/subcontractor` homes, or a `/dashboard` that resolves per role). This **removes the 3× triplication and the `basePath` logic** (links become absolute feature paths). **Expand `PERMISSION_MODULES`** to map every gated route to a module. **Hide nav items** a person can't `view` (in `Layout`); show a **styled "Access Restricted" page** on direct-URL hits to denied routes. Edit/delete stay gated **inside** pages via `PermissionGate` (read-vs-edit). **No seeded override examples** — capability built; exceptions configured live in Org Settings. `/emp/fa` is retired in favor of `/agent` for the landing.
|
- **Target — ABAC (attribute-based access control) + Option-B unified routes:**
|
||||||
- **Why:** the demo's two example asks — "all admins except one get feature X," "all agents except one are read-only" — already map to the data model (role default + a per-person grant/deny override; view-only = read-only). This phase makes that granularity **enforced at the route and reflected in the UI**, and simultaneously cleans the routes to one obvious, human-readable URL per feature — a credible enterprise-RBAC story plus a far tidier app.
|
- **Policy engine over attributes.** Access decisions evaluate **policies** against attributes of: the **subject** (user: `id`, `role`/`orgRole`, `team`, `territory`/regions, `skills`, `employmentType`, seniority), the **resource** (e.g. project: `ownerId`, `assignedUserIds`, `subcontractorIds`, `status`, `region`, `sensitivity`, `value`), the **action** (`view`/`edit`/`delete`/`approve`/`assign`/`export`), and optional **context** (time). Policies are ordered `{ effect: 'permit'|'deny', when: <condition over attributes> }`; **explicit deny wins; default deny.**
|
||||||
- **Decisions:** Option B unified routes (no role prefix) + human-readable names; route refactor **folded into this phase** (reverses the earlier "no consolidation"); permission-aware guard (`can(view)` + overrides); hide-nav **and** restricted page; role-specific dashboards kept; no seeded overrides.
|
- **The existing data folds in:** `role` is one subject attribute; each `userPermissionOverride` becomes a `subject.id`-scoped policy; the role×module matrix compiles to baseline permit policies. So nothing is thrown away — ABAC is a superset.
|
||||||
- **Acceptance:** every feature has exactly one route; the guard blocks a `userPermissionOverride`-denied user at the route (Access Restricted) and hides it in nav; allowed users see the view while edit controls respect `can(module,'edit')`; owner sees everything; no `basePath`/role-prefix duplication remains; every gated route maps to a `PERMISSION_MODULES` entry.
|
- **New API:** `can(action, resource, ctx?)` (resource-aware) replaces the module-only `can(module, action)`; a thin module-level form (`can('view', { type: module })`) backs route guards. `PermissionGate` and a new **route guard** both call it.
|
||||||
- **Note on Plan 2:** Plan 2 (unify) adds interim role-prefixed project routes (`/owner|/admin|/emp-fa/projects/:id`) so won-leads are viewable before Phase 8; Phase 8 then **consolidates all routes** (including projects) to the unified scheme. Accept the minor interim churn to keep each plan shippable.
|
- **Expressive rules this enables (examples):** sales rep may `edit` a project only if `subject.id ∈ resource.assignedUserIds`; a subcontractor may `view` only projects where `subject.id ∈ resource.subcontractorIds` (and only their slice); a canvasser may `edit` leads they sourced and `view` others in their `territory`; "all admins except one get feature X" = role-permit + a `deny when subject.id == that admin`.
|
||||||
|
- **Unified routes (Option B):** one human-readable route per feature, no role prefix — `/territory-map`, `/pipeline`, `/projects/:id`, `/leads/:id`, `/dispatch`, `/storm-intel`, `/estimates`, `/leaderboard`, `/pro-canvas`, `/org-settings`, `/subcontractor-tasks`. The route guard evaluates the policy for that route's resource-type + `view`. Role-specific **dashboards/landing stay distinct** (`/owner`, `/admin`, `/agent`, `/contractor`, `/vendor`, `/subcontractor`). Removes the 3× triplication and `basePath`.
|
||||||
|
- **UX:** nav hides what a subject can't `view`; direct-URL hits to denied routes show a styled **Access Restricted** page; edit/delete gated inside pages via `PermissionGate`. **No seeded override examples** — capability built, configured live in Org Settings (which becomes the policy/attribute editor, ideally keeping the friendly matrix as a front-end that compiles to policies).
|
||||||
|
- **Why:** ABAC is a materially more impressive and realistic enterprise-access story than role gates — it expresses ownership/assignment/territory rules RBAC can't — while still cleanly covering the simple "deny one admin / one agent read-only" asks. Folded with the route cleanup, it also gives one obvious URL per feature.
|
||||||
|
- **Decisions:** ABAC policy engine (attributes + ordered permit/deny policies, deny-wins, default-deny) generalizing the current role/override data; `can(action, resource, ctx)` API; Option-B unified routes folded in; hide-nav + Access Restricted page; role dashboards kept; no seeded overrides.
|
||||||
|
- **Acceptance:** access decisions consider resource attributes (e.g. a rep blocked from editing a project they're not on; a sub sees only their projects); the route guard blocks denied subjects (Access Restricted) and nav hides denied routes; owner sees all; one route per feature; no `basePath`/role-prefix duplication; every gated route maps to a resource-type/module.
|
||||||
|
- **Note on Plan 2:** Plan 2 lands the final unified `/projects/:id` route with a **coarse interim `allowedRoles` guard**; Phase 8 swaps that for the ABAC policy guard and consolidates the remaining feature routes (removing the legacy `/owner/projects/:id`).
|
||||||
|
|
||||||
## 9. The project lifecycle state machine
|
## 9. The project lifecycle state machine
|
||||||
|
|
||||||
@@ -250,7 +257,7 @@ These hold across all phases and are the acceptance backbone:
|
|||||||
|
|
||||||
**Current (relevant routes):** `/owner/projects` + `/owner/projects/:projectId` (OwnerProjectDetail, owner-only); `/owner|admin|emp-fa/kanban` (KanbanPage); `/owner|admin|emp-fa/leads/:leadId` (LeadProjectPage); `/emp/fa/leads`, `/emp/fa/leads/new`. Kanban cards open a side **drawer**; the drawer's "More Details" is the only kanban→detail navigation, and it always goes to the lead page.
|
**Current (relevant routes):** `/owner/projects` + `/owner/projects/:projectId` (OwnerProjectDetail, owner-only); `/owner|admin|emp-fa/kanban` (KanbanPage); `/owner|admin|emp-fa/leads/:leadId` (LeadProjectPage); `/emp/fa/leads`, `/emp/fa/leads/new`. Kanban cards open a side **drawer**; the drawer's "More Details" is the only kanban→detail navigation, and it always goes to the lead page.
|
||||||
|
|
||||||
**Plan-2 interim target:** add `/admin/projects/:projectId` and `/emp/fa/projects/:projectId` → the same rich detail page, viewable by owner/admin/field-agent. Won leads' "More Details" routes to `/…/projects/PRJ-2026-###`; pre-sale leads keep `/…/leads/:id`. Project ids become `PRJ-2026-###` throughout. (These role-prefixed routes are interim; Phase 8 consolidates them.)
|
**Plan-2 target:** introduce the **final unified `/projects/:id` route** (Option B) now, gated by a coarse interim `allowedRoles` (OWNER/ADMIN/FIELD_AGENT). Won leads' "More Details" routes to `/projects/PRJ-2026-###`; pre-sale leads keep `/…/leads/:id` (role-prefixed until Phase 8). Project ids become `PRJ-2026-###` throughout. The legacy `/owner/projects/:id` stays until Phase 8 removes the duplicate. (No interim role-prefixed project routes — Plan 2 lands the real URL; Phase 8 only swaps the coarse guard for the ABAC policy guard and consolidates the *other* feature routes.)
|
||||||
|
|
||||||
**Final target (Phase 8 — Option B, unified):** **one human-readable route per feature, no role prefix**, access decided by the permission guard:
|
**Final target (Phase 8 — Option B, unified):** **one human-readable route per feature, no role prefix**, access decided by the permission guard:
|
||||||
|
|
||||||
@@ -307,6 +314,8 @@ Role-specific **dashboards/landing stay distinct**: `/owner`, `/admin`, `/agent`
|
|||||||
- **Scope variance** — the gap between a customer's reported claim and the inspection-verified scope.
|
- **Scope variance** — the gap between a customer's reported claim and the inspection-verified scope.
|
||||||
- **LynkDispatch** — the AI rep-recommendation module.
|
- **LynkDispatch** — the AI rep-recommendation module.
|
||||||
- **ProCanvas** — the gamified canvassing module (XP/levels/badges).
|
- **ProCanvas** — the gamified canvassing module (XP/levels/badges).
|
||||||
|
- **ABAC** — Attribute-Based Access Control: access decided by policies over subject/resource/action/context attributes (Phase 8), generalizing the prior role-based model.
|
||||||
|
- **Policy** — an ordered `{ effect: permit|deny, when }` rule; explicit deny wins, default deny.
|
||||||
|
|
||||||
## 20. Document index
|
## 20. Document index
|
||||||
|
|
||||||
@@ -382,10 +391,14 @@ Field-level reference for the core entities. "Phase" = where the field is introd
|
|||||||
- **`DISPATCH_REPS[]`** (map to canonical people in Phase 7): `{ id, repId→personId, initials, name, status:'available'|'en_route'|'busy', rating, closeRate, todayAppointments, maxDaily, performanceScore }`.
|
- **`DISPATCH_REPS[]`** (map to canonical people in Phase 7): `{ id, repId→personId, initials, name, status:'available'|'en_route'|'busy', rating, closeRate, todayAppointments, maxDaily, performanceScore }`.
|
||||||
- **`DISPATCH_RECOMMENDATIONS[leadId][]`:** `{ repId, score, factors{proximity,driveTime,calendarFit,weather,skillMatch}, reasons[], stormReasons[], aiInsight, distanceMi, slotStart, slotEnd, conflictFlag }`. Phase 7 authors `skillMatch`/`reasons`/`aiInsight` to cite real profile facts.
|
- **`DISPATCH_RECOMMENDATIONS[leadId][]`:** `{ repId, score, factors{proximity,driveTime,calendarFit,weather,skillMatch}, reasons[], stormReasons[], aiInsight, distanceMi, slotStart, slotEnd, conflictFlag }`. Phase 7 authors `skillMatch`/`reasons`/`aiInsight` to cite real profile facts.
|
||||||
|
|
||||||
### A.8 Access control — Org Settings
|
### A.8 Access control — ABAC (Phase 8)
|
||||||
- `orgPermissions[moduleKey][roleKey] = [actions]` (role defaults).
|
ABAC = decisions from **policies over attributes**. The existing RBAC data folds in as inputs.
|
||||||
- `userPermissionOverrides[] = { userId, moduleKey, action, effect:'grant'|'deny' }` (per-person wins over role default).
|
- **Subject attributes** (user): `id`, `role`/`orgRole`, `team`, `territory`/regions, `skills`, `employmentType`, seniority.
|
||||||
- `PERMISSION_MODULES[moduleKey] = { label, actions[] }` — expanded in Phase 8 to cover every gated route.
|
- **Resource attributes** (e.g. project): `ownerId`, `assignedUserIds`, `subcontractorIds`, `status`, `region`, `sensitivity`, `value`, `type`.
|
||||||
|
- **Action:** `view`/`edit`/`delete`/`approve`/`assign`/`export`. **Context (optional):** time.
|
||||||
|
- **Policy:** ordered `{ effect:'permit'|'deny', when:<condition over attributes> }`; **explicit deny wins; default deny.**
|
||||||
|
- **Engine API:** `can(action, resource, ctx?)` (resource-aware) + module-level form `can('view', { type: module })` for route guards.
|
||||||
|
- **Folded-in legacy data:** `role` → a subject attribute; `orgPermissions[module][role]` → baseline permit policies; each `userPermissionOverrides` entry → a `subject.id`-scoped permit/deny policy. Org Settings becomes the policy/attribute editor (the friendly role×module matrix may remain as a UI that compiles to policies).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -470,3 +483,49 @@ No unit-test framework exists, so verification is layered:
|
|||||||
4. **Cross-login coherence:** the same job viewed as owner vs subcontractor agrees (Phase 6); no "Unknown" person labels.
|
4. **Cross-login coherence:** the same job viewed as owner vs subcontractor agrees (Phase 6); no "Unknown" person labels.
|
||||||
5. **Dev-run spot checks:** Leads page non-empty; each role login lands correctly; denied routes show Access Restricted (Phase 8); lifecycle progress % matches stage (Phase 4).
|
5. **Dev-run spot checks:** Leads page non-empty; each role login lands correctly; denied routes show Access Restricted (Phase 8); lifecycle progress % matches stage (Phase 4).
|
||||||
6. **Final whole-implementation review** per plan, then `superpowers:finishing-a-development-branch`.
|
6. **Final whole-implementation review** per plan, then `superpowers:finishing-a-development-branch`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Appendix G — Plan-by-plan roadmap (what · why · how)
|
||||||
|
|
||||||
|
Each plan = one phase, produces working/verifiable software on its own, and is executed via the subagent protocol. "How" lists the approach and the main tasks; full step-by-step lives in each plan file.
|
||||||
|
|
||||||
|
### Plan 1 — Data Foundation ✅ DONE
|
||||||
|
- **What:** canonical identity + `resolvePerson`; Texan roster + `LUP-####` empIds; reconciling project financials + 6 missing breakdowns; seeded Leads page; `sub_002–005` logins; address-matched subcontractor tasks + On-Hold styling; kanban lead values + storm flags; per-role login picker.
|
||||||
|
- **Why:** nothing else is trustworthy until identities are consistent, money ties out, and no view is empty — this is the bedrock the demo and every later phase stand on.
|
||||||
|
- **How:** 11 sequential subagent tasks on `mockStore.jsx` (one writer at a time), each with a `npm run build` gate + grep/reconciliation checks; an independent financial-reconciliation review; final whole-implementation review. *(Delivered in 14 commits, APPROVED.)*
|
||||||
|
|
||||||
|
### Plan 2 — Unify Lead↔Project + Routing (written; next to execute)
|
||||||
|
- **What:** migrate `proj_###`→`PRJ-2026-###` (+ inbound FKs); convert the 16 won kanban leads into full project records (normalize fields, back-fill, link both ways); make the rich detail page viewable by owner/admin/field-agent; introduce the unified `/projects/:id` route; relink the kanban drawer + project list.
|
||||||
|
- **Why:** a won lead and a project are the same job — collapsing the two parallel data models into one entity + one page is the prerequisite that makes the rich detail, lifecycle, estimates, analytics, and subcontractor parity apply everywhere for free.
|
||||||
|
- **How:** Phase A sequential on `mockStore.jsx` (T1 id migration → T2 conversion); Phase B **parallel** across 4 disjoint files (detail-page access, route, kanban relink, list relink); Phase C verify (reviewer re-checks financial invariants + nav). Field-normalization map + back-fill rules in the plan; coarse `allowedRoles` guard interim (ABAC arrives in Plan 8).
|
||||||
|
|
||||||
|
### Plan 3 — Kanban/pipeline depth + analytics
|
||||||
|
- **What:** realistic pre-sale lead details (customers, $ values, notes, real dates — kill the sequential fake phones); the **10 concrete demo-case scenarios** (Appendix B) authored as coherent threads; a working pipeline $ total + conversion funnel; storm attribution on leads; and `src/data/selectors.js` deriving dashboard analytics (pipeline value/funnel/win-rate, revenue/gross/net profit, commission by rep, subcontractor performance, storm attribution) wired into the owner/admin dashboards.
|
||||||
|
- **Why:** the pipeline must read like a busy real company (investor breadth) and let a salesperson demo any scenario (sales depth); computed selectors guarantee dashboards never contradict the records a prospect drills into.
|
||||||
|
- **How:** data-authoring subagent tasks on `mockStore.jsx` (sequential) for lead/scenario data; then a **new `selectors.js`** (its own file → parallelizable) of pure functions over the store arrays; then wire dashboards (`OwnerSnapshot`, admin dashboard) to the selectors (per-dashboard files → parallel). Verify: a reviewer confirms each dashboard KPI equals the sum of the underlying records; pipeline total equals Σ lead values.
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
|
### 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).
|
||||||
|
- **Why:** mirrors the real field workflow and gives the subcontractor an interactive inbox; the review/rework loop is a credible operational story feeding owner-side visibility.
|
||||||
|
- **How:** extend `MOCK_SUBCONTRACTOR_TASKS` + status constants + mutators in `mockStore.jsx` (sequential); update the owner `SubcontractorTasksPage` (columns) and the task modal/detail components (parallel — different files). Verify: stages advance with notes in both owner and subcontractor views.
|
||||||
|
|
||||||
|
### Plan 6 — Subcontractor portal data + project parity
|
||||||
|
- **What:** realistic data on `/subcontractor/*`; a subcontractor viewing their task sees the **same project** the owner sees (permitted subset — address, scope, schedule, their slice of budget/payments).
|
||||||
|
- **Why:** coherence across logins is the demo's credibility test — the same job must look like the same job to owner and subcontractor.
|
||||||
|
- **How:** reuse the unified project entity (Plan 2) + the upcoming ABAC visibility (Plan 8) to render a scoped project view in the subcontractor pages; seed/enrich subcontractor-facing data in `mockStore.jsx`; build the portal views (component files → parallelizable). Verify: a sub's task links to the real project; the owner and sub views agree.
|
||||||
|
|
||||||
|
### Plan 7 — Profiles, skill catalog & AI dispatch context
|
||||||
|
- **What:** two-layer profiles (self + company) per person; the self-service User Details page (all roles); the owner Employee/Subcontractor Details view; the org skill catalog; ProCanvas achievements surfaced; `DISPATCH_REPS` mapped to canonical people; dispatch `reasons`/`aiInsight`/`skillMatch` authored to cite real profile facts.
|
||||||
|
- **Why:** richer structured context yields more believable AI dispatch (investor wow), and profile depth is what a prospect expects from a mature CRM.
|
||||||
|
- **How:** add `profile`/`skillCatalog`/`skillScores` to people + dispatch-rep↔person mapping in `mockStore.jsx` (sequential); build the User Details page + owner Details view (new component files → parallel); rewrite the pre-authored `DISPATCH_RECOMMENDATIONS` to reference profile facts. Verify: a recommendation's rationale cites a real skill/equipment value from the cited rep's profile.
|
||||||
|
|
||||||
|
### Plan 8 — ABAC access control + unified routing
|
||||||
|
- **What:** an attribute-based policy engine (subject/resource/action/context attributes; ordered permit/deny; deny-wins; default-deny) generalizing the role/override data; `can(action, resource, ctx)` API; a permission-aware route guard; the Option-B unified route scheme (one human-readable route per feature, role landings only); nav hiding + styled Access Restricted page; Org Settings as policy/attribute editor.
|
||||||
|
- **Why:** ABAC expresses ownership/assignment/territory rules RBAC can't (a rep edits only their projects; a sub sees only theirs) while still covering the simple "deny one admin / one agent read-only" asks — a credible enterprise-access story; folded with the route cleanup it also yields one obvious URL per feature.
|
||||||
|
- **How:** define attributes + a policy evaluator in `src/utils/permissions.js` (+ a policies data source); extend `usePermissions` to resource-aware `can`; add a route-guard component; **consolidate `App.jsx`** to unified routes and strip `basePath` across components (the largest cross-file change — sequence carefully, lane-map per file); add the Access Restricted page; make `Layout` hide denied nav; update Org Settings. Verify: resource-aware decisions (rep blocked from a project they're not on; sub sees only their projects); denied direct-URL → Access Restricted; one route per feature; no `basePath` left.
|
||||||
|
|||||||
@@ -76,19 +76,21 @@
|
|||||||
- [ ] **Step 2:** `npm run build` → `✓ built`.
|
- [ ] **Step 2:** `npm run build` → `✓ built`.
|
||||||
- [ ] **Step 3:** commit `git commit -m "fix(projects): allow admin/field-agent to open project detail, not just owner"`.
|
- [ ] **Step 3:** commit `git commit -m "fix(projects): allow admin/field-agent to open project detail, not just owner"`.
|
||||||
|
|
||||||
## Task 4: Add `/…/projects/:projectId` routes for all three role prefixes
|
## Task 4: Add the unified `/projects/:projectId` route (Option B)
|
||||||
|
|
||||||
**Files:** `src/App.jsx` only. **Phase B (parallel).**
|
**Files:** `src/App.jsx` only. **Phase B (parallel).**
|
||||||
|
|
||||||
- [ ] **Step 1:** Add routes rendering `OwnerProjectDetail`: `/admin/projects/:projectId` (ADMIN, OWNER) and `/emp/fa/projects/:projectId` (FIELD_AGENT, ADMIN, OWNER), mirroring the existing `/owner/projects/:projectId`. (Optionally also `/admin/projects` and `/emp/fa/projects` → `OwnerProjectList`.)
|
We adopt the final Option-B scheme for the projects route now — a single, role-prefix-free URL — so won leads and projects share one page. The **attribute-based access guard arrives in Phase 8**; for the interim, gate with a coarse `allowedRoles`.
|
||||||
|
|
||||||
|
- [ ] **Step 1:** Add a route `/projects/:projectId` → `OwnerProjectDetail`, gated `allowedRoles={['OWNER','ADMIN','FIELD_AGENT']}` (interim coarse guard — Phase 8 replaces it with the ABAC policy guard). Leave the existing `/owner/projects/:projectId` route in place for now (Phase 8 removes the duplicate). Do NOT add `/admin/projects` / `/emp/fa/projects` role-prefixed copies.
|
||||||
- [ ] **Step 2:** `npm run build` → `✓ built`.
|
- [ ] **Step 2:** `npm run build` → `✓ built`.
|
||||||
- [ ] **Step 3:** commit `git commit -m "feat(routes): add /admin and /emp/fa projects routes for the unified detail page"`.
|
- [ ] **Step 3:** commit `git commit -m "feat(routes): add unified /projects/:id route for the shared detail page"`.
|
||||||
|
|
||||||
## Task 5: Relink the kanban drawer so won leads open the project page
|
## Task 5: Relink the kanban drawer so won leads open the project page
|
||||||
|
|
||||||
**Files:** `src/components/kanban/LeadInfoDrawer.jsx`, `src/pages/KanbanPage.jsx`. **Phase B (parallel).**
|
**Files:** `src/components/kanban/LeadInfoDrawer.jsx`, `src/pages/KanbanPage.jsx`. **Phase B (parallel).**
|
||||||
|
|
||||||
- [ ] **Step 1:** In `LeadInfoDrawer.jsx` (~line 302) the "More Details" button does `navigate(\`${basePath}/leads/${lead.id}\`)`. Change it so: if the lead has a `projectId` (won), navigate to `\`${basePath}/projects/${lead.projectId}\``; else keep `\`${basePath}/leads/${lead.id}\``.
|
- [ ] **Step 1:** In `LeadInfoDrawer.jsx` (~line 302) the "More Details" button does `navigate(\`${basePath}/leads/${lead.id}\`)`. Change it so: if the lead has a `projectId` (won), navigate to the unified `\`/projects/${lead.projectId}\`` (no basePath); else keep `\`${basePath}/leads/${lead.id}\`` (pre-sale leads stay role-prefixed until Phase 8).
|
||||||
- [ ] **Step 2:** Check `KanbanPage.jsx` for any other lead→detail navigation and apply the same conditional.
|
- [ ] **Step 2:** Check `KanbanPage.jsx` for any other lead→detail navigation and apply the same conditional.
|
||||||
- [ ] **Step 3:** `npm run build` → `✓ built`.
|
- [ ] **Step 3:** `npm run build` → `✓ built`.
|
||||||
- [ ] **Step 4:** commit `git commit -m "feat(kanban): won leads open the unified project page; pre-sale leads stay on lead page"`.
|
- [ ] **Step 4:** commit `git commit -m "feat(kanban): won leads open the unified project page; pre-sale leads stay on lead page"`.
|
||||||
@@ -97,7 +99,7 @@
|
|||||||
|
|
||||||
**Files:** `src/pages/owner/OwnerProjectList.jsx` only. **Phase B (parallel).**
|
**Files:** `src/pages/owner/OwnerProjectList.jsx` only. **Phase B (parallel).**
|
||||||
|
|
||||||
- [ ] **Step 1:** This page lists both construction projects (→ `/owner/projects/:id`) and pipeline leads (→ `/owner/leads/:id`, lines ~499/565). For pipeline leads that now have a `projectId` (won), link to `/owner/projects/:projectId` instead. Pre-sale leads keep `/owner/leads/:id`.
|
- [ ] **Step 1:** This page lists both construction projects and pipeline leads (links ~lines 280/366 and 499/565). Point ALL project links AND won pipeline leads (those with a `projectId`) at the unified `/projects/:id`. Pre-sale leads keep `/owner/leads/:id`.
|
||||||
- [ ] **Step 2:** `npm run build` → `✓ built`.
|
- [ ] **Step 2:** `npm run build` → `✓ built`.
|
||||||
- [ ] **Step 3:** commit `git commit -m "feat(projects): project list links won pipeline jobs to the unified project page"`.
|
- [ ] **Step 3:** commit `git commit -m "feat(projects): project list links won pipeline jobs to the unified project page"`.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user