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:
2026-04-18 13:11:34 +02:00
parent 37d099cdbd
commit 0280a2fad1
110 changed files with 8008 additions and 1115 deletions

View File

@@ -0,0 +1,66 @@
<UserControl x:Class="HC_APTBS.Views.Pages.BenchPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:uc="clr-namespace:HC_APTBS.Views.UserControls"
mc:Ignorable="d"
d:DesignHeight="900" d:DesignWidth="1280">
<!--
Bench page — HMI-style manual hardware operation.
DataContext = BenchPageViewModel.
Three zones:
A. Live readings (LCD panel + encoder angles)
B. Live plots (Q flows + pressure traces)
C. Controls (drive, temperature, relay bank)
Interlock banner spans the page above zone contents when triggered.
-->
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<Grid Margin="6">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <!-- interlock banner -->
<RowDefinition Height="*"/> <!-- main content -->
</Grid.RowDefinitions>
<!-- Interlock banner: hidden unless InterlockBannerViewModel raises a condition -->
<uc:InterlockBannerView Grid.Row="0" DataContext="{Binding Interlock}"/>
<!-- Main content: 3 columns -->
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="400"/> <!-- Zone A: readings + angles -->
<ColumnDefinition Width="*"/> <!-- Zone B: live plots -->
<ColumnDefinition Width="210"/> <!-- Zone C: controls -->
</Grid.ColumnDefinitions>
<!-- ── Zone A: readings + encoder angles ──────────────────── -->
<StackPanel Grid.Column="0" Margin="0,0,6,0">
<uc:BenchReadingsView/>
<uc:AngleDisplayView DataContext="{Binding AngleDisplay}"
Margin="0,6,0,0"/>
</StackPanel>
<!-- ── Zone B: live plots (flows + pressure) ──────────────── -->
<StackPanel Grid.Column="1" Margin="0,0,6,0">
<uc:FlowmeterChartView DataContext="{Binding FlowmeterChart.Delivery}"/>
<uc:FlowmeterChartView DataContext="{Binding FlowmeterChart.Over}"
Margin="0,4,0,0"/>
<uc:FlowmeterChartView DataContext="{Binding PressureTrace.P1}"
Margin="0,4,0,0"/>
<uc:FlowmeterChartView DataContext="{Binding PressureTrace.P2}"
Margin="0,4,0,0"/>
</StackPanel>
<!-- ── Zone C: stacked control panels ─────────────────────── -->
<StackPanel Grid.Column="2">
<uc:BenchDriveControlView/>
<uc:TemperatureControlView DataContext="{Binding TempControl}"
Margin="0,10,0,0"/>
<uc:RelayBankView DataContext="{Binding RelayBank}"
Margin="0,10,0,0"/>
</StackPanel>
</Grid>
</Grid>
</ScrollViewer>
</UserControl>

View File

