DigitalFormsDashboard.xaml.cs 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993
  1. using Comal.Classes;
  2. using InABox.Clients;
  3. using InABox.Core;
  4. using InABox.DynamicGrid;
  5. using InABox.Scripting;
  6. using InABox.WPF;
  7. using PRSDesktop.Configuration;
  8. using PRSDesktop.Forms;
  9. using PRSDesktop.WidgetGroups;
  10. using Syncfusion.UI.Xaml.Grid;
  11. using Syncfusion.Windows.Shared;
  12. using System;
  13. using System.Collections;
  14. using System.Collections.Generic;
  15. using System.Data;
  16. using System.Diagnostics.CodeAnalysis;
  17. using System.Linq;
  18. using System.Linq.Expressions;
  19. using System.Reflection;
  20. using System.Text;
  21. using System.Threading;
  22. using System.Threading.Tasks;
  23. using System.Windows;
  24. using System.Windows.Controls;
  25. using System.Windows.Data;
  26. using System.Windows.Documents;
  27. using System.Windows.Input;
  28. using System.Windows.Media;
  29. using System.Windows.Media.Imaging;
  30. using System.Windows.Navigation;
  31. using System.Windows.Shapes;
  32. using SelectionChangedEventArgs = System.Windows.Controls.SelectionChangedEventArgs;
  33. namespace PRSDesktop
  34. {
  35. public enum DateFilterType
  36. {
  37. Today,
  38. Yesterday,
  39. Week,
  40. SevenDays,
  41. Month,
  42. ThirtyDays,
  43. Year,
  44. TwelveMonths,
  45. Custom
  46. }
  47. public class DigitalFormsDashboardProperties : IDashboardProperties
  48. {
  49. public bool ShowJobFilter { get; set; } = false;
  50. public bool ShowDateFilter { get; set; } = true;
  51. public Guid JobID { get; set; }
  52. public DateFilterType DateFilterType { get; set; } = DateFilterType.Today;
  53. public DateTime FromDate { get; set; }
  54. public DateTime ToDate { get; set; }
  55. }
  56. public class DigitalFormsDashboardElement : DashboardElement<DigitalFormsDashboard, Common, DigitalFormsDashboardProperties> { }
  57. /// <summary>
  58. /// Interaction logic for DigitalFormsDashboard.xaml
  59. /// </summary>
  60. public partial class DigitalFormsDashboard : UserControl,
  61. IDashboardWidget<Common, DigitalFormsDashboardProperties>,
  62. IRequiresSecurity<CanViewDigitalFormsDashbaord>,
  63. IHeaderDashboard, IActionsDashboard
  64. {
  65. public DigitalFormsDashboardProperties Properties { get; set; }
  66. private List<DigitalForm> DigitalForms;
  67. private List<Job> Jobs;
  68. private Dictionary<string, string> Categories;
  69. public DashboardHeader Header { get; set; } = new();
  70. public DigitalFormsDashboard()
  71. {
  72. InitializeComponent();
  73. }
  74. public void Setup()
  75. {
  76. var results = Client.QueryMultiple(
  77. new KeyedQueryDef<DigitalForm>(new Filter<DigitalForm>(x => x.Active).IsEqualTo(true)),
  78. new KeyedQueryDef<Job>(
  79. LookupFactory.DefineFilter<Job>(),
  80. new Columns<Job>(x => x.ID)
  81. .Add(x => x.JobNumber)
  82. .Add(x => x.Name)));
  83. DigitalForms = results.Get<DigitalForm>().ToList<DigitalForm>();
  84. var categories = new DigitalFormCategoryLookups(null);
  85. categories.OnAfterGenerateLookups += (sender, entries) => { entries.Insert(0, new LookupEntry("", "Select Category")); };
  86. Categories = categories.AsTable("AppliesTo")
  87. .ToDictionary("AppliesTo", "Display")
  88. .Cast<KeyValuePair<object, string>>()
  89. .ToDictionary(x => (x.Key as string)!, x => x.Value);
  90. Jobs = results.Get<Job>().ToList<Job>();
  91. Jobs.Insert(0, new Job { ID = Guid.Empty, JobNumber = "ALL", Name = "All Jobs" });
  92. SetupHeader();
  93. SetupFilters();
  94. }
  95. #region Header
  96. private ComboBox CategoryBox;
  97. private ComboBox FormBox;
  98. private ComboBox JobBox;
  99. private ComboBox DateTypeBox;
  100. private Label FromLabel;
  101. private DatePicker FromPicker;
  102. private Label ToLabel;
  103. private DatePicker ToPicker;
  104. private static Dictionary<DateFilterType, string> FilterTypes = new()
  105. {
  106. { DateFilterType.Today, "Today" },
  107. { DateFilterType.Yesterday, "Yesterday" },
  108. { DateFilterType.Week, "Week to Date" },
  109. { DateFilterType.SevenDays, "Last 7 Days" },
  110. { DateFilterType.Month, "Month to Date" },
  111. { DateFilterType.ThirtyDays, "Last 30 Days" },
  112. { DateFilterType.Year, "Year to Date" },
  113. { DateFilterType.TwelveMonths, "Last 12 Months" },
  114. { DateFilterType.Custom, "Custom" }
  115. };
  116. public void SetupHeader()
  117. {
  118. CategoryBox = new ComboBox {
  119. Width = 150,
  120. VerticalContentAlignment = VerticalAlignment.Center,
  121. Margin = new Thickness(0, 0, 5, 0)
  122. };
  123. CategoryBox.ItemsSource = Categories;
  124. CategoryBox.SelectedValuePath = "Key";
  125. CategoryBox.DisplayMemberPath = "Value";
  126. CategoryBox.SelectionChanged += Category_SelectionChanged;
  127. FormBox = new ComboBox
  128. {
  129. Width = 250,
  130. VerticalContentAlignment = VerticalAlignment.Center,
  131. Margin = new Thickness(0, 0, 5, 0),
  132. IsEnabled = false
  133. };
  134. FormBox.SelectionChanged += FormBox_SelectionChanged;
  135. FormBox.ItemsSource = new Dictionary<DigitalForm, string> { };
  136. JobBox = new ComboBox
  137. {
  138. Width = 250,
  139. Margin = new Thickness(0, 0, 5, 0),
  140. VerticalContentAlignment = VerticalAlignment.Center
  141. };
  142. JobBox.ItemsSource = Jobs.ToDictionary(x => x.ID, x => $"{x.JobNumber} : {x.Name}");
  143. JobBox.SelectedIndex = 0;
  144. JobBox.SelectedValuePath = "Key";
  145. JobBox.DisplayMemberPath = "Value";
  146. JobBox.SelectionChanged += JobBox_SelectionChanged;
  147. DateTypeBox = new ComboBox
  148. {
  149. Width = 120,
  150. VerticalContentAlignment = VerticalAlignment.Center
  151. };
  152. DateTypeBox.ItemsSource = FilterTypes;
  153. DateTypeBox.SelectedValuePath = "Key";
  154. DateTypeBox.DisplayMemberPath = "Value";
  155. DateTypeBox.SelectedValue = Properties.DateFilterType;
  156. DateTypeBox.SelectionChanged += DateTypeBox_SelectionChanged;
  157. FromLabel = new Label { Content = "From", VerticalContentAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 5, 0) };
  158. FromPicker = new DatePicker {
  159. Width = 100,
  160. Background = new SolidColorBrush(Colors.LightYellow),
  161. VerticalContentAlignment = VerticalAlignment.Center,
  162. FirstDayOfWeek = DayOfWeek.Monday,
  163. Margin = new Thickness(0, 0, 5, 0)
  164. };
  165. FromPicker.SelectedDateChanged += FromPicker_SelectedDateChanged;
  166. ToLabel = new Label { Content = "To", VerticalContentAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 5, 0) };
  167. ToPicker = new DatePicker
  168. {
  169. Width = 100,
  170. Background = new SolidColorBrush(Colors.LightYellow),
  171. VerticalContentAlignment = VerticalAlignment.Center,
  172. FirstDayOfWeek = DayOfWeek.Monday,
  173. Margin = new Thickness(0, 0, 5, 0)
  174. };
  175. ToPicker.SelectedDateChanged += ToPicker_SelectedDateChanged;
  176. Header.BeginUpdate()
  177. .Clear()
  178. .Add(CategoryBox)
  179. .Add(FormBox)
  180. .Add(JobBox)
  181. .Add(DateTypeBox)
  182. .Add(FromLabel)
  183. .Add(FromPicker)
  184. .Add(ToLabel)
  185. .Add(ToPicker);
  186. Header.EndUpdate();
  187. }
  188. private void Search_KeyUp(object sender, KeyEventArgs e)
  189. {
  190. Refresh();
  191. }
  192. private void JobBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
  193. {
  194. Properties.JobID = (Guid)JobBox.SelectedValue;
  195. Refresh();
  196. }
  197. private void SetDateFilterVisibility(bool visible)
  198. {
  199. var visibility = visible ? Visibility.Visible : Visibility.Collapsed;
  200. FromLabel.Visibility = visibility;
  201. FromPicker.Visibility = visibility;
  202. ToLabel.Visibility = visibility;
  203. ToPicker.Visibility = visibility;
  204. DateTypeBox.Visibility = visibility;
  205. }
  206. private void SetJobFilterVisibility(bool visible)
  207. {
  208. var visibility = visible ? Visibility.Visible : Visibility.Collapsed;
  209. JobBox.Visibility = visibility;
  210. }
  211. private void DateTypeBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
  212. {
  213. var filterType = (DateFilterType)DateTypeBox.SelectedValue;
  214. Properties.DateFilterType = filterType;
  215. if(filterType == DateFilterType.Custom)
  216. {
  217. if (FromPicker.SelectedDate == null || FromPicker.SelectedDate == DateTime.MinValue)
  218. {
  219. Properties.FromDate = DateTime.Today;
  220. }
  221. else
  222. {
  223. Properties.FromDate = FromPicker.SelectedDate.Value;
  224. }
  225. if (ToPicker.SelectedDate == null || ToPicker.SelectedDate == DateTime.MinValue)
  226. {
  227. Properties.ToDate = DateTime.Today;
  228. }
  229. else
  230. {
  231. Properties.ToDate = ToPicker.SelectedDate.Value;
  232. }
  233. }
  234. SetupDateFilters();
  235. Refresh();
  236. }
  237. private void FromPicker_SelectedDateChanged(object? sender, SelectionChangedEventArgs e)
  238. {
  239. Properties.FromDate = FromPicker.SelectedDate ?? DateTime.Today;
  240. }
  241. private void ToPicker_SelectedDateChanged(object? sender, SelectionChangedEventArgs e)
  242. {
  243. Properties.ToDate = ToPicker.SelectedDate ?? DateTime.Today;
  244. }
  245. private void Category_SelectionChanged(object sender, SelectionChangedEventArgs e)
  246. {
  247. SetCategory((CategoryBox.SelectedValue as string)!);
  248. var jobLink = FormType is not null ? GetJobLink("", FormType) : "";
  249. if (string.IsNullOrWhiteSpace(jobLink))
  250. {
  251. var jobID = Properties.JobID;
  252. JobBox.SelectedValue = Guid.Empty;
  253. JobBox.IsEnabled = false;
  254. Properties.JobID = jobID;
  255. }
  256. else
  257. {
  258. JobBox.SelectedValue = Properties.JobID;
  259. JobBox.IsEnabled = true;
  260. }
  261. if (ParentType is null)
  262. {
  263. FormBox.IsEnabled = false;
  264. FormBox.ItemsSource = new Dictionary<DigitalForm, string> { };
  265. }
  266. else
  267. {
  268. var forms = DigitalForms.Where(x => x.AppliesTo == ParentType.Name).ToList();
  269. forms.Insert(0, new DigitalForm { ID = Guid.Empty, Description = "Select Form" });
  270. FormBox.ItemsSource = forms;
  271. FormBox.DisplayMemberPath = "Description";
  272. FormBox.SelectedIndex = 0;
  273. FormBox.IsEnabled = true;
  274. }
  275. Refresh();
  276. }
  277. private void FormBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
  278. {
  279. Form = (FormBox.SelectedValue as DigitalForm)!;
  280. Refresh();
  281. }
  282. #endregion
  283. private string SectionName
  284. {
  285. get
  286. {
  287. if (Form is null || Form.ID == Guid.Empty)
  288. return "Digital Forms";
  289. return Form.ID.ToString() ?? "Digital Forms";
  290. }
  291. }
  292. private DataModel DataModel(Selection selection)
  293. {
  294. if(FormType is null || Form is null || Form.ID == Guid.Empty)
  295. {
  296. return new AutoDataModel<DigitalForm>(new Filter<DigitalForm>().None());
  297. }
  298. IFilter filter;
  299. switch (selection)
  300. {
  301. case Selection.Selected:
  302. var formids = DataGrid.SelectedItems.Select(x => (x as DataRowView)!.Row["ID"]).ToArray();
  303. filter = Filter.Create<Entity>(FormType, x => x.ID).InList(formids);
  304. break;
  305. case Selection.All:
  306. filter = Filter.Create(FormType).All();
  307. break;
  308. case Selection.None:
  309. default:
  310. filter = Filter.Create(FormType).None();
  311. break;
  312. }
  313. return (Activator.CreateInstance(typeof(DigitalFormReportDataModel<>)!
  314. .MakeGenericType(FormType), new object?[] { filter, Form.ID }) as DataModel)!;
  315. }
  316. public void BuildActionsMenu(ContextMenu menu)
  317. {
  318. menu.AddCheckItem<object?>("Show Date Filter", null, ToggleDateFilter, Properties.ShowDateFilter);
  319. menu.AddCheckItem<object?>("Show Job Filter", null, ToggleJobFilter, Properties.ShowJobFilter);
  320. menu.AddSeparator();
  321. foreach(var (module, image) in CustomModuleUtils.LoadCustomModuleThumbnails(SectionName, DataModel(Selection.None)))
  322. {
  323. menu.AddItem(module.Name, image, module, ExecuteModule_Click);
  324. }
  325. if (Security.IsAllowed<CanCustomiseModules>())
  326. {
  327. menu.AddSeparatorIfNeeded();
  328. menu.AddItem("Manage Modules", PRSDesktop.Resources.script, ManageModules_Click);
  329. }
  330. }
  331. private void ExecuteModule_Click(CustomModule obj)
  332. {
  333. if (!string.IsNullOrWhiteSpace(obj.Script))
  334. try
  335. {
  336. Selection selection;
  337. if (obj.SelectedRecords && obj.AllRecords)
  338. selection = RecordSelectionDialog.Execute();
  339. else if (obj.SelectedRecords)
  340. selection = Selection.Selected;
  341. else if (obj.AllRecords)
  342. selection = Selection.All;
  343. else
  344. selection = Selection.None;
  345. var result = ScriptDocument.RunCustomModule(DataModel(selection), new Dictionary<string, object[]>(), obj.Script);
  346. if (result)
  347. Refresh();
  348. }
  349. catch (CompileException c)
  350. {
  351. MessageBox.Show(c.Message);
  352. }
  353. catch (Exception err)
  354. {
  355. MessageBox.Show(CoreUtils.FormatException(err));
  356. }
  357. else
  358. MessageBox.Show("Unable to load " + obj.Name);
  359. }
  360. private void ManageModules_Click()
  361. {
  362. var section = SectionName;
  363. var dataModel = DataModel(Selection.Selected);
  364. var manager = new CustomModuleManager()
  365. {
  366. Section = section,
  367. DataModel = dataModel
  368. };
  369. manager.ShowDialog();
  370. }
  371. private void ToggleDateFilter(object? tag, bool isChecked)
  372. {
  373. Properties.ShowDateFilter = isChecked;
  374. SetDateFilterVisibility(Properties.ShowDateFilter);
  375. }
  376. private void ToggleJobFilter(object? tag, bool isChecked)
  377. {
  378. Properties.ShowJobFilter = isChecked;
  379. SetJobFilterVisibility(Properties.ShowJobFilter);
  380. Refresh();
  381. }
  382. #region Filtering
  383. private DateTime From { get; set; }
  384. private DateTime To { get; set; }
  385. private bool IsEntityForm { get; set; }
  386. private Type? ParentType { get; set; }
  387. private Type? FormType { get; set; }
  388. private DigitalForm? Form { get; set; }
  389. private readonly Dictionary<string, string> QuestionCodes = new();
  390. private static int WeekDay(DateTime date)
  391. {
  392. if (date.DayOfWeek == DayOfWeek.Sunday)
  393. return 7;
  394. return (int)date.DayOfWeek - 1;
  395. }
  396. private void SetupDateFilters()
  397. {
  398. switch (Properties.DateFilterType)
  399. {
  400. case DateFilterType.Today:
  401. From = DateTime.Today;
  402. To = DateTime.Today;
  403. break;
  404. case DateFilterType.Yesterday:
  405. From = DateTime.Today.AddDays(-1);
  406. To = DateTime.Today.AddDays(-1);
  407. break;
  408. case DateFilterType.Week:
  409. From = DateTime.Today.AddDays(-WeekDay(DateTime.Today));
  410. To = DateTime.Today;
  411. break;
  412. case DateFilterType.SevenDays:
  413. From = DateTime.Today.AddDays(-6);
  414. To = DateTime.Today;
  415. break;
  416. case DateFilterType.Month:
  417. From = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1);
  418. To = DateTime.Today;
  419. break;
  420. case DateFilterType.ThirtyDays:
  421. From = DateTime.Today.AddDays(-29);
  422. To = DateTime.Today;
  423. break;
  424. case DateFilterType.Year:
  425. From = new DateTime(DateTime.Today.Year, 1, 1);
  426. To = DateTime.Today;
  427. break;
  428. case DateFilterType.TwelveMonths:
  429. From = DateTime.Today.AddYears(-1).AddDays(1);
  430. To = DateTime.Today;
  431. break;
  432. case DateFilterType.Custom:
  433. From = Properties.FromDate;
  434. To = Properties.ToDate;
  435. break;
  436. }
  437. DateTypeBox.SelectedValue = Properties.DateFilterType;
  438. FromPicker.SelectedDate = From;
  439. ToPicker.SelectedDate = To;
  440. var enabledPicker = Properties.DateFilterType == DateFilterType.Custom;
  441. FromPicker.IsEnabled = enabledPicker;
  442. ToPicker.IsEnabled = enabledPicker;
  443. }
  444. private void SetupJobFilter()
  445. {
  446. JobBox.SelectedValue = Properties.JobID;
  447. }
  448. private void SetupFilters()
  449. {
  450. SetupDateFilters();
  451. SetupJobFilter();
  452. SetDateFilterVisibility(Properties.ShowDateFilter);
  453. SetJobFilterVisibility(Properties.ShowJobFilter);
  454. }
  455. #region Categories
  456. private static Dictionary<string, Tuple<Type, Type>>? FormInstanceTypes;
  457. private static readonly Dictionary<Type, List<Tuple<string, string>>> parentColumns = new()
  458. {
  459. { typeof(Kanban), new() { new("Parent.Number", "Task No") } },
  460. { typeof(Job), new() { new("Parent.JobNumber", "Job No") } },
  461. { typeof(JobITP), new() { new("Parent.Code", "Code") } },
  462. { typeof(Assignment), new() { new("Parent.Number", "Ass. No") } },
  463. { typeof(TimeSheet), new() { } },
  464. { typeof(LeaveRequest), new() { } },
  465. { typeof(Employee), new() { new("Parent.Code", "Employee") } },
  466. { typeof(PurchaseOrderItem), new() { new("Parent.PONumber", "PO No") } },
  467. };
  468. private static bool CategoryToType(string category, [NotNullWhen(true)] out Type? formType, [NotNullWhen(true)] out Type? parentType)
  469. {
  470. FormInstanceTypes ??= CoreUtils.TypeList(
  471. AppDomain.CurrentDomain.GetAssemblies(),
  472. x => !x.IsAbstract && x.GetInterfaces().Contains(typeof(IDigitalFormInstance))
  473. ).Select(x =>
  474. {
  475. var inter = x.GetInterfaces()
  476. .Where(x => x.IsGenericType && x.GetGenericTypeDefinition().Equals(typeof(IDigitalFormInstance<>))).FirstOrDefault();
  477. if (inter is not null)
  478. {
  479. var link = inter.GenericTypeArguments[0];
  480. var entityLinkDef = link.GetSuperclassDefinition(typeof(EntityLink<>));
  481. if (entityLinkDef is not null)
  482. {
  483. var entityType = entityLinkDef.GenericTypeArguments[0];
  484. return new Tuple<string, Type, Type>(entityType.Name, x, entityType);
  485. }
  486. }
  487. return null;
  488. }).Where(x => x is not null).ToDictionary(x => x!.Item1, x => new Tuple<Type, Type>(x!.Item2, x!.Item3));
  489. if (!FormInstanceTypes.TryGetValue(category, out var result))
  490. {
  491. formType = null;
  492. parentType = null;
  493. return false;
  494. }
  495. formType = result.Item1;
  496. parentType = result.Item2;
  497. return true;
  498. }
  499. private void SetCategory(string category)
  500. {
  501. if (!CategoryToType(category, out var formType, out var parentType))
  502. {
  503. IsEntityForm = false;
  504. ParentType = null;
  505. FormType = null;
  506. return;
  507. }
  508. IsEntityForm = formType.IsSubclassOfRawGeneric(typeof(EntityForm<,>));
  509. ParentType = parentType;
  510. FormType = formType;
  511. }
  512. #endregion
  513. private string GetJobLink(string prefix, Type type)
  514. {
  515. var props = type.GetProperties().Where(x =>
  516. x.PropertyType.BaseType != null && x.PropertyType.BaseType.IsGenericType &&
  517. x.PropertyType.BaseType.GetGenericTypeDefinition() == typeof(EntityLink<>));
  518. foreach (var prop in props)
  519. {
  520. if (prop.PropertyType == typeof(JobLink))
  521. return (string.IsNullOrEmpty(prefix) ? "" : prefix + ".") + prop.Name;
  522. var result = GetJobLink((string.IsNullOrEmpty(prefix) ? "" : prefix + ".") + prop.Name, prop.PropertyType);
  523. if (!string.IsNullOrEmpty(result))
  524. return result;
  525. }
  526. return "";
  527. }
  528. /// <summary>
  529. /// Find a link from the form type to an associated <see cref="Job"/>, allowing us to filter based on jobs.
  530. /// </summary>
  531. /// <returns>The property name of the <see cref="JobLink"/>.</returns>
  532. private string GetJobLink<T>() where T : IDigitalFormInstance
  533. => GetJobLink("", typeof(T));
  534. private IKeyedQueryDef GetFormQuery<T>()
  535. where T : Entity, IRemotable, IPersistent, IDigitalFormInstance, new()
  536. {
  537. var sort = LookupFactory.DefineSort<T>();
  538. var jobLink = GetJobLink<T>();
  539. var filter = new Filter<T>(x => x.FormCompleted).IsGreaterThanOrEqualTo(From)
  540. .And(x => x.FormCompleted).IsLessThan(To.AddDays(1))
  541. .And(x => x.Form.ID).IsEqualTo(Form!.ID);
  542. if (Properties.JobID != Guid.Empty && Properties.ShowJobFilter)
  543. {
  544. filter.And(jobLink + ".ID").IsEqualTo(Properties.JobID);
  545. }
  546. var columns = new Columns<T>(x => x.ID)
  547. .Add(x => x.Form.ID)
  548. .Add(x => x.FormData)
  549. .Add(x => x.FormCompleted)
  550. .Add(x => x.FormCompletedBy.UserID)
  551. .Add(x => x.Location.Timestamp)
  552. .Add(x => x.Location.Latitude)
  553. .Add(x => x.Location.Longitude);
  554. var parentcols = LookupFactory.DefineColumns(ParentType!);
  555. foreach (var col in parentcols.ColumnNames())
  556. columns.Add("Parent." + col);
  557. if (parentColumns.TryGetValue(ParentType!, out var pColumns))
  558. {
  559. foreach (var (field, name) in pColumns)
  560. {
  561. columns.Add(field);
  562. }
  563. }
  564. if (IsEntityForm)
  565. columns.Add("Processed");
  566. if (!string.IsNullOrWhiteSpace(jobLink))
  567. columns.Add(jobLink + ".JobNumber");
  568. return new KeyedQueryDef<T>(filter, columns, sort);
  569. }
  570. #endregion
  571. private void RefreshData<TForm>()
  572. where TForm : Entity, IRemotable, IPersistent, IDigitalFormInstance, new()
  573. {
  574. var formQuery = GetFormQuery<TForm>();
  575. var queries = new List<IKeyedQueryDef>()
  576. {
  577. new KeyedQueryDef<QAQuestion>(new Filter<QAQuestion>(x => x.Form.ID).IsEqualTo(Form!.ID)),
  578. new KeyedQueryDef<DigitalFormVariable>(
  579. new Filter<DigitalFormVariable>(x => x.Form.ID).IsEqualTo(Form.ID),
  580. null,
  581. new SortOrder<DigitalFormVariable>(x => x.Sequence)),
  582. formQuery
  583. };
  584. if (ParentType == typeof(JobITPForm))
  585. {
  586. queries.Add(new KeyedQueryDef<JobITP>(
  587. new Filter<JobITP>(x => x.ID).InQuery((formQuery.Filter as Filter<JobITPForm>)!, x => x.Parent.ID),
  588. new Columns<JobITP>(x => x.ID, x => x.Job.JobNumber)));
  589. }
  590. var results = Client.QueryMultiple(queries);
  591. var questions = results.Get<QAQuestion>();
  592. var variables = results.Get<DigitalFormVariable>().ToList<DigitalFormVariable>();
  593. var formData = results.Get(formQuery.Key).Rows;
  594. var data = new DataTable();
  595. data.Columns.Add("ID", typeof(Guid));
  596. data.Columns.Add("Form_ID", typeof(Guid));
  597. data.Columns.Add("Parent_ID", typeof(Guid));
  598. data.Columns.Add("Location_Timestamp", typeof(DateTime));
  599. data.Columns.Add("Location_Latitude", typeof(double));
  600. data.Columns.Add("Location_Longitude", typeof(double));
  601. data.Columns.Add("FormData", typeof(string));
  602. if (ParentType == typeof(JobITP))
  603. {
  604. data.Columns.Add("Job No", typeof(string));
  605. }
  606. if (parentColumns.TryGetValue(ParentType!, out var pColumns))
  607. {
  608. foreach (var (field, name) in pColumns)
  609. {
  610. data.Columns.Add(name, typeof(string));
  611. }
  612. }
  613. data.Columns.Add("Description", typeof(string));
  614. data.Columns.Add("Completed", typeof(DateTime));
  615. data.Columns.Add("Completed By", typeof(string));
  616. if (IsEntityForm)
  617. data.Columns.Add("Processed", typeof(bool));
  618. if (variables.Any())
  619. {
  620. foreach (var variable in variables)
  621. {
  622. var code = variable.Code.Replace("/", " ");
  623. QuestionCodes[code] = Thread.CurrentThread.CurrentCulture.TextInfo.ToTitleCase(code.ToLower());
  624. data.Columns.Add(code, typeof(string));
  625. }
  626. }
  627. else if (questions.Rows.Any())
  628. {
  629. Progress.SetMessage("Loading Checks");
  630. QAGrid.Clear();
  631. QAGrid.LoadChecks(Form.Description, questions.Rows.Select(x => x.ToObject<QAQuestion>()), new Dictionary<Guid, object>());
  632. QAGrid.CollapseMargins();
  633. var i = 1;
  634. foreach (var row in questions.Rows)
  635. {
  636. var id = row.Get<QAQuestion, Guid>(x => x.ID).ToString();
  637. if (!row.Get<QAQuestion, QAAnswer>(x => x.Answer).Equals(QAAnswer.Comment))
  638. {
  639. data.Columns.Add(id, typeof(string));
  640. var code = row.Get<QAQuestion, string>(x => x.Code);
  641. QuestionCodes[id] = string.IsNullOrEmpty(code) ? string.Format("{0}.", i) : code;
  642. i++;
  643. }
  644. }
  645. }
  646. foreach (var row in formData)
  647. {
  648. var form = (row.ToObject(FormType!) as IDigitalFormInstance)!;
  649. if (!string.IsNullOrWhiteSpace(form.FormData))
  650. {
  651. var dataRow = data.NewRow();
  652. dataRow["ID"] = form.ID;
  653. dataRow["Form_ID"] = form.Form.ID;
  654. dataRow["Parent_ID"] = form.ParentID();
  655. dataRow["Location_Timestamp"] = form.Location.Timestamp;
  656. dataRow["Location_Latitude"] = form.Location.Latitude;
  657. dataRow["Location_Longitude"] = form.Location.Longitude;
  658. dataRow["FormData"] = form.FormData;
  659. var desc = new List<string>();
  660. foreach (var col in formQuery.Columns!.ColumnNames().Where(x => x != "ID"))
  661. {
  662. if (col.StartsWith("Parent."))
  663. {
  664. var val = row[col];
  665. if (val != null && val is not Guid)
  666. desc.Add(val.ToString() ?? "");
  667. }
  668. }
  669. dataRow["Description"] = string.Join(" : ", desc);
  670. dataRow["Completed"] = form.FormCompleted;
  671. dataRow["Completed By"] = form.FormCompletedBy.UserID;
  672. if (IsEntityForm)
  673. dataRow["Processed"] = (bool?)row["Processed"] ?? false;
  674. if (ParentType == typeof(JobITP))
  675. {
  676. var jobITP = results.Get<JobITP>().Rows.FirstOrDefault(x => x.Get<JobITP, Guid>(x => x.ID) == form.ParentID());
  677. dataRow["Job No"] = jobITP?.Get<JobITP, string>(x => x.Job.JobNumber);
  678. }
  679. if (pColumns != null)
  680. {
  681. foreach (var (field, name) in pColumns)
  682. {
  683. dataRow[name] = row[field]?.ToString();
  684. }
  685. }
  686. //datarow["Job No"] = (String)row[JobLink + ".JobNumber"];
  687. var bHasData = false;
  688. if (variables.Any())
  689. {
  690. var dict = Serialization.Deserialize<Dictionary<string, object>>(form.FormData);
  691. foreach (var key in dict.Keys)
  692. {
  693. var variable = variables.FirstOrDefault(x => string.Equals(key, x.Code));
  694. if (variable != null)
  695. {
  696. var value = variable.ParseValue(dict[key]);
  697. object format = variable.FormatValue(value);
  698. var sKey = key.Replace("/", " ");
  699. if (data.Columns.Contains(sKey))
  700. {
  701. dataRow[sKey] = format;
  702. bHasData = true;
  703. }
  704. }
  705. }
  706. }
  707. else
  708. {
  709. var dict = Serialization.Deserialize<Dictionary<Guid, object>>(form.FormData);
  710. foreach (var key in dict.Keys)
  711. if (data.Columns.Contains(key.ToString()))
  712. {
  713. dataRow[key.ToString()] = dict[key];
  714. bHasData = true;
  715. }
  716. }
  717. if (bHasData)
  718. data.Rows.Add(dataRow);
  719. }
  720. }
  721. DataGrid.ItemsSource = data;
  722. QAGrid.Visibility = !variables.Any() && questions.Rows.Any() ? Visibility.Visible : Visibility.Collapsed;
  723. DataGrid.Visibility = Visibility.Visible;
  724. }
  725. public void Refresh()
  726. {
  727. Progress.Show("Refreshing");
  728. try
  729. {
  730. QAGrid.Clear();
  731. QAGrid.LoadChecks("", Array.Empty<QAQuestion>(), new Dictionary<Guid, object>());
  732. DataGrid.ItemsSource = null;
  733. if (ParentType is null || FormType is null || Form is null || Form.ID == Guid.Empty)
  734. {
  735. QAGrid.Visibility = Visibility.Collapsed;
  736. DataGrid.Visibility = Visibility.Collapsed;
  737. return;
  738. }
  739. Progress.SetMessage("Loading Data");
  740. var refreshMethod = typeof(DigitalFormsDashboard).GetMethod(nameof(RefreshData), BindingFlags.Instance | BindingFlags.NonPublic)!.MakeGenericMethod(FormType);
  741. refreshMethod.Invoke(this, Array.Empty<object?>());
  742. }
  743. finally
  744. {
  745. Progress.Close();
  746. }
  747. }
  748. public void Shutdown()
  749. {
  750. }
  751. #region DataGrid Configuration
  752. private void DataGrid_AutoGeneratingColumn(object sender, Syncfusion.UI.Xaml.Grid.AutoGeneratingColumnArgs e)
  753. {
  754. e.Column.TextAlignment = TextAlignment.Center;
  755. e.Column.HorizontalHeaderContentAlignment = HorizontalAlignment.Center;
  756. e.Column.ColumnSizer = GridLengthUnitType.None;
  757. var value = (e.Column.ValueBinding as Binding)!;
  758. if (value.Path.Path.Equals("ID") || value.Path.Path.Equals("Form_ID") || value.Path.Path.Equals("Parent_ID") ||
  759. value.Path.Path.Equals("FormData") || value.Path.Path.Equals("Location_Latitude") || value.Path.Path.Equals("Location_Longitude"))
  760. {
  761. e.Cancel = true;
  762. }
  763. else if (value.Path.Path.Equals("Location_Timestamp"))
  764. {
  765. e.Column = new GridImageColumn();
  766. e.Column.Width = DataGrid.RowHeight;
  767. e.Column.HeaderStyle = Resources["TemplateHeaderStyle"] as Style;
  768. e.Column.HeaderText = "";
  769. e.Column.Padding = new Thickness(4);
  770. e.Column.ValueBinding = new Binding
  771. {
  772. Path = new PropertyPath(value.Path.Path),
  773. Converter = new MileStoneImageConverter()
  774. };
  775. e.Column.MappingName = "Location.Timestamp";
  776. }
  777. else if (ParentType is not null && parentColumns.TryGetValue(ParentType, out var pColumns) && pColumns.Any(x => x.Item2.Equals(value.Path.Path)))
  778. {
  779. e.Column.ColumnSizer = GridLengthUnitType.Auto;
  780. e.Column.HeaderStyle = Resources["TemplateHeaderStyle"] as Style;
  781. }
  782. else if (value.Path.Path.Equals("Job No"))
  783. {
  784. e.Column.Width = 60;
  785. e.Column.HeaderStyle = Resources["TemplateHeaderStyle"] as Style;
  786. }
  787. else if (value.Path.Path.Equals("Description"))
  788. {
  789. e.Column.TextAlignment = TextAlignment.Left;
  790. e.Column.HorizontalHeaderContentAlignment = HorizontalAlignment.Left;
  791. e.Column.Width = 450;
  792. e.Column.HeaderStyle = Resources["TemplateHeaderStyle"] as Style;
  793. }
  794. else if (value.Path.Path.Equals("Completed"))
  795. {
  796. e.Column.Width = 100;
  797. e.Column.HeaderStyle = Resources["TemplateHeaderStyle"] as Style;
  798. (e.Column as GridDateTimeColumn)!.Pattern = DateTimePattern.CustomPattern;
  799. (e.Column as GridDateTimeColumn)!.CustomPattern = "dd MMM yy hh:mm";
  800. }
  801. else if (value.Path.Path.Equals("Completed By"))
  802. {
  803. e.Column.Width = 100;
  804. e.Column.HeaderStyle = Resources["TemplateHeaderStyle"] as Style;
  805. }
  806. else if (value.Path.Path.Equals("Processed"))
  807. {
  808. e.Column.Width = 100;
  809. e.Column.HeaderStyle = Resources["TemplateHeaderStyle"] as Style;
  810. }
  811. else
  812. {
  813. var data = DataGrid.ItemsSource as DataTable;
  814. //int index = data.Columns.IndexOf(e.Column.MappingName) - 2;
  815. //Style style = new Style(typeof(GridCell));
  816. //e.Column.CellStyle = style;
  817. e.Column.Width = 100;
  818. e.Column.HeaderStyle = Resources["TemplateHeaderStyle"] as Style;
  819. e.Column.HeaderText = QuestionCodes[e.Column.MappingName];
  820. }
  821. }
  822. private Entity? GetEntityForm<T>(Guid id) where T : Entity, IDigitalFormInstance, IRemotable, IPersistent, new()
  823. {
  824. var columns = new Columns<T>(x => x.ID)
  825. .Add(x => x.FormCompleted)
  826. .Add(x => x.FormData)
  827. .Add(x => x.Form.ID)
  828. .Add(x => x.Form.Description);
  829. if (typeof(T).HasInterface(typeof(IDigitalFormInstance<>)))
  830. {
  831. columns.Add("Parent.ID");
  832. }
  833. return new Client<T>().Query(
  834. new Filter<T>(x => x.ID).IsEqualTo(id),
  835. columns).Rows.FirstOrDefault()?.ToObject<T>();
  836. }
  837. private void DataGrid_CellDoubleTapped(object sender, Syncfusion.UI.Xaml.Grid.GridCellDoubleTappedEventArgs e)
  838. {
  839. if (e.RowColumnIndex.RowIndex == 0)
  840. return;
  841. var table = (DataGrid.ItemsSource as DataTable)!;
  842. var formid = (Guid)table.Rows[e.RowColumnIndex.RowIndex - 1]["Form_ID"];
  843. var formdata = (string)table.Rows[e.RowColumnIndex.RowIndex - 1]["FormData"];
  844. var id = (Guid)table.Rows[e.RowColumnIndex.RowIndex - 1]["ID"];
  845. if (FormType is null) return;
  846. var entityForm = typeof(DigitalFormsDashboard)
  847. .GetMethod(nameof(GetEntityForm), BindingFlags.NonPublic | BindingFlags.Instance)!
  848. .MakeGenericMethod(FormType)
  849. .Invoke(this, new object[] { id }) as IDigitalFormInstance;
  850. if (entityForm is not null)
  851. {
  852. if (DynamicFormEditWindow.EditDigitalForm(entityForm, out var dataModel))
  853. {
  854. dataModel.Update(null);
  855. /*typeof(QADashboard)
  856. .GetMethod(nameof(SaveEntityForm), System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!
  857. .MakeGenericMethod(formType)
  858. .Invoke(this, new object[] { entityForm });*/
  859. Refresh();
  860. }
  861. }
  862. }
  863. private void DataGrid_CellTapped(object sender, Syncfusion.UI.Xaml.Grid.GridCellTappedEventArgs e)
  864. {
  865. if (e.RowColumnIndex.ColumnIndex == 0)
  866. {
  867. var timestamp = (DateTime)(e.Record as DataRowView)!.Row["Location_Timestamp"];
  868. var latitude = (double)(e.Record as DataRowView)!.Row["Location_Latitude"];
  869. var longitude = (double)(e.Record as DataRowView)!.Row["Location_Longitude"];
  870. var form = new MapForm(latitude, longitude, timestamp);
  871. form.ShowDialog();
  872. }
  873. }
  874. #endregion
  875. }
  876. }