diff --git a/Marechai.App/Presentation/ComputersListPage.xaml b/Marechai.App/Presentation/ComputersListPage.xaml new file mode 100644 index 00000000..d07b048b --- /dev/null +++ b/Marechai.App/Presentation/ComputersListPage.xaml @@ -0,0 +1,261 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Marechai.App/Presentation/ComputersListPage.xaml.cs b/Marechai.App/Presentation/ComputersListPage.xaml.cs new file mode 100644 index 00000000..4b907dea --- /dev/null +++ b/Marechai.App/Presentation/ComputersListPage.xaml.cs @@ -0,0 +1,36 @@ +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; + +namespace Marechai.App.Presentation; + +/// +/// Professional list view for displaying computers filtered by letter, year, or all. +/// Features responsive layout, modern styling, and smooth navigation. +/// +public sealed partial class ComputersListPage : Page +{ + public ComputersListPage() + { + InitializeComponent(); + Loaded += ComputersListPage_Loaded; + DataContextChanged += ComputersListPage_DataContextChanged; + } + + private void ComputersListPage_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args) + { + if(DataContext is ComputersListViewModel vm) + { + // Load data when DataContext is set + vm.LoadData.Execute(null); + } + } + + private void ComputersListPage_Loaded(object sender, RoutedEventArgs e) + { + if(DataContext is ComputersListViewModel vm) + { + // Load data when page is loaded (fallback) + vm.LoadData.Execute(null); + } + } +} \ No newline at end of file diff --git a/Marechai.App/Presentation/ComputersListViewModel.cs b/Marechai.App/Presentation/ComputersListViewModel.cs new file mode 100644 index 00000000..dd9dfdaa --- /dev/null +++ b/Marechai.App/Presentation/ComputersListViewModel.cs @@ -0,0 +1,249 @@ +#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.Services; +using Uno.Extensions.Navigation; + +namespace Marechai.App.Presentation; + +/// +/// ViewModel for displaying a filtered list of computers +/// +public partial class ComputersListViewModel : ObservableObject +{ + private readonly ComputersService _computersService; + private readonly IComputersListFilterContext _filterContext; + private readonly IStringLocalizer _localizer; + private readonly ILogger _logger; + private readonly INavigator _navigator; + + [ObservableProperty] + private ObservableCollection computersList = []; + + [ObservableProperty] + private string errorMessage = string.Empty; + + [ObservableProperty] + private string filterDescription = string.Empty; + + [ObservableProperty] + private bool hasError; + + [ObservableProperty] + private bool isDataLoaded; + + [ObservableProperty] + private bool isLoading; + + [ObservableProperty] + private string pageTitle = string.Empty; + + public ComputersListViewModel(ComputersService computersService, IStringLocalizer localizer, + ILogger logger, INavigator navigator, + IComputersListFilterContext filterContext) + { + _computersService = computersService; + _localizer = localizer; + _logger = logger; + _navigator = navigator; + _filterContext = filterContext; + LoadData = new AsyncRelayCommand(LoadDataAsync); + GoBackCommand = new AsyncRelayCommand(GoBackAsync); + NavigateToComputerCommand = new AsyncRelayCommand(NavigateToComputerAsync); + } + + public IAsyncRelayCommand LoadData { get; } + public ICommand GoBackCommand { get; } + public IAsyncRelayCommand NavigateToComputerCommand { get; } + + /// + /// Gets or sets the filter type + /// + public ComputerListFilterType FilterType + { + get => _filterContext.FilterType; + set => _filterContext.FilterType = value; + } + + /// + /// Gets or sets the filter value + /// + public string FilterValue + { + get => _filterContext.FilterValue; + set => _filterContext.FilterValue = value; + } + + /// + /// Loads computers based on the current filter + /// + private async Task LoadDataAsync() + { + try + { + IsLoading = true; + ErrorMessage = string.Empty; + HasError = false; + IsDataLoaded = false; + ComputersList.Clear(); + + _logger.LogInformation("LoadDataAsync called. FilterType={FilterType}, FilterValue={FilterValue}", + FilterType, + FilterValue); + + // Update title and filter description based on filter type + UpdateFilterDescription(); + + // Load computers from the API based on the current filter + await LoadComputersFromApiAsync(); + + _logger.LogInformation("LoadComputersFromApiAsync completed. ComputersList.Count={Count}", + ComputersList.Count); + + if(ComputersList.Count == 0) + { + ErrorMessage = _localizer["No computers found for this filter"].Value; + HasError = true; + + _logger.LogWarning("No computers found for filter: {FilterType} {FilterValue}", + FilterType, + FilterValue); + } + else + IsDataLoaded = true; + } + catch(Exception ex) + { + _logger.LogError(ex, "Error loading computers: {Exception}", ex.Message); + ErrorMessage = _localizer["Failed to load computers. Please try again later."].Value; + HasError = true; + } + finally + { + IsLoading = false; + } + } + + /// + /// Updates the title and filter description based on the current filter + /// + private void UpdateFilterDescription() + { + switch(FilterType) + { + case ComputerListFilterType.All: + PageTitle = _localizer["All Computers"]; + FilterDescription = _localizer["Browsing all computers in the database"]; + + break; + + case ComputerListFilterType.Letter: + if(!string.IsNullOrEmpty(FilterValue) && FilterValue.Length == 1) + { + PageTitle = $"{_localizer["Computers Starting with"]} {FilterValue}"; + FilterDescription = $"{_localizer["Showing computers that start with"]} {FilterValue}"; + } + + break; + + case ComputerListFilterType.Year: + if(!string.IsNullOrEmpty(FilterValue) && int.TryParse(FilterValue, out int year)) + { + PageTitle = $"{_localizer["Computers from"]} {year}"; + FilterDescription = $"{_localizer["Showing computers released in"]} {year}"; + } + + break; + } + } + + /// + /// Loads computers from the API based on the current filter + /// + private async Task LoadComputersFromApiAsync() + { + try + { + List computers = FilterType switch + { + ComputerListFilterType.Letter when FilterValue.Length == 1 => + await _computersService.GetComputersByLetterAsync(FilterValue[0]), + + ComputerListFilterType.Year when int.TryParse(FilterValue, out int year) => + await _computersService.GetComputersByYearAsync(year), + + _ => await _computersService.GetAllComputersAsync() + }; + + // Add computers to the list sorted by name + foreach(MachineDto computer in computers.OrderBy(c => c.Name)) + { + int year = computer.Introduced?.Year ?? 0; + int id = UntypedNodeExtractor.ExtractInt(computer.Id); + + _logger.LogInformation("Computer: {Name}, Introduced: {Introduced}, Year: {Year}, Company: {Company}, ID: {Id}", + computer.Name, + computer.Introduced, + year, + computer.Company, + id); + + ComputersList.Add(new ComputerListItem + { + Id = id, + Name = computer.Name ?? string.Empty, + Year = year, + Manufacturer = computer.Company ?? string.Empty + }); + } + } + catch(Exception ex) + { + _logger.LogError(ex, "Error loading computers from API"); + } + } + + /// + /// Navigates back to the computers main view + /// + private async Task GoBackAsync() + { + await _navigator.NavigateViewModelAsync(this); + } + + /// + /// Navigates to the computer detail view + /// + private async Task NavigateToComputerAsync(ComputerListItem? computer) + { + if(computer is null) return; + + _logger.LogInformation("Navigating to computer detail: {ComputerName} (ID: {ComputerId})", + computer.Name, + computer.Id); + + var navParam = new MachineViewNavigationParameter + { + MachineId = computer.Id, + NavigationSource = this + }; + + await _navigator.NavigateViewModelAsync(this, data: navParam); + } +} + +/// +/// Data model for a computer in the list +/// +public class ComputerListItem +{ + public int Id { get; set; } + public string Name { get; set; } = string.Empty; + public int Year { get; set; } + public string Manufacturer { get; set; } = string.Empty; +} \ No newline at end of file