DEVELOPER MODE - Hover over any field to see API field name
← Back to Index
Welcome back,
John Smith
3
12
Active Jobs
5
Today's Jobs
3
Completed
2
Pending Accept
NEXT JOB in 45 minutes
Meter Reading - Gas & Electric
123 High Street, London EC1V 2NX
2:00 PM - 2:30 PM
2.3 miles away VIP Client
This Month's Earnings
£1,847.50
December 202447 jobs completed

Upcoming Jobs

Meter Reading - Gas & Electric
123 High Street, London
Today, 2:00 PM Accept by 3:00 PM
Urgent
Property Inspection
45 Queens Road, Manchester
Today, 4:30 PM 2.3 miles
Normal
Site Visit - Vacant Property
78 Park Lane, Birmingham
Tomorrow, 10:00 AM Due: Dec 15
5.2 milesNot Urgent
Low
View All Jobs →

Performance Trend

Performance Summary

4.8
Based on 47 reviews
96%
Completion Rate
98%
On-Time Rate
2
Disputes
12 min
Avg Response
Home Jobs Messages Earnings History

📱 Mobile Implementation Guide

API Endpoints (2 calls required):

// 1. Main dashboard data
GET /api/dashboard/agent
Returns: AgentDashboardStatsDto (all dashboard data)

// 2. Agent profile (for name display)
GET /api/Agent/profile  
Returns: { firstName, lastName, ... }
        

Client-Side Calculations Required:

Swift (iOS):

// 1. Active Jobs Count
let activeJobs = dashboard.jobStats.scheduled + dashboard.jobStats.inProgress

// 2. Today's Jobs Count  
let todayJobsCount = dashboard.todaySchedule.count

// 3. Time Until Next Job
if let nextJob = dashboard.todaySchedule.first {
    let timeInterval = nextJob.scheduledDate.timeIntervalSinceNow
    let hours = Int(timeInterval / 3600)
    let minutes = Int((timeInterval.truncatingRemainder(dividingBy: 3600)) / 60)
    
    if hours > 0 {
        timeUntilLabel.text = "in \(hours) hours"
    } else {
        timeUntilLabel.text = "in \(minutes) minutes"
    }
    
    // Show card only if within 4 hours
    nextJobCard.isHidden = hours > 4
}

// 4. Format Date as "Today" or "Tomorrow"
func formatJobDate(_ date: Date) -> String {
    if Calendar.current.isDateInToday(date) {
        return "Today"
    } else if Calendar.current.isDateInTomorrow(date) {
        return "Tomorrow"
    } else {
        let formatter = DateFormatter()
        formatter.dateFormat = "MMM d"
        return formatter.string(from: date)
    }
}

// 5. Format Currency
earningsLabel.text = "£\(String(format: "%.2f", dashboard.earningsData.currentMonthEarnings))"
        

Kotlin (Android):

// 1. Active Jobs Count
val activeJobs = dashboard.jobStats.scheduled + dashboard.jobStats.inProgress

// 2. Today's Jobs Count
val todayJobsCount = dashboard.todaySchedule.size

// 3. Time Until Next Job
dashboard.todaySchedule.firstOrNull()?.let { nextJob ->
    val now = LocalDateTime.now()
    val scheduledTime = nextJob.scheduledDate.toLocalDateTime()
    val duration = Duration.between(now, scheduledTime)
    
    val hours = duration.toHours()
    val minutes = duration.toMinutes() % 60
    
    timeUntilText.text = if (hours > 0) {
        "in $hours hours"
    } else {
        "in $minutes minutes"
    }
    
    // Show card only if within 4 hours
    nextJobCard.visibility = if (hours <= 4) View.VISIBLE else View.GONE
}

// 4. Format Date as "Today" or "Tomorrow"
fun formatJobDate(date: LocalDateTime): String {
    val today = LocalDate.now()
    return when (date.toLocalDate()) {
        today -> "Today"
        today.plusDays(1) -> "Tomorrow"
        else -> date.format(DateTimeFormatter.ofPattern("MMM d"))
    }
}

// 5. Format Currency
earningsText.text = "£%.2f".format(dashboard.earningsData.currentMonthEarnings)
        

Dart (Flutter):

// 1. Active Jobs Count
final activeJobs = dashboard.jobStats.scheduled + dashboard.jobStats.inProgress;

// 2. Today's Jobs Count
final todayJobsCount = dashboard.todaySchedule.length;

