Files
LynkedUpPro_CRM/docs/superpowers/plans/2026-05-29-demo-data-foundation.md

371 lines
21 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Demo Data Foundation Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Make the mock data internally coherent — one canonical identity scheme, reconciling financials, and no empty/broken views — as the foundation for the larger demo build.
**Architecture:** All edits are to `src/data/mockStore.jsx` (a ~6,950-line mock store) plus a few consuming components. Changes are surgical and additive. A new `resolvePerson(id)` selector gives every module one consistent way to look up people. Verification is by `npm run build` + `grep` reconciliation + dev-run, since the project has no unit-test harness.
**Tech Stack:** React 18, Vite 7, mock React-context store (`useMockStore`), pnpm.
**Spec:** `docs/superpowers/specs/2026-05-29-demo-data-coherence-design.md` (Phases 13).
**Scope of this plan:** Phase 1 (canonical identity), Phase 2 (financial coherence), Phase 3 (feature-breaking fixes). Phases 410 are separate plans.
---
## Canonical roster (single source of truth — used throughout)
Canonical id scheme = `MOCK_USERS`. Real Texan names. Unified `LUP-####` empIds. Shared password `password`.
| id | Role | Name | empId | Login type | Login id | Old id(s) | Old name |
|----|------|------|-------|-----------|----------|-----------|----------|
| own_001 | OWNER | Justin Johnson | LUP-1001 | owner | justin | `owner_001` | — (email → `justin@lynkeduppro.com`) |
| own_002 | OWNER | Diana Reeves | LUP-1002 | owner | diana | `owner_002` | "Maria Garcia" |
| a1 | ADMIN | Wade Hollis | LUP-1010 | employee | LUP-1010 | `ADM01` | "Adam Admin" / "Admin One" |
| a2 | ADMIN | Darlene Brooks | LUP-1011 | employee | LUP-1011 | `ADM02` | "Amanda Manager" / "Admin Two" |
| a3 | ADMIN | Roy Schaefer | LUP-1012 | employee | LUP-1012 | `ADM03` | "Arthur Director" / "Admin Three" |
| p1 | SALES_REP | Jesus Gonzales | LUP-1030 | employee | LUP-1030 | — | — (keep) |
| p2 | SALES_REP | Sarah Calloway | LUP-1031 | employee | LUP-1031 | — | "Sarah Sales" |
| e1 | FIELD_AGENT | Cody Tatum | LUP-1040 | employee | LUP-1040 | `FA001` | "Frank Agent" |
| e2 | FIELD_AGENT | Hannah Reyes | LUP-1041 | employee | LUP-1041 | `FA002` | "Fiona Field" |
| e3 | FIELD_AGENT | Travis Boone | LUP-1042 | employee | LUP-1042 | `FA003` | "Fred Flyer" |
| e4 | FIELD_AGENT | Shelby Greer | LUP-1043 | employee | LUP-1043 | `FA004` | "Felicity Fast" |
| e5 | FIELD_AGENT | Dalton Pruitt | LUP-1044 | employee | LUP-1044 | `FA005` | "Felix Fixer" |
| con_001 | CONTRACTOR | Mike Brennan | LUP-3001 | contractor | mike | — | "Mike Contractor" |
| sub_001 | SUBCONTRACTOR | Carlos Mendoza | LUP-2001 | subcontractor | carlos | — | "Carlos Subcontractor" |
| sub_002 | SUBCONTRACTOR | Maya Patel | LUP-2002 | subcontractor | maya | — | (keep) |
| sub_003 | SUBCONTRACTOR | Dwayne Boudreaux | LUP-2003 | subcontractor | dwayne | — | (current sub_003 name) |
| sub_004 | SUBCONTRACTOR | Jennifer Castillo | LUP-2004 | subcontractor | jennifer | — | (current sub_004 name) |
| sub_005 | SUBCONTRACTOR | Greg Alston | LUP-2005 | subcontractor | greg | — | (keep) |
Org role taxonomy (`orgMembers[].roleKey`) stays; only the `userId` it points to changes. Email rule: use `@lynkeduppro.com` consistently.
**Execution order** (overrides plain numeric order; see execution protocol): 1 → 2 → **R (roster rename, below)** → 3 → 4 → 5 → 6 → 7 → 8 → 9 → **L (login picker, below)**.
---
## File structure
- **Modify:** `src/data/mockStore.jsx` — identity remap, `resolvePerson`, financial fixes, breakdowns, `leads` seed, sub logins, task↔project fixes, kanban lead values, `DEMO_TODAY`.
- **Modify:** `src/pages/owner/SubcontractorTasksPage.jsx`, `src/components/subcontractor/TaskViewModal.jsx` — add `On Hold` styling.
- **Create:** none in this plan.
---
## Task 1: Add `DEMO_TODAY` constant
**Files:**
- Modify: `src/data/mockStore.jsx` (near top, after imports)
- Modify: `src/components/kanban/KanbanCard.jsx:14` (replace hardcoded date)
- [ ] **Step 1: Add the constant** near the top of `mockStore.jsx` (after the import lines):
```jsx
// Fixed "today" for the demo so seeded dates never go stale or land in the future.
export const DEMO_TODAY = new Date('2026-05-29T12:00:00');
```
- [ ] **Step 2: Use it in KanbanCard.** In `src/components/kanban/KanbanCard.jsx` replace the hardcoded `new Date('2026-04-07')` (line ~14) with an import + use:
```jsx
import { DEMO_TODAY } from '../../data/mockStore';
// ...
const NOW = DEMO_TODAY; // was: new Date('2026-04-07')
```
- [ ] **Step 3: Verify build.** Run: `npm run build` — Expected: `✓ built` with no errors.
- [ ] **Step 4: Commit.**
```bash
git add src/data/mockStore.jsx src/components/kanban/KanbanCard.jsx
git commit -m "chore(data): add DEMO_TODAY anchor and use it for kanban day-counter"
```
---
## Task 2: Add `resolvePerson` selector
**Files:**
- Modify: `src/data/mockStore.jsx` — add a memoized people index + `resolvePerson` exposed on the store context value.
- [ ] **Step 1: Build a merged people index** inside the provider (after the people collections are defined, before the `value={{...}}`):
```jsx
// One lookup for any person across users/personnel/owners/subcontractors.
const peopleIndex = useMemo(() => {
const idx = {};
const add = (p) => { if (p?.id && !idx[p.id]) idx[p.id] = p; };
[...(users || []), ...(personnel || []), ...(owners || []),
...(subcontractors || [])].forEach(add);
return idx;
}, [users, personnel, owners, subcontractors]);
const resolvePerson = (id) => {
const p = peopleIndex[id];
if (!p) return { id, name: 'Unknown', email: '', role: '' };
return { id: p.id, name: p.name || p.fullName || 'Unknown', email: p.email || p.emailAddress || '', role: p.role || '' };
};
```
- [ ] **Step 2: Expose it** on the context value object (add `resolvePerson,` to the `value={{ ... }}`).
- [ ] **Step 3: Verify build.** Run: `npm run build` — Expected: `✓ built`.
- [ ] **Step 4: Commit.**
```bash
git add src/data/mockStore.jsx
git commit -m "feat(data): add resolvePerson selector over a merged people index"
```
---
## Task 3: Remap `orgMembers` to canonical ids + fix names
**Files:**
- Modify: `src/data/mockStore.jsx` — the `orgMembers` useState array.
- [ ] **Step 1: Apply the ID + name remap** in `orgMembers` using the Canonical ID reference table:
- `userId: 'owner_001'``'own_001'` (name "Justin Johnson", email `justin@lynkeduppro.com`)
- `userId: 'owner_002'``'own_002'`, **name "Maria Garcia" → "Diana Reeves"**, email → Diana's canonical email from `MOCK_USERS`
- `userId: 'ADM01'/'ADM02'/'ADM03'``'a1'/'a2'/'a3'`, **names "Admin One/Two/Three" → "Adam Admin"/"Amanda Manager"/"Arthur Director"**
- `userId: 'FA001'..'FA005'``'e1'..'e5'` (names already match)
- [ ] **Step 2: Verify no stale ids remain.** Run:
```bash
git grep -nE "owner_00[12]|ADM0[123]|FA00[12345]" -- src/data/mockStore.jsx
```
Expected: **no matches in `orgMembers`** (matches may still exist in subcontractor tasks — fixed in Task 4). Confirm none are in the `orgMembers` block.
- [ ] **Step 3: Verify build.** Run: `npm run build` — Expected: `✓ built`.
- [ ] **Step 4: Commit.**
```bash
git add src/data/mockStore.jsx
git commit -m "fix(data): remap orgMembers to canonical ids; reconcile Owner #2 + admin names"
```
---
## Task 4: Remap subcontractor-task person references
**Files:**
- Modify: `src/data/mockStore.jsx``MOCK_SUBCONTRACTOR_TASKS` (and `MOCK_NOTIFICATIONS` actor ids).
- [ ] **Step 1: Replace ids** in every task's `assignedBy`, `actorId`, `senderId`, and status/thread rows:
- `'owner_001'``'own_001'`
- `'ADM01'``'a1'`
- Update the denormalized `assignedByName`/`actorName`/`senderName` so "Admin One" → "Adam Admin" and any owner name → "Justin Johnson".
- [ ] **Step 2: Verify no stale ids remain anywhere.** Run:
```bash
git grep -nE "owner_00[12]|\bADM0[123]\b|FA00[12345]" -- src/data/mockStore.jsx
```
Expected: **no matches** (all remapped).
- [ ] **Step 3: Verify build.** Run: `npm run build` — Expected: `✓ built`.
- [ ] **Step 4: Commit.**
```bash
git add src/data/mockStore.jsx
git commit -m "fix(data): remap subcontractor task/notification actor ids to canonical scheme"
```
---
## Task 5: Financial coherence — fix the flagged projects
**Files:**
- Modify: `src/data/mockStore.jsx` — the `projects` array.
Reference (from audit): rules = breakdown `allocated`→budget, `committed`→committedCost, `actual`→actualCost; `committed ≥ actual`; `variancePercent = (actualCost budget)/budget × 100`.
- [ ] **Step 1: proj_005** — set top-level `committedCost: 28850` (matches the breakdown's committed sum). Leave actualCost 14200 (breakdown actual sum). Recompute `variancePercent = (1420028000)/28000×100 = -49.3`.
- [ ] **Step 2: proj_008** — reduce paid invoices so the paid total ≤ `actualCost` (44500). Adjust the invoice `amount`s / `status` so `Σ paid ≤ 44500` and `Σ paid ≤ committedCost (52000)`.
- [ ] **Step 3: proj_002 & proj_010** (completed) — reduce paid invoices so `Σ paid ≤ actualCost` (6200 and 23800 respectively).
- [ ] **Step 4: proj_004** — in `budgetBreakdown`, fix the Contingency line so `committed ≥ actual` (raise committed to ≥ 11500, keeping the over-budget total intact).
- [ ] **Step 5: Add `budgetBreakdown`** to proj_006, 008, 009, 010, 011, 012. Each: 47 line items where `allocated` sums to the project `budget`, `committed` sums to `committedCost`, `actual` sums to `actualCost`, and `committed ≥ actual` per line. Use realistic vendor/category names matching the project type (e.g. for a roof job: "ABC Supply Co. — Materials", "Texas Roofing Crew A — Labor", "City of Plano — Permits").
- [ ] **Step 6: Verify variance signs** render correctly — over-budget shows `+`, under shows ``. (Display logic already does this; just confirm the stored `variancePercent` sign matches `(actualCostbudget)`.)
- [ ] **Step 7: Verify build.** Run: `npm run build` — Expected: `✓ built`.
- [ ] **Step 8: Reconciliation spot-check.** For each project with a `budgetBreakdown`, confirm the three column sums equal `committedCost`/`actualCost` and `allocated``budget`. (Manual read or a scratch node script; do not commit the script.)
- [ ] **Step 9: Commit.**
```bash
git add src/data/mockStore.jsx
git commit -m "fix(data): reconcile project financials and add missing budget breakdowns"
```
---
## Task 6: Seed the Leads List page
**Files:**
- Modify: `src/data/mockStore.jsx` — the `leads` useState (currently `useState([])`).
- [ ] **Step 1: Inspect the shape** the page reads. Open `src/pages/LeadsListPage.jsx` and note the fields used (`firstName`/`lastName`, `phones[]`, `urgency`, `status`, `leadSource`, `canvasserName`, `createdByName`, `createdAt`, `stormSource`, `address`, etc.).
- [ ] **Step 2: Seed ~2835 records** by initializing `leads` from the existing storm-attribution leads (`MOCK_STORM_ATTRIBUTION_LEADS`), mapped into the LeadsListPage shape. Use canonical `canvasserName`/`createdByName` (Frank Agent, Fiona Field, … — names that exist in `MOCK_USERS`). Spread `createdAt` across recent dates relative to `DEMO_TODAY`; set `stormSource: true` on storm-sourced ones; vary `urgency`/`status`.
```jsx
const [leads, setLeads] = useState(() => seedLeadsFromAttribution());
```
(Define `seedLeadsFromAttribution()` above the provider; map each SAL-* record to the page shape.)
- [ ] **Step 3: Verify in dev.** Run dev server, open `/emp/fa/leads` (or owner equivalent). Expected: the list is populated (no "No Leads Yet" empty state).
- [ ] **Step 4: Verify build.** Run: `npm run build` — Expected: `✓ built`.
- [ ] **Step 5: Commit.**
```bash
git add src/data/mockStore.jsx
git commit -m "fix(data): seed leads list so the Leads page is populated on load"
```
---
## Task 7: Add subcontractor logins (sub_002005)
**Files:**
- Modify: `src/data/mockStore.jsx``MOCK_USERS`.
- [ ] **Step 1: Add four login user records** to `MOCK_USERS` for `sub_002``sub_005`, mirroring the `sub_001` (Carlos Mendoza) record shape (`id`, `name`, `email`, `empId`, `password: 'password'`, `role: 'SUBCONTRACTOR'`, type `subcontractor`). Use the **canonical roster** names + empIds (Maya Patel/LUP-2002, Dwayne Boudreaux/LUP-2003, Jennifer Castillo/LUP-2004, Greg Alston/LUP-2005) and usernames (maya/dwayne/jennifer/greg) so ids/names match across collections and the login picker works.
- [ ] **Step 2: Verify in dev.** Log in as `sub_002`'s email / `password`. Expected: the subcontractor dashboard loads and shows that sub's tasks/notifications (Maya's rich set is now reachable).
- [ ] **Step 3: Verify build.** Run: `npm run build` — Expected: `✓ built`.
- [ ] **Step 4: Commit.**
```bash
git add src/data/mockStore.jsx
git commit -m "feat(data): add login records for sub_002-005 so each subcontractor inbox is reachable"
```
---
## Task 8: Fix task↔project associations + On Hold styling
**Files:**
- Modify: `src/data/mockStore.jsx``MOCK_SUBCONTRACTOR_TASKS` (projectId/location).
- Modify: `src/pages/owner/SubcontractorTasksPage.jsx``STATUS_CONFIG`.
- Modify: `src/components/subcontractor/TaskViewModal.jsx``STATUS_STYLES`.
- [ ] **Step 1: Re-point tasks** so each task's `projectId` references a project whose `address` matches the task `location` (residential roofing/electrical tasks → residential projects, not the commercial `proj_011`/`proj_012`). Fix the 6 mismatches the audit flagged (sct_004, _005, _006, _007, _008, _010).
- [ ] **Step 2: Add `On Hold`** to `STATUS_CONFIG` in `SubcontractorTasksPage.jsx` (give it an icon + amber/grey styling, consistent with siblings) and to `STATUS_STYLES` in `TaskViewModal.jsx`.
- [ ] **Step 3: Verify in dev.** Open `/owner/subcontractor-tasks`: On-Hold tasks (sct_005, sct_009) show distinct On-Hold styling (not "Assigned"); the project column shows a sensible, matching project.
- [ ] **Step 4: Verify build.** Run: `npm run build` — Expected: `✓ built`.
- [ ] **Step 5: Commit.**
```bash
git add src/data/mockStore.jsx src/pages/owner/SubcontractorTasksPage.jsx src/components/subcontractor/TaskViewModal.jsx
git commit -m "fix(data): match subcontractor tasks to real projects; style On Hold status"
```
---
## Task 9: Add structured `value` to kanban leads + fix Complete-stage data
**Files:**
- Modify: `src/data/mockStore.jsx``KANBAN_LEADS_INITIAL` and `KANBAN_PROJECT_DATA`.
- [ ] **Step 1: Add a numeric `value`** (estimate $) to **every** `kl_*` lead. For signed+ leads reuse the existing `estimatedAmount` from `KANBAN_PROJECT_DATA`; for earlier-stage leads assign realistic varied amounts ($6k$42k).
- [ ] **Step 2: Fix Complete-stage leads** (kl_034038): set their project `completionPct: 100` and a real `actualCost` (≈ their estimate), since they're in the Complete column.
- [ ] **Step 3: Populate `stormSource: true`** on storm-canvassed kanban leads (those whose notes mention storm/hail).
- [ ] **Step 4: Verify in dev.** Open the Kanban board: cards render; storm badges appear on storm leads; Complete column leads show 100%.
- [ ] **Step 5: Verify build.** Run: `npm run build` — Expected: `✓ built`.
- [ ] **Step 6: Commit.**
```bash
git add src/data/mockStore.jsx
git commit -m "feat(data): add lead values, fix Complete-stage data, populate stormSource on kanban leads"
```
---
## Task R: Canonical roster rename (real Texan names + LUP empIds)
**Runs after Task 2, before Task 3.** **Files:** `src/data/mockStore.jsx` (people source records) + repo-wide name-string replacement.
- [ ] **Step 1: Set names + empIds on source records.** In `MOCK_USERS`, `MOCK_PERSONNEL`/`personnel`, `MOCK_OWNERS`, and `MOCK_SUBCONTRACTORS`, for each person in the Canonical Roster set the `name` to the roster name and set `empId` to the roster `LUP-####`. Reconcile Justin's email to `justin@lynkeduppro.com`. Keep password `password`.
- [ ] **Step 2: Repo-wide name replace.** Replace every occurrence of each old name string with its new name across the whole repo (covers denormalized copies in teamMembers, subcontractor tasks, kanban activity, `DISPATCH_REPS`, and any component). Pairs:
- "Frank Agent"→"Cody Tatum", "Fiona Field"→"Hannah Reyes", "Fred Flyer"→"Travis Boone", "Felicity Fast"→"Shelby Greer", "Felix Fixer"→"Dalton Pruitt"
- "Adam Admin"→"Wade Hollis", "Amanda Manager"→"Darlene Brooks", "Arthur Director"→"Roy Schaefer"
- "Admin One"→"Wade Hollis", "Admin Two"→"Darlene Brooks", "Admin Three"→"Roy Schaefer"
- "Maria Garcia"→"Diana Reeves", "Sarah Sales"→"Sarah Calloway"
- "Carlos Subcontractor"→"Carlos Mendoza", "Mike Contractor"→"Mike Brennan"
- sub_003 current name → "Dwayne Boudreaux", sub_004 current name → "Jennifer Castillo" (look up the current strings first via `git grep`).
- [ ] **Step 3: Verify old names gone.** Run:
```bash
git grep -nE "Frank Agent|Fiona Field|Fred Flyer|Felicity Fast|Felix Fixer|Adam Admin|Amanda Manager|Arthur Director|Admin (One|Two|Three)|Maria Garcia|Sarah Sales|Carlos Subcontractor|Mike Contractor" -- src
```
Expected: **no matches**.
- [ ] **Step 4: Verify build.** Run: `npm run build` — Expected: `✓ built`.
- [ ] **Step 5: Commit.**
```bash
git add -A
git commit -m "feat(data): canonical roster — real Texan names + unified LUP-#### employee ids"
```
---
## Task L: Login per-role person picker
**Runs last.** **Files:** `src/pages/Login.jsx`; `src/context/AuthContext.jsx` (only if id matching needs updating).
- [ ] **Step 1: Inline demo-account list** in `Login.jsx` (keeps this task's lane to Login.jsx). Define from the roster:
```jsx
const DEMO_ACCOUNTS = {
customer: [{ name: 'Alice Carter', loginType: 'customer', id: 'alice', pw: 'password' }],
employee: [
{ name: 'Wade Hollis (Admin)', loginType: 'employee', id: 'LUP-1010', pw: 'password' },
{ name: 'Darlene Brooks (Admin)',loginType: 'employee', id: 'LUP-1011', pw: 'password' },
{ name: 'Roy Schaefer (Admin)', loginType: 'employee', id: 'LUP-1012', pw: 'password' },
{ name: 'Cody Tatum (Agent)', loginType: 'employee', id: 'LUP-1040', pw: 'password' },
{ name: 'Hannah Reyes (Agent)', loginType: 'employee', id: 'LUP-1041', pw: 'password' },
{ name: 'Travis Boone (Agent)', loginType: 'employee', id: 'LUP-1042', pw: 'password' },
{ name: 'Shelby Greer (Agent)', loginType: 'employee', id: 'LUP-1043', pw: 'password' },
{ name: 'Dalton Pruitt (Agent)', loginType: 'employee', id: 'LUP-1044', pw: 'password' },
],
owner: [
{ name: 'Justin Johnson', loginType: 'owner', id: 'justin', pw: 'password' },
{ name: 'Diana Reeves', loginType: 'owner', id: 'diana', pw: 'password' },
],
contractor: [{ name: 'Mike Brennan', loginType: 'contractor', id: 'mike', pw: 'password' }],
subcontractor: [
{ name: 'Carlos Mendoza', loginType: 'subcontractor', id: 'carlos', pw: 'password' },
{ name: 'Maya Patel', loginType: 'subcontractor', id: 'maya', pw: 'password' },
{ name: 'Dwayne Boudreaux', loginType: 'subcontractor', id: 'dwayne', pw: 'password' },
{ name: 'Jennifer Castillo',loginType: 'subcontractor', id: 'jennifer', pw: 'password' },
{ name: 'Greg Alston', loginType: 'subcontractor', id: 'greg', pw: 'password' },
],
};
```
- [ ] **Step 2: Replace the quick-access row** so each role chip, when clicked, expands the list of people for that role; clicking a person calls a `pickDemo(acct)` that sets `loginType`, `identifier`, `password` (and may submit). Single-person roles act as one-click.
- [ ] **Step 3: Ensure AuthContext accepts the ids.** Confirm `login(identifier, password, loginType)` matches employees by `empId` (`LUP-####`) and owner/contractor/sub by username. If `MOCK_USERS` records carry the LUP `empId` (set in Task R / Task 7), update the match in `AuthContext.jsx` only if needed.
- [ ] **Step 4: Verify in dev.** Log in via the picker as an Admin (LUP-1010), an Agent (LUP-1041), the second Owner (Diana), and a Subcontractor (Maya). Each lands on the right dashboard.
- [ ] **Step 5: Verify build.** Run: `npm run build` — Expected: `✓ built`.
- [ ] **Step 6: Commit.**
```bash
git add src/pages/Login.jsx src/context/AuthContext.jsx
git commit -m "feat(login): per-role person picker for quick demo access"
```
---
## Self-review checklist (run after all tasks)
- [ ] `git grep -nE "owner_00[12]|\bADM0[123]\b|FA00[12345]"` returns **no matches** in `src/data/mockStore.jsx`.
- [ ] All old placeholder names gone (the Task R Step 3 grep → no matches); roster names appear in projects/tables/schedule/dispatch/leaderboard.
- [ ] `own_002` is "Diana Reeves" everywhere (`git grep -n "Maria Garcia"` → no matches).
- [ ] Login picker: signing in as an Admin (LUP-1010), Agent (LUP-1041), Owner #2 (Diana), and a Sub (Maya) each lands on the correct dashboard.
- [ ] Every project with a `budgetBreakdown` reconciles (column sums == totals).
- [ ] `npm run build` is clean.
- [ ] Dev run: Leads page non-empty; each `sub_00N` login sees its own tasks; On-Hold styled; no "Unknown" person labels on subcontractor tasks.
## Spec coverage note
This plan covers spec Phases 13. Phases 46 (threaded scenarios, pipeline breadth, computed analytics) and 710 (lifecycle/inspections, sub-task stage machine, profiles) are separate plans authored after this one lands.