532 lines
20 KiB
C#
532 lines
20 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Drawing;
|
||
using System.Linq;
|
||
using System.Windows.Forms;
|
||
using Elfisa.UI.Controls;
|
||
using Elfisa.UI.Helpers;
|
||
using Elfisa.UI.Loading;
|
||
using Elfisa.UI.Notifications;
|
||
using Elfisa.UI.Theming;
|
||
using Электронная_Фармация.Classes;
|
||
using Электронная_Фармация.Properties;
|
||
using Электронная_Фармация.Forms;
|
||
using Электронная_Фармация.HelpForms;
|
||
using Электронная_Фармация.UserControls;
|
||
|
||
namespace Электронная_Фармация
|
||
{
|
||
public partial class ElectroPharmacy
|
||
{
|
||
private readonly Dictionary<string, Control> _documentContents = new Dictionary<string, Control>();
|
||
private readonly ThemeService _themeService = new ThemeService();
|
||
private ToastService _toastService;
|
||
private LoadingOverlay _loadingOverlay;
|
||
private ModernAppHeader _appHeader;
|
||
private ModernTitleBar _titleBar;
|
||
private ModernDocumentTabStrip _documentTabStrip;
|
||
private Panel _documentHost;
|
||
private Panel _statusFooter;
|
||
private Label _lblCurrentUser;
|
||
private ModernButton _btnSettings;
|
||
private ContextMenuStrip _directoryMenu;
|
||
private ContextMenuStrip _documentTabMenu;
|
||
private ToolStripMenuItem _documentCloseTabMenuItem;
|
||
private ToolStripMenuItem _documentCloseAllTabsMenuItem;
|
||
|
||
partial void InitializeModernShell()
|
||
{
|
||
Font = new Font("Segoe UI", 12f);
|
||
AutoScaleMode = AutoScaleMode.Dpi;
|
||
KeyPreview = true;
|
||
FormBorderStyle = FormBorderStyle.None;
|
||
MaximizeBox = true;
|
||
MinimizeBox = true;
|
||
MinimumSize = new Size(900, 620);
|
||
|
||
bindControl.Visible = false;
|
||
tabContent.Visible = false;
|
||
|
||
_toastService = new ToastService(this);
|
||
AppServices.Initialize(_toastService);
|
||
_loadingOverlay = new LoadingOverlay { Name = "loadingOverlay", Dock = DockStyle.Fill };
|
||
|
||
_titleBar = new ModernTitleBar { Name = "titleBar" };
|
||
_titleBar.Attach(this);
|
||
_titleBar.ApplyTheme(ThemeManager.Colors);
|
||
|
||
_appHeader = new ModernAppHeader { Name = "appHeader", Dock = DockStyle.Top };
|
||
_appHeader.SetItems(new[]
|
||
{
|
||
new SidebarItem { Key = "price", Text = "Прайс-лист", IconGlyph = "\uE14C" },
|
||
new SidebarItem { Key = "orders", Text = "Заказы", IconGlyph = "\uE7BF" },
|
||
new SidebarItem { Key = "invoices", Text = "Накладные", IconGlyph = "\uE8A5" },
|
||
new SidebarItem { Key = "upload", Text = "Загрузить", IconGlyph = "\uE896" },
|
||
new SidebarItem { Key = "send", Text = "Отправить", IconGlyph = "\uE725" },
|
||
new SidebarItem { Key = "refusals", Text = "Отказы", IconGlyph = "\uE7BA" },
|
||
new SidebarItem { Key = "directory", Text = "Справочник", IconGlyph = "\uE8F1" },
|
||
new SidebarItem { Key = "auth", Text = "Вход", IconGlyph = "\uE192" }
|
||
});
|
||
|
||
_documentTabStrip = new ModernDocumentTabStrip { Name = "documentTabStrip", Dock = DockStyle.Top };
|
||
_documentHost = new Panel { Name = "documentHost", Dock = DockStyle.Fill, Tag = "chrome-child" };
|
||
|
||
_statusFooter = new Panel
|
||
{
|
||
Name = "statusFooter",
|
||
Dock = DockStyle.Bottom,
|
||
Height = 32,
|
||
Tag = "status-footer",
|
||
Padding = new Padding(12, 0, 8, 0)
|
||
};
|
||
_statusFooter.Paint += (_, e) =>
|
||
{
|
||
var theme = ThemeManager.Colors;
|
||
var g = e.Graphics;
|
||
using (var fill = new SolidBrush(theme.CartHeaderBackground))
|
||
{
|
||
g.FillRectangle(fill, _statusFooter.ClientRectangle);
|
||
}
|
||
|
||
using (var pen = new Pen(theme.AccentPrimaryBorder))
|
||
{
|
||
g.DrawLine(pen, 0, 0, _statusFooter.Width, 0);
|
||
}
|
||
};
|
||
|
||
_lblCurrentUser = new Label
|
||
{
|
||
Name = "lblCurrentUser",
|
||
Dock = DockStyle.Fill,
|
||
TextAlign = ContentAlignment.MiddleLeft,
|
||
AutoEllipsis = true,
|
||
Tag = "muted",
|
||
Padding = new Padding(0, 0, 4, 0)
|
||
};
|
||
_btnSettings = new ModernButton
|
||
{
|
||
Name = "btnSettings",
|
||
Dock = DockStyle.Right,
|
||
Margin = new Padding(0, 2, 2, 2),
|
||
Text = "Настройки",
|
||
CompactIconMode = false,
|
||
AllowTextEllipsis = false,
|
||
MinimumSize = new Size(88, 22),
|
||
Size = new Size(88, 22),
|
||
Padding = new Padding(6, 0, 6, 0),
|
||
Font = new Font("Segoe UI Semibold", 8.5f, FontStyle.Bold),
|
||
TabIndex = 0
|
||
};
|
||
_btnSettings.Click += (_, __) => OpenSettingsDialog();
|
||
// Сначала Fill (пользователь), потом Right (кнопка) — кнопка справа, текст не перекрывается.
|
||
_statusFooter.Controls.Add(_lblCurrentUser);
|
||
_statusFooter.Controls.Add(_btnSettings);
|
||
|
||
// Клик по строке "аптека" открывает выбор грузополучателя без перезапуска приложения.
|
||
// Т.к. метка у нас одна (Dock=Fill), обработчик навешиваем на весь Label.
|
||
_lblCurrentUser.Cursor = Cursors.Hand;
|
||
_lblCurrentUser.Click += (_, __) => SwitchConsignee();
|
||
|
||
Controls.Add(_loadingOverlay);
|
||
Controls.Add(_documentHost);
|
||
Controls.Add(_statusFooter);
|
||
Controls.Add(_documentTabStrip);
|
||
Controls.Add(_appHeader);
|
||
Controls.Add(_titleBar);
|
||
|
||
_directoryMenu = new ContextMenuStrip();
|
||
var contractorsItem = new ToolStripMenuItem("Поставщики");
|
||
contractorsItem.Click += (_, __) => tsItemContractors_Click(_, __);
|
||
var consigneesItem = new ToolStripMenuItem("Грузополучатели");
|
||
consigneesItem.Click += (_, __) => tsConsignee_Click(_, __);
|
||
var switchConsigneeItem = new ToolStripMenuItem("Сменить грузополучателя");
|
||
switchConsigneeItem.Click += (_, __) => SwitchConsignee();
|
||
var invoicesItem = new ToolStripMenuItem("Накладные");
|
||
invoicesItem.Click += (_, __) => tsItemInvoices_Click(_, __);
|
||
var debugItem = new ToolStripMenuItem("Отладка");
|
||
debugItem.Click += (_, __) => tsDebug_Click(_, __);
|
||
_directoryMenu.Items.AddRange(new ToolStripItem[]
|
||
{
|
||
contractorsItem,
|
||
consigneesItem,
|
||
switchConsigneeItem,
|
||
invoicesItem,
|
||
new ToolStripSeparator(),
|
||
debugItem
|
||
});
|
||
|
||
_appHeader.ItemSelected += OnAppHeaderItemSelected;
|
||
_documentTabStrip.ThemeToggleClicked += OnThemeToggleClicked;
|
||
_documentTabStrip.TabSelected += OnDocumentTabSelected;
|
||
_documentTabStrip.TabClosed += OnDocumentTabClosed;
|
||
_documentTabStrip.TabCloseAllRequested += OnDocumentTabCloseAll;
|
||
|
||
_documentCloseTabMenuItem = new ToolStripMenuItem("Закрыть вкладку");
|
||
_documentCloseTabMenuItem.Click += (_, __) =>
|
||
{
|
||
var tabId = _documentTabStrip.ActiveTabId;
|
||
if (!string.IsNullOrEmpty(tabId))
|
||
{
|
||
CloseDocumentTab(tabId);
|
||
}
|
||
};
|
||
|
||
_documentCloseAllTabsMenuItem = new ToolStripMenuItem("Закрыть все вкладки");
|
||
_documentCloseAllTabsMenuItem.Click += (_, __) => ClearDocumentTabs();
|
||
|
||
_documentTabMenu = new ContextMenuStrip();
|
||
_documentTabMenu.Items.Add(_documentCloseTabMenuItem);
|
||
_documentTabMenu.Items.Add(_documentCloseAllTabsMenuItem);
|
||
|
||
ThemeManager.ThemeChanged += (_, __) =>
|
||
{
|
||
UiThemeHelper.ApplyToControlTree(this);
|
||
UpdateThemeToggleState();
|
||
_titleBar?.ApplyTheme(ThemeManager.Colors);
|
||
WindowChromeHelper.ApplyTitleBarTheme(this, ThemeManager.Colors);
|
||
};
|
||
|
||
HandleCreated += (_, __) => WindowChromeHelper.ApplyTitleBarTheme(this, ThemeManager.Colors);
|
||
}
|
||
|
||
protected override void WndProc(ref Message m)
|
||
{
|
||
if (FormBorderStyle == FormBorderStyle.None
|
||
&& WindowChromeHelper.TryHandleNcHitTest(this, ref m))
|
||
{
|
||
return;
|
||
}
|
||
|
||
base.WndProc(ref m);
|
||
}
|
||
|
||
private void UpdateThemeToggleState()
|
||
{
|
||
if (_documentTabStrip != null)
|
||
{
|
||
_documentTabStrip.IsDarkTheme = ThemeManager.IsDarkProductivityActive;
|
||
}
|
||
}
|
||
|
||
private void OnThemeToggleClicked(object sender, EventArgs e)
|
||
{
|
||
ThemeManager.ToggleProductivityTheme();
|
||
Settings.Default.UseDarkTheme = ThemeManager.IsDarkProductivityActive;
|
||
Settings.Default.Save();
|
||
UpdateThemeToggleState();
|
||
}
|
||
|
||
partial void FinalizeModernShell()
|
||
{
|
||
ThemeManager.CurrentVariant = Settings.Default.UseDarkTheme
|
||
? ThemeVariant.DarkProductivity
|
||
: ThemeVariant.ClinicalPro;
|
||
_themeService.Initialize(this);
|
||
UpdateThemeToggleState();
|
||
UiThemeHelper.ApplyToControlTree(this);
|
||
_titleBar?.ApplyTheme(ThemeManager.Colors);
|
||
WindowChromeHelper.ApplyTitleBarTheme(this, ThemeManager.Colors);
|
||
try
|
||
{
|
||
Icon = CreateAppIcon();
|
||
}
|
||
catch
|
||
{
|
||
// keep default
|
||
}
|
||
RefreshCurrentUserStatus();
|
||
}
|
||
|
||
private static Icon CreateAppIcon()
|
||
{
|
||
using (var bmp = new Bitmap(32, 32))
|
||
using (var g = Graphics.FromImage(bmp))
|
||
{
|
||
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
|
||
g.Clear(Color.Transparent);
|
||
using (var brush = new SolidBrush(Color.FromArgb(15, 118, 110)))
|
||
{
|
||
g.FillEllipse(brush, 1, 1, 30, 30);
|
||
}
|
||
|
||
using (var pen = new Pen(Color.White, 3f)
|
||
{
|
||
StartCap = System.Drawing.Drawing2D.LineCap.Round,
|
||
EndCap = System.Drawing.Drawing2D.LineCap.Round
|
||
})
|
||
{
|
||
g.DrawLine(pen, 16, 8, 16, 24);
|
||
g.DrawLine(pen, 8, 16, 24, 16);
|
||
}
|
||
|
||
return Icon.FromHandle(bmp.GetHicon());
|
||
}
|
||
}
|
||
|
||
private void RefreshCurrentUserStatus()
|
||
{
|
||
if (_lblCurrentUser == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
var login = Settings.Default.stringLogin?.Trim();
|
||
var hasToken = !string.IsNullOrWhiteSpace(Settings.Default.stringToken);
|
||
var pharmacy = ConsigneeName?.Trim();
|
||
if (hasToken &&
|
||
(string.IsNullOrWhiteSpace(pharmacy) || pharmacy == "Прайс-Лист"))
|
||
{
|
||
pharmacy = ConsigneeHelper.GetDefaultConsigneeName();
|
||
if (!string.IsNullOrWhiteSpace(pharmacy) &&
|
||
(string.IsNullOrWhiteSpace(ConsigneeName) || ConsigneeName == "Прайс-Лист"))
|
||
{
|
||
ConsigneeName = pharmacy;
|
||
}
|
||
}
|
||
|
||
string userPart;
|
||
if (!string.IsNullOrWhiteSpace(login) && hasToken)
|
||
{
|
||
userPart = $"Пользователь: {login}";
|
||
}
|
||
else if (!string.IsNullOrWhiteSpace(login))
|
||
{
|
||
userPart = $"Пользователь: {login} (не авторизован)";
|
||
}
|
||
else
|
||
{
|
||
userPart = "Пользователь не авторизован";
|
||
}
|
||
|
||
if (hasToken && !string.IsNullOrWhiteSpace(pharmacy) && pharmacy != "Прайс-Лист")
|
||
{
|
||
_lblCurrentUser.Text = $"{userPart} | Аптека: {pharmacy}";
|
||
}
|
||
else
|
||
{
|
||
_lblCurrentUser.Text = $"{userPart} | Аптека: не выбрана";
|
||
}
|
||
}
|
||
|
||
private void OnAppHeaderItemSelected(object sender, string key)
|
||
{
|
||
switch (key)
|
||
{
|
||
case "price":
|
||
loadDefaultPriceList();
|
||
break;
|
||
case "orders":
|
||
OpenOrdersTab();
|
||
break;
|
||
case "invoices":
|
||
OpenInvoicesTab();
|
||
break;
|
||
case "upload":
|
||
tsBtnDownloadData_Click(this, EventArgs.Empty);
|
||
_appHeader.SetActive("upload");
|
||
break;
|
||
case "send":
|
||
tsBtnSentData_Click(this, EventArgs.Empty);
|
||
_appHeader.SetActive("send");
|
||
break;
|
||
case "refusals":
|
||
OpenRefusalsTab();
|
||
break;
|
||
case "directory":
|
||
_directoryMenu.Show(_appHeader, new Point(_appHeader.Width - 220, _appHeader.Height - 4));
|
||
break;
|
||
case "auth":
|
||
tsBtnAuth_Click(this, EventArgs.Empty);
|
||
break;
|
||
}
|
||
}
|
||
|
||
private void OnDocumentTabSelected(object sender, string tabId)
|
||
{
|
||
ShowDocumentTab(tabId);
|
||
var tab = _documentTabStrip.Tabs.FirstOrDefault(item => item.Id == tabId);
|
||
if (tab != null)
|
||
{
|
||
_appHeader.SetActive(tab.NavKey);
|
||
}
|
||
}
|
||
|
||
private string OpenDocumentTab(string title, string navKey, Control content, bool allowDuplicate)
|
||
{
|
||
if (!allowDuplicate)
|
||
{
|
||
var existing = _documentTabStrip.FindByNavKey(navKey);
|
||
if (existing != null)
|
||
{
|
||
_documentTabStrip.SelectTab(existing.Id);
|
||
ShowDocumentTab(existing.Id);
|
||
_appHeader.SetActive(navKey);
|
||
return existing.Id;
|
||
}
|
||
}
|
||
|
||
var tabId = _documentTabStrip.AddTab(title, navKey);
|
||
content.Dock = DockStyle.Fill;
|
||
content.Tag = "chrome-child";
|
||
content.Visible = false;
|
||
_documentContents[tabId] = content;
|
||
_documentHost.Controls.Add(content);
|
||
UiThemeHelper.ApplyToControlTree(content);
|
||
WireDocumentContextMenu(content);
|
||
_documentTabStrip.SelectTab(tabId);
|
||
ShowDocumentTab(tabId);
|
||
_appHeader.SetActive(navKey);
|
||
return tabId;
|
||
}
|
||
|
||
private void ShowDocumentTab(string tabId)
|
||
{
|
||
foreach (var pair in _documentContents)
|
||
{
|
||
pair.Value.Visible = pair.Key == tabId;
|
||
}
|
||
}
|
||
|
||
private void OnDocumentTabClosed(object sender, string tabId)
|
||
{
|
||
CloseDocumentTab(tabId);
|
||
}
|
||
|
||
private void OnDocumentTabCloseAll(object sender, EventArgs e)
|
||
{
|
||
ClearDocumentTabs();
|
||
}
|
||
|
||
private void WireDocumentContextMenu(Control control)
|
||
{
|
||
control.MouseUp -= OnDocumentContentMouseUp;
|
||
control.MouseUp += OnDocumentContentMouseUp;
|
||
foreach (Control child in control.Controls)
|
||
{
|
||
WireDocumentContextMenu(child);
|
||
}
|
||
}
|
||
|
||
private void OnDocumentContentMouseUp(object sender, MouseEventArgs e)
|
||
{
|
||
if (e.Button != MouseButtons.Right || _documentTabStrip.Tabs.Count == 0)
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (sender is Control clicked && HasDedicatedContextMenu(clicked))
|
||
{
|
||
return;
|
||
}
|
||
|
||
_documentCloseTabMenuItem.Enabled = !string.IsNullOrEmpty(_documentTabStrip.ActiveTabId);
|
||
_documentCloseAllTabsMenuItem.Enabled = _documentTabStrip.Tabs.Count > 0;
|
||
|
||
var screenPoint = (sender as Control)?.PointToScreen(e.Location) ?? Cursor.Position;
|
||
_documentTabMenu.Show(screenPoint);
|
||
}
|
||
|
||
private static bool HasDedicatedContextMenu(Control control)
|
||
{
|
||
for (var current = control; current != null; current = current.Parent)
|
||
{
|
||
if (current.ContextMenuStrip != null)
|
||
{
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
private void CloseDocumentTab(string tabId)
|
||
{
|
||
if (_documentContents.TryGetValue(tabId, out var control))
|
||
{
|
||
_documentHost.Controls.Remove(control);
|
||
control.Dispose();
|
||
_documentContents.Remove(tabId);
|
||
}
|
||
|
||
_documentTabStrip.RemoveTab(tabId);
|
||
}
|
||
|
||
private void ClearDocumentTabs()
|
||
{
|
||
foreach (var control in _documentContents.Values)
|
||
{
|
||
_documentHost.Controls.Remove(control);
|
||
control.Dispose();
|
||
}
|
||
|
||
_documentContents.Clear();
|
||
_documentTabStrip.ClearTabs();
|
||
}
|
||
|
||
private void OpenOrdersTab()
|
||
{
|
||
var orders = new FOrders
|
||
{
|
||
Dock = DockStyle.Fill,
|
||
TopLevel = false,
|
||
TopMost = true
|
||
};
|
||
OpenDocumentTab("Заказы", "orders", orders, allowDuplicate: true);
|
||
orders.Show();
|
||
}
|
||
|
||
public void OpenInvoicesTab(string sourceOrderId = null)
|
||
{
|
||
var existing = _documentTabStrip.FindByNavKey("invoices");
|
||
if (existing != null &&
|
||
_documentContents.TryGetValue(existing.Id, out var existingControl) &&
|
||
existingControl is UCInvoices existingInvoices)
|
||
{
|
||
existingInvoices.SetSourceOrderFilter(sourceOrderId);
|
||
_documentTabStrip.SelectTab(existing.Id);
|
||
ShowDocumentTab(existing.Id);
|
||
_appHeader.SetActive("invoices");
|
||
return;
|
||
}
|
||
|
||
var invoices = new UCInvoices(sourceOrderId);
|
||
OpenDocumentTab("Накладные", "invoices", invoices, allowDuplicate: false);
|
||
}
|
||
|
||
private void OpenRefusalsTab()
|
||
{
|
||
var refusals = new UCRefusals();
|
||
OpenDocumentTab("Отказы", "refusals", refusals, allowDuplicate: false);
|
||
}
|
||
|
||
partial void ShowShellLoading(string message)
|
||
{
|
||
_loadingOverlay?.Show(message);
|
||
_loadingOverlay?.BringToFront();
|
||
}
|
||
|
||
partial void HideShellLoading()
|
||
{
|
||
_loadingOverlay?.HideOverlay();
|
||
}
|
||
|
||
private bool HasDocumentTabs()
|
||
{
|
||
return _documentContents.Count > 0;
|
||
}
|
||
|
||
partial void RefreshOpenPriceLists()
|
||
{
|
||
foreach (var control in _documentContents.Values.OfType<UCPriceList>())
|
||
{
|
||
control.RefreshFromDatabase();
|
||
}
|
||
|
||
if (!_documentContents.Values.OfType<UCPriceList>().Any())
|
||
{
|
||
loadDefaultPriceList();
|
||
}
|
||
}
|
||
}
|
||
}
|