diff --git a/Marechai.App/App.xaml b/Marechai.App/App.xaml
index e430aa6d..77a46865 100644
--- a/Marechai.App/App.xaml
+++ b/Marechai.App/App.xaml
@@ -1,19 +1,23 @@
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:local="using:Marechai.App.Presentation.Converters">
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
+
+
+
+
-
-
+
+
-
+
\ No newline at end of file
diff --git a/Marechai.App/App.xaml.cs b/Marechai.App/App.xaml.cs
index d2c04866..0ba22de6 100644
--- a/Marechai.App/App.xaml.cs
+++ b/Marechai.App/App.xaml.cs
@@ -10,6 +10,7 @@ using Uno.Extensions.Http;
using Uno.Extensions.Localization;
using Uno.Extensions.Navigation;
using Uno.UI;
+using CompanyDetailViewModel = Marechai.App.Presentation.ViewModels.CompanyDetailViewModel;
using ComputersListViewModel = Marechai.App.Presentation.ViewModels.ComputersListViewModel;
using ComputersViewModel = Marechai.App.Presentation.ViewModels.ComputersViewModel;
using MachineViewViewModel = Marechai.App.Presentation.ViewModels.MachineViewViewModel;
@@ -113,6 +114,8 @@ public partial class App : Application
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
services.AddSingleton();
services
@@ -148,6 +151,7 @@ public partial class App : Application
new ViewMap(),
new ViewMap(),
new ViewMap(),
+ new ViewMap(),
new ViewMap(),
new DataViewMap());
@@ -183,7 +187,13 @@ public partial class App : Application
ConsolesListViewModel>())
]),
new RouteMap("companies",
- views.FindByViewModel()),
+ views.FindByViewModel(),
+ Nested:
+ [
+ new RouteMap("detail",
+ views.FindByViewModel<
+ CompanyDetailViewModel>())
+ ]),
new RouteMap("Second",
views.FindByViewModel())
])
diff --git a/Marechai.App/Presentation/Converters/CompanyConverters.cs b/Marechai.App/Presentation/Converters/CompanyConverters.cs
new file mode 100644
index 00000000..17881c88
--- /dev/null
+++ b/Marechai.App/Presentation/Converters/CompanyConverters.cs
@@ -0,0 +1,51 @@
+using System;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Data;
+
+namespace Marechai.App.Presentation.Converters;
+
+///
+/// Converts null object to Collapsed visibility
+///
+public class ObjectToVisibilityConverter : IValueConverter
+{
+ public object Convert(object value, Type targetType, object parameter, string language) =>
+ value != null ? Visibility.Visible : Visibility.Collapsed;
+
+ public object ConvertBack(object value, Type targetType, object parameter, string language) =>
+ throw new NotImplementedException();
+}
+
+///
+/// Converts empty/null string to Collapsed visibility
+///
+public class StringToVisibilityConverter : IValueConverter
+{
+ public object Convert(object value, Type targetType, object parameter, string language)
+ {
+ if(value is string str && !string.IsNullOrWhiteSpace(str)) return Visibility.Visible;
+
+ return Visibility.Collapsed;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, string language) =>
+ throw new NotImplementedException();
+}
+
+///
+/// Converts zero count to Collapsed visibility, otherwise Visible
+///
+public class ZeroToVisibilityConverter : IValueConverter
+{
+ public object Convert(object value, Type targetType, object parameter, string language)
+ {
+ if(value is int count && count > 0) return Visibility.Visible;
+
+ if(value is long longCount && longCount > 0) return Visibility.Visible;
+
+ return Visibility.Collapsed;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, string language) =>
+ throw new NotImplementedException();
+}
\ No newline at end of file
diff --git a/Marechai.App/Presentation/Models/CompanyDetailNavigationParameter.cs b/Marechai.App/Presentation/Models/CompanyDetailNavigationParameter.cs
new file mode 100644
index 00000000..d3c6ed48
--- /dev/null
+++ b/Marechai.App/Presentation/Models/CompanyDetailNavigationParameter.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 CompanyDetailPage containing both the company ID and the navigation source.
+///
+public class CompanyDetailNavigationParameter
+{
+ public required int CompanyId { get; init; }
+ public object? NavigationSource { get; init; }
+}
\ No newline at end of file
diff --git a/Marechai.App/Presentation/ViewModels/CompaniesViewModel.cs b/Marechai.App/Presentation/ViewModels/CompaniesViewModel.cs
index 80b41897..5aeed4e9 100644
--- a/Marechai.App/Presentation/ViewModels/CompaniesViewModel.cs
+++ b/Marechai.App/Presentation/ViewModels/CompaniesViewModel.cs
@@ -7,6 +7,7 @@ using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using Marechai.App.Helpers;
+using Marechai.App.Presentation.Models;
using Marechai.App.Services;
using Uno.Extensions.Navigation;
@@ -14,13 +15,12 @@ namespace Marechai.App.Presentation.ViewModels;
public partial class CompaniesViewModel : ObservableObject
{
+ private readonly List _allCompanies = [];
private readonly CompaniesService _companiesService;
private readonly IStringLocalizer _localizer;
private readonly ILogger _logger;
private readonly INavigator _navigator;
- private readonly List _allCompanies = [];
-
[ObservableProperty]
private ObservableCollection _companiesList = [];
@@ -146,8 +146,14 @@ public partial class CompaniesViewModel : ObservableObject
_logger.LogInformation("Navigating to company: {CompanyName} (ID: {CompanyId})", company.Name, company.Id);
- // TODO: Implement company detail view
- // For now, just log the navigation
+ // Navigate to company detail view with navigation parameter
+ var navParam = new CompanyDetailNavigationParameter
+ {
+ CompanyId = company.Id,
+ NavigationSource = this
+ };
+
+ await _navigator.NavigateViewModelAsync(this, data: navParam);
}
///
diff --git a/Marechai.App/Presentation/ViewModels/CompanyDetailViewModel.cs b/Marechai.App/Presentation/ViewModels/CompanyDetailViewModel.cs
new file mode 100644
index 00000000..b05d5748
--- /dev/null
+++ b/Marechai.App/Presentation/ViewModels/CompanyDetailViewModel.cs
@@ -0,0 +1,373 @@
+#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.Helpers;
+using Marechai.App.Presentation.Models;
+using Marechai.App.Services;
+using Marechai.Data;
+using Uno.Extensions.Navigation;
+
+namespace Marechai.App.Presentation.ViewModels;
+
+public partial class CompanyDetailViewModel : ObservableObject
+{
+ private readonly CompanyDetailService _companyDetailService;
+ private readonly IStringLocalizer _localizer;
+ private readonly ILogger _logger;
+ private readonly INavigator _navigator;
+
+ [ObservableProperty]
+ private CompanyDto? _company;
+
+ [ObservableProperty]
+ private int _companyId;
+
+ [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 _hasError;
+
+ [ObservableProperty]
+ private bool _isDataLoaded;
+
+ [ObservableProperty]
+ private bool _isLoading;
+
+ [ObservableProperty]
+ private CompanyDto? _soldToCompany;
+
+ public CompanyDetailViewModel(CompanyDetailService companyDetailService, IStringLocalizer localizer,
+ ILogger logger, INavigator navigator)
+ {
+ _companyDetailService = companyDetailService;
+ _localizer = localizer;
+ _logger = logger;
+ _navigator = navigator;
+ LoadData = new AsyncRelayCommand(LoadDataAsync);
+ GoBackCommand = new AsyncRelayCommand(GoBackAsync);
+ NavigateToMachineCommand = new AsyncRelayCommand(NavigateToMachineAsync);
+ }
+
+ ///
+ /// Gets the display text for the company's status
+ ///
+ public string CompanyStatusDisplay => Company != null ? GetStatusMessage(Company) : string.Empty;
+
+ ///
+ /// Gets the display text for the company's founded date
+ ///
+ public string CompanyFoundedDateDisplay => Company != null ? GetFoundedDateDisplay(Company) : string.Empty;
+
+ public IAsyncRelayCommand LoadData { get; }
+ public ICommand GoBackCommand { get; }
+ public IAsyncRelayCommand NavigateToMachineCommand { get; }
+ public string Title { get; } = "Company Details";
+
+ partial void OnCompanyChanged(CompanyDto? oldValue, CompanyDto? newValue)
+ {
+ // Notify that computed properties have changed
+ OnPropertyChanged(nameof(CompanyStatusDisplay));
+ OnPropertyChanged(nameof(CompanyFoundedDateDisplay));
+ }
+
+ partial void OnComputersFilterTextChanged(string value)
+ {
+ FilterComputers(value);
+ }
+
+ partial void OnConsoelsFilterTextChanged(string value)
+ {
+ FilterConsoles(value);
+ }
+
+ private void FilterComputers(string filterText)
+ {
+ ObservableCollection filtered = string.IsNullOrWhiteSpace(filterText)
+ ? new ObservableCollection<
+ CompanyDetailMachine>(Computers)
+ : new
+ ObservableCollection<
+ CompanyDetailMachine>(Computers.Where(c =>
+ c.Name.Contains(filterText,
+ StringComparison
+ .OrdinalIgnoreCase)));
+
+ FilteredComputers = filtered;
+ }
+
+ private void FilterConsoles(string filterText)
+ {
+ ObservableCollection filtered = string.IsNullOrWhiteSpace(filterText)
+ ? new ObservableCollection<
+ CompanyDetailMachine>(Consoles)
+ : new
+ ObservableCollection<
+ CompanyDetailMachine>(Consoles.Where(c =>
+ c.Name.Contains(filterText,
+ StringComparison
+ .OrdinalIgnoreCase)));
+
+ FilteredConsoles = filtered;
+ }
+
+ private async Task NavigateToMachineAsync(CompanyDetailMachine? machine)
+ {
+ if(machine == null) return;
+
+ var navParam = new MachineViewNavigationParameter
+ {
+ MachineId = machine.Id,
+ NavigationSource = this
+ };
+
+ await _navigator.NavigateViewModelAsync(this, data: navParam);
+ }
+
+ ///
+ /// Gets the formatted founding date with unknown handling
+ ///
+ public string GetFoundedDateDisplay(CompanyDto company)
+ {
+ if(company.Founded is null) return string.Empty;
+
+ DateTime date = company.Founded.Value.DateTime;
+
+ if(company.FoundedMonthIsUnknown ?? false) return $"{date.Year}.";
+
+ if(company.FoundedDayIsUnknown ?? false) return $"{date:Y}.";
+
+ return $"{date:D}.";
+ }
+
+ ///
+ /// Gets the formatted sold/event date with unknown handling
+ ///
+ public string GetEventDateDisplay(CompanyDto? company, bool monthUnknown = false, bool dayUnknown = false)
+ {
+ if(company?.Sold is null) return _localizer["unknown date"].Value;
+
+ DateTime date = company.Sold.Value.DateTime;
+
+ if(monthUnknown || (company.SoldMonthIsUnknown ?? false)) return $"{date.Year}";
+
+ if(dayUnknown || (company.SoldDayIsUnknown ?? false)) return $"{date:Y}";
+
+ return $"{date:D}";
+ }
+
+ ///
+ /// Gets the status message for the company
+ ///
+ public string GetStatusMessage(CompanyDto company)
+ {
+ return company.Status switch
+ {
+ 1 => _localizer["Company is active."].Value,
+ 2 => GetSoldStatusMessage(company),
+ 3 => GetMergedStatusMessage(company),
+ 4 => GetBankruptcyMessage(company),
+ 5 => GetDefunctMessage(company),
+ 6 => GetRenamedStatusMessage(company),
+ _ => _localizer["Current company status is unknown."].Value
+ };
+ }
+
+ private string GetSoldStatusMessage(CompanyDto company)
+ {
+ if(SoldToCompany != null)
+ {
+ return string.Format(_localizer["Company sold to {0} on {1}."].Value,
+ SoldToCompany.Name,
+ GetEventDateDisplay(company));
+ }
+
+ if(company.Sold != null)
+ {
+ return string.Format(_localizer["Company sold on {0} to an unknown company."].Value,
+ GetEventDateDisplay(company));
+ }
+
+ return SoldToCompany != null
+ ? string.Format(_localizer["Company sold to {0} on an unknown date."].Value, SoldToCompany.Name)
+ : _localizer["Company was sold to an unknown company on an unknown date."].Value;
+ }
+
+ private string GetMergedStatusMessage(CompanyDto company)
+ {
+ if(SoldToCompany != null)
+ {
+ return string.Format(_localizer["Company merged on {0} to form {1}."].Value,
+ GetEventDateDisplay(company),
+ SoldToCompany.Name);
+ }
+
+ if(company.Sold != null)
+ {
+ return string.Format(_localizer["Company merged on {0} to form an unknown company."].Value,
+ GetEventDateDisplay(company));
+ }
+
+ return SoldToCompany != null
+ ? string.Format(_localizer["Company merged on an unknown date to form {0}."].Value,
+ SoldToCompany.Name)
+ : _localizer["Company merged to form an unknown company on an unknown date."].Value;
+ }
+
+ private string GetBankruptcyMessage(CompanyDto company) => company.Sold != null
+ ? string.Format(_localizer
+ ["Company declared bankruptcy on {0}."]
+ .Value,
+ GetEventDateDisplay(company))
+ : _localizer
+ ["Company declared bankruptcy on an unknown date."]
+ .Value;
+
+ private string GetDefunctMessage(CompanyDto company) => company.Sold != null
+ ? string.Format(_localizer
+ ["Company ceased operations on {0}."]
+ .Value,
+ GetEventDateDisplay(company))
+ : _localizer
+ ["Company ceased operations on an unknown date."]
+ .Value;
+
+ private string GetRenamedStatusMessage(CompanyDto company)
+ {
+ if(SoldToCompany != null)
+ {
+ return string.Format(_localizer["Company renamed to {0} on {1}."].Value,
+ SoldToCompany.Name,
+ GetEventDateDisplay(company));
+ }
+
+ if(company.Sold != null)
+ {
+ return string.Format(_localizer["Company was renamed on {0} to an unknown name."].Value,
+ GetEventDateDisplay(company));
+ }
+
+ return SoldToCompany != null
+ ? string.Format(_localizer["Company renamed to {0} on an unknown date."].Value, SoldToCompany.Name)
+ : _localizer["Company renamed to an unknown name on an unknown date."].Value;
+ }
+
+ ///
+ /// Loads company details from the API
+ ///
+ private async Task LoadDataAsync()
+ {
+ try
+ {
+ IsLoading = true;
+ ErrorMessage = string.Empty;
+ HasError = false;
+ IsDataLoaded = false;
+
+ if(CompanyId <= 0)
+ {
+ ErrorMessage = _localizer["Invalid company ID."].Value;
+ HasError = true;
+
+ return;
+ }
+
+ // Load company details
+ Company = await _companyDetailService.GetCompanyByIdAsync(CompanyId);
+
+ if(Company is null)
+ {
+ ErrorMessage = _localizer["Company not found."].Value;
+ HasError = true;
+
+ return;
+ }
+
+ // Load sold-to company if applicable
+ if(Company.SoldToId != null)
+ {
+ int soldToId = UntypedNodeExtractor.ExtractInt(Company.SoldToId);
+ if(soldToId > 0) SoldToCompany = await _companyDetailService.GetSoldToCompanyAsync(soldToId);
+ }
+
+ // Load computers and consoles made by this company
+ List machines = await _companyDetailService.GetComputersByCompanyAsync(CompanyId);
+ Computers.Clear();
+ Consoles.Clear();
+ FilteredComputers.Clear();
+ FilteredConsoles.Clear();
+
+ foreach(MachineDto machine in machines)
+ {
+ int machineId = UntypedNodeExtractor.ExtractInt(machine.Id);
+
+ var machineItem = new CompanyDetailMachine
+ {
+ Id = machineId,
+ Name = machine.Name ?? string.Empty
+ };
+
+ // Categorize by machine type enum
+ if(machine.Type == (int)MachineType.Computer)
+ Computers.Add(machineItem);
+ else if(machine.Type == (int)MachineType.Console) Consoles.Add(machineItem);
+ }
+
+ // Initialize filtered lists
+ FilteredComputers = new ObservableCollection(Computers);
+ FilteredConsoles = new ObservableCollection(Consoles);
+
+ IsDataLoaded = true;
+ }
+ catch(Exception ex)
+ {
+ _logger.LogError("Error loading company details: {Exception}", ex.Message);
+ ErrorMessage = _localizer["Failed to load company details. Please try again later."].Value;
+ HasError = true;
+ }
+ finally
+ {
+ IsLoading = false;
+ }
+ }
+
+ ///
+ /// Handles back navigation
+ ///
+ private async Task GoBackAsync()
+ {
+ await _navigator.NavigateViewModelAsync(this);
+ }
+}
+
+///
+/// Data model for a machine in the company detail view
+///
+public class CompanyDetailMachine
+{
+ public int Id { get; set; }
+ public string Name { get; set; } = string.Empty;
+}
\ No newline at end of file
diff --git a/Marechai.App/Presentation/ViewModels/MachineViewViewModel.cs b/Marechai.App/Presentation/ViewModels/MachineViewViewModel.cs
index 706c9493..181254d8 100644
--- a/Marechai.App/Presentation/ViewModels/MachineViewViewModel.cs
+++ b/Marechai.App/Presentation/ViewModels/MachineViewViewModel.cs
@@ -31,6 +31,7 @@ using System.Collections.ObjectModel;
using System.Threading.Tasks;
using Humanizer;
using Marechai.App.Helpers;
+using Marechai.App.Presentation.Models;
using Marechai.App.Services;
using Marechai.Data;
using Microsoft.UI.Xaml;
@@ -127,6 +128,19 @@ public partial class MachineViewViewModel : ObservableObject
return;
}
+ // If we came from CompanyDetailViewModel, navigate back to company details
+ if(_navigationSource is CompanyDetailViewModel companyVm)
+ {
+ var navParam = new CompanyDetailNavigationParameter
+ {
+ CompanyId = companyVm.CompanyId
+ };
+
+ await _navigator.NavigateViewModelAsync(this, data: navParam);
+
+ return;
+ }
+
// If we came from ConsolesListViewModel, navigate back to consoles list
if(_navigationSource is ConsolesListViewModel)
{
diff --git a/Marechai.App/Presentation/Views/CompaniesPage.xaml b/Marechai.App/Presentation/Views/CompaniesPage.xaml
index 801b4dee..b4a7be2b 100644
--- a/Marechai.App/Presentation/Views/CompaniesPage.xaml
+++ b/Marechai.App/Presentation/Views/CompaniesPage.xaml
@@ -92,7 +92,23 @@
-
+
+
+
+
+
diff --git a/Marechai.App/Presentation/Views/CompaniesPage.xaml.cs b/Marechai.App/Presentation/Views/CompaniesPage.xaml.cs
new file mode 100644
index 00000000..f1954a10
--- /dev/null
+++ b/Marechai.App/Presentation/Views/CompaniesPage.xaml.cs
@@ -0,0 +1,59 @@
+using Marechai.App.Presentation.ViewModels;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Navigation;
+
+namespace Marechai.App.Presentation.Views;
+
+public sealed partial class CompaniesPage : Page
+{
+ public CompaniesPage()
+ {
+ InitializeComponent();
+ DataContextChanged += CompaniesPage_DataContextChanged;
+ Loaded += CompaniesPage_Loaded;
+ }
+
+ private void CompaniesPage_Loaded(object sender, RoutedEventArgs e)
+ {
+ if(DataContext is not CompaniesViewModel viewModel) return;
+
+ // Trigger data loading
+ _ = viewModel.LoadData.ExecuteAsync(null);
+ }
+
+ private void CompaniesPage_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
+ {
+ if(args.NewValue is CompaniesViewModel viewModel)
+ {
+ // Trigger data loading when data context changes
+ _ = viewModel.LoadData.ExecuteAsync(null);
+ }
+ }
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ base.OnNavigatedTo(e);
+
+ if(DataContext is CompaniesViewModel viewModel)
+ {
+ // Trigger data loading when navigating to the page
+ _ = viewModel.LoadData.ExecuteAsync(null);
+ }
+ }
+
+ private void OnSearchTextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
+ {
+ if(args.Reason == AutoSuggestionBoxTextChangeReason.UserInput)
+ {
+ // The two-way binding will automatically update SearchQuery in ViewModel,
+ // which will trigger OnSearchQueryChanged and filter the list
+ }
+ }
+
+ private void OnSearchQuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
+ {
+ // The two-way binding will automatically update SearchQuery in ViewModel,
+ // which will trigger OnSearchQueryChanged and filter the list
+ }
+}
\ No newline at end of file
diff --git a/Marechai.App/Presentation/Views/CompanyDetailPage.xaml b/Marechai.App/Presentation/Views/CompanyDetailPage.xaml
new file mode 100644
index 00000000..8378831b
--- /dev/null
+++ b/Marechai.App/Presentation/Views/CompanyDetailPage.xaml
@@ -0,0 +1,273 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Marechai.App/Presentation/Views/CompanyDetailPage.xaml.cs b/Marechai.App/Presentation/Views/CompanyDetailPage.xaml.cs
new file mode 100644
index 00000000..dc031e05
--- /dev/null
+++ b/Marechai.App/Presentation/Views/CompanyDetailPage.xaml.cs
@@ -0,0 +1,77 @@
+#nullable enable
+
+using System;
+using Windows.System;
+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;
+
+public sealed partial class CompanyDetailPage : Page
+{
+ private object? _navigationSource;
+ private int? _pendingCompanyId;
+
+ public CompanyDetailPage()
+ {
+ InitializeComponent();
+ DataContextChanged += CompanyDetailPage_DataContextChanged;
+ }
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ base.OnNavigatedTo(e);
+
+ int? companyId = null;
+
+ // Handle both int and CompanyDetailNavigationParameter
+ if(e.Parameter is int intId)
+ companyId = intId;
+ else if(e.Parameter is CompanyDetailNavigationParameter navParam)
+ {
+ companyId = navParam.CompanyId;
+ _navigationSource = navParam.NavigationSource;
+ }
+
+ if(companyId.HasValue)
+ {
+ _pendingCompanyId = companyId;
+
+ if(DataContext is CompanyDetailViewModel viewModel)
+ {
+ viewModel.CompanyId = companyId.Value;
+ _ = viewModel.LoadData.ExecuteAsync(null);
+ }
+ }
+ }
+
+ private void CompanyDetailPage_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
+ {
+ if(DataContext is CompanyDetailViewModel viewModel && _pendingCompanyId.HasValue)
+ {
+ viewModel.CompanyId = _pendingCompanyId.Value;
+ _ = viewModel.LoadData.ExecuteAsync(null);
+ }
+ }
+
+ private async void OnTwitterClick(object sender, RoutedEventArgs e)
+ {
+ if(DataContext is CompanyDetailViewModel viewModel && viewModel.Company?.Twitter is not null)
+ {
+ var uri = new Uri($"https://www.twitter.com/{viewModel.Company.Twitter}");
+ await Launcher.LaunchUriAsync(uri);
+ }
+ }
+
+ private async void OnFacebookClick(object sender, RoutedEventArgs e)
+ {
+ if(DataContext is CompanyDetailViewModel viewModel && viewModel.Company?.Facebook is not null)
+ {
+ var uri = new Uri($"https://www.facebook.com/{viewModel.Company.Facebook}");
+ await Launcher.LaunchUriAsync(uri);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Marechai.App/Presentation/Views/ConsolesListPage.xaml.cs b/Marechai.App/Presentation/Views/ConsolesListPage.xaml.cs
new file mode 100644
index 00000000..535f1e93
--- /dev/null
+++ b/Marechai.App/Presentation/Views/ConsolesListPage.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 consoles filtered by letter, year, or all.
+/// Features responsive layout, modern styling, and smooth navigation.
+///
+public sealed partial class ConsolesListPage : Page
+{
+ public ConsolesListPage()
+ {
+ InitializeComponent();
+ Loaded += ConsolesListPage_Loaded;
+ DataContextChanged += ConsolesListPage_DataContextChanged;
+ }
+
+ private void ConsolesListPage_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
+ {
+ if(DataContext is ConsolesListViewModel vm)
+ {
+ // Load data when DataContext is set
+ vm.LoadData.Execute(null);
+ }
+ }
+
+ private void ConsolesListPage_Loaded(object sender, RoutedEventArgs e)
+ {
+ if(DataContext is ConsolesListViewModel vm)
+ {
+ // Load data when page is loaded (fallback)
+ vm.LoadData.Execute(null);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Marechai.App/Presentation/Views/ConsolesPage.xaml.cs b/Marechai.App/Presentation/Views/ConsolesPage.xaml.cs
new file mode 100644
index 00000000..3d5fbdad
--- /dev/null
+++ b/Marechai.App/Presentation/Views/ConsolesPage.xaml.cs
@@ -0,0 +1,44 @@
+using Marechai.App.Presentation.ViewModels;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Navigation;
+
+namespace Marechai.App.Presentation.Views;
+
+public sealed partial class ConsolesPage : Page
+{
+ public ConsolesPage()
+ {
+ InitializeComponent();
+ DataContextChanged += ConsolesPage_DataContextChanged;
+ Loaded += ConsolesPage_Loaded;
+ }
+
+ private void ConsolesPage_Loaded(object sender, RoutedEventArgs e)
+ {
+ if(DataContext is not ConsolesViewModel viewModel) return;
+
+ // Trigger data loading
+ _ = viewModel.LoadData.ExecuteAsync(null);
+ }
+
+ private void ConsolesPage_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
+ {
+ if(args.NewValue is ConsolesViewModel viewModel)
+ {
+ // Trigger data loading when data context changes
+ _ = viewModel.LoadData.ExecuteAsync(null);
+ }
+ }
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ base.OnNavigatedTo(e);
+
+ if(DataContext is ConsolesViewModel viewModel)
+ {
+ // Trigger data loading when navigating to the page
+ _ = viewModel.LoadData.ExecuteAsync(null);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Marechai.App/Services/CompanyDetailService.cs b/Marechai.App/Services/CompanyDetailService.cs
new file mode 100644
index 00000000..b66b7588
--- /dev/null
+++ b/Marechai.App/Services/CompanyDetailService.cs
@@ -0,0 +1,101 @@
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Marechai.App.Models;
+
+namespace Marechai.App.Services;
+
+///
+/// Service for fetching company details from the API
+///
+public class CompanyDetailService
+{
+ private readonly ApiClient _apiClient;
+ private readonly ILogger _logger;
+
+ public CompanyDetailService(ApiClient apiClient, ILogger logger)
+ {
+ _apiClient = apiClient;
+ _logger = logger;
+ }
+
+ ///
+ /// Gets a single company by ID with full details
+ ///
+ public async Task GetCompanyByIdAsync(int companyId)
+ {
+ try
+ {
+ _logger.LogInformation("Fetching company {CompanyId} from API", companyId);
+
+ CompanyDto? company = await _apiClient.Companies[companyId].GetAsync();
+
+ if(company == null)
+ {
+ _logger.LogWarning("Company {CompanyId} not found", companyId);
+
+ return null;
+ }
+
+ _logger.LogInformation("Successfully fetched company {CompanyId}: {CompanyName}", companyId, company.Name);
+
+ return company;
+ }
+ catch(Exception ex)
+ {
+ _logger.LogError(ex, "Error fetching company {CompanyId} from API", companyId);
+
+ return null;
+ }
+ }
+
+ ///
+ /// Gets machines (computers) made by a company
+ ///
+ public async Task> GetComputersByCompanyAsync(int companyId)
+ {
+ try
+ {
+ _logger.LogInformation("Fetching computers for company {CompanyId}", companyId);
+
+ List? machines = await _apiClient.Companies[companyId].Machines.GetAsync();
+
+ if(machines == null) return [];
+
+ _logger.LogInformation("Successfully fetched {Count} computers for company {CompanyId}", machines.Count, companyId);
+
+ return machines;
+ }
+ catch(Exception ex)
+ {
+ _logger.LogError(ex, "Error fetching computers for company {CompanyId}", companyId);
+
+ return [];
+ }
+ }
+
+ ///
+ /// Gets the sold-to company (when company was sold, merged, or renamed)
+ ///
+ public async Task GetSoldToCompanyAsync(int? companyId)
+ {
+ if(companyId is null or <= 0) return null;
+
+ try
+ {
+ _logger.LogInformation("Fetching sold-to company {CompanyId}", companyId);
+
+ CompanyDto? company = await _apiClient.Companies[companyId.Value].GetAsync();
+
+ return company;
+ }
+ catch(Exception ex)
+ {
+ _logger.LogError(ex, "Error fetching sold-to company {CompanyId}", companyId);
+
+ return null;
+ }
+ }
+}
diff --git a/Marechai.App/Strings/en/Resources.resw b/Marechai.App/Strings/en/Resources.resw
index bb951adf..16dff704 100644
--- a/Marechai.App/Strings/en/Resources.resw
+++ b/Marechai.App/Strings/en/Resources.resw
@@ -109,4 +109,61 @@
Logout
+
+ Company is active.
+
+
+ Company sold to {0} on {1}.
+
+
+ Company sold on {0} to an unknown company.
+
+
+ Company sold to {0} on an unknown date.
+
+
+ Company was sold to an unknown company on an unknown date.
+
+
+ Company merged on {0} to form {1}.
+
+
+ Company merged on {0} to form an unknown company.
+
+
+ Company merged on an unknown date to form {0}.
+
+
+ Company merged to form an unknown company on an unknown date.
+
+
+ Current company status is unknown.
+
+
+ unknown date
+
+
+ Company declared bankruptcy on {0}.
+
+
+ Company declared bankruptcy on an unknown date.
+
+
+ Company ceased operations on {0}.
+
+
+ Company ceased operations on an unknown date.
+
+
+ Company renamed to {0} on {1}.
+
+
+ Company was renamed on {0} to an unknown name.
+
+
+ Company renamed to {0} on an unknown date.
+
+
+ Company was renamed on an unknown date to an unknown name.
+
diff --git a/Marechai.App/Strings/es/Resources.resw b/Marechai.App/Strings/es/Resources.resw
index 16b08eb9..72018021 100644
--- a/Marechai.App/Strings/es/Resources.resw
+++ b/Marechai.App/Strings/es/Resources.resw
@@ -109,4 +109,61 @@
Cerrar sesión
+
+ La empresa está activa.
+
+
+ La empresa fue vendida a {0} el {1}.
+
+
+ La empresa fue vendida el {0} a una empresa desconocida.
+
+
+ La empresa fue vendida a {0} en una fecha desconocida.
+
+
+ La empresa fue vendida a una empresa desconocida en una fecha desconocida.
+
+
+ La empresa se fusionó el {0} para formar {1}.
+
+
+ La empresa se fusionó el {0} para formar una empresa desconocida.
+
+
+ La empresa se fusionó en una fecha desconocida para formar {0}.
+
+
+ La empresa se fusionó para formar una empresa desconocida en una fecha desconocida.
+
+
+ El estado actual de la empresa es desconocido.
+
+
+ fecha desconocida
+
+
+ La empresa declaró quiebra el {0}.
+
+
+ La empresa declaró quiebra en una fecha desconocida.
+
+
+ La empresa cesó operaciones el {0}.
+
+
+ La empresa cesó operaciones en una fecha desconocida.
+
+
+ La empresa fue renombrada a {0} el {1}.
+
+
+ La empresa fue renombrada el {0} a un nombre desconocido.
+
+
+ La empresa fue renombrada a {0} en una fecha desconocida.
+
+
+ La empresa fue renombrada en una fecha desconocida a un nombre desconocido.
+
diff --git a/Marechai.App/Strings/fr/Resources.resw b/Marechai.App/Strings/fr/Resources.resw
index f448255b..d5298761 100644
--- a/Marechai.App/Strings/fr/Resources.resw
+++ b/Marechai.App/Strings/fr/Resources.resw
@@ -109,4 +109,61 @@
Déconnexion
+
+ L'entreprise est active.
+
+
+ L'entreprise a été vendue à {0} le {1}.
+
+
+ L'entreprise a été vendue le {0} à une entreprise inconnue.
+
+
+ L'entreprise a été vendue à {0} à une date inconnue.
+
+
+ L'entreprise a été vendue à une entreprise inconnue à une date inconnue.
+
+
+ L'entreprise a fusionné le {0} pour former {1}.
+
+
+ L'entreprise a fusionné le {0} pour former une entreprise inconnue.
+
+
+ L'entreprise a fusionné à une date inconnue pour former {0}.
+
+
+ L'entreprise a fusionné pour former une entreprise inconnue à une date inconnue.
+
+
+ L'état actuel de l'entreprise est inconnu.
+
+
+ date inconnue
+
+
+ L'entreprise a déclaré faillite le {0}.
+
+
+ L'entreprise a déclaré faillite à une date inconnue.
+
+
+ L'entreprise a cessé ses activités le {0}.
+
+
+ L'entreprise a cessé ses activités à une date inconnue.
+
+
+ L'entreprise a été renommée en {0} le {1}.
+
+
+ L'entreprise a été renommée le {0} en un nom inconnu.
+
+
+ L'entreprise a été renommée en {0} à une date inconnue.
+
+
+ L'entreprise a été renommée à une date inconnue en un nom inconnu.
+
diff --git a/Marechai.App/Strings/pt-BR/Resources.resw b/Marechai.App/Strings/pt-BR/Resources.resw
index 50e4a3e8..f2f52baa 100644
--- a/Marechai.App/Strings/pt-BR/Resources.resw
+++ b/Marechai.App/Strings/pt-BR/Resources.resw
@@ -109,4 +109,61 @@
Fazer Logout
+
+ A empresa está ativa.
+
+
+ A empresa foi vendida para {0} em {1}.
+
+
+ A empresa foi vendida em {0} para uma empresa desconhecida.
+
+
+ A empresa foi vendida para {0} em uma data desconhecida.
+
+
+ A empresa foi vendida para uma empresa desconhecida em uma data desconhecida.
+
+
+ A empresa se fundiu em {0} para formar {1}.
+
+
+ A empresa se fundiu em {0} para formar uma empresa desconhecida.
+
+
+ A empresa se fundiu em uma data desconhecida para formar {0}.
+
+
+ A empresa se fundiu para formar uma empresa desconhecida em uma data desconhecida.
+
+
+ O status atual da empresa é desconhecido.
+
+
+ data desconhecida
+
+
+ A empresa declarou falência em {0}.
+
+
+ A empresa declarou falência em uma data desconhecida.
+
+
+ A empresa cessou operações em {0}.
+
+
+ A empresa cessou operações em uma data desconhecida.
+
+
+ A empresa foi renomeada para {0} em {1}.
+
+
+ A empresa foi renomeada em {0} para um nome desconhecido.
+
+
+ A empresa foi renomeada para {0} em uma data desconhecida.
+
+
+ A empresa foi renomeada em uma data desconhecida para um nome desconhecido.
+