From 96381d4e0a7b24ec2adc631fbb506388f5fbe68f Mon Sep 17 00:00:00 2001 From: Mayur Shinde Date: Thu, 4 Jun 2026 14:53:35 +0530 Subject: [PATCH] Owners box: subcontractor table restrutcured name wise and display projects of each subcon with payment statuses --- .../owner/SubcontractorPaymentsPanel.jsx | 777 +++++++++++------- src/data/mockStore.jsx | 401 ++++++++- 2 files changed, 881 insertions(+), 297 deletions(-) diff --git a/src/components/owner/SubcontractorPaymentsPanel.jsx b/src/components/owner/SubcontractorPaymentsPanel.jsx index 546b71c..4e4dde8 100644 --- a/src/components/owner/SubcontractorPaymentsPanel.jsx +++ b/src/components/owner/SubcontractorPaymentsPanel.jsx @@ -1,6 +1,9 @@ import React, { useMemo, useState, useRef, useEffect } from 'react'; -import { Wallet, Users, Search, ChevronDown, X, Check, FilterX } from 'lucide-react'; +import { Wallet, Users, Search, ChevronDown, ChevronRight, X, Check, FilterX } from 'lucide-react'; +// ───────────────────────────────────────────────────────────────────────────── +// Helpers +// ───────────────────────────────────────────────────────────────────────────── const CATEGORY_COLORS = { 'Electrical': 'text-cyan-500 dark:text-cyan-400 bg-cyan-500/10 border-cyan-500/20', 'Roofing': 'text-orange-500 dark:text-orange-400 bg-orange-500/10 border-orange-500/20', @@ -9,47 +12,67 @@ const CATEGORY_COLORS = { 'Windows & Glazing': 'text-teal-500 dark:text-teal-400 bg-teal-500/10 border-teal-500/20', }; -const PAYMENT_STATUS_COLORS = { - 'Paid': 'text-emerald-600 dark:text-emerald-400 bg-emerald-500/10 border-emerald-500/20', - 'Unpaid': 'text-amber-600 dark:text-amber-400 bg-amber-500/10 border-amber-500/20', +// Contractor-level status: Paid | Partially Paid | Unpaid +const CONTRACTOR_STATUS = { + Paid: 'text-emerald-700 dark:text-emerald-400 bg-emerald-500/10 border-emerald-500/25', + 'Partially Paid':'text-amber-700 dark:text-amber-400 bg-amber-500/10 border-amber-500/25', + Unpaid: 'text-red-700 dark:text-red-400 bg-red-500/10 border-red-500/25', +}; + +// Request-level status colours +const REQUEST_STATUS = { + Paid: 'text-emerald-600 dark:text-emerald-400 bg-emerald-500/10 border-emerald-500/20', + 'Partially Paid':'text-amber-600 dark:text-amber-400 bg-amber-500/10 border-amber-500/20', + Unpaid: 'text-red-600 dark:text-red-400 bg-red-500/10 border-red-500/20', }; function taskTotal(task) { const expenses = (task.expenses || []).reduce((s, e) => s + (Number(e.amount) || 0), 0); - const fees = (task.fees || []).reduce((s, f) => s + (Number(f.amount) || 0), 0); + const fees = (task.fees || []).reduce((s, f) => s + (Number(f.amount) || 0), 0); return expenses + fees; } +function taskPaid(task) { + return Number(task.amountPaid || 0); +} + const fmt = (amt) => new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0 }).format(Number(amt) || 0); const fmtDate = (iso) => { if (!iso) return '—'; - try { - return new Date(iso).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }); - } catch { - return iso; - } + try { return new Date(iso).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }); } + catch { return iso; } }; -// Dark-mode-aware dropdown — replaces native setNameQuery(e.target.value)} className="w-full bg-zinc-100 dark:bg-black/40 border border-zinc-200 dark:border-white/10 rounded-xl py-2 pl-9 pr-8 text-sm text-zinc-900 dark:text-white placeholder:text-zinc-400 dark:placeholder:text-zinc-500 outline-none focus:ring-2 focus:ring-amber-500/20 focus:border-amber-500/40" /> {nameQuery && ( - )} - {/* Project searchable dropdown */} -
- - {projectOpen && ( -
-
- - setProjectQuery(e.target.value)} - className="w-full bg-zinc-50 dark:bg-black/40 border border-zinc-200 dark:border-white/10 rounded-lg py-1.5 pl-8 pr-2 text-sm text-zinc-900 dark:text-white placeholder:text-zinc-400 dark:placeholder:text-zinc-500 outline-none" - /> -
-
- - {filteredProjectOptions.length === 0 ? ( -

No projects match "{projectQuery}"

- ) : ( - filteredProjectOptions.map(p => { - const isSelected = p.name === projectFilter; - return ( - - ); - }) - )} -
-
- )} -
- - {/* Work Category filter */} + {/* Category filter */} - {/* Payment Status filter */} + {/* Contractor-status filter */} {hasActiveFilters && ( -
-
@@ -358,122 +512,155 @@ const SubcontractorPaymentsPanel = ({ )} - {rows.length === 0 ? ( + {/* Empty state */} + {contractorGroups.length === 0 ? (

No subcontractor payments yet.

Tasks with logged fees or expenses will appear here.

) : ( -
- - - - - - - - - - - - - - {filteredRows.length === 0 && ( - - - - )} - {filteredRows.map((row) => ( - - - - - - - - - - ))} - - - - - - - - - - - -
SubcontractorProjectWork CategoryAmount to PayRequestedPaid DatePayment Status
- -

No payments match the current filters.

+
+ + {/* ── Parent table header ── */} +
+ {['Contractor Name', 'Payment Requested', 'Amount Left to be Paid', 'Status', ''].map((h, i) => ( +
= 1 && i <= 2 ? 'text-right' : ''}`}> + {h} +
+ ))} +
+ + {filteredContractors.length === 0 ? ( +
+ +

No contractors match the current filters.

+ +
+ ) : ( +
+ {filteredContractors.map((c) => { + const remaining = Math.max(0, c.totalRequested - c.totalPaid); + const cStatus = contractorStatus(c.totalRequested, c.totalPaid); + const isExpanded = expanded.has(c.subId); + + return ( +
+ {/* ── Parent row — desktop grid ── */} -
-
-
- {row.name?.[0] || '?'} -
-
-
{row.name}
- {row.companyName && ( -
{row.companyName}
- )} -
-
-
-
-
- {row.projectName} -
- {row.projectAddress && ( -
- {row.projectAddress} + {/* Desktop */} +
+ {/* Contractor name */} +
+
+ {c.subName?.[0] || '?'} +
+
+
{c.subName}
+ {c.companyName &&
{c.companyName}
} +
+ {c.projects.length} project{c.projects.length !== 1 ? 's' : ''} · {allRequests.filter(r => r.subId === c.subId).length} request{allRequests.filter(r => r.subId === c.subId).length !== 1 ? 's' : ''} +
+
- )} -
-
- - {row.workCategory} - - - - {fmt(row.amount)} - - - - {fmtDate(row.requestedAt)} - - - - {fmtDate(row.paidAt)} - - - - {row.paymentStatus} - -
- {hasActiveFilters ? 'Filtered Total' : 'Total Amount'} - - - {fmt(total)} + {/* Payment Requested */} +
+ {fmt(c.totalRequested)} +
+ {/* Amount Left */} +
+ 0 ? 'text-red-500 dark:text-red-400' : 'text-zinc-400 dark:text-zinc-500'}`}> + {remaining > 0 ? fmt(remaining) : '—'} + +
+ {/* Status */} +
+ +
+ {/* Chevron */} +
+ {isExpanded + ? + : + } +
+ + + {/* Mobile card */} +
+
+
+
+ {c.subName?.[0] || '?'} +
+
+
{c.subName}
+ {c.companyName &&
{c.companyName}
} +
+
+
+ + {isExpanded + ? + : + } +
+
+
+
Requested: {fmt(c.totalRequested)}
+
Outstanding: 0 ? 'text-red-500 dark:text-red-400' : 'text-zinc-400'}`}>{remaining > 0 ? fmt(remaining) : '—'}
+
+
+ + + {/* ── Expanded detail ── */} + {isExpanded && ( +
+ +
+ )} + + ); + })} + + )} + + {/* Footer totals */} + {filteredContractors.length > 0 && ( +
+ {/* Desktop totals row */} +
+
+ {hasActiveFilters ? 'Filtered Total' : 'Grand Total'} +
+
+ {fmt(grandTotalRequested)} +
+
+ 0 ? 'text-red-500 dark:text-red-400' : 'text-zinc-400'}`}> + {grandTotalRemaining > 0 ? fmt(grandTotalRemaining) : '—'} -
-
- Outstanding (Unpaid) - - - {fmt(outstanding)} - - -
+
+
+
+ {/* Mobile totals */} +
+ {hasActiveFilters ? 'Filtered Total' : 'Grand Total'} +
+ {fmt(grandTotalRequested)} requested + {grandTotalRemaining > 0 && {fmt(grandTotalRemaining)} outstanding} +
+
+ + )} )} diff --git a/src/data/mockStore.jsx b/src/data/mockStore.jsx index 50584c7..b03932a 100644 --- a/src/data/mockStore.jsx +++ b/src/data/mockStore.jsx @@ -7388,8 +7388,10 @@ const MOCK_SUBCONTRACTOR_TASKS = [ }, ], projectId: 'PRJ-2026-004', - paymentStatus: 'Unpaid', + paymentStatus: 'Partially Paid', paymentRequestedAt: '2026-05-16T17:30:00Z', + amountPaid: 350, + paidAt: '2026-05-20T11:00:00Z', createdAt: '2026-05-15T09:12:00Z', updatedAt: '2026-05-16T17:30:00Z', }, @@ -7484,6 +7486,7 @@ const MOCK_SUBCONTRACTOR_TASKS = [ projectId: 'PRJ-2026-003', paymentStatus: 'Unpaid', paymentRequestedAt: '2026-05-12T17:00:00Z', + amountPaid: 0, createdAt: '2026-05-10T15:40:00Z', updatedAt: '2026-05-12T17:00:00Z', }, @@ -7561,6 +7564,7 @@ const MOCK_SUBCONTRACTOR_TASKS = [ projectId: 'PRJ-2026-006', paymentStatus: 'Paid', paymentRequestedAt: '2026-05-14T17:35:00Z', + amountPaid: 232, paidAt: '2026-05-18T10:15:00Z', createdAt: '2026-05-05T11:00:00Z', updatedAt: '2026-05-14T17:30:00Z', @@ -7603,6 +7607,7 @@ const MOCK_SUBCONTRACTOR_TASKS = [ projectId: 'PRJ-2026-001', paymentStatus: 'Unpaid', paymentRequestedAt: null, + amountPaid: 0, createdAt: '2026-05-18T22:40:00Z', updatedAt: '2026-05-18T23:10:00Z', }, @@ -7672,8 +7677,10 @@ const MOCK_SUBCONTRACTOR_TASKS = [ }, ], projectId: 'PRJ-2026-007', - paymentStatus: 'Unpaid', + paymentStatus: 'Partially Paid', paymentRequestedAt: '2026-05-14T13:00:00Z', + amountPaid: 80, + paidAt: '2026-05-18T09:00:00Z', createdAt: '2026-05-12T10:00:00Z', updatedAt: '2026-05-15T16:20:00Z', }, @@ -7733,6 +7740,7 @@ const MOCK_SUBCONTRACTOR_TASKS = [ projectId: 'PRJ-2026-009', paymentStatus: 'Unpaid', paymentRequestedAt: null, + amountPaid: 0, createdAt: '2026-05-17T14:00:00Z', updatedAt: '2026-05-26T11:00:00Z', }, @@ -7812,6 +7820,7 @@ const MOCK_SUBCONTRACTOR_TASKS = [ projectId: 'PRJ-2026-001', paymentStatus: 'Paid', paymentRequestedAt: '2026-05-07T16:50:00Z', + amountPaid: 1089, paidAt: '2026-05-12T14:00:00Z', createdAt: '2026-05-01T09:00:00Z', updatedAt: '2026-05-07T17:00:00Z', @@ -7908,6 +7917,7 @@ const MOCK_SUBCONTRACTOR_TASKS = [ projectId: 'PRJ-2026-004', paymentStatus: 'Unpaid', paymentRequestedAt: '2026-05-19T12:35:00Z', + amountPaid: 0, createdAt: '2026-05-16T13:00:00Z', updatedAt: '2026-05-19T18:00:00Z', }, @@ -7996,6 +8006,7 @@ const MOCK_SUBCONTRACTOR_TASKS = [ projectId: 'PRJ-2026-006', paymentStatus: 'Unpaid', paymentRequestedAt: null, + amountPaid: 0, createdAt: '2026-05-20T09:00:00Z', updatedAt: '2026-05-27T11:05:00Z', }, @@ -8049,9 +8060,395 @@ const MOCK_SUBCONTRACTOR_TASKS = [ projectId: null, paymentStatus: 'Unpaid', paymentRequestedAt: null, + amountPaid: 0, createdAt: '2026-05-09T10:00:00Z', updatedAt: '2026-05-20T15:30:00Z', }, + + // ------------------------------------------------------------------- + // sct_011 — Maya Patel (sub_002) — full roof replacement PRJ-2026-001, PAID + // ------------------------------------------------------------------- + { + id: 'sct_011', + companyId: 'lynkeduppro', + companyName: 'LynkedUp Pro Roofing', + subcontractorId: 'sub_002', + subcontractorName: 'Maya Patel', + assignedBy: 'own_001', + assignedByName: 'Justin Johnson', + title: 'Full roof replacement — PRJ-001', + location: '2604 Dunwick Dr, Plano, TX 75023', + description: 'Insurance-approved full tear-off and replacement. 28 sq IKO Cambridge Charcoal. Includes ice & water shield at eaves.', + dueDate: '2026-04-20', + priority: 'high', + category: 'Roofing', + assignedDate: '2026-04-10T09:00:00Z', + status: 'Completed', + photos: [], + statusHistory: [ + { id: 'sct_011_h1', status: 'Assigned', actorId: 'own_001', actorName: 'Justin Johnson', comment: 'Task assigned.', at: '2026-04-10T09:00:00Z' }, + { id: 'sct_011_h2', status: 'Completed', actorId: 'sub_002', actorName: 'Maya Patel', comment: 'Roof complete.', at: '2026-04-18T15:00:00Z' }, + ], + thread: [], + expenses: [ + { id: 'sct_011_e1', description: 'IKO Cambridge Charcoal — 28 sq', category: 'Materials', amount: 2940, notes: '', receiptUrl: '', createdAt: '2026-04-11T08:00:00Z' }, + { id: 'sct_011_e2', description: 'Ice & water shield — 4 rolls', category: 'Materials', amount: 320, notes: '', receiptUrl: '', createdAt: '2026-04-11T08:10:00Z' }, + { id: 'sct_011_e3', description: 'Dumpster — 3 days', category: 'Disposal / Dumpster', amount: 420, notes: '', receiptUrl: '', createdAt: '2026-04-18T16:00:00Z' }, + { id: 'sct_011_e4', description: 'Fuel & crew transport', category: 'Travel / Fuel', amount: 140, notes: '', receiptUrl: '', createdAt: '2026-04-18T17:00:00Z' }, + ], + fees: [ + { id: 'sct_011_f1', description: 'Labour — full replacement (3-day crew)', type: 'Labour / Service Fee', amount: 4200, createdAt: '2026-04-18T15:30:00Z' }, + { id: 'sct_011_f2', description: 'Mobilization', type: 'Mobilization Fee', amount: 200, createdAt: '2026-04-10T09:00:00Z' }, + ], + activities: [], + projectId: 'PRJ-2026-001', + paymentStatus: 'Paid', + paymentRequestedAt: '2026-04-18T16:00:00Z', + amountPaid: 8220, + paidAt: '2026-04-25T10:00:00Z', + createdAt: '2026-04-10T09:00:00Z', + updatedAt: '2026-04-18T17:00:00Z', + }, + + // ------------------------------------------------------------------- + // sct_012 — Maya Patel (sub_002) — shingle repair PRJ-2026-003, Partially Paid + // ------------------------------------------------------------------- + { + id: 'sct_012', + companyId: 'lynkeduppro', + companyName: 'LynkedUp Pro Roofing', + subcontractorId: 'sub_002', + subcontractorName: 'Maya Patel', + assignedBy: 'a1', + assignedByName: 'Wade Hollis', + title: 'Hail damage shingle repair — PRJ-003', + location: '6613 Phoenix Pl, Plano, TX 75023', + description: 'Replace 6 damaged shingle sections on west slope identified during adjuster inspection.', + dueDate: '2026-05-10', + priority: 'medium', + category: 'Roofing', + assignedDate: '2026-05-02T09:00:00Z', + status: 'Completed', + photos: [], + statusHistory: [ + { id: 'sct_012_h1', status: 'Assigned', actorId: 'a1', actorName: 'Wade Hollis', comment: 'Assigned.', at: '2026-05-02T09:00:00Z' }, + { id: 'sct_012_h2', status: 'Completed', actorId: 'sub_002', actorName: 'Maya Patel', comment: 'Complete.', at: '2026-05-09T14:00:00Z' }, + ], + thread: [], + expenses: [ + { id: 'sct_012_e1', description: 'Materials — premium shingles, underlayment & sealant', category: 'Materials', amount: 1200, notes: '', receiptUrl: '', createdAt: '2026-05-03T07:30:00Z' }, + ], + fees: [ + { id: 'sct_012_f1', description: 'Labour — shingle repair and roof patch', type: 'Labour / Service Fee', amount: 3800, createdAt: '2026-05-09T14:10:00Z' }, + ], + activities: [], + projectId: 'PRJ-2026-003', + paymentStatus: 'Partially Paid', + paymentRequestedAt: '2026-05-09T15:00:00Z', + amountPaid: 4000, + paidAt: '2026-05-15T09:30:00Z', + createdAt: '2026-05-02T09:00:00Z', + updatedAt: '2026-05-09T15:00:00Z', + }, + + // ------------------------------------------------------------------- + // sct_013 — Carlos Mendoza (sub_001) — electrical rough-in PRJ-2026-001, PAID + // ------------------------------------------------------------------- + { + id: 'sct_013', + companyId: 'lynkeduppro', + companyName: 'LynkedUp Pro Roofing', + subcontractorId: 'sub_001', + subcontractorName: 'Carlos Mendoza', + assignedBy: 'own_001', + assignedByName: 'Justin Johnson', + title: 'Electrical rough-in — PRJ-001 addition', + location: '2604 Dunwick Dr, Plano, TX 75023', + description: 'Rough-in wiring for garage addition: 2 circuits, 4 outlets, overhead LED fixture prewire.', + dueDate: '2026-04-15', + priority: 'medium', + category: 'Electrical', + assignedDate: '2026-04-08T10:00:00Z', + status: 'Completed', + photos: [], + statusHistory: [ + { id: 'sct_013_h1', status: 'Assigned', actorId: 'own_001', actorName: 'Justin Johnson', comment: 'Assigned.', at: '2026-04-08T10:00:00Z' }, + { id: 'sct_013_h2', status: 'Completed', actorId: 'sub_001', actorName: 'Carlos Mendoza', comment: 'Complete.', at: '2026-04-14T16:00:00Z' }, + ], + thread: [], + expenses: [ + { id: 'sct_013_e1', description: '14/2 NM cable — 100 ft', category: 'Materials', amount: 68, notes: '', receiptUrl: '', createdAt: '2026-04-09T07:00:00Z' }, + { id: 'sct_013_e2', description: 'Outlet boxes + covers', category: 'Materials', amount: 32, notes: '', receiptUrl: '', createdAt: '2026-04-09T07:05:00Z' }, + { id: 'sct_013_e3', description: 'Conduit + fittings', category: 'Materials', amount: 41, notes: '', receiptUrl: '', createdAt: '2026-04-09T07:10:00Z' }, + ], + fees: [ + { id: 'sct_013_f1', description: 'Labour — rough-in (1.5 days)', type: 'Labour / Service Fee', amount: 640, createdAt: '2026-04-14T16:10:00Z' }, + { id: 'sct_013_f2', description: 'Mobilization', type: 'Mobilization Fee', amount: 75, createdAt: '2026-04-08T10:00:00Z' }, + ], + activities: [], + projectId: 'PRJ-2026-001', + paymentStatus: 'Paid', + paymentRequestedAt: '2026-04-14T17:00:00Z', + amountPaid: 856, + paidAt: '2026-04-20T10:00:00Z', + createdAt: '2026-04-08T10:00:00Z', + updatedAt: '2026-04-14T17:00:00Z', + }, + + // ------------------------------------------------------------------- + // sct_014 — Jennifer Castillo (sub_004) — plumbing PRJ-2026-004, Paid + // ------------------------------------------------------------------- + { + id: 'sct_014', + companyId: 'lynkeduppro', + companyName: 'LynkedUp Pro Roofing', + subcontractorId: 'sub_004', + subcontractorName: 'Jennifer Castillo', + assignedBy: 'own_001', + assignedByName: 'Justin Johnson', + title: 'Re-pipe master bath supply lines', + location: '2612 Dunwick Dr, Plano, TX 75023', + description: 'Replace old galvanized supply lines in master bath with 1/2" PEX. Two shut-offs, one shower valve, one sink valve.', + dueDate: '2026-04-30', + priority: 'medium', + category: 'Plumbing', + assignedDate: '2026-04-22T08:00:00Z', + status: 'Completed', + photos: [], + statusHistory: [ + { id: 'sct_014_h1', status: 'Assigned', actorId: 'own_001', actorName: 'Justin Johnson', comment: 'Assigned.', at: '2026-04-22T08:00:00Z' }, + { id: 'sct_014_h2', status: 'Completed', actorId: 'sub_004', actorName: 'Jennifer Castillo', comment: 'Complete.', at: '2026-04-28T15:00:00Z' }, + ], + thread: [], + expenses: [ + { id: 'sct_014_e1', description: '1/2" PEX — 50 ft', category: 'Materials', amount: 62, notes: '', receiptUrl: '', createdAt: '2026-04-23T07:30:00Z' }, + { id: 'sct_014_e2', description: 'PEX fittings + crimp rings', category: 'Materials', amount: 44, notes: '', receiptUrl: '', createdAt: '2026-04-23T07:35:00Z' }, + { id: 'sct_014_e3', description: 'Shower valve (Moen 2520)', category: 'Materials', amount: 128, notes: '', receiptUrl: '', createdAt: '2026-04-23T07:40:00Z' }, + ], + fees: [ + { id: 'sct_014_f1', description: 'Labour — master bath re-pipe', type: 'Labour / Service Fee', amount: 740, createdAt: '2026-04-28T15:10:00Z' }, + ], + activities: [], + projectId: 'PRJ-2026-004', + paymentStatus: 'Paid', + paymentRequestedAt: '2026-04-28T16:00:00Z', + amountPaid: 974, + paidAt: '2026-05-05T10:00:00Z', + createdAt: '2026-04-22T08:00:00Z', + updatedAt: '2026-04-28T16:00:00Z', + }, + + // ------------------------------------------------------------------- + // sct_015 — Jennifer Castillo (sub_004) — PRJ-2026-007, Partially Paid + // ------------------------------------------------------------------- + { + id: 'sct_015', + companyId: 'lynkeduppro', + companyName: 'LynkedUp Pro Roofing', + subcontractorId: 'sub_004', + subcontractorName: 'Jennifer Castillo', + assignedBy: 'a1', + assignedByName: 'Wade Hollis', + title: 'Hot water heater replacement — PRJ-007', + location: '6909 Custer Rd, Plano, TX 75023', + description: 'Replace 40-gal gas water heater (unit failed). Install Bradford White 40-gal NG, reconnect supply & drain.', + dueDate: '2026-05-20', + priority: 'high', + category: 'Plumbing', + assignedDate: '2026-05-14T08:00:00Z', + status: 'Completed', + photos: [], + statusHistory: [ + { id: 'sct_015_h1', status: 'Assigned', actorId: 'a1', actorName: 'Wade Hollis', comment: 'Assigned.', at: '2026-05-14T08:00:00Z' }, + { id: 'sct_015_h2', status: 'Completed', actorId: 'sub_004', actorName: 'Jennifer Castillo', comment: 'Complete.', at: '2026-05-16T13:00:00Z' }, + ], + thread: [], + expenses: [ + { id: 'sct_015_e1', description: 'Bradford White 40-gal NG heater', category: 'Materials', amount: 680, notes: '', receiptUrl: '', createdAt: '2026-05-14T10:00:00Z' }, + { id: 'sct_015_e2', description: 'Gas flex connector + fittings', category: 'Materials', amount: 48, notes: '', receiptUrl: '', createdAt: '2026-05-14T10:05:00Z' }, + { id: 'sct_015_e3', description: 'Expansion tank + pressure relief', category: 'Materials', amount: 95, notes: '', receiptUrl: '', createdAt: '2026-05-14T10:10:00Z' }, + { id: 'sct_015_e4', description: 'Haul-away — old unit', category: 'Disposal / Dumpster', amount: 65, notes: '', receiptUrl: '', createdAt: '2026-05-16T14:00:00Z' }, + ], + fees: [ + { id: 'sct_015_f1', description: 'Labour — WHR install', type: 'Labour / Service Fee', amount: 420, createdAt: '2026-05-16T13:10:00Z' }, + { id: 'sct_015_f2', description: 'Mobilization', type: 'Mobilization Fee', amount: 75, createdAt: '2026-05-14T08:00:00Z' }, + ], + activities: [], + projectId: 'PRJ-2026-007', + paymentStatus: 'Partially Paid', + paymentRequestedAt: '2026-05-16T14:00:00Z', + amountPaid: 700, + paidAt: '2026-05-22T10:00:00Z', + createdAt: '2026-05-14T08:00:00Z', + updatedAt: '2026-05-16T14:00:00Z', + }, + + // ------------------------------------------------------------------- + // sct_016 — Dwayne Boudreaux (sub_003) — interior repaint PRJ-2026-004, Paid + // ------------------------------------------------------------------- + { + id: 'sct_016', + companyId: 'lynkeduppro', + companyName: 'LynkedUp Pro Roofing', + subcontractorId: 'sub_003', + subcontractorName: 'Dwayne Boudreaux', + assignedBy: 'own_001', + assignedByName: 'Justin Johnson', + title: 'Interior repaint — living & dining', + location: '2612 Dunwick Dr, Plano, TX 75023', + description: 'Repaint living room and dining area after drywall repair. 2 coats SW Accessible Beige. Mask trim.', + dueDate: '2026-05-05', + priority: 'low', + category: 'Painting', + assignedDate: '2026-04-28T11:00:00Z', + status: 'Completed', + photos: [], + statusHistory: [ + { id: 'sct_016_h1', status: 'Assigned', actorId: 'own_001', actorName: 'Justin Johnson', comment: 'Assigned.', at: '2026-04-28T11:00:00Z' }, + { id: 'sct_016_h2', status: 'Completed', actorId: 'sub_003', actorName: 'Dwayne Boudreaux', comment: 'Complete.', at: '2026-05-03T15:00:00Z' }, + ], + thread: [], + expenses: [ + { id: 'sct_016_e1', description: 'SW Accessible Beige — 2 gal', category: 'Materials', amount: 104, notes: '', receiptUrl: '', createdAt: '2026-04-29T08:00:00Z' }, + { id: 'sct_016_e2', description: 'Tape, drop cloths, roller kit', category: 'Materials', amount: 38, notes: '', receiptUrl: '', createdAt: '2026-04-29T08:05:00Z' }, + ], + fees: [ + { id: 'sct_016_f1', description: 'Labour — 2-day interior repaint', type: 'Labour / Service Fee', amount: 760, createdAt: '2026-05-03T15:10:00Z' }, + ], + activities: [], + projectId: 'PRJ-2026-004', + paymentStatus: 'Paid', + paymentRequestedAt: '2026-05-03T16:00:00Z', + amountPaid: 902, + paidAt: '2026-05-10T09:00:00Z', + createdAt: '2026-04-28T11:00:00Z', + updatedAt: '2026-05-03T16:00:00Z', + }, + + // ------------------------------------------------------------------- + // sct_017 — Dwayne Boudreaux (sub_003) — PRJ-2026-007 ceiling repaint, Unpaid + // ------------------------------------------------------------------- + { + id: 'sct_017', + companyId: 'lynkeduppro', + companyName: 'LynkedUp Pro Roofing', + subcontractorId: 'sub_003', + subcontractorName: 'Dwayne Boudreaux', + assignedBy: 'a1', + assignedByName: 'Wade Hollis', + title: 'Ceiling repaint after water damage — PRJ-007', + location: '6909 Custer Rd, Plano, TX 75023', + description: 'Repaint ceilings in master bedroom and en-suite after remediation. Stain block primer + 2 coats flat white.', + dueDate: '2026-05-28', + priority: 'medium', + category: 'Painting', + assignedDate: '2026-05-18T10:00:00Z', + status: 'Completed', + photos: [], + statusHistory: [ + { id: 'sct_017_h1', status: 'Assigned', actorId: 'a1', actorName: 'Wade Hollis', comment: 'Assigned.', at: '2026-05-18T10:00:00Z' }, + { id: 'sct_017_h2', status: 'Completed', actorId: 'sub_003', actorName: 'Dwayne Boudreaux', comment: 'Complete.', at: '2026-05-25T14:00:00Z' }, + ], + thread: [], + expenses: [ + { id: 'sct_017_e1', description: 'Zinsser stain-block primer — 1 gal', category: 'Materials', amount: 52, notes: '', receiptUrl: '', createdAt: '2026-05-19T08:00:00Z' }, + { id: 'sct_017_e2', description: 'Ceiling flat white — 1 gal', category: 'Materials', amount: 42, notes: '', receiptUrl: '', createdAt: '2026-05-19T08:05:00Z' }, + ], + fees: [ + { id: 'sct_017_f1', description: 'Labour — ceiling repaint (1.5 days)', type: 'Labour / Service Fee', amount: 580, createdAt: '2026-05-25T14:10:00Z' }, + ], + activities: [], + projectId: 'PRJ-2026-007', + paymentStatus: 'Unpaid', + paymentRequestedAt: '2026-05-25T15:00:00Z', + amountPaid: 0, + createdAt: '2026-05-18T10:00:00Z', + updatedAt: '2026-05-25T15:00:00Z', + }, + + // ------------------------------------------------------------------- + // sct_018 — Greg Alston (sub_005) — window replacement PRJ-2026-003, Partially Paid + // ------------------------------------------------------------------- + { + id: 'sct_018', + companyId: 'lynkeduppro', + companyName: 'LynkedUp Pro Roofing', + subcontractorId: 'sub_005', + subcontractorName: 'Greg Alston', + assignedBy: 'own_001', + assignedByName: 'Justin Johnson', + title: 'Replace 3 hail-damaged windows — PRJ-003', + location: '6613 Phoenix Pl, Plano, TX 75023', + description: 'Insurance scope: replace 3 double-hung windows (front elevation). Source Andersen 400 series double-pane. Include disposal of broken units.', + dueDate: '2026-05-15', + priority: 'high', + category: 'Windows & Glazing', + assignedDate: '2026-05-03T09:00:00Z', + status: 'Completed', + photos: [], + statusHistory: [ + { id: 'sct_018_h1', status: 'Assigned', actorId: 'own_001', actorName: 'Justin Johnson', comment: 'Assigned.', at: '2026-05-03T09:00:00Z' }, + { id: 'sct_018_h2', status: 'Completed', actorId: 'sub_005', actorName: 'Greg Alston', comment: 'Complete.', at: '2026-05-13T15:00:00Z' }, + ], + thread: [], + expenses: [ + { id: 'sct_018_e1', description: 'Andersen 400 DH windows (qty 3)', category: 'Materials', amount: 1470, notes: '', receiptUrl: '', createdAt: '2026-05-04T08:00:00Z' }, + { id: 'sct_018_e2', description: 'Weatherstrip + sealant', category: 'Materials', amount: 55, notes: '', receiptUrl: '', createdAt: '2026-05-04T08:05:00Z' }, + { id: 'sct_018_e3', description: 'Disposal — broken units', category: 'Disposal / Dumpster', amount: 90, notes: '', receiptUrl: '', createdAt: '2026-05-13T16:00:00Z' }, + ], + fees: [ + { id: 'sct_018_f1', description: 'Labour — window replacement (2 days)', type: 'Labour / Service Fee', amount: 960, createdAt: '2026-05-13T15:10:00Z' }, + { id: 'sct_018_f2', description: 'Mobilization', type: 'Mobilization Fee', amount: 100, createdAt: '2026-05-03T09:00:00Z' }, + ], + activities: [], + projectId: 'PRJ-2026-003', + paymentStatus: 'Partially Paid', + paymentRequestedAt: '2026-05-13T16:00:00Z', + amountPaid: 1500, + paidAt: '2026-05-20T10:00:00Z', + createdAt: '2026-05-03T09:00:00Z', + updatedAt: '2026-05-13T16:00:00Z', + }, + + // ------------------------------------------------------------------- + // sct_019 — Greg Alston (sub_005) — window sealing PRJ-2026-009, Unpaid + // ------------------------------------------------------------------- + { + id: 'sct_019', + companyId: 'lynkeduppro', + companyName: 'LynkedUp Pro Roofing', + subcontractorId: 'sub_005', + subcontractorName: 'Greg Alston', + assignedBy: 'a1', + assignedByName: 'Wade Hollis', + title: 'Re-seal & caulk all windows — PRJ-009', + location: '7224 Independence Pkwy, Plano, TX 75025', + description: 'Full perimeter re-seal on 8 windows. Remove old silicone, clean frames, apply new NP1 polyurethane sealant.', + dueDate: '2026-06-05', + priority: 'low', + category: 'Windows & Glazing', + assignedDate: '2026-05-22T09:00:00Z', + status: 'In Progress', + photos: [], + statusHistory: [ + { id: 'sct_019_h1', status: 'Assigned', actorId: 'a1', actorName: 'Wade Hollis', comment: 'Assigned.', at: '2026-05-22T09:00:00Z' }, + { id: 'sct_019_h2', status: 'In Progress', actorId: 'sub_005', actorName: 'Greg Alston', comment: 'In progress.', at: '2026-05-28T08:30:00Z' }, + ], + thread: [], + expenses: [ + { id: 'sct_019_e1', description: 'NP1 polyurethane sealant (6 tubes)', category: 'Materials', amount: 78, notes: '', receiptUrl: '', createdAt: '2026-05-23T07:00:00Z' }, + ], + fees: [ + { id: 'sct_019_f1', description: 'Labour — window sealing (1 day)', type: 'Labour / Service Fee', amount: 380, createdAt: '2026-05-28T12:00:00Z' }, + ], + activities: [], + projectId: 'PRJ-2026-009', + paymentStatus: 'Unpaid', + paymentRequestedAt: '2026-05-28T12:00:00Z', + amountPaid: 0, + createdAt: '2026-05-22T09:00:00Z', + updatedAt: '2026-05-28T12:00:00Z', + }, ]; const MOCK_NOTIFICATIONS = [