JobDocumentStatusChart.xaml.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. using Comal.Classes;
  2. using InABox.Clients;
  3. using InABox.Core;
  4. using InABox.WPF;
  5. using Syncfusion.Data.Extensions;
  6. using Syncfusion.UI.Xaml.Charts;
  7. using Syncfusion.Windows.Tools.Controls;
  8. using System;
  9. using System.Collections.Generic;
  10. using System.Linq;
  11. using System.Windows;
  12. using System.Windows.Controls;
  13. using System.Windows.Media;
  14. using InABox.Configuration;
  15. using System.ComponentModel;
  16. namespace PRSDesktop
  17. {
  18. public class JobDocumentItemViewModel
  19. {
  20. public string Group { get; set; }
  21. public string MileStoneCode { get; set; }
  22. public JobDocumentStatusChart.StatusType Status { get; set; }
  23. public int Count { get; set; }
  24. public SolidColorBrush Colour { get; set; }
  25. }
  26. public class JobDocumentStatusChartProperties : IUserConfigurationSettings, IDashboardProperties
  27. {
  28. public JobDocumentStatusChart.ItemType ItemType { get; set; } = JobDocumentStatusChart.ItemType.Sets;
  29. public JobDocumentStatusChart.StatusType[]? StatusTypes { get; set; } = null;
  30. public Guid[] MileStones { get; set; } = Array.Empty<Guid>();
  31. public Guid JobID { get; set; }
  32. }
  33. public class JobDocumentStatusChartElement : DashboardElement<JobDocumentStatusChart, WidgetGroups.Projects, JobDocumentStatusChartProperties> { }
  34. /// <summary>
  35. /// Interaction logic for JobDocumentStatusChart.xaml
  36. /// </summary>
  37. public partial class JobDocumentStatusChart : UserControl,
  38. IDashboardWidget<WidgetGroups.Projects, JobDocumentStatusChartProperties>,
  39. IRequiresCanView<JobDocumentSetMileStone>,
  40. IHeaderDashboard
  41. {
  42. private Dictionary<Guid, Color> MileStoneColours { get; set; }
  43. private Dictionary<Guid, JobDocumentSetMileStoneType> MilestoneTypes { get; set; }
  44. private List<KeyValuePair<Guid, string>> JobLookups { get; set; }
  45. public JobDocumentStatusChartProperties Properties { get; set; }
  46. public event LoadSettings<JobDocumentStatusChartProperties>? LoadSettings;
  47. public event SaveSettings<JobDocumentStatusChartProperties>? SaveSettings;
  48. public DashboardHeader Header { get; } = new();
  49. public enum ItemType
  50. {
  51. Sets,
  52. Documents
  53. }
  54. public enum StatusType
  55. {
  56. Failed,
  57. Incomplete,
  58. Submitted,
  59. Approved
  60. }
  61. public JobDocumentStatusChart()
  62. {
  63. InitializeComponent();
  64. }
  65. public void Setup()
  66. {
  67. JobLookups = new Client<Job>()
  68. .Query(null,
  69. LookupFactory.DefineColumns<Job>())
  70. .Rows.Select(x => new KeyValuePair<Guid, string>(
  71. (Guid?)x["ID"] ?? Guid.Empty,
  72. LookupFactory.FormatLookup<Job>(x.ToDictionary(new[] { "ID" }), Array.Empty<string>())))
  73. .ToList();
  74. JobLookups.Insert(0, new(Guid.Empty, "Select Job"));
  75. var pallete = new ChartColorModel().GetMetroBrushes()
  76. .Select(x => (x as SolidColorBrush)?.Color)
  77. .Where(x => x != null)
  78. .Select(x => x!.Value).ToList();
  79. MilestoneTypes = new Client<JobDocumentSetMileStoneType>()
  80. .Query(null,
  81. Columns.None<JobDocumentSetMileStoneType>().Add(x => x.ID)
  82. .Add(x => x.Code)
  83. .Add(x => x.Description))
  84. .ToObjects<JobDocumentSetMileStoneType>().ToDictionary(x => x.ID, x => x);
  85. int i = 0;
  86. MileStoneColours = MilestoneTypes.ToDictionary(x => x.Key, x => pallete[i++ % pallete.Count]);
  87. SetupHeader();
  88. }
  89. #region Header
  90. private ComboBox JobBox;
  91. private ComboBox ItemTypeBox;
  92. private ComboBoxAdv MilestoneBox;
  93. private ComboBoxAdv StatusTypeBox;
  94. private void SetupHeader()
  95. {
  96. JobBox = new ComboBox
  97. {
  98. Margin = new Thickness(5, 0, 0, 0)
  99. };
  100. JobBox.ItemsSource = JobLookups;
  101. JobBox.SelectedValuePath = "Key";
  102. JobBox.DisplayMemberPath = "Value";
  103. JobBox.SelectedValue = Properties.JobID;
  104. JobBox.SelectionChanged += JobBox_SelectionChanged;
  105. ItemTypeBox = new ComboBox
  106. {
  107. Margin = new Thickness(5, 0, 0, 0)
  108. };
  109. ItemTypeBox.ItemsSource = Enum.GetValues<ItemType>();
  110. ItemTypeBox.SelectedValue = Properties.ItemType;
  111. ItemTypeBox.SelectionChanged += ItemTypeBox_SelectionChanged;
  112. MilestoneBox = new ComboBoxAdv
  113. {
  114. VerticalAlignment = VerticalAlignment.Stretch,
  115. VerticalContentAlignment = VerticalAlignment.Center,
  116. IsEditable = false,
  117. AllowMultiSelect = true,
  118. Width = 150,
  119. DefaultText = "Select Milestones",
  120. Margin = new Thickness(5, 0, 0, 0)
  121. };
  122. var items = MilestoneTypes.ToDictionary(x => x.Key, x => $"{x.Value.Code}: {x.Value.Description}");
  123. MilestoneBox.ItemsSource = items;
  124. MilestoneBox.SelectedValuePath = "Key";
  125. MilestoneBox.DisplayMemberPath = "Value";
  126. MilestoneBox.SelectedItems = Properties.MileStones.Select(x => items.Where(y => x == y.Key).FirstOrDefault()).ToObservableCollection();
  127. MilestoneBox.SelectionChanged += MileStoneBox_SelectionChanged;
  128. StatusTypeBox = new ComboBoxAdv
  129. {
  130. VerticalAlignment = VerticalAlignment.Stretch,
  131. VerticalContentAlignment = VerticalAlignment.Center,
  132. IsEditable = false,
  133. AllowMultiSelect = true,
  134. Width = 150,
  135. DefaultText = "Select Status",
  136. Margin = new Thickness(5, 0, 0, 0)
  137. };
  138. StatusTypeBox.ItemsSource = Enum.GetValues<StatusType>();
  139. StatusTypeBox.SelectedItems = GetStatusTypes().ToObservableCollection();
  140. StatusTypeBox.SelectionChanged += StatusTypeBox_SelectionChanged;
  141. Header.BeginUpdate()
  142. .Add(JobBox)
  143. .Add(ItemTypeBox)
  144. .Add(StatusTypeBox)
  145. .Add(MilestoneBox)
  146. .EndUpdate();
  147. }
  148. private void MileStoneBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
  149. {
  150. Properties.MileStones = MilestoneBox.SelectedItems.Cast<KeyValuePair<Guid, string>>().Select(x => x.Key).ToArray();
  151. Refresh();
  152. }
  153. private void JobBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
  154. {
  155. Properties.JobID = (Guid)JobBox.SelectedValue;
  156. Refresh();
  157. }
  158. private void ItemTypeBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
  159. {
  160. Properties.ItemType = (ItemType)ItemTypeBox.SelectedValue;
  161. Refresh();
  162. }
  163. private void StatusTypeBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
  164. {
  165. Properties.StatusTypes = StatusTypeBox.SelectedItems.Cast<StatusType>().OrderBy(x => x).ToArray();
  166. Refresh();
  167. }
  168. #endregion
  169. private void ClearView(string message = "No Data")
  170. {
  171. NoDataLabel.Content = message;
  172. Pie.Visibility = Visibility.Collapsed;
  173. ChartRow.Height = new GridLength(0);
  174. LabelRow.Height = new GridLength(1, GridUnitType.Star);
  175. }
  176. private void ShowView()
  177. {
  178. Pie.Visibility = Visibility.Visible;
  179. ChartRow.Height = new GridLength(1, GridUnitType.Star);
  180. LabelRow.Height = new GridLength(0);
  181. }
  182. private StatusType[] GetStatusTypes()
  183. {
  184. Properties.StatusTypes ??= Enum.GetValues<StatusType>().OrderBy(x => x).ToArray();
  185. return Properties.StatusTypes;
  186. }
  187. private StatusType ConvertStatus(JobDocumentSetMileStoneStatus status)
  188. {
  189. switch (status)
  190. {
  191. case JobDocumentSetMileStoneStatus.Approved:
  192. return StatusType.Approved;
  193. case JobDocumentSetMileStoneStatus.Submitted:
  194. return StatusType.Submitted;
  195. case JobDocumentSetMileStoneStatus.Rejected:
  196. case JobDocumentSetMileStoneStatus.Cancelled:
  197. return StatusType.Failed;
  198. case JobDocumentSetMileStoneStatus.NotStarted:
  199. case JobDocumentSetMileStoneStatus.InProgress:
  200. case JobDocumentSetMileStoneStatus.OnHold:
  201. case JobDocumentSetMileStoneStatus.InfoRequired:
  202. case JobDocumentSetMileStoneStatus.Unknown:
  203. default:
  204. return StatusType.Incomplete;
  205. }
  206. }
  207. private Filter<JobDocumentSetMileStone> GetStatusFilter()
  208. {
  209. var statusTypes = GetStatusTypes();
  210. if (statusTypes.Length == 0)
  211. return new Filter<JobDocumentSetMileStone>().All();
  212. var statuses = new List<JobDocumentSetMileStoneStatus>();
  213. if (statusTypes.Contains(StatusType.Incomplete))
  214. {
  215. statuses.Add(JobDocumentSetMileStoneStatus.NotStarted);
  216. statuses.Add(JobDocumentSetMileStoneStatus.InProgress);
  217. statuses.Add(JobDocumentSetMileStoneStatus.OnHold);
  218. statuses.Add(JobDocumentSetMileStoneStatus.InfoRequired);
  219. statuses.Add(JobDocumentSetMileStoneStatus.Unknown);
  220. }
  221. if (statusTypes.Contains(StatusType.Approved))
  222. {
  223. statuses.Add(JobDocumentSetMileStoneStatus.Approved);
  224. }
  225. if (statusTypes.Contains(StatusType.Submitted))
  226. {
  227. statuses.Add(JobDocumentSetMileStoneStatus.Submitted);
  228. }
  229. if (statusTypes.Contains(StatusType.Failed))
  230. {
  231. statuses.Add(JobDocumentSetMileStoneStatus.Rejected);
  232. statuses.Add(JobDocumentSetMileStoneStatus.Cancelled);
  233. }
  234. if (statuses.Count == 0)
  235. return new Filter<JobDocumentSetMileStone>().None();
  236. //return new Filter<JobDocumentSetMileStone>(x => x.Status).InList(statuses.ToArray());
  237. var filter = new Filter<JobDocumentSetMileStone>(x => x.Status).IsEqualTo(statuses[0]);
  238. for(int i = 1; i < statuses.Count; ++i)
  239. {
  240. filter.Or(x => x.Status).IsEqualTo(statuses[i]);
  241. }
  242. return filter;
  243. }
  244. private Filter<JobDocumentSetMileStone> GetMileStoneFilter()
  245. {
  246. if (Properties.MileStones.Length == 0)
  247. return new Filter<JobDocumentSetMileStone>().All();
  248. return new Filter<JobDocumentSetMileStone>(x => x.Type.ID).InList(Properties.MileStones);
  249. }
  250. public void Refresh()
  251. {
  252. if (Properties.JobID == Guid.Empty)
  253. {
  254. ClearView("Please select a Job");
  255. return;
  256. }
  257. var statusFilter = GetStatusFilter();
  258. var milestoneFilter = GetMileStoneFilter();
  259. var columns = Columns.None<JobDocumentSetMileStone>().Add(x => x.ID)
  260. .Add(x => x.Type.ID)
  261. .Add(x => x.Status);
  262. if (Properties.ItemType == ItemType.Documents)
  263. {
  264. columns.Add(x => x.Attachments);
  265. }
  266. var milestones = new Client<JobDocumentSetMileStone>()
  267. .Query(
  268. new Filter<JobDocumentSetMileStone>(x => x.DocumentSet.Job.ID).IsEqualTo(Properties.JobID)
  269. .And(statusFilter)
  270. .And(milestoneFilter),
  271. columns);
  272. if (!milestones.Rows.Any())
  273. {
  274. ClearView();
  275. return;
  276. }
  277. var grouping = milestones
  278. .ToObjects<JobDocumentSetMileStone>()
  279. .GroupBy(x => new { x.Type.ID, Status = ConvertStatus(x.Status) })
  280. .OrderBy(x => x.Key.ID).ThenBy(x => x.Key.Status);
  281. var statusTypes = GetStatusTypes();
  282. if (statusTypes.Length == 0)
  283. statusTypes = Enum.GetValues<StatusType>();
  284. float i = 0f;
  285. var statusShades = statusTypes.ToDictionary(x => x, x => -0.5f + (++i / statusTypes.Length) * 0.5f);
  286. var data = grouping.Select(x =>
  287. {
  288. if (!MilestoneTypes.TryGetValue(x.Key.ID, out var milestoneType)) return null;
  289. if (!MileStoneColours.TryGetValue(x.Key.ID, out var milestoneColour)) return null;
  290. return new JobDocumentItemViewModel
  291. {
  292. Group = $"{milestoneType.Code} : {x.Key.Status}",
  293. Count = Properties.ItemType == ItemType.Documents ? x.Sum(x => x.Attachments) : x.Count(),
  294. MileStoneCode = milestoneType.Code,
  295. Status = x.Key.Status,
  296. Colour = new SolidColorBrush(ImageUtils.AdjustBrightness(milestoneColour, statusShades[x.Key.Status]))
  297. };
  298. }).Where(x => x is not null).Select(x => x!).ToList();
  299. Legend.ItemsSource = MilestoneTypes.Select(x =>
  300. {
  301. var colour = MileStoneColours[x.Key];
  302. return new { Text = x.Value.Code, Colour = new SolidColorBrush(colour) };
  303. }).ToList();
  304. Pie.ItemsSource = data;
  305. ShowView();
  306. }
  307. public void Shutdown(CancelEventArgs? cancel)
  308. {
  309. }
  310. }
  311. }