@@ -0,0 +1,16 @@
using System.Windows.Controls;
namespace HC_APTBS.Views.Pages
{
/// <summary>
/// Bench navigation page. DataContext is expected to be a
/// <see cref="HC_APTBS.ViewModels.Pages.BenchPageViewModel"/>.
/// </summary>
public partial class BenchPage : UserControl
{
public BenchPage()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,241 @@
<UserControl x:Class="HC_APTBS.Views.Pages.DashboardPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:uc="clr-namespace:HC_APTBS.Views.UserControls"
mc:Ignorable="d"
d:DesignHeight="700" d:DesignWidth="980">
<!--
Dashboard — operator "at a glance" landing page.
DataContext: DashboardPageViewModel. Read-first. Only Start / Stop / E-Stop are interactive.
-->
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
<!-- Section card style -->
<Style x:Key="DashCard" TargetType="Border">
<Setter Property="Background" Value="#FAFAFA"/>
<Setter Property="BorderBrush" Value="#DDD"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="CornerRadius" Value="4"/>
<Setter Property="Padding" Value="10"/>
<Setter Property="Margin" Value="4"/>
</Style>
<Style x:Key="DashHeader" TargetType="TextBlock">
<Setter Property="FontSize" Value="13"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="Foreground" Value="#333"/>
<Setter Property="Margin" Value="0,0,0,6"/>
</Style>
</UserControl.Resources>
<Grid Margin="6">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/> <!-- footer: quick actions -->
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="340"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- ── Left column: readings ───────────────────────────────────────── -->
<Border Grid.Row="0" Grid.Column="0" Style="{StaticResource DashCard}">
<StackPanel>
<TextBlock Text="{DynamicResource Dashboard.Readings}" Style="{StaticResource DashHeader}"/>
<uc:DashboardReadingsView/>
</StackPanel>
</Border>
<!-- ── Right column: connections + test summary + alarms ───────────── -->
<Grid Grid.Row="0" Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- Connections -->
<Border Grid.Row="0" Grid.Column="0" Style="{StaticResource DashCard}">
<uc:DashboardConnectionView/>
</Border>
<!-- Test summary -->
<Border Grid.Row="0" Grid.Column="1" Style="{StaticResource DashCard}">
<StackPanel>
<TextBlock Text="{DynamicResource Dashboard.TestSummary}" Style="{StaticResource DashHeader}"/>
<!-- Active test view (when running) -->
<StackPanel Visibility="{Binding Root.IsTestRunning, Converter={StaticResource BoolToVis}}">
<Grid Margin="0,2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{DynamicResource Dashboard.TestActive}" Foreground="#555" FontSize="12"/>
<TextBlock Grid.Column="1" Text="{Binding Root.TestPanel.TestName}"
FontWeight="SemiBold" FontSize="13" TextTrimming="CharacterEllipsis"/>
</Grid>
<Grid Margin="0,2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{DynamicResource Dashboard.TestPhase}" Foreground="#555" FontSize="12"/>
<TextBlock Grid.Column="1" Text="{Binding Root.CurrentPhaseName}"
FontFamily="Consolas" FontSize="13" TextTrimming="CharacterEllipsis"/>
</Grid>
<TextBlock Text="{Binding Root.VerboseStatus}" FontStyle="Italic"
Foreground="#666" FontSize="11" Margin="0,6,0,0"
TextWrapping="Wrap"/>
</StackPanel>
<!-- Idle view (last result or "no test run") -->
<StackPanel>
<StackPanel.Style>
<Style TargetType="StackPanel">
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Root.IsTestRunning}" Value="True">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<TextBlock Text="{DynamicResource Dashboard.NoTestRunning}"
Foreground="#888" FontStyle="Italic" FontSize="12"/>
<Border Margin="0,8,0,0" Padding="8,4" CornerRadius="3"
HorizontalAlignment="Left">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background" Value="#D62828"/>
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Root.IsTestSaved}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter Property="Visibility" Value="Visible"/>
</MultiDataTrigger>
<DataTrigger Binding="{Binding Root.LastTestSuccess}" Value="True">
<Setter Property="Background" Value="#26C200"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<TextBlock Foreground="White" FontWeight="Bold" FontSize="12">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{DynamicResource Dashboard.LastTestFail}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Root.LastTestSuccess}" Value="True">
<Setter Property="Text" Value="{DynamicResource Dashboard.LastTestPass}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Border>
</StackPanel>
</StackPanel>
</Border>
<!-- Alarms (spans both columns of the right grid) -->
<Border Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Style="{StaticResource DashCard}">
<DockPanel>
<TextBlock DockPanel.Dock="Top" Text="{DynamicResource Dashboard.Alarms}"
Style="{StaticResource DashHeader}"/>
<!-- "System OK" banner when no alarms -->
<Border Background="#26C200" CornerRadius="3" Padding="10,6"
HorizontalAlignment="Left" VerticalAlignment="Top"
Visibility="{Binding Alarms.IsClear, Converter={StaticResource BoolToVis}}">
<TextBlock Text="{DynamicResource Dashboard.AlarmsNone}"
Foreground="White" FontWeight="Bold" FontSize="12"/>
</Border>
<!-- Active alarm list -->
<ItemsControl ItemsSource="{Binding Alarms.ActiveAlarms}">
<ItemsControl.Style>
<Style TargetType="ItemsControl">
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Alarms.IsClear}" Value="True">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ItemsControl.Style>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Margin="0,2" Padding="8,4" CornerRadius="3">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background" Value="#FFB020"/> <!-- warning -->
<Style.Triggers>
<DataTrigger Binding="{Binding IsCritical}" Value="True">
<Setter Property="Background" Value="#D62828"/> <!-- critical -->
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="●" Foreground="White" FontSize="14" VerticalAlignment="Center" Margin="0,0,6,0"/>
<TextBlock Grid.Column="1" Text="{Binding Description}"
Foreground="White" FontWeight="SemiBold" FontSize="12"
VerticalAlignment="Center" TextWrapping="Wrap"/>
<TextBlock Grid.Column="2" Foreground="#FFF" FontSize="11" FontFamily="Consolas"
VerticalAlignment="Center" Margin="6,0,0,0">
<Run Text="bit "/><Run Text="{Binding Bit, Mode=OneWay}"/>
</TextBlock>
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DockPanel>
</Border>
</Grid>
<!-- ── Footer: quick actions ───────────────────────────────────────── -->
<Border Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"
Background="#F0F0F0" BorderBrush="#CCC" BorderThickness="0,1,0,0"
Padding="8" Margin="4,4,4,4">
<DockPanel LastChildFill="False">
<Button DockPanel.Dock="Left"
Content="{DynamicResource Dashboard.Action.StartTest}"
Command="{Binding Root.StartTestCommand}"
Height="44" MinWidth="140" FontSize="13" FontWeight="Bold"
Foreground="DarkGreen" Margin="0,0,6,0"
ToolTipService.ShowOnDisabled="True"
ToolTip="{DynamicResource Dashboard.Action.StartTest.Tip}"/>
<Button DockPanel.Dock="Left"
Content="{DynamicResource Dashboard.Action.Stop}"
Command="{Binding Root.StopTestCommand}"
Height="44" MinWidth="120" FontSize="13" FontWeight="Bold"
Foreground="DarkRed" Margin="0,0,6,0"/>
<!-- E-Stop pinned right, red, always visible -->
<Button DockPanel.Dock="Right"
Content="{DynamicResource Dashboard.Action.EmergencyStop}"
Command="{Binding Root.EmergencyStopCommand}"
Height="44" MinWidth="200" FontSize="14" FontWeight="Bold"
Foreground="White" Background="#D62828" BorderBrush="#8B0000"
BorderThickness="2"/>
</DockPanel>
</Border>
</Grid>
</UserControl>

View File

@@ -0,0 +1,16 @@
using System.Windows.Controls;
namespace HC_APTBS.Views.Pages
{
/// <summary>
/// Dashboard navigation page. DataContext is expected to be a
/// <see cref="HC_APTBS.ViewModels.Pages.DashboardPageViewModel"/>.
/// </summary>
public partial class DashboardPage : UserControl
{
public DashboardPage()
{
InitializeComponent();
}
}
}

288
Views/Pages/PumpPage.xaml Normal file
View File

