Initial commit
This commit is contained in:
@@ -0,0 +1,248 @@
|
||||
@using RobotNet10.FleetManager.Shared.DTOs.Robot
|
||||
@using RobotNet10.FleetManager.Shared.DTOs.RobotModel
|
||||
@using RobotNet10.FleetManager.Client.Services
|
||||
@using RobotNet10.MapEditor.Services.API
|
||||
@using RobotNet10.MapEditor.Shared.DTOs.Layout
|
||||
@inject ISnackbar Snackbar
|
||||
@inject MapManagerApiService MapApiService
|
||||
|
||||
<MudDialog>
|
||||
<TitleContent>
|
||||
<MudText Typo="Typo.h6">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Add" Color="Color.Primary" Class="mr-2" />
|
||||
Create Robot
|
||||
</MudText>
|
||||
</TitleContent>
|
||||
<DialogContent>
|
||||
<MudStack Spacing="3">
|
||||
<MudTextField @bind-Value="request.RobotId"
|
||||
Label="Robot ID *"
|
||||
Variant="Variant.Outlined"
|
||||
ErrorText="@GetValidationError("RobotId")"
|
||||
HelperText="Unique identifier for the robot" />
|
||||
|
||||
<MudTextField @bind-Value="request.Name"
|
||||
Label="Name *"
|
||||
Variant="Variant.Outlined"
|
||||
ErrorText="@GetValidationError("Name")" />
|
||||
|
||||
<MudSelect @bind-Value="request.ModelId"
|
||||
Label="Robot Model *"
|
||||
Variant="Variant.Outlined"
|
||||
T="Guid"
|
||||
ErrorText="@GetValidationError("ModelId")">
|
||||
@if (isLoadingModels)
|
||||
{
|
||||
<MudProgressCircular Size="Size.Small" Indeterminate="true" />
|
||||
}
|
||||
else
|
||||
{
|
||||
@foreach (var model in robotModels)
|
||||
{
|
||||
<MudSelectItem Value="@model.Id">@model.ModelName</MudSelectItem>
|
||||
}
|
||||
}
|
||||
</MudSelect>
|
||||
|
||||
<MudSelect @bind-Value="request.MapId"
|
||||
Label="Map (Optional)"
|
||||
Variant="Variant.Outlined"
|
||||
Clearable="true"
|
||||
T="Guid?"
|
||||
Disabled="@isLoadingMaps"
|
||||
ErrorText="@GetValidationError("MapId")">
|
||||
@if (isLoadingMaps)
|
||||
{
|
||||
<MudSelectItem Value="@((Guid?)null)" Disabled="true">Loading maps...</MudSelectItem>
|
||||
}
|
||||
else if (mapLevels != null && mapLevels.Any())
|
||||
{
|
||||
<MudSelectItem Value="@((Guid?)null)">No Map</MudSelectItem>
|
||||
@foreach (var mapLevel in mapLevels)
|
||||
{
|
||||
<MudSelectItem Value="@((Guid?)mapLevel.LevelId)">@mapLevel.DisplayName</MudSelectItem>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudSelectItem Value="@((Guid?)null)" Disabled="true">No maps available</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudStack>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Cancel</MudButton>
|
||||
<MudButton Color="Color.Primary"
|
||||
Variant="Variant.Filled"
|
||||
OnClick="Submit"
|
||||
Disabled="@isCreating">
|
||||
@if (isCreating)
|
||||
{
|
||||
<MudProgressCircular Class="mr-2" Size="Size.Small" Indeterminate="true" />
|
||||
<span>Creating...</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>Create</span>
|
||||
}
|
||||
</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@code {
|
||||
[CascadingParameter] private IMudDialogInstance? MudDialog { get; set; }
|
||||
[Parameter] public RobotApiService RobotApiService { get; set; } = null!;
|
||||
[Parameter] public RobotModelApiService RobotModelApiService { get; set; } = null!;
|
||||
|
||||
private CreateRobotRequest request = new();
|
||||
private List<RobotModelDto> robotModels = new();
|
||||
private List<MapLevelInfo> mapLevels = new();
|
||||
private Dictionary<string, string> validationErrors = new();
|
||||
private bool isCreating = false;
|
||||
private bool isLoadingModels = true;
|
||||
private bool isLoadingMaps = false;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await Task.WhenAll(
|
||||
LoadRobotModelsAsync(),
|
||||
LoadMapsAsync()
|
||||
);
|
||||
}
|
||||
|
||||
private async Task LoadRobotModelsAsync()
|
||||
{
|
||||
isLoadingModels = true;
|
||||
try
|
||||
{
|
||||
robotModels = await RobotModelApiService.GetAllAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"Error loading robot models: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
isLoadingModels = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadMapsAsync()
|
||||
{
|
||||
isLoadingMaps = true;
|
||||
StateHasChanged();
|
||||
try
|
||||
{
|
||||
// Load all layouts (with nested versions and levels)
|
||||
var layouts = await MapApiService.SearchLayoutsAsync();
|
||||
if (layouts is null) return;
|
||||
|
||||
// Get all levels from all layouts (not just active layouts)
|
||||
mapLevels = new List<MapLevelInfo>();
|
||||
|
||||
foreach (var layout in layouts.Where(l => l.Versions != null))
|
||||
{
|
||||
if (layout.Versions is null) continue;
|
||||
foreach (var version in layout.Versions.Where(v => v.Levels != null))
|
||||
{
|
||||
if (version.Levels is null) continue;
|
||||
foreach (var level in version.Levels.OrderBy(l => l.LevelOrder))
|
||||
{
|
||||
mapLevels.Add(new MapLevelInfo
|
||||
{
|
||||
LevelId = level.Id,
|
||||
DisplayName = $"{layout.LayoutName} - {version.Version} - {level.LayoutLevelId}"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"Error loading maps: {ex.Message}", Severity.Warning);
|
||||
mapLevels = new List<MapLevelInfo>();
|
||||
}
|
||||
finally
|
||||
{
|
||||
isLoadingMaps = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private bool Validate()
|
||||
{
|
||||
validationErrors.Clear();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.RobotId))
|
||||
{
|
||||
validationErrors["RobotId"] = "Robot ID is required";
|
||||
}
|
||||
else if (request.RobotId.Length > 64)
|
||||
{
|
||||
validationErrors["RobotId"] = "Robot ID must be 64 characters or less";
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.Name))
|
||||
{
|
||||
validationErrors["Name"] = "Name is required";
|
||||
}
|
||||
else if (request.Name.Length > 256)
|
||||
{
|
||||
validationErrors["Name"] = "Name must be 256 characters or less";
|
||||
}
|
||||
|
||||
if (request.ModelId == Guid.Empty)
|
||||
{
|
||||
validationErrors["ModelId"] = "Robot Model is required";
|
||||
}
|
||||
|
||||
return validationErrors.Count == 0;
|
||||
}
|
||||
|
||||
private string? GetValidationError(string fieldName)
|
||||
{
|
||||
return validationErrors.TryGetValue(fieldName, out var error) ? error : null;
|
||||
}
|
||||
|
||||
private void Cancel()
|
||||
{
|
||||
MudDialog?.Cancel();
|
||||
}
|
||||
|
||||
private async Task Submit()
|
||||
{
|
||||
if (!Validate())
|
||||
{
|
||||
Snackbar.Add("Please fix validation errors", Severity.Warning);
|
||||
StateHasChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
isCreating = true;
|
||||
StateHasChanged();
|
||||
|
||||
try
|
||||
{
|
||||
var created = await RobotApiService.CreateAsync(request);
|
||||
Snackbar.Add($"Robot '{request.Name}' created successfully", Severity.Success);
|
||||
MudDialog?.Close(DialogResult.Ok(created));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"Error creating robot: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
isCreating = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
// Helper class for map level display
|
||||
private class MapLevelInfo
|
||||
{
|
||||
public Guid LevelId { get; set; }
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
@using RobotNet10.FleetManager.Shared.DTOs.Robot
|
||||
@using RobotNet10.FleetManager.Client.Services
|
||||
@inject ISnackbar Snackbar
|
||||
|
||||
<MudDialog>
|
||||
<TitleContent>
|
||||
<MudText Typo="Typo.h6">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Delete" Color="Color.Error" Class="mr-2" />
|
||||
Delete Robot
|
||||
</MudText>
|
||||
</TitleContent>
|
||||
<DialogContent>
|
||||
<MudStack Spacing="3">
|
||||
<MudText Typo="Typo.body1">
|
||||
Are you sure you want to delete the robot <strong>@Robot.Name</strong> (ID: <strong>@Robot.RobotId</strong>)?
|
||||
</MudText>
|
||||
|
||||
<MudAlert Severity="Severity.Warning">
|
||||
<MudText>This action cannot be undone.</MudText>
|
||||
</MudAlert>
|
||||
</MudStack>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Cancel</MudButton>
|
||||
<MudButton Color="Color.Error"
|
||||
Variant="Variant.Filled"
|
||||
OnClick="Submit"
|
||||
Disabled="@isDeleting">
|
||||
@if (isDeleting)
|
||||
{
|
||||
<MudProgressCircular Class="mr-2" Size="Size.Small" Indeterminate="true" />
|
||||
<span>Deleting...</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>Delete</span>
|
||||
}
|
||||
</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@code {
|
||||
[CascadingParameter] private IMudDialogInstance? MudDialog { get; set; }
|
||||
[Parameter] public RobotApiService RobotApiService { get; set; } = null!;
|
||||
[Parameter] public RobotDto Robot { get; set; } = null!;
|
||||
|
||||
private bool isDeleting = false;
|
||||
|
||||
private void Cancel()
|
||||
{
|
||||
MudDialog?.Cancel();
|
||||
}
|
||||
|
||||
private async Task Submit()
|
||||
{
|
||||
isDeleting = true;
|
||||
StateHasChanged();
|
||||
|
||||
try
|
||||
{
|
||||
await RobotApiService.DeleteAsync(Robot.Id);
|
||||
Snackbar.Add($"Robot '{Robot.Name}' deleted successfully", Severity.Success);
|
||||
MudDialog?.Close(DialogResult.Ok(true));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"Error deleting robot: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
isDeleting = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
@using RobotNet10.FleetManager.Shared.DTOs.Robot
|
||||
@using RobotNet10.FleetManager.Shared.DTOs.RobotModel
|
||||
@using RobotNet10.FleetManager.Client.Services
|
||||
@using RobotNet10.MapEditor.Services.API
|
||||
@using RobotNet10.MapEditor.Shared.DTOs.Layout
|
||||
@inject ISnackbar Snackbar
|
||||
@inject MapManagerApiService MapApiService
|
||||
|
||||
<MudDialog>
|
||||
<TitleContent>
|
||||
<MudText Typo="Typo.h6">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Edit" Color="Color.Primary" Class="mr-2" />
|
||||
Edit Robot
|
||||
</MudText>
|
||||
</TitleContent>
|
||||
<DialogContent>
|
||||
<MudStack Spacing="3">
|
||||
<MudTextField @bind-Value="request.RobotId"
|
||||
Label="Robot ID"
|
||||
Variant="Variant.Outlined"
|
||||
ErrorText="@GetValidationError("RobotId")"
|
||||
HelperText="Unique identifier for the robot" ReadOnly />
|
||||
|
||||
<MudTextField @bind-Value="request.Name"
|
||||
Label="Name"
|
||||
Variant="Variant.Outlined"
|
||||
ErrorText="@GetValidationError("Name")" />
|
||||
|
||||
<MudSelect @bind-Value="request.ModelId"
|
||||
Label="Robot Model"
|
||||
Variant="Variant.Outlined"
|
||||
T="Guid ?"
|
||||
ErrorText="@GetValidationError("ModelId")">
|
||||
@if (isLoadingModels)
|
||||
{
|
||||
<MudProgressCircular Size="Size.Small" Indeterminate="true" />
|
||||
}
|
||||
else
|
||||
{
|
||||
@foreach (var model in robotModels)
|
||||
{
|
||||
<MudSelectItem Value="@((Guid?)model.Id)">@model.ModelName</MudSelectItem>
|
||||
}
|
||||
}
|
||||
</MudSelect>
|
||||
|
||||
<MudSelect @bind-Value="request.MapId"
|
||||
Label="Map (Optional)"
|
||||
Variant="Variant.Outlined"
|
||||
Clearable="true"
|
||||
T="Guid ?"
|
||||
Disabled="@isLoadingMaps"
|
||||
ErrorText="@GetValidationError("MapId")">
|
||||
@if (isLoadingMaps)
|
||||
{
|
||||
<MudSelectItem Value="@((Guid?)null)" Disabled="true">Loading maps...</MudSelectItem>
|
||||
}
|
||||
else if (mapLevels != null && mapLevels.Any())
|
||||
{
|
||||
<MudSelectItem Value="@((Guid?)null)">No Map</MudSelectItem>
|
||||
@foreach (var mapLevel in mapLevels)
|
||||
{
|
||||
<MudSelectItem Value="@((Guid?)mapLevel.LevelId)">@mapLevel.DisplayName</MudSelectItem>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudSelectItem Value="@((Guid?)null)" Disabled="true">No maps available</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudStack>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Cancel</MudButton>
|
||||
<MudButton Color="Color.Primary"
|
||||
Variant="Variant.Filled"
|
||||
OnClick="Submit"
|
||||
Disabled="@isUpdating">
|
||||
@if (isUpdating)
|
||||
{
|
||||
<MudProgressCircular Class="mr-2" Size="Size.Small" Indeterminate="true" />
|
||||
<span>Updating...</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>Update</span>
|
||||
}
|
||||
</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@code {
|
||||
[CascadingParameter] private IMudDialogInstance? MudDialog { get; set; }
|
||||
[Parameter] public RobotApiService RobotApiService { get; set; } = null!;
|
||||
[Parameter] public RobotModelApiService RobotModelApiService { get; set; } = null!;
|
||||
[Parameter] public RobotDto Robot { get; set; } = null!;
|
||||
|
||||
private UpdateRobotRequest request = new();
|
||||
private List<RobotModelDto> robotModels = new();
|
||||
private List<MapLevelInfo> mapLevels = new();
|
||||
private Dictionary<string, string> validationErrors = new();
|
||||
private bool isUpdating = false;
|
||||
private bool isLoadingModels = true;
|
||||
private bool isLoadingMaps = false;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
// Pre-fill with existing data
|
||||
request.RobotId = Robot.RobotId;
|
||||
request.Name = Robot.Name;
|
||||
request.ModelId = Robot.ModelId;
|
||||
request.MapId = Robot.MapId;
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await Task.WhenAll(
|
||||
LoadRobotModelsAsync(),
|
||||
LoadMapsAsync()
|
||||
);
|
||||
}
|
||||
|
||||
private async Task LoadRobotModelsAsync()
|
||||
{
|
||||
isLoadingModels = true;
|
||||
try
|
||||
{
|
||||
robotModels = await RobotModelApiService.GetAllAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"Error loading robot models: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
isLoadingModels = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadMapsAsync()
|
||||
{
|
||||
isLoadingMaps = true;
|
||||
StateHasChanged();
|
||||
try
|
||||
{
|
||||
// Load all layouts (with nested versions and levels)
|
||||
var layouts = await MapApiService.SearchLayoutsAsync();
|
||||
if (layouts is null) return;
|
||||
|
||||
// Get all levels from all layouts (not just active layouts)
|
||||
mapLevels = new List<MapLevelInfo>();
|
||||
|
||||
foreach (var layout in layouts.Where(l => l.Versions != null))
|
||||
{
|
||||
if (layout.Versions is null) continue;
|
||||
foreach (var version in layout.Versions.Where(v => v.Levels != null))
|
||||
{
|
||||
if (version.Levels is null) continue;
|
||||
foreach (var level in version.Levels.OrderBy(l => l.LevelOrder))
|
||||
{
|
||||
mapLevels.Add(new MapLevelInfo
|
||||
{
|
||||
LevelId = level.Id,
|
||||
DisplayName = $"{layout.LayoutName} - {version.Version} - {level.LayoutLevelId}"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"Error loading maps: {ex.Message}", Severity.Warning);
|
||||
mapLevels = new List<MapLevelInfo>();
|
||||
}
|
||||
finally
|
||||
{
|
||||
isLoadingMaps = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private bool Validate()
|
||||
{
|
||||
validationErrors.Clear();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(request.RobotId) && request.RobotId.Length > 64)
|
||||
{
|
||||
validationErrors["RobotId"] = "Robot ID must be 64 characters or less";
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(request.Name) && request.Name.Length > 256)
|
||||
{
|
||||
validationErrors["Name"] = "Name must be 256 characters or less";
|
||||
}
|
||||
|
||||
return validationErrors.Count == 0;
|
||||
}
|
||||
|
||||
private string? GetValidationError(string fieldName)
|
||||
{
|
||||
return validationErrors.TryGetValue(fieldName, out var error) ? error : null;
|
||||
}
|
||||
|
||||
private void Cancel()
|
||||
{
|
||||
MudDialog?.Cancel();
|
||||
}
|
||||
|
||||
private async Task Submit()
|
||||
{
|
||||
if (!Validate())
|
||||
{
|
||||
Snackbar.Add("Please fix validation errors", Severity.Warning);
|
||||
StateHasChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
isUpdating = true;
|
||||
StateHasChanged();
|
||||
|
||||
try
|
||||
{
|
||||
var updated = await RobotApiService.UpdateAsync(Robot.Id, request);
|
||||
Snackbar.Add($"Robot '{updated.Name}' updated successfully", Severity.Success);
|
||||
MudDialog?.Close(DialogResult.Ok(updated));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"Error updating robot: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
isUpdating = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
// Helper class for map level display
|
||||
private class MapLevelInfo
|
||||
{
|
||||
public Guid LevelId { get; set; }
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,392 @@
|
||||
@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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user