DigitalFormsDashboard.xaml.cs 56 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477
  1. using Comal.Classes;
  2. using InABox.Clients;
  3. using InABox.Core;
  4. using InABox.DynamicGrid;
  5. using InABox.Reports;
  6. using InABox.Core.Reports;
  7. using InABox.Scripting;
  8. using InABox.WPF;
  9. using PRSDesktop.Configuration;
  10. using PRSDesktop.Forms;
  11. using PRSDesktop.WidgetGroups;
  12. using Syncfusion.UI.Xaml.Grid;
  13. using Syncfusion.UI.Xaml.Grid.Converter;
  14. using Syncfusion.XlsIO;
  15. using System;
  16. using System.Collections;
  17. using System.Collections.Generic;
  18. using System.Data;
  19. using System.Diagnostics;
  20. using System.Diagnostics.CodeAnalysis;
  21. using System.Linq;
  22. using System.Linq.Expressions;
  23. using System.Reflection;
  24. using System.Text;
  25. using System.Text.RegularExpressions;
  26. using System.Threading;
  27. using System.Threading.Tasks;
  28. using System.Windows;
  29. using System.Windows.Controls;
  30. using System.Windows.Data;
  31. using System.Windows.Documents;
  32. using System.Windows.Input;
  33. using System.Windows.Media;
  34. using InABox.Configuration;
  35. using SelectionChangedEventArgs = System.Windows.Controls.SelectionChangedEventArgs;
  36. using InABox.Wpf.Reports;
  37. using System.ComponentModel;
  38. using Syncfusion.Windows.Shared;
  39. using System.Globalization;
  40. using System.Windows.Media.Imaging;
  41. using System.Drawing;
  42. using Image = System.Windows.Controls.Image;
  43. namespace PRSDesktop
  44. {
  45. public enum DateFilterType
  46. {
  47. Today,
  48. Yesterday,
  49. Week,
  50. SevenDays,
  51. Month,
  52. ThirtyDays,
  53. Year,
  54. TwelveMonths,
  55. Custom
  56. }
  57. public class DFFilter : BaseObject
  58. {
  59. [EditorSequence(1)]
  60. [TextBoxEditor]
  61. public string Name { get; set; } = "";
  62. [EditorSequence(2)]
  63. [FilterEditor]
  64. public string Filter { get; set; } = "";
  65. }
  66. internal class MileStoneImageConverter : IValueConverter
  67. {
  68. public object? Convert(object value, Type targetType, object parameter, CultureInfo culture)
  69. {
  70. return Equals(value, DateTime.MinValue) ? null : Resources.milestone.AsBitmapImage();
  71. }
  72. public object? ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
  73. {
  74. return null;
  75. }
  76. }
  77. public class DigitalFormsDashboardProperties : IUserConfigurationSettings, IDashboardProperties
  78. {
  79. public bool ShowJobFilter { get; set; } = false;
  80. public bool ShowDateFilter { get; set; } = true;
  81. public bool UseIconsForFormTypes { get; set; } = false;
  82. public string? Category { get; set; }
  83. public Guid SelectedForm { get; set; }
  84. public Guid JobID { get; set; }
  85. public DateFilterType DateFilterType { get; set; } = DateFilterType.Today;
  86. public DateTime FromDate { get; set; }
  87. public DateTime ToDate { get; set; }
  88. public Dictionary<string, List<DFFilter>> Filters { get; set; } = new();
  89. }
  90. public class DigitalFormsDashboardElement : DashboardElement<DigitalFormsDashboard, Common, DigitalFormsDashboardProperties> { }
  91. /// <summary>
  92. /// Interaction logic for DigitalFormsDashboard.xaml
  93. /// </summary>
  94. public partial class DigitalFormsDashboard : UserControl,
  95. IDashboardWidget<Common, DigitalFormsDashboardProperties>,
  96. IBasePanel,
  97. IRequiresSecurity<CanViewDigitalFormsDashbaord>,
  98. IHeaderDashboard, IActionsDashboard
  99. {
  100. public DigitalFormsDashboardProperties Properties { get; set; }
  101. public event LoadSettings<DigitalFormsDashboardProperties>? LoadSettings;
  102. public event SaveSettings<DigitalFormsDashboardProperties>? SaveSettings;
  103. private List<DigitalForm> DigitalForms;
  104. private List<Job> Jobs;
  105. private List<Tuple<string, Type?, string>> Categories;
  106. public DashboardHeader Header { get; set; } = new();
  107. private bool IsQAForm = false;
  108. private List<QAQuestion> Questions = new();
  109. private bool IsSetup = false;
  110. public DigitalFormsDashboard()
  111. {
  112. InitializeComponent();
  113. }
  114. public void Setup()
  115. {
  116. var results = Client.QueryMultiple(
  117. new KeyedQueryDef<DigitalForm>(new Filter<DigitalForm>(x => x.Active).IsEqualTo(true)),
  118. new KeyedQueryDef<Job>(
  119. LookupFactory.DefineFilter<Job>(),
  120. new Columns<Job>(x => x.ID)
  121. .Add(x => x.JobNumber)
  122. .Add(x => x.Name)));
  123. DigitalForms = results.Get<DigitalForm>().ToObjects<DigitalForm>().ToList();
  124. var categories = new DigitalFormCategoryLookups(null);
  125. categories.OnAfterGenerateLookups += (sender, entries) => { entries.Insert(0, new LookupEntry("", "Select Category")); };
  126. Categories = categories.AsTable("AppliesTo")
  127. .Rows.Select(x =>
  128. {
  129. var appliesTo = x.Get<string>("AppliesTo");
  130. if (CategoryToType(appliesTo, out var formType, out var parentType))
  131. {
  132. return new Tuple<string, Type?, string>(appliesTo, formType, x.Get<string>("Display"));
  133. }
  134. else
  135. {
  136. return new Tuple<string, Type?, string>(appliesTo, null, x.Get<string>("Display"));
  137. }
  138. }).ToList();
  139. Jobs = results.Get<Job>().ToObjects<Job>().ToList();
  140. Jobs.Insert(0, new Job { ID = Guid.Empty, JobNumber = "ALL", Name = "All Jobs" });
  141. SetupHeader();
  142. SetupFilters();
  143. IsSetup = true;
  144. }
  145. #region Header
  146. private StackPanel CategoryButtonPanel;
  147. private Dictionary<Type, Button> CategoryButtons = new();
  148. private ComboBox CategoryBox;
  149. private ComboBox FormBox;
  150. private ComboBox JobBox;
  151. private ComboBox DateTypeBox;
  152. private Label FromLabel;
  153. private DatePicker FromPicker;
  154. private Label ToLabel;
  155. private DatePicker ToPicker;
  156. private Button Print;
  157. private Button FilterBtn;
  158. private static Dictionary<DateFilterType, string> FilterTypes = new()
  159. {
  160. { DateFilterType.Today, "Today" },
  161. { DateFilterType.Yesterday, "Yesterday" },
  162. { DateFilterType.Week, "Week to Date" },
  163. { DateFilterType.SevenDays, "Last 7 Days" },
  164. { DateFilterType.Month, "Month to Date" },
  165. { DateFilterType.ThirtyDays, "Last 30 Days" },
  166. { DateFilterType.Year, "Year to Date" },
  167. { DateFilterType.TwelveMonths, "Last 12 Months" },
  168. { DateFilterType.Custom, "Custom" }
  169. };
  170. private static readonly SolidColorBrush EnabledBrush = new SolidColorBrush(Colors.LightYellow);
  171. private static readonly SolidColorBrush DisabledBrush = new SolidColorBrush(Colors.LightGray);
  172. private static readonly Dictionary<Type, Bitmap> CategoryImages = new()
  173. {
  174. { typeof(AssignmentForm), PRSDesktop.Resources.assignments },
  175. { typeof(KanbanForm), PRSDesktop.Resources.kanban },
  176. { typeof(JobForm), PRSDesktop.Resources.project },
  177. { typeof(JobITPForm), PRSDesktop.Resources.checklist },
  178. { typeof(EmployeeForm), PRSDesktop.Resources.employees },
  179. { typeof(LeaveRequestForm), PRSDesktop.Resources.leave },
  180. { typeof(ManufacturingPacketStage), PRSDesktop.Resources.factory },
  181. { typeof(TimeSheetForm), PRSDesktop.Resources.time },
  182. { typeof(PurchaseOrderItemForm), PRSDesktop.Resources.purchase },
  183. { typeof(DeliveryForm), PRSDesktop.Resources.truck },
  184. };
  185. public void SetupHeader()
  186. {
  187. CategoryBox = new ComboBox
  188. {
  189. Width = 150,
  190. VerticalContentAlignment = VerticalAlignment.Center,
  191. Margin = new Thickness(0, 0, 5, 0)
  192. };
  193. CategoryBox.ItemsSource = Categories;
  194. CategoryBox.SelectedValuePath = "Item1";
  195. CategoryBox.DisplayMemberPath = "Item3";
  196. CategoryBox.SelectedValue = Properties.Category;
  197. CategoryBox.SelectionChanged += Category_SelectionChanged;
  198. CategoryButtonPanel = new StackPanel
  199. {
  200. Orientation = Orientation.Horizontal,
  201. Margin = new Thickness(0, 0, 5, 0)
  202. };
  203. CategoryButtons.Clear();
  204. foreach(var (appliesTo, category, display) in Categories)
  205. {
  206. if(category is null)
  207. {
  208. continue;
  209. }
  210. var button = new Button();
  211. button.Tag = appliesTo;
  212. button.Margin = new Thickness(0, 0, 2, 0);
  213. button.BorderBrush = new SolidColorBrush(Colors.Gray);
  214. button.BorderThickness = new Thickness(0.75);
  215. button.Width = 25D;
  216. button.Padding = new Thickness(2);
  217. button.ToolTip = category.EntityName().Split('.').Last().SplitCamelCase();
  218. if (CategoryImages.TryGetValue(category, out var image))
  219. {
  220. button.Content = new Image { Source = image.AsBitmapImage() };
  221. }
  222. else
  223. {
  224. button.Content = display;
  225. }
  226. button.Click += CatagoryButton_Click;
  227. CategoryButtons.Add(category, button);
  228. CategoryButtonPanel.Children.Add(button);
  229. }
  230. if (Properties.UseIconsForFormTypes)
  231. {
  232. CategoryButtonPanel.Visibility = Visibility.Visible;
  233. CategoryBox.Visibility = Visibility.Collapsed;
  234. }
  235. else
  236. {
  237. CategoryButtonPanel.Visibility = Visibility.Collapsed;
  238. CategoryBox.Visibility = Visibility.Visible;
  239. }
  240. FormBox = new ComboBox
  241. {
  242. Width = 250,
  243. VerticalContentAlignment = VerticalAlignment.Center,
  244. Margin = new Thickness(0, 0, 5, 0),
  245. IsEnabled = false
  246. };
  247. FormBox.SelectionChanged += FormBox_SelectionChanged;
  248. FormBox.ItemsSource = new Dictionary<DigitalForm, string> { };
  249. JobBox = new ComboBox
  250. {
  251. Width = 250,
  252. Margin = new Thickness(0, 0, 5, 0),
  253. VerticalContentAlignment = VerticalAlignment.Center
  254. };
  255. JobBox.ItemsSource = Jobs.ToDictionary(x => x.ID, x => $"{x.JobNumber} : {x.Name}");
  256. JobBox.SelectedIndex = 0;
  257. JobBox.SelectedValuePath = "Key";
  258. JobBox.DisplayMemberPath = "Value";
  259. JobBox.SelectionChanged += JobBox_SelectionChanged;
  260. DateTypeBox = new ComboBox
  261. {
  262. Width = 120,
  263. VerticalContentAlignment = VerticalAlignment.Center
  264. };
  265. DateTypeBox.ItemsSource = FilterTypes;
  266. DateTypeBox.SelectedValuePath = "Key";
  267. DateTypeBox.DisplayMemberPath = "Value";
  268. DateTypeBox.SelectedValue = Properties.DateFilterType;
  269. DateTypeBox.SelectionChanged += DateTypeBox_SelectionChanged;
  270. FromLabel = new Label { Content = "From", VerticalContentAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 5, 0) };
  271. FromPicker = new DatePicker
  272. {
  273. Width = 100,
  274. Background = new SolidColorBrush(Colors.LightYellow),
  275. VerticalContentAlignment = VerticalAlignment.Center,
  276. FirstDayOfWeek = DayOfWeek.Monday,
  277. Margin = new Thickness(0, 0, 5, 0)
  278. };
  279. FromPicker.SelectedDateChanged += FromPicker_SelectedDateChanged;
  280. ToLabel = new Label { Content = "To", VerticalContentAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 5, 0) };
  281. ToPicker = new DatePicker
  282. {
  283. Width = 100,
  284. Background = new SolidColorBrush(Colors.LightYellow),
  285. VerticalContentAlignment = VerticalAlignment.Center,
  286. FirstDayOfWeek = DayOfWeek.Monday,
  287. Margin = new Thickness(0, 0, 5, 0)
  288. };
  289. ToPicker.SelectedDateChanged += ToPicker_SelectedDateChanged;
  290. Print = new Button
  291. {
  292. Width = 25,
  293. Height = 25,
  294. Content = new Image { Source = PRSDesktop.Resources.printer.AsBitmapImage() }
  295. };
  296. Print.Click += Print_Click;
  297. FilterBtn = new Button
  298. {
  299. Width = 25,
  300. Height = 25,
  301. Content = new Image { Source = InABox.Wpf.Resources.filter.AsBitmapImage() },
  302. Margin = new Thickness(0, 0, 5, 0)
  303. };
  304. FilterBtn.Click += Filter_Click;
  305. Header.BeginUpdate()
  306. .Clear()
  307. .Add(CategoryBox)
  308. .Add(CategoryButtonPanel)
  309. .Add(FormBox)
  310. .Add(JobBox)
  311. .Add(DateTypeBox)
  312. .Add(FromLabel)
  313. .Add(FromPicker)
  314. .Add(ToLabel)
  315. .Add(ToPicker)
  316. .AddRight(FilterBtn)
  317. .AddRight(Print)
  318. .EndUpdate();
  319. UpdateCategory(Properties.Category);
  320. }
  321. private void Filter_Click(object sender, RoutedEventArgs e)
  322. {
  323. var menu = new ContextMenu();
  324. menu.AddCheckItem("Use Form Type Icons", ToggleFormTypeIcons, isChecked: Properties.UseIconsForFormTypes);
  325. menu.AddCheckItem("Show Date Filter", ToggleDateFilter, Properties.ShowDateFilter);
  326. menu.AddCheckItem("Show Job Filter", ToggleJobFilter, Properties.ShowJobFilter);
  327. menu.AddSeparator();
  328. if (ParentType is not null)
  329. {
  330. if (Properties.Filters.TryGetValue(ParentType.Name, out var filters))
  331. {
  332. var i = 0;
  333. var items = new List<MenuItem>();
  334. foreach (var filter in filters)
  335. {
  336. items.Add(menu.AddCheckItem(
  337. filter.Name,
  338. new Tuple<int, string, List<MenuItem>>(i, filter.Filter, items),
  339. ExecuteFilter,
  340. i == CustomFilterIndex));
  341. ++i;
  342. }
  343. }
  344. menu.AddSeparatorIfNeeded();
  345. menu.AddItem("Manage Filters", InABox.Wpf.Resources.filter, ManageFilters_Click);
  346. }
  347. menu.IsOpen = true;
  348. }
  349. private void ToggleFormTypeIcons(bool isChecked)
  350. {
  351. Properties.UseIconsForFormTypes = !Properties.UseIconsForFormTypes;
  352. if (Properties.UseIconsForFormTypes)
  353. {
  354. CategoryButtonPanel.Visibility = Visibility.Visible;
  355. CategoryBox.Visibility = Visibility.Collapsed;
  356. }
  357. else
  358. {
  359. CategoryButtonPanel.Visibility = Visibility.Collapsed;
  360. CategoryBox.Visibility = Visibility.Visible;
  361. }
  362. }
  363. private void Print_Click(object sender, RoutedEventArgs e)
  364. {
  365. var menu = new ContextMenu();
  366. foreach (var report in ReportUtils.LoadReports(SectionName, DataModel(Selection.None)))
  367. {
  368. menu.AddItem(report.Name, PRSDesktop.Resources.printer, report, PrintReport_Click);
  369. }
  370. if (Security.IsAllowed<CanDesignReports>())
  371. {
  372. menu.AddSeparatorIfNeeded();
  373. menu.AddItem("Manage Reports", PRSDesktop.Resources.printer, ManageReports_Click);
  374. }
  375. menu.IsOpen = true;
  376. }
  377. private void PrintReport_Click(ReportTemplate obj)
  378. {
  379. Selection selection;
  380. if (obj.SelectedRecords && obj.AllRecords)
  381. selection = RecordSelectionDialog.Execute();
  382. else if (obj.SelectedRecords)
  383. selection = Selection.Selected;
  384. else if (obj.AllRecords)
  385. selection = Selection.All;
  386. else
  387. selection = Selection.None;
  388. ReportUtils.PreviewReport(obj, DataModel(selection), false, Security.IsAllowed<CanDesignReports>());
  389. }
  390. private void ManageReports_Click()
  391. {
  392. var manager = new ReportManager()
  393. {
  394. DataModel = DataModel(Selection.None),
  395. Section = SectionName,
  396. Populate = true
  397. };
  398. manager.ShowDialog();
  399. }
  400. private void Search_KeyUp(object sender, KeyEventArgs e)
  401. {
  402. Refresh();
  403. }
  404. private void JobBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
  405. {
  406. Properties.JobID = (Guid)JobBox.SelectedValue;
  407. Refresh();
  408. }
  409. private void SetDateFilterVisibility(bool visible)
  410. {
  411. var visibility = visible ? Visibility.Visible : Visibility.Collapsed;
  412. FromLabel.Visibility = visibility;
  413. FromPicker.Visibility = visibility;
  414. ToLabel.Visibility = visibility;
  415. ToPicker.Visibility = visibility;
  416. DateTypeBox.Visibility = visibility;
  417. }
  418. private void SetJobFilterVisibility(bool visible)
  419. {
  420. var visibility = visible ? Visibility.Visible : Visibility.Collapsed;
  421. JobBox.Visibility = visibility;
  422. }
  423. private void DateTypeBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
  424. {
  425. var filterType = (DateFilterType)DateTypeBox.SelectedValue;
  426. Properties.DateFilterType = filterType;
  427. if (filterType == DateFilterType.Custom)
  428. {
  429. if (FromPicker.SelectedDate == null || FromPicker.SelectedDate == DateTime.MinValue)
  430. {
  431. Properties.FromDate = DateTime.Today;
  432. }
  433. else
  434. {
  435. Properties.FromDate = FromPicker.SelectedDate.Value;
  436. }
  437. if (ToPicker.SelectedDate == null || ToPicker.SelectedDate == DateTime.MinValue)
  438. {
  439. Properties.ToDate = DateTime.Today;
  440. }
  441. else
  442. {
  443. Properties.ToDate = ToPicker.SelectedDate.Value;
  444. }
  445. }
  446. SetupDateFilters();
  447. Refresh();
  448. }
  449. private void FromPicker_SelectedDateChanged(object? sender, SelectionChangedEventArgs e)
  450. {
  451. Properties.FromDate = FromPicker.SelectedDate ?? DateTime.Today;
  452. Refresh();
  453. }
  454. private void ToPicker_SelectedDateChanged(object? sender, SelectionChangedEventArgs e)
  455. {
  456. Properties.ToDate = ToPicker.SelectedDate ?? DateTime.Today;
  457. Refresh();
  458. }
  459. private bool _changing = false;
  460. private void UpdateCategory(string? category)
  461. {
  462. _changing = true;
  463. Properties.Category = category;
  464. SetCategory(Properties.Category);
  465. foreach(var (type, button) in CategoryButtons)
  466. {
  467. if(type == FormType)
  468. {
  469. button.Background = EnabledBrush;
  470. }
  471. else
  472. {
  473. button.Background = DisabledBrush;
  474. }
  475. }
  476. var jobLink = FormType is not null ? GetJobLink("", FormType) : "";
  477. if (string.IsNullOrWhiteSpace(jobLink))
  478. {
  479. var jobID = Properties.JobID;
  480. JobBox.SelectedValue = Guid.Empty;
  481. JobBox.IsEnabled = false;
  482. Properties.JobID = jobID;
  483. }
  484. else
  485. {
  486. JobBox.SelectedValue = Properties.JobID;
  487. JobBox.IsEnabled = true;
  488. }
  489. if (ParentType is null)
  490. {
  491. FormBox.IsEnabled = false;
  492. FormBox.ItemsSource = new Dictionary<DigitalForm, string> { };
  493. }
  494. else
  495. {
  496. var forms = DigitalForms.Where(x => x.AppliesTo == ParentType.Name).ToList();
  497. forms.Insert(0, new DigitalForm { ID = Guid.Empty, Description = "Select Form" });
  498. FormBox.ItemsSource = forms;
  499. if (Properties.SelectedForm != Guid.Empty && forms.Where(x => x.ID == Properties.SelectedForm).FirstOrDefault() is DigitalForm form)
  500. {
  501. FormBox.SelectedItem = form;
  502. }
  503. else
  504. {
  505. FormBox.SelectedIndex = 0;
  506. }
  507. FormBox.DisplayMemberPath = "Description";
  508. FormBox.IsEnabled = true;
  509. }
  510. _changing = false;
  511. OnUpdateDataModel?.Invoke(SectionName, DataModel(Selection.None));
  512. }
  513. private void CatagoryButton_Click(object sender, RoutedEventArgs e)
  514. {
  515. UpdateCategory(((sender as Button)!.Tag as string)!);
  516. Refresh();
  517. }
  518. private void Category_SelectionChanged(object sender, SelectionChangedEventArgs e)
  519. {
  520. UpdateCategory((CategoryBox.SelectedValue as string)!);
  521. Refresh();
  522. }
  523. private void FormBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
  524. {
  525. Form = FormBox.SelectedValue as DigitalForm;
  526. Properties.SelectedForm = Form?.ID ?? Guid.Empty;
  527. if (!_changing)
  528. {
  529. OnUpdateDataModel?.Invoke(SectionName, DataModel(Selection.None));
  530. }
  531. Refresh();
  532. }
  533. #endregion
  534. #region IBasePanel
  535. public bool IsReady { get; set; }
  536. public event DataModelUpdateEvent? OnUpdateDataModel;
  537. public void CreateToolbarButtons(IPanelHost host)
  538. {
  539. }
  540. public void Heartbeat(TimeSpan time)
  541. {
  542. }
  543. public Dictionary<string, object[]> Selected()
  544. {
  545. return new Dictionary<string, object[]>();
  546. }
  547. #endregion
  548. public string SectionName
  549. {
  550. get
  551. {
  552. if (Form is null || Form.ID == Guid.Empty)
  553. return "Digital Forms";
  554. return Form.ID.ToString() ?? "Digital Forms";
  555. }
  556. }
  557. public DataModel DataModel(Selection selection)
  558. {
  559. if (FormType is null || Form is null || Form.ID == Guid.Empty)
  560. {
  561. return new AutoDataModel<DigitalForm>(new Filter<DigitalForm>().None());
  562. }
  563. IFilter filter;
  564. switch (selection)
  565. {
  566. case Selection.Selected:
  567. var formids = DataGrid.SelectedItems.Select(x => (x as DataRowView)!.Row["ID"]).ToArray();
  568. filter = Filter.Create<Entity>(FormType, x => x.ID).InList(formids);
  569. break;
  570. case Selection.All:
  571. filter = Filter.Create(FormType).All();
  572. break;
  573. case Selection.None:
  574. default:
  575. filter = Filter.Create(FormType).None();
  576. break;
  577. }
  578. return (Activator.CreateInstance(typeof(DigitalFormReportDataModel<>)!
  579. .MakeGenericType(FormType), new object?[] { filter, Form.ID }) as DataModel)!;
  580. }
  581. public void BuildActionsMenu(ContextMenu menu)
  582. {
  583. menu.AddItem("Export", InABox.Wpf.Resources.doc_xls, Export_Click, Form is not null && Form.ID != Guid.Empty);
  584. var loadingModules = menu.AddItem("Loading Modules...", null, null, false);
  585. Task.Run(() =>
  586. {
  587. return CustomModuleUtils.LoadCustomModuleThumbnails(SectionName, DataModel(Selection.None));
  588. }).ContinueWith((task) =>
  589. {
  590. var modules = task.Result;
  591. var index = menu.Items.IndexOf(loadingModules);
  592. menu.Items.RemoveAt(index);
  593. foreach (var (module, image) in modules)
  594. {
  595. menu.AddItem(module.Name, image, module, ExecuteModule_Click, index: index);
  596. ++index;
  597. }
  598. }, TaskScheduler.FromCurrentSynchronizationContext());
  599. if (Security.IsAllowed<CanCustomiseModules>())
  600. {
  601. menu.AddSeparatorIfNeeded();
  602. menu.AddItem("Manage Modules", PRSDesktop.Resources.script, ManageModules_Click);
  603. }
  604. }
  605. private void Export_Click()
  606. {
  607. var formName = Regex.Replace(Form?.Description ?? "", "[^ a-zA-Z0-9]", "");
  608. var filename = string.Format("{0} - {1} - {2:yyyy-MM-dd} - {3:yyyy-MM-dd}.xlsx", ParentType!.Name, formName, From, To);
  609. var options = new ExcelExportingOptions();
  610. options.ExcelVersion = ExcelVersion.Excel2013;
  611. options.ExportStackedHeaders = true;
  612. var excelEngine = DataGrid.ExportToExcel(DataGrid.View, options);
  613. var workBook = excelEngine.Excel.Workbooks[0];
  614. var sheet = workBook.Worksheets[0];
  615. sheet.Name = "Summary";
  616. sheet.UsedRange.AutofitRows();
  617. sheet.UsedRange.AutofitColumns();
  618. sheet = workBook.Worksheets.Create("Questions");
  619. sheet.Move(0);
  620. var questions = new Client<QAQuestion>().Query(new Filter<QAQuestion>(x => x.Form.ID).IsEqualTo(Form!.ID));
  621. sheet.Range[1, 1].Text = Form?.Description ?? "";
  622. sheet.Range[1, 1, 1, 3].Merge();
  623. var i = 1;
  624. foreach (var row in questions.Rows)
  625. if (!row.Get<QAQuestion, QAAnswer>(x => x.Answer).Equals(QAAnswer.Comment))
  626. {
  627. sheet.Range[i + 2, 1].Text = string.Format("{0}.", i);
  628. sheet.Range[i + 2, 2].Text = string.Format("{0}", row.Get<QAQuestion, string>(x => x.Question));
  629. sheet.Range[i + 2, 3].Text = string.Format("[{0}]", row.Get<QAQuestion, string>(x => x.Code));
  630. i++;
  631. }
  632. sheet.UsedRange.AutofitRows();
  633. sheet.UsedRange.AutofitColumns();
  634. try
  635. {
  636. workBook.SaveAs(filename);
  637. var startInfo = new ProcessStartInfo(filename);
  638. startInfo.Verb = "open";
  639. startInfo.UseShellExecute = true;
  640. Process.Start(startInfo);
  641. }
  642. catch
  643. {
  644. MessageBox.Show(string.Format("Unable to Save/Launch [{0}]!\n\nIs the file already open?", filename));
  645. }
  646. }
  647. private void ManageFilters_Click()
  648. {
  649. var filters = Properties.Filters.GetValueOrDefault(ParentType!.Name) ?? new List<DFFilter>();
  650. var gridFilters = new CoreFilterDefinitions();
  651. gridFilters.AddRange(filters.Select(x => new CoreFilterDefinition { Name = x.Name, Filter = x.Filter }));
  652. var grid = new DynamicGridFilterEditor(gridFilters, FormType!);
  653. if (grid.ShowDialog() == true)
  654. {
  655. Properties.Filters[ParentType!.Name] = grid.Filters.Select(x => new DFFilter { Name = x.Name, Filter = x.Filter }).ToList();
  656. if (CustomFilterIndex != null)
  657. {
  658. Refresh();
  659. }
  660. }
  661. }
  662. private void ExecuteFilter(Tuple<int, string, List<MenuItem>> tag, bool isChecked)
  663. {
  664. var (index, filter, items) = tag;
  665. if (isChecked)
  666. {
  667. var i = 0;
  668. foreach (var item in items)
  669. {
  670. item.IsChecked = i == index;
  671. ++i;
  672. }
  673. }
  674. if (isChecked)
  675. {
  676. CustomFilter = Serialization.Deserialize(typeof(Filter<>).MakeGenericType(FormType!), filter) as IFilter;
  677. CustomFilterIndex = index;
  678. Refresh();
  679. }
  680. else if (index == CustomFilterIndex)
  681. {
  682. CustomFilter = null;
  683. CustomFilterIndex = null;
  684. Refresh();
  685. }
  686. }
  687. private void ExecuteModule_Click(CustomModule obj)
  688. {
  689. if (!string.IsNullOrWhiteSpace(obj.Script))
  690. try
  691. {
  692. Selection selection;
  693. if (obj.SelectedRecords && obj.AllRecords)
  694. selection = RecordSelectionDialog.Execute();
  695. else if (obj.SelectedRecords)
  696. selection = Selection.Selected;
  697. else if (obj.AllRecords)
  698. selection = Selection.All;
  699. else
  700. selection = Selection.None;
  701. var result = ScriptDocument.RunCustomModule(DataModel(selection), new Dictionary<string, object[]>(), obj.Script);
  702. if (result)
  703. Refresh();
  704. }
  705. catch (CompileException c)
  706. {
  707. MessageBox.Show(c.Message);
  708. }
  709. catch (Exception err)
  710. {
  711. MessageBox.Show(CoreUtils.FormatException(err));
  712. }
  713. else
  714. MessageBox.Show("Unable to load " + obj.Name);
  715. }
  716. private void ManageModules_Click()
  717. {
  718. var section = SectionName;
  719. var dataModel = DataModel(Selection.Selected);
  720. var manager = new CustomModuleManager()
  721. {
  722. Section = section,
  723. DataModel = dataModel
  724. };
  725. manager.ShowDialog();
  726. }
  727. private void ToggleDateFilter(bool isChecked)
  728. {
  729. Properties.ShowDateFilter = isChecked;
  730. SetDateFilterVisibility(Properties.ShowDateFilter);
  731. }
  732. private void ToggleJobFilter(bool isChecked)
  733. {
  734. Properties.ShowJobFilter = isChecked;
  735. SetJobFilterVisibility(Properties.ShowJobFilter);
  736. Refresh();
  737. }
  738. #region Filtering
  739. private DateTime From { get; set; }
  740. private DateTime To { get; set; }
  741. private bool IsEntityForm { get; set; }
  742. private Type? ParentType { get; set; }
  743. private Type? FormType { get; set; }
  744. private DigitalForm? Form { get; set; }
  745. private IFilter? CustomFilter { get; set; }
  746. private int? CustomFilterIndex { get; set; }
  747. private readonly Dictionary<string, string> QuestionCodes = new();
  748. private static int WeekDay(DateTime date)
  749. {
  750. if (date.DayOfWeek == DayOfWeek.Sunday)
  751. return 7;
  752. return (int)date.DayOfWeek - 1;
  753. }
  754. private void SetupDateFilters()
  755. {
  756. switch (Properties.DateFilterType)
  757. {
  758. case DateFilterType.Today:
  759. From = DateTime.Today;
  760. To = DateTime.Today;
  761. break;
  762. case DateFilterType.Yesterday:
  763. From = DateTime.Today.AddDays(-1);
  764. To = DateTime.Today.AddDays(-1);
  765. break;
  766. case DateFilterType.Week:
  767. From = DateTime.Today.AddDays(-WeekDay(DateTime.Today));
  768. To = DateTime.Today;
  769. break;
  770. case DateFilterType.SevenDays:
  771. From = DateTime.Today.AddDays(-6);
  772. To = DateTime.Today;
  773. break;
  774. case DateFilterType.Month:
  775. From = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1);
  776. To = DateTime.Today;
  777. break;
  778. case DateFilterType.ThirtyDays:
  779. From = DateTime.Today.AddDays(-29);
  780. To = DateTime.Today;
  781. break;
  782. case DateFilterType.Year:
  783. From = new DateTime(DateTime.Today.Year, 1, 1);
  784. To = DateTime.Today;
  785. break;
  786. case DateFilterType.TwelveMonths:
  787. From = DateTime.Today.AddYears(-1).AddDays(1);
  788. To = DateTime.Today;
  789. break;
  790. case DateFilterType.Custom:
  791. From = Properties.FromDate;
  792. To = Properties.ToDate;
  793. break;
  794. }
  795. DateTypeBox.SelectedValue = Properties.DateFilterType;
  796. FromPicker.SelectedDate = From;
  797. ToPicker.SelectedDate = To;
  798. var enabledPicker = Properties.DateFilterType == DateFilterType.Custom;
  799. FromPicker.IsEnabled = enabledPicker;
  800. ToPicker.IsEnabled = enabledPicker;
  801. }
  802. private void SetupJobFilter()
  803. {
  804. JobBox.SelectedValue = Properties.JobID;
  805. }
  806. private void SetupFilters()
  807. {
  808. SetupDateFilters();
  809. SetupJobFilter();
  810. SetDateFilterVisibility(Properties.ShowDateFilter);
  811. SetJobFilterVisibility(Properties.ShowJobFilter);
  812. }
  813. #region Categories
  814. private static Dictionary<string, Tuple<Type, Type>>? FormInstanceTypes;
  815. private static readonly Dictionary<Type, List<Tuple<string, string>>> parentColumns = new()
  816. {
  817. { typeof(Kanban), new() { new("Parent.Number", "Task No") } },
  818. { typeof(Job), new() { new("Parent.JobNumber", "Job No") } },
  819. { typeof(JobITP), new() { new("Parent.Code", "Code") } },
  820. { typeof(Assignment), new() { new("Parent.Number", "Ass. No") } },
  821. { typeof(TimeSheet), new() { } },
  822. { typeof(LeaveRequest), new() { } },
  823. { typeof(Employee), new() { new("Parent.Code", "Employee") } },
  824. { typeof(PurchaseOrderItem), new() { new("Parent.PONumber", "PO No") } },
  825. };
  826. private static bool CategoryToType(string category, [NotNullWhen(true)] out Type? formType, [NotNullWhen(true)] out Type? parentType)
  827. {
  828. FormInstanceTypes ??= CoreUtils.TypeList(
  829. AppDomain.CurrentDomain.GetAssemblies(),
  830. x => !x.IsAbstract && x.GetInterfaces().Contains(typeof(IDigitalFormInstance))
  831. ).Select(x =>
  832. {
  833. var inter = x.GetInterfaces()
  834. .Where(x => x.IsGenericType && x.GetGenericTypeDefinition().Equals(typeof(IDigitalFormInstance<>))).FirstOrDefault();
  835. if (inter is not null)
  836. {
  837. var link = inter.GenericTypeArguments[0];
  838. var entityLinkDef = link.GetSuperclassDefinition(typeof(EntityLink<>));
  839. if (entityLinkDef is not null)
  840. {
  841. var entityType = entityLinkDef.GenericTypeArguments[0];
  842. return new Tuple<string, Type, Type>(entityType.Name, x, entityType);
  843. }
  844. }
  845. return null;
  846. }).Where(x => x is not null).ToDictionary(x => x!.Item1, x => new Tuple<Type, Type>(x!.Item2, x!.Item3));
  847. if (!FormInstanceTypes.TryGetValue(category, out var result))
  848. {
  849. formType = null;
  850. parentType = null;
  851. return false;
  852. }
  853. formType = result.Item1;
  854. parentType = result.Item2;
  855. return true;
  856. }
  857. private void SetCategory(string? category)
  858. {
  859. CustomFilter = null;
  860. CustomFilterIndex = null;
  861. if (category is null || !CategoryToType(category, out var formType, out var parentType))
  862. {
  863. IsEntityForm = false;
  864. ParentType = null;
  865. FormType = null;
  866. return;
  867. }
  868. IsEntityForm = formType.IsSubclassOfRawGeneric(typeof(EntityForm<,,>));
  869. ParentType = parentType;
  870. FormType = formType;
  871. }
  872. #endregion
  873. private string GetJobLink(string prefix, Type type)
  874. {
  875. var props = type.GetProperties().Where(x =>
  876. x.PropertyType.BaseType != null && x.PropertyType.BaseType.IsGenericType &&
  877. x.PropertyType.BaseType.GetGenericTypeDefinition() == typeof(EntityLink<>));
  878. foreach (var prop in props)
  879. {
  880. if (prop.PropertyType == typeof(JobLink))
  881. return (string.IsNullOrEmpty(prefix) ? "" : prefix + ".") + prop.Name;
  882. var result = GetJobLink((string.IsNullOrEmpty(prefix) ? "" : prefix + ".") + prop.Name, prop.PropertyType);
  883. if (!string.IsNullOrEmpty(result))
  884. return result;
  885. }
  886. return "";
  887. }
  888. /// <summary>
  889. /// Find a link from the form type to an associated <see cref="Job"/>, allowing us to filter based on jobs.
  890. /// </summary>
  891. /// <returns>The property name of the <see cref="JobLink"/>.</returns>
  892. private string GetJobLink<T>() where T : IDigitalFormInstance
  893. => GetJobLink("", typeof(T));
  894. private IKeyedQueryDef GetFormQuery<T>()
  895. where T : Entity, IRemotable, IPersistent, IDigitalFormInstance, new()
  896. {
  897. var sort = LookupFactory.DefineSort<T>();
  898. var jobLink = GetJobLink<T>();
  899. var filter = new Filter<T>(x => x.FormCompleted).IsGreaterThanOrEqualTo(From)
  900. .And(x => x.FormCompleted).IsLessThan(To.AddDays(1))
  901. .And(x => x.Form.ID).IsEqualTo(Form!.ID)
  902. .And(x => x.FormCancelled).IsEqualTo(DateTime.MinValue);
  903. if (Properties.JobID != Guid.Empty && Properties.ShowJobFilter)
  904. {
  905. filter.And(jobLink + ".ID").IsEqualTo(Properties.JobID);
  906. }
  907. if (CustomFilter is not null)
  908. {
  909. filter.And(CustomFilter);
  910. }
  911. var columns = new Columns<T>(x => x.ID)
  912. .Add(x => x.Number)
  913. .Add(x => x.CreatedBy)
  914. .Add(x => x.Created)
  915. .Add(x => x.Form.ID)
  916. .Add(x => x.FormData)
  917. .Add(x => x.FormCompleted)
  918. .Add(x => x.FormCompletedBy.UserID)
  919. .Add(x => x.Location.Timestamp)
  920. .Add(x => x.Location.Latitude)
  921. .Add(x => x.Location.Longitude);
  922. if (IsEntityForm)
  923. columns.Add(x => x.FormProcessed); //"Processed");
  924. var parentcols = LookupFactory.DefineColumns(ParentType!);
  925. foreach (var col in parentcols.ColumnNames())
  926. columns.Add("Parent." + col);
  927. if (parentColumns.TryGetValue(ParentType!, out var pColumns))
  928. {
  929. foreach (var (field, name) in pColumns)
  930. {
  931. columns.Add(field);
  932. }
  933. }
  934. if (!string.IsNullOrWhiteSpace(jobLink))
  935. columns.Add(jobLink + ".ID");
  936. return new KeyedQueryDef<T>(filter, columns, sort);
  937. }
  938. #endregion
  939. private void LoadDataIntoGrid(List<DigitalFormVariable> variables, List<QAQuestion> questions, CoreTable formData, List<string> additionalColumns, CoreTable? jobITPs)
  940. {
  941. var data = new DataTable();
  942. data.Columns.Add("ID", typeof(Guid));
  943. data.Columns.Add("Form_ID", typeof(Guid));
  944. data.Columns.Add("Parent_ID", typeof(Guid));
  945. data.Columns.Add("Location_Timestamp", typeof(DateTime));
  946. data.Columns.Add("Location_Latitude", typeof(double));
  947. data.Columns.Add("Location_Longitude", typeof(double));
  948. data.Columns.Add("FormData", typeof(string));
  949. data.Columns.Add("Number", typeof(string));
  950. if (ParentType == typeof(JobITP))
  951. {
  952. data.Columns.Add("Job No", typeof(string));
  953. }
  954. if (parentColumns.TryGetValue(ParentType!, out var pColumns))
  955. {
  956. foreach (var (field, name) in pColumns)
  957. {
  958. data.Columns.Add(name, typeof(string));
  959. }
  960. }
  961. data.Columns.Add("Description", typeof(string));
  962. data.Columns.Add("Created", typeof(DateTime));
  963. data.Columns.Add("Created By", typeof(string));
  964. data.Columns.Add("Completed", typeof(DateTime));
  965. data.Columns.Add("Completed By", typeof(string));
  966. if (IsEntityForm)
  967. data.Columns.Add("Processed", typeof(bool));
  968. if (variables.Any())
  969. {
  970. foreach (var variable in variables)
  971. {
  972. var code = variable.Code.Replace("/", " ");
  973. QuestionCodes[code] = Thread.CurrentThread.CurrentCulture.TextInfo.ToTitleCase(code.ToLower());
  974. try
  975. {
  976. data.Columns.Add(code, typeof(string));
  977. }
  978. catch (DuplicateNameException)
  979. {
  980. MessageBox.Show($"Error: duplicate variable code {code}");
  981. }
  982. }
  983. }
  984. else if (questions.Any())
  985. {
  986. Questions = questions;
  987. Progress.SetMessage("Loading Checks");
  988. QAGrid.Clear();
  989. QAGrid.LoadChecks(Form!.Description, Questions, new Dictionary<Guid, object>());
  990. QAGrid.CollapseMargins();
  991. var i = 1;
  992. foreach (var question in Questions)
  993. {
  994. var id = question.ID.ToString();
  995. if (!question.Answer.Equals(QAAnswer.Comment))
  996. {
  997. data.Columns.Add(id, typeof(string));
  998. var code = question.Code;
  999. QuestionCodes[id] = string.IsNullOrEmpty(code) ? string.Format("{0}.", i) : code;
  1000. i++;
  1001. }
  1002. }
  1003. }
  1004. foreach (var row in formData.Rows)
  1005. {
  1006. var form = (row.ToObject(FormType!) as IDigitalFormInstance)!;
  1007. if (!string.IsNullOrWhiteSpace(form.FormData))
  1008. {
  1009. var dataRow = data.NewRow();
  1010. dataRow["ID"] = form.ID;
  1011. dataRow["Form_ID"] = form.Form.ID;
  1012. dataRow["Parent_ID"] = form.ParentID();
  1013. dataRow["Location_Timestamp"] = form.Location.Timestamp;
  1014. dataRow["Location_Latitude"] = form.Location.Latitude;
  1015. dataRow["Location_Longitude"] = form.Location.Longitude;
  1016. dataRow["FormData"] = form.FormData;
  1017. dataRow["Number"] = form.Number;
  1018. var desc = new List<string>();
  1019. foreach (var col in additionalColumns)
  1020. {
  1021. var val = row[col];
  1022. if (val != null && val is not Guid)
  1023. desc.Add(val.ToString() ?? "");
  1024. }
  1025. dataRow["Description"] = string.Join(" : ", desc);
  1026. dataRow["Created"] = (form as Entity)!.Created;
  1027. dataRow["Created By"] = (form as Entity)!.CreatedBy;
  1028. dataRow["Completed"] = form.FormCompleted;
  1029. dataRow["Completed By"] = form.FormCompletedBy.UserID;
  1030. if (IsEntityForm)
  1031. dataRow["Processed"] = form.FormProcessed > DateTime.MinValue;
  1032. if (ParentType == typeof(JobITP))
  1033. {
  1034. var jobITP = jobITPs!.Rows.FirstOrDefault(x => x.Get<JobITP, Guid>(x => x.ID) == form.ParentID());
  1035. if (jobITP is not null)
  1036. {
  1037. var jobID = jobITP.Get<JobITP, Guid>(x => x.Job.ID);
  1038. dataRow["Job No"] = Jobs.Where(x => x.ID == jobID).FirstOrDefault()?.JobNumber;
  1039. }
  1040. }
  1041. if (pColumns != null)
  1042. {
  1043. foreach (var (field, name) in pColumns)
  1044. {
  1045. dataRow[name] = row[field]?.ToString();
  1046. }
  1047. }
  1048. //datarow["Job No"] = (String)row[JobLink + ".JobNumber"];
  1049. var bHasData = false;
  1050. if (variables.Any())
  1051. {
  1052. var dict = Serialization.Deserialize<Dictionary<string, object?>>(form.FormData);
  1053. if (dict is not null)
  1054. {
  1055. var storage = new DFLoadStorage(dict, null);
  1056. foreach (var variable in variables)
  1057. {
  1058. var value = variable.Deserialize(storage.GetEntry(variable.Code));
  1059. var format = variable.FormatValue(value);
  1060. var sKey = variable.Code.Replace("/", " ");
  1061. if (data.Columns.Contains(sKey))
  1062. {
  1063. dataRow[sKey] = format;
  1064. bHasData = true;
  1065. }
  1066. }
  1067. }
  1068. }
  1069. else
  1070. {
  1071. var dict = Serialization.Deserialize<Dictionary<Guid, object>>(form.FormData);
  1072. if (dict is not null)
  1073. foreach (var key in dict.Keys)
  1074. if (data.Columns.Contains(key.ToString()))
  1075. {
  1076. dataRow[key.ToString()] = dict[key];
  1077. bHasData = true;
  1078. }
  1079. }
  1080. if (bHasData)
  1081. data.Rows.Add(dataRow);
  1082. }
  1083. }
  1084. DataGrid.ItemsSource = data;
  1085. IsQAForm = !variables.Any() && questions.Any();
  1086. QAGrid.Visibility = IsQAForm ? Visibility.Visible : Visibility.Collapsed;
  1087. DataGrid.Visibility = Visibility.Visible;
  1088. }
  1089. private void RefreshData<TForm>()
  1090. where TForm : Entity, IRemotable, IPersistent, IDigitalFormInstance, new()
  1091. {
  1092. var formQuery = GetFormQuery<TForm>();
  1093. var queries = new List<IKeyedQueryDef>()
  1094. {
  1095. new KeyedQueryDef<QAQuestion>(new Filter<QAQuestion>(x => x.Form.ID).IsEqualTo(Form!.ID)),
  1096. new KeyedQueryDef<DigitalFormVariable>(
  1097. new Filter<DigitalFormVariable>(x => x.Form.ID).IsEqualTo(Form.ID),
  1098. null,
  1099. new SortOrder<DigitalFormVariable>(x => x.Sequence)),
  1100. formQuery
  1101. };
  1102. if (ParentType == typeof(JobITP))
  1103. {
  1104. queries.Add(new KeyedQueryDef<JobITP>(
  1105. new Filter<JobITP>(x => x.ID).InQuery((formQuery.Filter as Filter<JobITPForm>)!, x => x.Parent.ID),
  1106. new Columns<JobITP>(x => x.ID, x => x.Job.JobNumber)));
  1107. }
  1108. var results = Client.QueryMultiple(queries);
  1109. var questions = results.Get<QAQuestion>().ToObjects<QAQuestion>().ToList();
  1110. var variables = results.Get<DigitalFormVariable>().ToObjects<DigitalFormVariable>().ToList();
  1111. var formData = results.Get(formQuery.Key);
  1112. LoadDataIntoGrid(
  1113. variables, questions,
  1114. formData,
  1115. formQuery.Columns!.ColumnNames().Where(x => x != "ID" && x.StartsWith("Parent.")).ToList(),
  1116. ParentType == typeof(JobITP) ? results.Get<JobITP>() : null);
  1117. }
  1118. public void Refresh()
  1119. {
  1120. if (!IsSetup) return;
  1121. Questions.Clear();
  1122. QAGrid.Clear();
  1123. QAGrid.LoadChecks("", Array.Empty<QAQuestion>(), new Dictionary<Guid, object>());
  1124. DataGrid.ItemsSource = null;
  1125. if (ParentType is null || FormType is null || Form is null || Form.ID == Guid.Empty)
  1126. {
  1127. QAGrid.Visibility = Visibility.Collapsed;
  1128. DataGrid.Visibility = Visibility.Collapsed;
  1129. return;
  1130. }
  1131. var refreshMethod = typeof(DigitalFormsDashboard).GetMethod(nameof(RefreshData), BindingFlags.Instance | BindingFlags.NonPublic)!.MakeGenericMethod(FormType);
  1132. refreshMethod.Invoke(this, Array.Empty<object?>());
  1133. }
  1134. public void Shutdown(CancelEventArgs? cancel)
  1135. {
  1136. }
  1137. #region DataGrid Configuration
  1138. private void DataGrid_AutoGeneratingColumn(object sender, Syncfusion.UI.Xaml.Grid.AutoGeneratingColumnArgs e)
  1139. {
  1140. e.Column.TextAlignment = TextAlignment.Center;
  1141. e.Column.HorizontalHeaderContentAlignment = HorizontalAlignment.Center;
  1142. e.Column.ColumnSizer = GridLengthUnitType.None;
  1143. e.Column.ImmediateUpdateColumnFilter = true;
  1144. e.Column.FilterRowCondition = FilterRowCondition.Contains;
  1145. e.Column.FilterRowOptionsVisibility = Visibility.Collapsed;
  1146. var value = (e.Column.ValueBinding as Binding)!;
  1147. if (value.Path.Path.Equals("ID") || value.Path.Path.Equals("Form_ID") || value.Path.Path.Equals("Parent_ID") ||
  1148. value.Path.Path.Equals("FormData") || value.Path.Path.Equals("Location_Latitude") || value.Path.Path.Equals("Location_Longitude"))
  1149. {
  1150. e.Cancel = true;
  1151. }
  1152. else if (value.Path.Path.Equals("Number"))
  1153. {
  1154. e.Column.Width = 80;
  1155. e.Column.HeaderStyle = Application.Current.Resources["TemplateHeaderStyle"] as Style;
  1156. }
  1157. else if (value.Path.Path.Equals("Location_Timestamp"))
  1158. {
  1159. e.Column = new GridImageColumn();
  1160. e.Column.Width = DataGrid.RowHeight;
  1161. e.Column.HeaderStyle = Application.Current.Resources["TemplateHeaderStyle"] as Style;
  1162. e.Column.HeaderText = "";
  1163. e.Column.Padding = new Thickness(4);
  1164. e.Column.ValueBinding = new Binding
  1165. {
  1166. Path = new PropertyPath(value.Path.Path),
  1167. Converter = new MileStoneImageConverter()
  1168. };
  1169. e.Column.MappingName = "Location.Timestamp";
  1170. }
  1171. else if (ParentType is not null && parentColumns.TryGetValue(ParentType, out var pColumns) && pColumns.Any(x => x.Item2.Equals(value.Path.Path)))
  1172. {
  1173. e.Column.ColumnSizer = GridLengthUnitType.Auto;
  1174. e.Column.HeaderStyle = Application.Current.Resources["TemplateHeaderStyle"] as Style;
  1175. }
  1176. else if (value.Path.Path.Equals("Job No"))
  1177. {
  1178. e.Column.Width = 60;
  1179. e.Column.HeaderStyle = Application.Current.Resources["TemplateHeaderStyle"] as Style;
  1180. }
  1181. else if (value.Path.Path.Equals("Description"))
  1182. {
  1183. e.Column.TextAlignment = TextAlignment.Left;
  1184. e.Column.HorizontalHeaderContentAlignment = HorizontalAlignment.Left;
  1185. e.Column.Width = 450;
  1186. e.Column.HeaderStyle = Application.Current.Resources["TemplateHeaderStyle"] as Style;
  1187. }
  1188. else if (value.Path.Path.Equals("Completed"))
  1189. {
  1190. e.Column.Width = 100;
  1191. e.Column.HeaderStyle = Application.Current.Resources["TemplateHeaderStyle"] as Style;
  1192. (e.Column as GridDateTimeColumn)!.Pattern = DateTimePattern.CustomPattern;
  1193. (e.Column as GridDateTimeColumn)!.CustomPattern = "dd MMM yy hh:mm";
  1194. }
  1195. else if (value.Path.Path.Equals("Completed By"))
  1196. {
  1197. e.Column.Width = 100;
  1198. e.Column.HeaderStyle = Application.Current.Resources["TemplateHeaderStyle"] as Style;
  1199. }
  1200. else if (value.Path.Path.Equals("Processed"))
  1201. {
  1202. e.Column.Width = 100;
  1203. e.Column.HeaderStyle = Application.Current.Resources["TemplateHeaderStyle"] as Style;
  1204. }
  1205. else if (value.Path.Path.Equals("Created By"))
  1206. {
  1207. e.Column.Width = 100;
  1208. e.Column.HeaderStyle = Application.Current.Resources["TemplateHeaderStyle"] as Style;
  1209. }
  1210. else if (value.Path.Path.Equals("Created"))
  1211. {
  1212. e.Column.Width = 100;
  1213. e.Column.HeaderStyle = Application.Current.Resources["TemplateHeaderStyle"] as Style;
  1214. (e.Column as GridDateTimeColumn).Pattern = DateTimePattern.CustomPattern;
  1215. (e.Column as GridDateTimeColumn).CustomPattern = "dd MMM yy hh:mm";
  1216. }
  1217. else
  1218. {
  1219. var data = DataGrid.ItemsSource as DataTable;
  1220. //int index = data.Columns.IndexOf(e.Column.MappingName) - 2;
  1221. //Style style = new Style(typeof(GridCell));
  1222. //e.Column.CellStyle = style;
  1223. e.Column.Width = 100;
  1224. e.Column.HeaderStyle = Application.Current.Resources["TemplateHeaderStyle"] as Style;
  1225. e.Column.HeaderText = QuestionCodes[e.Column.MappingName];
  1226. }
  1227. }
  1228. private Entity? GetEntityForm<T>(Guid id) where T : Entity, IDigitalFormInstance, IRemotable, IPersistent, new()
  1229. {
  1230. var columns = DynamicFormEditWindow.FormColumns<T>();
  1231. return new Client<T>().Query(
  1232. new Filter<T>(x => x.ID).IsEqualTo(id),
  1233. columns).Rows.FirstOrDefault()?.ToObject<T>();
  1234. }
  1235. private void DataGrid_CellDoubleTapped(object sender, Syncfusion.UI.Xaml.Grid.GridCellDoubleTappedEventArgs e)
  1236. {
  1237. if (e.RowColumnIndex.RowIndex < 2)
  1238. return;
  1239. var rowOffset = -2;
  1240. var table = (DataGrid.ItemsSource as DataTable)!;
  1241. var formid = (Guid)table.Rows[e.RowColumnIndex.RowIndex + rowOffset]["Form_ID"];
  1242. var formdata = (string)table.Rows[e.RowColumnIndex.RowIndex + rowOffset]["FormData"];
  1243. var id = (Guid)table.Rows[e.RowColumnIndex.RowIndex + rowOffset]["ID"];
  1244. if (FormType is null) return;
  1245. if (IsQAForm)
  1246. {
  1247. var values = new Dictionary<Guid, object>();
  1248. var formData = Serialization.Deserialize<Dictionary<string, object>>(formdata);
  1249. if (formData is not null)
  1250. {
  1251. foreach (var (idStr, value) in formData)
  1252. {
  1253. if (Guid.TryParse(idStr, out var codeID))
  1254. {
  1255. values[codeID] = value;
  1256. }
  1257. }
  1258. }
  1259. QAGrid.Clear();
  1260. QAGrid.LoadChecks(Form!.Description, Questions, values);
  1261. QAGrid.CollapseMargins();
  1262. return;
  1263. }
  1264. var entityForm = typeof(DigitalFormsDashboard)
  1265. .GetMethod(nameof(GetEntityForm), BindingFlags.NonPublic | BindingFlags.Instance)!
  1266. .MakeGenericMethod(FormType)
  1267. .Invoke(this, new object[] { id }) as IDigitalFormInstance;
  1268. if (entityForm is not null)
  1269. {
  1270. if (DynamicFormEditWindow.EditDigitalForm(entityForm, out var dataModel))
  1271. {
  1272. dataModel.Update(null);
  1273. /*typeof(QADashboard)
  1274. .GetMethod(nameof(SaveEntityForm), System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!
  1275. .MakeGenericMethod(formType)
  1276. .Invoke(this, new object[] { entityForm });*/
  1277. Refresh();
  1278. }
  1279. }
  1280. }
  1281. private void DataGrid_CellTapped(object sender, Syncfusion.UI.Xaml.Grid.GridCellTappedEventArgs e)
  1282. {
  1283. if (e.RowColumnIndex.ColumnIndex == 0)
  1284. {
  1285. var timestamp = (DateTime)(e.Record as DataRowView)!.Row["Location_Timestamp"];
  1286. var latitude = (double)(e.Record as DataRowView)!.Row["Location_Latitude"];
  1287. var longitude = (double)(e.Record as DataRowView)!.Row["Location_Longitude"];
  1288. var form = new MapForm(latitude, longitude, timestamp);
  1289. form.ShowDialog();
  1290. }
  1291. }
  1292. #endregion
  1293. }
  1294. }