@@ -0,0 +1,288 @@
<UserControl x:Class="HC_APTBS.Views.Pages.PumpPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:uc="clr-namespace:HC_APTBS.Views.UserControls"
xmlns:vm="clr-namespace:HC_APTBS.ViewModels.Pages"
mc:Ignorable="d"
d:DesignHeight="780" d:DesignWidth="1100">
<!--
Pump page — ECU diagnostics and control.
DataContext = PumpPageViewModel.
Layout:
- Banner row: K-Line session banner + "no pump selected" banner
- Sub-nav (left): Identification / DTCs / LiveData / Adaptation / Unlock
- Sub-page content (right): selected sub-section via hidden tabs
-->
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
<!-- Sub-nav item: narrower than the main rail, same accent pattern -->
<Style x:Key="SubNavItem" TargetType="ListBoxItem">
<Setter Property="Height" Value="44"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border x:Name="Root" Background="Transparent">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border x:Name="Accent" Grid.Column="0" Background="Transparent"/>
<ContentPresenter Grid.Column="1"
VerticalAlignment="Center"
Margin="12,0,6,0"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Root" Property="Background" Value="#ECECEC"/>
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Root" Property="Background" Value="#E4EEF7"/>
<Setter TargetName="Accent" Property="Background" Value="#2196F3"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" Value="0.35"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="SubNavText" TargetType="TextBlock">
<Setter Property="FontSize" Value="13"/>
<Setter Property="Foreground" Value="#333"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <!-- banners -->
<RowDefinition Height="*"/> <!-- body -->
</Grid.RowDefinitions>
<!-- ══════════════════════════════════════════════════════════════
Banners: K-Line session + no pump selected
══════════════════════════════════════════════════════════════ -->
<StackPanel Grid.Row="0">
<!-- K-Line session banner (closed / failed) -->
<Border Padding="10,6">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background" Value="#FFF3CD"/>
<Setter Property="BorderBrush" Value="#FFC107"/>
<Setter Property="BorderThickness" Value="0,0,0,1"/>
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsKLineSessionOpen}" Value="True">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsKLineSessionFailed}" Value="True">
<Setter Property="Background" Value="#FDECEA"/>
<Setter Property="BorderBrush" Value="#D62828"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<TextBlock FontSize="12" TextWrapping="Wrap">
<Run Text="●" FontSize="14"/>
<Run Text=" "/>
<Run Text="{DynamicResource Pump.KLineClosed}"/>
</TextBlock>
</Border>
<!-- No pump selected banner (BoolToVis has no Invert support, so use a DataTrigger style) -->
<Border Background="#E3F2FD" BorderBrush="#2196F3" BorderThickness="0,0,0,1"
Padding="10,6">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsPumpSelected}" Value="False">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<TextBlock Text="{DynamicResource Pump.NoPumpSelected}"
FontSize="12" Foreground="#0D47A1"/>
</Border>
</StackPanel>
<!-- ══════════════════════════════════════════════════════════════
Body: sub-nav + sub-page content
══════════════════════════════════════════════════════════════ -->
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- ── Sub-nav rail ───────────────────────────────────────── -->
<Border Grid.Column="0" Background="#F7F7F7"
BorderBrush="#DDD" BorderThickness="0,0,1,0">
<ListBox SelectedValuePath="Tag" BorderThickness="0"
Background="Transparent"
ItemContainerStyle="{StaticResource SubNavItem}"
SelectedValue="{Binding SelectedSubPage, Mode=TwoWay}">
<ListBoxItem Tag="{x:Static vm:PumpSubPage.Identification}">
<TextBlock Text="{DynamicResource PumpSub.Identification}"
Style="{StaticResource SubNavText}"/>
</ListBoxItem>
<ListBoxItem Tag="{x:Static vm:PumpSubPage.Dtcs}"
IsEnabled="{Binding IsPumpSelected}">
<TextBlock Text="{DynamicResource PumpSub.Dtcs}"
Style="{StaticResource SubNavText}"/>
</ListBoxItem>
<ListBoxItem Tag="{x:Static vm:PumpSubPage.LiveData}"
IsEnabled="{Binding IsPumpSelected}">
<TextBlock Text="{DynamicResource PumpSub.LiveData}"
Style="{StaticResource SubNavText}"/>
</ListBoxItem>
<ListBoxItem Tag="{x:Static vm:PumpSubPage.Adaptation}"
IsEnabled="{Binding IsPumpSelected}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{DynamicResource PumpSub.Adaptation}"
Style="{StaticResource SubNavText}"/>
<TextBlock Text=" 🔒" FontSize="11"
VerticalAlignment="Center">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding DataContext.AdaptationAuth.IsAuthenticated,
RelativeSource={RelativeSource AncestorType=UserControl}}"
Value="True">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</ListBoxItem>
<ListBoxItem Tag="{x:Static vm:PumpSubPage.Unlock}">
<ListBoxItem.Style>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource SubNavItem}">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsUnlockApplicable}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBoxItem.Style>
<TextBlock Text="{DynamicResource PumpSub.Unlock}"
Style="{StaticResource SubNavText}"/>
</ListBoxItem>
</ListBox>
</Border>
<!-- ── Sub-page content ───────────────────────────────────── -->
<TabControl Grid.Column="1"
Style="{StaticResource HiddenTabsTabControl}"
SelectedIndex="{Binding SelectedSubPage, Converter={StaticResource EnumToInt}}">
<!-- 3a Identification -->
<TabItem>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<uc:PumpIdentificationPanelView
DataContext="{Binding Identification}"/>
</ScrollViewer>
</TabItem>
<!-- 3b DTCs -->
<TabItem>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<uc:DtcListView DataContext="{Binding DtcList}"/>
</ScrollViewer>
</TabItem>
<!-- 3c Live Data — DataContext stays as PumpPageViewModel so
the view can reach Root.PumpXxx and StatusDisplay1/2. -->
<TabItem>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<uc:PumpLiveDataView/>
</ScrollViewer>
</TabItem>
<!-- 3d Adaptation (auth-gated) -->
<TabItem>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<uc:AuthGateView DataContext="{Binding AdaptationAuth}">
<uc:AuthGateView.GatedContent>
<Border Background="#FAFAFA" BorderBrush="#DDD"
BorderThickness="1" CornerRadius="4"
Padding="12" Margin="6">
<StackPanel>
<TextBlock Text="{DynamicResource PumpSub.Adaptation}"
FontSize="15" FontWeight="SemiBold"
Foreground="#333" Margin="0,0,0,8"/>
<uc:DfiManageView DataContext="{Binding DataContext.DfiViewModel,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>
<Separator Margin="0,10"/>
<uc:PumpControlView DataContext="{Binding DataContext.PumpControl,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>
</StackPanel>
</Border>
</uc:AuthGateView.GatedContent>
</uc:AuthGateView>
</ScrollViewer>
</TabItem>
<!-- 3e Unlock -->
<TabItem>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<Grid>
<!-- "No active unlock" placeholder — visible when UnlockVm is null -->
<Border Background="#F5F5F5" BorderBrush="#CCC"
BorderThickness="1" CornerRadius="4"
Padding="20" Margin="6"
HorizontalAlignment="Center" VerticalAlignment="Center">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding UnlockVm}" Value="{x:Null}">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<TextBlock Text="{DynamicResource Pump.NoUnlockActive}"
FontSize="13" Foreground="#666"/>
</Border>
<!-- Active unlock panel — visible when UnlockVm is non-null.
DataContext binds to UnlockVm so `{Binding}` inside the
style evaluates to the VM instance (or null). -->
<uc:UnlockPanelView DataContext="{Binding UnlockVm}">
<uc:UnlockPanelView.Style>
<Style TargetType="uc:UnlockPanelView">
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</uc:UnlockPanelView.Style>
</uc:UnlockPanelView>
</Grid>
</ScrollViewer>
</TabItem>
</TabControl>
</Grid>
</Grid>
</UserControl>

