UserControls/HistorySearchBoxControl.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using ChatworkBulkSender.Utils;

namespace ChatworkBulkSender.UserControls
{
    public partial class HistorySearchBoxControl : UserControl
    {
        public event EventHandler<Dictionary<string, object>> SearchRequested;

        private int _pnlSearchCurrentX;
        
        // エラー表示用ツールチップ
        private ToolTip errorToolTip = new ToolTip();
        
        // 日付が設定されているかどうかを追跡するフラグ
        private bool _startDateSet = false;
        private bool _endDateSet = false;

        public HistorySearchBoxControl()
        {
            InitializeComponent();

            _pnlSearchCurrentX = panelSearchBox.Width;
            
            // デフォルト表示は空文字
            dateTimePickerStart.CustomFormat = " ";
            dateTimePickerEnd.CustomFormat = " ";

            rdoScheduleSendSelection.Checked = true;

            // 検索ボタンのイベントハンドラを設定
            btnSearch.Click += BtnSearch_Click;
            
            // 管理番号テキストボックスのイベントハンドラを設定
            mgmtNumberTxtBox.TextChanged += MgmtNumberTxtBox_TextChanged;
            mgmtNumberTxtBox.KeyPress += MgmtNumberTxtBox_KeyPress;
            
            // DateTimePickerのイベントハンドラを設定
            dateTimePickerStart.KeyDown += DateTimePickerStart_KeyDown;
            dateTimePickerEnd.KeyDown += DateTimePickerEnd_KeyDown;
            dateTimePickerStart.CloseUp += DateTimePickerStart_CloseUp;
            dateTimePickerEnd.CloseUp += DateTimePickerEnd_CloseUp;
            dateTimePickerStart.KeyPress += DateTimePickerStart_KeyPress;
            dateTimePickerEnd.KeyPress += DateTimePickerEnd_KeyPress;
            
            // 各テキストボックスの最大文字数を設定
            mgmtNumberTxtBox.MaxLength = 10;      // 管理番号は10桁まで
            PattrernNameTxtBox.MaxLength = 50;    // パターン名称は50文字まで
            customerNameTxtBox.MaxLength = 100;   // 顧客名は100文字まで

            // Dockを使用して親パネル全体を使用
            this.Dock = DockStyle.Fill;
        }

        /// <summary>
        /// 選択時に日付を西暦/月/日のフォーマットで表示する。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void dateTimePickerStart_ValueChanged(object sender, EventArgs e)
        {
            dateTimePickerStart.CustomFormat = "yyyy/MM/dd";
            _startDateSet = true;
        }

        /// <summary>
        /// 選択時に日付を西暦/月/日のフォーマットで表示する。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void dateTimePickerEnd_ValueChanged(object sender, EventArgs e)
        {
            dateTimePickerEnd.CustomFormat = "yyyy/MM/dd";
            _endDateSet = true;
        }

        private void panelSearchBox_Resize(object sender, EventArgs e)
        {
            int Width = _pnlSearchCurrentX - panelSearchBox.Width;

            customerNameTxtBox.Width -= Width;
        }
        