// 3. Time Until Next Job
if (dashboard.todaySchedule.isNotEmpty) {
  final nextJob = dashboard.todaySchedule.first;
  final now = DateTime.now();
  final difference = nextJob.scheduledDate.difference(now);
  
  String timeUntilText;
  if (difference.inHours > 0) {
    timeUntilText = 'in ${difference.inHours} hours';
  } else {
    timeUntilText = 'in ${difference.inMinutes} minutes';
  }
  
  // Show card only if within 4 hours
  showNextJobCard = difference.inHours <= 4;
}

// 4. Format Date as "Today" or "Tomorrow"
String formatJobDate(DateTime date) {
  final now = DateTime.now();
  final today = DateTime(now.year, now.month, now.day);
  final tomorrow = today.add(Duration(days: 1));
  final jobDay = DateTime(date.year, date.month, date.day);
  
  if (jobDay == today) {
    return 'Today';
  } else if (jobDay == tomorrow) {
    return 'Tomorrow';
  } else {
    return DateFormat('MMM d').format(date);
  }
}

// 5. Format Currency  
final earningsText = '£${dashboard.earningsData.currentMonthEarnings.toStringAsFixed(2)}';
        

Java (Android):

// 1. Active Jobs Count
int activeJobs = dashboard.getJobStats().getScheduled() + 
                 dashboard.getJobStats().getInProgress();

// 2. Today's Jobs Count
int todayJobsCount = dashboard.getTodaySchedule().size();

// 3. Time Until Next Job
if (!dashboard.getTodaySchedule().isEmpty()) {
    ScheduledJobDto nextJob = dashboard.getTodaySchedule().get(0);
    Date now = new Date();
    long diffMs = nextJob.getScheduledDate().getTime() - now.getTime();
    long hours = TimeUnit.MILLISECONDS.toHours(diffMs);
    long minutes = TimeUnit.MILLISECONDS.toMinutes(diffMs) % 60;
    
    String timeUntilText;
    if (hours > 0) {
        timeUntilText = "in " + hours + " hours";
    } else {
        timeUntilText = "in " + minutes + " minutes";
    }
    
    // Show card only if within 4 hours
    nextJobCard.setVisibility(hours <= 4 ? View.VISIBLE : View.GONE);
}

// 4. Format Date as "Today" or "Tomorrow"
private String formatJobDate(Date date) {
    Calendar cal = Calendar.getInstance();
    cal.setTime(date);
    Calendar today = Calendar.getInstance();
    
    if (isSameDay(cal, today)) {
        return "Today";
    }
    
    today.add(Calendar.DAY_OF_YEAR, 1);
    if (isSameDay(cal, today)) {
        return "Tomorrow";
    }
    
    SimpleDateFormat sdf = new SimpleDateFormat("MMM d");
    return sdf.format(date);
}

// 5. Format Currency
String earningsText = String.format("£%.2f", 
    dashboard.getEarningsData().getCurrentMonthEarnings());
        

Performance Chart Implementation:

iOS (Swift) - Using Charts library:

import Charts

func setupChart() {
    // Filter data by period
    let filteredData = filterByPeriod(dashboard.performance.performanceTrend, period: selectedPeriod)
    
    // Create data entries
    var completionEntries: [ChartDataEntry] = []
    var ratingEntries: [ChartDataEntry] = []
    
    for (index, item) in filteredData.enumerated() {
        // Completion rate (already 0-1, multiply by 100 for display)
        completionEntries.append(ChartDataEntry(x: Double(index), 
                                               y: item.completionRate * 100))
        
        // Rating (0-5, convert to percentage)
        ratingEntries.append(ChartDataEntry(x: Double(index), 
                                           y: item.averageRating * 20))
    }
    
    // Create datasets
    let completionDataSet = LineChartDataSet(entries: completionEntries, 
                                            label: "Completion Rate")
    completionDataSet.colors = [UIColor.systemBlue]
    completionDataSet.drawCirclesEnabled = false
    completionDataSet.lineWidth = 2
    
    let ratingDataSet = LineChartDataSet(entries: ratingEntries, 
                                        label: "Customer Rating")
    ratingDataSet.colors = [UIColor.systemGreen]
    ratingDataSet.drawCirclesEnabled = false
    ratingDataSet.lineWidth = 2
    ratingDataSet.lineDashLengths = [5, 5]
    
    // Combine datasets
    let chartData = LineChartData(dataSets: [completionDataSet, ratingDataSet])
    lineChartView.data = chartData
    
    // Configure axes
    lineChartView.leftAxis.axisMinimum = 80
    lineChartView.leftAxis.axisMaximum = 100
    lineChartView.xAxis.labelPosition = .bottom
}
        

