Files
BQP/srcs/RobotNet10/FleetManager/RobotNet10.FleetManager.Client/Components/RobotManager/RobotTable.razor
2026-07-13 09:25:40 +07:00

393 lines
14 KiB
Plaintext

@using RobotNet10.FleetManager.Shared.DTOs.Robot
@using RobotNet10.FleetManager.Shared.DTOs.RobotModel
@using RobotNet10.FleetManager.Client.Services
@using RobotNet10.FleetManager.Client.Components.RobotManager.Dialogs
@using RobotNet10.MapEditor.Services.API
@using RobotNet10.MapEditor.Shared.DTOs.Layout
@using RobotNet10.Shared
@using System.Net.Http.Json
@inject IDialogService DialogService
@inject ISnackbar Snackbar
@inject NavigationManager NavigationManager
@inject MapManagerApiService MapApiService
@inject HttpClient HttpClient
<MudPaper Class="pa-4" Elevation="1">
<MudStack Spacing="3">
<!-- Search and Filters -->
<MudGrid>
<MudItem xs="12" md="4">
<MudTextField Value="@searchText"
Placeholder="Search by RobotId or Name..."
Variant="Variant.Outlined"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Search"
Margin="Margin.Dense"
Immediate="false"
T="string"
ValueChanged="OnSearchTextChanged"
Clearable="true"
Class="mt-2" />
</MudItem>
<MudItem xs="12" md="4">
<MudSelect @bind-Value="selectedModelId"
@bind-Value:after="OnModelFilterChanged"
Label="Filter by Model"
Variant="Variant.Outlined"
Margin="Margin.Dense"
Clearable="true"
T="Guid ?">
@foreach (var model in robotModels)
{
<MudSelectItem Value="@((Guid?)model.Id)">@model.ModelName</MudSelectItem>
}
</MudSelect>
</MudItem>
<MudItem xs="12" md="4">
<MudSelect @bind-Value="selectedMapId"
@bind-Value:after="OnMapFilterChanged"
Label="Filter by Map"
Variant="Variant.Outlined"
Margin="Margin.Dense"
Clearable="true"
T="Guid ?">
<!-- TODO: Load maps from MapManager service if available -->
<MudSelectItem Value="@((Guid?)null)">All Maps</MudSelectItem>
</MudSelect>
</MudItem>
</MudGrid>
<!-- Robot Table -->
@if (isLoading)
{
<MudStack AlignItems="AlignItems.Center" Spacing="2" Class="my-4">
<MudProgressCircular Color="Color.Primary" Indeterminate="true" Size="Size.Medium" />
<MudText Typo="Typo.body2">Loading robots...</MudText>
</MudStack>
}
else if (robots.Count == 0)
{
<MudPaper Class="pa-4" Elevation="0">
<MudText Typo="Typo.body1" Align="Align.Center" Class="mt-4">
@if (!string.IsNullOrWhiteSpace(searchText) || selectedModelId.HasValue || selectedMapId.HasValue)
{
<span>No robots found matching the current filters.</span>
}
else
{
<span>No robots found. Click "Add Robot" to create one.</span>
}
</MudText>
</MudPaper>
}
else
{
<MudTable Items="@robots"
@ref="@table"
T="RobotDto"
Hover="true"
Dense="true"
FixedHeader="true"
Elevation="0"
Height="calc(100vh - 325px)">
<HeaderContent>
<MudTh>Robot ID</MudTh>
<MudTh>Name</MudTh>
<MudTh>Model</MudTh>
<MudTh>Map</MudTh>
<MudTh>Status</MudTh>
<MudTh>Created Date</MudTh>
<MudTh></MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Robot ID">
<MudText Typo="Typo.body2">@context.RobotId</MudText>
</MudTd>
<MudTd DataLabel="Name">
<MudText Typo="Typo.body2">@context.Name</MudText>
</MudTd>
<MudTd DataLabel="Model">
<MudChip T="string" Size="Size.Small" Color="Color.Primary">
@(context.ModelName ?? "N/A")
</MudChip>
</MudTd>
<MudTd DataLabel="Map">
<MudText Typo="Typo.body2">@GetMapDisplayName(context.MapId)</MudText>
</MudTd>
<MudTd DataLabel="Status">
@if (GetRobotOnlineStatus(context.RobotId))
{
<MudChip T="string" Size="Size.Small" Color="Color.Success">Online</MudChip>
}
else
{
<MudChip T="string" Size="Size.Small" Color="Color.Default">Offline</MudChip>
}
</MudTd>
<MudTd DataLabel="Created Date">
<MudText Typo="Typo.body2">@context.CreatedDate.ToString("g")</MudText>
</MudTd>
<MudTd DataLabel="Actions">
<MudIconButton Icon="@Icons.Material.Filled.Edit"
Color="Color.Primary"
Size="Size.Small"
OnClick="@(() => HandleEdit(context))" />
<MudIconButton Icon="@Icons.Material.Filled.Delete"
Color="Color.Error"
Size="Size.Small"
OnClick="@(() => HandleDelete(context))" />
<MudIconButton Color="Color.Primary"
Size="Size.Small"
Icon="@Icons.Material.Filled.TrendingFlat"
OnClick="@(() => NavigateToDetail(context.RobotId))">
</MudIconButton>
</MudTd>
</RowTemplate>
<PagerContent>
<div class="d-flex w-100 flex-row-reverse">
<MudTablePager Style="width: 100%;" PageSizeOptions="new[] { 25, 50, 100 }" />
</div>
</PagerContent>
</MudTable>
}
</MudStack>
</MudPaper>
@code {
[Parameter]
public RobotApiService RobotApiService { get; set; } = null!;
[Parameter]
public RobotModelApiService RobotModelApiService { get; set; } = null!;
[Parameter]
public EventCallback OnRobotDeleted { get; set; }
[Parameter]
public EventCallback OnRobotUpdated { get; set; }
private List<RobotDto> robots = new();
private List<RobotModelDto> robotModels = new();
private MudTable<RobotDto>? table;
private bool isLoading = false;
private string searchText = string.Empty;
private Guid? selectedModelId;
private Guid? selectedMapId;
private Dictionary<Guid, string> mapDisplayNames = new();
private Dictionary<string, bool> robotOnlineStatus = new();
protected override async Task OnInitializedAsync()
{
await LoadRobotModelsAsync();
await LoadDataAsync();
}
protected override async Task OnParametersSetAsync()
{
await LoadDataAsync();
}
public async Task LoadDataAsync()
{
isLoading = true;
StateHasChanged();
try
{
if (!string.IsNullOrWhiteSpace(searchText))
{
robots = await RobotApiService.SearchAsync(searchText);
}
else
{
robots = await RobotApiService.GetAllAsync(selectedModelId, selectedMapId);
}
// Load map display names and online status
await LoadMapDisplayNamesAsync();
await LoadRobotOnlineStatusAsync();
table?.ReloadServerData();
}
catch (HttpRequestException ex)
{
Snackbar.Add($"Network error loading robots: {ex.Message}", Severity.Error);
robots = new List<RobotDto>();
}
catch (Exception ex)
{
Snackbar.Add($"Error loading robots: {ex.Message}", Severity.Error);
robots = new List<RobotDto>();
}
finally
{
isLoading = false;
StateHasChanged();
}
}
private async Task LoadMapDisplayNamesAsync()
{
mapDisplayNames.Clear();
var mapIds = robots.Where(r => r.MapId.HasValue).Select(r => r.MapId!.Value).Distinct().ToList();
if (mapIds.Count == 0) return;
try
{
// Load all layouts once
var layouts = await MapApiService.SearchLayoutsAsync();
// Create a dictionary to map level ID to layout/version info
var levelInfoMap = new Dictionary<Guid, (string LayoutName, string Version, string LevelId)>();
foreach (var layout in layouts)
{
if (layout.Versions != null)
{
foreach (var version in layout.Versions)
{
if (version.Levels != null)
{
foreach (var level in version.Levels)
{
levelInfoMap[level.Id] = (layout.LayoutName, version.Version, level.LayoutLevelId);
}
}
}
}
}
// Build display names for each map ID
foreach (var mapId in mapIds)
{
if (levelInfoMap.TryGetValue(mapId, out var info))
{
mapDisplayNames[mapId] = $"{info.LayoutName} - {info.Version} - {info.LevelId}";
}
else
{
mapDisplayNames[mapId] = "N/A";
}
}
}
catch (Exception ex)
{
// If loading fails, set all to N/A
foreach (var mapId in mapIds)
{
mapDisplayNames[mapId] = "N/A";
}
Snackbar.Add($"Error loading map information: {ex.Message}", Severity.Warning);
}
}
private async Task LoadRobotOnlineStatusAsync()
{
robotOnlineStatus.Clear();
var tasks = robots.Select(async robot =>
{
try
{
var response = await HttpClient.GetAsync($"/api/RobotManager/OnlineStatus/{robot.RobotId}");
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadFromJsonAsync<MessageResult<bool>>();
robotOnlineStatus[robot.RobotId] = result?.Data ?? false;
}
else
{
robotOnlineStatus[robot.RobotId] = false;
}
}
catch
{
robotOnlineStatus[robot.RobotId] = false;
}
});
await Task.WhenAll(tasks);
}
private string GetMapDisplayName(Guid? mapId)
{
if (!mapId.HasValue) return "N/A";
return mapDisplayNames.TryGetValue(mapId.Value, out var displayName) ? displayName : "Loading...";
}
private bool GetRobotOnlineStatus(string robotId)
{
return robotOnlineStatus.TryGetValue(robotId, out var isOnline) && isOnline;
}
private async Task LoadRobotModelsAsync()
{
try
{
robotModels = await RobotModelApiService.GetAllAsync();
}
catch (Exception ex)
{
Snackbar.Add($"Error loading robot models: {ex.Message}", Severity.Error);
}
}
private async Task OnSearchTextChanged(string text)
{
searchText = text;
await LoadDataAsync();
}
private async Task OnModelFilterChanged()
{
await LoadDataAsync();
}
private async Task OnMapFilterChanged()
{
await LoadDataAsync();
}
private void NavigateToDetail(string robotId)
{
NavigationManager.NavigateTo($"/robots/{robotId}/detail");
}
private async Task HandleEdit(RobotDto robot)
{
var parameters = new DialogParameters
{
["RobotApiService"] = RobotApiService,
["RobotModelApiService"] = RobotModelApiService,
["Robot"] = robot
};
var dialog = await DialogService.ShowAsync<EditRobotDialog>("Edit Robot", parameters,
new DialogOptions { MaxWidth = MaxWidth.Medium, FullWidth = true });
var result = await dialog.Result;
if (result != null && !result.Canceled)
{
await LoadDataAsync();
await OnRobotUpdated.InvokeAsync();
}
}
private async Task HandleDelete(RobotDto robot)
{
var parameters = new DialogParameters
{
["RobotApiService"] = RobotApiService,
["Robot"] = robot
};
var dialog = await DialogService.ShowAsync<DeleteRobotDialog>("Delete Robot", parameters);
var result = await dialog.Result;
if (result != null && !result.Canceled)
{
await LoadDataAsync();
await OnRobotDeleted.InvokeAsync();
}
}
}