|
@@ -0,0 +1,379 @@
|
|
|
+using Comal.Classes;
|
|
|
+using InABox.Clients;
|
|
|
+using InABox.Core;
|
|
|
+using InABox.WPF;
|
|
|
+using java.nio.file;
|
|
|
+using Syncfusion.Data.Extensions;
|
|
|
+using Syncfusion.UI.Xaml.Charts;
|
|
|
+using Syncfusion.Windows.Controls.Gantt.Chart;
|
|
|
+using Syncfusion.Windows.Tools.Controls;
|
|
|
+using System;
|
|
|
+using System.Collections.Generic;
|
|
|
+using System.Linq;
|
|
|
+using System.Reactive.Linq;
|
|
|
+using System.Text;
|
|
|
+using System.Threading.Tasks;
|
|
|
+using System.Windows;
|
|
|
+using System.Windows.Controls;
|
|
|
+using System.Windows.Data;
|
|
|
+using System.Windows.Documents;
|
|
|
+using System.Windows.Input;
|
|
|
+using System.Windows.Media;
|
|
|
+using System.Windows.Media.Imaging;
|
|
|
+using System.Windows.Navigation;
|
|
|
+using System.Windows.Shapes;
|
|
|
+
|
|
|
+namespace PRSDesktop
|
|
|
+{
|
|
|
+ public class JobDocumentItemViewModel
|
|
|
+ {
|
|
|
+ public string Group { get; set; }
|
|
|
+
|
|
|
+ public string MileStoneCode { get; set; }
|
|
|
+
|
|
|
+ public JobDocumentStatusChart.StatusType Status { get; set; }
|
|
|
+
|
|
|
+ public int Count { get; set; }
|
|
|
+
|
|
|
+ public SolidColorBrush Colour { get; set; }
|
|
|
+ }
|
|
|
+
|
|
|
+ public class JobDocumentStatusChartProperties : BaseObject, IDashboardProperties
|
|
|
+ {
|
|
|
+ public JobDocumentStatusChart.ItemType ItemType { get; set; } = JobDocumentStatusChart.ItemType.Sets;
|
|
|
+
|
|
|
+ public JobDocumentStatusChart.StatusType[]? StatusTypes { get; set; } = null;
|
|
|
+
|
|
|
+ public Guid[] MileStones { get; set; } = Array.Empty<Guid>();
|
|
|
+
|
|
|
+ public Guid JobID { get; set; }
|
|
|
+ }
|
|
|
+
|
|
|
+ public class JobDocumentStatusChartElement : DashboardElement<JobDocumentStatusChart, WidgetGroups.Projects, JobDocumentStatusChartProperties> { }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Interaction logic for JobDocumentStatusChart.xaml
|
|
|
+ /// </summary>
|
|
|
+ public partial class JobDocumentStatusChart : UserControl,
|
|
|
+ IDashboardWidget<WidgetGroups.Projects, JobDocumentStatusChartProperties>,
|
|
|
+ IHeaderDashboard
|
|
|
+ {
|
|
|
+ private Dictionary<Guid, Color> MileStoneColours { get; set; }
|
|
|
+
|
|
|
+ private Dictionary<Guid, JobDocumentSetMileStoneType> MilestoneTypes { get; set; }
|
|
|
+ private List<KeyValuePair<Guid, string>> JobLookups { get; set; }
|
|
|
+
|
|
|
+ public JobDocumentStatusChartProperties Properties { get; set; }
|
|
|
+
|
|
|
+ public DashboardHeader Header { get; } = new();
|
|
|
+
|
|
|
+ public enum ItemType
|
|
|
+ {
|
|
|
+ Sets,
|
|
|
+ Documents
|
|
|
+ }
|
|
|
+
|
|
|
+ public enum StatusType
|
|
|
+ {
|
|
|
+ Failed,
|
|
|
+ Incomplete,
|
|
|
+ Submitted,
|
|
|
+ Approved
|
|
|
+ }
|
|
|
+
|
|
|
+ public JobDocumentStatusChart()
|
|
|
+ {
|
|
|
+ InitializeComponent();
|
|
|
+ }
|
|
|
+
|
|
|
+ public void Setup()
|
|
|
+ {
|
|
|
+ JobLookups = new Client<Job>()
|
|
|
+ .Query(null,
|
|
|
+ LookupFactory.DefineColumns<Job>())
|
|
|
+ .Rows.Select(x => new KeyValuePair<Guid, string>(
|
|
|
+ (Guid?)x["ID"] ?? Guid.Empty,
|
|
|
+ LookupFactory.FormatLookup<Job>(x.ToDictionary(new[] { "ID" }), Array.Empty<string>())))
|
|
|
+ .ToList();
|
|
|
+ JobLookups.Insert(0, new(Guid.Empty, "Select Job"));
|
|
|
+
|
|
|
+ var pallete = new ChartColorModel().GetMetroBrushes()
|
|
|
+ .Select(x => (x as SolidColorBrush)?.Color)
|
|
|
+ .Where(x => x != null)
|
|
|
+ .Select(x => x!.Value).ToList();
|
|
|
+
|
|
|
+ MilestoneTypes = new Client<JobDocumentSetMileStoneType>()
|
|
|
+ .Query(null,
|
|
|
+ new Columns<JobDocumentSetMileStoneType>(x => x.ID)
|
|
|
+ .Add(x => x.Code)
|
|
|
+ .Add(x => x.Description))
|
|
|
+ .ToList<JobDocumentSetMileStoneType>().ToDictionary(x => x.ID, x => x);
|
|
|
+
|
|
|
+ int i = 0;
|
|
|
+ MileStoneColours = MilestoneTypes.ToDictionary(x => x.Key, x => pallete[i++ % pallete.Count]);
|
|
|
+
|
|
|
+ SetupHeader();
|
|
|
+ }
|
|
|
+
|
|
|
+ #region Header
|
|
|
+
|
|
|
+ private ComboBox JobBox;
|
|
|
+ private ComboBox ItemTypeBox;
|
|
|
+ private ComboBoxAdv MilestoneBox;
|
|
|
+ private ComboBoxAdv StatusTypeBox;
|
|
|
+
|
|
|
+ private void SetupHeader()
|
|
|
+ {
|
|
|
+ JobBox = new ComboBox
|
|
|
+ {
|
|
|
+ Margin = new Thickness(5, 0, 0, 0)
|
|
|
+ };
|
|
|
+ JobBox.ItemsSource = JobLookups;
|
|
|
+ JobBox.SelectedValuePath = "Key";
|
|
|
+ JobBox.DisplayMemberPath = "Value";
|
|
|
+ JobBox.SelectedValue = Properties.JobID;
|
|
|
+ JobBox.SelectionChanged += JobBox_SelectionChanged;
|
|
|
+
|
|
|
+ ItemTypeBox = new ComboBox
|
|
|
+ {
|
|
|
+ Margin = new Thickness(5, 0, 0, 0)
|
|
|
+ };
|
|
|
+ ItemTypeBox.ItemsSource = Enum.GetValues<ItemType>();
|
|
|
+ ItemTypeBox.SelectedValue = Properties.ItemType;
|
|
|
+ ItemTypeBox.SelectionChanged += ItemTypeBox_SelectionChanged;
|
|
|
+
|
|
|
+ MilestoneBox = new ComboBoxAdv
|
|
|
+ {
|
|
|
+ VerticalAlignment = VerticalAlignment.Stretch,
|
|
|
+ VerticalContentAlignment = VerticalAlignment.Center,
|
|
|
+ IsEditable = false,
|
|
|
+ AllowMultiSelect = true,
|
|
|
+ Width = 150,
|
|
|
+ DefaultText = "Select Milestones",
|
|
|
+
|
|
|
+ Margin = new Thickness(5, 0, 0, 0)
|
|
|
+ };
|
|
|
+
|
|
|
+ var items = MilestoneTypes.ToDictionary(x => x.Key, x => $"{x.Value.Code}: {x.Value.Description}");
|
|
|
+ MilestoneBox.ItemsSource = items;
|
|
|
+ MilestoneBox.SelectedValuePath = "Key";
|
|
|
+ MilestoneBox.DisplayMemberPath = "Value";
|
|
|
+ MilestoneBox.SelectedItems = Properties.MileStones.Select(x => items.Where(y => x == y.Key).FirstOrDefault()).ToObservableCollection();
|
|
|
+ MilestoneBox.SelectionChanged += MileStoneBox_SelectionChanged;
|
|
|
+
|
|
|
+ StatusTypeBox = new ComboBoxAdv
|
|
|
+ {
|
|
|
+ VerticalAlignment = VerticalAlignment.Stretch,
|
|
|
+ VerticalContentAlignment = VerticalAlignment.Center,
|
|
|
+ IsEditable = false,
|
|
|
+ AllowMultiSelect = true,
|
|
|
+ Width = 150,
|
|
|
+ DefaultText = "Select Status",
|
|
|
+
|
|
|
+ Margin = new Thickness(5, 0, 0, 0)
|
|
|
+ };
|
|
|
+ StatusTypeBox.ItemsSource = Enum.GetValues<StatusType>();
|
|
|
+ StatusTypeBox.SelectedItems = GetStatusTypes().ToObservableCollection();
|
|
|
+ StatusTypeBox.SelectionChanged += StatusTypeBox_SelectionChanged;
|
|
|
+
|
|
|
+ Header.BeginUpdate()
|
|
|
+ .Add(JobBox)
|
|
|
+ .Add(ItemTypeBox)
|
|
|
+ .Add(StatusTypeBox)
|
|
|
+ .Add(MilestoneBox)
|
|
|
+ .EndUpdate();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void MileStoneBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
|
+ {
|
|
|
+ Properties.MileStones = MilestoneBox.SelectedItems.Cast<KeyValuePair<Guid, string>>().Select(x => x.Key).ToArray();
|
|
|
+ Refresh();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void JobBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
|
+ {
|
|
|
+ Properties.JobID = (Guid)JobBox.SelectedValue;
|
|
|
+ Refresh();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void ItemTypeBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
|
+ {
|
|
|
+ Properties.ItemType = (ItemType)ItemTypeBox.SelectedValue;
|
|
|
+ Refresh();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void StatusTypeBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
|
+ {
|
|
|
+ Properties.StatusTypes = StatusTypeBox.SelectedItems.Cast<StatusType>().OrderBy(x => x).ToArray();
|
|
|
+ Refresh();
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ private void ClearView(string message = "No Data")
|
|
|
+ {
|
|
|
+ NoDataLabel.Content = message;
|
|
|
+
|
|
|
+ Pie.Visibility = Visibility.Collapsed;
|
|
|
+
|
|
|
+ ChartRow.Height = new GridLength(0);
|
|
|
+ LabelRow.Height = new GridLength(1, GridUnitType.Star);
|
|
|
+ }
|
|
|
+ private void ShowView()
|
|
|
+ {
|
|
|
+ Pie.Visibility = Visibility.Visible;
|
|
|
+
|
|
|
+ ChartRow.Height = new GridLength(1, GridUnitType.Star);
|
|
|
+ LabelRow.Height = new GridLength(0);
|
|
|
+ }
|
|
|
+
|
|
|
+ private StatusType[] GetStatusTypes()
|
|
|
+ {
|
|
|
+ Properties.StatusTypes ??= Enum.GetValues<StatusType>().OrderBy(x => x).ToArray();
|
|
|
+ return Properties.StatusTypes;
|
|
|
+ }
|
|
|
+
|
|
|
+ private StatusType ConvertStatus(JobDocumentSetMileStoneStatus status)
|
|
|
+ {
|
|
|
+ switch (status)
|
|
|
+ {
|
|
|
+ case JobDocumentSetMileStoneStatus.Approved:
|
|
|
+ return StatusType.Approved;
|
|
|
+ case JobDocumentSetMileStoneStatus.Submitted:
|
|
|
+ return StatusType.Submitted;
|
|
|
+ case JobDocumentSetMileStoneStatus.Rejected:
|
|
|
+ case JobDocumentSetMileStoneStatus.Cancelled:
|
|
|
+ return StatusType.Failed;
|
|
|
+ case JobDocumentSetMileStoneStatus.NotStarted:
|
|
|
+ case JobDocumentSetMileStoneStatus.InProgress:
|
|
|
+ case JobDocumentSetMileStoneStatus.OnHold:
|
|
|
+ case JobDocumentSetMileStoneStatus.InfoRequired:
|
|
|
+ case JobDocumentSetMileStoneStatus.Unknown:
|
|
|
+ default:
|
|
|
+ return StatusType.Incomplete;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private Filter<JobDocumentSetMileStone> GetStatusFilter()
|
|
|
+ {
|
|
|
+ var statusTypes = GetStatusTypes();
|
|
|
+
|
|
|
+ if (statusTypes.Length == 0)
|
|
|
+ return new Filter<JobDocumentSetMileStone>().All();
|
|
|
+
|
|
|
+ var statuses = new List<JobDocumentSetMileStoneStatus>();
|
|
|
+ if (statusTypes.Contains(StatusType.Incomplete))
|
|
|
+ {
|
|
|
+ statuses.Add(JobDocumentSetMileStoneStatus.NotStarted);
|
|
|
+ statuses.Add(JobDocumentSetMileStoneStatus.InProgress);
|
|
|
+ statuses.Add(JobDocumentSetMileStoneStatus.OnHold);
|
|
|
+ statuses.Add(JobDocumentSetMileStoneStatus.InfoRequired);
|
|
|
+ statuses.Add(JobDocumentSetMileStoneStatus.Unknown);
|
|
|
+ }
|
|
|
+ if (statusTypes.Contains(StatusType.Approved))
|
|
|
+ {
|
|
|
+ statuses.Add(JobDocumentSetMileStoneStatus.Approved);
|
|
|
+ }
|
|
|
+ if (statusTypes.Contains(StatusType.Submitted))
|
|
|
+ {
|
|
|
+ statuses.Add(JobDocumentSetMileStoneStatus.Submitted);
|
|
|
+ }
|
|
|
+ if (statusTypes.Contains(StatusType.Failed))
|
|
|
+ {
|
|
|
+ statuses.Add(JobDocumentSetMileStoneStatus.Rejected);
|
|
|
+ statuses.Add(JobDocumentSetMileStoneStatus.Cancelled);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (statuses.Count == 0)
|
|
|
+ return new Filter<JobDocumentSetMileStone>().None();
|
|
|
+
|
|
|
+ //return new Filter<JobDocumentSetMileStone>(x => x.Status).InList(statuses.ToArray());
|
|
|
+ var filter = new Filter<JobDocumentSetMileStone>(x => x.Status).IsEqualTo(statuses[0]);
|
|
|
+ for(int i = 1; i < statuses.Count; ++i)
|
|
|
+ {
|
|
|
+ filter.Or(x => x.Status).IsEqualTo(statuses[i]);
|
|
|
+ }
|
|
|
+ return filter;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Filter<JobDocumentSetMileStone> GetMileStoneFilter()
|
|
|
+ {
|
|
|
+ if (Properties.MileStones.Length == 0)
|
|
|
+ return new Filter<JobDocumentSetMileStone>().All();
|
|
|
+ return new Filter<JobDocumentSetMileStone>(x => x.Type.ID).InList(Properties.MileStones);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void Refresh()
|
|
|
+ {
|
|
|
+ if (Properties.JobID == Guid.Empty)
|
|
|
+ {
|
|
|
+ ClearView("Please select a Job");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ var statusFilter = GetStatusFilter();
|
|
|
+ var milestoneFilter = GetMileStoneFilter();
|
|
|
+ var columns = new Columns<JobDocumentSetMileStone>(x => x.ID)
|
|
|
+ .Add(x => x.Type.ID)
|
|
|
+ .Add(x => x.Status);
|
|
|
+
|
|
|
+ if (Properties.ItemType == ItemType.Documents)
|
|
|
+ {
|
|
|
+ columns.Add(x => x.Attachments);
|
|
|
+ }
|
|
|
+
|
|
|
+ var milestones = new Client<JobDocumentSetMileStone>()
|
|
|
+ .Query(
|
|
|
+ new Filter<JobDocumentSetMileStone>(x => x.DocumentSet.Job.ID).IsEqualTo(Properties.JobID)
|
|
|
+ .And(statusFilter)
|
|
|
+ .And(milestoneFilter),
|
|
|
+ columns);
|
|
|
+ if (!milestones.Rows.Any())
|
|
|
+ {
|
|
|
+ ClearView();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ var grouping = milestones
|
|
|
+ .ToObjects<JobDocumentSetMileStone>()
|
|
|
+ .GroupBy(x => new { x.Type.ID, Status = ConvertStatus(x.Status) })
|
|
|
+ .OrderBy(x => x.Key.ID).ThenBy(x => x.Key.Status);
|
|
|
+
|
|
|
+ var statusTypes = GetStatusTypes();
|
|
|
+ if (statusTypes.Length == 0)
|
|
|
+ statusTypes = Enum.GetValues<StatusType>();
|
|
|
+
|
|
|
+ float i = 0f;
|
|
|
+ var statusShades = statusTypes.ToDictionary(x => x, x => -0.5f + (++i / statusTypes.Length) * 0.5f);
|
|
|
+
|
|
|
+ var data = grouping.Select(x =>
|
|
|
+ {
|
|
|
+ if (!MilestoneTypes.TryGetValue(x.Key.ID, out var milestoneType)) return null;
|
|
|
+ if (!MileStoneColours.TryGetValue(x.Key.ID, out var milestoneColour)) return null;
|
|
|
+
|
|
|
+ return new JobDocumentItemViewModel
|
|
|
+ {
|
|
|
+ Group = $"{milestoneType.Code} : {x.Key.Status}",
|
|
|
+ Count = Properties.ItemType == ItemType.Documents ? x.Sum(x => x.Attachments) : x.Count(),
|
|
|
+ MileStoneCode = milestoneType.Code,
|
|
|
+ Status = x.Key.Status,
|
|
|
+ Colour = new SolidColorBrush(ImageUtils.AdjustBrightness(milestoneColour, statusShades[x.Key.Status]))
|
|
|
+ };
|
|
|
+ }).Where(x => x is not null).Select(x => x!).ToList();
|
|
|
+
|
|
|
+ Legend.ItemsSource = MilestoneTypes.Select(x =>
|
|
|
+ {
|
|
|
+ var colour = MileStoneColours[x.Key];
|
|
|
+ return new { Text = x.Value.Code, Colour = new SolidColorBrush(colour) };
|
|
|
+ }).ToList();
|
|
|
+
|
|
|
+ Pie.ItemsSource = data;
|
|
|
+
|
|
|
+ ShowView();
|
|
|
+ }
|
|
|
+
|
|
|
+ public void Shutdown()
|
|
|
+ {
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|