using Comal.Classes; using InABox.Clients; using InABox.Core; using InABox.WPF; using javax.smartcardio; using NPOI.SS.Formula.Functions; using PRSDesktop.WidgetGroups; using Syncfusion.UI.Xaml.Grid; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Linq; using System.Runtime.CompilerServices; 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; using InABox.Configuration; using DataColumn = System.Data.DataColumn; using DataRow = System.Data.DataRow; using Columns = InABox.Core.Columns; namespace PRSDesktop.Dashboards.Manufacturing { public class HeaderModel { public string Name { get; set; } public HeaderModel(string name) { Name = name; } } public class JobModel { public string JobNumber { get; set; } public string Name { get; set; } public Guid ID { get; set; } public double NHours { get; set; } public int NPackets { get; set; } public Visibility HoursVisibility; public JobModel(string jobNumber, string name, Guid ID, double nHours, int nPackets) { JobNumber = jobNumber; Name = name; this.ID = ID; NHours = nHours; NPackets = nPackets; } } public class CardModel { private bool _empty; public int NPackets { get; set; } public double NHours { get; set; } public bool Empty { get => _empty || NPackets == 0; set => _empty = value; } public Visibility HoursVisibility; public CardModel(int nPackets, int nHours) { NPackets = nPackets; NHours = nHours; Empty = false; } public CardModel() { NPackets = 0; NHours = 0.0; Empty = true; } } public class JobManufacturingSummaryProperties : IUserConfigurationSettings, IDashboardProperties { public Guid JobStatus; public bool ShowEmptyJobs; public bool IncludeTimeRemaining; } public class JobManufacturingSummaryElement : DashboardElement { } /// /// Interaction logic for JobManufacturingSummary.xaml /// public partial class JobManufacturingSummary : UserControl, IDashboardWidget, IHeaderDashboard, INotifyPropertyChanged { private class SectionColumn { public string ColumnName { get; set; } public string DisplayName { get; set; } public SolidColorBrush Background { get; set; } } private class ManufacturingTotal { public int NPackets { get; set; } = 0; public double NHours { get; set; } = 0; } private readonly Dictionary SectionColumns = new(); private readonly Dictionary FactoryColumns = new(); public event PropertyChangedEventHandler? PropertyChanged; private List _jobs = new(); private List Jobs { get => _jobs; set { _jobs = value; OnPropertyChanged(nameof(NumberOfJobs)); } } private List Factories { get; set; } = new(); private Dictionary> Sections { get; set; } = new(); private List Cards { get; set; } = new(); private DataTable Data { get; set; } private Dictionary FactoryColumnHeaders = new(); public int NumberOfJobs => Jobs.Count; public JobManufacturingSummary() { InitializeComponent(); } public JobManufacturingSummaryProperties Properties { get; set; } public event LoadSettings? LoadSettings; public event SaveSettings? SaveSettings; public DashboardHeader Header { get; } = new(); public void Setup() { Jobs = new Client() .Query( new Filter().None(), Columns.None().Add(x => x.ID) .Add(x => x.JobNumber) .Add(x => x.Name) .Add(x => x.Color)) .ToObjects().ToList(); Factories = new Client() .Query(null, Columns.None().Add(x => x.ID).Add(x => x.Name), new SortOrder(x => x.Sequence)) .ToObjects().ToList(); Sections = new Client() .Query( new Filter(x => x.Hidden).IsEqualTo(false), Columns.None().Add(x => x.ID) .Add(x => x.Factory.ID) .Add(x => x.Name), new SortOrder(x => x.Sequence)) .ToObjects() .GroupBy(x => x.Factory.ID).ToDictionary(x => x.Key, x => x.ToList()); SetupHeader(); SetupGrid(); RefreshJobs(); } #region Header private ComboBox JobStatusBox; private JobStatus? SelectedStatus => JobStatusBox.SelectedValue as JobStatus; private void SetupHeader() { JobStatusBox = new ComboBox { Padding = new Thickness(5) }; var statuses = new Client().Load(); JobStatusBox.ItemsSource = statuses.Select(x => new Tuple($"{x.Code}: {x.Description}", x)); JobStatusBox.DisplayMemberPath = "Item1"; JobStatusBox.SelectedValuePath = "Item2"; JobStatusBox.SelectedValue = (Properties.JobStatus != Guid.Empty ? statuses.FirstOrDefault(x => x.ID == Properties.JobStatus) : null) ?? statuses.FirstOrDefault(x => x.Default) ?? statuses.FirstOrDefault(); JobStatusBox.SelectionChanged += StatusBox_SelectionChanged; var showEmptyJobsLabel = new Label { Content = "Show Empty Jobs?" }; var emptyJobsCheckBox = new CheckBox { IsChecked = Properties.ShowEmptyJobs, VerticalAlignment = VerticalAlignment.Center }; emptyJobsCheckBox.Checked += EmptyJobsCheckBox_Checked; emptyJobsCheckBox.Unchecked += EmptyJobsCheckBox_Checked; var includeRemainingLabel = new Label { Content = "Include Remaining Time?" }; var includeRemainingCheckBox = new CheckBox { IsChecked = Properties.IncludeTimeRemaining, VerticalAlignment = VerticalAlignment.Center }; includeRemainingCheckBox.Checked += IncludeRemainingCheckBox_Checked; includeRemainingCheckBox.Unchecked += IncludeRemainingCheckBox_Checked; Header.Add(JobStatusBox) .Add(showEmptyJobsLabel) .Add(emptyJobsCheckBox) .Add(includeRemainingLabel) .Add(includeRemainingCheckBox); } private void IncludeRemainingCheckBox_Checked(object sender, RoutedEventArgs e) { if (sender is not CheckBox checkBox) return; Properties.IncludeTimeRemaining = checkBox.IsChecked == true; Refresh(); } private void EmptyJobsCheckBox_Checked(object sender, RoutedEventArgs e) { if (sender is not CheckBox checkBox) return; Properties.ShowEmptyJobs = checkBox.IsChecked == true; Refresh(); } private void RefreshJobs() { Jobs = new Client() .Query( new Filter(x => x.JobStatus.ID).IsEqualTo(Properties.JobStatus), Columns.None().Add(x => x.ID) .Add(x => x.JobNumber) .Add(x => x.Name) .Add(x => x.Color)) .ToObjects().ToList(); Refresh(); } private void StatusBox_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e) { Properties.JobStatus = SelectedStatus?.ID ?? Guid.Empty; RefreshJobs(); } #endregion private void SetupGrid() { DataGrid.HeaderTemplate = Resources["HeaderTemplate"] as DataTemplate; Data = new DataTable(); Data.Columns.Add("Job", typeof(object)); var stackedHeaderRow = new StackedHeaderRow(); var stackedDetailRow = new StackedHeaderRow(); var factoryIdx = 0; foreach (var factory in Factories) { var columns = new List(); var toBeIssued = factory.ID.ToString(); Data.Columns.Add(toBeIssued, typeof(object)); columns.Add(toBeIssued); FactoryColumns.Add(factory.ID, new SectionColumn { ColumnName = toBeIssued, DisplayName = "To Be Issued", Background = factoryIdx % 2 == 0 ? new SolidColorBrush(Colors.WhiteSmoke) : new SolidColorBrush(Colors.LightGray) }); if (Sections.TryGetValue(factory.ID, out var sections)) { foreach (var section in sections) { var columnName = section.ID.ToString(); SectionColumns.Add(section.ID, new SectionColumn { ColumnName = columnName, DisplayName = section.Name, Background = factoryIdx % 2 == 0 ? new SolidColorBrush(Colors.WhiteSmoke) : new SolidColorBrush(Colors.LightGray) }); Data.Columns.Add(columnName, typeof(object)); columns.Add(columnName); } } var column = new StackedColumn { ChildColumns = string.Join(',', columns), HeaderText = "" }; FactoryColumnHeaders.Add(factory.ID, column); stackedDetailRow.StackedColumns.Add(column); stackedHeaderRow.StackedColumns.Add(new StackedColumn { ChildColumns = string.Join(',', columns), HeaderText = factory.Name }); ++factoryIdx; } DataGrid.StackedHeaderRows.Add(stackedHeaderRow); DataGrid.StackedHeaderRows.Add(stackedDetailRow); } private void SetPosition(FrameworkElement element, int row, int rowSpan, int column, int columnSpan) { element.SetValue(Grid.RowProperty, row); element.SetValue(Grid.RowSpanProperty, rowSpan); element.SetValue(Grid.ColumnProperty, column); element.SetValue(Grid.ColumnSpanProperty, columnSpan); } private void LoadPackets( IEnumerable> packets, Dictionary columns, Dictionary jobRows) { var groupPackets = packets .ToDictionary( x => x.Key, x => x.GroupBy(x => x.SetoutLink.JobLink.ID) .ToDictionary(x => x.Key, x => x.Aggregate(new CardModel(0, 0) { HoursVisibility = Properties.IncludeTimeRemaining ? Visibility.Visible : Visibility.Collapsed }, (c, p) => { c.NHours += p.TimeRemaining.TotalHours; ++c.NPackets; return c; }))); foreach (var (id, column) in columns) { var packetList = groupPackets.GetValueOrDefault(id); foreach (var job in Jobs) { if (!jobRows.TryGetValue(job.ID, out var row)) { continue; } if (packetList is null || !packetList.TryGetValue(job.ID, out var model)) { model = new CardModel(); } row[column.ColumnName] = model; } } } public void Refresh() { using var profiler = new Profiler(true); Data.Rows.Clear(); var rows = new Dictionary(); foreach (var job in Jobs) { var row = Data.NewRow(); Data.Rows.Add(row); row["Job"] = job; rows[job.ID] = row; } var sectionColumns = Columns.None().Add(x => x.ID) .Add(x => x.SetoutLink.JobLink.ID) .Add(x => x.StageLink.SectionID); var factoryColumns = Columns.None().Add(x => x.ID) .Add(x => x.SetoutLink.JobLink.ID) .Add(x => x.ManufacturingTemplateLink.Factory.ID); if (Properties.IncludeTimeRemaining) { sectionColumns.Add(x => x.TimeRemaining); factoryColumns.Add(x => x.TimeRemaining); } profiler.Log("Setup"); LoadPackets( new Client() .Query( new Filter(x => x.Archived).IsEqualTo(DateTime.MinValue) .And(x => x.StageLink.SectionID).IsNotEqualTo(Guid.Empty) .And(x => x.Completed).IsEqualTo(DateTime.MinValue) .And(x => x.SetoutLink.JobLink.ID).InList(Jobs.Select(x => x.ID).ToArray()), sectionColumns) .ToObjects() .GroupBy(x => x.StageLink.SectionID), SectionColumns, rows); profiler.Log("Section"); LoadPackets( new Client() .Query( new Filter(x => x.Archived).IsEqualTo(DateTime.MinValue) .And(x => x.StageLink.SectionID).IsEqualTo(Guid.Empty) .And(x => x.Completed).IsEqualTo(DateTime.MinValue) .And(x => x.SetoutLink.JobLink.ID).InList(Jobs.Select(x => x.ID).ToArray()), factoryColumns) .ToObjects() .GroupBy(x => x.ManufacturingTemplateLink.Factory.ID), FactoryColumns, rows); profiler.Log("Factory"); var removes = new List(); foreach(var (jobID, row) in rows) { var any = false; var nHours = 0.0; var nPackets = 0; foreach (var (id, column) in FactoryColumns.Concat(SectionColumns)) { if (row[column.ColumnName] is CardModel model && !model.Empty) { any = true; nHours += model.NHours; nPackets += model.NPackets; } } if (!any) { removes.Add(jobID); } else { if(row["Job"] is Job job) { row["Job"] = new JobModel(job.JobNumber, job.Name, job.ID, nHours, nPackets) { HoursVisibility = Properties.IncludeTimeRemaining ? Visibility.Visible : Visibility.Collapsed, }; } } } if (!Properties.ShowEmptyJobs) { foreach(var remove in removes) { if(rows.Remove(remove, out var row)) { Data.Rows.Remove(row); } } } var totals = new Dictionary(); foreach(var column in Data.Columns) { if (column is not DataColumn dataColumn) continue; Guid factoryID; var sectionColumn = SectionColumns.FirstOrDefault(x => x.Value.ColumnName == dataColumn.ColumnName); if(sectionColumn.Key != Guid.Empty) { factoryID = Sections.FirstOrDefault(x => x.Value.Any(x => x.ID == sectionColumn.Key)).Key; } else { var factoryColumn = FactoryColumns.FirstOrDefault(x => x.Value.ColumnName == dataColumn.ColumnName); factoryID = factoryColumn.Key; } if (factoryID == Guid.Empty) continue; if(!totals.TryGetValue(factoryID, out var total)) { total = new(); totals[factoryID] = total; } foreach(var row in rows.Values) { if (row[dataColumn] is not CardModel model) continue; total.NHours += model.NHours; total.NPackets += model.NPackets; } } foreach(var (factoryID, total) in totals) { var header = FactoryColumnHeaders[factoryID]; if (Properties.IncludeTimeRemaining) { header.HeaderText = $"Packets: {total.NPackets}, Hours: {total.NHours:f2}"; } else { header.HeaderText = $"Packets: {total.NPackets}"; } } DataGrid.ItemsSource = Data; } public void Shutdown(CancelEventArgs? cancel) { } private void DataGrid_AutoGeneratingColumn(object sender, Syncfusion.UI.Xaml.Grid.AutoGeneratingColumnArgs e) { if(Guid.TryParse(e.Column.MappingName, out var id)) { if(SectionColumns.TryGetValue(id, out var column) || FactoryColumns.TryGetValue(id, out column)) { var style = new Style(); style.Setters.Add(new Setter(GridCell.BackgroundProperty, column.Background)); e.Column = new GridTemplateColumn { HeaderText = column.DisplayName, CellStyle = style, MappingName = e.Column.MappingName, CellTemplate = Resources["CardTemplate"] as DataTemplate, SetCellBoundValue = true, ColumnSizer = GridLengthUnitType.Auto }; } } else { e.Column = new GridTemplateColumn { CellTemplate = Resources["JobHeaderTemplate"] as DataTemplate, HeaderText = e.Column.HeaderText, MappingName = e.Column.MappingName, SetCellBoundValue = true, ColumnSizer = GridLengthUnitType.Auto }; } } private void DataGrid_QueryRowHeight(object sender, QueryRowHeightEventArgs e) { if(DataGrid.GridColumnSizer.GetAutoRowHeight(e.RowIndex, new GridRowSizingOptions(), out var height)) { e.Height = height; e.Handled = true; } } private void OnPropertyChanged([CallerMemberName] string name = "") { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); } } }