subcontractor: add progress updates and task activity timeline
This commit is contained in:
+248
-1
@@ -5152,6 +5152,43 @@ const MOCK_SUBCONTRACTOR_TASKS = [
|
||||
{ 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' },
|
||||
],
|
||||
activities: [
|
||||
{
|
||||
id: 'sct_001_a1', type: 'status_change', status: 'Assigned',
|
||||
note: 'Task created and assigned. Materials list shared with crew.',
|
||||
photos: [],
|
||||
actorId: 'owner_001', actorName: 'Justin Johnson', actorRole: 'Owner',
|
||||
at: '2026-05-15T09:12:00Z',
|
||||
},
|
||||
{
|
||||
id: 'sct_001_a2', type: 'status_change', status: 'In Progress',
|
||||
note: 'On site, sourced replacement Owens Corning Estate Gray. Starting on the south slope.',
|
||||
photos: [
|
||||
{ id: 'sct_001_a2_p1', url: '/assets/images/properties/Hail_Damaged_Shingles.jpg', name: 'arrival.jpg' },
|
||||
],
|
||||
actorId: 'sub_002', actorName: 'Maya Patel', actorRole: 'Subcontractor',
|
||||
at: '2026-05-16T08:30:00Z',
|
||||
},
|
||||
{
|
||||
id: 'sct_001_a3', type: 'progress_update', status: 'In Progress',
|
||||
note: 'First two damaged ridge tiles removed. Decking underneath is clean — no rot.',
|
||||
photos: [
|
||||
{ id: 'sct_001_a3_p1', url: '/assets/images/properties/Storm_Worn_Roof.jpg', name: 'decking-clean.jpg' },
|
||||
],
|
||||
actorId: 'sub_002', actorName: 'Maya Patel', actorRole: 'Subcontractor',
|
||||
at: '2026-05-16T10:45:00Z',
|
||||
},
|
||||
{
|
||||
id: 'sct_001_a4', type: 'progress_update', status: 'In Progress',
|
||||
note: 'Replacement ridge caps installed on lower row. Sealing next.',
|
||||
photos: [
|
||||
{ id: 'sct_001_a4_p1', url: '/assets/images/properties/Cracked_Storm_Shingles.jpg', name: 'lower-row.jpg' },
|
||||
{ id: 'sct_001_a4_p2', url: '/assets/images/properties/Overlapping_Roof_Shingles.jpg', name: 'wide-shot.jpg' },
|
||||
],
|
||||
actorId: 'sub_002', actorName: 'Maya Patel', actorRole: 'Subcontractor',
|
||||
at: '2026-05-16T14:20:00Z',
|
||||
},
|
||||
],
|
||||
projectId: 'proj_004',
|
||||
paymentStatus: 'Unpaid',
|
||||
paymentRequestedAt: '2026-05-16T17:30:00Z',
|
||||
@@ -5197,6 +5234,53 @@ const MOCK_SUBCONTRACTOR_TASKS = [
|
||||
fees: [
|
||||
{ id: 'sct_002_f1', description: 'Labour — Day 1 disconnect / prep', type: 'Labour / Service Fee', amount: 425, createdAt: '2026-05-12T17:00:00Z' },
|
||||
],
|
||||
activities: [
|
||||
{
|
||||
id: 'sct_002_a1', type: 'status_change', status: 'Assigned',
|
||||
note: 'Task created and assigned. Permit pulled, materials staged.',
|
||||
photos: [],
|
||||
actorId: 'owner_001', actorName: 'Justin Johnson', actorRole: 'Owner',
|
||||
at: '2026-05-10T15:40:00Z',
|
||||
},
|
||||
{
|
||||
id: 'sct_002_a2', type: 'status_change', status: 'In Progress',
|
||||
note: 'Oncor on site for disconnect. Cutting power to existing 100A panel and starting tear-out.',
|
||||
photos: [
|
||||
{ id: 'sct_002_a2_p1', url: '/assets/images/properties/Beige_Two_Story_House.jpg', name: 'oncor-disconnect.jpg' },
|
||||
],
|
||||
actorId: 'sub_001', actorName: 'Carlos Subcontractor', actorRole: 'Subcontractor',
|
||||
at: '2026-05-12T08:20:00Z',
|
||||
},
|
||||
{
|
||||
id: 'sct_002_a3', type: 'progress_update', status: 'In Progress',
|
||||
note: 'Old 100A panel removed. Service entrance wires capped and labelled. Mast looks good — no signs of corrosion.',
|
||||
photos: [
|
||||
{ id: 'sct_002_a3_p1', url: '/assets/images/properties/Modern_White_Suburban_Home.jpg', name: 'mast-check.jpg' },
|
||||
{ id: 'sct_002_a3_p2', url: '/assets/images/properties/Brick_Front_Porch_Home.jpg', name: 'old-panel-out.jpg' },
|
||||
],
|
||||
actorId: 'sub_001', actorName: 'Carlos Subcontractor', actorRole: 'Subcontractor',
|
||||
at: '2026-05-12T10:30:00Z',
|
||||
},
|
||||
{
|
||||
id: 'sct_002_a4', type: 'progress_update', status: 'In Progress',
|
||||
note: 'New 200A Square D panel mounted and bonded. Two new ground rods driven and connected.',
|
||||
photos: [
|
||||
{ id: 'sct_002_a4_p1', url: '/assets/images/properties/Sunny_Suburban_House.jpg', name: 'new-panel-mount.jpg' },
|
||||
],
|
||||
actorId: 'sub_001', actorName: 'Carlos Subcontractor', actorRole: 'Subcontractor',
|
||||
at: '2026-05-12T13:15:00Z',
|
||||
},
|
||||
{
|
||||
id: 'sct_002_a5', type: 'progress_update', status: 'In Progress',
|
||||
note: 'Migrated 20 of 24 home circuits to the new panel. Tested with multimeter — all live and balanced across legs. Remaining 4 circuits (HVAC + dryer) to be wired tomorrow before re-energizing.',
|
||||
photos: [
|
||||
{ id: 'sct_002_a5_p1', url: '/assets/images/properties/Luxury_Driveway_Residence.jpg', name: 'circuits-migrated.jpg' },
|
||||
{ id: 'sct_002_a5_p2', url: '/assets/images/properties/Overlapping_Roof_Shingles.jpg', name: 'panel-labels.jpg' },
|
||||
],
|
||||
actorId: 'sub_001', actorName: 'Carlos Subcontractor', actorRole: 'Subcontractor',
|
||||
at: '2026-05-12T16:00:00Z',
|
||||
},
|
||||
],
|
||||
projectId: 'proj_003',
|
||||
paymentStatus: 'Unpaid',
|
||||
paymentRequestedAt: '2026-05-12T17:00:00Z',
|
||||
@@ -5319,6 +5403,33 @@ const MOCK_SUBCONTRACTOR_TASKS = [
|
||||
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: 'ADM01', actorName: 'Admin One', 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: 'proj_012',
|
||||
paymentStatus: 'Unpaid',
|
||||
paymentRequestedAt: '2026-05-14T13:00:00Z',
|
||||
@@ -5449,6 +5560,33 @@ const MOCK_SUBCONTRACTOR_TASKS = [
|
||||
{ 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' },
|
||||
],
|
||||
activities: [
|
||||
{
|
||||
id: 'sct_008_a1', type: 'status_change', status: 'Assigned',
|
||||
note: 'Task created and assigned to Carlos. Insurance covering code upgrade.',
|
||||
photos: [],
|
||||
actorId: 'ADM01', actorName: 'Admin One', actorRole: 'Admin',
|
||||
at: '2026-05-16T13:00:00Z',
|
||||
},
|
||||
{
|
||||
id: 'sct_008_a2', type: 'status_change', status: 'In Progress',
|
||||
note: 'Arrived on site. Verified wiring is copper (no aluminum). Starting kitchen counter outlets first.',
|
||||
photos: [
|
||||
{ id: 'sct_008_a2_p1', url: '/assets/images/properties/Modern_White_Suburban_Home.jpg', name: 'kitchen-before.jpg' },
|
||||
],
|
||||
actorId: 'sub_001', actorName: 'Carlos Subcontractor', actorRole: 'Subcontractor',
|
||||
at: '2026-05-19T10:15:00Z',
|
||||
},
|
||||
{
|
||||
id: 'sct_008_a3', type: 'progress_update', status: 'In Progress',
|
||||
note: 'Both kitchen counter outlets swapped to Leviton 20A GFCI. Tested with plug-in tester — passed.',
|
||||
photos: [
|
||||
{ id: 'sct_008_a3_p1', url: '/assets/images/properties/Sunny_Suburban_House.jpg', name: 'gfci-installed.jpg' },
|
||||
],
|
||||
actorId: 'sub_001', actorName: 'Carlos Subcontractor', actorRole: 'Subcontractor',
|
||||
at: '2026-05-19T12:30:00Z',
|
||||
},
|
||||
],
|
||||
projectId: 'proj_011',
|
||||
paymentStatus: 'Unpaid',
|
||||
paymentRequestedAt: '2026-05-19T12:35:00Z',
|
||||
@@ -5456,6 +5594,43 @@ const MOCK_SUBCONTRACTOR_TASKS = [
|
||||
updatedAt: '2026-05-19T18:00:00Z',
|
||||
},
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// sct_010 — Carlos (sub_001) — newly assigned, awaiting acknowledgement
|
||||
// -------------------------------------------------------------------
|
||||
{
|
||||
id: 'sct_010',
|
||||
companyId: 'lynkeduppro',
|
||||
companyName: 'LynkedUp Pro Roofing',
|
||||
subcontractorId: 'sub_001',
|
||||
subcontractorName: 'Carlos Subcontractor',
|
||||
assignedBy: 'owner_001',
|
||||
assignedByName: 'Justin Johnson',
|
||||
title: 'Install EV charger circuit — garage',
|
||||
location: '3917 Arizona Pl, Plano, TX 75023',
|
||||
description: 'Homeowner purchased a Tesla Wall Connector. Run a dedicated 60A 240V circuit from the main panel to the garage wall. Coordinate with homeowner for outage window. Permit required.',
|
||||
dueDate: '2026-05-29',
|
||||
priority: 'medium',
|
||||
status: 'Assigned',
|
||||
photos: [
|
||||
{ id: 'sct_010_p1', name: 'Garage_Wall.jpg', url: '/assets/images/properties/Modern_White_Suburban_Home.jpg', annotation: 'Wall Connector mounting location' },
|
||||
{ id: 'sct_010_p2', name: 'Panel_Location.jpg', url: '/assets/images/properties/Brick_Front_Porch_Home.jpg', annotation: 'Main panel — check available breaker slots' },
|
||||
],
|
||||
statusHistory: [
|
||||
{ id: 'sct_010_h1', status: 'Assigned', actorId: 'owner_001', actorName: 'Justin Johnson', comment: 'Task created and assigned. Please confirm ETA.', at: '2026-05-20T09:00:00Z' },
|
||||
],
|
||||
thread: [
|
||||
{ id: 'sct_010_t1', channel: 'external', senderId: 'owner_001', senderName: 'Justin Johnson', senderRole: 'Owner', body: 'Carlos — homeowner is hoping to have this in before next weekend. Can you take a look at the panel and confirm slot availability?', at: '2026-05-20T09:02:00Z' },
|
||||
],
|
||||
expenses: [],
|
||||
fees: [],
|
||||
activities: [],
|
||||
projectId: 'proj_011',
|
||||
paymentStatus: 'Unpaid',
|
||||
paymentRequestedAt: null,
|
||||
createdAt: '2026-05-20T09:00:00Z',
|
||||
updatedAt: '2026-05-20T09:02:00Z',
|
||||
},
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// sct_009 — Jennifer Wu (sub_004) — On Hold plumbing
|
||||
// -------------------------------------------------------------------
|
||||
@@ -5952,13 +6127,19 @@ export const MockStoreProvider = ({ children }) => {
|
||||
return task;
|
||||
};
|
||||
|
||||
const setSubcontractorTaskStatus = (taskId, nextStatus, comment, actor) => {
|
||||
const setSubcontractorTaskStatus = (taskId, nextStatus, comment, actor, photos = []) => {
|
||||
const now = new Date().toISOString();
|
||||
let didChange = false;
|
||||
let companyId = null;
|
||||
let companyName = null;
|
||||
let assignedByUserId = null;
|
||||
|
||||
const normalizedPhotos = (photos || []).map((p, i) => ({
|
||||
id: p.id || `${Date.now()}_${i}_${Math.random().toString(36).slice(2, 7)}`,
|
||||
url: p.url,
|
||||
name: p.name || '',
|
||||
}));
|
||||
|
||||
setSubcontractorTasks(prev => prev.map(t => {
|
||||
if (t.id !== taskId) return t;
|
||||
if (t.status === nextStatus) return t;
|
||||
@@ -5974,10 +6155,22 @@ export const MockStoreProvider = ({ children }) => {
|
||||
comment: comment?.trim() || '',
|
||||
at: now,
|
||||
};
|
||||
const activityEntry = {
|
||||
id: `a_${Date.now()}`,
|
||||
type: 'status_change',
|
||||
status: nextStatus,
|
||||
note: comment?.trim() || '',
|
||||
photos: normalizedPhotos,
|
||||
actorId: actor?.id || t.subcontractorId,
|
||||
actorName: actor?.name || t.subcontractorName,
|
||||
actorRole: actor?.role || 'Subcontractor',
|
||||
at: now,
|
||||
};
|
||||
return {
|
||||
...t,
|
||||
status: nextStatus,
|
||||
statusHistory: [...(t.statusHistory || []), entry],
|
||||
activities: [...(t.activities || []), activityEntry],
|
||||
updatedAt: now,
|
||||
};
|
||||
}));
|
||||
@@ -6085,6 +6278,59 @@ export const MockStoreProvider = ({ children }) => {
|
||||
toast.success('Fee submitted');
|
||||
};
|
||||
|
||||
const addSubcontractorTaskActivity = (taskId, { note, photos = [] }, actor) => {
|
||||
const trimmed = note?.trim() || '';
|
||||
if (!trimmed || !photos.length) return;
|
||||
const now = new Date().toISOString();
|
||||
const normalizedPhotos = photos.map((p, i) => ({
|
||||
id: p.id || `${Date.now()}_${i}_${Math.random().toString(36).slice(2, 7)}`,
|
||||
url: p.url,
|
||||
name: p.name || '',
|
||||
}));
|
||||
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 entry = {
|
||||
id: `a_${Date.now()}`,
|
||||
type: 'progress_update',
|
||||
status: t.status,
|
||||
note: trimmed,
|
||||
photos: normalizedPhotos,
|
||||
actorId: actor?.id || t.subcontractorId,
|
||||
actorName: actor?.name || t.subcontractorName,
|
||||
actorRole: actor?.role || 'Subcontractor',
|
||||
at: now,
|
||||
};
|
||||
return {
|
||||
...t,
|
||||
activities: [...(t.activities || []), entry],
|
||||
updatedAt: now,
|
||||
};
|
||||
}));
|
||||
|
||||
const recipientUserId = actor?.id === assignedByUserId ? subId : assignedByUserId;
|
||||
if (recipientUserId) {
|
||||
addNotification({
|
||||
recipientUserId,
|
||||
recipientRole: actor?.id === assignedByUserId ? 'SUBCONTRACTOR' : 'OWNER',
|
||||
type: 'progress_update',
|
||||
message: `New progress update from ${actor?.name || 'subcontractor'}`,
|
||||
taskId,
|
||||
fromCompanyId: companyId,
|
||||
fromCompanyName: companyName,
|
||||
});
|
||||
}
|
||||
toast.success('Progress update posted');
|
||||
};
|
||||
|
||||
const updateSubcontractorTask = (taskId, data) => {
|
||||
let updatedSubId = null;
|
||||
let prevSubId = null;
|
||||
@@ -6421,6 +6667,7 @@ export const MockStoreProvider = ({ children }) => {
|
||||
addSubcontractorTaskMessage,
|
||||
addSubcontractorTaskExpense,
|
||||
addSubcontractorTaskFee,
|
||||
addSubcontractorTaskActivity,
|
||||
notifications,
|
||||
addNotification,
|
||||
markNotificationRead,
|
||||
|
||||
Reference in New Issue
Block a user