Android (MPAndroidChart):

// Kotlin
import com.github.mikephil.charting.charts.LineChart
import com.github.mikephil.charting.data.*

fun setupChart() {
    val chart = findViewById(R.id.performanceChart)
    
    // Filter data by period
    val filteredData = filterByPeriod(dashboard.performance.performanceTrend, period)
    
    // Create entries
    val completionEntries = ArrayList()
    val ratingEntries = ArrayList()
    
    filteredData.forEachIndexed { index, item ->
        // Completion rate (multiply by 100 for percentage)
        completionEntries.add(Entry(index.toFloat(), 
                                   (item.completionRate * 100).toFloat()))
        
        // Rating (convert to percentage)
        ratingEntries.add(Entry(index.toFloat(), 
                              (item.averageRating * 20).toFloat()))
    }
    
    // Create datasets
    val completionDataSet = LineDataSet(completionEntries, "Completion Rate")
    completionDataSet.color = Color.parseColor("#1A73E8")
    completionDataSet.setDrawCircles(false)
    completionDataSet.lineWidth = 2f
    
    val ratingDataSet = LineDataSet(ratingEntries, "Customer Rating")
    ratingDataSet.color = Color.parseColor("#16A34A")
    ratingDataSet.setDrawCircles(false)
    ratingDataSet.lineWidth = 2f
    ratingDataSet.enableDashedLine(10f, 10f, 0f)
    
    // Combine and set data
    val lineData = LineData(completionDataSet, ratingDataSet)
    chart.data = lineData
    
    // Configure chart
    chart.axisLeft.axisMinimum = 80f
    chart.axisLeft.axisMaximum = 100f
    chart.xAxis.position = XAxis.XAxisPosition.BOTTOM
    chart.invalidate()
}
        

Flutter (fl_chart):

import 'package:fl_chart/fl_chart.dart';

Widget buildPerformanceChart() {
  // Filter data by period
  final filteredData = filterByPeriod(
    dashboard.performance.performanceTrend, 
    selectedPeriod
  );
  
  // Create spots for lines
  final completionSpots = [];
  final ratingSpots = [];
  
  for (int i = 0; i < filteredData.length; i++) {
    final item = filteredData[i];
    // Completion rate (multiply by 100)
    completionSpots.add(FlSpot(i.toDouble(), item.completionRate * 100));
    // Rating (convert to percentage)
    ratingSpots.add(FlSpot(i.toDouble(), item.averageRating * 20));
  }
  
  return LineChart(
    LineChartData(
      minY: 80,
      maxY: 100,
      lineBarsData: [
        // Completion rate line
        LineChartBarData(
          spots: completionSpots,
          color: Color(0xFF1A73E8),
          barWidth: 2,
          dotData: FlDotData(show: false),
          isCurved: true,
        ),
        // Customer rating line
        LineChartBarData(
          spots: ratingSpots,
          color: Color(0xFF16A34A),
          barWidth: 2,
          dotData: FlDotData(show: false),
          dashArray: [5, 5],
          isCurved: true,
        ),
      ],
      titlesData: FlTitlesData(
        leftTitles: AxisTitles(
          sideTitles: SideTitles(
            showTitles: true,
            getTitlesWidget: (value, meta) => Text('${value.toInt()}%'),
          ),
        ),
        bottomTitles: AxisTitles(
          sideTitles: SideTitles(
            showTitles: true,
            getTitlesWidget: (value, meta) {
              // Show date labels based on period
              if (selectedPeriod == '7d') {
                final days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
                return Text(days[value.toInt() % 7]);
              }
              return Text('');
            },
          ),
        ),
      ),
    ),
  );
}

// Helper function to filter data
List filterByPeriod(
  List data, 
  String period
) {
  final now = DateTime.now();
  final cutoff = period == '7d' 
    ? now.subtract(Duration(days: 7))
    : period == '30d'
      ? now.subtract(Duration(days: 30))
      : now.subtract(Duration(days: 90));
  
  return data.where((item) => item.date.isAfter(cutoff)).toList();
}
        

Missing API Fields (Backend TODO):

Important Notes: