Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 141 additions & 17 deletions src/samples/FleetManagement.Library/FleetManagementComponent.razor
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,118 @@
}
else
{
<IgbGrid Data="FleetService.Data"
AutoGenerate="false"
AllowFiltering="true"
Height="calc(100vh - 200px)"
@ref="grid">
<IgbColumn Field="Id" Header="ID" Width="5%" MinWidth="60px"></IgbColumn>
<IgbColumn Field="VehicleId" Header="Vehicle ID" Width="10%" MinWidth="100px" Sortable="true"></IgbColumn>
<IgbColumn Field="Make" Header="Make" Width="10%" MinWidth="90px"></IgbColumn>
<IgbColumn Field="Model" Header="Model" Width="12%" MinWidth="100px"></IgbColumn>
<IgbColumn Field="Year" Header="Year" DataType="GridColumnDataType.Number" Width="7%" MinWidth="70px"></IgbColumn>
<IgbColumn Field="Mileage" Header="Mileage" DataType="GridColumnDataType.Number" Width="10%" MinWidth="90px"></IgbColumn>
<IgbColumn Field="FuelLevel" Header="Fuel %" DataType="GridColumnDataType.Number" Width="8%" MinWidth="80px"></IgbColumn>
<IgbColumn Field="Status" Header="Status" Width="10%" MinWidth="90px"></IgbColumn>
<IgbColumn Field="Driver" Header="Driver" Width="12%" MinWidth="110px"></IgbColumn>
<IgbColumn Field="Location" Header="Location" Width="12%" MinWidth="110px"></IgbColumn>
</IgbGrid>
<div class="mb-4">
<h2 class="h3 mb-2">Fleet Management</h2>
<p class="text-muted mb-3">Master-Detail Grid for managing vehicle acquisition, operations, and maintenance records</p>

<!-- Summary Cards -->
<div class="row g-3 mb-4">
<div class="col-md-3">
<div class="card bg-primary text-white">
<div class="card-body">
<h6 class="card-title">Total Vehicles</h6>
<h3 class="mb-0">@FleetService.Data.Count</h3>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-success text-white">
<div class="card-body">
<h6 class="card-title">Active</h6>
<h3 class="mb-0">@FleetService.Data.Count(v => v.Status == "Active")</h3>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-warning text-dark">
<div class="card-body">
<h6 class="card-title">In Maintenance</h6>
<h3 class="mb-0">@FleetService.Data.Count(v => v.Status == "Maintenance")</h3>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-info text-white">
<div class="card-body">
<h6 class="card-title">Avg Fuel Level</h6>
<h3 class="mb-0">@(FleetService.Data.Any() ? FleetService.Data.Average(v => v.FuelLevel).ToString("N1") : "0")%</h3>
</div>
</div>
</div>
</div>
</div>

<!-- Master Grid -->
<div class="card shadow-sm mb-3">
<div class="card-header bg-dark text-white">
<h5 class="mb-0">Vehicle Fleet</h5>
</div>
<div class="card-body p-0">
<IgbGrid Data="FleetService.Data"
AutoGenerate="false"
AllowFiltering="true"
PrimaryKey="VehicleId"
Height="400px"
CellClick="OnCellClick"
@ref="grid">
<IgbColumn Field="VehicleId" Header="Vehicle ID" Width="12%" Sortable="true" Filterable="true"></IgbColumn>
<IgbColumn Field="Make" Header="Make" Width="10%" Sortable="true" Filterable="true"></IgbColumn>
<IgbColumn Field="Model" Header="Model" Width="12%" Sortable="true" Filterable="true"></IgbColumn>
<IgbColumn Field="Year" Header="Year" DataType="GridColumnDataType.Number" Width="8%" Sortable="true"></IgbColumn>
<IgbColumn Field="Mileage" Header="Mileage" DataType="GridColumnDataType.Number" Width="10%" Sortable="true"></IgbColumn>
<IgbColumn Field="FuelLevel" Header="Fuel %" DataType="GridColumnDataType.Number" Width="9%" Sortable="true"></IgbColumn>
<IgbColumn Field="Status" Header="Status" Width="11%" Filterable="true"></IgbColumn>
<IgbColumn Field="Driver" Header="Driver" Width="13%" Filterable="true"></IgbColumn>
<IgbColumn Field="Location" Header="Location" Width="15%" Filterable="true"></IgbColumn>
</IgbGrid>
</div>
</div>

<!-- Maintenance Detail Grid -->
@if (selectedVehicle != null)
{
<div class="card shadow-sm">
<div class="card-header bg-secondary text-white">
<h5 class="mb-0">Maintenance History - @selectedVehicle.VehicleId (@selectedVehicle.Make @selectedVehicle.Model)</h5>
</div>
<div class="card-body p-0">
@{
var maintenanceRecords = FleetService.GetMaintenanceForVehicle(selectedVehicle.VehicleId);
}
@if (maintenanceRecords.Any())
{
<IgbGrid Data="maintenanceRecords"
AutoGenerate="false"
Height="300px"
@ref="maintenanceGrid">
<IgbColumn Field="Date" Header="Date" DataType="GridColumnDataType.Date" Width="12%"></IgbColumn>
<IgbColumn Field="Type" Header="Type" Width="15%"></IgbColumn>
<IgbColumn Field="Description" Header="Description" Width="25%"></IgbColumn>
<IgbColumn Field="Cost" Header="Cost" DataType="GridColumnDataType.Currency" Width="10%"></IgbColumn>
<IgbColumn Field="Technician" Header="Technician" Width="15%"></IgbColumn>
<IgbColumn Field="Status" Header="Status" Width="12%"></IgbColumn>
<IgbColumn Field="Mileage" Header="Mileage" DataType="GridColumnDataType.Number" Width="11%"></IgbColumn>
</IgbGrid>
}
else
{
<div class="p-3 text-center text-muted">
<p>No maintenance records found for this vehicle.</p>
</div>
}
</div>
</div>
}
else
{
<div class="alert alert-info" role="alert">
<i class="bi bi-info-circle"></i> Select a vehicle from the grid above to view its maintenance history.
</div>
}

<div class="mt-3">
<small class="text-muted">
<i class="bi bi-info-circle"></i> Data updates every 3 seconds to simulate live fleet tracking
<i class="bi bi-info-circle"></i> Data updates every 3 seconds to simulate live fleet tracking. Click on a row to view maintenance history.
</small>
</div>
}
Expand All @@ -50,6 +143,8 @@
private bool isLoading = true;
private System.Timers.Timer? updateTimer;
private IgbGrid? grid;
private IgbGrid? maintenanceGrid;
private FleetData? selectedVehicle = null;

protected override async Task OnInitializedAsync()
{
Expand All @@ -64,6 +159,12 @@
FleetService.UpdateAllData();
};
updateTimer.Start();

// Select first vehicle by default
if (FleetService.Data.Any())
{
selectedVehicle = FleetService.Data.First();
}
}

private void OnDataChanged()
Expand All @@ -75,9 +176,32 @@
{
grid.MarkForCheck();
}
if (maintenanceGrid != null)
{
maintenanceGrid.MarkForCheck();
}
});
}

private void OnCellClick(object args)
{
// Extract the row data from the click event
dynamic eventArgs = args;
try
{
var clickedVehicle = eventArgs.Cell.Row.Data as FleetData;
if (clickedVehicle != null)
{
selectedVehicle = clickedVehicle;
StateHasChanged();
}
}
catch
{
// Fallback: ignore if event structure is different
}
}

public void Dispose()
{
updateTimer?.Stop();
Expand Down
3 changes: 3 additions & 0 deletions src/samples/FleetManagement.Library/Models/FleetData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,7 @@ public class FleetData

[JsonPropertyName("nextService")]
public DateTime NextService { get; set; }

// Property for child grid data (not serialized from JSON)
public List<MaintenanceData> MaintenanceRecords { get; set; } = new();
}
33 changes: 33 additions & 0 deletions src/samples/FleetManagement.Library/Models/MaintenanceData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System.Text.Json.Serialization;

namespace FleetManagement.Library.Models;

public class MaintenanceData
{
[JsonPropertyName("id")]
public string? Id { get; set; }

[JsonPropertyName("vehicleId")]
public string? VehicleId { get; set; }

[JsonPropertyName("date")]
public DateTime Date { get; set; }

[JsonPropertyName("type")]
public string? Type { get; set; }

[JsonPropertyName("description")]
public string? Description { get; set; }

[JsonPropertyName("cost")]
public double Cost { get; set; }

[JsonPropertyName("technician")]
public string? Technician { get; set; }

[JsonPropertyName("status")]
public string? Status { get; set; }

[JsonPropertyName("mileage")]
public double Mileage { get; set; }
}
31 changes: 29 additions & 2 deletions src/samples/FleetManagement.Library/Services/FleetService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public class FleetService
};

public List<FleetData> Data { get; private set; } = new();
public Dictionary<string, List<MaintenanceData>> MaintenanceData { get; private set; } = new();
public event Action? OnDataChanged;

public FleetService(HttpClient httpClient)
Expand All @@ -30,8 +31,25 @@ public async Task LoadDataAsync()
{
try
{
var jsonText = await _httpClient.GetStringAsync(VehicleDataUrl);
Data = JsonSerializer.Deserialize<List<FleetData>>(jsonText, options) ?? new();
// Load vehicles data
var vehicleJsonText = await _httpClient.GetStringAsync(VehicleDataUrl);
Data = JsonSerializer.Deserialize<List<FleetData>>(vehicleJsonText, options) ?? new();

// Load maintenance data
try
{
var maintenanceJsonText = await _httpClient.GetStringAsync(MaintanenceDataUrl);
var maintenanceList = JsonSerializer.Deserialize<List<MaintenanceData>>(maintenanceJsonText, options) ?? new();

// Group maintenance records by vehicleId
MaintenanceData = maintenanceList
.GroupBy(m => m.VehicleId ?? string.Empty)
.ToDictionary(g => g.Key, g => g.ToList());
}
catch
{
// Maintenance data is optional - continue if it fails
}

OnDataChanged?.Invoke();
}
Expand All @@ -56,4 +74,13 @@ public void UpdateAllData()

OnDataChanged?.Invoke();
}

public List<MaintenanceData> GetMaintenanceForVehicle(string? vehicleId)
{
if (string.IsNullOrEmpty(vehicleId) || !MaintenanceData.ContainsKey(vehicleId))
{
return new List<MaintenanceData>();
}
return MaintenanceData[vehicleId];
}
}
2 changes: 1 addition & 1 deletion src/samples/FleetManagement/Components/App.razor
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<link rel="stylesheet" href="app.css" />
<link rel="stylesheet" href="FleetManagement.styles.css" />
<link rel="icon" type="image/png" href="favicon.png" />
<link href="_content/IgniteUI.Blazor/themes/grid/light/bootstrap.css" rel="stylesheet" />
<link href="_content/IgniteUI.Blazor/themes/grid/dark/material.css" rel="stylesheet" />
<HeadOutlet />
</head>

Expand Down
Loading