PanelHost.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  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. if (ClientFactory.IsSupported<CustomModule>())
  99. {
  100. foreach (var (module, image) in CustomModuleUtils.LoadCustomModuleThumbnails(section, model))
  101. {
  102. HostControl.CreatePanelAction(new PanelAction
  103. {
  104. Caption = module.Name ?? "",
  105. Image = image,
  106. OnExecute = (action) =>
  107. {
  108. ClickModule(action, module);
  109. }
  110. });
  111. }
  112. }
  113. }
  114. private void ClickModule(PanelAction action, CustomModule code)
  115. {
  116. if (CurrentPanel != null)
  117. {
  118. if (!string.IsNullOrWhiteSpace(code.Script))
  119. try
  120. {
  121. Selection selection;
  122. if (code.SelectedRecords && code.AllRecords)
  123. selection = RecordSelectionDialog.Execute();
  124. else if (code.SelectedRecords)
  125. selection = Selection.Selected;
  126. else if (code.AllRecords)
  127. selection = Selection.All;
  128. else
  129. selection = Selection.None;
  130. var result = ScriptDocument.RunCustomModule(CurrentPanel.DataModel(selection), CurrentPanel.Selected(), code.Script);
  131. if (result)
  132. CurrentPanel.Refresh();
  133. }
  134. catch (CompileException c)
  135. {
  136. MessageWindow.ShowError(c.Message, c, shouldLog: false);
  137. }
  138. catch (Exception err)
  139. {
  140. MessageWindow.ShowError($"Unable to load {action.Caption}", err);
  141. }
  142. else
  143. MessageWindow.ShowMessage("Unable to load " + action.Caption, "Error", image: MessageWindow.WarningImage);
  144. }
  145. }
  146. private void ManageModules(PanelAction action)
  147. {
  148. if (CurrentPanel != null)
  149. {
  150. var section = CurrentPanel.SectionName;
  151. var dataModel = CurrentPanel.DataModel(Selection.Selected);
  152. var manager = new CustomModuleManager()
  153. {
  154. Section = section,
  155. DataModel = dataModel
  156. };
  157. manager.ShowDialog();
  158. ReloadActions(section, dataModel);
  159. }
  160. }
  161. #endregion
  162. #region Reports
  163. public static PanelAction CreateReportAction(ReportTemplate template, Func<Selection, DataModel> getDataModel)
  164. {
  165. var action = new PanelAction
  166. {
  167. Caption = template.Name,
  168. Image = PRSDesktop.Resources.printer,
  169. OnExecute = (action) =>
  170. {
  171. PrintReport(template.ID, getDataModel);
  172. }
  173. };
  174. if (Security.IsAllowed<CanDesignReports>())
  175. {
  176. var menu = new ContextMenu();
  177. menu.AddItem("Design Report", PRSDesktop.Resources.pencil, () => DesignReport(template.ID, getDataModel));
  178. action.Menu = menu;
  179. }
  180. return action;
  181. }
  182. private void CreateReports(string section, DataModel model)
  183. {
  184. if (CurrentPanel is null) return;
  185. var client = new Client<ReportTemplate>();
  186. var templates = ReportUtils.LoadReports(section, model, Columns.None<ReportTemplate>().Add(x => x.ID, x => x.Name));
  187. foreach (var template in templates)
  188. {
  189. HostControl.CreateReport(CreateReportAction(template, CurrentPanel.DataModel));
  190. }
  191. }
  192. private static void DesignReport(Guid templateID, Func<Selection, DataModel> getDataModel)
  193. {
  194. var template = new Client<ReportTemplate>().Load(new Filter<ReportTemplate>(x => x.ID).IsEqualTo(templateID)).FirstOrDefault();
  195. if (template is null)
  196. {
  197. Logger.Send(LogType.Error, "", $"No Report Template with ID '{templateID}'");
  198. MessageWindow.ShowMessage("Report does not exist!", "Error", image: MessageWindow.WarningImage);
  199. return;
  200. }
  201. ReportUtils.DesignReport(template, getDataModel(Selection.None));
  202. }
  203. private static void PrintReport(Guid id, Func<Selection, DataModel> getDataModel)
  204. {
  205. var template = new Client<ReportTemplate>().Load(new Filter<ReportTemplate>(x => x.ID).IsEqualTo(id)).FirstOrDefault();
  206. if (template == null)
  207. {
  208. Logger.Send(LogType.Error, "", $"No Report Template with ID '{id}'");
  209. MessageWindow.ShowMessage("Report does not exist!", "Error", image: MessageWindow.WarningImage);
  210. return;
  211. }
  212. var selection = Selection.None;
  213. if (template.SelectedRecords && template.AllRecords)
  214. selection = RecordSelectionDialog.Execute();
  215. else if (template.SelectedRecords)
  216. selection = Selection.Selected;
  217. else if (template.AllRecords)
  218. selection = Selection.All;
  219. else
  220. MessageWindow.ShowMessage(
  221. "Report must have either [Selected Records] or [All Records] checked to display!",
  222. "Error",
  223. image: MessageWindow.WarningImage);
  224. if (selection != Selection.None)
  225. ReportUtils.PreviewReport(template, getDataModel(selection), false, Security.IsAllowed<CanDesignReports>());
  226. }
  227. private void ManageReports(PanelAction action)
  228. {
  229. if (CurrentPanel is null)
  230. return;
  231. var section = CurrentPanel.SectionName;
  232. var model = CurrentPanel.DataModel(Selection.None);
  233. if (model == null)
  234. {
  235. MessageWindow.ShowMessage("No DataModel for " + CurrentPanel.SectionName, "No DataModel");
  236. return;
  237. }
  238. var form = new ReportManager { DataModel = model, Section = section, Populate = true };
  239. form.ShowDialog();
  240. ReloadActions(section, model);
  241. }
  242. private void ManageEmailTemplates(PanelAction action)
  243. {
  244. if (CurrentPanel is null)
  245. return;
  246. var section = CurrentPanel.SectionName;
  247. var model = CurrentPanel.DataModel(Selection.None);
  248. if (model == null)
  249. {
  250. MessageWindow.ShowMessage("No DataModel for " + section, "No DataModel");
  251. return;
  252. }
  253. var window = new EmailTemplateManagerWindow(model);
  254. window.ShowDialog();
  255. }
  256. #endregion
  257. #region Public Interface
  258. public void InitialiseSetupMenu(ContextMenu menu)
  259. {
  260. var items = new List<IPanelActionItem>();
  261. items.AddRange(SetupActions);
  262. items.Add(new PanelActionSeparator());
  263. if (Security.IsAllowed<CanCustomiseModules>())
  264. {
  265. items.Add(new PanelAction("Custom Modules", PRSDesktop.Resources.script, ManageModules));
  266. }
  267. if (Security.IsAllowed<CanDesignReports>())
  268. {
  269. items.Add(new PanelAction("Reports", PRSDesktop.Resources.printer, ManageReports));
  270. }
  271. if (Security.IsAllowed<CanDesignReports>())
  272. {
  273. items.Add(new PanelAction("Email Templates", PRSDesktop.Resources.email, ManageEmailTemplates));
  274. }
  275. for (var i = 0; i < items.Count; ++i)
  276. {
  277. var item = items[i];
  278. if (item is PanelAction setupAction)
  279. {
  280. menu.AddItem(setupAction.Caption, setupAction.Image, setupAction, setupAction.OnExecute);
  281. }
  282. else if (item is PanelActionSeparator && i > 0 && i < items.Count - 1)
  283. {
  284. var last = items[i - 1];
  285. if (last is not PanelActionSeparator)
  286. menu.AddSeparator();
  287. }
  288. }
  289. if (CurrentPanel?.GetType().HasInterface(typeof(IPropertiesPanel<>)) == true && Security.IsAllowed<CanConfigurePanels>())
  290. {
  291. var securityInterface = CurrentPanel?.GetType().GetInterfaceDefinition(typeof(IPropertiesPanel<,>));
  292. var canConfigure = false;
  293. if (securityInterface is not null)
  294. {
  295. var token = securityInterface.GenericTypeArguments[1];
  296. canConfigure = Security.IsAllowed(token);
  297. }
  298. else
  299. {
  300. canConfigure = Security.IsAllowed<CanConfigurePanels>();
  301. }
  302. if (canConfigure)
  303. {
  304. menu.AddItem("Configure Panel", PRSDesktop.Resources.edit, ConfigurePanel);
  305. }
  306. }
  307. if (menu.Items.Count == 0)
  308. {
  309. menu.AddItem("No Items", null, null, false);
  310. }
  311. }
  312. public IBasePanel LoadPanel(Type T, string moduleName)
  313. {
  314. return (LoadPanelMethod.MakeGenericMethod(T).Invoke(this, new object[] { moduleName })
  315. as IBasePanel)!;
  316. }
  317. private static readonly MethodInfo LoadPanelMethod = typeof(PanelHost)
  318. .GetMethods().First(x => x.Name == nameof(LoadPanel) && x.IsGenericMethod);
  319. public T LoadPanel<T>(string moduleName) where T : class, IBasePanel, new()
  320. {
  321. var panel = PanelUtils.LoadPanel<T>();
  322. CurrentPanel = panel;
  323. CurrentModuleName = moduleName;
  324. TrackedTicks = DateTime.Now;
  325. CurrentPanel.OnUpdateDataModel += ReloadActions;
  326. var model = CurrentPanel.DataModel(Selection.None);
  327. var section = CurrentPanel.SectionName;
  328. ReloadActions(section, model);
  329. return panel;
  330. }
  331. public void Refresh()
  332. {
  333. CurrentPanel?.Refresh();
  334. }
  335. private void Heartbeat(TimeSpan time, bool closing)
  336. {
  337. if (TrackedTicks == DateTime.MinValue)
  338. return;
  339. if (!closing && time.TotalMinutes < 5)
  340. return;
  341. TrackedTicks = DateTime.Now;
  342. if (CurrentPanel is not null)
  343. {
  344. //Logger.Send(LogType.Information, "", string.Format("Heartbeat: {0}", CurrentPanel_Label));
  345. if (ClientFactory.IsSupported<ModuleTracking>())
  346. {
  347. var keys = TrackedKeys;
  348. TrackedKeys = 0;
  349. var clicks = TrackedClicks;
  350. TrackedClicks = 0;
  351. var tracking = new ModuleTracking
  352. {
  353. Date = DateTime.Today,
  354. Module = CurrentModuleName,
  355. Clicks = clicks,
  356. Keys = keys,
  357. ActiveTime = clicks + keys > 0 ? time : new TimeSpan(),
  358. IdleTime = clicks + keys == 0 ? time : new TimeSpan()
  359. };
  360. tracking.User.ID = ClientFactory.UserGuid;
  361. new Client<ModuleTracking>().Save(tracking, "", (mt, ex) => { });
  362. }
  363. CurrentPanel.Heartbeat(time);
  364. }
  365. }
  366. public void Heartbeat()
  367. {
  368. Heartbeat(DateTime.Now - TrackedTicks, false);
  369. }
  370. public void UnloadPanel(CancelEventArgs? cancel)
  371. {
  372. if (CurrentPanel != null)
  373. {
  374. Heartbeat(DateTime.Now - TrackedTicks, true);
  375. PanelUtils.UnloadPanel(CurrentPanel, cancel);
  376. if (cancel?.Cancel == true)
  377. {
  378. return;
  379. }
  380. TrackedTicks = DateTime.MinValue;
  381. CurrentModuleName = "";
  382. TrackedClicks = 0;
  383. TrackedKeys = 0;
  384. }
  385. }
  386. #endregion
  387. }