        /// <summary>
        /// 開始日付のKeyDownイベントハンドラ
        /// DeleteキーまたはBackspaceキーで日付をクリアする
        /// </summary>
        private void DateTimePickerStart_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Delete || e.KeyCode == Keys.Back)
            {
                dateTimePickerStart.CustomFormat = " ";
                _startDateSet = false;
                e.Handled = true;
                e.SuppressKeyPress = true;
            }
        }
        
        /// <summary>
        /// 終了日付のKeyDownイベントハンドラ
        /// DeleteキーまたはBackspaceキーで日付をクリアする
        /// </summary>
        private void DateTimePickerEnd_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Delete || e.KeyCode == Keys.Back)
            {
                dateTimePickerEnd.CustomFormat = " ";
                _endDateSet = false;
                e.Handled = true;
                e.SuppressKeyPress = true;
            }
        }
        
        /// <summary>
        /// 開始日付のCloseUpイベントハンドラ
        /// カレンダーから日付を選択したときの処理
        /// </summary>
        private void DateTimePickerStart_CloseUp(object sender, EventArgs e)
        {
            dateTimePickerStart.CustomFormat = "yyyy/MM/dd";
            _startDateSet = true;
        }
        
        /// <summary>
        /// 終了日付のCloseUpイベントハンドラ
        /// カレンダーから日付を選択したときの処理
        /// </summary>
        private void DateTimePickerEnd_CloseUp(object sender, EventArgs e)
        {
            dateTimePickerEnd.CustomFormat = "yyyy/MM/dd";
            _endDateSet = true;
        }
        
        /// <summary>
        /// 開始日付のKeyPressイベントハンドラ
        /// 何か入力があったら日付フォーマットを有効化
        /// </summary>
        private void DateTimePickerStart_KeyPress(object sender, KeyPressEventArgs e)
        {
            // 数字が入力されたら日付フォーマットを有効化
            if (char.IsDigit(e.KeyChar) && !_startDateSet)
            {
                dateTimePickerStart.CustomFormat = "yyyy/MM/dd";
                _startDateSet = true;
            }
        }
        
        /// <summary>
        /// 終了日付のKeyPressイベントハンドラ
        /// 何か入力があったら日付フォーマットを有効化
        /// </summary>
        private void DateTimePickerEnd_KeyPress(object sender, KeyPressEventArgs e)
        {
            // 数字が入力されたら日付フォーマットを有効化
            if (char.IsDigit(e.KeyChar) && !_endDateSet)
            {
                dateTimePickerEnd.CustomFormat = "yyyy/MM/dd";
                _endDateSet = true;
            }
        }

        /// <summary>
        /// 管理番号テキストボックスのKeyPressイベントハンドラ
        /// </summary>
        private void MgmtNumberTxtBox_KeyPress(object sender, KeyPressEventArgs e)
        {
            // バックスペース、Delete、Ctrl+V は許可
            if (e.KeyChar == '\b' || char.IsControl(e.KeyChar))
            {
                return;
            }

            // 数字以外の入力時
            if (!char.IsDigit(e.KeyChar))
            {
                e.Handled = true;
                
                // ツールチップでエラーメッセージを表示
                errorToolTip.Show("半角数字のみ入力可能です", mgmtNumberTxtBox, 0, -25, 2000);
            }
        }

        /// <summary>
        /// 管理番号テキストボックスのTextChangedイベントハンドラ
        /// </summary>
        private void MgmtNumberTxtBox_TextChanged(object sender, EventArgs e)
        {
            // 先頭のゼロを削除する処理
            if (!string.IsNullOrWhiteSpace(mgmtNumberTxtBox.Text))
            {
                string text = mgmtNumberTxtBox.Text;
                
                // 先頭の0を削除(001 → 1)
                if (text.Length > 1 && text.StartsWith("0"))
                {
                    if (int.TryParse(text, out int number))
                    {
                        mgmtNumberTxtBox.Text = number.ToString();
                        // カーソルを末尾に移動
                        mgmtNumberTxtBox.SelectionStart = mgmtNumberTxtBox.Text.Length;
                    }
                }
                
                // バリデーション
                if (!ValidationHelperUtil.IsValidManagementNumber(mgmtNumberTxtBox.Text))
                {
                    // 背景色を薄い赤にして視覚的にエラーを示す
                    mgmtNumberTxtBox.BackColor = Color.FromArgb(255, 230, 230);
                    errorToolTip.Show(ValidationHelperUtil.GetManagementNumberFormatErrorMessage(), 
                        mgmtNumberTxtBox, 0, -25, 3000);
                }
                else
                {
                    // 正常な場合は背景色を元に戻す
                    mgmtNumberTxtBox.BackColor = SystemColors.Window;
                    errorToolTip.Hide(mgmtNumberTxtBox);
                }
            }
            else
            {
                // 空の場合も背景色を元に戻す
                mgmtNumberTxtBox.BackColor = SystemColors.Window;
                errorToolTip.Hide(mgmtNumberTxtBox);
            }
        }

        /// <summary>
        /// 検索ボタンクリック
        /// </summary>
        private void BtnSearch_Click(object sender, EventArgs e)
        {
            // バリデーション実行
            string validationError;
            if (!ValidateSearchCriteria(out validationError))
            {
                MessageBox.Show(validationError, "入力エラー", 
                    MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }
            
            var criteria = GetSearchCriteria();
            SearchRequested?.Invoke(this, criteria);
        }

        /// <summary>
        /// 検索条件を取得
        /// </summary>
        private Dictionary<string, object> GetSearchCriteria()
        {
            var criteria = new Dictionary<string, object>();

            // 管理番号
            if (!string.IsNullOrWhiteSpace(mgmtNumberTxtBox.Text))
            {
                criteria["ManagementNumber"] = mgmtNumberTxtBox.Text.Trim();
            }

            // パターン名称
            if (!string.IsNullOrWhiteSpace(PattrernNameTxtBox.Text))
            {
                criteria["PatternName"] = PattrernNameTxtBox.Text.Trim();
            }

            // 顧客名
            if (!string.IsNullOrWhiteSpace(customerNameTxtBox.Text))
            {
                criteria["CustomerName"] = customerNameTxtBox.Text.Trim();
            }

            // 送信日時(開始)
            if (_startDateSet)
            {
                criteria["StartDate"] = dateTimePickerStart.Value.Date;
            }

            // 送信日時(終了)
            if (_endDateSet)
            {
                criteria["EndDate"] = dateTimePickerEnd.Value.Date.AddDays(1).AddSeconds(-1);
            }

            // 送信タイプ
            if (rdoScheduleSendSelection.Checked)
            {
                criteria["SendType"] = 0; // 定期送信
            }
            else if (rdoManualSendSelection.Checked)
            {
                criteria["SendType"] = 1; // 不定期送信
            }

            return criteria;
        }

        /// <summary>
        /// 検索条件のバリデーション
        /// </summary>
        /// <param name="errorMessage">エラーメッセージ</param>
        /// <returns>バリデーション成功時はtrue</returns>
        private bool ValidateSearchCriteria(out string errorMessage)
        {
            errorMessage = string.Empty;
            var errors = new List<string>();
            
            // 管理番号のバリデーション
            if (!string.IsNullOrWhiteSpace(mgmtNumberTxtBox.Text))
            {
                if (!ValidationHelperUtil.IsValidManagementNumber(mgmtNumberTxtBox.Text))
                {
                    errors.Add(ValidationHelperUtil.GetManagementNumberFormatErrorMessage());
                }
            }
            
            // 日付範囲のバリデーション
            if (_startDateSet && _endDateSet)
            {
                if (dateTimePickerStart.Value.Date > dateTimePickerEnd.Value.Date)
                {
                    errors.Add("送信日時の開始日は終了日以前の日付を指定してください。");
                }
            }
            
            if (errors.Any())
            {
                errorMessage = string.Join("\n", errors);
                return false;
            }
            
            return true;
        }

    }
}