DEVELOPER MODE - Hover over any field to see API field name
← Back to Index
My Jobs
History
⚡ Action Required - Accept/Decline
Meter Reading
British Gas • #GSC-2024-1847
2.3 miles
Tomorrow, 2PM
30 min to decide
📅 Needs Scheduling
Site Visit
Property Management Co • #GSC-2024-1843
3.7 miles
Complete by Dec 6
3 days left
📝 Completed - Reports Pending
Meter Reading
E.ON Energy • #GSC-2024-1841
Completed today 11AM
Report due in 24hrs
📍 Upcoming Scheduled Jobs
Security Check
Secure Properties • #GSC-2024-1849
Today, 2:00 PM
4.2 miles
🚀 Currently Active Jobs
Meter Reading - En Route
Scottish Power • #GSC-2024-1840
Travelling to site
Started 10:45 AM
ETA: 11:15 AM
Property Inspection - On Site
Savills • #GSC-2024-1839
On site since 9:30 AM
45 mins elapsed
12 photos taken
⚠️ Jobs Requiring Attention
Site Visit - Access Issue
Property Guard • #GSC-2024-1837
No access - keysafe issue
Attempted 3:00 PM

📱 Mobile Implementation Guide

API Endpoints Required:

Client-Side Calculations Required:

Note: All field mappings are shown via tooltips when hovering over UI elements.
This documentation covers the calculations and logic that tooltips cannot explain.
            

1. Tab Badge Counts:

// Swift (iOS)
// All Active count - exclude completed/cancelled
let allActiveCount = jobs.filter { 
    !["Completed", "Cancelled", "Rejected"].contains($0.status) 
}.count

// Urgent count - based on deadline OR priority
let urgentCount = jobs.filter { job in
    if let deadline = job.acceptanceDeadline {
        return deadline.timeIntervalSinceNow < 14400 // 4 hours
    }
    return job.priority == "Urgent" || job.priority == "High"
}.count

// Kotlin (Android)
val allActiveCount = jobs.count { 
    it.status !in listOf("Completed", "Cancelled", "Rejected") 
}

val urgentCount = jobs.count { job ->
    job.acceptanceDeadline?.let {
        (it.time - System.currentTimeMillis()) < 14400000 // 4 hours in ms
    } ?: (job.priority in listOf("Urgent", "High"))
}

// Dart (Flutter)
final allActiveCount = jobs.where((j) => 
    !['Completed', 'Cancelled', 'Rejected'].contains(j.status)
).length;

final urgentCount = jobs.where((j) {
    if (j.acceptanceDeadline != null) {
        return j.acceptanceDeadline.difference(DateTime.now()).inHours < 4;
    }
    return j.priority == 'Urgent' || j.priority == 'High';
}).length;
            

2. Time Countdown Calculations:

// Swift - "30 min to decide" from acceptanceDeadline
func timeRemaining(from deadline: Date) -> String {
    let minutes = Calendar.current.dateComponents([.minute], 
        from: Date(), to: deadline).minute ?? 0
    
    if minutes < 0 {
        return "Overdue"
    } else if minutes < 60 {
        return "\(minutes) min to decide"
    } else if minutes < 1440 { // Less than 24 hours
        return "\(minutes / 60) hrs to decide"
    } else {
        return "\(minutes / 1440) days to decide"
    }
}

// Kotlin - Days until completion deadline
fun daysUntil(date: Date): String {
    val days = TimeUnit.DAYS.convert(
        date.time - System.currentTimeMillis(), 
        TimeUnit.MILLISECONDS
    )
    return when {
        days < 0 -> "Overdue"
        days == 0L -> "Due today"
        days == 1L -> "1 day left"
        else -> "$days days left"
    }
}

// Dart - Report deadline (24hrs after completion)
String reportDeadline(DateTime completionDate) {
    final deadline = completionDate.add(Duration(hours: 24));
    final remaining = deadline.difference(DateTime.now());
    
    if (remaining.isNegative) {
        return "Report overdue";
    } else if (remaining.inHours < 1) {
        return "Report due in ${remaining.inMinutes} mins";
    } else {
        return "Report due in ${remaining.inHours} hrs";
    }
}
            

