DigitalFormsDashboard.xaml.cs 67 KB

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