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>
234 lines
10 KiB
C#
234 lines
10 KiB
C#
using System.ComponentModel;
|
|
using System.Linq;
|
|
using System.Windows;
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using CommunityToolkit.Mvvm.Input;
|
|
using HC_APTBS.Models;
|
|
using HC_APTBS.Services;
|
|
using HC_APTBS.ViewModels.Dialogs;
|
|
using HC_APTBS.Views.Dialogs;
|
|
|
|
namespace HC_APTBS.ViewModels.Pages
|
|
{
|
|
/// <summary>Wrapper VM exposing <see cref="TestPanel"/> when the wizard is in the Plan step.</summary>
|
|
public sealed class PlanStateViewModel
|
|
{
|
|
/// <summary>Shared test panel (enable/disable phases).</summary>
|
|
public TestPanelViewModel TestPanel { get; }
|
|
|
|
/// <summary>Creates a new Plan-state wrapper around the shared test panel.</summary>
|
|
public PlanStateViewModel(TestPanelViewModel testPanel) => TestPanel = testPanel;
|
|
}
|
|
|
|
/// <summary>Wrapper VM exposing <see cref="TestPanel"/> when the wizard is in the Running step.</summary>
|
|
public sealed class RunningStateViewModel
|
|
{
|
|
/// <summary>Shared test panel (live phase updates).</summary>
|
|
public TestPanelViewModel TestPanel { get; }
|
|
|
|
/// <summary>Creates a new Running-state wrapper around the shared test panel.</summary>
|
|
public RunningStateViewModel(TestPanelViewModel testPanel) => TestPanel = testPanel;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Orchestrator view-model for the Tests navigation page.
|
|
///
|
|
/// <para>Drives the <c>Plan → Preconditions → Running → Done</c> wizard defined in
|
|
/// <c>docs/ui-structure.md §4</c>. Exposes <see cref="CurrentStateVm"/>, which the
|
|
/// view's <c>ContentControl</c> routes through typed DataTemplates to the four
|
|
/// step views. Commands (<see cref="NextCommand"/>, <see cref="BackCommand"/>,
|
|
/// <see cref="AbortCommand"/>, <see cref="RunAgainCommand"/>,
|
|
/// <see cref="ViewFullResultsCommand"/>) form the wizard's state-machine edges.</para>
|
|
///
|
|
/// <para>Observes <see cref="MainViewModel.IsTestRunning"/> to perform the
|
|
/// Preconditions→Running (on true) and Running→Done (on false) transitions
|
|
/// automatically, so the page stays in sync regardless of which control fired
|
|
/// the underlying start/stop command.</para>
|
|
/// </summary>
|
|
public sealed partial class TestsPageViewModel : ObservableObject
|
|
{
|
|
private readonly IConfigurationService _config;
|
|
private readonly ILocalizationService _loc;
|
|
|
|
/// <summary>Root ViewModel — owns services, live readings, and global commands.</summary>
|
|
public MainViewModel Root { get; }
|
|
|
|
/// <summary>Test panel: sections, phase cards, live indicators.</summary>
|
|
public TestPanelViewModel TestPanel => Root.TestPanel;
|
|
|
|
/// <summary>Measurement results table (per-phase pass/fail).</summary>
|
|
public ResultDisplayViewModel ResultDisplay => Root.ResultDisplay;
|
|
|
|
/// <summary>Preconditions checklist — lazily instantiated on first entry into the step.</summary>
|
|
public TestPreconditionsViewModel Preconditions { get; }
|
|
|
|
/// <summary>Auth gate scoped to the Tests page (used by preconditions for auth-required tests).</summary>
|
|
public AuthGateViewModel TestAuth { get; }
|
|
|
|
private readonly PlanStateViewModel _planVm;
|
|
private readonly RunningStateViewModel _runningVm;
|
|
|
|
/// <summary>
|
|
/// Creates the Tests page orchestrator.
|
|
/// </summary>
|
|
/// <param name="root">Root coordinator.</param>
|
|
/// <param name="config">Configuration service (passed to the scoped auth gate).</param>
|
|
/// <param name="loc">Localisation service.</param>
|
|
public TestsPageViewModel(MainViewModel root, IConfigurationService config, ILocalizationService loc)
|
|
{
|
|
Root = root;
|
|
_config = config;
|
|
_loc = loc;
|
|
|
|
TestAuth = new AuthGateViewModel(config, loc);
|
|
Preconditions = new TestPreconditionsViewModel(root, loc, Root.TestPanel, TestAuth);
|
|
_planVm = new PlanStateViewModel(Root.TestPanel);
|
|
_runningVm = new RunningStateViewModel(Root.TestPanel);
|
|
|
|
CurrentStateVm = _planVm;
|
|
|
|
Root.PropertyChanged += OnRootPropertyChanged;
|
|
}
|
|
|
|
// ── State ─────────────────────────────────────────────────────────────────
|
|
|
|
/// <summary>Current wizard step.</summary>
|
|
[ObservableProperty]
|
|
[NotifyCanExecuteChangedFor(nameof(NextCommand))]
|
|
[NotifyCanExecuteChangedFor(nameof(BackCommand))]
|
|
[NotifyCanExecuteChangedFor(nameof(AbortCommand))]
|
|
[NotifyCanExecuteChangedFor(nameof(RunAgainCommand))]
|
|
[NotifyCanExecuteChangedFor(nameof(ViewFullResultsCommand))]
|
|
private TestFlowState _currentState = TestFlowState.Plan;
|
|
|
|
/// <summary>
|
|
/// View-model currently rendered by the step <c>ContentControl</c>. Swaps to
|
|
/// <see cref="PlanStateViewModel"/>, <see cref="TestPreconditionsViewModel"/>,
|
|
/// <see cref="RunningStateViewModel"/>, or <c>this</c> (for the Done step).
|
|
/// </summary>
|
|
[ObservableProperty] private object _currentStateVm;
|
|
|
|
/// <summary>Convenience flag for view styling — true while a test is actively running.</summary>
|
|
public bool IsRunningStep => CurrentState == TestFlowState.Running;
|
|
|
|
/// <summary>Convenience flag for view styling — true when the page is on the Done step.</summary>
|
|
public bool IsDoneStep => CurrentState == TestFlowState.Done;
|
|
|
|
partial void OnCurrentStateChanged(TestFlowState oldValue, TestFlowState newValue)
|
|
{
|
|
if (oldValue == TestFlowState.Preconditions && newValue != TestFlowState.Preconditions)
|
|
Preconditions.Deactivate();
|
|
|
|
switch (newValue)
|
|
{
|
|
case TestFlowState.Plan:
|
|
CurrentStateVm = _planVm;
|
|
break;
|
|
case TestFlowState.Preconditions:
|
|
Preconditions.Activate();
|
|
Preconditions.OnEnabledPhasesChanged();
|
|
CurrentStateVm = Preconditions;
|
|
break;
|
|
case TestFlowState.Running:
|
|
CurrentStateVm = _runningVm;
|
|
break;
|
|
case TestFlowState.Done:
|
|
CurrentStateVm = this;
|
|
break;
|
|
}
|
|
|
|
OnPropertyChanged(nameof(IsRunningStep));
|
|
OnPropertyChanged(nameof(IsDoneStep));
|
|
}
|
|
|
|
// ── Commands ──────────────────────────────────────────────────────────────
|
|
|
|
/// <summary>
|
|
/// Advances Plan → Preconditions. No-op if no phases are enabled (defensive — the
|
|
/// Preconditions step would fail its auth-detection anyway so there's nothing to
|
|
/// start). The phase-enable state is not observed live, so the button itself is
|
|
/// only guarded by <see cref="CurrentState"/>.
|
|
/// </summary>
|
|
[RelayCommand(CanExecute = nameof(CanNext))]
|
|
private void Next()
|
|
{
|
|
if (CurrentState != TestFlowState.Plan) return;
|
|
if (!Root.TestPanel.Tests.Any(s => s.Phases.Any(p => p.IsEnabled))) return;
|
|
CurrentState = TestFlowState.Preconditions;
|
|
}
|
|
|
|
private bool CanNext() => CurrentState == TestFlowState.Plan;
|
|
|
|
/// <summary>Goes back Preconditions → Plan. Disabled during Running / Done.</summary>
|
|
[RelayCommand(CanExecute = nameof(CanBack))]
|
|
private void Back()
|
|
{
|
|
if (CurrentState == TestFlowState.Preconditions)
|
|
CurrentState = TestFlowState.Plan;
|
|
}
|
|
|
|
private bool CanBack() => CurrentState == TestFlowState.Preconditions;
|
|
|
|
/// <summary>
|
|
/// Opens a confirmation dialog and, if accepted, delegates to <see cref="MainViewModel.StopTestCommand"/>.
|
|
/// </summary>
|
|
[RelayCommand(CanExecute = nameof(CanAbort))]
|
|
private void Abort()
|
|
{
|
|
var vm = new ConfirmDialogViewModel
|
|
{
|
|
Title = _loc.GetString("Test.Abort.Title"),
|
|
Message = _loc.GetString("Test.Abort.Message"),
|
|
ConfirmText = _loc.GetString("Test.Abort.Confirm"),
|
|
CancelText = _loc.GetString("Test.Abort.Cancel"),
|
|
};
|
|
var dlg = new ConfirmDialog(vm) { Owner = Application.Current.MainWindow };
|
|
dlg.ShowDialog();
|
|
if (!vm.Accepted) return;
|
|
|
|
if (Root.StopTestCommand.CanExecute(null))
|
|
Root.StopTestCommand.Execute(null);
|
|
}
|
|
|
|
private bool CanAbort() => CurrentState == TestFlowState.Running;
|
|
|
|
/// <summary>Resets the page for a fresh run without reloading the pump.</summary>
|
|
[RelayCommand(CanExecute = nameof(CanRunAgain))]
|
|
private void RunAgain()
|
|
{
|
|
Root.TestPanel.ResetResults();
|
|
Root.ResultDisplay.Clear();
|
|
CurrentState = TestFlowState.Plan;
|
|
}
|
|
|
|
private bool CanRunAgain() => CurrentState == TestFlowState.Done;
|
|
|
|
/// <summary>Jumps to the Results navigation page.</summary>
|
|
[RelayCommand(CanExecute = nameof(CanViewFullResults))]
|
|
private void ViewFullResults()
|
|
{
|
|
Root.SelectedPage = AppPage.Results;
|
|
}
|
|
|
|
private bool CanViewFullResults() => CurrentState == TestFlowState.Done;
|
|
|
|
// ── IsTestRunning → wizard state sync ─────────────────────────────────────
|
|
|
|
private void OnRootPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
|
{
|
|
if (e.PropertyName != nameof(MainViewModel.IsTestRunning)) return;
|
|
|
|
if (Root.IsTestRunning)
|
|
{
|
|
if (CurrentState == TestFlowState.Preconditions)
|
|
CurrentState = TestFlowState.Running;
|
|
}
|
|
else
|
|
{
|
|
if (CurrentState == TestFlowState.Running)
|
|
CurrentState = TestFlowState.Done;
|
|
}
|
|
}
|
|
}
|
|
}
|