Utopia-Canteen-System/UtopiaCanteen.Client/ViewModels/ClientSettingsViewModel.cs

384 lines
12 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 System.Diagnostics;
using System.IO;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using UtopiaCanteenSystem.Services;
using UtopiaCanteenSystem.Services.Logging;
namespace UtopiaCanteen.Client.ViewModels;
public partial class ClientSettingsViewModel : ObservableObject
{
public const string BackendUnavailableMessage =
"Backend server unavailable. Please check Backend Server Base URL or server service.";
private readonly IConfigService _configService;
private readonly INavigationService _navigation;
private readonly ICanteenBackendApiClient _backendApi;
private readonly AppSession _session;
[ObservableProperty]
private string _scanIntervalDays = "0";
[ObservableProperty]
private string _scanIntervalHours = "0";
[ObservableProperty]
private string _scanIntervalMinutes = "0";
[ObservableProperty]
private string _scanIntervalSeconds = "5";
[ObservableProperty]
private string _adminCardId = "ADMIN";
[ObservableProperty]
private string _applicationModeDisplay = "Client";
[ObservableProperty]
private string _backendBaseUrl = string.Empty;
[ObservableProperty]
private string _deviceId = string.Empty;
[ObservableProperty]
private string _siteId = string.Empty;
[ObservableProperty]
private string _backendHealthDisplay = "Checking backend...";
[ObservableProperty]
private bool _isBackendOnline;
[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";
public bool HasBackendUrl => !string.IsNullOrWhiteSpace(BackendBaseUrl?.Trim());
public bool CanPostNow => !IsPosting && HasBackendUrl;
public string PostButtonText => IsPosting ? "Posting..." : "Post Data";
public bool CanSyncCacheNow => !IsSyncingCache && HasBackendUrl;
public string SyncCacheButtonText => IsSyncingCache ? "Syncing..." : "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 OnBackendBaseUrlChanged(string value)
{
OnPropertyChanged(nameof(HasBackendUrl));
OnPropertyChanged(nameof(CanPostNow));
OnPropertyChanged(nameof(CanSyncCacheNow));
}
public ClientSettingsViewModel(
IConfigService configService,
INavigationService navigation,
ICanteenBackendApiClient backendApi,
AppSession session)
{
_configService = configService;
_navigation = navigation;
_backendApi = backendApi;
_session = session;
LoadFromConfig();
_ = InitializeAsync();
}
public void LoadFromConfig()
{
ScanIntervalDays = _configService.GetScanIntervalDays().ToString();
ScanIntervalHours = _configService.GetScanIntervalHours().ToString();
ScanIntervalMinutes = _configService.GetScanIntervalMinutes().ToString();
ScanIntervalSeconds = _configService.GetScanIntervalSeconds().ToString();
AdminCardId = AdminLoginHelper.ResolveAdminCardIdForDisplay(_configService, _session);
ApplicationModeDisplay = "Client";
BackendBaseUrl = _configService.GetBackendBaseUrl();
DeviceId = _configService.GetDeviceId();
SiteId = AdminLoginHelper.FormatSiteIdForDisplay(_configService.GetSiteId());
OnPropertyChanged(nameof(HasBackendUrl));
}
public async Task InitializeAsync()
{
await RefreshBackendStatusAsync().ConfigureAwait(true);
await RefreshCacheSyncTimestampsAsync().ConfigureAwait(true);
}
public async Task RefreshBackendStatusAsync()
{
if (!HasBackendUrl)
{
IsBackendOnline = false;
BackendHealthDisplay = "Backend URL not configured.";
return;
}
try
{
var health = await _backendApi.GetHealthAsync().ConfigureAwait(true);
if (health != null && string.Equals(health.Status, "ok", StringComparison.OrdinalIgnoreCase))
{
IsBackendOnline = true;
BackendHealthDisplay = $"Backend online ({health.Mode}) — {health.Utc.ToLocalTime():MM/dd/yyyy hh:mm:ss tt}";
}
else
{
IsBackendOnline = false;
BackendHealthDisplay = BackendUnavailableMessage;
}
}
catch (Exception ex)
{
Logger.Log(ex, "ClientSettingsViewModel.RefreshBackendStatusAsync");
IsBackendOnline = false;
BackendHealthDisplay = BackendUnavailableMessage;
}
}
public async Task RefreshCacheSyncTimestampsAsync()
{
if (!HasBackendUrl || !IsBackendOnline)
return;
var status = await _backendApi.GetCacheStatusAsync().ConfigureAwait(true);
if (status == null)
return;
LastEmployeeRfidCacheSyncDisplay = FormatSyncTime(status.LastEmployeeRfidCacheSyncUtc);
LastMealMenuCacheSyncDisplay = FormatSyncTime(status.LastMealMenuCacheSyncUtc);
}
[RelayCommand]
private async Task Save()
{
IsSaving = true;
SaveMessage = string.Empty;
IsError = false;
try
{
if (!int.TryParse(ScanIntervalDays, out var days) || days < 0 || days > 365)
{
SetSaveError("Scan interval Days must be 0365.");
return;
}
if (!int.TryParse(ScanIntervalHours, out var hours) || hours < 0 || hours > 23)
{
SetSaveError("Scan interval Hours must be 023.");
return;
}
if (!int.TryParse(ScanIntervalMinutes, out var minutes) || minutes < 0 || minutes > 59)
{
SetSaveError("Scan interval Minutes must be 059.");
return;
}
if (!int.TryParse(ScanIntervalSeconds, out var seconds) || seconds < 0 || seconds > 59)
{
SetSaveError("Scan interval Seconds must be 059.");
return;
}
if (days == 0 && hours == 0 && minutes == 0 && seconds == 0)
{
SetSaveError("Scan interval cannot be zero. At least 1 second required.");
return;
}
if (string.IsNullOrWhiteSpace(BackendBaseUrl?.Trim()))
{
SetSaveError("Backend Server Base URL is required.");
return;
}
_configService.SetScanIntervalDays(days);
_configService.SetScanIntervalHours(hours);
_configService.SetScanIntervalMinutes(minutes);
_configService.SetScanIntervalSeconds(seconds);
_configService.SetAdminCardId(AdminCardId?.Trim() ?? string.Empty);
FileLogger.Info(
"ClientSettings",
$"Settings saved. AdminCardId={AdminCardId?.Trim()}, SiteId={SiteId?.Trim()}, DeviceId={DeviceId?.Trim()}");
_configService.SetCentralServerBaseUrl(BackendBaseUrl.Trim());
_configService.SetDeviceId(DeviceId?.Trim() ?? string.Empty);
var site = SiteId?.Trim() ?? string.Empty;
_configService.SetSiteId(
string.IsNullOrEmpty(site)
? string.Empty
: AdminLoginHelper.FormatSiteIdForDisplay(site));
SaveMessage = "Settings saved.";
IsError = false;
await RefreshBackendStatusAsync().ConfigureAwait(true);
}
finally
{
IsSaving = false;
}
}
[RelayCommand]
private void Back() => _navigation.NavigateBackFromSettings(_configService.GetScanInterval());
[RelayCommand]
private void OpenMealSchedules() => _navigation.NavigateToMealSchedules();
[RelayCommand]
private void OpenLogsFolder()
{
try
{
var dir = LogPaths.ClientLogsDirectory;
Directory.CreateDirectory(dir);
Process.Start(new ProcessStartInfo
{
FileName = dir,
UseShellExecute = true
});
FileLogger.Info("ClientSettings", $"Opened logs folder: {dir}");
}
catch (Exception ex)
{
FileLogger.Error("ClientSettings", "Failed to open logs folder.", ex);
SaveMessage = "Could not open logs folder: " + ex.Message;
IsError = true;
}
}
[RelayCommand]
private async Task SyncEmployeeAndMenuCacheNow()
{
if (IsSyncingCache)
return;
SaveMessage = "Syncing...";
IsError = false;
IsSyncingCache = true;
try
{
FileLogger.Info("ClientSettings", "Sync Now clicked.");
if (!await EnsureBackendAvailableAsync().ConfigureAwait(true))
return;
var url = $"{_configService.GetBackendBaseUrl().TrimEnd('/')}/api/cache/sync-now";
FileLogger.Info("ClientSettings", $"Calling POST {url}");
var result = await _backendApi.SyncCacheNowAsync().ConfigureAwait(true);
await RefreshCacheSyncTimestampsAsync().ConfigureAwait(true);
SaveMessage = result.Message;
IsError = !result.Success;
FileLogger.Info(
"ClientSettings",
$"Sync Now response. Success={result.Success}, Message={result.Message}, Details={result.Details}");
if (!string.IsNullOrWhiteSpace(result.Details) && result.Success)
SaveMessage += " " + result.Details;
}
catch (Exception ex)
{
Logger.Log(ex, "ClientSettingsViewModel.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
{
FileLogger.Info("ClientSettings", "Post Data clicked.");
if (!await EnsureBackendAvailableAsync().ConfigureAwait(true))
return;
var url = $"{_configService.GetBackendBaseUrl().TrimEnd('/')}/api/orders/sync-now";
FileLogger.Info("ClientSettings", $"Calling POST {url}");
var result = await _backendApi.SyncOrdersNowAsync().ConfigureAwait(true);
SaveMessage = result.Message;
IsError = !result.Success;
FileLogger.Info(
"ClientSettings",
$"Post Data response. Success={result.Success}, Message={result.Message}, Details={result.Details}");
if (!string.IsNullOrWhiteSpace(result.Details) && result.Success)
SaveMessage += " " + result.Details;
}
catch (Exception ex)
{
Logger.Log(ex, "ClientSettingsViewModel.PostDataNow");
SaveMessage = "Failed to post data: " + ex.Message;
IsError = true;
}
finally
{
IsPosting = false;
}
}
private async Task<bool> EnsureBackendAvailableAsync()
{
if (!HasBackendUrl)
{
SaveMessage = "Backend URL is not configured.";
IsError = true;
return false;
}
await RefreshBackendStatusAsync().ConfigureAwait(true);
if (IsBackendOnline)
return true;
SaveMessage = BackendUnavailableMessage;
IsError = true;
FileLogger.Warn("ClientSettings", $"Backend unavailable. Url={_configService.GetBackendBaseUrl()}");
return false;
}
private void SetSaveError(string message)
{
SaveMessage = message;
IsError = true;
}
private static string FormatSyncTime(DateTime? utc) =>
utc == null ? "Never" : utc.Value.ToLocalTime().ToString("MM/dd/yyyy, hh:mm:ss tt");
}