3. Date/Time Formatting:

// Swift - Format as "Today", "Tomorrow", or date
func formatScheduledDate(_ date: Date, _ time: String?) -> String {
    let calendar = Calendar.current
    let dateString: String
    
    if calendar.isDateInToday(date) {
        dateString = "Today"
    } else if calendar.isDateInTomorrow(date) {
        dateString = "Tomorrow"
    } else {
        let formatter = DateFormatter()
        formatter.dateFormat = "MMM d"
        dateString = formatter.string(from: date)
    }
    
    if let time = time {
        return "\(dateString), \(time)"
    }
    return dateString
}

// Kotlin
fun formatScheduledDate(date: Date, time: String?): String {
    val calendar = Calendar.getInstance()
    val today = Calendar.getInstance()
    
    val dateString = when {
        isSameDay(date, today) -> "Today"
        isTomorrow(date, today) -> "Tomorrow"
        else -> SimpleDateFormat("MMM d", Locale.getDefault()).format(date)
    }
    
    return time?.let { "$dateString, $it" } ?: dateString
}

// Dart
String formatScheduledDate(DateTime date, String? time) {
    final now = DateTime.now();
    final today = DateTime(now.year, now.month, now.day);
    final tomorrow = today.add(Duration(days: 1));
    
    String dateString;
    if (date.isAfter(today) && date.isBefore(tomorrow)) {
        dateString = "Today";
    } else if (date.isAfter(tomorrow) && 
               date.isBefore(tomorrow.add(Duration(days: 1)))) {
        dateString = "Tomorrow";
    } else {
        dateString = DateFormat('MMM d').format(date);
    }
    
    return time != null ? "$dateString, $time" : dateString;
}
            

4. Elapsed Time Calculation (On Site):

// Swift - Calculate time since arrival
func elapsedTime(since arrivalTime: Date) -> String {
    let minutes = Int(Date().timeIntervalSince(arrivalTime) / 60)
    
    if minutes < 60 {
        return "\(minutes) mins elapsed"
    } else {
        let hours = minutes / 60
        let mins = minutes % 60
        return "\(hours)h \(mins)m elapsed"
    }
}

// Kotlin
fun elapsedTime(arrivalTime: Date): String {
    val minutes = TimeUnit.MINUTES.convert(
        System.currentTimeMillis() - arrivalTime.time,
        TimeUnit.MILLISECONDS
    ).toInt()
    
    return when {
        minutes < 60 -> "$minutes mins elapsed"
        else -> {
            val hours = minutes / 60
            val mins = minutes % 60
            "${hours}h ${mins}m elapsed"
        }
    }
}

// Dart
String elapsedTime(DateTime arrivalTime) {
    final duration = DateTime.now().difference(arrivalTime);
    final minutes = duration.inMinutes;
    
    if (minutes < 60) {
        return '$minutes mins elapsed';
    } else {
        final hours = minutes ~/ 60;
        final mins = minutes % 60;
        return '${hours}h ${mins}m elapsed';
    }
}
            

5. ETA Display (En Route):

// NOTE: ETA fields are CONDITIONALLY added by API - ONLY for En Route jobs
// Condition: status === "InProgress" AND travelStartTime !== null
// When these conditions are met, API adds:
//   - eta: "11:15 AM" (formatted time)
//   - etaDateTime: "2024-12-06T11:15:00" (ISO datetime)
//   - distanceToSite: "2.3 miles" (formatted driving distance)
//   - durationToSite: "15 mins" (formatted driving duration)
//   - isEnRoute: true (flag to indicate En Route status)
// These fields DO NOT exist for other jobs - check for existence before use!

// Swift - Display ETA from API
if job.status == "InProgress" && job.travelStartTime != nil {
    // API provides these fields for En Route jobs:
    etaLabel.text = job.eta ?? "--" // "11:15 AM"
    distanceLabel.text = job.distanceToSite ?? "--" // "2.3 miles"
    durationLabel.text = job.durationToSite ?? "--" // "15 mins"
    
    // Use etaDateTime for calculations if needed
    if let etaDateStr = job.etaDateTime,
       let etaDate = ISO8601DateFormatter().date(from: etaDateStr) {
        // Can calculate time until arrival, etc.
    }
}

