@page "/" @rendermode InteractiveServer Overview Dashboard

Travel Overview

Consolidated single source of truth, May 2026 reporting period

Total Travel Spend

R @(_kpiSpend.ToString("N0"))
▲ 4.8% vs. April 2026

Negotiated Savings

R @(_kpiSavings.ToString("N0"))
▲ 14.5% discounted fares

Active Travellers

@(_kpiTravellers)
▲ 12 this month

Bookings Outside Policy

@(_kpiPolicy)
▼ 3 vs. last month

Monthly Spend Trend

Air, accommodation and ground transport over 6 months

Live from GDS
R @((MaxValue).ToString("N0"))k R @((MaxValue * 0.75).ToString("N0"))k R @((MaxValue * 0.5).ToString("N0"))k R @((MaxValue * 0.25).ToString("N0"))k R 0k
@for (int i = 0; i < Months.Length; i++) { var groundCoord = GetCoordinate(i, GroundData[i]); var accCoord = GetCoordinate(i, AccData[i]); var airCoord = GetCoordinate(i, AirData[i]); double delaySeconds = 1.5 * ((double)i / (Months.Length - 1)); string delayStyle = $"transition-delay: {delaySeconds.ToString(System.Globalization.CultureInfo.InvariantCulture)}s;"; R @GroundData[i]k R @AccData[i]k R @AirData[i]k }
@foreach (var month in Months) { @month }
Air
Accommodation
Ground

Spend by Category

May 2026

Air Travel R 1.24M
Accommodation R 720k
Car Rental R 290k
Shuttle R 110k
Other R 80k

Recent Bookings

Last 5 confirmed trips

Traveller Route / Stay Type Status Cost
JM
J. MokoenaExecutive
JNB → CPT, 02 Jun Flight Confirmed R 3,240
NN
N. NdlovuManager
Premier Hotel OR Tambo Hotel Confirmed R 1,895
SP
S. PillayCoordinator
Avis Group B, 3 days Car Pending R 1,420
TM
T. MalulekeDirector
JNB → LHR, 14 Jun Flight Confirmed R 18,940
LK
L. KhumaloOfficer
Shuttle: Sandton ⇄ OR Tambo Shuttle Cancelled R 380

Top Destinations

By spend, this month

CT
Cape Town42 trips · ZA
R 612,400
DBN
Durban28 trips · ZA
R 318,750
PE
Gqeberha19 trips · ZA
R 214,300
LDN
London4 trips · UK
R 188,900
NBO
Nairobi6 trips · KE
R 142,100
@code { private bool _isLoaded = false; // KPI Data variables to manage count-up animation private double _kpiSpend = 0; private double _kpiSavings = 0; private int _kpiTravellers = 0; private int _kpiPolicy = 0; // Dynamic Chart Data configuration private readonly string[] Months = { "Dec", "Jan", "Feb", "Mar", "Apr", "May" }; // HTML Data private readonly double[] AirData = { 820, 905, 870, 1020, 1150, 1240 }; private readonly double[] AccData = { 510, 560, 590, 640, 680, 720 }; private readonly double[] GroundData = { 180, 195, 210, 240, 260, 290 }; // Calculate maximums for scaling (approx 1400 based on data) private double MaxValue => 1400; // Chart dimensions mapped to SVG viewBox private double ChartWidth = 800; private double ChartHeight = 220; protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { await Task.Delay(50); _isLoaded = true; StateHasChanged(); // Run the KPI Count-up animation logic int steps = 40; double targetSpend = 2847310; double targetSavings = 412580; double targetTravellers = 238; double targetPolicy = 7; for (int i = 1; i <= steps; i++) { _kpiSpend = (targetSpend / steps) * i; _kpiSavings = (targetSavings / steps) * i; _kpiTravellers = (int)((targetTravellers / steps) * i); _kpiPolicy = (int)((targetPolicy / steps) * i); StateHasChanged(); await Task.Delay(22); // Replicates the Javascript animation timing } // Ensure exact final values _kpiSpend = targetSpend; _kpiSavings = targetSavings; _kpiTravellers = (int)targetTravellers; _kpiPolicy = (int)targetPolicy; StateHasChanged(); } } private string GetPolylinePoints(double[] data) { var points = new List(); for (int i = 0; i < data.Length; i++) { var coord = GetCoordinate(i, data[i]); points.Add($"{coord.X.ToString(System.Globalization.CultureInfo.InvariantCulture)},{coord.Y.ToString(System.Globalization.CultureInfo.InvariantCulture)}"); } return string.Join(" ", points); } private string GetPolygonPoints(double[] data) { var points = new List(); points.Add($"0,{ChartHeight.ToString(System.Globalization.CultureInfo.InvariantCulture)}"); for (int i = 0; i < data.Length; i++) { var coord = GetCoordinate(i, data[i]); points.Add($"{coord.X.ToString(System.Globalization.CultureInfo.InvariantCulture)},{coord.Y.ToString(System.Globalization.CultureInfo.InvariantCulture)}"); } points.Add($"{ChartWidth.ToString(System.Globalization.CultureInfo.InvariantCulture)},{ChartHeight.ToString(System.Globalization.CultureInfo.InvariantCulture)}"); return string.Join(" ", points); } private (double X, double Y) GetCoordinate(int index, double value) { double xStep = ChartWidth / (Months.Length - 1); double x = index * xStep; double y = ChartHeight - ((value / MaxValue) * ChartHeight); // 10% padding so max values don't clip the top of the SVG box y = y * 0.90 + (ChartHeight * 0.10); return (x, y); } }