DigitalFormsDashboard.xaml.cs 58 KB

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