feat: move test buttons to right panel, add user auth dialog for reports

Move Start/Stop/Report buttons from the middle panel to the top of
TestPanelView (automated tests section), matching the old application
layout. Remove inline Operator/Client text fields — operator identity
now comes from a UserCheckDialog (username/password) shown before the
existing ReportDialog. Add credential storage to ConfigurationService.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-11 16:41:20 +02:00
parent d34e81163a
commit 4964806de1
10 changed files with 267 additions and 53 deletions

View File

@@ -0,0 +1,81 @@
using System.Windows;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using HC_APTBS.Services;
namespace HC_APTBS.ViewModels.Dialogs
{
/// <summary>
/// ViewModel for the user authentication dialog shown before report generation.
/// Validates operator credentials against the stored user list.
/// </summary>
public sealed partial class UserCheckViewModel : ObservableObject
{
private readonly IConfigurationService _config;
/// <summary>Initialises the dialog, optionally pre-filling the last used username.</summary>
public UserCheckViewModel(IConfigurationService config, string lastUsername = "")
{
_config = config;
_username = lastUsername;
}
// ── Bindable properties ───────────────────────────────────────────────────
/// <summary>Username entered by the operator.</summary>
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(AcceptCommand))]
private string _username = string.Empty;
/// <summary>Password entered by the operator (set from code-behind PasswordChanged handler).</summary>
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(AcceptCommand))]
private string _password = string.Empty;
// ── Dialog result ─────────────────────────────────────────────────────────
/// <summary>True if the user authenticated successfully.</summary>
public bool Accepted { get; private set; }
/// <summary>The validated username, available after <see cref="Accepted"/> is true.</summary>
public string AuthenticatedUser { get; private set; } = string.Empty;
// ── Commands ──────────────────────────────────────────────────────────────
/// <summary>Validates credentials and closes the dialog on success.</summary>
[RelayCommand(CanExecute = nameof(CanAccept))]
private void Accept()
{
if (_config.ValidateUser(Username, Password))
{
AuthenticatedUser = Username;
Accepted = true;
RequestClose?.Invoke();
}
else
{
MessageBox.Show(
"Invalid username or password.\n(Both are case-sensitive.)",
"Authentication Error",
MessageBoxButton.OK,
MessageBoxImage.Stop);
}
}
private bool CanAccept() => !string.IsNullOrWhiteSpace(Username)
&& !string.IsNullOrEmpty(Password);
/// <summary>Cancels authentication and closes the dialog.</summary>
[RelayCommand]
private void Cancel()
{
Accepted = false;
RequestClose?.Invoke();
}
// ── Events ────────────────────────────────────────────────────────────────
/// <summary>Raised when the dialog should close itself.</summary>
public event System.Action? RequestClose;
}
}

View File

@@ -9,6 +9,8 @@ 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
{
@@ -43,6 +45,9 @@ namespace HC_APTBS.ViewModels
private CancellationTokenSource? _testCts;
/// <summary>Remembers the last authenticated username to pre-fill the next auth dialog.</summary>
private string _lastAuthenticatedUser = string.Empty;
// ── Child ViewModels ──────────────────────────────────────────────────────
/// <summary>ViewModel for pump selection and K-Line ECU identification.</summary>
@@ -331,14 +336,6 @@ namespace HC_APTBS.ViewModels
/// <summary>Verbose status message from bench/test operations.</summary>
[ObservableProperty] private string _verboseStatus = string.Empty;
// ── Operator / client info ────────────────────────────────────────────────
/// <summary>Operator name for report generation.</summary>
[ObservableProperty] private string _operatorName = string.Empty;
/// <summary>Client name for report generation.</summary>
[ObservableProperty] private string _clientName = string.Empty;
// ── Test saved state ──────────────────────────────────────────────────────
/// <summary>True when the current test results have been saved to a report.</summary>
@@ -407,14 +404,28 @@ namespace HC_APTBS.ViewModels
private void GenerateReport()
{
if (CurrentPump == null) return;
// Step 1: Authenticate operator.
var authVm = new UserCheckViewModel(_config, _lastAuthenticatedUser);
var authDlg = new UserCheckDialog(authVm) { Owner = Application.Current.MainWindow };
authDlg.ShowDialog();
if (!authVm.Accepted) return;
_lastAuthenticatedUser = authVm.AuthenticatedUser;
// Step 2: Collect report details (client, company, observations).
var reportVm = new ReportViewModel(_config) { OperatorName = authVm.AuthenticatedUser };
var reportDlg = new ReportDialog(reportVm) { Owner = Application.Current.MainWindow };
reportDlg.ShowDialog();
if (!reportVm.Accepted) return;
try
{
string desktop = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
string path = _pdf.GenerateReport(CurrentPump, OperatorName, ClientName, desktop);
string path = _pdf.GenerateReport(
CurrentPump, reportVm.OperatorName, reportVm.SelectedClientName, desktop);
_log.Info(LogId, $"Report saved: {path}");
IsTestSaved = true;
// Open the generated PDF with the default viewer.
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(path)
{ UseShellExecute = true });
}