using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using Comal.Classes; using InABox.Clients; using InABox.Configuration; using InABox.Core; using InABox.DynamicGrid; using InABox.WPF; using Syncfusion.UI.Xaml.Grid; using Syncfusion.UI.Xaml.Grid.Helpers; using Syncfusion.Windows.Shared; using MultiQuery = InABox.Core.MultiQuery; using SelectionChangedEventArgs = System.Windows.Controls.SelectionChangedEventArgs; namespace PRSDesktop { public class EquipmentPlannerValue { public Guid ID { get; set; } public Brush Background { get; set; } public Brush Foreground { get; set; } public String Text { get; set; } private String _color = ""; public String Color { get { return _color; } set { _color = value; var color = String.IsNullOrWhiteSpace(value) ? Colors.Transparent : (Color)ColorConverter.ConvertFromString(value); Background = new SolidColorBrush(color) { Opacity = 0.8 }; Foreground = new SolidColorBrush(ImageUtils.GetForegroundColor(color)) { Opacity = 0.8 }; } } } public class EquipmentResourceModel : Model { public String? Code { get; } public BitmapImage? Image { get; set; } public EquipmentActivity[] Activities { get; private set; } public EquipmentResourceModel(CoreRow row, BitmapImage? image, EquipmentActivity[] activities) : base(row) { Activities = activities; Code = Get(c => c.Code); Image = image; } public override Columns GetColumns() { return InABox.Core.Columns.None().Add(c => c.ID) .Add(c => c.Code) .Add(c=>c.GroupLink.ID); } } public class TimeSlot { public TimeSpan From { get; set; } public TimeSpan To { get; set; } public TimeSlot(TimeSpan from, TimeSpan to) { From = from; To = to; } public override string ToString() { return $"{From:HH\\:mm}"; } } public partial class EquipmentPlanner : UserControl { private enum Suppress { This } private EquipmentResourceModel[] _equipment = new EquipmentResourceModel[] { }; private JobModel[] _jobs = new JobModel[] { }; private CoreFilterDefinitions _jobfilters = new CoreFilterDefinitions(); public event LoadSettings LoadSettings; public event SaveSettings SaveSettings; private void DoLoadSettings() { Properties = LoadSettings?.Invoke(this) ?? new EquipmentPlannerProperties(); _jobfilters = new GlobalConfiguration("Job").Load(); } private void DoSaveSettings() { SaveSettings?.Invoke(this, Properties); } public EquipmentPlannerProperties Properties { get; set; } public EquipmentPlanner() { using (new EventSuppressor(Suppress.This)) InitializeComponent(); } private Filter? GetJobFilter() { var jobfilter = _jobfilters.FirstOrDefault(x => String.Equals(x.Name, Properties.JobFilter)); return !String.IsNullOrWhiteSpace(jobfilter?.Filter) ? Serialization.Deserialize>(jobfilter.Filter) : LookupFactory.DefineFilter(); } public void Setup() { using (new EventSuppressor(Suppress.This)) { DoLoadSettings(); EquipmentSelector.Setup(); EquipmentSelector.Settings = Properties.EquipmentSettings; EquipmentSelector.Selection = Properties.EquipmentSelection; FromDate.DateTime = DateTime.Today; ToDate.DateTime = DateTime.Today.AddYears(1); MultiQuery query = new MultiQuery(); query.Add( GetJobFilter(), JobModel.Columns, new SortOrder(x => x.JobNumber) ); query.Query(); _jobs = query.Get().Rows.Select(r => new JobModel(r)).ToArray(); JobFilter.ItemsSource = _jobfilters; JobFilter.SelectedValue = _jobfilters.FirstOrDefault(x => String.Equals(x.Name, Properties.JobFilter)); } } public void Shutdown(CancelEventArgs? cancel) { } public void Refresh() { using (new WaitCursor()) { _equipment = EquipmentSelector.GetEquipmentData((row, img, activities) => new EquipmentResourceModel(row, img, activities)); var eqids = _equipment.Select(x => x.ID).ToArray(); DateTime fromdate = FromDate.DateTime.HasValue ? FromDate.DateTime.Value.Date : DateTime.Today; DateTime todate = ToDate.DateTime.HasValue ? ToDate.DateTime.Value.Date : DateTime.Today.AddYears(1); MultiQuery query = new MultiQuery(); query.Add( new Filter(x => x.Equipment.ID).InList(eqids) .And(x => x.Date).IsGreaterThanOrEqualTo(fromdate) .And(x => x.Date).IsLessThanOrEqualTo(todate), EquipmentAssignmentModel.Columns, new SortOrder(x => x.Equipment.ID).ThenBy(x => x.Date).ThenBy(x => x.Booked.Duration, SortDirection.Descending) ); query.Query(); var assignments = query.Get().Rows.Select(r => new EquipmentAssignmentModel(r)).ToArray(); var data = new DataTable(); data.Columns.Add("Date", typeof(DateTime)); data.Columns.Add("From", typeof(TimeSpan)); data.Columns.Add("To", typeof(TimeSpan)); foreach (var equipment in _equipment) data.Columns.Add(equipment.ID.ToString(), typeof(object)); for (var curdate = fromdate; curdate <= todate; curdate = curdate.AddDays(1)) { foreach (var slot in Properties.TimeSlots) { var values = new List { curdate, slot.From, slot.To }; foreach (var equipment in _equipment) { var value = new EquipmentPlannerValue(); var bOK = CheckAssignments(equipment, curdate, slot, assignments, value); //bOK = bOK || CheckRoster(employee, curdate, value); //bOK = bOK || CheckStandardLeave(leavevalue, value); //bOK = bOK || CheckLeaveRequest(employee, curdate, _leaverequests, value); values.Add(value); } data.Rows.Add(values.ToArray()); } } dataGrid.ItemsSource = data; } } private bool CheckAssignments(EquipmentResourceModel equipment, DateTime curdate, TimeSlot slot, EquipmentAssignmentModel[] assignments, EquipmentPlannerValue value) { var assignment = assignments.FirstOrDefault(x => (x.EquipmentID == equipment.ID) && (x.Date == curdate.Date) && (x.BookedStart < slot.To) && (x.BookedFinish > slot.From)); var bgColor = Properties.WorkDays.Contains(curdate.DayOfWeek) ? System.Drawing.Color.LightYellow : System.Drawing.Color.LightGray; value.Color = ImageUtils.ColorToString(bgColor); if (assignment == null) return false; value.ID = assignment.ID; value.Text = (value.ID != Guid.Empty) ? assignment?.JobNumber ?? "" : "XX"; value.Color = ImageUtils.ColorToString(System.Drawing.Color.LightGreen); return true; } #region AutoGenerate Columns / Styling private class EquipmentResourcePlannerBackgroundConverter : AbstractConverter { public override Brush Convert(EquipmentPlannerValue value) => value?.Background ?? new SolidColorBrush(Colors.LightYellow) { Opacity = 0.8 }; } private class EquipmentResourcePlannerForegroundConverter : AbstractConverter { public override Brush Convert(EquipmentPlannerValue value) => value?.Foreground ?? new SolidColorBrush(Colors.DimGray) { Opacity = 0.8 }; } private class EquipmentResourcePlannerFontStyleConverter : AbstractConverter { public override FontStyle Convert(EquipmentPlannerValue value) => FontStyles.Normal; } private class EquipmentResourcePlannerFontWeightConverter : AbstractConverter { public override FontWeight Convert(EquipmentPlannerValue value) => FontWeights.Normal; } private class EquipmentResourcePlannerContentConverter : AbstractConverter { public override String? Convert(EquipmentPlannerValue value) => value?.Text; } private class DateFormatConverter : AbstractConverter { public String Format { get; private set; } public override string Convert(DateTime value) { return String.Format($"{{0:{Format}}}", value); } public DateFormatConverter(string format) { Format = format; } } private void DataGrid_AutoGeneratingColumn(object? sender, AutoGeneratingColumnArgs e) { e.Column.TextAlignment = TextAlignment.Center; e.Column.HorizontalHeaderContentAlignment = HorizontalAlignment.Center; e.Column.ColumnSizer = GridLengthUnitType.None; var value = (e.Column.ValueBinding as Binding)!; if (value.Path.Path.Equals("Date")) // && e.Column is GridDateTimeColumn dt) { if (dataGrid.Columns.FirstOrDefault() is GridTemplateColumn) e.Cancel = true; else { var col = new GridTemplateColumn(); col.CellTemplate = TemplateGenerator.CreateDataTemplate(() => { var stack = new StackPanel() { Orientation = Orientation.Vertical, VerticalAlignment = VerticalAlignment.Center, HorizontalAlignment = HorizontalAlignment.Stretch }; var day = new Label() { HorizontalContentAlignment = HorizontalAlignment.Center }; day.SetBinding(Label.ContentProperty, new Binding("Date") { Converter = new DateFormatConverter("dddd") }); stack.Children.Add(day); var date = new Label() { HorizontalContentAlignment = HorizontalAlignment.Center }; date.SetBinding(Label.ContentProperty, new Binding("Date") { Converter = new DateFormatConverter("dd MMM yy") }); stack.Children.Add(date); return stack; }); col.Width = 70; col.HeaderStyle = Resources["DateHeaderStyle"] as Style; col.HeaderText = "Date"; col.AllowFocus = false; e.Column = col; } //dt.Width = 70; //dt.HeaderStyle = Resources["DateHeaderStyle"] as Style; //dt.AllowFocus = false; //dt.CustomPattern = "dd MMM yy"; //dt.Pattern = DateTimePattern.CustomPattern; } else if (value.Path.Path.Equals("From") && e.Column is GridTimeSpanColumn ts) { ts.Width = Properties.TimeSlots.Length > 1 ? 50 : 0; ts.HeaderText = "Time"; ts.HeaderStyle = Resources["TimeHeaderStyle"] as Style; ts.Format = "hh:mm"; ts.AllowFocus = false; } else if (value.Path.Path.Equals("To")) { e.Column.Width = 0; e.Column.HeaderStyle = Resources["TimeHeaderStyle"] as Style; e.Column.AllowFocus = false; } else { var col = new GridTemplateColumn(); col.CellTemplate = TemplateGenerator.CreateDataTemplate(() => { var grid = new Grid() { VerticalAlignment = VerticalAlignment.Stretch, HorizontalAlignment = HorizontalAlignment.Stretch }; grid.ColumnDefinitions.Add(new ColumnDefinition() {Width = new GridLength(1, GridUnitType.Star)}); grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star)}); grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Auto)}); var job = new Label() { HorizontalContentAlignment = HorizontalAlignment.Center, VerticalContentAlignment = VerticalAlignment.Center }; job.SetValue(Grid.ColumnProperty,0); job.SetValue(Grid.RowProperty,0); //day.SetBinding(Label.ContentProperty, new Binding("Date") { Converter = new DateFormatConverter("dddd") }); grid.Children.Add(job); return grid; }); col.Width = 80; col.HeaderStyle = Resources["ContentHeaderStyle"] as Style; col.AllowFocus = true; var style = new Style(typeof(GridCell)); style.Setters.Add(new Setter(BackgroundProperty, new Binding(value.Path.Path) { Converter = new EquipmentResourcePlannerBackgroundConverter() })); style.Setters.Add(new Setter(ForegroundProperty, new Binding(value.Path.Path) { Converter = new EquipmentResourcePlannerForegroundConverter() })); col.CellStyle = style; col.HeaderText = (Guid.TryParse(value.Path.Path, out Guid id)) ? _equipment.FirstOrDefault(x => String.Equals(x.ID, id))?.Code ?? value.Path.Path : value.Path.Path; e.Column = col; // var style = new Style(typeof(GridCell)); // style.Setters.Add(new Setter(BackgroundProperty, new Binding(value.Path.Path) { Converter = new EquipmentResourcePlannerBackgroundConverter() })); // style.Setters.Add(new Setter(ForegroundProperty, new Binding(value.Path.Path) { Converter = new EquipmentResourcePlannerForegroundConverter() })); // style.Setters.Add(new Setter(FontStyleProperty, new Binding(value.Path.Path) { Converter = new EquipmentResourcePlannerFontStyleConverter() })); // style.Setters.Add(new Setter(FontWeightProperty, new Binding(value.Path.Path) { Converter = new EquipmentResourcePlannerFontWeightConverter() })); // e.Column.CellStyle = style; // e.Column.Width = 80; // e.Column.HeaderStyle = Resources["ContentHeaderStyle"] as Style; // // e.Column.HeaderText = (Guid.TryParse(value.Path.Path, out Guid id)) // ? _equipment.FirstOrDefault(x => String.Equals(x.ID, id))?.Code ?? value.Path.Path // : value.Path.Path; // // e.Column.DisplayBinding = new Binding { Path = new PropertyPath(e.Column.MappingName), Converter = new EquipmentResourcePlannerContentConverter() }; // //e.Column.ValueBinding = new Binding() { Path = new PropertyPath(e.Column.MappingName), Converter = new LeaveContentConverter() }; // //e.Column.UseBindingValue = true; // e.Column.AllowFocus = true; } } #endregion private bool HasData() { foreach (var cell in dataGrid.GetSelectedCells()) { if (!cell.IsDataRowCell) continue; var propertyCollection = dataGrid.View.GetPropertyAccessProvider(); var cellValue = propertyCollection.GetValue(cell.RowData, cell.Column.MappingName); if (cellValue is EquipmentPlannerValue val && val.ID != Guid.Empty) return true; } return false; } private bool HasData(GridCellInfo cell) { if (!cell.IsDataRowCell) return false; var propertyCollection = dataGrid.View.GetPropertyAccessProvider(); var cellValue = propertyCollection.GetValue(cell.RowData, cell.Column.MappingName); return cellValue is EquipmentPlannerValue val && val.ID != Guid.Empty; } private void DataGrid_ContextMenuOpening(object sender, ContextMenuEventArgs e) { var vc = dataGrid.GetVisualContainer(); var p = Mouse.GetPosition(vc); var rci = vc.PointToCellRowColumnIndex(p); if (rci.RowIndex < 1 || rci.ColumnIndex < 1) { e.Handled = true; return; } dataGrid.ContextMenu.Items.Clear(); var bAssign = !HasData(dataGrid.CurrentCellInfo); var bClear = HasData(); if (bAssign) { foreach (var job in _jobs) { var assign = new MenuItem { Header = job.Name, Tag = job }; assign.Click += AssignJobClick; dataGrid.ContextMenu.Items.Add(assign); } } if (bClear && bAssign) dataGrid.ContextMenu.Items.Add(new Separator()); if (bClear) { var clear = new MenuItem { Header = "Clear Assignments" }; clear.Click += ClearJobClick; dataGrid.ContextMenu.Items.Add(clear); } } private void GetSelectionData(out DateTime from, out DateTime to, out Guid[] employees, out Guid[] assignments) { var emps = new List(); var items = new List(); from = DateTime.MaxValue; to = DateTime.MinValue; foreach (var cell in dataGrid.GetSelectedCells()) { var binding = (cell.Column.ValueBinding as Binding)!; if (Guid.TryParse(binding.Path.Path, out var emp)) if (!emps.Contains(emp)) emps.Add(emp); var row = (cell.RowData as DataRowView)!; var date = (DateTime)row.Row.ItemArray.First()!; var fromtime = (TimeSpan)row.Row.ItemArray.Skip(1).First()!; var totime = (TimeSpan)row.Row.ItemArray.Skip(2).First()!; if (date.Add(fromtime) < from) from = date.Add(fromtime); if (date.Add(totime) > to) to = date.Add(totime); Guid itemid = (row[binding.Path.Path] as EquipmentPlannerValue).ID; if (itemid != Guid.Empty) items.Add(itemid); } employees = emps.ToArray(); assignments = items.ToArray(); } private void AssignJobClick(object sender, RoutedEventArgs e) { JobModel? job = (sender as MenuItem)?.Tag as JobModel; if (job == null) return; GetSelectionData(out var from, out var to, out var ids, out var assignments); var updates = new List(); foreach (var id in ids) { for (DateTime curdate = from.Date; curdate <= to.Date; curdate = curdate.AddDays(1)) { var equipment = _equipment.FirstOrDefault(x => x.ID == id); if (equipment != null) { var assign = new EquipmentAssignment(); assign.Date = curdate; assign.Booked.Start = curdate == from.Date ? from.TimeOfDay : Properties.TimeSlots.FirstOrDefault()?.From ?? TimeSpan.Zero; assign.Booked.Finish = curdate == to.Date ? to.TimeOfDay : Properties.TimeSlots.LastOrDefault()?.To ?? TimeSpan.FromDays(1).Subtract(TimeSpan.FromSeconds(1)); assign.JobLink.ID = job.ID; assign.Equipment.ID = id; updates.Add(assign); } } } if (updates.Any()) { using (new WaitCursor()) { new Client().Save(updates, "Assigned from Employee Resource Planner"); Refresh(); } } } private void ClearJobClick(object sender, RoutedEventArgs e) { GetSelectionData(out DateTime from, out DateTime to, out Guid[] ids, out Guid[] assignments); if (assignments.Any() && MessageBox.Show("Clear Assignments?", "Confirm", MessageBoxButton.YesNo) == MessageBoxResult.Yes) { var deletes = assignments.Select(x => new EquipmentAssignment() { ID = x }).ToArray(); using (new WaitCursor()) { new Client().Delete(deletes, "Deleted from Employee Resource Planner"); Refresh(); } } } public void Heartbeat(TimeSpan time) { } private void _equipment_OnSettingsChanged(object sender, EquipmentSelectorSettingsChangedArgs args) { Properties.EquipmentSettings = args.Settings; DoSaveSettings(); } private void _equipment_OnSelectionChanged(object sender, EquipmentSelectorSelectionChangedArgs args) { Properties.EquipmentSelection = args.Selection; DoSaveSettings(); Refresh(); } private void DateTimeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (EventSuppressor.IsSet(Suppress.This)) return; Refresh(); } private void JobFilter_OnSelectionChanged(object sender, SelectionChangedEventArgs e) { if (EventSuppressor.IsSet(Suppress.This)) return; var sel = JobFilter.SelectedValue as CoreFilterDefinition; Properties.JobFilter = sel?.Name ?? ""; using (new WaitCursor()) { DoSaveSettings(); _jobs = new Client().Query( GetJobFilter(), JobModel.Columns, new SortOrder(x => x.JobNumber) ).Rows.Select(r => new JobModel(r)).ToArray(); } } private void JobFilterButton_Click(object sender, RoutedEventArgs e) { var window = new DynamicGridFilterEditor(_jobfilters, typeof(Job)); if (window.ShowDialog() == true) { new GlobalConfiguration("Job").Save(_jobfilters); JobFilter.SelectedValue = _jobfilters.FirstOrDefault(x => String.Equals(x.Name, Properties.JobFilter)); } } private void DataGrid_OnQueryCoveredRange(object? sender, GridQueryCoveredRangeEventArgs e) { if (Properties.TimeSlots.Length <= 1 || e.RowColumnIndex.RowIndex == 0) return; if (e.RowColumnIndex.ColumnIndex == 0) { var top = (((e.RowColumnIndex.RowIndex - 1) / Properties.TimeSlots.Length) * Properties.TimeSlots.Length) + 1; var bottom = top + Properties.TimeSlots.Length - 1; try { e.Range = new CoveredCellInfo(0, 0, top, bottom); } catch (Exception _exception) { System.Console.WriteLine(_exception); throw; } } else if (e.Record is DataRowView drv && e.RowColumnIndex.ColumnIndex > 1 && e.RowColumnIndex.ColumnIndex < drv.Row.ItemArray.Length && drv.Row.ItemArray[e.RowColumnIndex.ColumnIndex] is EquipmentPlannerValue epv) { if (epv.ID != Guid.Empty) { int iMin = int.MaxValue; int iMax = int.MinValue; var rows = drv.DataView.OfType().ToArray(); for (int i = 0; i < drv.DataView.Count; i++) { var test = drv.DataView[i].Row.ItemArray[e.RowColumnIndex.ColumnIndex] as EquipmentPlannerValue; if (test != null && test.ID == epv.ID) { if (i < iMin) iMin = i; if (i > iMax) iMax = i; } } e.Range = new CoveredCellInfo(e.RowColumnIndex.ColumnIndex, e.RowColumnIndex.ColumnIndex, iMin+1, iMax+1); } } e.Handled = true; } } }