View File

@@ -0,0 +1,16 @@
using System.Windows.Controls;
namespace HC_APTBS.Views.Pages
{
/// <summary>
/// Pump navigation page. DataContext is expected to be a
/// <see cref="HC_APTBS.ViewModels.Pages.PumpPageViewModel"/>.
/// </summary>
public partial class PumpPage : UserControl
{
public PumpPage()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,162 @@
<UserControl x:Class="HC_APTBS.Views.Pages.ResultsPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:uc="clr-namespace:HC_APTBS.Views.UserControls"
mc:Ignorable="d"
d:DesignHeight="700" d:DesignWidth="1100">
<!--
Results — review completed test runs, edit observations, export PDF.
DataContext: ResultsPageViewModel. No live hardware data on this page.
-->
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
<Style x:Key="DashCard" TargetType="Border">
<Setter Property="Background" Value="#FAFAFA"/>
<Setter Property="BorderBrush" Value="#DDD"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="CornerRadius" Value="4"/>
<Setter Property="Padding" Value="10"/>
<Setter Property="Margin" Value="4"/>
</Style>
<Style x:Key="DashHeader" TargetType="TextBlock">
<Setter Property="FontSize" Value="13"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="Foreground" Value="#333"/>
<Setter Property="Margin" Value="0,0,0,6"/>
</Style>
</UserControl.Resources>
<Grid Margin="6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="320"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- ── Left: history list ─────────────────────────────────────────── -->
<Border Grid.Column="0" Style="{StaticResource DashCard}">
<uc:ResultHistoryView/>
</Border>
<!-- ── Right: detail pane ──────────────────────────────────────────── -->
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <!-- header -->
<RowDefinition Height="*"/> <!-- results table -->
<RowDefinition Height="Auto"/> <!-- observations -->
<RowDefinition Height="Auto"/> <!-- actions -->
</Grid.RowDefinitions>
<!-- Selected-run header -->
<Border Grid.Row="0" Style="{StaticResource DashCard}"
Visibility="{Binding SelectedRun, Converter={StaticResource BoolToVis}}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Style="{StaticResource DashHeader}">
<Run Text="{Binding SelectedRun.PumpModel, Mode=OneWay}"/>
<Run Text=" · "/>
<Run Text="{Binding SelectedRun.PumpSerial, Mode=OneWay}"/>
</TextBlock>
<TextBlock FontSize="11" Foreground="#666"
Text="{Binding SelectedRun.CompletedAt, StringFormat='{}{0:dd MMM yyyy HH:mm:ss}'}"/>
<TextBlock FontSize="11" Foreground="#666" Margin="0,2,0,0"
Text="{Binding SelectedRun.TestNames}"/>
</StackPanel>
<!-- Overall verdict pill -->
<Border Grid.Column="1" Padding="8,3" CornerRadius="3"
VerticalAlignment="Center">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background" Value="#B71C1C"/>
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedRun.OverallPassed}" Value="True">
<Setter Property="Background" Value="#2E7D32"/>
</DataTrigger>
<DataTrigger Binding="{Binding SelectedRun.Interrupted}" Value="True">
<Setter Property="Background" Value="#FFB020"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<TextBlock Foreground="White" FontSize="13" FontWeight="Bold">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{DynamicResource Common.Fail}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedRun.OverallPassed}" Value="True">
<Setter Property="Text" Value="{DynamicResource Common.Pass}"/>
</DataTrigger>
<DataTrigger Binding="{Binding SelectedRun.Interrupted}" Value="True">
<Setter Property="Text" Value="{DynamicResource Results.InterruptedLabel}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Border>
</Grid>
</Border>
<!-- Embedded ResultDisplayView — bound to the page's Detail VM -->
<Border Grid.Row="1" Style="{StaticResource DashCard}"
Visibility="{Binding SelectedRun, Converter={StaticResource BoolToVis}}">
<uc:ResultDisplayView DataContext="{Binding Detail}"/>
</Border>
<!-- Observations / notes -->
<Border Grid.Row="2" Style="{StaticResource DashCard}"
Visibility="{Binding SelectedRun, Converter={StaticResource BoolToVis}}">
<StackPanel>
<TextBlock Text="{DynamicResource Results.ObservationsLabel}"
Style="{StaticResource DashHeader}"/>
<TextBox Text="{Binding SelectedRun.Observations, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
AcceptsReturn="True" TextWrapping="Wrap"
Height="80" VerticalScrollBarVisibility="Auto"
FontSize="12"/>
</StackPanel>
</Border>
<!-- Action row -->
<Border Grid.Row="3" Background="#F0F0F0" BorderBrush="#CCC"
BorderThickness="0,1,0,0" Padding="8" Margin="4,4,4,4"
Visibility="{Binding SelectedRun, Converter={StaticResource BoolToVis}}">
<DockPanel LastChildFill="False">
<Button DockPanel.Dock="Right"
Content="{DynamicResource Results.ExportButton}"
Command="{Binding ExportPdfCommand}"
Height="36" MinWidth="160" FontSize="13" FontWeight="Bold"
Foreground="White" Background="#1976D2" BorderBrush="#0D47A1"
BorderThickness="1"/>
</DockPanel>
</Border>
<!-- Empty-state when no selection (covers rows 0-3) -->
<Border Grid.Row="0" Grid.RowSpan="4"
VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Border.Style>
<Style TargetType="Border" BasedOn="{StaticResource DashCard}">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedRun}" Value="{x:Null}">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<TextBlock Text="{DynamicResource Results.NoSelection}"
HorizontalAlignment="Center" VerticalAlignment="Center"
FontSize="13" Foreground="#888" FontStyle="Italic"
TextWrapping="Wrap" TextAlignment="Center"/>
</Border>
</Grid>
</Grid>
</UserControl>

