Update settings screen for backend operations
Updates settings to work through the backend API for cache sync, order posting, backend health checks, backend URL display, and server/client mode configuration.feature/centralized-offline-canteen
parent
659c162c93
commit
e59a3f8af6
|
|
@ -6,15 +6,14 @@ using UtopiaCanteenSystem.Services;
|
|||
namespace UtopiaCanteenSystem.ViewModels;
|
||||
|
||||
/// <summary>
|
||||
/// ViewModel for SettingsView: scan settings, production post, and offline cache sync.
|
||||
/// 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 ISyncService _syncService;
|
||||
private readonly IOfflineCacheSyncService _offlineCacheSyncService;
|
||||
private readonly ICanteenBackendApiClient _backendApi;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _syncApiEndpoint = string.Empty;
|
||||
|
|
@ -59,20 +58,26 @@ public partial class SettingsViewModel : ObservableObject
|
|||
private string _selectedAppMode = "Server";
|
||||
|
||||
[ObservableProperty]
|
||||
private string _centralServerBaseUrl = string.Empty;
|
||||
private string _backendBaseUrl = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _localServerListenUrls = "http://0.0.0.0:5000";
|
||||
|
||||
public IReadOnlyList<string> AppModeOptions { get; } = new[] { "Server", "Client" };
|
||||
|
||||
/// <summary>Central PC: runs SQLite, sync jobs, and Kestrel API.</summary>
|
||||
public bool IsCentralServerMode => string.Equals(SelectedAppMode, "Server", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
public bool CanPostNow => !IsPosting && IsCentralServerMode;
|
||||
public string PostButtonText => IsPosting ? "Posting..." : "Post Data";
|
||||
public string EffectiveBackendUrlDisplay =>
|
||||
IsCentralServerMode
|
||||
? _configService.GetBackendBaseUrl()
|
||||
: (BackendBaseUrl?.Trim() ?? string.Empty);
|
||||
|
||||
public bool CanSyncCacheNow => !IsSyncingCache && IsCentralServerMode;
|
||||
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)
|
||||
|
|
@ -90,6 +95,14 @@ public partial class SettingsViewModel : ObservableObject
|
|||
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));
|
||||
}
|
||||
|
|
@ -98,16 +111,14 @@ public partial class SettingsViewModel : ObservableObject
|
|||
IConfigService configService,
|
||||
INavigationService navigation,
|
||||
IAdminAuditService adminAudit,
|
||||
ISyncService syncService,
|
||||
IOfflineCacheSyncService offlineCacheSyncService)
|
||||
ICanteenBackendApiClient backendApi)
|
||||
{
|
||||
_configService = configService;
|
||||
_navigation = navigation;
|
||||
_adminAudit = adminAudit;
|
||||
_syncService = syncService;
|
||||
_offlineCacheSyncService = offlineCacheSyncService;
|
||||
_backendApi = backendApi;
|
||||
LoadFromConfig();
|
||||
RefreshCacheSyncTimestamps();
|
||||
_ = RefreshCacheSyncTimestampsAsync();
|
||||
}
|
||||
|
||||
public void LoadFromConfig()
|
||||
|
|
@ -118,21 +129,37 @@ public partial class SettingsViewModel : ObservableObject
|
|||
ScanIntervalMinutes = _configService.GetScanIntervalMinutes().ToString();
|
||||
ScanIntervalSeconds = _configService.GetScanIntervalSeconds().ToString();
|
||||
|
||||
var last = _adminAudit.GetLastLogin();
|
||||
if (last != null && !string.IsNullOrWhiteSpace(last.EmployeeId))
|
||||
AdminCardId = last.EmployeeId;
|
||||
else
|
||||
AdminCardId = "ADMIN";
|
||||
AdminCardId = AdminLoginHelper.ResolveAdminCardIdForDisplay(_configService, adminAudit: _adminAudit);
|
||||
|
||||
SelectedAppMode = _configService.GetAppMode() == AppMode.Client ? "Client" : "Server";
|
||||
CentralServerBaseUrl = _configService.GetCentralServerBaseUrl();
|
||||
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()
|
||||
{
|
||||
LastEmployeeRfidCacheSyncDisplay = FormatSyncTime(_configService.GetLastEmployeeRfidCacheSyncUtc());
|
||||
LastMealMenuCacheSyncDisplay = FormatSyncTime(_configService.GetLastMealMenuCacheSyncUtc());
|
||||
_ = RefreshCacheSyncTimestampsAsync();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
|
|
@ -176,9 +203,9 @@ public partial class SettingsViewModel : ObservableObject
|
|||
}
|
||||
|
||||
if (string.Equals(SelectedAppMode, "Client", StringComparison.OrdinalIgnoreCase) &&
|
||||
string.IsNullOrWhiteSpace(CentralServerBaseUrl?.Trim()))
|
||||
string.IsNullOrWhiteSpace(BackendBaseUrl?.Trim()))
|
||||
{
|
||||
SaveMessage = "Client mode requires Central server base URL (e.g. http://192.168.1.10:5000).";
|
||||
SaveMessage = "Client mode requires Backend base URL (e.g. http://192.168.1.10:5000).";
|
||||
IsError = true;
|
||||
IsSaving = false;
|
||||
return;
|
||||
|
|
@ -187,7 +214,7 @@ public partial class SettingsViewModel : ObservableObject
|
|||
_configService.SetAppMode(string.Equals(SelectedAppMode, "Client", StringComparison.OrdinalIgnoreCase)
|
||||
? AppMode.Client
|
||||
: AppMode.Server);
|
||||
_configService.SetCentralServerBaseUrl(CentralServerBaseUrl?.Trim() ?? string.Empty);
|
||||
_configService.SetCentralServerBaseUrl(BackendBaseUrl?.Trim() ?? string.Empty);
|
||||
_configService.SetLocalServerListenUrls(LocalServerListenUrls?.Trim() ?? string.Empty);
|
||||
|
||||
_configService.SetSyncApiEndpoint(SyncApiEndpoint?.Trim() ?? string.Empty);
|
||||
|
|
@ -196,9 +223,11 @@ public partial class SettingsViewModel : ObservableObject
|
|||
_configService.SetScanIntervalMinutes(minutes);
|
||||
_configService.SetScanIntervalSeconds(seconds);
|
||||
_configService.SetAdminCardId(AdminCardId?.Trim() ?? string.Empty);
|
||||
SaveMessage = "Settings saved. Restart the app for App mode / server URL changes to take full effect.";
|
||||
SaveMessage = "Settings saved. Restart the app after changing App mode or listen URLs.";
|
||||
IsError = false;
|
||||
IsSaving = false;
|
||||
OnPropertyChanged(nameof(EffectiveBackendUrlDisplay));
|
||||
OnPropertyChanged(nameof(HasBackendUrl));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
|
|
@ -225,33 +254,27 @@ public partial class SettingsViewModel : ObservableObject
|
|||
|
||||
try
|
||||
{
|
||||
if (!IsCentralServerMode)
|
||||
if (string.IsNullOrWhiteSpace(_configService.GetBackendBaseUrl()))
|
||||
{
|
||||
SaveMessage = "Cache sync runs only on the central server PC.";
|
||||
SaveMessage = "Backend URL is not configured.";
|
||||
IsError = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_configService.GetHrmsLookupConnectionString()))
|
||||
if (!await _backendApi.HealthCheckAsync().ConfigureAwait(true))
|
||||
{
|
||||
SaveMessage = "HRMS connection is not configured. Cannot sync offline cache.";
|
||||
SaveMessage = "Cannot reach backend. Ensure the central server app is running and API is listening.";
|
||||
IsError = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var result = await _offlineCacheSyncService.SyncEmployeeAndMenuCacheAsync().ConfigureAwait(true);
|
||||
RefreshCacheSyncTimestamps();
|
||||
var result = await _backendApi.SyncCacheNowAsync().ConfigureAwait(true);
|
||||
await RefreshCacheSyncTimestampsAsync().ConfigureAwait(true);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
SaveMessage = "Employee and menu cache synced successfully.";
|
||||
IsError = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
SaveMessage = result.ErrorMessage ?? "Cache sync failed.";
|
||||
IsError = true;
|
||||
}
|
||||
SaveMessage = result.Message;
|
||||
IsError = !result.Success;
|
||||
if (!string.IsNullOrWhiteSpace(result.Details) && result.Success)
|
||||
SaveMessage += " " + result.Details;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -277,28 +300,30 @@ public partial class SettingsViewModel : ObservableObject
|
|||
|
||||
try
|
||||
{
|
||||
if (!IsCentralServerMode)
|
||||
if (string.IsNullOrWhiteSpace(_configService.GetBackendBaseUrl()))
|
||||
{
|
||||
SaveMessage = "Posting to production runs only on the central server PC.";
|
||||
SaveMessage = "Backend URL is not configured.";
|
||||
IsError = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_configService.GetMySqlConnectionString()))
|
||||
if (!await _backendApi.HealthCheckAsync().ConfigureAwait(true))
|
||||
{
|
||||
SaveMessage = "MySQL connection string not configured.";
|
||||
SaveMessage = "Cannot reach backend. Ensure the central server app is running.";
|
||||
IsError = true;
|
||||
return;
|
||||
}
|
||||
|
||||
await _syncService.SyncNowAsync().ConfigureAwait(false);
|
||||
SaveMessage = "Posted data to production.";
|
||||
IsError = false;
|
||||
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 data to production.";
|
||||
SaveMessage = "Failed to post pending orders: " + ex.Message;
|
||||
IsError = true;
|
||||
}
|
||||
finally
|
||||
|
|
|
|||
|
|
@ -286,10 +286,14 @@
|
|||
Margin="0,0,0,12"
|
||||
Padding="12,8"/>
|
||||
|
||||
<TextBlock Text="Central server base URL (client PCs)" FontSize="16" Foreground="{StaticResource PrimaryText}" FontWeight="SemiBold" />
|
||||
<TextBox Text="{Binding CentralServerBaseUrl, UpdateSourceTrigger=PropertyChanged}"
|
||||
<TextBlock Text="Backend base URL" FontSize="16" Foreground="{StaticResource PrimaryText}" FontWeight="SemiBold" />
|
||||
<TextBox Text="{Binding BackendBaseUrl, UpdateSourceTrigger=PropertyChanged}"
|
||||
Style="{StaticResource ModernTextBoxStyle}" Margin="0,8,0,4" />
|
||||
<TextBlock Text="Example: http://192.168.1.10:5000 — used when this PC is in Client mode."
|
||||
<TextBlock Text="Client: central server IP (e.g. http://192.168.1.10:5000). Server: leave blank to use http://localhost:5000 from listen URL below."
|
||||
FontSize="14"
|
||||
Foreground="{StaticResource MutedText}"
|
||||
Margin="0,0,0,4" />
|
||||
<TextBlock Text="{Binding EffectiveBackendUrlDisplay, StringFormat=API calls use: {0}}"
|
||||
FontSize="14"
|
||||
Foreground="{StaticResource MutedText}"
|
||||
Margin="0,0,0,12" />
|
||||
|
|
@ -302,43 +306,9 @@
|
|||
Foreground="{StaticResource MutedText}"
|
||||
Margin="0,0,0,20" />
|
||||
|
||||
<StackPanel Visibility="{Binding IsCentralServerMode, Converter={StaticResource BoolToVis}}">
|
||||
<!--<TextBlock Text="Sync API Endpoint (UIND)" FontSize="16" Foreground="{StaticResource PrimaryText}" FontWeight="SemiBold" />
|
||||
<TextBox Text="{Binding SyncApiEndpoint, UpdateSourceTrigger=PropertyChanged}"
|
||||
Style="{StaticResource ModernTextBoxStyle}" Margin="0,8,0,8" />
|
||||
<TextBlock Text="Endpoint used to sync scan records."
|
||||
FontSize="14"
|
||||
Foreground="{StaticResource MutedText}"
|
||||
Margin="0,0,0,20" />-->
|
||||
<TextBlock Text="Sync Data To (UIND)"
|
||||
FontSize="16"
|
||||
Foreground="{StaticResource PrimaryText}"
|
||||
FontWeight="SemiBold" />
|
||||
<TextBlock Text="Backend jobs (calls central API — same on server and client)" FontSize="16" Foreground="{StaticResource PrimaryText}" FontWeight="SemiBold" Margin="0,0,0,8" />
|
||||
|
||||
<Grid Margin="0,8,0,8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="16" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Grid.Column="0"
|
||||
Text="Post data to production"
|
||||
FontSize="18"
|
||||
Foreground="{StaticResource PrimaryText}"
|
||||
VerticalAlignment="Center"
|
||||
Margin="4,0,0,0" />
|
||||
|
||||
<!-- Post button right next to textbox -->
|
||||
<Button Grid.Column="2"
|
||||
Content="{Binding PostButtonText}"
|
||||
Command="{Binding PostDataNowCommand}"
|
||||
Style="{StaticResource PrimaryButtonStyle}"
|
||||
MinWidth="150"
|
||||
IsEnabled="{Binding CanPostNow}" />
|
||||
</Grid>
|
||||
|
||||
<TextBlock Text="Download employee RFID tags, meal schedules, and menu data for offline scanning."
|
||||
<TextBlock Text="Download employee RFID tags, meal schedules, and menu data to central SQLite (HRMS → backend)."
|
||||
FontSize="14"
|
||||
Foreground="{StaticResource MutedText}"
|
||||
Margin="0,0,0,8" />
|
||||
|
|
@ -358,9 +328,27 @@
|
|||
<TextBlock Text="{Binding LastMealMenuCacheSyncDisplay, StringFormat=Last meal/menu cache sync: {0}}"
|
||||
FontSize="14"
|
||||
Foreground="{StaticResource MutedText}"
|
||||
Margin="0,0,0,20" />
|
||||
Margin="0,0,0,12" />
|
||||
|
||||
</StackPanel>
|
||||
<Grid Margin="0,0,0,8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="16" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0"
|
||||
Text="Post pending orders to production (UIND)"
|
||||
FontSize="18"
|
||||
Foreground="{StaticResource PrimaryText}"
|
||||
VerticalAlignment="Center"
|
||||
Margin="4,0,0,0" />
|
||||
<Button Grid.Column="2"
|
||||
Content="{Binding PostButtonText}"
|
||||
Command="{Binding PostDataNowCommand}"
|
||||
Style="{StaticResource PrimaryButtonStyle}"
|
||||
MinWidth="200"
|
||||
IsEnabled="{Binding CanPostNow}" />
|
||||
</Grid>
|
||||
|
||||
<!-- Message area -->
|
||||
<Border Margin="0,0,0,20" Padding="16" CornerRadius="8">
|
||||
|
|
|
|||
Loading…
Reference in New Issue