JobDocumentStatusChart.xaml.cs 14 KB

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