using System; using System.IO; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Threading; using Housing.Services; namespace Housing.Login { public sealed class StartupLoginWindowResult { public string Maker { get; set; } = string.Empty; public string Model { get; set; } = string.Empty; public string Variant1 { get; set; } = string.Empty; public string Variant2 { get; set; } = string.Empty; public string Operator { get; set; } = string.Empty; public string Password { get; set; } = string.Empty; public string LineNo { get; set; } = string.Empty; public string LotNo { get; set; } = string.Empty; public string JigNo { get; set; } = string.Empty; } public partial class StartupLoginWindow : Window { private const string OperatorHistoryFieldName = "Operator"; private const string SuppressedOperatorHistoryValue = "su"; private readonly LoginHistoryStore _loginHistoryStore; private Popup _openHistoryPopup; public StartupLoginWindowResult Result { get; private set; } = new StartupLoginWindowResult(); public StartupLoginWindow() { _loginHistoryStore = LoginHistoryStore.Load(GetLoginHistoryPath()); RemoveSuppressedOperatorHistory(); InitializeComponent(); AttachLoginHistoryMenus(); this.PasswordBox.GotKeyboardFocus += (s, ev) => CloseHistoryPopup(); LoginButton.GotKeyboardFocus += (s, ev) => CloseHistoryPopup(); OperatorTextBox.Focus(); } private async void LogInButton_Click(object sender, RoutedEventArgs e) { CloseHistoryPopup(); if (!TryReadRequiredFields(out var loginResult)) { return; } if (sender is Button button) { button.IsEnabled = false; } try { var loginSettings = LoginSettings.Load(GetDatabaseIniPath()); if (loginSettings.OfflinePreview) { Result = loginResult; SaveLoginHistory(loginResult); DialogResult = true; Close(); return; } var repository = new LoginAccountRepository( DatabaseSettings.Load(GetDatabaseIniPath()), loginSettings); if (!await repository.ExistsAsync(loginResult.Operator, loginResult.Password)) { MessageBox.Show(this, "DB에 등록된 ID/PW가 아닙니다.", "Login", MessageBoxButton.OK, MessageBoxImage.Error); this.PasswordBox.Clear(); this.PasswordBox.Focus(); return; } Result = loginResult; SaveLoginHistory(loginResult); DialogResult = true; Close(); } catch (Exception ex) { MessageBox.Show(this, $"로그인 DB 확인 실패: {ex.Message}", "Login", MessageBoxButton.OK, MessageBoxImage.Error); OperatorTextBox.Focus(); OperatorTextBox.SelectAll(); } finally { if (sender is Button loginButton) { loginButton.IsEnabled = true; } } } private bool TryReadRequiredFields(out StartupLoginWindowResult result) { result = new StartupLoginWindowResult(); if (!TryReadTextBox(MakerTextBox, "Maker", out var maker) || !TryReadTextBox(ModelTextBox, "Model", out var model) || !TryReadTextBox(Variant1TextBox, "Variant 1", out var variant1) || !TryReadTextBox(Variant2TextBox, "Variant 2", out var variant2) || !TryReadTextBox(OperatorTextBox, "ID", out var loginId) || !TryReadPasswordBox(this.PasswordBox, "PW", out var password) || !TryReadTextBox(LineNoTextBox, "Line", out var lineNo) || !TryReadTextBox(LotNoTextBox, "Lot No", out var lotNo) || !TryReadTextBox(JigNoTextBox, "Jig No", out var jigNo)) { return false; } result = new StartupLoginWindowResult { Maker = maker, Model = model, Variant1 = variant1, Variant2 = variant2, Operator = loginId, Password = password, LineNo = lineNo, LotNo = lotNo, JigNo = jigNo }; return true; } private void AttachLoginHistoryMenus() { AttachHistoryMenu(MakerTextBox, "Maker", ModelTextBox); AttachHistoryMenu(ModelTextBox, "Model", Variant1TextBox); AttachHistoryMenu(Variant1TextBox, "Variant1", Variant2TextBox); AttachHistoryMenu(Variant2TextBox, "Variant2", OperatorTextBox); AttachHistoryMenu(OperatorTextBox, OperatorHistoryFieldName, this.PasswordBox); AttachHistoryMenu(LineNoTextBox, "LineNo", LotNoTextBox); AttachHistoryMenu(LotNoTextBox, "LotNo", JigNoTextBox); AttachHistoryMenu(JigNoTextBox, "JigNo", LoginButton); } private void AttachHistoryMenu(TextBox textBox, string fieldName, Control nextFocusControl) { textBox.GotKeyboardFocus += (s, ev) => ShowHistoryMenu(textBox, fieldName, nextFocusControl); textBox.PreviewMouseLeftButtonDown += (s, ev) => { if (textBox.IsKeyboardFocusWithin) { ShowHistoryMenu(textBox, fieldName, nextFocusControl); } }; } private void ShowHistoryMenu(TextBox textBox, string fieldName, Control nextFocusControl) { var values = _loginHistoryStore.GetValues(fieldName); if (values.Count == 0) { CloseHistoryPopup(); return; } CloseHistoryPopup(); var menuWidth = Math.Max(160, textBox.ActualWidth); var stackPanel = new StackPanel(); var popup = new Popup { PlacementTarget = textBox, Placement = PlacementMode.Bottom, HorizontalOffset = 0, StaysOpen = true, AllowsTransparency = true, Focusable = false }; foreach (var value in values) { var item = new Button { Content = value, MinWidth = menuWidth, Padding = new Thickness(10, 6, 10, 6), BorderThickness = new Thickness(0), Background = textBox.Background, Foreground = textBox.Foreground, HorizontalContentAlignment = HorizontalAlignment.Left }; item.Click += (s, ev) => { textBox.Text = value; textBox.CaretIndex = textBox.Text.Length; CloseHistoryPopup(); FocusControl(nextFocusControl); }; stackPanel.Children.Add(item); } popup.Child = new Border { Width = menuWidth, MaxHeight = 180, Background = textBox.Background, BorderBrush = textBox.BorderBrush, BorderThickness = new Thickness(1), Child = new ScrollViewer { MaxHeight = 180, VerticalScrollBarVisibility = ScrollBarVisibility.Auto, Content = stackPanel } }; _openHistoryPopup = popup; popup.IsOpen = true; } private void CloseHistoryPopup() { if (_openHistoryPopup != null) { _openHistoryPopup.IsOpen = false; _openHistoryPopup = null; } } private void FocusControl(Control control) { Dispatcher.BeginInvoke(new Action(() => { control.Focus(); if (control is TextBox textBox) { textBox.CaretIndex = textBox.Text.Length; } else if (control is PasswordBox passwordBox) { passwordBox.Focus(); } }), DispatcherPriority.Input); } private void SaveLoginHistory(StartupLoginWindowResult result) { _loginHistoryStore.Remember("Maker", result.Maker); _loginHistoryStore.Remember("Model", result.Model); _loginHistoryStore.Remember("Variant1", result.Variant1); _loginHistoryStore.Remember("Variant2", result.Variant2); RememberOperatorHistory(result.Operator); _loginHistoryStore.Remember("LineNo", result.LineNo); _loginHistoryStore.Remember("LotNo", result.LotNo); _loginHistoryStore.Remember("JigNo", result.JigNo); _loginHistoryStore.Save(); } private void RememberOperatorHistory(string operatorId) { if (IsSuppressedOperatorHistoryValue(operatorId)) { _loginHistoryStore.Forget(OperatorHistoryFieldName, operatorId); return; } _loginHistoryStore.Remember(OperatorHistoryFieldName, operatorId); } private void RemoveSuppressedOperatorHistory() { try { if (_loginHistoryStore.Forget(OperatorHistoryFieldName, SuppressedOperatorHistoryValue)) { _loginHistoryStore.Save(); } } catch { // Login history cleanup is optional and should not block the login window. } } private static bool IsSuppressedOperatorHistoryValue(string value) { return string.Equals(value.Trim(), SuppressedOperatorHistoryValue, StringComparison.OrdinalIgnoreCase); } private bool TryReadTextBox(TextBox textBox, string fieldName, out string value) { value = textBox.Text.Trim(); if (!string.IsNullOrWhiteSpace(value)) { return true; } MessageBox.Show(this, $"{fieldName}을(를) 입력하세요.", "Login", MessageBoxButton.OK, MessageBoxImage.Warning); textBox.Focus(); textBox.SelectAll(); return false; } private bool TryReadPasswordBox(PasswordBox passwordBox, string fieldName, out string value) { value = passwordBox.Password; if (!string.IsNullOrWhiteSpace(value)) { return true; } MessageBox.Show(this, $"{fieldName}을(를) 입력하세요.", "Login", MessageBoxButton.OK, MessageBoxImage.Warning); passwordBox.Focus(); passwordBox.SelectAll(); return false; } private static string GetDatabaseIniPath() { return Path.Combine(AppContext.BaseDirectory, "Database.ini"); } private static string GetLoginHistoryPath() { return Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Housing", "LoginHistory.json"); } } }