View File

@@ -0,0 +1,14 @@
using System.Windows.Controls;
namespace HC_APTBS.Views.Pages
{
/// <summary>
/// Results navigation page (§5 in <c>docs/ui-structure.md</c>).
/// Review, compare, and export completed test runs. DataContext must be a
/// <see cref="ViewModels.Pages.ResultsPageViewModel"/>.
/// </summary>
public partial class ResultsPage : UserControl
{
public ResultsPage() => InitializeComponent();
}
}

View File

@@ -0,0 +1,342 @@
<UserControl x:Class="HC_APTBS.Views.Pages.SettingsPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
FontFamily="Ebrima"
Background="#FFEDEDED"
d:DesignHeight="700" d:DesignWidth="980">
<!--
Settings page — form-based, grouped, tabbed.
DataContext: SettingsPageViewModel. Changes only apply on Save.
-->
<DockPanel Margin="12">
<!-- ── Bottom Save/Discard bar ───────────────────────────────────── -->
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal"
HorizontalAlignment="Right" Margin="0,12,0,0">
<Button Content="{DynamicResource Common.Accept}" Width="90" Height="28"
Margin="0,0,8,0" Command="{Binding SaveCommand}" IsDefault="True"/>
<Button Content="{DynamicResource Common.Cancel}" Width="90" Height="28"
Command="{Binding DiscardCommand}"/>
</StackPanel>
<!-- ── Tab control ───────────────────────────────────────────────── -->
<TabControl>
<!-- ══ General ══════════════════════════════════════════════════ -->
<TabItem Header="{DynamicResource Dialog.Settings.Tab.General}">
<StackPanel Margin="16">
<TextBlock Text="{DynamicResource Dialog.Settings.Language}"
FontWeight="SemiBold" Margin="0,0,0,4"/>
<ComboBox ItemsSource="{Binding AvailableLanguages}"
SelectedItem="{Binding SelectedLanguage}"
Width="120" HorizontalAlignment="Left"/>
<TextBlock Text="{DynamicResource Dialog.Settings.DaysKeepLogs}"
FontWeight="SemiBold" Margin="0,16,0,4"/>
<TextBox Text="{Binding DaysKeepLogs, UpdateSourceTrigger=LostFocus}"
Width="80" HorizontalAlignment="Left"/>
<TextBlock Text="{DynamicResource Dialog.Settings.UsersHeader}"
FontWeight="SemiBold" Margin="0,16,0,4"/>
<Button Content="{DynamicResource Dialog.Settings.ManageUsers}"
Command="{Binding ManageUsersCommand}"
Width="200" Height="26" HorizontalAlignment="Left"/>
</StackPanel>
</TabItem>
<!-- ══ Safety ═══════════════════════════════════════════════════ -->
<TabItem Header="{DynamicResource Dialog.Settings.Tab.Safety}">
<Grid Margin="16">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="120"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{DynamicResource Dialog.Settings.TempMax}"
VerticalAlignment="Center" Margin="0,0,12,6"/>
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding TempMax, UpdateSourceTrigger=LostFocus}"
Margin="0,0,0,6"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="{DynamicResource Dialog.Settings.TempMin}"
VerticalAlignment="Center" Margin="0,0,12,6"/>
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding TempMin, UpdateSourceTrigger=LostFocus}"
Margin="0,0,0,6"/>
<TextBlock Grid.Row="2" Grid.Column="0" Text="{DynamicResource Dialog.Settings.SecurityRpmLimit}"
VerticalAlignment="Center" Margin="0,0,12,6"/>
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding SecurityRpmLimit, UpdateSourceTrigger=LostFocus}"
Margin="0,0,0,6"/>
<TextBlock Grid.Row="3" Grid.Column="0" Text="{DynamicResource Dialog.Settings.MaxPressureBar}"
VerticalAlignment="Center" Margin="0,0,12,6"/>
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding MaxPressureBar, UpdateSourceTrigger=LostFocus}"
Margin="0,0,0,6"/>
<TextBlock Grid.Row="4" Grid.Column="0" Text="{DynamicResource Dialog.Settings.ToleranceUp}"
VerticalAlignment="Center" Margin="0,0,12,6"/>
<TextBox Grid.Row="4" Grid.Column="1" Text="{Binding ToleranceUpExtension, UpdateSourceTrigger=LostFocus}"
Margin="0,0,0,6"/>
<TextBlock Grid.Row="5" Grid.Column="0" Text="{DynamicResource Dialog.Settings.TolerancePfp}"
VerticalAlignment="Center" Margin="0,0,12,6"/>
<TextBox Grid.Row="5" Grid.Column="1" Text="{Binding TolerancePfpExtension, UpdateSourceTrigger=LostFocus}"
Margin="0,0,0,6"/>
<CheckBox Grid.Row="6" Grid.Column="0" Grid.ColumnSpan="2"
Content="{DynamicResource Dialog.Settings.IgnoreTin}"
IsChecked="{Binding DefaultIgnoreTin}" Margin="0,4,0,0"/>
</Grid>
</TabItem>
<!-- ══ PID ══════════════════════════════════════════════════════ -->
<TabItem Header="{DynamicResource Dialog.Settings.Tab.Pid}">
<Grid Margin="16">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="120"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{DynamicResource Dialog.Settings.PidP}"
VerticalAlignment="Center" Margin="0,0,12,6"/>
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding PidP, UpdateSourceTrigger=LostFocus}"
Margin="0,0,0,6"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="{DynamicResource Dialog.Settings.PidI}"
VerticalAlignment="Center" Margin="0,0,12,6"/>
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding PidI, UpdateSourceTrigger=LostFocus}"
Margin="0,0,0,6"/>
<TextBlock Grid.Row="2" Grid.Column="0" Text="{DynamicResource Dialog.Settings.PidD}"
VerticalAlignment="Center" Margin="0,0,12,6"/>
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding PidD, UpdateSourceTrigger=LostFocus}"
Margin="0,0,0,6"/>
<TextBlock Grid.Row="3" Grid.Column="0" Text="{DynamicResource Dialog.Settings.PidLoopMs}"
VerticalAlignment="Center" Margin="0,0,12,6"/>
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding PidLoopMs, UpdateSourceTrigger=LostFocus}"
Margin="0,0,0,6"/>
</Grid>
</TabItem>
<!-- ══ Motor ════════════════════════════════════════════════════ -->
<TabItem Header="{DynamicResource Dialog.Settings.Tab.Motor}">
<DockPanel Margin="16">
<!-- Top: motor parameters -->
<Grid DockPanel.Dock="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="120"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{DynamicResource Dialog.Settings.EncoderRes}"
VerticalAlignment="Center" Margin="0,0,12,6"/>
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding EncoderResolution, UpdateSourceTrigger=LostFocus}"
Margin="0,0,0,6"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="{DynamicResource Dialog.Settings.VoltMaxRpm}"
VerticalAlignment="Center" Margin="0,0,12,6"/>
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding VoltageForMaxRpm, UpdateSourceTrigger=LostFocus}"
Margin="0,0,0,6"/>
<TextBlock Grid.Row="2" Grid.Column="0" Text="{DynamicResource Dialog.Settings.MaxRpm}"
VerticalAlignment="Center" Margin="0,0,12,6"/>
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding MaxRpm, UpdateSourceTrigger=LostFocus}"
Margin="0,0,0,6"/>
<CheckBox Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2"
Content="{DynamicResource Dialog.Settings.RightRelay}"
IsChecked="{Binding RightRelayValue}" Margin="0,4,0,0"/>
</Grid>
<!-- RPM-Voltage relation table -->
<GroupBox Margin="0,12,0,0">
<GroupBox.Header>
<TextBlock Text="{DynamicResource Dialog.Settings.Relations}" FontWeight="SemiBold"/>
</GroupBox.Header>
<DockPanel>
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" Margin="0,4,0,0">
<Button Content="{DynamicResource Dialog.Settings.AddRow}"
Command="{Binding AddRelationCommand}"
Width="75" Margin="0,0,8,0"/>
<Button Content="{DynamicResource Dialog.Settings.RemoveRow}"
Command="{Binding RemoveRelationCommand}"
CommandParameter="{Binding SelectedItem, ElementName=RelationsGrid}"
Width="75"/>
</StackPanel>
<DataGrid x:Name="RelationsGrid"
ItemsSource="{Binding Relations}"
AutoGenerateColumns="False"
CanUserAddRows="False"
CanUserDeleteRows="False"
SelectionMode="Single"
HeadersVisibility="Column"
GridLinesVisibility="Horizontal"
MinHeight="120">
<DataGrid.Columns>
<DataGridTemplateColumn Width="*">
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{DynamicResource Dialog.Settings.RelRpm}"/>
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Rpm}" VerticalAlignment="Center" Margin="4,0"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding Rpm, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="*">
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{DynamicResource Dialog.Settings.RelVoltage}"/>
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Voltage}" VerticalAlignment="Center" Margin="4,0"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding Voltage, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</DockPanel>
</GroupBox>
</DockPanel>
</TabItem>
<!-- ══ Report ═══════════════════════════════════════════════════ -->
<TabItem Header="{DynamicResource Dialog.Settings.Tab.Report}">
<StackPanel Margin="16">
<TextBlock Text="{DynamicResource Dialog.Settings.CompanyName}"
FontWeight="SemiBold" Margin="0,0,0,4"/>
<TextBox Text="{Binding CompanyName, UpdateSourceTrigger=LostFocus}"/>
<TextBlock Text="{DynamicResource Dialog.Settings.CompanyInfo}"
FontWeight="SemiBold" Margin="0,12,0,4"/>
<TextBox Text="{Binding CompanyInfo, UpdateSourceTrigger=LostFocus}"
TextWrapping="Wrap" AcceptsReturn="True" Height="80"
VerticalScrollBarVisibility="Auto"/>
<TextBlock Text="{DynamicResource Dialog.Settings.ReportLogo}"
FontWeight="SemiBold" Margin="0,12,0,4"/>
<DockPanel>
<Button DockPanel.Dock="Right" Content="..." Width="30"
Margin="4,0,0,0" Command="{Binding BrowseLogoCommand}"/>
<TextBox Text="{Binding ReportLogoPath, UpdateSourceTrigger=LostFocus}"
IsReadOnly="True" Background="#F0F0F0"/>
</DockPanel>
</StackPanel>
</TabItem>
<!-- ══ K-Line ═══════════════════════════════════════════════════ -->
<TabItem Header="{DynamicResource Dialog.Settings.Tab.KLine}">
<StackPanel Margin="16">
<TextBlock Text="{DynamicResource Dialog.Settings.KLinePort}"
FontWeight="SemiBold" Margin="0,0,0,4"/>
<DockPanel>
<Button DockPanel.Dock="Right"
Content="{DynamicResource Dialog.Settings.RefreshPorts}"
Margin="8,0,0,0" Width="80"
Command="{Binding RefreshPortsCommand}"/>
<ComboBox ItemsSource="{Binding AvailablePorts}"
SelectedItem="{Binding SelectedKLinePort}"
IsEditable="True"
Text="{Binding SelectedKLinePort, UpdateSourceTrigger=LostFocus}"/>
</DockPanel>
<TextBlock Text="{DynamicResource Dialog.Settings.KLineHint}"
FontStyle="Italic" Foreground="Gray" Margin="0,8,0,0"
TextWrapping="Wrap"/>
</StackPanel>
</TabItem>
<!-- ══ Advanced ═════════════════════════════════════════════════ -->
<TabItem Header="{DynamicResource Dialog.Settings.Tab.Advanced}">
<Grid Margin="16">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="120"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{DynamicResource Dialog.Settings.RefreshBench}"
VerticalAlignment="Center" Margin="0,0,12,6"/>
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding RefreshBenchInterfaceMs, UpdateSourceTrigger=LostFocus}"
Margin="0,0,0,6"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="{DynamicResource Dialog.Settings.RefreshReading}"
VerticalAlignment="Center" Margin="0,0,12,6"/>
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding RefreshWhileReadingMs, UpdateSourceTrigger=LostFocus}"
Margin="0,0,0,6"/>
<TextBlock Grid.Row="2" Grid.Column="0" Text="{DynamicResource Dialog.Settings.RefreshCanBus}"
VerticalAlignment="Center" Margin="0,0,12,6"/>
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding RefreshCanBusReadMs, UpdateSourceTrigger=LostFocus}"
Margin="0,0,0,6"/>
<TextBlock Grid.Row="3" Grid.Column="0" Text="{DynamicResource Dialog.Settings.RefreshPumpReq}"
VerticalAlignment="Center" Margin="0,0,12,6"/>
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding RefreshPumpRequestMs, UpdateSourceTrigger=LostFocus}"
Margin="0,0,0,6"/>
<TextBlock Grid.Row="4" Grid.Column="0" Text="{DynamicResource Dialog.Settings.RefreshPumpParams}"
VerticalAlignment="Center" Margin="0,0,12,6"/>
<TextBox Grid.Row="4" Grid.Column="1" Text="{Binding RefreshPumpParamsMs, UpdateSourceTrigger=LostFocus}"
Margin="0,0,0,6"/>
<TextBlock Grid.Row="5" Grid.Column="0" Text="{DynamicResource Dialog.Settings.BlinkInterval}"
VerticalAlignment="Center" Margin="0,0,12,6"/>
<TextBox Grid.Row="5" Grid.Column="1" Text="{Binding BlinkIntervalMs, UpdateSourceTrigger=LostFocus}"
Margin="0,0,0,6"/>
<TextBlock Grid.Row="6" Grid.Column="0" Text="{DynamicResource Dialog.Settings.FlasherInterval}"
VerticalAlignment="Center" Margin="0,0,12,6"/>
<TextBox Grid.Row="6" Grid.Column="1" Text="{Binding FlasherIntervalMs, UpdateSourceTrigger=LostFocus}"
Margin="0,0,0,6"/>
</Grid>
</TabItem>
</TabControl>
</DockPanel>
</UserControl>

