Files
marechai/Marechai.App/Presentation/ViewModels/Admin/AdminMachinesViewModel.cs

788 lines
35 KiB
C#

#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using Humanizer;
using Marechai.App.Navigation;
using Marechai.App.Presentation.Views.Admin;
using Marechai.App.Services.Authentication;
using Marechai.Data;
namespace Marechai.App.Presentation.ViewModels.Admin;
public partial class AdminMachinesViewModel : ObservableObject, IRegionAware
{
private readonly Client _apiClient;
private readonly IJwtService _jwtService;
private readonly IStringLocalizer _localizer;
private readonly ILogger<AdminMachinesViewModel> _logger;
private readonly IRegionManager _regionManager;
private readonly ITokenService _tokenService;
// --- List state ---
[ObservableProperty] private ObservableCollection<MachineDto> _machines = [];
[ObservableProperty] private ObservableCollection<MachineDto> _filteredMachines = [];
[ObservableProperty] private string _filterText = string.Empty;
[ObservableProperty] private MachineDto? _selectedMachine;
private List<MachineDto>? _allMachines;
[ObservableProperty] private bool _isLoading;
[ObservableProperty] private bool _isDataLoaded;
[ObservableProperty] private bool _hasError;
[ObservableProperty] private string _errorMessage = string.Empty;
[ObservableProperty] private bool _isAdmin;
[ObservableProperty] private bool _isEditing;
[ObservableProperty] private bool _isEditingExisting;
[ObservableProperty] private string _editPanelTitle = string.Empty;
private int? _editingId;
// --- Base form fields ---
[ObservableProperty] private string _machineName = string.Empty;
[ObservableProperty] private string _model = string.Empty;
[ObservableProperty] private int _machineTypeIndex;
[ObservableProperty] private DateTimeOffset? _introduced;
[ObservableProperty] private int _introducedPrecision;
[ObservableProperty] private List<string> _machineTypeItems = [];
// --- Company picker ---
[ObservableProperty] private CompanyDto? _selectedCompany;
[ObservableProperty] private string _companySearchText = string.Empty;
[ObservableProperty] private ObservableCollection<CompanyDto> _companySuggestions = [];
private List<CompanyDto>? _allCompanies;
// --- Family picker ---
[ObservableProperty] private MachineFamilyDto? _selectedFamily;
[ObservableProperty] private ObservableCollection<MachineFamilyDto> _families = [];
// --- GPU junction ---
[ObservableProperty] private ObservableCollection<GpuByMachineDto> _machineGpus = [];
[ObservableProperty] private ObservableCollection<string> _machineGpuDisplays = [];
[ObservableProperty] private ObservableCollection<GpuDto> _availableGpus = [];
[ObservableProperty] private GpuDto? _selectedAvailableGpu;
private List<GpuDto>? _allGpusList;
// --- Processor junction ---
[ObservableProperty] private ObservableCollection<ProcessorByMachineDto> _machineProcessors = [];
[ObservableProperty] private ObservableCollection<string> _machineProcessorDisplays = [];
[ObservableProperty] private ObservableCollection<ProcessorDto> _availableProcessors = [];
[ObservableProperty] private ProcessorDto? _selectedAvailableProcessor;
[ObservableProperty] private double? _processorSpeed;
private List<ProcessorDto>? _allProcessorsList;
// --- Sound synth junction ---
[ObservableProperty] private ObservableCollection<SoundSynthByMachineDto> _machineSounds = [];
[ObservableProperty] private ObservableCollection<string> _machineSoundDisplays = [];
[ObservableProperty] private ObservableCollection<SoundSynthDto> _availableSounds = [];
[ObservableProperty] private SoundSynthDto? _selectedAvailableSound;
private List<SoundSynthDto>? _allSoundsList;
// --- Screen junction ---
[ObservableProperty] private ObservableCollection<ScreenByMachineDto> _machineScreens = [];
[ObservableProperty] private ObservableCollection<string> _machineScreenDisplays = [];
[ObservableProperty] private ObservableCollection<ScreenDto> _availableScreens = [];
[ObservableProperty] private ScreenDto? _selectedAvailableScreen;
private List<ScreenDto>? _allScreensList;
// --- Memory (inline creation) ---
[ObservableProperty] private ObservableCollection<MemoryByMachineDto> _machineMemories = [];
[ObservableProperty] private ObservableCollection<string> _machineMemoryDisplays = [];
[ObservableProperty] private int _memoryTypeIndex;
[ObservableProperty] private int _memoryUsageIndex;
[ObservableProperty] private long? _memorySize;
[ObservableProperty] private double? _memorySpeed;
// --- Storage (inline creation) ---
[ObservableProperty] private ObservableCollection<StorageByMachineDto> _machineStorage = [];
[ObservableProperty] private ObservableCollection<string> _machineStorageDisplays = [];
[ObservableProperty] private int _storageTypeIndex;
[ObservableProperty] private int _storageInterfaceIndex;
[ObservableProperty] private long? _storageCapacity;
// --- Software Platform junction ---
[ObservableProperty] private ObservableCollection<SoftwarePlatformByMachineDto> _machineSoftwarePlatforms = [];
[ObservableProperty] private ObservableCollection<string> _machineSoftwarePlatformDisplays = [];
[ObservableProperty] private ObservableCollection<SoftwarePlatformDto> _availableSoftwarePlatforms = [];
[ObservableProperty] private SoftwarePlatformDto? _selectedAvailableSoftwarePlatform;
private List<SoftwarePlatformDto>? _allSoftwarePlatformsList;
// --- Enum items for ComboBoxes ---
public List<string> MemoryTypeItems { get; private set; } = [];
public List<string> MemoryUsageItems { get; private set; } = [];
public List<string> StorageTypeItems { get; private set; } = [];
public List<string> StorageInterfaceItems { get; private set; } = [];
// Enum value arrays for mapping ComboBox index → actual enum int value
static readonly int[] StorageTypeValues = Enum.GetValues<StorageType>().Select(e => (int)e).ToArray();
static readonly int[] StorageInterfaceValues = Enum.GetValues<StorageInterface>().Select(e => (int)e).ToArray();
public AdminMachinesViewModel(Client apiClient,
IJwtService jwtService,
ITokenService tokenService,
ILogger<AdminMachinesViewModel> logger,
IStringLocalizer localizer,
IRegionManager regionManager)
{
_apiClient = apiClient;
_jwtService = jwtService;
_tokenService = tokenService;
_logger = logger;
_localizer = localizer;
_regionManager = regionManager;
MachineTypeItems = [localizer["MachineTypeUnknown"], localizer["MachineTypeComputer"], localizer["MachineTypeConsole"]];
MemoryTypeItems = Enum.GetValues<MemoryType>().Select(e => e.Humanize()).ToList();
MemoryUsageItems = Enum.GetValues<MemoryUsage>().Select(e => e.Humanize()).ToList();
StorageTypeItems = Enum.GetValues<StorageType>().Select(e => e.Humanize()).ToList();
StorageInterfaceItems = Enum.GetValues<StorageInterface>().Select(e => e.Humanize()).ToList();
LoadItemsCommand = new AsyncRelayCommand(LoadItemsAsync);
OpenAddCommand = new RelayCommand(OpenAdd);
OpenEditCommand = new RelayCommand<MachineDto>(OpenEdit);
DeleteCommand = new AsyncRelayCommand<MachineDto>(DeleteAsync);
SaveCommand = new AsyncRelayCommand(SaveAsync);
CancelEditCommand = new RelayCommand(CancelEdit);
// Junction commands
AddGpuCommand = new AsyncRelayCommand(AddGpuAsync);
RemoveGpuCommand = new AsyncRelayCommand<string>(RemoveGpuByDisplayAsync);
AddProcessorCommand = new AsyncRelayCommand(AddProcessorAsync);
RemoveProcessorCommand = new AsyncRelayCommand<string>(RemoveProcessorByDisplayAsync);
AddSoundCommand = new AsyncRelayCommand(AddSoundAsync);
RemoveSoundCommand = new AsyncRelayCommand<string>(RemoveSoundByDisplayAsync);
AddScreenCommand = new AsyncRelayCommand(AddScreenAsync);
RemoveScreenCommand = new AsyncRelayCommand<string>(RemoveScreenByDisplayAsync);
AddMemoryCommand = new AsyncRelayCommand(AddMemoryAsync);
RemoveMemoryCommand = new AsyncRelayCommand<string>(RemoveMemoryByDisplayAsync);
AddStorageCommand = new AsyncRelayCommand(AddStorageAsync);
RemoveStorageCommand = new AsyncRelayCommand<string>(RemoveStorageByDisplayAsync);
AddSoftwarePlatformCommand = new AsyncRelayCommand(AddSoftwarePlatformAsync);
RemoveSoftwarePlatformCommand = new AsyncRelayCommand<string>(RemoveSoftwarePlatformByDisplayAsync);
OpenPhotosCommand = new RelayCommand<MachineDto>(OpenPhotos);
CheckAdminRole();
}
// --- Commands ---
public IAsyncRelayCommand LoadItemsCommand { get; }
public IRelayCommand OpenAddCommand { get; }
public IRelayCommand<MachineDto> OpenEditCommand { get; }
public IAsyncRelayCommand<MachineDto> DeleteCommand { get; }
public IAsyncRelayCommand SaveCommand { get; }
public IRelayCommand CancelEditCommand { get; }
public IAsyncRelayCommand AddGpuCommand { get; }
public IAsyncRelayCommand<string> RemoveGpuCommand { get; }
public IAsyncRelayCommand AddProcessorCommand { get; }
public IAsyncRelayCommand<string> RemoveProcessorCommand { get; }
public IAsyncRelayCommand AddSoundCommand { get; }
public IAsyncRelayCommand<string> RemoveSoundCommand { get; }
public IAsyncRelayCommand AddScreenCommand { get; }
public IAsyncRelayCommand<string> RemoveScreenCommand { get; }
public IAsyncRelayCommand AddMemoryCommand { get; }
public IAsyncRelayCommand<string> RemoveMemoryCommand { get; }
public IAsyncRelayCommand AddStorageCommand { get; }
public IAsyncRelayCommand<string> RemoveStorageCommand { get; }
public IAsyncRelayCommand AddSoftwarePlatformCommand { get; }
public IAsyncRelayCommand<string> RemoveSoftwarePlatformCommand { get; }
public IRelayCommand<MachineDto> OpenPhotosCommand { get; }
public bool IsNavigationTarget(NavigationContext navigationContext) => true;
public void OnNavigatedFrom(NavigationContext navigationContext) { }
public void OnNavigatedTo(NavigationContext navigationContext)
{
CheckAdminRole();
if(IsAdmin)
{
_ = LoadItemsCommand.ExecuteAsync(null);
_ = LoadPickerDataAsync();
}
}
private void CheckAdminRole()
{
try
{
string token = _tokenService.GetToken();
if(string.IsNullOrWhiteSpace(token)) { IsAdmin = false; return; }
IEnumerable<string> roles = _jwtService.GetRoles(token);
IsAdmin = roles.Contains("Uberadmin", StringComparer.OrdinalIgnoreCase) ||
roles.Contains("Admin", StringComparer.OrdinalIgnoreCase);
}
catch { IsAdmin = false; }
}
private async Task LoadItemsAsync()
{
try
{
IsLoading = true; HasError = false; ErrorMessage = string.Empty;
Machines.Clear();
List<MachineDto>? response = await _apiClient.Machines.GetAsync();
_allMachines = response;
if(response != null) foreach(MachineDto m in response) Machines.Add(m);
ApplyFilter();
IsDataLoaded = true;
}
catch(Exception ex)
{
_logger.LogError(ex, "Error loading machines");
ErrorMessage = _localizer["FailedToLoadMachines"];
HasError = true;
}
finally { IsLoading = false; }
}
private void OpenAdd()
{
_editingId = null;
EditPanelTitle = _localizer["AddMachineDialog_Title"];
ClearForm();
IsEditingExisting = false;
IsEditing = true;
}
private async void OpenEdit(MachineDto? item)
{
if(item?.Id == null) return;
try
{
MachineDto? full = await _apiClient.Machines[item.Id.Value].GetAsync();
if(full == null) return;
_editingId = item.Id;
EditPanelTitle = _localizer["EditMachineDialog_Title"];
PopulateForm(full);
await LoadAllJunctionsAsync(item.Id.Value);
IsEditingExisting = true;
IsEditing = true;
}
catch(Exception ex)
{
_logger.LogError(ex, "Error loading machine {Id}", item.Id);
ErrorMessage = _localizer["FailedToLoadMachines"];
HasError = true;
}
}
private async Task DeleteAsync(MachineDto? item)
{
if(item?.Id == null) return;
try
{
await _apiClient.Machines[item.Id.Value].DeleteAsync();
await LoadItemsAsync();
}
catch(Exception ex)
{
_logger.LogError(ex, "Error deleting machine {Id}", item.Id);
ErrorMessage = _localizer["FailedToDeleteMachine"];
HasError = true;
}
}
private async Task SaveAsync()
{
try
{
if(string.IsNullOrWhiteSpace(MachineName))
{
ErrorMessage = _localizer["NameIsRequired"];
HasError = true;
return;
}
if(SelectedCompany?.Id == null)
{
ErrorMessage = _localizer["MachineCompanyRequired"];
HasError = true;
return;
}
var dto = new MachineDto
{
Name = MachineName,
Model = string.IsNullOrWhiteSpace(Model) ? null : Model,
CompanyId = SelectedCompany.Id.Value,
Type = MachineTypeIndex,
Introduced = Introduced,
IntroducedPrecision = IntroducedPrecision,
FamilyId = SelectedFamily?.Id
};
if(_editingId == null)
await _apiClient.Machines.PostAsync(dto);
else
{
dto.Id = _editingId;
await _apiClient.Machines[_editingId.Value].PutAsync(dto);
}
IsEditing = false;
ClearForm();
await LoadItemsAsync();
}
catch(Exception ex)
{
_logger.LogError(ex, "Error saving machine");
ErrorMessage = _localizer["FailedToSaveMachine"];
HasError = true;
}
}
private void CancelEdit()
{
IsEditing = false; _editingId = null;
IsEditingExisting = false;
ClearForm();
HasError = false; ErrorMessage = string.Empty;
}
public void ApplyFilter()
{
FilteredMachines.Clear();
IEnumerable<MachineDto> source = (IEnumerable<MachineDto>?)_allMachines ?? Machines;
if(!string.IsNullOrWhiteSpace(FilterText))
source = source.Where(m => (m.Name != null && m.Name.Contains(FilterText, StringComparison.OrdinalIgnoreCase)) ||
(m.Company != null && m.Company.Contains(FilterText, StringComparison.OrdinalIgnoreCase)) ||
(m.Model != null && m.Model.Contains(FilterText, StringComparison.OrdinalIgnoreCase)));
foreach(MachineDto m in source) FilteredMachines.Add(m);
}
public void UpdateCompanySuggestions(string query)
{
CompanySuggestions.Clear();
if(_allCompanies == null) return;
IEnumerable<CompanyDto> source = _allCompanies;
if(!string.IsNullOrWhiteSpace(query))
source = source.Where(c => c.Name != null && c.Name.Contains(query, StringComparison.OrdinalIgnoreCase));
foreach(CompanyDto match in source) CompanySuggestions.Add(match);
}
public async Task LoadPickerDataAsync()
{
try { _allCompanies = await _apiClient.Companies.GetAsync(); }
catch(Exception ex) { _logger.LogError(ex, "Error loading companies"); }
try
{
List<MachineFamilyDto>? fams = await _apiClient.MachineFamilies.GetAsync();
Families.Clear();
if(fams != null) foreach(MachineFamilyDto f in fams) Families.Add(f);
}
catch(Exception ex) { _logger.LogError(ex, "Error loading families"); }
try { _allGpusList = await _apiClient.Gpus.GetAsync(); }
catch(Exception ex) { _logger.LogError(ex, "Error loading GPUs"); }
try { _allProcessorsList = await _apiClient.Processors.GetAsync(); }
catch(Exception ex) { _logger.LogError(ex, "Error loading processors"); }
try { _allSoundsList = await _apiClient.SoundSynths.GetAsync(); }
catch(Exception ex) { _logger.LogError(ex, "Error loading sound synths"); }
try { _allScreensList = await _apiClient.Screens.GetAsync(); }
catch(Exception ex) { _logger.LogError(ex, "Error loading screens"); }
try { _allSoftwarePlatformsList = await _apiClient.Software.Platforms.GetAsync(); }
catch(Exception ex) { _logger.LogError(ex, "Error loading software platforms"); }
}
// ======================== JUNCTION MANAGEMENT ========================
private async Task LoadAllJunctionsAsync(int machineId)
{
await Task.WhenAll(
LoadMachineGpusAsync(machineId),
LoadMachineProcessorsAsync(machineId),
LoadMachineSoundsAsync(machineId),
LoadMachineScreensAsync(machineId),
LoadMachineMemoriesAsync(machineId),
LoadMachineStorageAsync(machineId),
LoadMachineSoftwarePlatformsAsync(machineId)
);
}
// --- GPUs ---
private async Task LoadMachineGpusAsync(int machineId)
{
MachineGpus.Clear(); MachineGpuDisplays.Clear();
try
{
List<GpuByMachineDto>? items = await _apiClient.Machines.Gpus.ByMachine[machineId].GetAsync();
if(items != null) foreach(GpuByMachineDto g in items)
{
MachineGpus.Add(g);
MachineGpuDisplays.Add($"{g.Company} {g.Name}".Trim());
}
}
catch(Exception ex) { _logger.LogError(ex, "Error loading GPUs for machine"); }
RefreshAvailable(_allGpusList, MachineGpus.Select(g => g.GpuId), AvailableGpus);
}
private async Task AddGpuAsync()
{
if(_editingId == null || SelectedAvailableGpu?.Id == null) return;
try
{
await _apiClient.Machines.Gpus.PostAsync(new GpuByMachineDto { GpuId = SelectedAvailableGpu.Id, MachineId = _editingId });
SelectedAvailableGpu = null;
await LoadMachineGpusAsync(_editingId.Value);
}
catch(Exception ex) { _logger.LogError(ex, "Error adding GPU"); ErrorMessage = _localizer["FailedToSaveMachine"]; HasError = true; }
}
private async Task RemoveGpuByDisplayAsync(string? display)
{
if(display == null || _editingId == null) return;
int idx = MachineGpuDisplays.IndexOf(display);
if(idx >= 0 && idx < MachineGpus.Count && MachineGpus[idx].Id.HasValue)
{
try { await _apiClient.Machines.Gpus[MachineGpus[idx].Id.Value].DeleteAsync(); await LoadMachineGpusAsync(_editingId.Value); }
catch(Exception ex) { _logger.LogError(ex, "Error removing GPU"); }
}
}
// --- Processors ---
private async Task LoadMachineProcessorsAsync(int machineId)
{
MachineProcessors.Clear(); MachineProcessorDisplays.Clear();
try
{
List<ProcessorByMachineDto>? items = await _apiClient.ProcessorsByMachine.ByMachine[machineId].GetAsync();
if(items != null) foreach(ProcessorByMachineDto p in items)
{
MachineProcessors.Add(p);
string speed = p.Speed.HasValue ? $" @ {p.Speed}MHz" : "";
MachineProcessorDisplays.Add($"{p.Company} {p.Name}{speed}".Trim());
}
}
catch(Exception ex) { _logger.LogError(ex, "Error loading processors for machine"); }
RefreshAvailable(_allProcessorsList, MachineProcessors.Select(p => p.ProcessorId), AvailableProcessors);
}
private async Task AddProcessorAsync()
{
if(_editingId == null || SelectedAvailableProcessor?.Id == null) return;
try
{
await _apiClient.ProcessorsByMachine.PostAsync(new ProcessorByMachineDto
{
ProcessorId = SelectedAvailableProcessor.Id, MachineId = _editingId,
Speed = ProcessorSpeed.HasValue ? (float)ProcessorSpeed.Value : null
});
SelectedAvailableProcessor = null; ProcessorSpeed = null;
await LoadMachineProcessorsAsync(_editingId.Value);
}
catch(Exception ex) { _logger.LogError(ex, "Error adding processor"); ErrorMessage = _localizer["FailedToSaveMachine"]; HasError = true; }
}
private async Task RemoveProcessorByDisplayAsync(string? display)
{
if(display == null || _editingId == null) return;
int idx = MachineProcessorDisplays.IndexOf(display);
if(idx >= 0 && idx < MachineProcessors.Count && MachineProcessors[idx].Id.HasValue)
{
try { await _apiClient.ProcessorsByMachine[MachineProcessors[idx].Id.Value].DeleteAsync(); await LoadMachineProcessorsAsync(_editingId.Value); }
catch(Exception ex) { _logger.LogError(ex, "Error removing processor"); }
}
}
// --- Sound Synths ---
private async Task LoadMachineSoundsAsync(int machineId)
{
MachineSounds.Clear(); MachineSoundDisplays.Clear();
try
{
List<SoundSynthByMachineDto>? items = await _apiClient.SoundSynthsByMachine.ByMachine[machineId].GetAsync();
if(items != null) foreach(SoundSynthByMachineDto s in items)
{
MachineSounds.Add(s);
MachineSoundDisplays.Add($"{s.Company} {s.Name}".Trim());
}
}
catch(Exception ex) { _logger.LogError(ex, "Error loading sounds for machine"); }
RefreshAvailable(_allSoundsList, MachineSounds.Select(s => s.SoundSynthId), AvailableSounds);
}
private async Task AddSoundAsync()
{
if(_editingId == null || SelectedAvailableSound?.Id == null) return;
try
{
await _apiClient.SoundSynthsByMachine.PostAsync(new SoundSynthByMachineDto { SoundSynthId = SelectedAvailableSound.Id, MachineId = _editingId });
SelectedAvailableSound = null;
await LoadMachineSoundsAsync(_editingId.Value);
}
catch(Exception ex) { _logger.LogError(ex, "Error adding sound synth"); ErrorMessage = _localizer["FailedToSaveMachine"]; HasError = true; }
}
private async Task RemoveSoundByDisplayAsync(string? display)
{
if(display == null || _editingId == null) return;
int idx = MachineSoundDisplays.IndexOf(display);
if(idx >= 0 && idx < MachineSounds.Count && MachineSounds[idx].Id.HasValue)
{
try { await _apiClient.SoundSynthsByMachine[MachineSounds[idx].Id.Value].DeleteAsync(); await LoadMachineSoundsAsync(_editingId.Value); }
catch(Exception ex) { _logger.LogError(ex, "Error removing sound synth"); }
}
}
// --- Screens ---
private async Task LoadMachineScreensAsync(int machineId)
{
MachineScreens.Clear(); MachineScreenDisplays.Clear();
try
{
List<ScreenByMachineDto>? items = await _apiClient.Machines[machineId].Screens.GetAsync();
if(items != null) foreach(ScreenByMachineDto s in items)
{
MachineScreens.Add(s);
MachineScreenDisplays.Add($"{s.Screen?.ScreenDto?.Diagonal}\" {s.Screen?.ScreenDto?.Type}".Trim());
}
}
catch(Exception ex) { _logger.LogError(ex, "Error loading screens for machine"); }
RefreshAvailable(_allScreensList, MachineScreens.Select(s => s.ScreenId), AvailableScreens);
}
private async Task AddScreenAsync()
{
if(_editingId == null || SelectedAvailableScreen?.Id == null) return;
try
{
await _apiClient.ScreensByMachine.PostAsync(new ScreenByMachineDto { ScreenId = SelectedAvailableScreen.Id, MachineId = _editingId });
SelectedAvailableScreen = null;
await LoadMachineScreensAsync(_editingId.Value);
}
catch(Exception ex) { _logger.LogError(ex, "Error adding screen"); ErrorMessage = _localizer["FailedToSaveMachine"]; HasError = true; }
}
private async Task RemoveScreenByDisplayAsync(string? display)
{
if(display == null || _editingId == null) return;
int idx = MachineScreenDisplays.IndexOf(display);
if(idx >= 0 && idx < MachineScreens.Count && MachineScreens[idx].Id.HasValue)
{
try { await _apiClient.ScreensByMachine[MachineScreens[idx].Id.Value].DeleteAsync(); await LoadMachineScreensAsync(_editingId.Value); }
catch(Exception ex) { _logger.LogError(ex, "Error removing screen"); }
}
}
// --- Memory (inline creation) ---
private async Task LoadMachineMemoriesAsync(int machineId)
{
MachineMemories.Clear(); MachineMemoryDisplays.Clear();
try
{
List<MemoryByMachineDto>? items = await _apiClient.Machines[machineId].Memories.GetAsync();
if(items != null) foreach(MemoryByMachineDto m in items)
{
MachineMemories.Add(m);
string type = m.Type.HasValue ? ((MemoryType)m.Type.Value).Humanize() : "?";
string usage = m.Usage.HasValue ? ((MemoryUsage)m.Usage.Value).Humanize() : "?";
string size = m.Size.HasValue ? $" {FormatBytes(m.Size.Value)}" : "";
MachineMemoryDisplays.Add($"{type} ({usage}){size}");
}
}
catch(Exception ex) { _logger.LogError(ex, "Error loading memories for machine"); }
}
private async Task AddMemoryAsync()
{
if(_editingId == null) return;
try
{
await _apiClient.MemoriesByMachine.PostAsync(new MemoryByMachineDto
{
MachineId = _editingId, Type = MemoryTypeIndex, Usage = MemoryUsageIndex,
Size = MemorySize, Speed = MemorySpeed
});
MemorySize = null; MemorySpeed = null;
await LoadMachineMemoriesAsync(_editingId.Value);
}
catch(Exception ex) { _logger.LogError(ex, "Error adding memory"); ErrorMessage = _localizer["FailedToSaveMachine"]; HasError = true; }
}
private async Task RemoveMemoryByDisplayAsync(string? display)
{
if(display == null || _editingId == null) return;
int idx = MachineMemoryDisplays.IndexOf(display);
if(idx >= 0 && idx < MachineMemories.Count && MachineMemories[idx].Id.HasValue)
{
try { await _apiClient.MemoriesByMachine[MachineMemories[idx].Id.Value].DeleteAsync(); await LoadMachineMemoriesAsync(_editingId.Value); }
catch(Exception ex) { _logger.LogError(ex, "Error removing memory"); }
}
}
// --- Storage (inline creation) ---
private async Task LoadMachineStorageAsync(int machineId)
{
MachineStorage.Clear(); MachineStorageDisplays.Clear();
try
{
List<StorageByMachineDto>? items = await _apiClient.Machines[machineId].Storage.GetAsync();
if(items != null) foreach(StorageByMachineDto s in items)
{
MachineStorage.Add(s);
string type = s.Type.HasValue ? ((StorageType)s.Type.Value).Humanize() : $"Type {s.Type}";
string iface = s.Interface.HasValue ? ((StorageInterface)s.Interface.Value).Humanize() : $"If {s.Interface}";
string cap = s.Capacity.HasValue ? $" {FormatBytes(s.Capacity.Value)}" : "";
MachineStorageDisplays.Add($"{type} ({iface}){cap}");
}
}
catch(Exception ex) { _logger.LogError(ex, "Error loading storage for machine"); }
}
private async Task AddStorageAsync()
{
if(_editingId == null) return;
try
{
await _apiClient.StorageByMachine.PostAsync(new StorageByMachineDto
{
MachineId = _editingId,
Type = StorageTypeIndex < StorageTypeValues.Length ? StorageTypeValues[StorageTypeIndex] : 0,
Interface = StorageInterfaceIndex < StorageInterfaceValues.Length ? StorageInterfaceValues[StorageInterfaceIndex] : 0,
Capacity = StorageCapacity
});
StorageCapacity = null;
await LoadMachineStorageAsync(_editingId.Value);
}
catch(Exception ex) { _logger.LogError(ex, "Error adding storage"); ErrorMessage = _localizer["FailedToSaveMachine"]; HasError = true; }
}
private async Task RemoveStorageByDisplayAsync(string? display)
{
if(display == null || _editingId == null) return;
int idx = MachineStorageDisplays.IndexOf(display);
if(idx >= 0 && idx < MachineStorage.Count && MachineStorage[idx].Id.HasValue)
{
try { await _apiClient.StorageByMachine[MachineStorage[idx].Id.Value].DeleteAsync(); await LoadMachineStorageAsync(_editingId.Value); }
catch(Exception ex) { _logger.LogError(ex, "Error removing storage"); }
}
}
// --- Software Platforms ---
private async Task LoadMachineSoftwarePlatformsAsync(int machineId)
{
MachineSoftwarePlatforms.Clear(); MachineSoftwarePlatformDisplays.Clear();
try
{
List<SoftwarePlatformByMachineDto>? items = await _apiClient.SoftwarePlatformsByMachine.ByMachine[machineId].GetAsync();
if(items != null) foreach(SoftwarePlatformByMachineDto sp in items)
{
MachineSoftwarePlatforms.Add(sp);
MachineSoftwarePlatformDisplays.Add(sp.Name ?? "?");
}
}
catch(Exception ex) { _logger.LogError(ex, "Error loading software platforms for machine"); }
RefreshAvailable(_allSoftwarePlatformsList, MachineSoftwarePlatforms.Select(sp => sp.SoftwarePlatformId), AvailableSoftwarePlatforms);
}
private async Task AddSoftwarePlatformAsync()
{
if(_editingId == null || SelectedAvailableSoftwarePlatform?.Id == null) return;
try
{
await _apiClient.SoftwarePlatformsByMachine.PostAsync(new SoftwarePlatformByMachineDto { SoftwarePlatformId = SelectedAvailableSoftwarePlatform.Id, MachineId = _editingId });
SelectedAvailableSoftwarePlatform = null;
await LoadMachineSoftwarePlatformsAsync(_editingId.Value);
}
catch(Exception ex) { _logger.LogError(ex, "Error adding software platform"); ErrorMessage = _localizer["FailedToSaveMachine"]; HasError = true; }
}
private async Task RemoveSoftwarePlatformByDisplayAsync(string? display)
{
if(display == null || _editingId == null) return;
int idx = MachineSoftwarePlatformDisplays.IndexOf(display);
if(idx >= 0 && idx < MachineSoftwarePlatforms.Count && MachineSoftwarePlatforms[idx].Id.HasValue)
{
try { await _apiClient.SoftwarePlatformsByMachine[MachineSoftwarePlatforms[idx].Id.Value].DeleteAsync(); await LoadMachineSoftwarePlatformsAsync(_editingId.Value); }
catch(Exception ex) { _logger.LogError(ex, "Error removing software platform"); }
}
}
// ======================== HELPERS ========================
private static void RefreshAvailable<TDto>(List<TDto>? all, IEnumerable<int?> assignedIds, ObservableCollection<TDto> available)
where TDto : class
{
available.Clear();
if(all == null) return;
HashSet<int> ids = new(assignedIds.Where(i => i.HasValue).Select(i => i!.Value));
foreach(TDto item in all)
{
int? id = item switch
{
GpuDto g => g.Id,
ProcessorDto p => p.Id,
SoundSynthDto s => s.Id,
ScreenDto sc => sc.Id,
SoftwarePlatformDto sp => sp.Id,
_ => null
};
if(id.HasValue && !ids.Contains(id.Value))
available.Add(item);
}
}
private static string FormatBytes(long bytes)
{
if(bytes >= 1073741824) return $"{bytes / 1073741824.0:F1} GiB";
if(bytes >= 1048576) return $"{bytes / 1048576.0:F1} MiB";
if(bytes >= 1024) return $"{bytes / 1024.0:F1} KiB";
return $"{bytes} B";
}
private void ClearForm()
{
MachineName = string.Empty; Model = string.Empty; MachineTypeIndex = 0; Introduced = null;
IntroducedPrecision = 0;
SelectedCompany = null; CompanySearchText = string.Empty; SelectedFamily = null;
MachineGpus.Clear(); MachineGpuDisplays.Clear(); AvailableGpus.Clear(); SelectedAvailableGpu = null;
MachineProcessors.Clear(); MachineProcessorDisplays.Clear(); AvailableProcessors.Clear(); SelectedAvailableProcessor = null; ProcessorSpeed = null;
MachineSounds.Clear(); MachineSoundDisplays.Clear(); AvailableSounds.Clear(); SelectedAvailableSound = null;
MachineScreens.Clear(); MachineScreenDisplays.Clear(); AvailableScreens.Clear(); SelectedAvailableScreen = null;
MachineMemories.Clear(); MachineMemoryDisplays.Clear(); MemoryTypeIndex = 0; MemoryUsageIndex = 0; MemorySize = null; MemorySpeed = null;
MachineStorage.Clear(); MachineStorageDisplays.Clear(); StorageTypeIndex = 0; StorageInterfaceIndex = 0; StorageCapacity = null;
MachineSoftwarePlatforms.Clear(); MachineSoftwarePlatformDisplays.Clear(); AvailableSoftwarePlatforms.Clear(); SelectedAvailableSoftwarePlatform = null;
HasError = false; ErrorMessage = string.Empty;
}
private void PopulateForm(MachineDto machine)
{
MachineName = machine.Name ?? string.Empty;
Model = machine.Model ?? string.Empty;
MachineTypeIndex = machine.Type ?? 0;
Introduced = machine.Introduced;
if(machine.CompanyId is > 0 && _allCompanies != null)
{
CompanyDto? c = _allCompanies.FirstOrDefault(x => x.Id == machine.CompanyId);
if(c != null) { CompanySearchText = c.Name ?? string.Empty; UpdateCompanySuggestions(CompanySearchText); SelectedCompany = CompanySuggestions.FirstOrDefault(x => x.Id == c.Id); }
else { CompanySearchText = machine.Company ?? string.Empty; SelectedCompany = null; }
}
else { CompanySearchText = string.Empty; SelectedCompany = null; }
SelectedFamily = machine.FamilyId.HasValue ? Families.FirstOrDefault(f => f.Id == machine.FamilyId.Value) : null;
}
private void OpenPhotos(MachineDto? machine)
{
if(machine?.Id == null) return;
var parameters = new NavigationParameters
{
{ NavParamKeys.MachineId, machine.Id.Value },
{ NavParamKeys.MachineName, machine.Name ?? string.Empty }
};
_regionManager.RequestNavigate(RegionNames.Content, nameof(AdminMachinePhotosPage), parameters);
}
}