@page "/robots/{robotId}/detail" @rendermode RobotNet10.Components.RenderMode.InteractiveWebAssemblyNoPrerender @implements IAsyncDisposable Robot Detail - @robotName @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 Robot Detail: @robotName @if (isRefreshing) { } Refresh @if (isLoadingRobot) { Loading robot information... } else if (robot == null) { Robot Not Found The robot with ID '@RobotId' could not be found. Go Back to Robot List } else { } @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); } } }