| Job Type | Meter Read & Property Check |
| Service Category | Utility |
| Client | Birmingham City Council |
| Location | 42 High Street, B1 2LB |
| Priority | In Progress |
| Started At | 09:15 AM |
| Our Reference | GSC-2024-0847 |
| Current Duration | 01:23:45 |
| Job Type | Meter Read & Property Check |
| Service Category | Utility |
| Client | Birmingham City Council |
| Location | 42 High Street, B1 2LB |
| Priority | In Progress |
| Started At | 09:15 AM |
| Our Reference | GSC-2024-0847 |
| Current Duration | 01:23:45 |
| property@birmingham.gov.uk | |
| Preferred Contact | Mobile (Morning) |
| Helpdesk | 0800 123 4567 |
| Customer Ref | BHAM-2024-847 |
| Parking | Street parking available |
| Key Safe | Location: Left of front door Code: 2847 |
| Access Instructions | Property currently vacant, use key safe |
| Occupancy | Vacant |
| Visit Reason | Routine meter reading and property condition check |
| Requirements | Check all utility meters, photograph any issues, verify property security |
| Est. Duration | 30 minutes |
| Notes | Check for signs of water damage in kitchen area (reported by previous contractor) |
| Job Value | £65.00 |
| Electric Meter |
Serial: E123456789 MPAN: 1234567890123 Type: Digital, Size: Standard Location: External meter box Last Read: 12345 (01/11/2024) |
| Gas Meter |
Serial: G987654321 MPAN: 9876543210987 Type: Credit, Size: U6 Location: External box - front Last Read: 6789 (01/11/2024) |
| Water Meter |
Serial: W555123789 Type: Standard Location: Under kitchen sink Last Read: 3456 (01/11/2024) |
| Assigned At | 08/12/24, 8:00 AM |
| Accepted At | 08/12/24, 8:30 AM |
| Scheduled Date | 08/12/2024 |
| Scheduled Time | 9:00 AM - 11:00 AM |
| Started At | 08/12/2024 9:15 AM |
// Get job details GET /api/subjob/{subJobId} Authorization: Bearer {token} // Report issue POST /api/subjob/{subJobId}/reportissue Authorization: Bearer {token} Body: { "agentId": "{agentId}", "issueType": "Access Problem", "issueDescription": "Cannot access property - wrong key code", "reportedAt": "2024-12-08T14:30:00Z" } // Complete job POST /api/subjob/{subJobId}/complete Authorization: Bearer {token} Body: { "agentId": "{agentId}", "completedAt": "2024-12-08T14:30:00Z", "reportData": { /* job-specific report data */ } } // Upload image POST /api/subjob/{subJobId}/uploadimage Authorization: Bearer {token} Content-Type: multipart/form-data Body: { "image": file, "description": "Property front view", "timestamp": "2024-12-08T14:30:00Z" } // Update job progress PUT /api/subjob/{subJobId}/progress Authorization: Bearer {token} Body: { "agentId": "{agentId}", "progressUpdate": "Arrived on site", "updatedAt": "2024-12-08T14:30:00Z" }
{
"subJob": {
"id": 847,
"ourJobReference": "GSC-2024-0847",
"status": "In Progress",
"priority": "Standard",
"price": 65.00,
"rateType": "Standard",
"assignedAt": "2024-12-08T08:00:00Z",
"acceptedAt": "2024-12-08T08:30:00Z",
"startedAt": "2024-12-08T09:15:00Z",
"scheduledDate": "2024-12-08",
"scheduledTime": "09:00 - 11:00",
"estimatedDurationMinutes": 30,
"notes": "Check for signs of water damage in kitchen area",
"ppeRequirements": "Standard PPE",
"requiresSpecialEquipment": false,
"assignedAgentName": "Current User"
},
"clientJob": {
"id": 2847,
"clientName": "Birmingham City Council",
"jobType": "Meter Read & Property Check",
"jobServiceCategory": "Utility",
"clientJobReference": "BHAM-2024-847",
"customerName": "Property Manager",
"customerEmail": "property@birmingham.gov.uk",
"customerTelephone": "07700 123456",
"customerAddress": "42 High Street",
"customerCity": "Birmingham",
"customerCounty": "West Midlands",
"customerPostcode": "B1 2LB",
"customerCountry": "United Kingdom",
"customerReference": "BHAM-2024-847",
"companyName": "Birmingham City Council",
"preferredContactMethod": "Mobile (Morning)",
"clientHelpdeskPhone": "0800 123 4567",
"visitReason": "Routine meter reading and property condition check",
"additionalRequirements": "Check all utility meters, photograph any issues, verify property security",
"parkingType": "Street parking available",
"accessInstructions": "Property currently vacant, use key safe",
"propertyDetails": {
"occupancyStatus": "Vacant"
},
"securityDetails": {
"keySafeDetails": "Location: Left of front door, Code: 2847"
},
"meters": {
"electric": {
"serial": "E123456789",
"mpan": "1234567890123",
"type": "Digital",
"size": "Standard",
"location": "External meter box",
"lastReading": "12345",
"lastReadingDate": "2024-11-01"
},
"gas": {
"serial": "G987654321",
"mpan": "9876543210987",
"type": "Credit",
"size": "U6",
"location": "External box - front",
"lastReading": "6789",
"lastReadingDate": "2024-11-01"
},
"water": {
"serial": "W555123789",
"type": "Standard",
"location": "Under kitchen sink",
"lastReading": "3456",
"lastReadingDate": "2024-11-01"
}
},
"jobImages": [
{
"id": 301,
"description": "Property Front",
"uploadedAt": "2024-12-08T09:30:00Z",
"isPdf": false,
"isMandatory": true
},
{
"id": 302,
"description": "Access Point",
"uploadedAt": "2024-12-08T09:32:00Z",
"isPdf": false,
"isMandatory": false
}
]
}
}
// 1. Current duration since job started
const startTime = new Date(subJob.startedAt);
const currentTime = new Date();
const durationMs = currentTime.getTime() - startTime.getTime();
const hours = Math.floor(durationMs / (1000 * 60 * 60));
const minutes = Math.floor((durationMs % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((durationMs % (1000 * 60)) / 1000);
const currentDuration = `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
// 2. Customer address concatenation
const customerAddress = `${clientJob.customerAddress}, ${clientJob.customerPostcode}`;
// 3. Image count
const imageCount = clientJob.jobImages?.length || 0;
// 4. Task progress tracking
const totalTasks = document.querySelectorAll('.checklist-item').length;
const completedTasks = document.querySelectorAll('.checklist-item.completed').length;
const taskProgress = `${completedTasks}/${totalTasks}`;
// 1. Current duration calculation
let startTime = ISO8601DateFormatter().date(from: subJob.startedAt) ?? Date()
let currentTime = Date()
let duration = currentTime.timeIntervalSince(startTime)
let hours = Int(duration) / 3600
let minutes = (Int(duration) % 3600) / 60
let seconds = Int(duration) % 60
let currentDuration = String(format: "%02d:%02d:%02d", hours, minutes, seconds)
// 2. Customer address
let customerAddress = "\(clientJob.customerAddress), \(clientJob.customerPostcode)"
// 3. Image count
let imageCount = clientJob.jobImages?.count ?? 0
// 4. Task progress
let totalTasks = 6 // from checklist UI
let completedTasks = 1 // count completed tasks
let taskProgress = "\(completedTasks)/\(totalTasks)"
// 1. Current duration with live updates
const [currentDuration, setCurrentDuration] = useState('00:00:00');
useEffect(() => {
const startTime = new Date(subJob.startedAt);
const interval = setInterval(() => {
const currentTime = new Date();
const diff = currentTime - startTime;
const hours = Math.floor(diff / (1000 * 60 * 60));
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((diff % (1000 * 60)) / 1000);
setCurrentDuration(
`${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`
);
}, 1000);
return () => clearInterval(interval);
}, [subJob.startedAt]);
// 2. Customer address
const customerAddress = `${clientJob.customerAddress}, ${clientJob.customerPostcode}`;
// 3. Image count
const imageCount = clientJob.jobImages?.length || 0;
New → Assigned → Accepted → En Route → In Progress → Completed → Invoiced
↑ Current State