View File

@@ -0,0 +1,17 @@
using System.Windows.Controls;
namespace HC_APTBS.Views.Pages
{
/// <summary>
/// Settings navigation page. Hosts grouped configuration forms (General,
/// Safety, PID, Motor, Report, K-Line, Advanced). DataContext is
/// <see cref="HC_APTBS.ViewModels.Pages.SettingsPageViewModel"/>.
/// </summary>
public partial class SettingsPage : UserControl
{
public SettingsPage()
{
InitializeComponent();
}
}
}

204
Views/Pages/TestsPage.xaml Normal file
View File

@@ -0,0 +1,204 @@
<UserControl x:Class="HC_APTBS.Views.Pages.TestsPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="clr-namespace:HC_APTBS.Models"
xmlns:uc="clr-namespace:HC_APTBS.Views.UserControls"
xmlns:vm="clr-namespace:HC_APTBS.ViewModels"
xmlns:vmp="clr-namespace:HC_APTBS.ViewModels.Pages"
mc:Ignorable="d"
d:DesignHeight="800" d:DesignWidth="1000"
d:DataContext="{d:DesignInstance Type=vmp:TestsPageViewModel, IsDesignTimeCreatable=False}">
<!--
Plan → Preconditions → Running → Done wizard (ui-structure.md §4).
DataContext: TestsPageViewModel.
Top row: 4-pill stepper. Middle: ContentControl routing CurrentStateVm
through typed DataTemplates. Bottom: wizard footer with Back / Next.
-->
<UserControl.Resources>
<!-- Step → View mappings -->
<DataTemplate DataType="{x:Type vmp:PlanStateViewModel}">
<uc:TestPlanView DataContext="{Binding TestPanel}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:TestPreconditionsViewModel}">
<uc:TestPreconditionsView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vmp:RunningStateViewModel}">
<uc:TestRunningView DataContext="{Binding TestPanel}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type vmp:TestsPageViewModel}">
<uc:TestDoneView/>
</DataTemplate>
<!-- Stepper pill style -->
<Style x:Key="StepPillStyle" TargetType="Border">
<Setter Property="CornerRadius" Value="12"/>
<Setter Property="Padding" Value="14,4"/>
<Setter Property="Margin" Value="0,0,6,0"/>
<Setter Property="Background" Value="#ECEFF1"/>
<Setter Property="BorderBrush" Value="#CFD8DC"/>
<Setter Property="BorderThickness" Value="1"/>
</Style>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Wizard stepper -->
<Border Background="White" BorderBrush="#DDD" BorderThickness="0,0,0,1" Padding="10,8">
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<Border Style="{StaticResource StepPillStyle}">
<Border.Resources>
<Style TargetType="Border" BasedOn="{StaticResource StepPillStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding CurrentState}"
Value="{x:Static models:TestFlowState.Plan}">
<Setter Property="Background" Value="#1565C0"/>
<Setter Property="BorderBrush" Value="#0D47A1"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Resources>
<TextBlock Text="{DynamicResource Test.Wizard.Plan}" FontSize="12">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="#455A64"/>
<Style.Triggers>
<DataTrigger Binding="{Binding CurrentState}"
Value="{x:Static models:TestFlowState.Plan}">
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontWeight" Value="SemiBold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Border>
<Border Style="{StaticResource StepPillStyle}">
<Border.Resources>
<Style TargetType="Border" BasedOn="{StaticResource StepPillStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding CurrentState}"
Value="{x:Static models:TestFlowState.Preconditions}">
<Setter Property="Background" Value="#1565C0"/>
<Setter Property="BorderBrush" Value="#0D47A1"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Resources>
<TextBlock Text="{DynamicResource Test.Wizard.Preconditions}" FontSize="12">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="#455A64"/>
<Style.Triggers>
<DataTrigger Binding="{Binding CurrentState}"
Value="{x:Static models:TestFlowState.Preconditions}">
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontWeight" Value="SemiBold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Border>
<Border Style="{StaticResource StepPillStyle}">
<Border.Resources>
<Style TargetType="Border" BasedOn="{StaticResource StepPillStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding CurrentState}"
Value="{x:Static models:TestFlowState.Running}">
<Setter Property="Background" Value="#1565C0"/>
<Setter Property="BorderBrush" Value="#0D47A1"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Resources>
<TextBlock Text="{DynamicResource Test.Wizard.Running}" FontSize="12">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="#455A64"/>
<Style.Triggers>
<DataTrigger Binding="{Binding CurrentState}"
Value="{x:Static models:TestFlowState.Running}">
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontWeight" Value="SemiBold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Border>
<Border Style="{StaticResource StepPillStyle}">
<Border.Resources>
<Style TargetType="Border" BasedOn="{StaticResource StepPillStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding CurrentState}"
Value="{x:Static models:TestFlowState.Done}">
<Setter Property="Background" Value="#1565C0"/>
<Setter Property="BorderBrush" Value="#0D47A1"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Resources>
<TextBlock Text="{DynamicResource Test.Wizard.Done}" FontSize="12">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="#455A64"/>
<Style.Triggers>
<DataTrigger Binding="{Binding CurrentState}"
Value="{x:Static models:TestFlowState.Done}">
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontWeight" Value="SemiBold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Border>
</ItemsControl>
</Border>
<!-- Current step body -->
<ContentControl Grid.Row="1" Content="{Binding CurrentStateVm}"/>
<!-- Wizard footer: Back / Next -->
<Border Grid.Row="2" Padding="10,8"
Background="White" BorderBrush="#DDD" BorderThickness="0,1,0,0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0"
Content="{DynamicResource Test.Wizard.Back}"
Command="{Binding BackCommand}"
Padding="16,6" FontSize="12"/>
<Button Grid.Column="2"
Content="{DynamicResource Test.Wizard.Next}"
Command="{Binding NextCommand}"
Padding="16,6" FontSize="12"
FontWeight="SemiBold"/>
</Grid>
</Border>
</Grid>
</UserControl>

View File

@@ -0,0 +1,16 @@
using System.Windows.Controls;
namespace HC_APTBS.Views.Pages
{
/// <summary>
/// Tests navigation page. DataContext is expected to be a
/// <see cref="HC_APTBS.ViewModels.Pages.TestsPageViewModel"/>.
/// </summary>
public partial class TestsPage : UserControl
{
public TestsPage()
{
InitializeComponent();
}
}
}