PanelHost.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. using Comal.Classes;
  2. using FastReport;
  3. using InABox.Clients;
  4. using InABox.Configuration;
  5. using InABox.Core;
  6. using InABox.Core.Reports;
  7. using InABox.DynamicGrid;
  8. using InABox.Scripting;
  9. using InABox.Wpf;
  10. using InABox.Wpf.Reports;
  11. using InABox.WPF;
  12. using PRSDesktop.Configuration;
  13. using System;
  14. using System.Collections.Generic;
  15. using System.ComponentModel;
  16. using System.Drawing;
  17. using System.Linq;
  18. using System.Reflection;
  19. using System.Text;
  20. using System.Threading.Tasks;
  21. using System.Windows;
  22. using System.Windows.Controls;
  23. namespace PRSDesktop;
  24. public interface IPanelHostControl
  25. {
  26. void ClearActions();
  27. void CreatePanelAction(PanelAction action);
  28. void ClearReports();
  29. void CreateReport(PanelAction action);
  30. }
  31. public class PanelHost : IPanelHost
  32. {
  33. public IBasePanel? CurrentPanel { get; private set; }
  34. public string CurrentModuleName { get; private set; } = "";
  35. private readonly IPanelHostControl HostControl;
  36. private readonly List<IPanelActionItem> SetupActions = new();
  37. public PanelHost(IPanelHostControl hostControl)
  38. {
  39. HostControl = hostControl;
  40. }
  41. #region Module Tracking
  42. private int TrackedClicks;
  43. private int TrackedKeys;
  44. private DateTime TrackedTicks = DateTime.MinValue;
  45. public void IncrementTrackingModuleClick()
  46. {
  47. if (CurrentPanel is not null)
  48. TrackedClicks++;
  49. }
  50. public void IncrementTrackingModuleKey()
  51. {
  52. if (CurrentPanel is not null)
  53. TrackedKeys++;
  54. }
  55. #endregion
  56. #region IPanelHost
  57. void IPanelHost.CreatePanelAction(PanelAction action)
  58. {
  59. HostControl.CreatePanelAction(action);
  60. }
  61. void IPanelHost.CreateReport(PanelAction action)
  62. {
  63. HostControl.CreateReport(action);
  64. }
  65. void IPanelHost.CreateSetupAction(PanelAction action)
  66. {
  67. SetupActions.Add(action);
  68. }
  69. void IPanelHost.CreateSetupSeparator()
  70. {
  71. SetupActions.Add(new PanelActionSeparator());
  72. }
  73. #endregion
  74. #region Panel Properties
  75. private void ConfigurePanel()
  76. {
  77. if (CurrentPanel is null) return;
  78. PanelUtils.ConfigurePanel(CurrentPanel);
  79. }
  80. #endregion
  81. #region Actions
  82. private void ReloadActions(string sectionName, DataModel model)
  83. {
  84. ReportUtils.ExportDefinitions.Clear();
  85. ReportUtils.ExportDefinitions.AddRange(PRSEmailUtils.CreateTemplateDefinitions(model));
  86. SetupActions.Clear();
  87. HostControl.ClearActions();
  88. HostControl.ClearReports();
  89. if (CurrentPanel != null)
  90. CurrentPanel.CreateToolbarButtons(this);
  91. CreateModules(sectionName, model);
  92. CreateReports(sectionName, model);
  93. }
  94. #endregion
  95. #region Custom Modules
  96. private void CreateModules(string section, DataModel model)
  97. {
  98. foreach (var (module, image) in CustomModuleUtils.LoadCustomModuleThumbnails(section, model))
  99. {
  100. HostControl.CreatePanelAction(new PanelAction
  101. {
  102. Caption = module.Name ?? "",
  103. Image = image,
  104. OnExecute = (action) =>
  105. {
  106. ClickModule(action, module);
  107. }
  108. });
  109. }
  110. }
  111. private void ClickModule(PanelAction action, CustomModule code)
  112. {
  113. if (CurrentPanel != null)
  114. {
  115. if (!string.IsNullOrWhiteSpace(code.Script))
  116. try
  117. {
  118. Selection selection;
  119. if (code.SelectedRecords && code.AllRecords)
  120. selection = RecordSelectionDialog.Execute();
  121. else if (code.SelectedRecords)
  122. selection = Selection.Selected;
  123. else if (code.AllRecords)
  124. selection = Selection.All;
  125. else
  126. selection = Selection.None;
  127. var result = ScriptDocument.RunCustomModule(CurrentPanel.DataModel(selection), CurrentPanel.Selected(), code.Script);
  128. if (result)
  129. CurrentPanel.Refresh();
  130. }
  131. catch (CompileException c)
  132. {
  133. MessageWindow.ShowError(c.Message, c, shouldLog: false);
  134. }
  135. catch (Exception err)
  136. {
  137. MessageWindow.ShowError($"Unable to load {action.Caption}", err);
  138. }
  139. else
  140. MessageWindow.ShowMessage("Unable to load " + action.Caption, "Error", image: MessageWindow.WarningImage);
  141. }
  142. }
  143. private void ManageModules(PanelAction action)
  144. {
  145. if (CurrentPanel != null)
  146. {
  147. var section = CurrentPanel.SectionName;
  148. var dataModel = CurrentPanel.DataModel(Selection.Selected);
  149. var manager = new CustomModuleManager()
  150. {
  151. Section = section,
  152. DataModel = dataModel
  153. };
  154. manager.ShowDialog();
  155. ReloadActions(section, dataModel);
  156. }
  157. }
  158. #endregion
  159. #region Reports
  160. public static PanelAction CreateReportAction(ReportTemplate template, Func<Selection, DataModel> getDataModel)
  161. {
  162. var action = new PanelAction
  163. {
  164. Caption = template.Name,
  165. Image = PRSDesktop.Resources.printer,
  166. OnExecute = (action) =>
  167. {
  168. PrintReport(template.ID, getDataModel);
  169. }
  170. };
  171. if (Security.IsAllowed<CanDesignReports>())
  172. {
  173. var menu = new ContextMenu();
  174. menu.AddItem("Design Report", PRSDesktop.Resources.pencil, () => DesignReport(template.ID, getDataModel));
  175. action.Menu = menu;
  176. }
  177. return action;
  178. }
  179. private void CreateReports(string section, DataModel model)
  180. {
  181. if (CurrentPanel is null) return;
  182. var client = new Client<ReportTemplate>();
  183. var templates = ReportUtils.LoadReports(section, model, Columns.None<ReportTemplate>().Add(x => x.ID, x => x.Name));
  184. foreach (var template in templates)
  185. {
  186. HostControl.CreateReport(CreateReportAction(template, CurrentPanel.DataModel));
  187. }
  188. }
  189. private static void DesignReport(Guid templateID, Func<Selection, DataModel> getDataModel)
  190. {
  191. var template = new Client<ReportTemplate>().Load(Filter<ReportTemplate>.Where(x => x.ID).IsEqualTo(templateID)).FirstOrDefault();
  192. if (template is null)
  193. {
  194. Logger.Send(LogType.Error, "", $"No Report Template with ID '{templateID}'");
  195. MessageWindow.ShowMessage("Report does not exist!", "Error", image: MessageWindow.WarningImage);
  196. return;
  197. }
  198. ReportUtils.DesignReport(template, getDataModel(Selection.None));
  199. }
  200. private static void PrintReport(Guid id, Func<Selection, DataModel> getDataModel)
  201. {
  202. var template = new Client<ReportTemplate>().Load(Filter<ReportTemplate>.Where(x => x.ID).IsEqualTo(id)).FirstOrDefault();
  203. if (template == null)
  204. {
  205. Logger.Send(LogType.Error, "", $"No Report Template with ID '{id}'");
  206. MessageWindow.ShowMessage("Report does not exist!", "Error", image: MessageWindow.WarningImage);
  207. return;
  208. }
  209. var selection = Selection.None;
  210. if (template.SelectedRecords && template.AllRecords)
  211. selection = RecordSelectionDialog.Execute();
  212. else if (template.SelectedRecords)
  213. selection = Selection.Selected;
  214. else if (template.AllRecords)
  215. selection = Selection.All;
  216. else
  217. MessageWindow.ShowMessage(
  218. "Report must have either [Selected Records] or [All Records] checked to display!",
  219. "Error",
  220. image: MessageWindow.WarningImage);
  221. if (selection != Selection.None)
  222. ReportUtils.PreviewReport(template, getDataModel(selection), false, Security.IsAllowed<CanDesignReports>());
  223. }
  224. private void ManageReports(PanelAction action)
  225. {
  226. if (CurrentPanel is null)
  227. return;
  228. var section = CurrentPanel.SectionName;
  229. var model = CurrentPanel.DataModel(Selection.None);
  230. if (model == null)
  231. {
  232. MessageWindow.ShowMessage("No DataModel for " + CurrentPanel.SectionName, "No DataModel");
  233. return;
  234. }
  235. var form = new ReportManager { DataModel = model, Section = section, Populate = true };
  236. form.ShowDialog();
  237. ReloadActions(section, model);
  238. }
  239. private void ManageEmailTemplates(PanelAction action)
  240. {
  241. if (CurrentPanel is null)
  242. return;
  243. var section = CurrentPanel.SectionName;
  244. var model = CurrentPanel.DataModel(Selection.None);
  245. if (model == null)
  246. {
  247. MessageWindow.ShowMessage("No DataModel for " + section, "No DataModel");
  248. return;
  249. }
  250. var window = new EmailTemplateManagerWindow(model);
  251. window.ShowDialog();
  252. }
  253. #endregion
  254. #region Public Interface
  255. public void InitialiseSetupMenu(ContextMenu menu)
  256. {
  257. var items = new List<IPanelActionItem>();
  258. items.AddRange(SetupActions);
  259. items.Add(new PanelActionSeparator());
  260. if (Security.IsAllowed<CanCustomiseModules>())
  261. {
  262. items.Add(new PanelAction("Custom Modules", PRSDesktop.Resources.script, ManageModules));
  263. }
  264. if (Security.IsAllowed<CanDesignReports>())
  265. {
  266. items.Add(new PanelAction("Reports", PRSDesktop.Resources.printer, ManageReports));
  267. }
  268. if (Security.IsAllowed<CanDesignReports>())
  269. {
  270. items.Add(new PanelAction("Email Templates", PRSDesktop.Resources.email, ManageEmailTemplates));
  271. }
  272. for (var i = 0; i < items.Count; ++i)
  273. {
  274. var item = items[i];
  275. if (item is PanelAction setupAction)
  276. {
  277. menu.AddItem(setupAction.Caption, setupAction.Image, setupAction, setupAction.OnExecute);
  278. }
  279. else if (item is PanelActionSeparator && i > 0 && i < items.Count - 1)
  280. {
  281. var last = items[i - 1];
  282. if (last is not PanelActionSeparator)
  283. menu.AddSeparator();
  284. }
  285. }
  286. if (CurrentPanel?.GetType().HasInterface(typeof(IPropertiesPanel<>)) == true && Security.IsAllowed<CanConfigurePanels>())
  287. {
  288. var securityInterface = CurrentPanel?.GetType().GetInterfaceDefinition(typeof(IPropertiesPanel<,>));
  289. var canConfigure = false;
  290. if (securityInterface is not null)
  291. {
  292. var token = securityInterface.GenericTypeArguments[1];
  293. canConfigure = Security.IsAllowed(token);
  294. }
  295. else
  296. {
  297. canConfigure = Security.IsAllowed<CanConfigurePanels>();
  298. }
  299. if (canConfigure)
  300. {
  301. menu.AddItem("Configure Panel", PRSDesktop.Resources.edit, ConfigurePanel);
  302. }
  303. }
  304. if (menu.Items.Count == 0)
  305. {
  306. menu.AddItem("No Items", null, null, false);
  307. }
  308. }
  309. public IBasePanel LoadPanel(Type T, string moduleName)
  310. {
  311. return (LoadPanelMethod.MakeGenericMethod(T).Invoke(this, new object[] { moduleName })
  312. as IBasePanel)!;
  313. }
  314. private static readonly MethodInfo LoadPanelMethod = typeof(PanelHost)
  315. .GetMethods().First(x => x.Name == nameof(LoadPanel) && x.IsGenericMethod);
  316. public T LoadPanel<T>(string moduleName) where T : class, IBasePanel, new()
  317. {
  318. var panel = PanelUtils.LoadPanel<T>();
  319. CurrentPanel = panel;
  320. CurrentModuleName = moduleName;
  321. TrackedTicks = DateTime.Now;
  322. CurrentPanel.OnUpdateDataModel += ReloadActions;
  323. var model = CurrentPanel.DataModel(Selection.None);
  324. var section = CurrentPanel.SectionName;
  325. ReloadActions(section, model);
  326. return panel;
  327. }
  328. public void Refresh()
  329. {
  330. CurrentPanel?.Refresh();
  331. }
  332. private void Heartbeat(TimeSpan time, bool closing)
  333. {
  334. if (TrackedTicks == DateTime.MinValue)
  335. return;
  336. if (!closing && time.TotalMinutes < 5)
  337. return;
  338. TrackedTicks = DateTime.Now;
  339. if (CurrentPanel is not null)
  340. {
  341. var keys = TrackedKeys;
  342. TrackedKeys = 0;
  343. var clicks = TrackedClicks;
  344. TrackedClicks = 0;
  345. var tracking = new ModuleTracking
  346. {
  347. Date = DateTime.Today,
  348. Module = CurrentModuleName,
  349. Clicks = clicks,
  350. Keys = keys,
  351. ActiveTime = clicks + keys > 0 ? time : new TimeSpan(),
  352. IdleTime = clicks + keys == 0 ? time : new TimeSpan()
  353. };
  354. tracking.User.ID = ClientFactory.UserGuid;
  355. Client.Save(tracking, "", (mt, ex) => { });
  356. CurrentPanel.Heartbeat(time);
  357. }
  358. }
  359. public void Heartbeat()
  360. {
  361. Heartbeat(DateTime.Now - TrackedTicks, false);
  362. }
  363. public void UnloadPanel(CancelEventArgs? cancel)
  364. {
  365. if (CurrentPanel != null)
  366. {
  367. Heartbeat(DateTime.Now - TrackedTicks, true);
  368. PanelUtils.UnloadPanel(CurrentPanel, cancel);
  369. if (cancel?.Cancel == true)
  370. {
  371. return;
  372. }
  373. TrackedTicks = DateTime.MinValue;
  374. CurrentModuleName = "";
  375. TrackedClicks = 0;
  376. TrackedKeys = 0;
  377. }
  378. }
  379. #endregion
  380. }