Add companies list.

This commit is contained in:
2025-11-15 04:48:11 +00:00
parent 7ee042bdec
commit d5fbb55425
5 changed files with 395 additions and 0 deletions

View File

@@ -111,6 +111,8 @@ public partial class App : Application
services.AddSingleton<ComputersViewModel>();
services.AddSingleton<ConsolesService>();
services.AddSingleton<ConsolesViewModel>();
services.AddSingleton<CompaniesService>();
services.AddSingleton<CompaniesViewModel>();
services.AddSingleton<MachineViewViewModel>();
services
@@ -145,6 +147,7 @@ public partial class App : Application
new ViewMap<ComputersListPage, ComputersListViewModel>(),
new ViewMap<ConsolesPage, ConsolesViewModel>(),
new ViewMap<ConsolesListPage, ConsolesListViewModel>(),
new ViewMap<CompaniesPage, CompaniesViewModel>(),
new ViewMap<MachineViewPage, MachineViewViewModel>(),
new DataViewMap<SecondPage, SecondViewModel, Entity>());
@@ -179,6 +182,8 @@ public partial class App : Application
views.FindByViewModel<
ConsolesListViewModel>())
]),
new RouteMap("companies",
views.FindByViewModel<CompaniesViewModel>()),
new RouteMap("Second",
views.FindByViewModel<SecondViewModel>())
])

View File

@@ -0,0 +1,20 @@
using System;
using Microsoft.UI.Xaml.Data;
namespace Marechai.App.Presentation.Converters;
/// <summary>
/// Converts DateTime to formatted foundation date string, returns empty if null
/// </summary>
public class FoundationDateConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if(value is DateTime dateTime) return dateTime.ToString("MMMM d, yyyy");
return string.Empty;
}
public object ConvertBack(object value, Type targetType, object parameter, string language) =>
throw new NotImplementedException();
}

View File

@@ -0,0 +1,189 @@
#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.Services;
using Uno.Extensions.Navigation;
namespace Marechai.App.Presentation.ViewModels;
public partial class CompaniesViewModel : ObservableObject
{
private readonly CompaniesService _companiesService;
private readonly IStringLocalizer _localizer;
private readonly ILogger<CompaniesViewModel> _logger;
private readonly INavigator _navigator;
private readonly List<CompanyListItem> _allCompanies = [];
[ObservableProperty]
private ObservableCollection<CompanyListItem> _companiesList = [];
[ObservableProperty]
private int _companyCount;
[ObservableProperty]
private string _companyCountText = string.Empty;
[ObservableProperty]
private string _errorMessage = string.Empty;
[ObservableProperty]
private bool _hasError;
[ObservableProperty]
private bool _isDataLoaded;
[ObservableProperty]
private bool _isLoading;
[ObservableProperty]
private string _searchQuery = string.Empty;
public CompaniesViewModel(CompaniesService companiesService, IStringLocalizer localizer,
ILogger<CompaniesViewModel> logger, INavigator navigator)
{
_companiesService = companiesService;
_localizer = localizer;
_logger = logger;
_navigator = navigator;
LoadData = new AsyncRelayCommand(LoadDataAsync);
GoBackCommand = new AsyncRelayCommand(GoBackAsync);
NavigateToCompanyCommand = new AsyncRelayCommand<CompanyListItem>(NavigateToCompanyAsync);
}
public IAsyncRelayCommand LoadData { get; }
public ICommand GoBackCommand { get; }
public IAsyncRelayCommand<CompanyListItem> NavigateToCompanyCommand { get; }
public string Title { get; } = "Companies";
partial void OnSearchQueryChanged(string value)
{
// Automatically filter when SearchQuery changes
UpdateFilter(value);
}
/// <summary>
/// Loads companies count and list from the API
/// </summary>
private async Task LoadDataAsync()
{
try
{
IsLoading = true;
ErrorMessage = string.Empty;
HasError = false;
IsDataLoaded = false;
CompaniesList.Clear();
_allCompanies.Clear();
// Load companies
List<CompanyDto> companies = await _companiesService.GetAllCompaniesAsync();
// Set count
CompanyCount = companies.Count;
CompanyCountText = _localizer["Companies in the database"];
// Build the full list in memory
foreach(CompanyDto company in companies)
{
// Extract id from UntypedNode
int companyId = UntypedNodeExtractor.ExtractInt(company.Id);
// Convert DateTimeOffset? to DateTime?
DateTime? foundedDate = company.Founded?.DateTime;
_allCompanies.Add(new CompanyListItem
{
Id = companyId,
Name = company.Name ?? string.Empty,
FoundationDate = foundedDate
});
}
// Apply current filter (will show all if SearchQuery is empty)
UpdateFilter(SearchQuery);
if(CompaniesList.Count == 0)
{
ErrorMessage = _localizer["No companies found"].Value;
HasError = true;
}
else
IsDataLoaded = true;
}
catch(Exception ex)
{
_logger.LogError("Error loading companies data: {Exception}", ex.Message);
ErrorMessage = _localizer["Failed to load companies data. Please try again later."].Value;
HasError = true;
}
finally
{
IsLoading = false;
}
}
/// <summary>
/// Handles back navigation
/// </summary>
private async Task GoBackAsync()
{
await _navigator.NavigateViewModelAsync<MainViewModel>(this);
}
/// <summary>
/// Navigates to company detail view
/// </summary>
private async Task NavigateToCompanyAsync(CompanyListItem? company)
{
if(company is null) return;
_logger.LogInformation("Navigating to company: {CompanyName} (ID: {CompanyId})", company.Name, company.Id);
// TODO: Implement company detail view
// For now, just log the navigation
}
/// <summary>
/// Updates the filtered list based on search query
/// </summary>
private void UpdateFilter(string? query)
{
string lowerQuery = string.IsNullOrWhiteSpace(query) ? string.Empty : query.Trim().ToLowerInvariant();
CompaniesList.Clear();
if(string.IsNullOrEmpty(lowerQuery))
{
// No filter, show all companies
foreach(CompanyListItem company in _allCompanies) CompaniesList.Add(company);
}
else
{
// Filter companies by name (case-insensitive)
var filtered = _allCompanies.Where(c => c.Name.Contains(lowerQuery, StringComparison.OrdinalIgnoreCase))
.ToList();
foreach(CompanyListItem company in filtered) CompaniesList.Add(company);
}
}
}
/// <summary>
/// Data model for a company in the list
/// </summary>
public class CompanyListItem
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public DateTime? FoundationDate { get; set; }
public string FoundationDateDisplay =>
FoundationDate.HasValue ? FoundationDate.Value.ToString("MMMM d, yyyy") : string.Empty;
}