// Kotlin - Display ETA from API
if (job.status == "InProgress" && job.travelStartTime != null) {
    // API provides these fields for En Route jobs:
    etaText.text = job.eta ?: "--" // "11:15 AM"
    distanceText.text = job.distanceToSite ?: "--" // "2.3 miles"
    durationText.text = job.durationToSite ?: "--" // "15 mins"
    
    // Use etaDateTime for calculations if needed
    job.etaDateTime?.let { etaDateStr ->
        val etaDate = Instant.parse(etaDateStr)
        // Can calculate time until arrival, etc.
    }
}

// Dart/Flutter - Display ETA from API  
if (job.status == 'InProgress' && job.travelStartTime != null) {
    // API provides these fields for En Route jobs:
    Text(job.eta ?? '--'), // "11:15 AM"
    Text(job.distanceToSite ?? '--'), // "2.3 miles"
    Text(job.durationToSite ?? '--'), // "15 mins"
    
    // Use etaDateTime for calculations if needed
    if (job.etaDateTime != null) {
        final etaDate = DateTime.parse(job.etaDateTime!);
        // Can calculate time until arrival, etc.
    }
}
            

API Response Structure:

{
    data: [  // Array of SubJobViewDto
        {
            // Core fields
            id: 123,
            ourJobReference: "GSC-2024-1847",  // Display after client name
            subJobType: "MeterReading",  // From ClientJob.JobType
            status: "Assigned",  // Current status
            priority: "Normal",  // "Low|Normal|High|Urgent"
            
            // Client info
            clientName: "British Gas",  // From Client via navigation
            
            // Schedule/Completion
            scheduledDate: "2024-12-06T00:00:00",
            scheduledTime: "14:30",
            completionDate: null,
            requiredCompletionDate: "2024-12-10T00:00:00",
            
            // Mobile-specific fields  
            acceptanceDeadline: "2024-12-05T15:30:00",  // For countdown timer (status=Assigned)
            travelStatus: "NotStarted",  // Status: "NotStarted"|"EnRoute"|"OnSite"|"Completed"
            travelStartTime: null,  // DateTime when agent started travel
            arrivalTime: null,  // DateTime when agent arrived on site
            customerTelephone: "07123456789",  // For Call Client button
            
            // Issues
            hasIssues: false,
            issueDescription: null,
            attemptTime: null,
            
            // Reports/Images
            hasReport: false,  // For filtering
            imageCount: 0,
            
            // Distance
            distance: 2.3,  // Miles from agent base location
            
            // Job location (for ETA calculation)
            jobLatitude: 51.5074,  // CustomerLatitude from ClientJobGroup
            jobLongitude: -0.1278,  // CustomerLongitude from ClientJobGroup
            
            // *** CONDITIONAL FIELDS - ONLY for En Route jobs (status="InProgress" with travelStartTime !== null) ***
            // eta: "11:15 AM",  // (CONDITIONAL - En Route only)
            // etaDateTime: "2024-12-06T11:15:00",  // (CONDITIONAL - En Route only)
            // distanceToSite: "2.3 miles",  // (CONDITIONAL - En Route only)
            // durationToSite: "15 mins",  // (CONDITIONAL - En Route only)
            // isEnRoute: true,  // (CONDITIONAL - En Route only)
            
            // Other desktop fields also included...
            price: 45.00,
            isVerified: false,
            isInvoiced: false
        }
    ],
    totalCount: 47,
    pageNumber: 1,
    pageSize: 20
}
            

Job Grouping Logic:

⚡ Action Required Section

// Swift/iOS
let actionRequired = jobs.filter { $0.status == "Assigned" }

// Kotlin/Android  
val actionRequired = jobs.filter { it.status == "Assigned" }

// Dart/Flutter
final actionRequired = jobs.where((j) => j.status == 'Assigned').toList();
            

📅 Needs Scheduling Section

// Swift/iOS
let needsScheduling = jobs.filter { 
    $0.status == "Accepted" && $0.scheduledDate == nil 
}

// Kotlin/Android
val needsScheduling = jobs.filter { 
    it.status == "Accepted" && it.scheduledDate == null 
}

