feat: page-based navigation shell + Tests page wizard
Replace the monolithic MainWindow with a SelectedPage-driven shell (Dashboard / Pump / Bench / Tests / Results / Settings). The Tests page gets the Plan -> Preconditions -> Running -> Done wizard from ui-structure.md \u00a74, backed by a 7-item precondition gate and shared sub-views (PhaseCardView / TestSectionView / GraphicIndicatorView) extracted from the now-deleted monolithic TestPanelView. New VMs / views: - Tests wizard: TestPreconditions, PhaseCard, GraphicIndicator, TestSection, TestPlan, TestRunning, TestDone - Dashboard panels: DashboardConnection, DashboardReadings, DashboardAlarms, InterlockBanner, ResultHistory - Pump / bench panels: PumpIdentificationPanel, PumpLiveData, UnlockPanel, BenchDriveControl, BenchReadings, RelayBank, TemperatureControl, DtcList, AuthGate - Dialogs: generic ConfirmDialog, UserManageDialog, UserPromptDialog Supporting changes: - IsOilPumpOn exposed on MainViewModel for precondition evaluation - RequiresAuth added to TestDefinition (XML round-trip) - BipStatusDefinition + CompletedTestRun models - ~35 new Test.* localization keys (en + es) - Settings moved from modal dialog to full page - Pause / Retry / Skip stubs in TestRunningView; full spec in docs/gap-test-running-controls.md for follow-up implementation - docs/ui-structure.md captures the wizard design Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
95
ViewModels/InterlockBannerViewModel.cs
Normal file
95
ViewModels/InterlockBannerViewModel.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using HC_APTBS.Services;
|
||||
|
||||
namespace HC_APTBS.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// ViewModel for the bench-page interlock banner.
|
||||
/// Surfaces two soft safety warnings inline (dismissible), matching the
|
||||
/// Bench page guideline in <c>docs/ui-structure.md</c> §2:
|
||||
/// <list type="bullet">
|
||||
/// <item>Oil pump off while RPM > threshold.</item>
|
||||
/// <item>RPM above configured <c>AppSettings.MaxRpm</c> safety limit.</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public sealed partial class InterlockBannerViewModel : ObservableObject
|
||||
{
|
||||
private const double OilPumpRpmThreshold = 300.0;
|
||||
|
||||
private readonly IConfigurationService _config;
|
||||
|
||||
/// <summary>Text shown in the banner. Empty string hides the banner.</summary>
|
||||
[ObservableProperty] private string _message = string.Empty;
|
||||
|
||||
/// <summary>True when the banner is currently shown.</summary>
|
||||
[ObservableProperty] private bool _isVisible;
|
||||
|
||||
/// <summary>True when the banner is showing a critical warning (red background).</summary>
|
||||
[ObservableProperty] private bool _isCritical;
|
||||
|
||||
private bool _dismissed;
|
||||
private int _dismissedFor; // Bit flags: 1=oil, 2=rpm
|
||||
|
||||
/// <param name="configService">Configuration service (source of <c>MaxRpm</c>).</param>
|
||||
public InterlockBannerViewModel(IConfigurationService configService)
|
||||
{
|
||||
_config = configService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recomputes the banner message from live bench state.
|
||||
/// Called from the page's refresh tick handler.
|
||||
/// </summary>
|
||||
/// <param name="benchRpm">Current bench motor RPM.</param>
|
||||
/// <param name="isOilPumpOn">True when the oil pump relay is energised.</param>
|
||||
public void Update(double benchRpm, bool isOilPumpOn)
|
||||
{
|
||||
int maxRpm = Math.Max(1, _config.Settings.MaxRpm);
|
||||
|
||||
bool oilWarn = !isOilPumpOn && benchRpm > OilPumpRpmThreshold;
|
||||
bool rpmWarn = benchRpm > maxRpm;
|
||||
|
||||
int activeFlags = (oilWarn ? 1 : 0) | (rpmWarn ? 2 : 0);
|
||||
|
||||
// Reset dismissal when the condition actually clears.
|
||||
if (activeFlags == 0)
|
||||
{
|
||||
_dismissed = false;
|
||||
_dismissedFor = 0;
|
||||
Message = string.Empty;
|
||||
IsCritical = false;
|
||||
IsVisible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// If the operator already dismissed this exact condition set, stay hidden.
|
||||
if (_dismissed && _dismissedFor == activeFlags)
|
||||
return;
|
||||
|
||||
_dismissed = false;
|
||||
|
||||
if (rpmWarn)
|
||||
{
|
||||
Message = $"RPM {benchRpm:F0} exceeds safety limit ({maxRpm}).";
|
||||
IsCritical = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Message = $"Oil pump is OFF while bench is running at {benchRpm:F0} RPM.";
|
||||
IsCritical = false;
|
||||
}
|
||||
IsVisible = true;
|
||||
_dismissedFor = activeFlags;
|
||||
}
|
||||
|
||||
/// <summary>Hides the banner until the underlying condition changes.</summary>
|
||||
[RelayCommand]
|
||||
private void Dismiss()
|
||||
{
|
||||
_dismissed = true;
|
||||
IsVisible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user