View File

@@ -0,0 +1,104 @@
<?xml version="1.0"
encoding="utf-8"?>
<Page x:Class="Marechai.App.Presentation.Views.CompaniesPage"
x:Name="PageRoot"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:utu="using:Uno.Toolkit.UI"
NavigationCacheMode="Required"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid utu:SafeArea.Insets="VisibleBounds">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Header -->
<utu:NavigationBar Grid.Row="0"
Content="{Binding Path=Title}">
<utu:NavigationBar.MainCommand>
<AppBarButton Icon="Back"
Label="Back"
Command="{Binding GoBackCommand}"
AutomationProperties.Name="Go back" />
</utu:NavigationBar.MainCommand>
</utu:NavigationBar>
<!-- Content -->
<ScrollViewer Grid.Row="1">
<StackPanel Padding="16"
Spacing="16">
<!-- Company Count Display -->
<StackPanel HorizontalAlignment="Center"
Spacing="8">
<TextBlock Text="{Binding CompanyCountText}"
TextAlignment="Center"
FontSize="18"
FontWeight="SemiBold"
Foreground="{ThemeResource SystemBaseMediumColor}" />
<TextBlock Text="{Binding CompanyCount}"
TextAlignment="Center"
FontSize="48"
FontWeight="Bold"
Foreground="{ThemeResource SystemAccentColor}" />
</StackPanel>
<!-- Search Box -->
<AutoSuggestBox x:Name="CompaniesSearchBox"
HorizontalAlignment="Stretch"
PlaceholderText="Search companies..."
Text="{Binding SearchQuery, Mode=TwoWay}"
QuerySubmitted="OnSearchQuerySubmitted"
TextChanged="OnSearchTextChanged">
<AutoSuggestBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</AutoSuggestBox.ItemTemplate>
</AutoSuggestBox>
<!-- Loading State -->
<StackPanel Visibility="{Binding IsLoading}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Padding="32"
Spacing="16">
<ProgressRing IsActive="True"
IsIndeterminate="True"
Height="48"
Width="48" />
<TextBlock Text="Loading..."
TextAlignment="Center"
FontSize="14" />
</StackPanel>
<!-- Error State -->
<StackPanel Visibility="{Binding HasError}"
Padding="16"
Background="{ThemeResource SystemErrorBackgroundColor}"
CornerRadius="8"
Spacing="8">
<TextBlock Text="Error"
FontWeight="Bold"
Foreground="{ThemeResource SystemErrorTextForegroundColor}" />
<TextBlock Text="{Binding ErrorMessage}"
Foreground="{ThemeResource SystemErrorTextForegroundColor}"
TextWrapping="Wrap" />
</StackPanel>
<!-- Companies List -->
<StackPanel Visibility="{Binding IsDataLoaded}"
Spacing="8">
<ItemsRepeater ItemsSource="{Binding CompaniesList}">
<ItemsRepeater.ItemTemplate></ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</StackPanel>
</StackPanel>
</ScrollViewer>
</Grid>
</Page>

View File

@@ -0,0 +1,77 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Marechai.App.Services;
/// <summary>
/// Service for fetching companies data from the API
/// </summary>
public class CompaniesService
{
private readonly ApiClient _apiClient;
private readonly ILogger<CompaniesService> _logger;
public CompaniesService(ApiClient apiClient, ILogger<CompaniesService> logger)
{
_apiClient = apiClient;
_logger = logger;
}
/// <summary>
/// Gets all companies
/// </summary>
public async Task<List<CompanyDto>> GetAllCompaniesAsync()
{
try
{
_logger.LogInformation("Fetching all companies from API");
List<CompanyDto>? companies = await _apiClient.Companies.GetAsync();
if(companies == null) return [];
_logger.LogInformation("Successfully fetched {Count} total companies", companies.Count);
return companies;
}
catch(Exception ex)
{
_logger.LogError(ex, "Error fetching all companies from API");
return [];
}
}
/// <summary>
/// Gets a single company by ID
/// </summary>
public async Task<CompanyDto?> 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;
}
}
}