diff --git a/src/ElectronicPharmacy/UserControls/UCPriceList.cs b/src/ElectronicPharmacy/UserControls/UCPriceList.cs index 53ec0e0..6bad437 100644 --- a/src/ElectronicPharmacy/UserControls/UCPriceList.cs +++ b/src/ElectronicPharmacy/UserControls/UCPriceList.cs @@ -49,6 +49,11 @@ namespace Электронная_Фармация.UserControls ModernSplitContainer splitNameMgBrowse; ModernDataGridView dgvBrowseNames; ModernDataGridView dgvBrowseMg; + List _uniqueBaseNames = new List(); + Dictionary> _mgByBaseName = + new Dictionary>(StringComparer.OrdinalIgnoreCase); + Timer _searchDebounceTimer; + const int SearchDebounceMs = 180; static readonly Regex DosageRegex = new Regex( @"(\d+(?:[.,]\d+)?\s*(?:мг|мкг|г|мл|МЕ|ме)\b)", RegexOptions.IgnoreCase | RegexOptions.Compiled); @@ -407,11 +412,77 @@ namespace Электронная_Фармация.UserControls EnsureDerivedBrowseColumns(tablePrice); _fullPriceTable = tablePrice; + RebuildBrowseIndex(tablePrice); _selectedBaseName = string.Empty; _selectedMg = string.Empty; ShowBrowseView(); } + private void RebuildBrowseIndex(DataTable tablePrice) + { + var names = new SortedSet(StringComparer.OrdinalIgnoreCase); + var mgMap = new Dictionary>(StringComparer.OrdinalIgnoreCase); + + foreach (DataRow row in tablePrice.Rows) + { + var baseName = row["Базовое наименование"]?.ToString() ?? string.Empty; + if (string.IsNullOrWhiteSpace(baseName)) + { + continue; + } + + names.Add(baseName); + if (!mgMap.TryGetValue(baseName, out var dosages)) + { + dosages = new SortedSet(StringComparer.OrdinalIgnoreCase); + mgMap[baseName] = dosages; + } + + dosages.Add(row["МГ"]?.ToString() ?? string.Empty); + } + + _uniqueBaseNames = names.ToList(); + _mgByBaseName = new Dictionary>(StringComparer.OrdinalIgnoreCase); + foreach (var pair in mgMap) + { + var list = new List(pair.Value.Count); + foreach (var dosage in pair.Value) + { + list.Add(string.IsNullOrEmpty(dosage) ? EmptyMgDisplay : dosage); + } + + _mgByBaseName[pair.Key] = list; + } + } + + private void EnsureSearchDebounceTimer() + { + if (_searchDebounceTimer != null) + { + return; + } + + _searchDebounceTimer = new Timer { Interval = SearchDebounceMs }; + _searchDebounceTimer.Tick += (_, __) => + { + _searchDebounceTimer.Stop(); + ResetBrowseToNames(); + }; + } + + private void ScheduleBrowseSearchRefresh(bool immediate = false) + { + EnsureSearchDebounceTimer(); + _searchDebounceTimer.Stop(); + if (immediate) + { + ResetBrowseToNames(); + return; + } + + _searchDebounceTimer.Start(); + } + private void HideOptionalColumn(string columnName) { if (dgvPriceList.Columns.Contains(columnName)) @@ -506,55 +577,51 @@ namespace Электронная_Фармация.UserControls } var search = (GoodsFilter ?? string.Empty).Trim(); - var namesTable = new DataTable(); - namesTable.Columns.Add("Наименование", typeof(string)); - - var seen = new HashSet(StringComparer.OrdinalIgnoreCase); - foreach (DataRow row in _fullPriceTable.Rows) + IList namesToShow; + if (string.IsNullOrWhiteSpace(search)) { - var baseName = row["Базовое наименование"]?.ToString() ?? string.Empty; - if (string.IsNullOrWhiteSpace(baseName) || !seen.Add(baseName)) + namesToShow = _uniqueBaseNames; + } + else + { + var matching = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (DataRow row in _fullPriceTable.Rows) { - continue; - } - - if (!string.IsNullOrWhiteSpace(search)) - { - var keep = false; - foreach (DataRow candidate in _fullPriceTable.Rows) - { - if (!string.Equals(candidate["Базовое наименование"]?.ToString(), baseName, StringComparison.OrdinalIgnoreCase)) - { - continue; - } - - var drugName = candidate["Наименование"]?.ToString() ?? string.Empty; - var mgText = candidate["МГ"]?.ToString() ?? string.Empty; - if (baseName.IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0 - || drugName.IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0 - || mgText.IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0) - { - keep = true; - break; - } - } - - if (!keep) + var baseName = row["Базовое наименование"]?.ToString() ?? string.Empty; + if (string.IsNullOrWhiteSpace(baseName) || matching.Contains(baseName)) { continue; } + + var drugName = row["Наименование"]?.ToString() ?? string.Empty; + var mgText = row["МГ"]?.ToString() ?? string.Empty; + if (baseName.IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0 + || drugName.IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0 + || mgText.IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0) + { + matching.Add(baseName); + } } - namesTable.Rows.Add(baseName); + namesToShow = matching + .OrderBy(n => n, StringComparer.OrdinalIgnoreCase) + .ToList(); } - namesTable.DefaultView.Sort = "Наименование ASC"; - var sortedNames = namesTable.DefaultView.ToTable(); + var namesTable = new DataTable(); + namesTable.Columns.Add("Наименование", typeof(string)); + namesTable.BeginLoadData(); + foreach (var baseName in namesToShow) + { + namesTable.Rows.Add(baseName); + } + namesTable.EndLoadData(); try { _suppressBrowseEvents = true; - dgvBrowseNames.DataSource = sortedNames; + dgvBrowseNames.SuspendLayout(); + dgvBrowseNames.DataSource = namesTable; ConfigureSingleColumnGrid(dgvBrowseNames, "Наименование"); ClearMgPanel(); @@ -569,6 +636,7 @@ namespace Электронная_Фармация.UserControls } finally { + dgvBrowseNames.ResumeLayout(); _suppressBrowseEvents = false; } @@ -595,28 +663,18 @@ namespace Электронная_Фармация.UserControls var mgTable = new DataTable(); mgTable.Columns.Add("МГ", typeof(string)); - var seen = new HashSet(StringComparer.OrdinalIgnoreCase); - foreach (DataRow row in _fullPriceTable.Rows) + if (_mgByBaseName.TryGetValue(baseName, out var dosages)) { - if (!string.Equals(row["Базовое наименование"]?.ToString(), baseName, StringComparison.OrdinalIgnoreCase)) + foreach (var dosage in dosages) { - continue; + mgTable.Rows.Add(dosage); } - - var dosage = row["МГ"]?.ToString() ?? string.Empty; - if (!seen.Add(dosage)) - { - continue; - } - - mgTable.Rows.Add(string.IsNullOrEmpty(dosage) ? EmptyMgDisplay : dosage); } - mgTable.DefaultView.Sort = "МГ ASC"; try { _suppressBrowseEvents = true; - dgvBrowseMg.DataSource = mgTable.DefaultView.ToTable(); + dgvBrowseMg.DataSource = mgTable; ConfigureSingleColumnGrid(dgvBrowseMg, "МГ"); dgvBrowseMg.ClearSelection(); } @@ -990,7 +1048,7 @@ namespace Электронная_Фармация.UserControls GoodsFilter = GoodsFilter.Substring(0, GoodsFilter.Length - 1); txtSearchGoodNameInPriceList.Text = GoodsFilter; - ResetBrowseToNames(); + ScheduleBrowseSearchRefresh(immediate: GoodsFilter.Length == 0); if (GoodsFilter.Length == 0) { @@ -1501,7 +1559,7 @@ select strftime('%Y-%m-%d','{OrderDate}'), { txtSearchGoodNameInPriceList.Text = string.Empty; GoodsFilter = string.Empty; - ResetBrowseToNames(); + ScheduleBrowseSearchRefresh(immediate: true); //loadPriceList(string.Empty); } @@ -1560,6 +1618,27 @@ and tempOrder.GoodName = PriceList.GoodName string GoodsFilter = string.Empty; string stringIWantBuy = string.Empty; + private static bool IsGoodsSearchKey(char keyChar, bool allowDigitsAndSeparators = true) + { + if ((keyChar >= 'A' && keyChar <= 'Z') + || (keyChar >= 'a' && keyChar <= 'z') + || (keyChar >= 'А' && keyChar <= 'Я') + || (keyChar >= 'а' && keyChar <= 'я') + || keyChar == (char)Keys.Subtract) + { + return true; + } + + if (!allowDigitsAndSeparators) + { + return false; + } + + return (keyChar >= '0' && keyChar <= '9') + || keyChar == ' ' + || keyChar == '.' + || keyChar == ','; + } private void dgvPriceList_KeyPress(object sender, KeyPressEventArgs e) { @@ -1573,7 +1652,7 @@ and tempOrder.GoodName = PriceList.GoodName { txtSearchGoodNameInPriceList.Text = string.Empty; GoodsFilter = string.Empty; - ResetBrowseToNames(); + ScheduleBrowseSearchRefresh(immediate: true); } return; @@ -1613,67 +1692,27 @@ and tempOrder.GoodName = PriceList.GoodName if (_browseMode != PriceBrowseMode.Offers) { - if ((e.KeyChar >= 'A' && e.KeyChar <= 'Z') - || (e.KeyChar >= 'a' && e.KeyChar <= 'z') - || (e.KeyChar >= 'А' && e.KeyChar <= 'Я') - || (e.KeyChar >= 'а' && e.KeyChar <= 'я') - || (e.KeyChar == (Char)Keys.Subtract)) + if (IsGoodsSearchKey(e.KeyChar)) { dgvPriceList.ClearSelection(); GoodsFilter += e.KeyChar.ToString(); txtSearchGoodNameInPriceList.Text = GoodsFilter; - ResetBrowseToNames(); - if (dgvBrowseNames != null && dgvBrowseNames.Rows.Count == 0) - { - UiDialogs.ShowInfo("Подходящий товар не найден.", "Поиск", FindForm()); - GoodsFilter = string.Empty; - txtSearchGoodNameInPriceList.Text = string.Empty; - ResetBrowseToNames(); - } + ScheduleBrowseSearchRefresh(); + timerForResetSearch.Enabled = true; + timerForResetSearch.Start(); } return; } - if ((e.KeyChar >= 'A' && e.KeyChar <= 'Z') || (e.KeyChar >= 'a' && e.KeyChar <= 'z') || (e.KeyChar >= 'А' && e.KeyChar <= 'Я') || (e.KeyChar >= 'а' && e.KeyChar <= 'я') || (e.KeyChar == (Char)Keys.Subtract)) + if (IsGoodsSearchKey(e.KeyChar, allowDigitsAndSeparators: false)) { - if (e.KeyChar == (Char)Keys.Enter) - { - - } - else - { - dgvPriceList.ClearSelection(); - GoodsFilter += e.KeyChar.ToString(); - txtSearchGoodNameInPriceList.Text = GoodsFilter; - - #region поиск без фильтрации (НЕ РАБОТАЕТ) - //foreach(DataGridViewRow row in dgvPriceList.Rows) - //{ - // //string cellValue = row.Cells["Наименование"].Value.ToString().ToLower(); - // if (row.Cells[2].Value.ToString().Equals(GoodsFilter)) - // { - // int indexOfSearchValue = row.Index; - // dgvPriceList.Rows[indexOfSearchValue].Selected = true; - // } - //} - #endregion - - #region старый способ поиска с фильтрацией данных - if (!string.IsNullOrEmpty(GoodsFilter)) - { - ResetBrowseToNames(); - if (dgvPriceList.Rows.Count == 0) - { - UiDialogs.ShowInfo("Подходящий товар не найден.", "Поиск", FindForm()); - GoodsFilter = string.Empty; - - txtSearchGoodNameInPriceList.Text = string.Empty; - ResetBrowseToNames(); - } - } - #endregion - } + dgvPriceList.ClearSelection(); + GoodsFilter += e.KeyChar.ToString(); + txtSearchGoodNameInPriceList.Text = GoodsFilter; + ScheduleBrowseSearchRefresh(); + timerForResetSearch.Enabled = true; + timerForResetSearch.Start(); return; } else if ((e.KeyChar >= (Char)Keys.D1 && e.KeyChar <= (Char)Keys.D9) || @@ -2511,7 +2550,7 @@ and SumOrderedItems = '{SumOrder}'"; { txtSearchGoodNameInPriceList.Text = string.Empty; GoodsFilter = string.Empty; - ResetBrowseToNames(); + ScheduleBrowseSearchRefresh(immediate: true); timerForResetSearch.Enabled = false; timerForResetSearch.Stop(); }