Initial commit
This commit is contained in:
@@ -0,0 +1,281 @@
|
||||
@page "/robots/{robotId}/detail"
|
||||
@rendermode RobotNet10.Components.RenderMode.InteractiveWebAssemblyNoPrerender
|
||||
@implements IAsyncDisposable
|
||||
|
||||
<PageTitle>Robot Detail - @robotName</PageTitle>
|
||||
|
||||
@using RobotNet10.FleetManager.Client.Services
|
||||
@using RobotNet10.FleetManager.Client.Components.RobotDetail
|
||||
@using RobotNet10.FleetManager.Shared.DTOs.Robot
|
||||
@using RobotNet.VDA5050.State
|
||||
@using RobotNet.VDA5050.Visualization
|
||||
@using Microsoft.AspNetCore.SignalR.Client
|
||||
@inject RobotApiService RobotApiService
|
||||
@inject RobotStateHubClient HubClient
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject ISnackbar Snackbar
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="mt-4">
|
||||
<!-- Header -->
|
||||
<MudPaper Class="pa-4 mb-4 align-content-center" MinHeight="80px">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Spacing="3">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.ArrowBack" OnClick="NavigateBack" />
|
||||
<MudText Typo="Typo.h5">Robot Detail: @robotName</MudText>
|
||||
</MudStack>
|
||||
<MudButton StartIcon="@Icons.Material.Filled.Refresh"
|
||||
Variant="Variant.Outlined"
|
||||
Color="Color.Primary"
|
||||
OnClick="HandleRefresh"
|
||||
Disabled="@isRefreshing">
|
||||
@if (isRefreshing)
|
||||
{
|
||||
<MudProgressCircular Class="mr-2" Size="Size.Small" Indeterminate="true" />
|
||||
}
|
||||
Refresh
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
|
||||
@if (isLoadingRobot)
|
||||
{
|
||||
<MudPaper Class="pa-4" Elevation="1">
|
||||
<MudStack AlignItems="AlignItems.Center" Spacing="3">
|
||||
<MudProgressCircular Color="Color.Primary" Indeterminate="true" Size="Size.Large" />
|
||||
<MudText Typo="Typo.body1">Loading robot information...</MudText>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
}
|
||||
else if (robot == null)
|
||||
{
|
||||
<MudPaper Class="pa-4" Elevation="1">
|
||||
<MudAlert Severity="Severity.Error" Variant="Variant.Filled">
|
||||
<MudText Typo="Typo.h6">Robot Not Found</MudText>
|
||||
<MudText Typo="Typo.body2">The robot with ID '@RobotId' could not be found.</MudText>
|
||||
<MudButton Variant="Variant.Text" Color="Color.Error" OnClick="NavigateBack" Class="mt-2">
|
||||
<MudIcon Icon="@Icons.Material.Filled.ArrowBack" Class="mr-2" />
|
||||
Go Back to Robot List
|
||||
</MudButton>
|
||||
</MudAlert>
|
||||
</MudPaper>
|
||||
}
|
||||
else
|
||||
{
|
||||
<!-- Main Content -->
|
||||
<MudGrid>
|
||||
<!-- Left: State Cards with Tabs (60%) -->
|
||||
<MudItem xs="12" md="7">
|
||||
<MudPaper Class="pa-2" Elevation="1" Style="height: calc(100vh - 200px); overflow: hidden">
|
||||
<MudTabs @bind-ActivePanelIndex="ActivePanelIndex" Elevation="0" Rounded="true" Color="Color.Primary">
|
||||
<!-- Overview Tab -->
|
||||
<MudTabPanel Text="Overview" Icon="@Icons.Material.Filled.Dashboard">
|
||||
<MudStack Spacing="3" Class="mt-2" Style="overflow-y: auto; height: calc(100vh - 288px);">
|
||||
<HeaderInfoCard @ref="HeaderInfoCardRef" />
|
||||
|
||||
<MudGrid Spacing="3">
|
||||
<MudItem xs="12" md="6">
|
||||
<PositionCard @ref="PositionCardRef" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="6">
|
||||
<VelocityCard @ref="VelocityCardRef" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
<MudGrid Spacing="3">
|
||||
<MudItem xs="12" md="6">
|
||||
<BatteryCard @ref="BatteryCardRef" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="6">
|
||||
<SafetyStateCard @ref="SafetyStateCardRef" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudStack>
|
||||
</MudTabPanel>
|
||||
|
||||
<!-- Navigation Tab -->
|
||||
<MudTabPanel Text="Navigation" Icon="@Icons.Material.Filled.Navigation">
|
||||
<MudStack Spacing="3" Class="mt-2" Style="overflow-y: auto; height: calc(100vh - 288px);">
|
||||
<MapsCard @ref="MapsCardRef" />
|
||||
<NodeStatesCard @ref="NodeStatesCardRef" />
|
||||
<EdgeStatesCard @ref="EdgeStatesCardRef" />
|
||||
</MudStack>
|
||||
</MudTabPanel>
|
||||
|
||||
<!-- Orders & Actions Tab -->
|
||||
<MudTabPanel Text="Orders & Actions" Icon="@Icons.Material.Filled.ListAlt">
|
||||
<MudStack Spacing="3" Class="mt-2" Style="overflow-y: auto; height: calc(100vh - 288px);">
|
||||
<OrderInfoCard @ref="OrderInfoCardRef" />
|
||||
<ActionStatesCard @ref="ActionStatesCardRef" />
|
||||
<LoadsCard @ref="LoadsCardRef" />
|
||||
</MudStack>
|
||||
</MudTabPanel>
|
||||
|
||||
<!-- Errors & Information Tab -->
|
||||
<MudTabPanel Text="Errors & Info" Icon="@Icons.Material.Filled.Info">
|
||||
<MudStack Spacing="3" Class="mt-2" Style="overflow-y: auto; height: calc(100vh - 288px);">
|
||||
<ErrorsCard @ref="ErrorsCardRef" />
|
||||
<InformationCard @ref="InformationCardRef" />
|
||||
</MudStack>
|
||||
</MudTabPanel>
|
||||
</MudTabs>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
|
||||
<!-- Right: Manual Actions & Orders (40%) -->
|
||||
<MudItem xs="12" md="5">
|
||||
<MudStack Spacing="3" Style="max-height: calc(100vh - 200px); overflow-y: auto;">
|
||||
<MudPaper Class="pa-2" Elevation="2">
|
||||
<ManualOrderPanel RobotId="@RobotId" MapId="@robot?.MapId" />
|
||||
</MudPaper>
|
||||
<MudPaper Class="pa-2" Elevation="2">
|
||||
<ManualActionsPanel RobotId="@RobotId" />
|
||||
</MudPaper>
|
||||
</MudStack>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
}
|
||||
</MudContainer>
|
||||
|
||||
<MudThemeProvider IsDarkMode />
|
||||
<MudPopoverProvider />
|
||||
<MudDialogProvider />
|
||||
<MudSnackbarProvider />
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public string RobotId { get; set; } = string.Empty;
|
||||
|
||||
private RobotDto? robot;
|
||||
private string robotName = "Loading...";
|
||||
private bool isLoadingRobot = true;
|
||||
private bool isRefreshing = false;
|
||||
|
||||
private HeaderInfoCard HeaderInfoCardRef = default!;
|
||||
private PositionCard PositionCardRef = default!;
|
||||
private VelocityCard VelocityCardRef = default!;
|
||||
private BatteryCard BatteryCardRef = default!;
|
||||
private SafetyStateCard SafetyStateCardRef = default!;
|
||||
private MapsCard MapsCardRef = default!;
|
||||
private NodeStatesCard NodeStatesCardRef = default!;
|
||||
private EdgeStatesCard EdgeStatesCardRef = default!;
|
||||
private OrderInfoCard OrderInfoCardRef = default!;
|
||||
private ActionStatesCard ActionStatesCardRef = default!;
|
||||
private LoadsCard LoadsCardRef = default!;
|
||||
private ErrorsCard ErrorsCardRef = default!;
|
||||
private InformationCard InformationCardRef = default!;
|
||||
|
||||
private int ActivePanelIndex = 0;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadRobotAsync();
|
||||
await InitializeSignalRAsync();
|
||||
}
|
||||
|
||||
private async Task LoadRobotAsync()
|
||||
{
|
||||
isLoadingRobot = true;
|
||||
try
|
||||
{
|
||||
robot = await RobotApiService.GetByRobotIdAsync(RobotId);
|
||||
if (robot != null)
|
||||
{
|
||||
robotName = robot.Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
Snackbar.Add($"Robot with ID '{RobotId}' not found", Severity.Error);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"Error loading robot: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
isLoadingRobot = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task InitializeSignalRAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Register event handlers
|
||||
HubClient.OnStateUpdate += HandleStateUpdate;
|
||||
HubClient.OnConnectionError += HandleConnectionError;
|
||||
|
||||
// Connect and subscribe
|
||||
await HubClient.ConnectAsync();
|
||||
await HubClient.SubscribeToRobotAsync(RobotId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"Error connecting to SignalR: {ex.Message}", Severity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void StateUpdate(StateMsg? state)
|
||||
{
|
||||
switch (ActivePanelIndex)
|
||||
{
|
||||
case 0:
|
||||
HeaderInfoCardRef.Update(state);
|
||||
BatteryCardRef.Update(state?.BatteryState);
|
||||
SafetyStateCardRef.Update(state);
|
||||
PositionCardRef.Update(state);
|
||||
VelocityCardRef.Update(state);
|
||||
break;
|
||||
case 1:
|
||||
MapsCardRef.Update(state);
|
||||
NodeStatesCardRef.Update(state);
|
||||
EdgeStatesCardRef.Update(state);
|
||||
break;
|
||||
case 2:
|
||||
OrderInfoCardRef.Update(state);
|
||||
ActionStatesCardRef.Update(state);
|
||||
LoadsCardRef.Update(state);
|
||||
break;
|
||||
case 3:
|
||||
ErrorsCardRef.Update(state?.Errors);
|
||||
InformationCardRef.Update(state?.Information);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleStateUpdate(StateMsg state)
|
||||
{
|
||||
if (state.SerialNumber != RobotId) return;
|
||||
StateUpdate(state);
|
||||
}
|
||||
|
||||
private void HandleConnectionError(string error)
|
||||
{
|
||||
Snackbar.Add($"SignalR connection error: {error}", Severity.Warning);
|
||||
}
|
||||
|
||||
private async Task HandleRefresh()
|
||||
{
|
||||
isRefreshing = true;
|
||||
StateHasChanged();
|
||||
await LoadRobotAsync();
|
||||
isRefreshing = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private void NavigateBack()
|
||||
{
|
||||
NavigationManager.NavigateTo("/robots");
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
if (HubClient != null)
|
||||
{
|
||||
HubClient.OnStateUpdate -= HandleStateUpdate;
|
||||
HubClient.OnConnectionError -= HandleConnectionError;
|
||||
await HubClient.UnsubscribeFromRobotAsync(RobotId);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user