adds sections as list tasks,log expenses. fees, notifications for sub contrcator

This commit is contained in:
Satyam Rastogi
2026-05-19 18:17:36 +05:30
parent c7a9849d1f
commit 2589975074
5 changed files with 2003 additions and 60 deletions
+636 -27
View File
@@ -5014,10 +5014,33 @@ export const SUBCONTRACTOR_TASK_PRIORITIES = [
export const SUBCONTRACTOR_TASK_STATUSES = [
'Assigned',
'In Progress',
'On Hold',
'Completed',
'Cancelled',
];
// Statuses the subcontractor themselves can set from the Task Detail page.
// Owner / admin can still flip a task to Cancelled from the admin page.
export const SUBCONTRACTOR_SELF_STATUSES = ['Assigned', 'In Progress', 'On Hold', 'Completed'];
export const SUBCONTRACTOR_EXPENSE_CATEGORIES = [
'Materials',
'Equipment Rental',
'Travel / Fuel',
'Permits',
'Disposal / Dumpster',
'Other',
];
export const SUBCONTRACTOR_FEE_TYPES = [
'Labour / Service Fee',
'Inspection Fee',
'Mobilization Fee',
'Overtime',
'Callback / Warranty',
'Other',
];
const MOCK_SUBCONTRACTORS = [
{
id: 'sub_001',
@@ -5082,6 +5105,9 @@ const MOCK_SUBCONTRACTORS = [
];
const MOCK_SUBCONTRACTOR_TASKS = [
// -------------------------------------------------------------------
// sct_001 Maya Patel (sub_002) fully populated reference task
// -------------------------------------------------------------------
{
id: 'sct_001',
companyId: 'lynkeduppro',
@@ -5092,17 +5118,47 @@ const MOCK_SUBCONTRACTOR_TASKS = [
assignedByName: 'Justin Johnson',
title: 'Replace damaged ridge cap',
location: '2612 Dunwick Dr, Plano, TX 75023',
description: 'Two ridge cap shingles missing on the south slope after wind storm. Replace with matching tile and seal.',
description: 'Two ridge cap shingles missing on the south slope after wind storm. Replace with matching tile and seal. Inspect adjacent tiles for hairline cracks while you are up there.',
dueDate: '2026-05-25',
priority: 'high',
status: 'Assigned',
status: 'In Progress',
photos: [
{ id: 'sct_001_p1', name: 'Roof_Damage.jpg', url: '/assets/images/properties/Hail_Damaged_Shingles.jpg', annotation: 'Repair this corner' },
{ id: 'sct_001_p2', name: 'Roof_Wide.jpg', url: '/assets/images/properties/Storm_Worn_Roof.jpg', annotation: 'Inspect adjacent tiles too' },
{ id: 'sct_001_p1', name: 'Roof_Damage.jpg', url: '/assets/images/properties/Hail_Damaged_Shingles.jpg', annotation: 'Repair this corner' },
{ id: 'sct_001_p2', name: 'Roof_Wide.jpg', url: '/assets/images/properties/Storm_Worn_Roof.jpg', annotation: 'Inspect adjacent tiles too' },
{ id: 'sct_001_p3', name: 'Ridge_Closeup.jpg', url: '/assets/images/properties/Cracked_Storm_Shingles.jpg', annotation: 'Closeup of missing cap' },
{ id: 'sct_001_p4', name: 'Roof_Reference.jpg', url: '/assets/images/properties/Overlapping_Roof_Shingles.jpg', annotation: '' },
],
statusHistory: [
{ id: 'sct_001_h1', status: 'Assigned', actorId: 'owner_001', actorName: 'Justin Johnson', comment: 'Task created and assigned.', at: '2026-05-15T09:12:00Z' },
{ id: 'sct_001_h2', status: 'In Progress', actorId: 'sub_002', actorName: 'Maya Patel', comment: 'On site, sourced materials.', at: '2026-05-16T08:30:00Z' },
],
thread: [
{ id: 'sct_001_t1', channel: 'external', senderId: 'owner_001', senderName: 'Justin Johnson', senderRole: 'Owner', body: 'Please source them — match Owens Corning Estate Gray as best you can.', at: '2026-05-15T09:14:00Z' },
{ id: 'sct_001_t2', channel: 'external', senderId: 'sub_002', senderName: 'Maya Patel', senderRole: 'Subcontractor', body: 'Got it. Ill be there on May 20. Do you have shingles in storage or should I source them?', at: '2026-05-15T10:30:00Z', mine: true },
{ id: 'sct_001_t3', channel: 'external', senderId: 'owner_001', senderName: 'Justin Johnson', senderRole: 'Owner', body: 'Source them and put it on the task expenses. Homeowner is OK to reimburse.', at: '2026-05-15T11:00:00Z' },
{ id: 'sct_001_t4', channel: 'external', senderId: 'sub_002', senderName: 'Maya Patel', senderRole: 'Subcontractor', body: 'Sounds good — picking up at Home Depot tomorrow morning.', at: '2026-05-15T11:08:00Z', mine: true },
{ id: 'sct_001_t5', channel: 'internal', senderId: 'owner_001', senderName: 'Justin Johnson', senderRole: 'Owner', body: 'Heads up team: homeowner is sensitive about noise — keep work to daylight hours only. No early-morning starts.', at: '2026-05-15T11:05:00Z' },
{ id: 'sct_001_t6', channel: 'internal', senderId: 'ADM01', senderName: 'Admin One', senderRole: 'Admin', body: 'Permits are not required for this scope. Skip the city portal step.', at: '2026-05-15T12:10:00Z' },
{ id: 'sct_001_t7', channel: 'internal', senderId: 'sub_002', senderName: 'Maya Patel', senderRole: 'Subcontractor', body: 'Noted. Will text homeowner when crew is 30 min out.', at: '2026-05-15T12:25:00Z', mine: true },
],
expenses: [
{ id: 'sct_001_e1', description: 'Owens Corning Estate Gray (single pkg)', category: 'Materials', amount: 64.5, notes: 'From Home Depot, kept receipt.', receiptUrl: 'mock://receipts/oc-estate-gray.pdf', createdAt: '2026-05-16T13:10:00Z' },
{ id: 'sct_001_e2', description: 'Roofing nails 1.25"', category: 'Materials', amount: 14.99, notes: '', receiptUrl: '', createdAt: '2026-05-16T13:12:00Z' },
{ id: 'sct_001_e3', description: 'Sealant tube', category: 'Materials', amount: 21.0, notes: 'NP1 polyurethane.', receiptUrl: '', createdAt: '2026-05-16T13:14:00Z' },
{ id: 'sct_001_e4', description: 'Dumpster rental (half day)', category: 'Equipment Rental', amount: 85.0, notes: 'Shared with adjacent job.', receiptUrl: '', createdAt: '2026-05-16T15:00:00Z' },
{ id: 'sct_001_e5', description: 'Fuel — round trip to supply yard', category: 'Travel / Fuel', amount: 18.4, notes: '', receiptUrl: '', createdAt: '2026-05-16T17:30:00Z' },
],
fees: [
{ id: 'sct_001_f1', description: 'Labour — Ridge Cap Replacement', type: 'Labour / Service Fee', amount: 350, createdAt: '2026-05-16T13:20:00Z' },
{ id: 'sct_001_f2', description: 'Mobilization to site', type: 'Mobilization Fee', amount: 75, createdAt: '2026-05-16T13:22:00Z' },
],
createdAt: '2026-05-15T09:12:00Z',
updatedAt: '2026-05-15T09:12:00Z',
updatedAt: '2026-05-16T17:30:00Z',
},
// -------------------------------------------------------------------
// sct_002 Carlos (sub_001) In Progress electrical
// -------------------------------------------------------------------
{
id: 'sct_002',
companyId: 'lynkeduppro',
@@ -5113,16 +5169,38 @@ const MOCK_SUBCONTRACTOR_TASKS = [
assignedByName: 'Justin Johnson',
title: 'Replace breaker panel',
location: '6613 Phoenix Pl, Plano, TX 75023',
description: 'Outdated 100A panel — upgrade to 200A. Permit pulled, materials staged on site.',
description: 'Outdated 100A panel — upgrade to 200A. Permit pulled, materials staged on site. Coordinate Oncor disconnect for the morning of installation.',
dueDate: '2026-05-22',
priority: 'medium',
status: 'In Progress',
photos: [
{ id: 'sct_002_p1', name: 'Old_Panel.jpg', url: '/assets/images/properties/Beige_Two_Story_House.jpg', annotation: 'Existing 100A panel — disconnect and remove' },
{ id: 'sct_002_p2', name: 'Service_Mast.jpg', url: '/assets/images/properties/Modern_White_Suburban_Home.jpg', annotation: 'Service mast — verify clearance' },
],
statusHistory: [
{ id: 'sct_002_h1', status: 'Assigned', actorId: 'owner_001', actorName: 'Justin Johnson', comment: 'Task created and assigned.', at: '2026-05-10T15:40:00Z' },
{ id: 'sct_002_h2', status: 'In Progress', actorId: 'sub_001', actorName: 'Carlos Subcontractor', comment: 'On site, started disconnect.', at: '2026-05-12T08:20:00Z' },
],
thread: [
{ id: 'sct_002_t1', channel: 'external', senderId: 'sub_001', senderName: 'Carlos Subcontractor', senderRole: 'Subcontractor', body: 'Oncor disconnect is confirmed for May 12, 8 AM.', at: '2026-05-11T16:00:00Z', mine: true },
{ id: 'sct_002_t2', channel: 'external', senderId: 'owner_001', senderName: 'Justin Johnson', senderRole: 'Owner', body: 'Perfect. Homeowner will be on site.', at: '2026-05-11T16:30:00Z' },
{ id: 'sct_002_t3', channel: 'internal', senderId: 'ADM01', senderName: 'Admin One', senderRole: 'Admin', body: 'Inspection slot booked with the city for May 15.', at: '2026-05-12T09:30:00Z' },
],
expenses: [
{ id: 'sct_002_e1', description: '200A panel — Square D', category: 'Materials', amount: 312.0, notes: '', receiptUrl: '', createdAt: '2026-05-11T09:00:00Z' },
{ id: 'sct_002_e2', description: 'Conduit + fittings', category: 'Materials', amount: 47.25, notes: '3/4" EMT + connectors.', receiptUrl: '', createdAt: '2026-05-11T09:10:00Z' },
{ id: 'sct_002_e3', description: 'Permit fee — City of Plano', category: 'Permits', amount: 95.0, notes: 'Reimbursable.', receiptUrl: '', createdAt: '2026-05-11T14:00:00Z' },
],
fees: [
{ id: 'sct_002_f1', description: 'Labour — Day 1 disconnect / prep', type: 'Labour / Service Fee', amount: 425, createdAt: '2026-05-12T17:00:00Z' },
],
createdAt: '2026-05-10T15:40:00Z',
updatedAt: '2026-05-12T08:20:00Z',
updatedAt: '2026-05-12T17:00:00Z',
},
// -------------------------------------------------------------------
// sct_003 Dwayne (sub_003) Completed paint job
// -------------------------------------------------------------------
{
id: 'sct_003',
companyId: 'lynkeduppro',
@@ -5133,40 +5211,420 @@ const MOCK_SUBCONTRACTOR_TASKS = [
assignedByName: 'Admin One',
title: 'Touch-up exterior paint',
location: '3913 Arizona Pl, Plano, TX 75023',
description: 'Front fascia and side trim need fresh coat after siding repair.',
description: 'Front fascia and side trim need fresh coat after siding repair. Match existing Sherwin-Williams color.',
dueDate: '2026-05-18',
priority: 'low',
status: 'Completed',
photos: [],
photos: [
{ id: 'sct_003_p1', name: 'Before.jpg', url: '/assets/images/properties/Brick_Front_Porch_Home.jpg', annotation: 'Before' },
],
statusHistory: [
{ id: 'sct_003_h1', status: 'Assigned', actorId: 'ADM01', actorName: 'Admin One', comment: 'Task created.', at: '2026-05-05T11:00:00Z' },
{ id: 'sct_003_h2', status: 'In Progress', actorId: 'sub_003', actorName: 'Dwayne Holland', comment: 'Started prep work.', at: '2026-05-13T08:00:00Z' },
{ id: 'sct_003_h3', status: 'Completed', actorId: 'sub_003', actorName: 'Dwayne Holland', comment: 'Walked through with homeowner.', at: '2026-05-14T17:30:00Z' },
],
thread: [
{ id: 'sct_003_t1', channel: 'external', senderId: 'sub_003', senderName: 'Dwayne Holland', senderRole: 'Subcontractor', body: 'Wrapped up — homeowner signed off. Photos uploaded.', at: '2026-05-14T17:25:00Z', mine: true },
],
expenses: [
{ id: 'sct_003_e1', description: 'Paint — SW Repose Gray (1 gal)', category: 'Materials', amount: 52.5, notes: '', receiptUrl: '', createdAt: '2026-05-13T07:30:00Z' },
],
fees: [
{ id: 'sct_003_f1', description: 'Touch-up paint labour', type: 'Labour / Service Fee', amount: 180, createdAt: '2026-05-14T17:35:00Z' },
],
createdAt: '2026-05-05T11:00:00Z',
updatedAt: '2026-05-14T17:30:00Z',
},
// -------------------------------------------------------------------
// sct_004 Maya (sub_002) emergency tarping, Assigned
// -------------------------------------------------------------------
{
id: 'sct_004',
companyId: 'lynkeduppro',
companyName: 'LynkedUp Pro Roofing',
subcontractorId: 'sub_002',
subcontractorName: 'Maya Patel',
assignedBy: 'owner_001',
assignedByName: 'Justin Johnson',
title: 'Emergency tarp — storm damage',
location: '4221 Magnolia Ln, Plano, TX 75023',
description: 'Hailstorm overnight; 34 ft section of decking exposed near chimney. Tarp ASAP to prevent further water intrusion. Permanent repair will be scheduled after insurance scope.',
dueDate: '2026-05-19',
priority: 'high',
status: 'Assigned',
photos: [
{ id: 'sct_004_p1', name: 'Storm_Damage.jpg', url: '/assets/images/properties/Broken_Roof_Surface.jpg', annotation: 'Exposed decking near chimney' },
{ id: 'sct_004_p2', name: 'Roof_Texture.jpg', url: '/assets/images/properties/Residential_Roof_Texture.jpg', annotation: '' },
],
statusHistory: [
{ id: 'sct_004_h1', status: 'Assigned', actorId: 'owner_001', actorName: 'Justin Johnson', comment: 'Storm-response priority. Please confirm ETA.', at: '2026-05-18T22:40:00Z' },
],
thread: [
{ id: 'sct_004_t1', channel: 'external', senderId: 'owner_001', senderName: 'Justin Johnson', senderRole: 'Owner', body: 'Can you make it first thing tomorrow morning?', at: '2026-05-18T22:42:00Z' },
{ id: 'sct_004_t2', channel: 'external', senderId: 'sub_002', senderName: 'Maya Patel', senderRole: 'Subcontractor', body: 'Yes — on site by 7:30 AM. Bringing tarps and battens.', at: '2026-05-18T23:05:00Z', mine: true },
{ id: 'sct_004_t3', channel: 'internal', senderId: 'owner_001', senderName: 'Justin Johnson', senderRole: 'Owner', body: 'Customer is filing an insurance claim — make sure to document everything with photos.', at: '2026-05-18T23:10:00Z' },
],
expenses: [],
fees: [],
createdAt: '2026-05-18T22:40:00Z',
updatedAt: '2026-05-18T23:10:00Z',
},
// -------------------------------------------------------------------
// sct_005 Maya (sub_002) Attic ventilation inspection, On Hold
// -------------------------------------------------------------------
{
id: 'sct_005',
companyId: 'lynkeduppro',
companyName: 'LynkedUp Pro Roofing',
subcontractorId: 'sub_002',
subcontractorName: 'Maya Patel',
assignedBy: 'ADM01',
assignedByName: 'Admin One',
title: 'Inspect attic ventilation',
location: '5510 Custer Rd, Plano, TX 75023',
description: 'Customer reports unusually high attic temps. Check ridge vent, soffit airflow, and intake/exhaust balance. Recommend remediation.',
dueDate: '2026-05-30',
priority: 'medium',
status: 'On Hold',
photos: [
{ id: 'sct_005_p1', name: 'Attic_View.jpg', url: '/assets/images/properties/Roof_Inspection_Angle.jpg', annotation: 'Access from south gable' },
],
statusHistory: [
{ id: 'sct_005_h1', status: 'Assigned', actorId: 'ADM01', actorName: 'Admin One', comment: 'Task created.', at: '2026-05-12T10:00:00Z' },
{ id: 'sct_005_h2', status: 'In Progress', actorId: 'sub_002', actorName: 'Maya Patel', comment: 'Initial pass done; baffles look blocked.', at: '2026-05-14T11:00:00Z' },
{ id: 'sct_005_h3', status: 'On Hold', actorId: 'sub_002', actorName: 'Maya Patel', comment: 'Waiting on homeowner to clear stored boxes from access path.', at: '2026-05-15T16:00:00Z' },
],
thread: [
{ id: 'sct_005_t1', channel: 'external', senderId: 'sub_002', senderName: 'Maya Patel', senderRole: 'Subcontractor', body: 'Access is blocked — homeowner needs to move some storage before I can complete the inspection.', at: '2026-05-15T15:55:00Z', mine: true },
{ id: 'sct_005_t2', channel: 'internal', senderId: 'ADM01', senderName: 'Admin One', senderRole: 'Admin', body: 'I will reach out to the homeowner and reschedule.', at: '2026-05-15T16:20:00Z' },
],
expenses: [
{ id: 'sct_005_e1', description: 'Infrared thermometer (rental)', category: 'Equipment Rental', amount: 35.0, notes: '', receiptUrl: '', createdAt: '2026-05-14T10:30:00Z' },
],
fees: [
{ id: 'sct_005_f1', description: 'Inspection — initial visit', type: 'Inspection Fee', amount: 125, createdAt: '2026-05-14T13:00:00Z' },
],
createdAt: '2026-05-12T10:00:00Z',
updatedAt: '2026-05-15T16:20:00Z',
},
// -------------------------------------------------------------------
// sct_006 Maya (sub_002) Gutter replacement, Assigned
// -------------------------------------------------------------------
{
id: 'sct_006',
companyId: 'lynkeduppro',
companyName: 'LynkedUp Pro Roofing',
subcontractorId: 'sub_002',
subcontractorName: 'Maya Patel',
assignedBy: 'owner_001',
assignedByName: 'Justin Johnson',
title: 'Replace gutter on south elevation',
location: '8917 Elmwood Dr, Plano, TX 75025',
description: '30 ft section of K-style gutter — pulling away from fascia after storm. Replace with new 6" oversized gutter, match existing bronze color.',
dueDate: '2026-06-02',
priority: 'low',
status: 'Assigned',
photos: [],
statusHistory: [
{ id: 'sct_006_h1', status: 'Assigned', actorId: 'owner_001', actorName: 'Justin Johnson', comment: 'Task created and assigned.', at: '2026-05-17T14:00:00Z' },
],
thread: [],
expenses: [],
fees: [],
createdAt: '2026-05-17T14:00:00Z',
updatedAt: '2026-05-17T14:00:00Z',
},
// -------------------------------------------------------------------
// sct_007 Maya (sub_002) completed earlier this month
// -------------------------------------------------------------------
{
id: 'sct_007',
companyId: 'lynkeduppro',
companyName: 'LynkedUp Pro Roofing',
subcontractorId: 'sub_002',
subcontractorName: 'Maya Patel',
assignedBy: 'ADM01',
assignedByName: 'Admin One',
title: 'Roof deck repair — north slope',
location: '1408 Cedar Ridge, Allen, TX 75002',
description: 'Sheathing soft in two ~2x2 ft areas. Cut out, replace with matching OSB, re-felt and re-shingle.',
dueDate: '2026-05-08',
priority: 'high',
status: 'Completed',
photos: [
{ id: 'sct_007_p1', name: 'Deck_Repair.jpg', url: '/assets/images/properties/Cracked_Asphalt_Shingles.jpg', annotation: 'Soft section, top right' },
],
statusHistory: [
{ id: 'sct_007_h1', status: 'Assigned', actorId: 'ADM01', actorName: 'Admin One', comment: 'Task created.', at: '2026-05-01T09:00:00Z' },
{ id: 'sct_007_h2', status: 'In Progress', actorId: 'sub_002', actorName: 'Maya Patel', comment: 'Tear-off underway.', at: '2026-05-05T07:30:00Z' },
{ id: 'sct_007_h3', status: 'Completed', actorId: 'sub_002', actorName: 'Maya Patel', comment: 'Closed out, photos in.', at: '2026-05-07T16:45:00Z' },
],
thread: [
{ id: 'sct_007_t1', channel: 'external', senderId: 'sub_002', senderName: 'Maya Patel', senderRole: 'Subcontractor', body: 'All done — closed out. Final photos uploaded.', at: '2026-05-07T16:40:00Z', mine: true },
],
expenses: [
{ id: 'sct_007_e1', description: 'OSB sheathing 4x8 (2 sheets)', category: 'Materials', amount: 76.0, notes: '', receiptUrl: '', createdAt: '2026-05-05T07:00:00Z' },
{ id: 'sct_007_e2', description: 'Underlayment roll', category: 'Materials', amount: 38.0, notes: '', receiptUrl: '', createdAt: '2026-05-05T07:05:00Z' },
{ id: 'sct_007_e3', description: 'Disposal — dump fee', category: 'Disposal / Dumpster', amount: 55.0, notes: '', receiptUrl: '', createdAt: '2026-05-07T17:00:00Z' },
],
fees: [
{ id: 'sct_007_f1', description: 'Labour — deck repair (2 days)', type: 'Labour / Service Fee', amount: 920, createdAt: '2026-05-07T16:50:00Z' },
],
createdAt: '2026-05-01T09:00:00Z',
updatedAt: '2026-05-07T17:00:00Z',
},
// -------------------------------------------------------------------
// sct_008 Carlos (sub_001) GFCI install, fully populated
// -------------------------------------------------------------------
{
id: 'sct_008',
companyId: 'lynkeduppro',
companyName: 'LynkedUp Pro Roofing',
subcontractorId: 'sub_001',
subcontractorName: 'Carlos Subcontractor',
assignedBy: 'ADM01',
assignedByName: 'Admin One',
title: 'Install GFCI outlets — kitchen + bath',
location: '224 Greenfield Way, Frisco, TX 75033',
description: 'Replace 4 standard receptacles with GFCI: 2 kitchen counter outlets (left of sink + island), 1 master bath vanity, 1 powder room vanity. Code update required as part of insurance work. Use Leviton SmartlockPro 20A devices. Test all branches with plug-in tester before signing off.',
dueDate: '2026-05-26',
priority: 'medium',
status: 'In Progress',
photos: [
{ id: 'sct_008_p1', name: 'Kitchen_Outlet.jpg', url: '/assets/images/properties/Modern_White_Suburban_Home.jpg', annotation: 'Kitchen — left of sink, current standard receptacle' },
{ id: 'sct_008_p2', name: 'Island_Outlet.jpg', url: '/assets/images/properties/Sunny_Suburban_House.jpg', annotation: 'Island outlet — replace with GFCI' },
{ id: 'sct_008_p3', name: 'Master_Bath.jpg', url: '/assets/images/properties/Brick_Front_Porch_Home.jpg', annotation: 'Master bath vanity outlet' },
{ id: 'sct_008_p4', name: 'Powder_Room.jpg', url: '/assets/images/properties/Luxury_Driveway_Residence.jpg', annotation: 'Powder room — confirm wire gauge' },
],
statusHistory: [
{ id: 'sct_008_h1', status: 'Assigned', actorId: 'ADM01', actorName: 'Admin One', comment: 'Task created and assigned.', at: '2026-05-16T13:00:00Z' },
{ id: 'sct_008_h2', status: 'In Progress', actorId: 'sub_001', actorName: 'Carlos Subcontractor', comment: 'On site, kitchen done; bath next.', at: '2026-05-19T10:15:00Z' },
],
thread: [
{ id: 'sct_008_t1', channel: 'external', senderId: 'ADM01', senderName: 'Admin One', senderRole: 'Admin', body: 'Insurance is reimbursing the code upgrade — keep receipts for the GFCI devices.', at: '2026-05-16T13:05:00Z' },
{ id: 'sct_008_t2', channel: 'external', senderId: 'sub_001', senderName: 'Carlos Subcontractor', senderRole: 'Subcontractor', body: 'Confirmed — Leviton 20A devices, will keep receipts. Targeting May 19 for the visit.', at: '2026-05-16T14:20:00Z', mine: true },
{ id: 'sct_008_t3', channel: 'external', senderId: 'owner_001', senderName: 'Justin Johnson', senderRole: 'Owner', body: 'Homeowner has 2 small kids — please bring outlet covers as a courtesy.', at: '2026-05-17T09:00:00Z' },
{ id: 'sct_008_t4', channel: 'external', senderId: 'sub_001', senderName: 'Carlos Subcontractor', senderRole: 'Subcontractor', body: 'Will do. Already in my truck.', at: '2026-05-17T09:05:00Z', mine: true },
{ id: 'sct_008_t5', channel: 'external', senderId: 'sub_001', senderName: 'Carlos Subcontractor', senderRole: 'Subcontractor', body: 'Kitchen done. Both counter outlets and the island are on GFCI now. Starting bath after lunch.', at: '2026-05-19T12:30:00Z', mine: true },
{ id: 'sct_008_t6', channel: 'internal', senderId: 'ADM01', senderName: 'Admin One', senderRole: 'Admin', body: 'No permit needed for like-for-like swap. Skip the city portal.', at: '2026-05-16T13:10:00Z' },
{ id: 'sct_008_t7', channel: 'internal', senderId: 'owner_001', senderName: 'Justin Johnson', senderRole: 'Owner', body: 'Carlos — if you find any aluminum branch wiring, stop and flag it. We will scope as a change order.', at: '2026-05-17T10:00:00Z' },
{ id: 'sct_008_t8', channel: 'internal', senderId: 'sub_001', senderName: 'Carlos Subcontractor', senderRole: 'Subcontractor', body: 'All copper, no aluminum. Proceeding as scoped.', at: '2026-05-19T10:30:00Z', mine: true },
],
expenses: [
{ id: 'sct_008_e1', description: 'Leviton 20A GFCI receptacles (qty 4)', category: 'Materials', amount: 92.0, notes: 'SmartlockPro tamper-resistant.', receiptUrl: 'mock://receipts/leviton-gfci.pdf', createdAt: '2026-05-18T16:00:00Z' },
{ id: 'sct_008_e2', description: 'Wire nuts + electrical tape', category: 'Materials', amount: 11.5, notes: '', receiptUrl: '', createdAt: '2026-05-18T16:02:00Z' },
{ id: 'sct_008_e3', description: 'Childproof outlet covers (10-pack)', category: 'Materials', amount: 8.99, notes: 'Courtesy for homeowner.', receiptUrl: '', createdAt: '2026-05-18T16:05:00Z' },
{ id: 'sct_008_e4', description: 'Plug-in circuit tester', category: 'Equipment Rental', amount: 24.0, notes: 'Sperry Instruments GFCI tester.', receiptUrl: '', createdAt: '2026-05-18T16:10:00Z' },
{ id: 'sct_008_e5', description: 'Fuel — Frisco round trip', category: 'Travel / Fuel', amount: 22.4, notes: '', receiptUrl: '', createdAt: '2026-05-19T18:00:00Z' },
],
fees: [
{ id: 'sct_008_f1', description: 'Labour — Kitchen GFCI install (2 outlets + island)', type: 'Labour / Service Fee', amount: 220, createdAt: '2026-05-19T12:35:00Z' },
{ id: 'sct_008_f2', description: 'Mobilization to site', type: 'Mobilization Fee', amount: 75, createdAt: '2026-05-19T08:00:00Z' },
],
createdAt: '2026-05-16T13:00:00Z',
updatedAt: '2026-05-19T18:00:00Z',
},
// -------------------------------------------------------------------
// sct_009 Jennifer Wu (sub_004) On Hold plumbing
// -------------------------------------------------------------------
{
id: 'sct_009',
companyId: 'lynkeduppro',
companyName: 'LynkedUp Pro Roofing',
subcontractorId: 'sub_004',
subcontractorName: 'Jennifer Wu',
assignedBy: 'owner_001',
assignedByName: 'Justin Johnson',
title: 'Rough-in plumbing for ADU bathroom',
location: '6112 Roundrock Dr, Plano, TX 75025',
description: 'New ADU build — bathroom rough-in. Coordinate with framing inspection.',
dueDate: '2026-06-15',
priority: 'medium',
status: 'On Hold',
photos: [],
statusHistory: [
{ id: 'sct_009_h1', status: 'Assigned', actorId: 'owner_001', actorName: 'Justin Johnson', comment: 'Task created.', at: '2026-05-09T10:00:00Z' },
{ id: 'sct_009_h2', status: 'On Hold', actorId: 'sub_004', actorName: 'Jennifer Wu', comment: 'Waiting on framing to finish.', at: '2026-05-13T12:00:00Z' },
],
thread: [],
expenses: [],
fees: [],
createdAt: '2026-05-09T10:00:00Z',
updatedAt: '2026-05-13T12:00:00Z',
},
];
const MOCK_NOTIFICATIONS = [
// ============================================
// sub_002 Maya Patel (default demo user)
// ============================================
// -- Task Assigned -----------------------------
{
id: 'notif_001',
recipientUserId: 'sub_001',
recipientRole: 'SUBCONTRACTOR',
id: 'notif_001', recipientUserId: 'sub_002', recipientRole: 'SUBCONTRACTOR',
type: 'task_assigned',
message: 'You have been assigned a task by LynkedUp Pro Roofing',
taskId: 'sct_002',
fromCompanyId: 'lynkeduppro',
fromCompanyName: 'LynkedUp Pro Roofing',
isRead: false,
createdAt: '2026-05-10T15:40:00Z',
message: 'You have been assigned a task by LynkedUp Pro Roofing — "Replace damaged ridge cap"',
taskId: 'sct_001', fromCompanyId: 'lynkeduppro', fromCompanyName: 'LynkedUp Pro Roofing',
isRead: false, createdAt: '2026-05-15T09:12:00Z',
},
{
id: 'notif_002',
recipientUserId: 'sub_002',
recipientRole: 'SUBCONTRACTOR',
id: 'notif_002', recipientUserId: 'sub_002', recipientRole: 'SUBCONTRACTOR',
type: 'task_assigned',
message: 'You have been assigned a task by LynkedUp Pro Roofing',
taskId: 'sct_001',
fromCompanyId: 'lynkeduppro',
fromCompanyName: 'LynkedUp Pro Roofing',
isRead: false,
createdAt: '2026-05-15T09:12:00Z',
message: 'You have been assigned a task by LynkedUp Pro Roofing — "Emergency tarp — storm damage"',
taskId: 'sct_004', fromCompanyId: 'lynkeduppro', fromCompanyName: 'LynkedUp Pro Roofing',
isRead: false, createdAt: '2026-05-18T22:40:00Z',
},
{
id: 'notif_003', recipientUserId: 'sub_002', recipientRole: 'SUBCONTRACTOR',
type: 'task_assigned',
message: 'You have been assigned a task by LynkedUp Pro Roofing — "Replace gutter on south elevation"',
taskId: 'sct_006', fromCompanyId: 'lynkeduppro', fromCompanyName: 'LynkedUp Pro Roofing',
isRead: false, createdAt: '2026-05-17T14:00:00Z',
},
{
id: 'notif_004', recipientUserId: 'sub_002', recipientRole: 'SUBCONTRACTOR',
type: 'task_assigned',
message: 'You have been assigned a task by LynkedUp Pro Roofing — "Inspect attic ventilation"',
taskId: 'sct_005', fromCompanyId: 'lynkeduppro', fromCompanyName: 'LynkedUp Pro Roofing',
isRead: true, createdAt: '2026-05-12T10:00:00Z',
},
// -- Message Received --------------------------
{
id: 'notif_005', recipientUserId: 'sub_002', recipientRole: 'SUBCONTRACTOR',
type: 'message_received',
message: 'Justin Johnson replied on sct_001 — "Please source them — match Owens Corning…"',
taskId: 'sct_001', fromCompanyId: 'lynkeduppro', fromCompanyName: 'LynkedUp Pro Roofing',
isRead: false, createdAt: '2026-05-15T09:14:00Z',
},
{
id: 'notif_006', recipientUserId: 'sub_002', recipientRole: 'SUBCONTRACTOR',
type: 'message_received',
message: 'Justin Johnson sent a new message on sct_004 — "Can you make it first thing tomorrow morning?"',
taskId: 'sct_004', fromCompanyId: 'lynkeduppro', fromCompanyName: 'LynkedUp Pro Roofing',
isRead: false, createdAt: '2026-05-18T22:42:00Z',
},
{
id: 'notif_007', recipientUserId: 'sub_002', recipientRole: 'SUBCONTRACTOR',
type: 'message_received',
message: 'Admin One added an internal note on sct_005 — "I will reach out to the homeowner…"',
taskId: 'sct_005', fromCompanyId: 'lynkeduppro', fromCompanyName: 'LynkedUp Pro Roofing',
isRead: true, createdAt: '2026-05-15T16:20:00Z',
},
// -- Task Updated ------------------------------
{
id: 'notif_008', recipientUserId: 'sub_002', recipientRole: 'SUBCONTRACTOR',
type: 'task_updated',
message: 'Due date for sct_001 was updated to May 25, 2026 by Justin Johnson.',
taskId: 'sct_001', fromCompanyId: 'lynkeduppro', fromCompanyName: 'LynkedUp Pro Roofing',
isRead: true, createdAt: '2026-05-15T11:00:00Z',
},
{
id: 'notif_009', recipientUserId: 'sub_002', recipientRole: 'SUBCONTRACTOR',
type: 'task_updated',
message: 'Priority for sct_004 raised to High by Justin Johnson.',
taskId: 'sct_004', fromCompanyId: 'lynkeduppro', fromCompanyName: 'LynkedUp Pro Roofing',
isRead: false, createdAt: '2026-05-18T22:50:00Z',
},
{
id: 'notif_010', recipientUserId: 'sub_002', recipientRole: 'SUBCONTRACTOR',
type: 'task_updated',
message: 'New photo attached to sct_001 by Justin Johnson.',
taskId: 'sct_001', fromCompanyId: 'lynkeduppro', fromCompanyName: 'LynkedUp Pro Roofing',
isRead: true, createdAt: '2026-05-15T12:00:00Z',
},
// -- Status Changed ----------------------------
{
id: 'notif_011', recipientUserId: 'sub_002', recipientRole: 'SUBCONTRACTOR',
type: 'status_changed',
message: 'Status for sct_001 changed to In Progress by Maya Patel.',
taskId: 'sct_001', fromCompanyId: 'lynkeduppro', fromCompanyName: 'LynkedUp Pro Roofing',
isRead: true, createdAt: '2026-05-16T08:30:00Z',
},
{
id: 'notif_012', recipientUserId: 'sub_002', recipientRole: 'SUBCONTRACTOR',
type: 'status_changed',
message: 'Status for sct_005 changed to On Hold by Maya Patel.',
taskId: 'sct_005', fromCompanyId: 'lynkeduppro', fromCompanyName: 'LynkedUp Pro Roofing',
isRead: true, createdAt: '2026-05-15T16:00:00Z',
},
{
id: 'notif_013', recipientUserId: 'sub_002', recipientRole: 'SUBCONTRACTOR',
type: 'status_changed',
message: 'Status for sct_007 changed to Completed by Maya Patel.',
taskId: 'sct_007', fromCompanyId: 'lynkeduppro', fromCompanyName: 'LynkedUp Pro Roofing',
isRead: true, createdAt: '2026-05-07T16:45:00Z',
},
// ============================================
// sub_001 Carlos
// ============================================
{
id: 'notif_021', recipientUserId: 'sub_001', recipientRole: 'SUBCONTRACTOR',
type: 'task_assigned',
message: 'You have been assigned a task by LynkedUp Pro Roofing — "Replace breaker panel"',
taskId: 'sct_002', fromCompanyId: 'lynkeduppro', fromCompanyName: 'LynkedUp Pro Roofing',
isRead: false, createdAt: '2026-05-10T15:40:00Z',
},
{
id: 'notif_022', recipientUserId: 'sub_001', recipientRole: 'SUBCONTRACTOR',
type: 'task_assigned',
message: 'You have been assigned a task by LynkedUp Pro Roofing — "Install GFCI outlets — kitchen + bath"',
taskId: 'sct_008', fromCompanyId: 'lynkeduppro', fromCompanyName: 'LynkedUp Pro Roofing',
isRead: false, createdAt: '2026-05-16T13:00:00Z',
},
{
id: 'notif_026', recipientUserId: 'sub_001', recipientRole: 'SUBCONTRACTOR',
type: 'message_received',
message: 'Admin One sent a new message on sct_008 — "Insurance is reimbursing the code upgrade…"',
taskId: 'sct_008', fromCompanyId: 'lynkeduppro', fromCompanyName: 'LynkedUp Pro Roofing',
isRead: false, createdAt: '2026-05-16T13:05:00Z',
},
{
id: 'notif_027', recipientUserId: 'sub_001', recipientRole: 'SUBCONTRACTOR',
type: 'message_received',
message: 'Justin Johnson sent a new message on sct_008 — "Homeowner has 2 small kids…"',
taskId: 'sct_008', fromCompanyId: 'lynkeduppro', fromCompanyName: 'LynkedUp Pro Roofing',
isRead: true, createdAt: '2026-05-17T09:00:00Z',
},
{
id: 'notif_028', recipientUserId: 'sub_001', recipientRole: 'SUBCONTRACTOR',
type: 'task_updated',
message: 'Photos updated on sct_008 by Admin One.',
taskId: 'sct_008', fromCompanyId: 'lynkeduppro', fromCompanyName: 'LynkedUp Pro Roofing',
isRead: false, createdAt: '2026-05-16T13:30:00Z',
},
{
id: 'notif_029', recipientUserId: 'sub_001', recipientRole: 'SUBCONTRACTOR',
type: 'status_changed',
message: 'Status for sct_008 changed to In Progress by Carlos Subcontractor.',
taskId: 'sct_008', fromCompanyId: 'lynkeduppro', fromCompanyName: 'LynkedUp Pro Roofing',
isRead: true, createdAt: '2026-05-19T10:15:00Z',
},
{
id: 'notif_023', recipientUserId: 'sub_001', recipientRole: 'SUBCONTRACTOR',
type: 'message_received',
message: 'Justin Johnson confirmed Oncor disconnect on sct_002.',
taskId: 'sct_002', fromCompanyId: 'lynkeduppro', fromCompanyName: 'LynkedUp Pro Roofing',
isRead: true, createdAt: '2026-05-11T16:30:00Z',
},
{
id: 'notif_024', recipientUserId: 'sub_001', recipientRole: 'SUBCONTRACTOR',
type: 'task_updated',
message: 'Permit fee on sct_002 was approved by Admin One.',
taskId: 'sct_002', fromCompanyId: 'lynkeduppro', fromCompanyName: 'LynkedUp Pro Roofing',
isRead: false, createdAt: '2026-05-11T14:30:00Z',
},
{
id: 'notif_025', recipientUserId: 'sub_001', recipientRole: 'SUBCONTRACTOR',
type: 'status_changed',
message: 'Status for sct_002 changed to In Progress by Carlos Subcontractor.',
taskId: 'sct_002', fromCompanyId: 'lynkeduppro', fromCompanyName: 'LynkedUp Pro Roofing',
isRead: true, createdAt: '2026-05-12T08:20:00Z',
},
];
@@ -5434,6 +5892,17 @@ export const MockStoreProvider = ({ children }) => {
url: p.url,
annotation: p.annotation || '',
})),
statusHistory: [{
id: `h_${Date.now()}`,
status: 'Assigned',
actorId: input.assignedBy,
actorName: input.assignedByName,
comment: 'Task created and assigned.',
at: now,
}],
thread: [],
expenses: [],
fees: [],
createdAt: now,
updatedAt: now,
};
@@ -5454,6 +5923,139 @@ export const MockStoreProvider = ({ children }) => {
return task;
};
const setSubcontractorTaskStatus = (taskId, nextStatus, comment, actor) => {
const now = new Date().toISOString();
let didChange = false;
let companyId = null;
let companyName = null;
let assignedByUserId = null;
setSubcontractorTasks(prev => prev.map(t => {
if (t.id !== taskId) return t;
if (t.status === nextStatus) return t;
didChange = true;
companyId = t.companyId;
companyName = t.companyName;
assignedByUserId = t.assignedBy;
const entry = {
id: `h_${Date.now()}`,
status: nextStatus,
actorId: actor?.id || t.subcontractorId,
actorName: actor?.name || t.subcontractorName,
comment: comment?.trim() || '',
at: now,
};
return {
...t,
status: nextStatus,
statusHistory: [...(t.statusHistory || []), entry],
updatedAt: now,
};
}));
if (didChange) {
// Notify owner / admin who assigned the task
if (assignedByUserId) {
addNotification({
recipientUserId: assignedByUserId,
recipientRole: 'OWNER',
type: 'status_changed',
message: `Status changed to ${nextStatus} by ${actor?.name || 'subcontractor'}`,
taskId,
fromCompanyId: companyId,
fromCompanyName: companyName,
});
}
toast.success(`Status updated to ${nextStatus}`);
}
};
const addSubcontractorTaskMessage = (taskId, { body, channel = 'external', sender }) => {
if (!body?.trim()) return;
const now = new Date().toISOString();
let companyId = null;
let companyName = null;
let assignedByUserId = null;
let subId = null;
setSubcontractorTasks(prev => prev.map(t => {
if (t.id !== taskId) return t;
companyId = t.companyId;
companyName = t.companyName;
assignedByUserId = t.assignedBy;
subId = t.subcontractorId;
const msg = {
id: `m_${Date.now()}`,
channel,
senderId: sender?.id,
senderName: sender?.name || 'You',
senderRole: sender?.role || 'Subcontractor',
body: body.trim(),
at: now,
mine: true,
};
return {
...t,
thread: [...(t.thread || []), msg],
updatedAt: now,
};
}));
// Notify the other side
const recipientUserId = sender?.id === assignedByUserId ? subId : assignedByUserId;
if (recipientUserId) {
addNotification({
recipientUserId,
recipientRole: sender?.id === assignedByUserId ? 'SUBCONTRACTOR' : 'OWNER',
type: 'message_received',
message: `New message from ${sender?.name || 'a teammate'}`,
taskId,
fromCompanyId: companyId,
fromCompanyName: companyName,
});
}
};
const addSubcontractorTaskExpense = (taskId, fields) => {
const now = new Date().toISOString();
setSubcontractorTasks(prev => prev.map(t => t.id === taskId ? {
...t,
expenses: [
...(t.expenses || []),
{
id: `e_${Date.now()}`,
description: fields.description?.trim() || '',
category: fields.category || 'Materials',
amount: Number(fields.amount) || 0,
notes: fields.notes?.trim() || '',
receiptUrl: fields.receiptUrl || '',
createdAt: now,
},
],
updatedAt: now,
} : t));
toast.success('Expense logged');
};
const addSubcontractorTaskFee = (taskId, fields) => {
const now = new Date().toISOString();
setSubcontractorTasks(prev => prev.map(t => t.id === taskId ? {
...t,
fees: [
...(t.fees || []),
{
id: `f_${Date.now()}`,
description: fields.description?.trim() || '',
type: fields.type || 'Labour / Service Fee',
amount: Number(fields.amount) || 0,
createdAt: now,
},
],
updatedAt: now,
} : t));
toast.success('Fee submitted');
};
const updateSubcontractorTask = (taskId, data) => {
let updatedSubId = null;
let prevSubId = null;
@@ -5779,10 +6381,17 @@ export const MockStoreProvider = ({ children }) => {
subcontractorTasks,
taskPriorities: SUBCONTRACTOR_TASK_PRIORITIES,
taskStatuses: SUBCONTRACTOR_TASK_STATUSES,
subSelfStatuses: SUBCONTRACTOR_SELF_STATUSES,
expenseCategories: SUBCONTRACTOR_EXPENSE_CATEGORIES,
feeTypes: SUBCONTRACTOR_FEE_TYPES,
addSubcontractorTask,
updateSubcontractorTask,
cancelSubcontractorTask,
reassignSubcontractorTask,
setSubcontractorTaskStatus,
addSubcontractorTaskMessage,
addSubcontractorTaskExpense,
addSubcontractorTaskFee,
notifications,
addNotification,
markNotificationRead,