Utopia-Canteen-System/ViewModels/SettingsViewModel.cs

344 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using UtopiaCanteenSystem.Models;
using UtopiaCanteenSystem.Services;
namespace UtopiaCanteenSystem.ViewModels;
/// <summary>
/// Settings UI (frontend): persists local kiosk config; cache/production jobs call backend APIs only.
/// </summary>
public partial class SettingsViewModel : ObservableObject
{
private readonly IConfigService _configService;
private readonly INavigationService _navigation;
private readonly IAdminAuditService _adminAudit;
private readonly ICanteenBackendApiClient _backendApi;
[ObservableProperty]
private string _syncApiEndpoint = string.Empty;
[ObservableProperty]
private string _scanIntervalDays = "0";
[ObservableProperty]
private string _scanIntervalHours = "0";
[ObservableProperty]
private string _scanIntervalMinutes = "1";
[ObservableProperty]
private string _scanIntervalSeconds = "0";
[ObservableProperty]
private string _adminCardId = "ADMIN";
[ObservableProperty]
private string _saveMessage = string.Empty;
[ObservableProperty]
private bool _isError;
[ObservableProperty]
private bool _isSaving;
[ObservableProperty]
private bool _isPosting;
[ObservableProperty]
private bool _isSyncingCache;
[ObservableProperty]
private string _lastEmployeeRfidCacheSyncDisplay = "Never";
[ObservableProperty]
private string _lastMealMenuCacheSyncDisplay = "Never";
[ObservableProperty]
private string _selectedAppMode = "Server";
[ObservableProperty]
private string _backendBaseUrl = string.Empty;
[ObservableProperty]
private string _localServerListenUrls = "http://0.0.0.0:5000";
public IReadOnlyList<string> AppModeOptions { get; } = new[] { "Server", "Client" };
public bool IsCentralServerMode => string.Equals(SelectedAppMode, "Server", StringComparison.OrdinalIgnoreCase);
public string EffectiveBackendUrlDisplay =>
IsCentralServerMode
? _configService.GetBackendBaseUrl()
: (BackendBaseUrl?.Trim() ?? string.Empty);
public bool HasBackendUrl => !string.IsNullOrWhiteSpace(_configService.GetBackendBaseUrl());
public bool CanPostNow => !IsPosting && HasBackendUrl;
public string PostButtonText => IsPosting ? "Posting..." : "Post Pending Orders";
public bool CanSyncCacheNow => !IsSyncingCache && HasBackendUrl;
public string SyncCacheButtonText => IsSyncingCache ? "Syncing cache..." : "Sync Now";
partial void OnIsPostingChanged(bool value)
{
OnPropertyChanged(nameof(PostButtonText));
OnPropertyChanged(nameof(CanPostNow));
}
partial void OnIsSyncingCacheChanged(bool value)
{
OnPropertyChanged(nameof(SyncCacheButtonText));
OnPropertyChanged(nameof(CanSyncCacheNow));
}
partial void OnSelectedAppModeChanged(string value)
{
OnPropertyChanged(nameof(IsCentralServerMode));
OnPropertyChanged(nameof(EffectiveBackendUrlDisplay));
OnPropertyChanged(nameof(CanPostNow));
OnPropertyChanged(nameof(CanSyncCacheNow));
}
partial void OnBackendBaseUrlChanged(string value)
{
OnPropertyChanged(nameof(HasBackendUrl));
OnPropertyChanged(nameof(CanPostNow));
OnPropertyChanged(nameof(CanSyncCacheNow));
}
public SettingsViewModel(
IConfigService configService,
INavigationService navigation,
IAdminAuditService adminAudit,
ICanteenBackendApiClient backendApi)
{
_configService = configService;
_navigation = navigation;
_adminAudit = adminAudit;
_backendApi = backendApi;
LoadFromConfig();
_ = RefreshCacheSyncTimestampsAsync();
}
public void LoadFromConfig()
{
SyncApiEndpoint = _configService.GetSyncApiEndpoint();
ScanIntervalDays = _configService.GetScanIntervalDays().ToString();
ScanIntervalHours = _configService.GetScanIntervalHours().ToString();
ScanIntervalMinutes = _configService.GetScanIntervalMinutes().ToString();
ScanIntervalSeconds = _configService.GetScanIntervalSeconds().ToString();
AdminCardId = AdminLoginHelper.ResolveAdminCardIdForDisplay(_configService, adminAudit: _adminAudit);
SelectedAppMode = _configService.GetAppMode() == AppMode.Client ? "Client" : "Server";
BackendBaseUrl = string.IsNullOrWhiteSpace(_configService.GetCentralServerBaseUrl())
? _configService.GetBackendBaseUrl()
: _configService.GetCentralServerBaseUrl();
LocalServerListenUrls = _configService.GetLocalServerListenUrls();
OnPropertyChanged(nameof(EffectiveBackendUrlDisplay));
OnPropertyChanged(nameof(HasBackendUrl));
}
public async Task RefreshCacheSyncTimestampsAsync()
{
if (IsCentralServerMode)
{
LastEmployeeRfidCacheSyncDisplay = FormatSyncTime(_configService.GetLastEmployeeRfidCacheSyncUtc());
LastMealMenuCacheSyncDisplay = FormatSyncTime(_configService.GetLastMealMenuCacheSyncUtc());
return;
}
var status = await _backendApi.GetCacheStatusAsync().ConfigureAwait(true);
if (status != null)
{
LastEmployeeRfidCacheSyncDisplay = FormatSyncTime(status.LastEmployeeRfidCacheSyncUtc);
LastMealMenuCacheSyncDisplay = FormatSyncTime(status.LastMealMenuCacheSyncUtc);
}
}
public void RefreshCacheSyncTimestamps()
{
_ = RefreshCacheSyncTimestampsAsync();
}
[RelayCommand]
private void Save()
{
IsSaving = true;
if (!int.TryParse(ScanIntervalDays, out var days) || days < 0 || days > 365)
{
SaveMessage = "Scan interval Days must be 0365.";
IsError = true;
IsSaving = false;
return;
}
if (!int.TryParse(ScanIntervalHours, out var hours) || hours < 0 || hours > 23)
{
SaveMessage = "Scan interval Hours must be 023.";
IsError = true;
IsSaving = false;
return;
}
if (!int.TryParse(ScanIntervalMinutes, out var minutes) || minutes < 0 || minutes > 59)
{
SaveMessage = "Scan interval Minutes must be 059.";
IsError = true;
IsSaving = false;
return;
}
if (!int.TryParse(ScanIntervalSeconds, out var seconds) || seconds < 0 || seconds > 59)
{
SaveMessage = "Scan interval Seconds must be 059.";
IsError = true;
IsSaving = false;
return;
}
if (days == 0 && hours == 0 && minutes == 0 && seconds == 0)
{
SaveMessage = "Scan interval cannot be zero. Use at least 1 second.";
IsError = true;
IsSaving = false;
return;
}
if (string.Equals(SelectedAppMode, "Client", StringComparison.OrdinalIgnoreCase) &&
string.IsNullOrWhiteSpace(BackendBaseUrl?.Trim()))
{
SaveMessage = "Client mode requires Backend base URL (e.g. http://192.168.1.10:5000).";
IsError = true;
IsSaving = false;
return;
}
_configService.SetAppMode(string.Equals(SelectedAppMode, "Client", StringComparison.OrdinalIgnoreCase)
? AppMode.Client
: AppMode.Server);
_configService.SetCentralServerBaseUrl(BackendBaseUrl?.Trim() ?? string.Empty);
_configService.SetLocalServerListenUrls(LocalServerListenUrls?.Trim() ?? string.Empty);
_configService.SetSyncApiEndpoint(SyncApiEndpoint?.Trim() ?? string.Empty);
_configService.SetScanIntervalDays(days);
_configService.SetScanIntervalHours(hours);
_configService.SetScanIntervalMinutes(minutes);
_configService.SetScanIntervalSeconds(seconds);
_configService.SetAdminCardId(AdminCardId?.Trim() ?? string.Empty);
SaveMessage = "Settings saved. Restart the app after changing App mode or listen URLs.";
IsError = false;
IsSaving = false;
OnPropertyChanged(nameof(EffectiveBackendUrlDisplay));
OnPropertyChanged(nameof(HasBackendUrl));
}
[RelayCommand]
private void Back()
{
_navigation.NavigateBackFromSettings(GetDashboardTimeout());
}
[RelayCommand]
private void OpenMealSchedules()
{
_navigation.NavigateToMealSchedules();
}
[RelayCommand]
private async Task SyncEmployeeAndMenuCacheNow()
{
if (IsSyncingCache)
return;
SaveMessage = "Syncing cache...";
IsError = false;
IsSyncingCache = true;
try
{
if (string.IsNullOrWhiteSpace(_configService.GetBackendBaseUrl()))
{
SaveMessage = "Backend URL is not configured.";
IsError = true;
return;
}
if (!await _backendApi.HealthCheckAsync().ConfigureAwait(true))
{
SaveMessage = "Cannot reach backend. Ensure the central server app is running and API is listening.";
IsError = true;
return;
}
var result = await _backendApi.SyncCacheNowAsync().ConfigureAwait(true);
await RefreshCacheSyncTimestampsAsync().ConfigureAwait(true);
SaveMessage = result.Message;
IsError = !result.Success;
if (!string.IsNullOrWhiteSpace(result.Details) && result.Success)
SaveMessage += " " + result.Details;
}
catch (Exception ex)
{
Logger.Log(ex, "SettingsViewModel.SyncEmployeeAndMenuCacheNow");
SaveMessage = "Cache sync failed: " + ex.Message;
IsError = true;
}
finally
{
IsSyncingCache = false;
}
}
[RelayCommand]
private async Task PostDataNow()
{
if (IsPosting)
return;
SaveMessage = string.Empty;
IsError = false;
IsPosting = true;
try
{
if (string.IsNullOrWhiteSpace(_configService.GetBackendBaseUrl()))
{
SaveMessage = "Backend URL is not configured.";
IsError = true;
return;
}
if (!await _backendApi.HealthCheckAsync().ConfigureAwait(true))
{
SaveMessage = "Cannot reach backend. Ensure the central server app is running.";
IsError = true;
return;
}
var result = await _backendApi.SyncOrdersNowAsync().ConfigureAwait(true);
SaveMessage = result.Message;
IsError = !result.Success;
if (!string.IsNullOrWhiteSpace(result.Details) && result.Success)
SaveMessage += " " + result.Details;
}
catch (Exception ex)
{
Logger.Log(ex, "SettingsViewModel.PostDataNow");
SaveMessage = "Failed to post pending orders: " + ex.Message;
IsError = true;
}
finally
{
IsPosting = false;
}
}
private static string FormatSyncTime(DateTime? utc)
{
if (utc == null)
return "Never";
return utc.Value.ToLocalTime().ToString("MM/dd/yyyy, hh:mm:ss tt");
}
private TimeSpan GetDashboardTimeout() => _configService.GetScanInterval();
}