diff --git a/Marechai.App/App.xaml.cs b/Marechai.App/App.xaml.cs index bc4f0bfa..2c49ad61 100644 --- a/Marechai.App/App.xaml.cs +++ b/Marechai.App/App.xaml.cs @@ -20,6 +20,8 @@ using MachineViewViewModel = Marechai.App.Presentation.ViewModels.MachineViewVie using MainViewModel = Marechai.App.Presentation.ViewModels.MainViewModel; using NewsViewModel = Marechai.App.Presentation.ViewModels.NewsViewModel; using PhotoDetailViewModel = Marechai.App.Presentation.ViewModels.PhotoDetailViewModel; +using ProcessorDetailViewModel = Marechai.App.Presentation.ViewModels.ProcessorDetailViewModel; +using ProcessorsListViewModel = Marechai.App.Presentation.ViewModels.ProcessorsListViewModel; namespace Marechai.App; @@ -125,6 +127,7 @@ public partial class App : Application services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddTransient(); services @@ -139,6 +142,8 @@ public partial class App : Application services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); + services.AddTransient(); }) .UseNavigation(RegisterRoutes)); @@ -167,6 +172,8 @@ public partial class App : Application new ViewMap(), new ViewMap(), new ViewMap(), + new ViewMap(), + new ViewMap(), new DataViewMap()); routes.Register(new RouteMap("", @@ -216,6 +223,14 @@ public partial class App : Application views.FindByViewModel< GpuDetailViewModel>()) ]), + new RouteMap("processors", + views.FindByViewModel(), + Nested: + [ + new RouteMap("processor-details", + views.FindByViewModel< + ProcessorDetailViewModel>()) + ]), new RouteMap("Second", views.FindByViewModel()) ]) diff --git a/Marechai.App/Presentation/Models/ProcessorDetailNavigationParameter.cs b/Marechai.App/Presentation/Models/ProcessorDetailNavigationParameter.cs new file mode 100644 index 00000000..3db35297 --- /dev/null +++ b/Marechai.App/Presentation/Models/ProcessorDetailNavigationParameter.cs @@ -0,0 +1,35 @@ +/****************************************************************************** +// MARECHAI: Master repository of computing history artifacts information +// ---------------------------------------------------------------------------- +// +// Author(s) : Natalia Portillo +// +// --[ License ] -------------------------------------------------------------- +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2003-2026 Natalia Portillo +*******************************************************************************/ + +namespace Marechai.App.Presentation.Models; + +/// +/// Navigation parameter for the ProcessorDetailPage containing both the processor ID and the navigation source. +/// +public class ProcessorDetailNavigationParameter +{ + public required int ProcessorId { get; init; } + public object? NavigationSource { get; init; } +} \ No newline at end of file diff --git a/Marechai.App/Presentation/ViewModels/MachineViewViewModel.cs b/Marechai.App/Presentation/ViewModels/MachineViewViewModel.cs index c10db29d..ce034f79 100644 --- a/Marechai.App/Presentation/ViewModels/MachineViewViewModel.cs +++ b/Marechai.App/Presentation/ViewModels/MachineViewViewModel.cs @@ -181,6 +181,20 @@ public partial class MachineViewViewModel : ObservableObject return; } + // If we came from ProcessorDetailViewModel, navigate back to processor details + if(_navigationSource is ProcessorDetailViewModel processorDetailVm) + { + var navParam = new ProcessorDetailNavigationParameter + { + ProcessorId = processorDetailVm.ProcessorId, + NavigationSource = this + }; + + await _navigator.NavigateViewModelAsync(this, data: navParam); + + return; + } + // Otherwise, try to go back in the navigation stack await _navigator.GoBack(this); } diff --git a/Marechai.App/Presentation/ViewModels/ProcessorDetailViewModel.cs b/Marechai.App/Presentation/ViewModels/ProcessorDetailViewModel.cs new file mode 100644 index 00000000..791b4376 --- /dev/null +++ b/Marechai.App/Presentation/ViewModels/ProcessorDetailViewModel.cs @@ -0,0 +1,294 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Input; +using Marechai.App.Presentation.Models; +using Marechai.App.Services; +using Uno.Extensions.Navigation; + +namespace Marechai.App.Presentation.ViewModels; + +public partial class ProcessorDetailViewModel : ObservableObject +{ + private readonly CompaniesService _companiesService; + private readonly IStringLocalizer _localizer; + private readonly ILogger _logger; + private readonly INavigator _navigator; + private readonly ProcessorsService _processorsService; + + [ObservableProperty] + private ObservableCollection _computers = []; + + [ObservableProperty] + private string _computersFilterText = string.Empty; + + [ObservableProperty] + private string _consoelsFilterText = string.Empty; + + [ObservableProperty] + private ObservableCollection _consoles = []; + + [ObservableProperty] + private string _errorMessage = string.Empty; + + [ObservableProperty] + private ObservableCollection _filteredComputers = []; + + [ObservableProperty] + private ObservableCollection _filteredConsoles = []; + + [ObservableProperty] + private bool _hasComputers; + + [ObservableProperty] + private bool _hasConsoles; + + [ObservableProperty] + private bool _hasError; + + [ObservableProperty] + private bool _isDataLoaded; + + [ObservableProperty] + private bool _isLoading; + + [ObservableProperty] + private string _manufacturerName = string.Empty; + + private object? _navigationSource; + + [ObservableProperty] + private ProcessorDto? _processor; + + [ObservableProperty] + private int _processorId; + + public ProcessorDetailViewModel(ProcessorsService processorsService, CompaniesService companiesService, + IStringLocalizer localizer, ILogger logger, + INavigator navigator) + { + _processorsService = processorsService; + _companiesService = companiesService; + _localizer = localizer; + _logger = logger; + _navigator = navigator; + LoadData = new AsyncRelayCommand(LoadDataAsync); + GoBackCommand = new AsyncRelayCommand(GoBackAsync); + SelectMachineCommand = new AsyncRelayCommand(SelectMachineAsync); + ComputersFilterCommand = new RelayCommand(() => FilterComputers()); + ConsolesFilterCommand = new RelayCommand(() => FilterConsoles()); + } + + public IAsyncRelayCommand LoadData { get; } + public ICommand GoBackCommand { get; } + public IAsyncRelayCommand SelectMachineCommand { get; } + public ICommand ComputersFilterCommand { get; } + public ICommand ConsolesFilterCommand { get; } + + public string Title { get; } = "Processor Details"; + + /// + /// Loads Processor details + /// + private async Task LoadDataAsync() + { + try + { + IsLoading = true; + ErrorMessage = string.Empty; + HasError = false; + IsDataLoaded = false; + Computers.Clear(); + Consoles.Clear(); + + if(ProcessorId <= 0) + { + ErrorMessage = _localizer["Invalid Processor ID"].Value; + HasError = true; + + return; + } + + _logger.LogInformation("Loading Processor details for ID: {ProcessorId}", ProcessorId); + + // Load Processor details + Processor = await _processorsService.GetProcessorByIdAsync(ProcessorId); + + if(Processor is null) + { + ErrorMessage = _localizer["Processor not found"].Value; + HasError = true; + + return; + } + + // Set manufacturer name (from Company field or fetch by CompanyId if empty) + ManufacturerName = Processor.Company ?? string.Empty; + + if(string.IsNullOrEmpty(ManufacturerName) && Processor.CompanyId.HasValue) + { + try + { + CompanyDto? company = await _companiesService.GetCompanyByIdAsync(Processor.CompanyId.Value); + if(company != null) ManufacturerName = company.Name ?? string.Empty; + } + catch(Exception ex) + { + _logger.LogWarning(ex, "Failed to load company for Processor {ProcessorId}", ProcessorId); + } + } + + _logger.LogInformation("Processor loaded: {Name}, Company: {Company}", Processor.Name, ManufacturerName); + + // Load machines and separate into computers and consoles + try + { + List? machines = await _processorsService.GetMachinesByProcessorAsync(ProcessorId); + + if(machines != null && machines.Count > 0) + { + Computers.Clear(); + Consoles.Clear(); + + foreach(MachineDto machine in machines) + { + var machineItem = new MachineItem + { + Id = machine.Id ?? 0, + Name = machine.Name ?? string.Empty, + Manufacturer = machine.Company ?? string.Empty, + Year = machine.Introduced?.Year ?? 0 + }; + + // Distinguish between computers and consoles based on Type + if(machine.Type == 2) // MachineType.Console + Consoles.Add(machineItem); + else // MachineType.Computer or Unknown + Computers.Add(machineItem); + } + + HasComputers = Computers.Count > 0; + HasConsoles = Consoles.Count > 0; + + // Initialize filtered collections + FilterComputers(); + FilterConsoles(); + + _logger.LogInformation("Loaded {ComputerCount} computers and {ConsoleCount} consoles for Processor {ProcessorId}", + Computers.Count, + Consoles.Count, + ProcessorId); + } + else + { + HasComputers = false; + HasConsoles = false; + } + } + catch(Exception ex) + { + _logger.LogWarning(ex, "Failed to load machines for Processor {ProcessorId}", ProcessorId); + HasComputers = false; + HasConsoles = false; + } + + IsDataLoaded = true; + } + catch(Exception ex) + { + _logger.LogError(ex, "Error loading Processor details: {Exception}", ex.Message); + ErrorMessage = _localizer["Failed to load processor details. Please try again later."].Value; + HasError = true; + } + finally + { + IsLoading = false; + } + } + + /// + /// Navigates back to the Processor list + /// + private async Task GoBackAsync() + { + // If we came from a machine view, go back to machine view + if(_navigationSource is MachineViewViewModel machineVm) + { + await _navigator.NavigateViewModelAsync(this); + + return; + } + + // Default: go back to Processor list + await _navigator.NavigateViewModelAsync(this); + } + + /// + /// Filters computers based on search text + /// + private void FilterComputers() + { + if(string.IsNullOrWhiteSpace(ComputersFilterText)) + { + FilteredComputers.Clear(); + foreach(MachineItem computer in Computers) FilteredComputers.Add(computer); + } + else + { + var filtered = Computers + .Where(c => c.Name.Contains(ComputersFilterText, StringComparison.OrdinalIgnoreCase)) + .ToList(); + + FilteredComputers.Clear(); + foreach(MachineItem computer in filtered) FilteredComputers.Add(computer); + } + } + + /// + /// Filters consoles based on search text + /// + private void FilterConsoles() + { + if(string.IsNullOrWhiteSpace(ConsoelsFilterText)) + { + FilteredConsoles.Clear(); + foreach(MachineItem console in Consoles) FilteredConsoles.Add(console); + } + else + { + var filtered = Consoles.Where(c => c.Name.Contains(ConsoelsFilterText, StringComparison.OrdinalIgnoreCase)) + .ToList(); + + FilteredConsoles.Clear(); + foreach(MachineItem console in filtered) FilteredConsoles.Add(console); + } + } + + /// + /// Navigates to machine detail view + /// + private async Task SelectMachineAsync(int machineId) + { + if(machineId <= 0) return; + + var navParam = new MachineViewNavigationParameter + { + MachineId = machineId, + NavigationSource = this + }; + + await _navigator.NavigateViewModelAsync(this, data: navParam); + } + + /// + /// Sets the navigation source (where we came from). + /// + public void SetNavigationSource(object? source) + { + _navigationSource = source; + } +} \ No newline at end of file diff --git a/Marechai.App/Presentation/ViewModels/ProcessorsListViewModel.cs b/Marechai.App/Presentation/ViewModels/ProcessorsListViewModel.cs new file mode 100644 index 00000000..5d3daf18 --- /dev/null +++ b/Marechai.App/Presentation/ViewModels/ProcessorsListViewModel.cs @@ -0,0 +1,177 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Threading.Tasks; +using Marechai.App.Presentation.Models; +using Marechai.App.Services; +using Uno.Extensions.Navigation; + +namespace Marechai.App.Presentation.ViewModels; + +/// +/// ViewModel for displaying a list of Processors +/// +public partial class ProcessorsListViewModel : ObservableObject +{ + private readonly IStringLocalizer _localizer; + private readonly ILogger _logger; + private readonly INavigator _navigator; + private readonly ProcessorsService _processorsService; + + [ObservableProperty] + private string _errorMessage = string.Empty; + + [ObservableProperty] + private bool _hasError; + + [ObservableProperty] + private bool _isDataLoaded; + + [ObservableProperty] + private bool _isLoading; + + [ObservableProperty] + private string _pageTitle = string.Empty; + + [ObservableProperty] + private ObservableCollection _processorsList = []; + + public ProcessorsListViewModel(ProcessorsService processorsService, IStringLocalizer localizer, + ILogger logger, INavigator navigator) + { + _processorsService = processorsService; + _localizer = localizer; + _logger = logger; + _navigator = navigator; + LoadData = new AsyncRelayCommand(LoadDataAsync); + NavigateToProcessorCommand = new AsyncRelayCommand(NavigateToProcessorAsync); + } + + public IAsyncRelayCommand LoadData { get; } + public IAsyncRelayCommand NavigateToProcessorCommand { get; } + + /// + /// Loads all Processors and sorts them alphabetically + /// + private async Task LoadDataAsync() + { + try + { + IsLoading = true; + ErrorMessage = string.Empty; + HasError = false; + IsDataLoaded = false; + ProcessorsList.Clear(); + + _logger.LogInformation("LoadDataAsync called for Processors"); + + PageTitle = _localizer["Processors"]; + + // Load Processors from the API + await LoadProcessorsFromApiAsync(); + + _logger.LogInformation("LoadProcessorsFromApiAsync completed. ProcessorsList.Count={Count}", + ProcessorsList.Count); + + if(ProcessorsList.Count == 0) + { + ErrorMessage = _localizer["No processors found"].Value; + HasError = true; + + _logger.LogWarning("No Processors found"); + } + else + IsDataLoaded = true; + } + catch(Exception ex) + { + _logger.LogError(ex, "Error loading Processors: {Exception}", ex.Message); + ErrorMessage = _localizer["Failed to load processors. Please try again later."].Value; + HasError = true; + } + finally + { + IsLoading = false; + } + } + + /// + /// Loads Processors from the API and sorts them alphabetically + /// + private async Task LoadProcessorsFromApiAsync() + { + try + { + List processors = await _processorsService.GetAllProcessorsAsync(); + + if(processors == null || processors.Count == 0) + { + _logger.LogInformation("No Processors returned from API"); + + return; + } + + var processorItems = new List(); + + foreach(ProcessorDto processor in processors) + { + var processorItem = new ProcessorListItem + { + Id = processor.Id ?? 0, + Name = processor.Name ?? string.Empty, + Company = processor.Company ?? string.Empty + }; + + processorItems.Add(processorItem); + + _logger.LogInformation("Processor: {Name}, Company: {Company}, ID: {Id}", + processor.Name, + processor.Company, + processor.Id); + } + + // Sort processors alphabetically + processorItems.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase)); + + // Add all processors to the list + foreach(ProcessorListItem processor in processorItems) ProcessorsList.Add(processor); + } + catch(Exception ex) + { + _logger.LogError(ex, "Error loading Processors from API"); + } + } + + /// + /// Navigates to the Processor detail view + /// + private async Task NavigateToProcessorAsync(ProcessorListItem? processor) + { + if(processor is null) return; + + _logger.LogInformation("Navigating to Processor detail: {ProcessorName} (ID: {ProcessorId})", + processor.Name, + processor.Id); + + // Navigate to Processor detail view with navigation parameter + var navParam = new ProcessorDetailNavigationParameter + { + ProcessorId = processor.Id, + NavigationSource = this + }; + + await _navigator.NavigateViewModelAsync(this, data: navParam); + } +} + +/// +/// Data model for a Processor in the list +/// +public class ProcessorListItem +{ + public int Id { get; set; } + public string Name { get; set; } = string.Empty; + public string Company { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/Marechai.App/Presentation/Views/ProcessorDetailPage.xaml b/Marechai.App/Presentation/Views/ProcessorDetailPage.xaml new file mode 100644 index 00000000..99e2bc24 --- /dev/null +++ b/Marechai.App/Presentation/Views/ProcessorDetailPage.xaml @@ -0,0 +1,366 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Marechai.App/Presentation/Views/ProcessorDetailPage.xaml.cs b/Marechai.App/Presentation/Views/ProcessorDetailPage.xaml.cs new file mode 100644 index 00000000..0698160f --- /dev/null +++ b/Marechai.App/Presentation/Views/ProcessorDetailPage.xaml.cs @@ -0,0 +1,98 @@ +#nullable enable + +using Marechai.App.Presentation.Models; +using Marechai.App.Presentation.ViewModels; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Navigation; + +namespace Marechai.App.Presentation.Views; + +/// +/// Processor detail page showing all information about a specific processor +/// +public sealed partial class ProcessorDetailPage : Page +{ + private object? _navigationSource; + private int? _pendingProcessorId; + + public ProcessorDetailPage() + { + InitializeComponent(); + DataContextChanged += ProcessorDetailPage_DataContextChanged; + } + + protected override void OnNavigatedTo(NavigationEventArgs e) + { + base.OnNavigatedTo(e); + + int? processorId = null; + + // Handle both int and ProcessorDetailNavigationParameter + if(e.Parameter is int intId) + processorId = intId; + else if(e.Parameter is ProcessorDetailNavigationParameter navParam) + { + processorId = navParam.ProcessorId; + _navigationSource = navParam.NavigationSource; + } + + if(processorId.HasValue) + { + _pendingProcessorId = processorId; + + if(DataContext is ProcessorDetailViewModel viewModel) + { + viewModel.ProcessorId = processorId.Value; + if(_navigationSource != null) viewModel.SetNavigationSource(_navigationSource); + _ = viewModel.LoadData.ExecuteAsync(null); + } + } + } + + private void ProcessorDetailPage_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args) + { + if(_pendingProcessorId.HasValue && DataContext is ProcessorDetailViewModel viewModel) + { + viewModel.ProcessorId = _pendingProcessorId.Value; + if(_navigationSource != null) viewModel.SetNavigationSource(_navigationSource); + _ = viewModel.LoadData.ExecuteAsync(null); + } + } + + private void ComputersSearchBox_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) + { + if(DataContext is ProcessorDetailViewModel vm) vm.ComputersFilterCommand.Execute(null); + } + + private void ComputersSearchBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) + { + if(args.Reason == AutoSuggestionBoxTextChangeReason.UserInput) + if(DataContext is ProcessorDetailViewModel vm) + vm.ComputersFilterCommand.Execute(null); + } + + private void ConsolesSearchBox_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) + { + if(DataContext is ProcessorDetailViewModel vm) vm.ConsolesFilterCommand.Execute(null); + } + + private void ConsolesSearchBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) + { + if(args.Reason == AutoSuggestionBoxTextChangeReason.UserInput) + if(DataContext is ProcessorDetailViewModel vm) + vm.ConsolesFilterCommand.Execute(null); + } + + private void Computer_Click(object sender, RoutedEventArgs e) + { + if(sender is Button button && button.Tag is int machineId && DataContext is ProcessorDetailViewModel vm) + _ = vm.SelectMachineCommand.ExecuteAsync(machineId); + } + + private void Console_Click(object sender, RoutedEventArgs e) + { + if(sender is Button button && button.Tag is int machineId && DataContext is ProcessorDetailViewModel vm) + _ = vm.SelectMachineCommand.ExecuteAsync(machineId); + } +} diff --git a/Marechai.App/Presentation/Views/ProcessorListPage.xaml b/Marechai.App/Presentation/Views/ProcessorListPage.xaml new file mode 100644 index 00000000..ad78e562 --- /dev/null +++ b/Marechai.App/Presentation/Views/ProcessorListPage.xaml @@ -0,0 +1,207 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Marechai.App/Presentation/Views/ProcessorListPage.xaml.cs b/Marechai.App/Presentation/Views/ProcessorListPage.xaml.cs new file mode 100644 index 00000000..b6010c31 --- /dev/null +++ b/Marechai.App/Presentation/Views/ProcessorListPage.xaml.cs @@ -0,0 +1,37 @@ +using Marechai.App.Presentation.ViewModels; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; + +namespace Marechai.App.Presentation.Views; + +/// +/// Professional list view for displaying all processors. +/// Features responsive layout and modern styling. +/// +public sealed partial class ProcessorListPage : Page +{ + public ProcessorListPage() + { + InitializeComponent(); + Loaded += ProcessorListPage_Loaded; + DataContextChanged += ProcessorListPage_DataContextChanged; + } + + private void ProcessorListPage_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args) + { + if(DataContext is ProcessorsListViewModel vm) + { + // Load data when DataContext is set + vm.LoadData.Execute(null); + } + } + + private void ProcessorListPage_Loaded(object sender, RoutedEventArgs e) + { + if(DataContext is ProcessorsListViewModel vm) + { + // Load data when page is loaded (fallback) + vm.LoadData.Execute(null); + } + } +} diff --git a/Marechai.App/Services/ProcessorsService.cs b/Marechai.App/Services/ProcessorsService.cs new file mode 100644 index 00000000..f1c8653a --- /dev/null +++ b/Marechai.App/Services/ProcessorsService.cs @@ -0,0 +1,127 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Marechai.App.Services; + +/// +/// Service for fetching and managing Processors from the Marechai API +/// +public class ProcessorsService +{ + private readonly ApiClient _apiClient; + private readonly ILogger _logger; + + public ProcessorsService(ApiClient apiClient, ILogger logger) + { + _apiClient = apiClient; + _logger = logger; + } + + /// + /// Fetches all Processors from the API + /// + public async Task> GetAllProcessorsAsync() + { + try + { + _logger.LogInformation("Fetching all Processors from API"); + + List? processors = await _apiClient.Processors.GetAsync(); + + if(processors == null) return []; + + _logger.LogInformation("Successfully fetched {Count} total Processors", processors.Count); + + return processors; + } + catch(Exception ex) + { + _logger.LogError(ex, "Error fetching all Processors from API"); + + return []; + } + } + + /// + /// Fetches a single Processor by ID from the API + /// + public async Task GetProcessorByIdAsync(int processorId) + { + try + { + _logger.LogInformation("Fetching Processor {ProcessorId} from API", processorId); + + ProcessorDto? processor = await _apiClient.Processors[processorId].GetAsync(); + + if(processor == null) + { + _logger.LogWarning("Processor {ProcessorId} not found", processorId); + + return null; + } + + _logger.LogInformation("Successfully fetched Processor {ProcessorId}: {ProcessorName}", + processorId, + processor.Name); + + return processor; + } + catch(Exception ex) + { + _logger.LogError(ex, "Error fetching Processor {ProcessorId} from API", processorId); + + return null; + } + } + + /// + /// Fetches machines that use a specific Processor + /// + public async Task> GetMachinesByProcessorAsync(int processorId) + { + try + { + _logger.LogInformation("Fetching machines for Processor {ProcessorId}", processorId); + + // Fetch from the processors-by-machine/by-processor/{processorId} endpoint + List? processorMachineRelationships = + await _apiClient.ProcessorsByMachine.ByProcessor[processorId].GetAsync(); + + if(processorMachineRelationships == null || processorMachineRelationships.Count == 0) return []; + + // Fetch full machine details for each to get Type information + var machines = new List(); + + foreach(ProcessorByMachineDto pm in processorMachineRelationships) + { + if(pm.MachineId.HasValue) + { + try + { + MachineDto? machine = await _apiClient.Machines[pm.MachineId.Value].GetAsync(); + if(machine != null) machines.Add(machine); + } + catch(Exception ex) + { + _logger.LogWarning(ex, "Failed to fetch machine {MachineId}", pm.MachineId); + } + } + } + + _logger.LogInformation("Successfully fetched {Count} machines for Processor {ProcessorId}", + machines.Count, + processorId); + + return machines; + } + catch(Exception ex) + { + _logger.LogWarning(ex, "Error fetching machines for Processor {ProcessorId}", processorId); + + return []; + } + } +} \ No newline at end of file