Owners box:navigate user to project details page when click on project from subcontractor table list
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import React, { useMemo, useState, useRef, useEffect } from 'react';
|
import React, { useMemo, useState, useRef, useEffect } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { Wallet, Users, Search, ChevronDown, ChevronRight, X, Check, FilterX } from 'lucide-react';
|
import { Wallet, Users, Search, ChevronDown, ChevronRight, X, Check, FilterX } from 'lucide-react';
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
@@ -134,17 +135,25 @@ function StatusBadge({ status, colorMap }) {
|
|||||||
// Expanded detail view (project → requests hierarchy)
|
// Expanded detail view (project → requests hierarchy)
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
function ContractorDetail({ projects }) {
|
function ContractorDetail({ projects }) {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-3 py-3 px-3 sm:px-5">
|
<div className="flex flex-col gap-3 py-3 px-3 sm:px-5">
|
||||||
{projects.map((proj, pi) => (
|
{projects.map((proj, pi) => (
|
||||||
<div key={proj.projectId || proj.projectName || pi} className="rounded-xl border border-zinc-200 dark:border-white/[0.06] overflow-hidden bg-white dark:bg-zinc-800/50">
|
<div key={proj.projectId || proj.projectName || pi} className="rounded-xl border border-zinc-200 dark:border-white/[0.06] overflow-hidden bg-white dark:bg-zinc-800/50">
|
||||||
{/* Project header */}
|
{/* Project header */}
|
||||||
<div className="flex flex-wrap items-center gap-x-4 gap-y-1 px-4 py-2.5 bg-zinc-50 dark:bg-white/[0.03] border-b border-zinc-200 dark:border-white/[0.06]">
|
<div
|
||||||
|
onClick={() => proj.projectId && navigate(`/owner/projects/${proj.projectId}?tab=budget`)}
|
||||||
|
className={`flex flex-wrap items-center gap-x-4 gap-y-1 px-4 py-2.5 bg-zinc-50 dark:bg-white/[0.03] border-b border-zinc-200 dark:border-white/[0.06] ${proj.projectId ? 'cursor-pointer hover:bg-zinc-100 dark:hover:bg-white/[0.06] transition-colors' : ''}`}
|
||||||
|
>
|
||||||
<span className="text-[11px] font-black uppercase tracking-widest text-zinc-500 dark:text-zinc-400">Project</span>
|
<span className="text-[11px] font-black uppercase tracking-widest text-zinc-500 dark:text-zinc-400">Project</span>
|
||||||
<span className="text-xs font-bold text-zinc-800 dark:text-zinc-200 flex-1 min-w-0 truncate">{proj.projectName}</span>
|
<span className="text-xs font-bold text-zinc-800 dark:text-zinc-200 flex-1 min-w-0 truncate hover:text-amber-500 transition-colors">{proj.projectName}</span>
|
||||||
{proj.projectAddress && (
|
{proj.projectAddress && (
|
||||||
<span className="text-[10px] text-zinc-400 dark:text-zinc-500 truncate max-w-[200px]">{proj.projectAddress}</span>
|
<span className="text-[10px] text-zinc-400 dark:text-zinc-500 truncate max-w-[200px]">{proj.projectAddress}</span>
|
||||||
)}
|
)}
|
||||||
|
{proj.projectId && (
|
||||||
|
<ChevronRight size={12} className="text-zinc-400 dark:text-zinc-500 shrink-0" />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Requests — table on md+, cards on mobile */}
|
{/* Requests — table on md+, cards on mobile */}
|
||||||
@@ -298,6 +307,7 @@ const SubcontractorPaymentsPanel = ({
|
|||||||
if (requested <= 0) continue;
|
if (requested <= 0) continue;
|
||||||
const sub = subById.get(task.subcontractorId);
|
const sub = subById.get(task.subcontractorId);
|
||||||
const project = task.projectId ? projectById.get(task.projectId) : null;
|
const project = task.projectId ? projectById.get(task.projectId) : null;
|
||||||
|
if (!project) continue;
|
||||||
entries.push({
|
entries.push({
|
||||||
taskId: task.id,
|
taskId: task.id,
|
||||||
subId: task.subcontractorId,
|
subId: task.subcontractorId,
|
||||||
|
|||||||
@@ -7612,139 +7612,6 @@ const MOCK_SUBCONTRACTOR_TASKS = [
|
|||||||
updatedAt: '2026-05-18T23:10: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: 'a1',
|
|
||||||
assignedByName: 'Wade Hollis',
|
|
||||||
title: 'Inspect attic ventilation',
|
|
||||||
location: '6909 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',
|
|
||||||
category: 'Inspection',
|
|
||||||
assignedDate: '2026-05-12T10:00:00Z',
|
|
||||||
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: 'a1', actorName: 'Wade Hollis', 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: 'group', 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: 'direct', senderId: 'a1', senderName: 'Wade Hollis', 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' },
|
|
||||||
],
|
|
||||||
activities: [
|
|
||||||
{
|
|
||||||
id: 'sct_005_a1', type: 'status_change', status: 'Assigned',
|
|
||||||
note: 'Task created. Customer reports unusually high attic temps.',
|
|
||||||
photos: [],
|
|
||||||
actorId: 'a1', actorName: 'Wade Hollis', actorRole: 'Admin',
|
|
||||||
at: '2026-05-12T10:00:00Z',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'sct_005_a2', type: 'status_change', status: 'In Progress',
|
|
||||||
note: 'Initial inspection pass — soffit baffles appear blocked. Need attic access for confirmation.',
|
|
||||||
photos: [
|
|
||||||
{ id: 'sct_005_a2_p1', url: '/assets/images/properties/Roof_Inspection_Angle.jpg', name: 'south-gable.jpg' },
|
|
||||||
],
|
|
||||||
actorId: 'sub_002', actorName: 'Maya Patel', actorRole: 'Subcontractor',
|
|
||||||
at: '2026-05-14T11:00:00Z',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'sct_005_a3', type: 'status_change', status: 'On Hold',
|
|
||||||
note: 'Work stopped — homeowner needs to clear stored boxes blocking attic access before I can finish.',
|
|
||||||
photos: [
|
|
||||||
{ id: 'sct_005_a3_p1', url: '/assets/images/properties/Beige_Two_Story_House.jpg', name: 'access-blocked.jpg' },
|
|
||||||
],
|
|
||||||
actorId: 'sub_002', actorName: 'Maya Patel', actorRole: 'Subcontractor',
|
|
||||||
at: '2026-05-15T16:00:00Z',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
projectId: 'PRJ-2026-007',
|
|
||||||
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',
|
|
||||||
},
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
|
||||||
// sct_006 — Maya (sub_002) — Gutter replacement, Pre-Work Inspection
|
|
||||||
// -------------------------------------------------------------------
|
|
||||||
{
|
|
||||||
id: 'sct_006',
|
|
||||||
companyId: 'lynkeduppro',
|
|
||||||
companyName: 'LynkedUp Pro Roofing',
|
|
||||||
subcontractorId: 'sub_002',
|
|
||||||
subcontractorName: 'Maya Patel',
|
|
||||||
assignedBy: 'own_001',
|
|
||||||
assignedByName: 'Justin Johnson',
|
|
||||||
title: 'Replace gutter on south elevation',
|
|
||||||
location: '7224 Independence Pkwy, 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',
|
|
||||||
category: 'Gutters',
|
|
||||||
assignedDate: '2026-05-17T14:00:00Z',
|
|
||||||
status: 'Pre-Work Inspection',
|
|
||||||
photos: [
|
|
||||||
{ id: 'sct_006_p1', name: 'Gutter_Damage.jpg', url: '/assets/images/properties/Broken_Roof_Surface.jpg', annotation: 'Gutter pulling away from fascia — south elevation' },
|
|
||||||
],
|
|
||||||
statusHistory: [
|
|
||||||
{ id: 'sct_006_h1', status: 'Assigned', actorId: 'own_001', actorName: 'Justin Johnson', comment: 'Task created and assigned.', at: '2026-05-17T14:00:00Z' },
|
|
||||||
{ id: 'sct_006_h2', status: 'Pre-Work Inspection', actorId: 'sub_002', actorName: 'Maya Patel', comment: 'Site walk done — materials staged, access confirmed. Measured 32 ft run, bronze 6" gutter ordered.', at: '2026-05-26T10:00:00Z' },
|
|
||||||
],
|
|
||||||
thread: [
|
|
||||||
{ id: 'sct_006_t1', channel: 'group', senderId: 'sub_002', senderName: 'Maya Patel', senderRole: 'Subcontractor', body: 'On site for pre-work walkthrough. Gutter is pulling at four hangers — 32 ft run needs full replacement. Ordering materials now.', at: '2026-05-26T10:05:00Z', mine: true },
|
|
||||||
{ id: 'sct_006_t2', channel: 'group', senderId: 'own_001', senderName: 'Justin Johnson', senderRole: 'Owner', body: 'Perfect. Confirm when materials arrive and I will coordinate access with the homeowner.', at: '2026-05-26T10:30:00Z' },
|
|
||||||
],
|
|
||||||
expenses: [
|
|
||||||
{ id: 'sct_006_e1', description: '6" K-style gutter bronze (32 ft)', category: 'Materials', amount: 118.0, notes: 'Special order — delivery expected May 28.', receiptUrl: '', createdAt: '2026-05-26T11:00:00Z' },
|
|
||||||
],
|
|
||||||
fees: [],
|
|
||||||
activities: [
|
|
||||||
{
|
|
||||||
id: 'sct_006_a1', type: 'status_change', status: 'Assigned',
|
|
||||||
note: 'Task created and assigned. 30 ft gutter section pulling away from fascia after storm damage.',
|
|
||||||
photos: [],
|
|
||||||
actorId: 'own_001', actorName: 'Justin Johnson', actorRole: 'Owner',
|
|
||||||
at: '2026-05-17T14:00:00Z',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'sct_006_a2', type: 'status_change', status: 'Pre-Work Inspection',
|
|
||||||
note: 'Site walk done — measured 32 ft run, confirmed bronze colour match, four failed hangers identified. Materials ordered, work scheduled for May 29.',
|
|
||||||
photos: [
|
|
||||||
{ id: 'sct_006_a2_p1', url: '/assets/images/properties/Broken_Roof_Surface.jpg', name: 'gutter-inspection.jpg' },
|
|
||||||
],
|
|
||||||
actorId: 'sub_002', actorName: 'Maya Patel', actorRole: 'Subcontractor',
|
|
||||||
at: '2026-05-26T10:00:00Z',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
projectId: 'PRJ-2026-009',
|
|
||||||
paymentStatus: 'Unpaid',
|
|
||||||
paymentRequestedAt: null,
|
|
||||||
amountPaid: 0,
|
|
||||||
createdAt: '2026-05-17T14:00:00Z',
|
|
||||||
updatedAt: '2026-05-26T11:00:00Z',
|
|
||||||
},
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
// sct_007 — Maya (sub_002) — completed earlier this month
|
// sct_007 — Maya (sub_002) — completed earlier this month
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
@@ -8011,60 +7878,6 @@ const MOCK_SUBCONTRACTOR_TASKS = [
|
|||||||
updatedAt: '2026-05-27T11:05:00Z',
|
updatedAt: '2026-05-27T11:05:00Z',
|
||||||
},
|
},
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
|
||||||
// sct_009 — Jennifer Castillo (sub_004) — Cancelled plumbing
|
|
||||||
// -------------------------------------------------------------------
|
|
||||||
{
|
|
||||||
id: 'sct_009',
|
|
||||||
companyId: 'lynkeduppro',
|
|
||||||
companyName: 'LynkedUp Pro Roofing',
|
|
||||||
subcontractorId: 'sub_004',
|
|
||||||
subcontractorName: 'Jennifer Castillo',
|
|
||||||
assignedBy: 'own_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',
|
|
||||||
category: 'Plumbing',
|
|
||||||
assignedDate: '2026-05-09T10:00:00Z',
|
|
||||||
status: 'Cancelled',
|
|
||||||
photos: [],
|
|
||||||
statusHistory: [
|
|
||||||
{ id: 'sct_009_h1', status: 'Assigned', actorId: 'own_001', actorName: 'Justin Johnson', comment: 'Task created and assigned.', at: '2026-05-09T10:00:00Z' },
|
|
||||||
{ id: 'sct_009_h2', status: 'Cancelled', actorId: 'own_001', actorName: 'Justin Johnson', comment: 'Homeowner cancelled the ADU project — entire scope suspended. Task voided, no further work required.', at: '2026-05-20T15:00:00Z' },
|
|
||||||
],
|
|
||||||
thread: [
|
|
||||||
{ id: 'sct_009_t1', channel: 'group', senderId: 'own_001', senderName: 'Justin Johnson', senderRole: 'Owner', body: 'Jennifer — unfortunately the homeowner has decided to cancel the ADU build entirely. No further work needed on this task. Apologies for the inconvenience.', at: '2026-05-20T15:02:00Z' },
|
|
||||||
{ id: 'sct_009_t2', channel: 'group', senderId: 'sub_004', senderName: 'Jennifer Castillo', senderRole: 'Subcontractor', body: 'Understood — no work had been started so no impact on our end. Thanks for the heads-up.', at: '2026-05-20T15:30:00Z', mine: true },
|
|
||||||
],
|
|
||||||
expenses: [],
|
|
||||||
fees: [],
|
|
||||||
activities: [
|
|
||||||
{
|
|
||||||
id: 'sct_009_a1', type: 'status_change', status: 'Assigned',
|
|
||||||
note: 'Task created. New ADU build — bathroom rough-in to coordinate with framing inspection.',
|
|
||||||
photos: [],
|
|
||||||
actorId: 'own_001', actorName: 'Justin Johnson', actorRole: 'Owner',
|
|
||||||
at: '2026-05-09T10:00:00Z',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'sct_009_a2', type: 'status_change', status: 'Cancelled',
|
|
||||||
note: 'Task cancelled — homeowner suspended the ADU project. No work was started.',
|
|
||||||
photos: [],
|
|
||||||
actorId: 'own_001', actorName: 'Justin Johnson', actorRole: 'Owner',
|
|
||||||
at: '2026-05-20T15:00:00Z',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
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
|
// sct_011 — Maya Patel (sub_002) — full roof replacement PRJ-2026-001, PAID
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
@@ -8238,51 +8051,6 @@ const MOCK_SUBCONTRACTOR_TASKS = [
|
|||||||
updatedAt: '2026-04-28T16: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
|
// sct_016 — Dwayne Boudreaux (sub_003) — interior repaint PRJ-2026-004, Paid
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
@@ -8325,47 +8093,6 @@ const MOCK_SUBCONTRACTOR_TASKS = [
|
|||||||
updatedAt: '2026-05-03T16: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
|
// sct_018 — Greg Alston (sub_005) — window replacement PRJ-2026-003, Partially Paid
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
@@ -8409,46 +8136,6 @@ const MOCK_SUBCONTRACTOR_TASKS = [
|
|||||||
createdAt: '2026-05-03T09:00:00Z',
|
createdAt: '2026-05-03T09:00:00Z',
|
||||||
updatedAt: '2026-05-13T16: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 = [
|
const MOCK_NOTIFICATIONS = [
|
||||||
@@ -8470,20 +8157,6 @@ const MOCK_NOTIFICATIONS = [
|
|||||||
taskId: 'sct_004', fromCompanyId: 'lynkeduppro', fromCompanyName: 'LynkedUp Pro Roofing',
|
taskId: 'sct_004', fromCompanyId: 'lynkeduppro', fromCompanyName: 'LynkedUp Pro Roofing',
|
||||||
isRead: false, createdAt: '2026-05-18T22:40:00Z',
|
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 --------------------------
|
// -- Message Received --------------------------
|
||||||
{
|
{
|
||||||
id: 'notif_005', recipientUserId: 'sub_002', recipientRole: 'SUBCONTRACTOR',
|
id: 'notif_005', recipientUserId: 'sub_002', recipientRole: 'SUBCONTRACTOR',
|
||||||
@@ -8499,13 +8172,6 @@ const MOCK_NOTIFICATIONS = [
|
|||||||
taskId: 'sct_004', fromCompanyId: 'lynkeduppro', fromCompanyName: 'LynkedUp Pro Roofing',
|
taskId: 'sct_004', fromCompanyId: 'lynkeduppro', fromCompanyName: 'LynkedUp Pro Roofing',
|
||||||
isRead: false, createdAt: '2026-05-18T22:42:00Z',
|
isRead: false, createdAt: '2026-05-18T22:42:00Z',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: 'notif_007', recipientUserId: 'sub_002', recipientRole: 'SUBCONTRACTOR',
|
|
||||||
type: 'message_received',
|
|
||||||
message: 'Wade Hollis 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 ------------------------------
|
// -- Task Updated ------------------------------
|
||||||
{
|
{
|
||||||
id: 'notif_008', recipientUserId: 'sub_002', recipientRole: 'SUBCONTRACTOR',
|
id: 'notif_008', recipientUserId: 'sub_002', recipientRole: 'SUBCONTRACTOR',
|
||||||
@@ -8536,13 +8202,6 @@ const MOCK_NOTIFICATIONS = [
|
|||||||
taskId: 'sct_001', fromCompanyId: 'lynkeduppro', fromCompanyName: 'LynkedUp Pro Roofing',
|
taskId: 'sct_001', fromCompanyId: 'lynkeduppro', fromCompanyName: 'LynkedUp Pro Roofing',
|
||||||
isRead: true, createdAt: '2026-05-16T08:30:00Z',
|
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',
|
id: 'notif_013', recipientUserId: 'sub_002', recipientRole: 'SUBCONTRACTOR',
|
||||||
type: 'status_changed',
|
type: 'status_changed',
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useMemo, useRef, useEffect } from 'react';
|
import React, { useState, useMemo, useRef, useEffect } from 'react';
|
||||||
import { useParams, useNavigate } from 'react-router-dom';
|
import { useParams, useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
import { useAuth } from '../../context/AuthContext';
|
import { useAuth } from '../../context/AuthContext';
|
||||||
import { useMockStore } from '../../data/mockStore';
|
import { useMockStore } from '../../data/mockStore';
|
||||||
import { LIFECYCLE_STAGES, REWORK_STAGE, nextStage, prevStage, stageToPct } from '../../data/lifecycle';
|
import { LIFECYCLE_STAGES, REWORK_STAGE, nextStage, prevStage, stageToPct } from '../../data/lifecycle';
|
||||||
@@ -199,7 +199,15 @@ const OwnerProjectDetail = () => {
|
|||||||
} = useMockStore();
|
} = useMockStore();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { can } = usePermissions();
|
const { can } = usePermissions();
|
||||||
const [activeTab, setActiveTab] = useState('overview');
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
const [activeTab, setActiveTab] = useState(() => searchParams.get('tab') || 'overview');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const tab = searchParams.get('tab');
|
||||||
|
if (tab && tab !== activeTab) {
|
||||||
|
setActiveTab(tab);
|
||||||
|
}
|
||||||
|
}, [searchParams, activeTab]);
|
||||||
const [selectedCO, setSelectedCO] = useState(null);
|
const [selectedCO, setSelectedCO] = useState(null);
|
||||||
const [selectedInvoice, setSelectedInvoice] = useState(null);
|
const [selectedInvoice, setSelectedInvoice] = useState(null);
|
||||||
const [selectedVersion, setSelectedVersion] = useState(null);
|
const [selectedVersion, setSelectedVersion] = useState(null);
|
||||||
@@ -554,7 +562,10 @@ const OwnerProjectDetail = () => {
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={tab.id}
|
key={tab.id}
|
||||||
onClick={() => setActiveTab(tab.id)}
|
onClick={() => {
|
||||||
|
setActiveTab(tab.id);
|
||||||
|
setSearchParams({ tab: tab.id });
|
||||||
|
}}
|
||||||
className={`flex items-center gap-1.5 sm:gap-2 px-2.5 sm:px-4 py-3 text-xs sm:text-sm font-semibold whitespace-nowrap border-b-2 transition-colors ${isActive
|
className={`flex items-center gap-1.5 sm:gap-2 px-2.5 sm:px-4 py-3 text-xs sm:text-sm font-semibold whitespace-nowrap border-b-2 transition-colors ${isActive
|
||||||
? 'border-amber-500 text-amber-600 dark:text-amber-400'
|
? 'border-amber-500 text-amber-600 dark:text-amber-400'
|
||||||
: 'border-transparent text-zinc-500 hover:text-zinc-900 dark:hover:text-white'
|
: 'border-transparent text-zinc-500 hover:text-zinc-900 dark:hover:text-white'
|
||||||
|
|||||||
@@ -602,7 +602,7 @@ const OwnerSnapshot = () => {
|
|||||||
<SubcontractorPaymentsPanel
|
<SubcontractorPaymentsPanel
|
||||||
subcontractorTasks={subcontractorTasks}
|
subcontractorTasks={subcontractorTasks}
|
||||||
subcontractors={subcontractors}
|
subcontractors={subcontractors}
|
||||||
projects={projects}
|
projects={ownerProjects}
|
||||||
/>
|
/>
|
||||||
</SpotlightCard>
|
</SpotlightCard>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
Reference in New Issue
Block a user