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>
281 lines
12 KiB
C#
281 lines
12 KiB
C#
using System;
|
|
using System.Collections.ObjectModel;
|
|
using System.ComponentModel;
|
|
using System.Linq;
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using CommunityToolkit.Mvvm.Input;
|
|
using HC_APTBS.Models;
|
|
using HC_APTBS.Services;
|
|
|
|
namespace HC_APTBS.ViewModels
|
|
{
|
|
/// <summary>
|
|
/// Preconditions checklist for the Tests page "Preconditions" wizard step.
|
|
///
|
|
/// <para>Aggregates the seven safety/readiness checks specified in
|
|
/// <c>docs/ui-structure.md</c> §4b. Items auto-refresh whenever the underlying
|
|
/// <see cref="MainViewModel"/> properties change; <see cref="StartTestCommand"/>
|
|
/// stays disabled until every required check passes.</para>
|
|
///
|
|
/// <para>Subscriptions are established in <see cref="Activate"/> and released in
|
|
/// <see cref="Deactivate"/>; the parent view-model calls these as the wizard state
|
|
/// transitions into/out of Preconditions so we do not do work during Plan/Running/Done.</para>
|
|
/// </summary>
|
|
public sealed partial class TestPreconditionsViewModel : ObservableObject
|
|
{
|
|
// ── Stable item identifiers ───────────────────────────────────────────────
|
|
|
|
private const string IdPump = "pump";
|
|
private const string IdCan = "can";
|
|
private const string IdKLine = "kline";
|
|
private const string IdRpmZero = "rpmZero";
|
|
private const string IdOilPump = "oilPump";
|
|
private const string IdNoAlarms = "noAlarms";
|
|
private const string IdAuth = "auth";
|
|
|
|
// ── Resource keys ─────────────────────────────────────────────────────────
|
|
|
|
private const string KeyLabelPump = "Test.Precheck.PumpSelected";
|
|
private const string KeyLabelCan = "Test.Precheck.CanLive";
|
|
private const string KeyLabelKLine = "Test.Precheck.KLineOpen";
|
|
private const string KeyLabelRpmZero = "Test.Precheck.RpmZero";
|
|
private const string KeyLabelOilPump = "Test.Precheck.OilPumpOn";
|
|
private const string KeyLabelNoAlarms = "Test.Precheck.NoCriticalAlarms";
|
|
private const string KeyLabelAuth = "Test.Precheck.UserAuth";
|
|
|
|
private const string KeyRemPump = "Test.Precheck.Remediation.SelectPump";
|
|
private const string KeyRemCan = "Test.Precheck.Remediation.CheckCan";
|
|
private const string KeyRemKLine = "Test.Precheck.Remediation.OpenKLine";
|
|
private const string KeyRemRpmZero = "Test.Precheck.Remediation.StopBench";
|
|
private const string KeyRemOilPump = "Test.Precheck.Remediation.StartOilPump";
|
|
private const string KeyRemNoAlarms = "Test.Precheck.Remediation.ClearAlarms";
|
|
private const string KeyRemAuth = "Test.Precheck.Remediation.Authenticate";
|
|
|
|
private readonly MainViewModel _root;
|
|
private readonly ILocalizationService _loc;
|
|
private readonly TestPanelViewModel _testPanel;
|
|
|
|
private bool _subscribed;
|
|
|
|
/// <summary>Rows rendered by the checklist view, in display order.</summary>
|
|
public ObservableCollection<PreconditionItemViewModel> Items { get; } = new();
|
|
|
|
/// <summary>Gate used to authenticate the operator when a required test has <see cref="TestDefinition.RequiresAuth"/>.</summary>
|
|
public AuthGateViewModel TestAuth { get; }
|
|
|
|
/// <summary>True when the currently-enabled tests include at least one requiring authentication.</summary>
|
|
[ObservableProperty] private bool _isAuthRequired;
|
|
|
|
/// <summary>True when every required check passes — gates <see cref="StartTestCommand"/>.</summary>
|
|
[ObservableProperty]
|
|
[NotifyCanExecuteChangedFor(nameof(StartTestCommand))]
|
|
private bool _allPassed;
|
|
|
|
/// <param name="root">Root VM — source of all live bench/ECU state.</param>
|
|
/// <param name="loc">Localisation service for label refresh.</param>
|
|
/// <param name="testPanel">Panel VM — used to discover which tests are enabled and whether any require auth.</param>
|
|
/// <param name="testAuth">Auth gate scoped to the Tests page.</param>
|
|
public TestPreconditionsViewModel(
|
|
MainViewModel root,
|
|
ILocalizationService loc,
|
|
TestPanelViewModel testPanel,
|
|
AuthGateViewModel testAuth)
|
|
{
|
|
_root = root;
|
|
_loc = loc;
|
|
_testPanel = testPanel;
|
|
TestAuth = testAuth;
|
|
|
|
BuildItems();
|
|
}
|
|
|
|
// ── Lifecycle ─────────────────────────────────────────────────────────────
|
|
|
|
/// <summary>
|
|
/// Called by the parent when the wizard enters the Preconditions state.
|
|
/// Subscribes to all live-state sources and evaluates once.
|
|
/// </summary>
|
|
public void Activate()
|
|
{
|
|
if (_subscribed) return;
|
|
|
|
_root.PropertyChanged += OnRootPropertyChanged;
|
|
_root.DashboardAlarms.PropertyChanged += OnAlarmsPropertyChanged;
|
|
TestAuth.PropertyChanged += OnAuthPropertyChanged;
|
|
_loc.LanguageChanged += OnLanguageChanged;
|
|
|
|
_subscribed = true;
|
|
|
|
RefreshAuthRequired();
|
|
RebuildAuthItemVisibility();
|
|
Reevaluate();
|
|
}
|
|
|
|
/// <summary>Called by the parent when the wizard leaves the Preconditions state.</summary>
|
|
public void Deactivate()
|
|
{
|
|
if (!_subscribed) return;
|
|
|
|
_root.PropertyChanged -= OnRootPropertyChanged;
|
|
_root.DashboardAlarms.PropertyChanged -= OnAlarmsPropertyChanged;
|
|
TestAuth.PropertyChanged -= OnAuthPropertyChanged;
|
|
_loc.LanguageChanged -= OnLanguageChanged;
|
|
|
|
_subscribed = false;
|
|
}
|
|
|
|
// ── Build ─────────────────────────────────────────────────────────────────
|
|
|
|
private void BuildItems()
|
|
{
|
|
Items.Clear();
|
|
Items.Add(new PreconditionItemViewModel(IdPump, _root, AppPage.Pump));
|
|
Items.Add(new PreconditionItemViewModel(IdCan, _root, AppPage.Dashboard));
|
|
Items.Add(new PreconditionItemViewModel(IdKLine, _root, AppPage.Pump));
|
|
Items.Add(new PreconditionItemViewModel(IdRpmZero, _root, AppPage.Bench));
|
|
Items.Add(new PreconditionItemViewModel(IdOilPump, _root, AppPage.Bench));
|
|
Items.Add(new PreconditionItemViewModel(IdNoAlarms, _root, AppPage.Dashboard));
|
|
// Auth item added on-demand (see RebuildAuthItemVisibility).
|
|
|
|
RefreshLabels();
|
|
}
|
|
|
|
private void RebuildAuthItemVisibility()
|
|
{
|
|
var authItem = Items.FirstOrDefault(i => i.Id == IdAuth);
|
|
if (IsAuthRequired && authItem == null)
|
|
{
|
|
Items.Add(new PreconditionItemViewModel(IdAuth, _root, remediationTargetPage: null));
|
|
RefreshLabels();
|
|
}
|
|
else if (!IsAuthRequired && authItem != null)
|
|
{
|
|
Items.Remove(authItem);
|
|
}
|
|
}
|
|
|
|
// ── Evaluation ────────────────────────────────────────────────────────────
|
|
|
|
/// <summary>Recomputes every item's satisfied state and <see cref="AllPassed"/>.</summary>
|
|
public void Reevaluate()
|
|
{
|
|
foreach (var item in Items)
|
|
item.IsSatisfied = EvaluateItem(item.Id);
|
|
|
|
AllPassed = Items.All(i => !i.IsRequired || i.IsSatisfied);
|
|
}
|
|
|
|
private bool EvaluateItem(string id) => id switch
|
|
{
|
|
IdPump => _root.CurrentPump != null,
|
|
IdCan => _root.IsCanConnected,
|
|
IdKLine => _root.KLineState == KLineConnectionState.Connected,
|
|
IdRpmZero => _root.BenchRpm == 0,
|
|
IdOilPump => _root.IsOilPumpOn,
|
|
IdNoAlarms => !_root.DashboardAlarms.HasCritical,
|
|
IdAuth => TestAuth.IsAuthenticated,
|
|
_ => true,
|
|
};
|
|
|
|
private void RefreshAuthRequired()
|
|
{
|
|
IsAuthRequired = _testPanel.Tests
|
|
.Any(s => (s.Source?.RequiresAuth ?? false) && s.Phases.Any(p => p.IsEnabled));
|
|
}
|
|
|
|
// ── Labels ────────────────────────────────────────────────────────────────
|
|
|
|
private void RefreshLabels()
|
|
{
|
|
foreach (var item in Items)
|
|
{
|
|
item.Label = _loc.GetString(LabelKeyFor(item.Id));
|
|
item.RemediationText = _loc.GetString(RemediationKeyFor(item.Id));
|
|
}
|
|
}
|
|
|
|
private static string LabelKeyFor(string id) => id switch
|
|
{
|
|
IdPump => KeyLabelPump,
|
|
IdCan => KeyLabelCan,
|
|
IdKLine => KeyLabelKLine,
|
|
IdRpmZero => KeyLabelRpmZero,
|
|
IdOilPump => KeyLabelOilPump,
|
|
IdNoAlarms => KeyLabelNoAlarms,
|
|
IdAuth => KeyLabelAuth,
|
|
_ => id,
|
|
};
|
|
|
|
private static string RemediationKeyFor(string id) => id switch
|
|
{
|
|
IdPump => KeyRemPump,
|
|
IdCan => KeyRemCan,
|
|
IdKLine => KeyRemKLine,
|
|
IdRpmZero => KeyRemRpmZero,
|
|
IdOilPump => KeyRemOilPump,
|
|
IdNoAlarms => KeyRemNoAlarms,
|
|
IdAuth => KeyRemAuth,
|
|
_ => string.Empty,
|
|
};
|
|
|
|
// ── Event handlers ────────────────────────────────────────────────────────
|
|
|
|
private void OnRootPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
|
{
|
|
switch (e.PropertyName)
|
|
{
|
|
case nameof(MainViewModel.CurrentPump):
|
|
case nameof(MainViewModel.IsCanConnected):
|
|
case nameof(MainViewModel.KLineState):
|
|
case nameof(MainViewModel.BenchRpm):
|
|
case nameof(MainViewModel.IsOilPumpOn):
|
|
Reevaluate();
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void OnAlarmsPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
|
{
|
|
if (e.PropertyName == nameof(DashboardAlarmsViewModel.HasCritical))
|
|
Reevaluate();
|
|
}
|
|
|
|
private void OnAuthPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
|
{
|
|
if (e.PropertyName == nameof(AuthGateViewModel.IsAuthenticated))
|
|
Reevaluate();
|
|
}
|
|
|
|
private void OnLanguageChanged() => RefreshLabels();
|
|
|
|
/// <summary>
|
|
/// Called by the parent VM whenever the test-panel enabled-phase selection changes,
|
|
/// so the auth item can be shown/hidden based on enabled tests' <see cref="TestDefinition.RequiresAuth"/>.
|
|
/// </summary>
|
|
public void OnEnabledPhasesChanged()
|
|
{
|
|
RefreshAuthRequired();
|
|
RebuildAuthItemVisibility();
|
|
Reevaluate();
|
|
}
|
|
|
|
partial void OnIsAuthRequiredChanged(bool value)
|
|
{
|
|
RebuildAuthItemVisibility();
|
|
Reevaluate();
|
|
}
|
|
|
|
// ── Commands ──────────────────────────────────────────────────────────────
|
|
|
|
/// <summary>Delegates to <see cref="MainViewModel.StartTestCommand"/> when <see cref="AllPassed"/> is true.</summary>
|
|
[RelayCommand(CanExecute = nameof(CanStart))]
|
|
private void StartTest()
|
|
{
|
|
if (_root.StartTestCommand.CanExecute(null))
|
|
_root.StartTestCommand.Execute(null);
|
|
}
|
|
|
|
private bool CanStart() => AllPassed;
|
|
}
|
|
}
|