// Dart/Flutter
final needsScheduling = jobs.where((j) => 
    j.status == 'Accepted' && j.scheduledDate == null
).toList();
            

📝 Completed - Reports Pending Section

// Swift/iOS
let reportsPending = jobs.filter { 
    $0.status == "Completed" && $0.hasReport == false 
}

// Kotlin/Android
val reportsPending = jobs.filter { 
    it.status == "Completed" && !it.hasReport 
}

// Dart/Flutter
final reportsPending = jobs.where((j) => 
    j.status == 'Completed' && !j.hasReport
).toList();
            

📍 Upcoming Scheduled Jobs Section

// Swift/iOS
let scheduled = jobs
    .filter { $0.status == "Scheduled" }
    .sorted { $0.scheduledDate < $1.scheduledDate }

// Kotlin/Android
val scheduled = jobs
    .filter { it.status == "Scheduled" }
    .sortedBy { it.scheduledDate }

// Dart/Flutter
final scheduled = jobs
    .where((j) => j.status == 'Scheduled')
    .toList()
    ..sort((a, b) => a.scheduledDate.compareTo(b.scheduledDate));
            

🚀 Currently Active Jobs Section

// Swift/iOS
let activeJobs = jobs.filter { $0.status == "InProgress" }
// Then check travelStatus for sub-grouping

// Kotlin/Android
val activeJobs = jobs.filter { it.status == "InProgress" }
// Then check travelStatus for sub-grouping

// Dart/Flutter
final activeJobs = jobs.where((j) => j.status == 'InProgress').toList();
// Then check travelStatus for sub-grouping
            

⚠️ Jobs With Issues Section

// Swift/iOS
let jobsWithIssues = jobs.filter { $0.hasIssues == true }

// Kotlin/Android
val jobsWithIssues = jobs.filter { it.hasIssues }

// Dart/Flutter
final jobsWithIssues = jobs.where((j) => j.hasIssues).toList();
            

Priority Color Scheme:

// Color scheme for job cards based on urgency
enum JobPriority {
    case urgent    // Red - #DC2626 (acceptanceDeadline < 4hrs OR priority == "Urgent")
    case warning   // Amber - #F59E0B (completionDeadline < 3 days OR priority == "High")  
    case success   // Green - #16A34A (status == "Completed")
    case active    // Blue - #1A73E8 (status == "InProgress")
    case normal    // Gray - #D4D4D4 (default for scheduled)
    case cancelled // Black - #000000 (status == "Cancelled" with 0.7 opacity)
}
            

Action Button API Payloads:

Accept: POST /api/AgentOwnJobs/{id}/accept
Body: {} // Empty

Decline: POST /api/AgentOwnJobs/{id}/reject  
Body: { "reason": "Unable to reach location in time" }

Schedule: POST /api/AgentOwnJobs/{id}/schedule
Body: { "scheduledDate": "2024-12-06T00:00:00", "scheduledTime": "14:30" }

Start Travel: POST /api/AgentOwnJobs/{id}/start-travel
Body: { "currentLocation": { "lat": 51.5074, "lng": -0.1278 } }

Arrive: POST /api/AgentOwnJobs/{id}/arrive
Body: { "arrivalTime": "2024-12-06T14:25:00" }

Complete: POST /api/AgentOwnJobs/{id}/complete
Body: { "completionNotes": "All tasks completed successfully" }

Report Issue: POST /api/AgentOwnJobs/{id}/report-issue
Body: { "issueType": "NoAccess", "description": "Keysafe code not working" }
            

Phone Call Integration:

// Swift/iOS
if let phone = job.customerTelephone, 
   let url = URL(string: "tel://\(phone)") {
    UIApplication.shared.open(url)
}

// Kotlin/Android
val intent = Intent(Intent.ACTION_DIAL).apply {
    data = Uri.parse("tel:${job.customerTelephone}")
}
startActivity(intent)

// Flutter
import 'package:url_launcher/url_launcher.dart';
final Uri phoneUri = Uri(scheme: 'tel', path: job.customerTelephone);
await launchUrl(phoneUri);
            

Important Notes: