@using RobotNet10.FleetManager.Shared.DTOs.Robot @using RobotNet10.FleetManager.Client.Services @using RobotNet10.MapEditor.Services.API @using RobotNet10.MapEditor.Shared.DTOs.LayoutData @using RobotNet10.MapEditor.Shared.DTOs.Node @using RobotNet10.FleetManager.Shared.Models @using RobotNet10.Shared @using System.Net.Http.Json @inject HttpClient HttpClient @inject ISnackbar Snackbar @inject RobotApiService RobotApiService @inject MapManagerApiService MapApiService Manual Order @if (isLoadingNodes) { Loading nodes... } else if (nodes != null && nodes.Any()) { -- Select Node -- @foreach (var node in nodes.Where(n => !string.IsNullOrEmpty(n.NodeName)).OrderBy(n => n.NodeName)) { var displayName = !string.IsNullOrWhiteSpace(node.NodeName) ? $"{node.NodeName} ({node.NodeId})" : node.NodeId; @displayName } } else { No nodes available } @if (!string.IsNullOrWhiteSpace(selectedNodeName)) { var selectedNode = nodes?.FirstOrDefault(n => (!string.IsNullOrWhiteSpace(n.NodeName) && n.NodeName == selectedNodeName) || (string.IsNullOrWhiteSpace(n.NodeName) && n.NodeId == selectedNodeName)); @if (selectedNode != null) { Node ID: @selectedNode.NodeId
@if (!string.IsNullOrWhiteSpace(selectedNode.NodeDescription)) { Description: @selectedNode.NodeDescription
} Position: X: @selectedNode.X.ToString("F2")m, Y: @selectedNode.Y.ToString("F2")m
} } @if (isSending) { Sending... } else { Send Order } @if (isCanceling) { Canceling... } else { Cancel Order } Recent Orders @if (orderHistory.Count > 0) { @foreach (var order in orderHistory.Take(10)) { @order.NodeName - @order.Timestamp.ToString("g") @if (!order.IsSuccess) { Failed } else { Success } } } else { No orders sent yet }
@code { [Parameter] public string RobotId { get; set; } = string.Empty; [Parameter] public Guid? MapId { get; set; } private string selectedNodeName = string.Empty; private List nodes = new(); private List orderHistory = new(); private bool isSending = false; private bool isCanceling = false; private bool isLoadingNodes = false; private Dictionary validationErrors = new(); protected override async Task OnInitializedAsync() { await LoadNodesAsync(); } private async Task LoadNodesAsync() { if (!MapId.HasValue || MapId.Value == Guid.Empty) { // Try to get MapId from robot try { var robot = await RobotApiService.GetByRobotIdAsync(RobotId); if (robot?.MapId.HasValue == true) { MapId = robot.MapId; } else { nodes = new List(); return; } } catch (Exception ex) { Snackbar.Add($"Error loading robot: {ex.Message}", Severity.Warning); nodes = new List(); return; } } isLoadingNodes = true; StateHasChanged(); try { var layoutData = await MapApiService.GetLayoutDataAsync(MapId.Value); nodes = layoutData.Nodes?.ToList() ?? new List(); } catch (Exception ex) { Snackbar.Add($"Error loading nodes: {ex.Message}", Severity.Warning); nodes = new List(); } finally { isLoadingNodes = false; StateHasChanged(); } } private string? GetValidationError(string fieldName) { return validationErrors.TryGetValue(fieldName, out var error) ? error : null; } private async Task SendOrder() { if (string.IsNullOrWhiteSpace(RobotId)) { Snackbar.Add("Robot ID is required", Severity.Warning); return; } if (string.IsNullOrWhiteSpace(selectedNodeName)) { validationErrors["NodeName"] = "Please select a node"; StateHasChanged(); return; } isSending = true; validationErrors.Clear(); StateHasChanged(); try { // Find the selected node to get NodeName (preferred) or NodeId var selectedNode = nodes?.FirstOrDefault(n => (!string.IsNullOrWhiteSpace(n.NodeName) && n.NodeName == selectedNodeName) || (string.IsNullOrWhiteSpace(n.NodeName) && n.NodeId == selectedNodeName)); if (selectedNode == null) { Snackbar.Add("Selected node not found", Severity.Warning); return; } // Use NodeName if available, otherwise use NodeId var nodeNameToSend = !string.IsNullOrWhiteSpace(selectedNode.NodeName) ? selectedNode.NodeName : selectedNode.NodeId; // Build order request var orderRequest = new RobotMoveToNodeModel { RobotId = RobotId, NodeName = nodeNameToSend, LastAngle = null, // Optional, can be enhanced later }; // Call API endpoint var response = await HttpClient.PostAsJsonAsync($"/api/RobotManager/MoveToNode", orderRequest); if (response.IsSuccessStatusCode) { var result = await response.Content.ReadFromJsonAsync(); if (result is null) { Snackbar.Add($"Sending order failed", Severity.Warning); orderHistory.Insert(0, new SentOrder { NodeName = selectedNodeName, Timestamp = DateTime.UtcNow, IsSuccess = false }); } else if (!result.IsSuccess) { Snackbar.Add($"{result.Message}", Severity.Warning); orderHistory.Insert(0, new SentOrder { NodeName = selectedNodeName, Timestamp = DateTime.UtcNow, IsSuccess = false }); } else { Snackbar.Add($"Order to node '{selectedNodeName}' sent successfully", Severity.Success); // Add to history orderHistory.Insert(0, new SentOrder { NodeName = selectedNodeName, Timestamp = DateTime.UtcNow, IsSuccess = true }); // Clear selection selectedNodeName = string.Empty; } } else if (response.StatusCode == System.Net.HttpStatusCode.NotFound) { Snackbar.Add($"Robot with ID '{RobotId}' not found", Severity.Error); orderHistory.Insert(0, new SentOrder { NodeName = selectedNodeName, Timestamp = DateTime.UtcNow, IsSuccess = false }); } else if (response.StatusCode == System.Net.HttpStatusCode.BadRequest) { var error = await response.Content.ReadAsStringAsync(); Snackbar.Add($"Invalid order request: {error}", Severity.Warning); orderHistory.Insert(0, new SentOrder { NodeName = selectedNodeName, Timestamp = DateTime.UtcNow, IsSuccess = false }); } else { var error = await response.Content.ReadAsStringAsync(); Snackbar.Add($"Error sending order: {error}", Severity.Error); orderHistory.Insert(0, new SentOrder { NodeName = selectedNodeName, Timestamp = DateTime.UtcNow, IsSuccess = false }); } } catch (Exception ex) { Snackbar.Add($"Error sending order: {ex.Message}", Severity.Error); orderHistory.Insert(0, new SentOrder { NodeName = selectedNodeName ?? "Unknown", Timestamp = DateTime.UtcNow, IsSuccess = false }); } finally { isSending = false; StateHasChanged(); } } private async Task CancelOrder() { if (string.IsNullOrWhiteSpace(RobotId)) { Snackbar.Add("Robot ID is required", Severity.Warning); return; } isCanceling = true; StateHasChanged(); try { // Call API endpoint var response = await HttpClient.DeleteAsync($"/api/RobotManager/MoveToNode/{RobotId}"); if (response.IsSuccessStatusCode) { var result = await response.Content.ReadFromJsonAsync(); if (result is null) { Snackbar.Add($"Canceling order failed", Severity.Warning); } else if (!result.IsSuccess) { Snackbar.Add($"{result.Message}", Severity.Warning); } else { Snackbar.Add($"Order canceled successfully", Severity.Success); } } else if (response.StatusCode == System.Net.HttpStatusCode.NotFound) { Snackbar.Add($"Robot with ID '{RobotId}' not found", Severity.Error); } else { var error = await response.Content.ReadAsStringAsync(); Snackbar.Add($"Error canceling order: {error}", Severity.Error); } } catch (Exception ex) { Snackbar.Add($"Error canceling order: {ex.Message}", Severity.Error); } finally { isCanceling = false; StateHasChanged(); } } // Helper class for order history private class SentOrder { public string NodeName { get; set; } = string.Empty; public DateTime Timestamp { get; set; } public bool IsSuccess { get; set; } } }