Sfoglia il codice sorgente

Added ability to swap rows and columns in ProjectPlanner

Kenric Nugteren 1 anno fa
parent
commit
19578cf3e5

+ 32 - 27
prs.desktop/Panels/EmployeePlanner/EmployeeResourcePlanner.xaml.cs

@@ -242,6 +242,30 @@ public partial class EmployeeResourcePlanner : UserControl, IPropertiesPanel<Emp
                 EmployeeResourcePlannerViewType.Day or _ => 7.5
             };
 
+            var dates = new List<DateTime>();
+            for (var curdate = fromdate; curdate <= todate; curdate += period)
+            {
+                dates.Add(curdate);
+            }
+            _dates = dates.ToArray();
+
+            var dataValues = new EmployeeResourcePlannerValue[dates.Count, _employees.Length];
+            foreach(var (dateIdx, curdate) in dates.WithIndex())
+            {
+                var leavevalue = GetStandardLeave(curdate, _standardleaves);
+                var values = new List<object> { curdate };
+                foreach (var (empIdx, employee) in _employees.WithIndex())
+                {
+                    var value = new EmployeeResourcePlannerValue();
+                    // Note use of short-circuiting here.
+                    var bOK = CheckAssignments(employee, curdate, curdate + period, assignments, standardHours, value)
+                        || CheckStandardLeave(leavevalue, value)
+                        || CheckLeaveRequest(employee, curdate, curdate + period, _leaverequests, value)
+                        || CheckRoster(employee, curdate, curdate + period, standardHours, value);
+
+                    dataValues[dateIdx, empIdx] = value;
+                }
+            }
             if(Settings.DisplayMode == EmployeePlannerDisplayMode.EmployeeColumns)
             {
                 data.Columns.Add("Date", typeof(DateTime));
@@ -251,22 +275,13 @@ public partial class EmployeeResourcePlanner : UserControl, IPropertiesPanel<Emp
                     data.Columns.Add(employee.ID.ToString(), typeof(object));
                 }
 
-                for (var curdate = fromdate; curdate <= todate; curdate += period)
+                foreach(var (dateIdx, curdate) in dates.WithIndex())
                 {
-                    var leavevalue = GetStandardLeave(curdate, _standardleaves);
                     var values = new List<object> { curdate };
-                    foreach (var employee in _employees)
+                    foreach (var (empIdx, employee) in _employees.WithIndex())
                     {
-                        var value = new EmployeeResourcePlannerValue();
-                        // Note use of short-circuiting here.
-                        var bOK = CheckAssignments(employee, curdate, curdate + period, assignments, standardHours, value)
-                            || CheckStandardLeave(leavevalue, value)
-                            || CheckLeaveRequest(employee, curdate, curdate + period, _leaverequests, value)
-                            || CheckRoster(employee, curdate, curdate + period, standardHours, value);
-
-                        values.Add(value);
+                        values.Add(dataValues[dateIdx, empIdx]);
                     }
-
                     data.Rows.Add(values.ToArray());
                 }
             }
@@ -274,27 +289,17 @@ public partial class EmployeeResourcePlanner : UserControl, IPropertiesPanel<Emp
             {
                 data.Columns.Add("Employee", typeof(object));
 
-                var dates = new List<DateTime>();
-                for (var curdate = fromdate; curdate <= todate; curdate += period)
+                foreach (var (dateIdx, date) in dates.WithIndex())
                 {
-                    data.Columns.Add(dates.Count.ToString(), typeof(object));
-                    dates.Add(curdate);
+                    data.Columns.Add(dateIdx.ToString(), typeof(object));
                 }
-                _dates = dates.ToArray();
 
-                foreach (var employee in _employees)
+                foreach (var (empIdx, employee) in _employees.WithIndex())
                 {
                     var values = new List<object> { employee };
-                    for (var curdate = fromdate; curdate <= todate; curdate += period)
+                    foreach(var (dateIdx, curdate) in dates.WithIndex())
                     {
-                        var leavevalue = GetStandardLeave(curdate, _standardleaves);
-                        var value = new EmployeeResourcePlannerValue();
-                        var bOK = CheckAssignments(employee, curdate, curdate + period, assignments, standardHours, value)
-                            || CheckStandardLeave(leavevalue, value)
-                            || CheckLeaveRequest(employee, curdate, curdate + period, _leaverequests, value)
-                            || CheckRoster(employee, curdate, curdate + period, standardHours, value);
-
-                        values.Add(value);
+                        values.Add(dataValues[dateIdx, empIdx]);
                     }
 
                     data.Rows.Add(values.ToArray());

+ 439 - 92
prs.desktop/Panels/JobPlanner/JobResourcePlanner.xaml.cs

@@ -23,6 +23,9 @@ using Syncfusion.Windows.Tools.Controls;
 using SelectionChangedEventArgs = System.Windows.Controls.SelectionChangedEventArgs;
 using PRS.Shared;
 using Columns = InABox.Core.Columns;
+using InABox.Wpf;
+using Syncfusion.Data.Extensions;
+using NPOI.OpenXmlFormats.Spreadsheet;
 
 namespace PRSDesktop;
 
@@ -58,7 +61,25 @@ public class JobResourcePlannerProperties : IUserConfigurationSettings, IDashboa
     }
 }
 
-public partial class JobResourcePlanner : UserControl
+public enum JobPlannerDisplayMode
+{
+    JobColumns,
+    DateColumns
+}
+
+public class JobResourcePlannerSettings : BaseObject, IGlobalConfigurationSettings
+{
+    [EnumLookupEditor(typeof(JobPlannerDisplayMode))]
+    [EditorSequence(1)]
+    public JobPlannerDisplayMode DisplayMode { get; set; }
+
+    public JobResourcePlannerSettings()
+    {
+        DisplayMode = JobPlannerDisplayMode.DateColumns;
+    }
+}
+
+public partial class JobResourcePlanner : UserControl, IPropertiesPanel<JobResourcePlannerSettings>
 {
     private enum Suppress
     {
@@ -74,11 +95,23 @@ public partial class JobResourcePlanner : UserControl
     private EmployeeResourceModel[] _emps = new EmployeeResourceModel[] { };
 
     private List<AssignmentModel> _assignments = new List<AssignmentModel>();
+
+    private DateTime[] _dates = Array.Empty<DateTime>();
+    private double[] _totals = Array.Empty<double>();
+    private double[] _available = Array.Empty<double>();
     
     public JobResourcePlannerProperties Properties { get; set; }
     public event LoadSettings<JobResourcePlannerProperties>? LoadSettings;
     public event SaveSettings<JobResourcePlannerProperties>? SaveSettings;
 
+    JobResourcePlannerSettings IPropertiesPanel<JobResourcePlannerSettings>.Properties
+    {
+        get => Settings;
+        set => Settings = value;
+    }
+
+    private JobResourcePlannerSettings Settings { get; set; }
+
     private void DoLoadSettings()
     {
         Properties = LoadSettings?.Invoke(this);
@@ -265,31 +298,34 @@ public partial class JobResourcePlanner : UserControl
             var empids = _emps.Select(x => x.ID).ToArray();
             var actids = _activities.Select(x => x.ID).ToArray();
 
-            DateTime todate = DateTime.Today.AddMonths(Properties.MonthsToView);
+            DateTime fromDate = DateTime.Today;
+            DateTime toDate = DateTime.Today.AddMonths(Properties.MonthsToView);
             
             MultiQuery query = new MultiQuery();
 
             query.Add<Assignment>(
                 new Filter<Assignment>(x=>x.EmployeeLink.ID).InList(empids)
-                    .And(x=>x.Date).IsGreaterThanOrEqualTo(DateTime.Today)
-                    .And(x=>x.Date).IsLessThanOrEqualTo(DateTime.Today.AddMonths(Properties.MonthsToView)),
+                    .And(x=>x.Date).IsGreaterThanOrEqualTo(fromDate)
+                    .And(x=>x.Date).IsLessThanOrEqualTo(toDate),
                 AssignmentModel.Columns
             );
             query.Query();
             _assignments = query.Get<Assignment>().Rows.Select(r => new AssignmentModel(r)).ToList();
 
             var data = new DataTable();
-            data.Columns.Add("Date", typeof(DateTime));
-            data.PrimaryKey = new System.Data.DataColumn[] { data.Columns[0] };
-            
-            data.Columns.Add("Total", typeof(object));
-            data.Columns.Add("Available", typeof(object));
-            
-            foreach (var job in _jobs)
-                data.Columns.Add(job.ID.ToString(), typeof(object));
-            
-            DateTime date = DateTime.Today;
-            while (date <= DateTime.Today.AddMonths(Properties.MonthsToView))
+
+            var dates = new List<DateTime>();
+            for (var curdate = fromDate; curdate <= toDate; curdate = curdate.AddDays(1))
+            {
+                dates.Add(curdate);
+            }
+            _dates = dates.ToArray();
+
+            var dataValues = new object?[dates.Count, _jobs.Length];
+            var available = new double[dates.Count];
+            var totals = new double[dates.Count];
+
+            foreach (var (dateIdx, date) in dates.WithIndex())
             {
                 double avail = 0.0F;
 
@@ -308,57 +344,188 @@ public partial class JobResourcePlanner : UserControl
 
                 }
                 
-                double total = avail;
+                var total = avail;
                 
                 var values = new List<object?> { date };
                 
                 var anyjobstoday = _assignments.Where(x => (x.Date.Date == date.Date)); 
                 avail -= anyjobstoday.Aggregate<AssignmentModel, double>(0F, (value, model) => value + model.BookedDuration.TotalHours);
 
-                foreach (var job in _jobs)
+                foreach (var (jobIdx, job) in _jobs.WithIndex())
                 {
                     var thisjobtoday = _assignments.Where(x => (x.Date.Date == date.Date) && (x.JobID == job.ID));
                     if (thisjobtoday.Any())
                     {
                         var assigned = thisjobtoday.Aggregate<AssignmentModel, double>(0F,
                             (value, model) => value + model.BookedDuration.TotalHours);
-                        values.Add(assigned / Properties.HoursPerDay);
+                        dataValues[dateIdx, jobIdx] = assigned / Properties.HoursPerDay;
                     }
                     else
-                        values.Add(null);
+                        dataValues[dateIdx, jobIdx] = null;
+                }
+
+                available[dateIdx] = avail / Properties.HoursPerDay;
+                totals[dateIdx] = total / Properties.HoursPerDay;
+            }
+            _totals = totals;
+            _available = available;
+
+            if(Settings.DisplayMode == JobPlannerDisplayMode.JobColumns)
+            {
+                data.Columns.Add("Date", typeof(DateTime));
+                
+                data.Columns.Add("Available", typeof(object));
+                
+                foreach (var job in _jobs)
+                    data.Columns.Add(job.ID.ToString(), typeof(object));
+                
+                foreach(var (dateIdx, date) in dates.WithIndex())
+                {
+                    var values = new List<object?>(_jobs.Length + 3) { date };
+                    
+                    foreach (var (jobIdx, job) in _jobs.WithIndex())
+                    {
+                        values.Add(dataValues[dateIdx, jobIdx]);
+                    }
+
+                    values.Insert(1, available[dateIdx]);
+
+                    data.Rows.Add(values.ToArray());
                 }
+            }
+            else if(Settings.DisplayMode == JobPlannerDisplayMode.DateColumns)
+            {
+                data.Columns.Add("Job", typeof(object));
+
+                var availableRow = new List<object?> { "Available" };
+                foreach(var (dateIdx, date) in dates.WithIndex())
+                {
+                    data.Columns.Add(dateIdx.ToString(), typeof(object));
+                    availableRow.Add(available[dateIdx]);
+                }
+                data.Rows.Add(availableRow.ToArray());
+
+                foreach (var (jobIdx, job) in _jobs.WithIndex())
+                {
+                    var values = new List<object?>(dates.Count + 1) { job };
 
-                values.Insert(1,avail / Properties.HoursPerDay);
-                values.Insert(1,total / Properties.HoursPerDay);
+                    foreach(var (dateIdx, date) in dates.WithIndex())
+                    {
+                        values.Add(dataValues[dateIdx, jobIdx]);
+                    }
 
-                data.Rows.Add(values.ToArray());
-                date = date.AddDays(1);
+                    data.Rows.Add(values.ToArray());
+                }
             }
 
             dataGrid.ItemsSource = data;
         }
     }
+
+    private interface ICellWrapper
+    {
+        DataRowView Row { get; }
+        
+        string ColumnName { get; }
+    }
+
+    public class GridCellInfoWrapper : ICellWrapper
+    {
+        private GridCellInfo Cell;
+
+        public DataRowView Row => (Cell.RowData as DataRowView)!;
+
+        public string ColumnName => (Cell.Column.ValueBinding as Binding)!.Path.Path; 
+
+        public GridCellInfoWrapper(GridCellInfo cell)
+        {
+            Cell = cell;
+        }
+    }
+
+    public class GridCellWrapper : ICellWrapper
+    {
+        private GridCell Cell;
+
+        public DataRowView Row => (Cell.DataContext as DataRowView)!;
+
+        public string ColumnName => (Cell.ColumnBase?.GridColumn.ValueBinding as Binding)?.Path.Path ?? ""; 
+
+        public GridCellWrapper(GridCell cell)
+        {
+            Cell = cell;
+        }
+    }
+
+    private int? GetDateIndex(ICellWrapper cell)
+    {
+        if(Settings.DisplayMode == JobPlannerDisplayMode.JobColumns)
+        {
+            return _dates.IndexOf((DateTime)cell.Row.Row.ItemArray.First()!);
+        }
+        else
+        {
+            if(int.TryParse(cell.ColumnName, out var idx))
+            {
+                return idx;
+            }
+            else
+            {
+                return null;
+            }
+        }
+    }
+
+    private double? GetTotal(ICellWrapper cell)
+    {
+        var dateIndex = GetDateIndex(cell);
+        return dateIndex.HasValue ? _totals[dateIndex.Value] : null;
+    }
+    private double? GetAvailable(ICellWrapper cell)
+    {
+        if(Settings.DisplayMode == JobPlannerDisplayMode.JobColumns)
+        {
+            var avail = cell.Row["Available"];
+            if(avail is double d)
+            {
+                return d;
+            }
+            else
+            {
+                return null;
+            }
+        }
+        else
+        {
+            var dateIndex = GetDateIndex(cell);
+            return dateIndex.HasValue ? _available[dateIndex.Value] : null;
+        }
+    }
     
     private abstract class LeaveConverter : IMultiValueConverter
     {
+        protected JobResourcePlanner Planner { get; set; }
+        public LeaveConverter(JobResourcePlanner planner)
+        {
+            Planner = planner;
+        }
 
         protected System.Windows.Media.Color GetColor(object[] value)
         {
-            if ((value[0] != DBNull.Value) && (value[0] != DependencyProperty.UnsetValue))
+            if ((value[0] != DBNull.Value) && (value[0] != DependencyProperty.UnsetValue) && (value[0] is not double d || d != 0.0))
                 return Colors.DarkSeaGreen;
             if (value[1] is GridCell cell)
             {
-                if (cell.DataContext is DataRowView rowview)
-                {
-                    var total = rowview.Row["Total"];
-                    if ((total == DBNull.Value) || ((double)total == 0.0F))
-                        return Colors.LightGray;
-
-                    var avail = rowview.Row["Available"];
-                    if ((avail == DBNull.Value) || ((double)avail == 0.0F))
-                        return Colors.LightSalmon;
-                    return Colors.LightYellow;
-                }
+                var cellWrp = new GridCellWrapper(cell);
+                var total = Planner.GetTotal(cellWrp);
+                var available = Planner.GetAvailable(cellWrp);
+
+                if (!total.HasValue || total == 0.0)
+                    return Colors.LightGray;
+
+                if (!available.HasValue || available == 0.0F)
+                    return Colors.LightSalmon;
+                return Colors.LightYellow;
             }
 
             return Colors.WhiteSmoke;
@@ -374,6 +541,8 @@ public partial class JobResourcePlanner : UserControl
     
     private class LeaveBackgroundConverter : LeaveConverter
     {
+        public LeaveBackgroundConverter(JobResourcePlanner planner): base(planner) { }
+
         public override object Convert(object[] value, Type targetType, object parameter, CultureInfo culture)
         {
             return new SolidColorBrush(base.GetColor(value)) { Opacity = 0.8 };
@@ -382,6 +551,8 @@ public partial class JobResourcePlanner : UserControl
 
     private class LeaveForegroundConverter : LeaveConverter
     {
+        public LeaveForegroundConverter(JobResourcePlanner planner): base(planner) { }
+
         public override object Convert(object[] value, Type targetType, object parameter, CultureInfo culture)
         {
             return new SolidColorBrush(ImageUtils.GetForegroundColor(base.GetColor(value)));
@@ -390,12 +561,12 @@ public partial class JobResourcePlanner : UserControl
     
    private void DataGrid_AutoGeneratingColumn(object sender, AutoGeneratingColumnArgs e)
     {
-        MultiBinding CreateBinding<TConverter>(String path) where TConverter : IMultiValueConverter, new()
+        MultiBinding CreateBinding<TConverter>(String path, TConverter converter) where TConverter : IMultiValueConverter
         {
             var binding = new MultiBinding();
             binding.Bindings.Add(new Binding(path));
             binding.Bindings.Add(new Binding() { RelativeSource = new RelativeSource(RelativeSourceMode.Self) });
-            binding.Converter = new TConverter();
+            binding.Converter = converter;
             return binding;
         }
 
@@ -407,9 +578,17 @@ public partial class JobResourcePlanner : UserControl
             e.Column.HeaderStyle = Resources["DateHeaderStyle"] as Style;
             e.Column.AllowFocus = false;
         }
-        else if (value.Path.Path.Equals("Total"))
+        else if (value.Path.Path.Equals("Job"))
         {
-            e.Cancel = true;
+            e.Column.Width = 200;
+            e.Column.HeaderStyle = Resources["DateHeaderStyle"] as Style;
+            e.Column.AllowFocus = false;
+            e.Column.TextAlignment = TextAlignment.Left;
+            e.Column.DisplayBinding = new Binding
+            {
+                Path = new PropertyPath(e.Column.MappingName),
+                Converter = new FuncConverter<object?, object?>(x => x is JobModel job ? job.Name : x)
+            };
         }
         else if (value.Path.Path.Equals("Available"))
         {
@@ -425,17 +604,27 @@ public partial class JobResourcePlanner : UserControl
             e.Column = new GridNumericColumn() { NumberDecimalDigits = 2, MappingName = e.Column.MappingName};
             e.Column.Width = 40;
             e.Column.HeaderStyle = Resources["ContentHeaderStyle"] as Style;
-            var jobid = Guid.Parse(value.Path.Path);
-            e.Column.HeaderText = _jobs.FirstOrDefault(x=>x.ID == jobid)?.Name ?? jobid.ToString();
+
+            if(Settings.DisplayMode == JobPlannerDisplayMode.JobColumns)
+            {
+                e.Column.HeaderText = (Guid.TryParse(value.Path.Path, out var id)
+                    ? _jobs.FirstOrDefault(x => x.ID == id)?.Name ?? value.Path.Path
+                    : value.Path.Path);
+            }
+            else
+            {
+                if(int.TryParse(value.Path.Path, out var idx))
+                {
+                    e.Column.HeaderText = _dates[idx].ToString("dd/MM/yyyy");
+                }
+            }
 
             e.Column.DisplayBinding = new Binding { Path = new PropertyPath(e.Column.MappingName) };
             e.Column.AllowFocus = true;
             var style = new Style(typeof(GridCell));
-            style.Setters.Add(new Setter(BackgroundProperty, CreateBinding<LeaveBackgroundConverter>(value.Path.Path)));
-            style.Setters.Add(new Setter(ForegroundProperty, CreateBinding<LeaveForegroundConverter>(value.Path.Path)));
+            style.Setters.Add(new Setter(BackgroundProperty, CreateBinding<LeaveBackgroundConverter>(value.Path.Path, new(this))));
+            style.Setters.Add(new Setter(ForegroundProperty, CreateBinding<LeaveForegroundConverter>(value.Path.Path, new(this))));
             e.Column.CellStyle = style;
-            
-
         }
         e.Column.TextAlignment = TextAlignment.Center;
         e.Column.HorizontalHeaderContentAlignment = HorizontalAlignment.Center;
@@ -482,31 +671,102 @@ public partial class JobResourcePlanner : UserControl
 
     }
 
-    private bool ExtractSelection(out Guid jobid, out DateTime[] dates)
+    private void GetCellData(ICellWrapper cell, out (Guid jobID, DateTime date) result)
     {
-        var selected = dataGrid.SelectionController.SelectedCells;
-        var colname = selected.Select(x => x.Column.MappingName).Distinct().FirstOrDefault();
-        if (Guid.TryParse(colname, out Guid job))
+        result = (Guid.Empty, DateTime.MinValue);
+
+        if(Settings.DisplayMode == JobPlannerDisplayMode.JobColumns)
+        {
+            if (Guid.TryParse(cell.ColumnName, out var emp))
+            {
+                result.jobID = emp;
+            }
+
+            result.date = (DateTime)cell.Row.Row.ItemArray.First()!;
+        }
+        else
+        {
+            if(int.TryParse(cell.ColumnName, out var idx))
+            {
+                result.date = _dates[idx];
+            }
+            else
+            {
+                result.date = DateTime.MinValue;
+            }
+
+            var jobModel = cell.Row.Row.ItemArray.First() as JobModel;
+            result.jobID = jobModel?.ID ?? Guid.Empty;
+        }
+    }
+    private ICellWrapper Wrap(GridCellInfo cell)
+    {
+        return new GridCellInfoWrapper(cell);
+    }
+    private ICellWrapper Wrap(GridCell cell)
+    {
+        return new GridCellWrapper(cell);
+    }
+
+    private bool ExtractSelection(out Guid[] jobs, out DateTime[] dates)
+    {
+        var from = DateTime.MaxValue;
+        var to = DateTime.MinValue;
+
+        var jobList = new List<Guid>();
+
+        foreach (var cell in dataGrid.GetSelectedCells())
+        {
+            GetCellData(Wrap(cell), out var result);
+            if (result.jobID == Guid.Empty) continue;
+
+            if (!jobList.Contains(result.jobID))
+                jobList.Add(result.jobID);
+
+            if(result.date != DateTime.MinValue)
+            {
+                if (result.date < from)
+                    from = result.date;
+                if (result.date > to)
+                    to = result.date;
+            }
+        }
+        if(jobList.Count > 0 && to != DateTime.MinValue && from != DateTime.MaxValue)
         {
-            jobid = job;
-            dates = selected.Select(x => ((DataRowView)x.RowData).Row["Date"]).Cast<DateTime>().ToArray();
+            jobs = jobList.ToArray();
+            var datesList = new List<DateTime>();
+            for(DateTime date = from; date <= to; date = date.AddDays(1))
+            {
+                datesList.Add(date);
+            }
+            dates = datesList.ToArray();
             return true;
         }
         else
         {
-            jobid = Guid.Empty;
-            dates = new DateTime[] { };
+            jobs = [];
+            dates = [];
             return false;
         }
     }
     
     private void DataGrid_OnMouseUp(object sender, MouseButtonEventArgs e)
     {
-        if (ExtractSelection(out Guid jobid, out DateTime[] dates))
+        if (ExtractSelection(out var jobIDs, out var dates))
         {
-            LoadAssignedEmployees(jobid, dates);
-            LoadAvailableEmployees(dates);
-        }            
+            if(jobIDs.Length == 1)
+            {
+                LoadAssignedEmployees(jobIDs, dates);
+                LoadAvailableEmployees(dates);
+            }
+            else
+            {
+                AvailableEmployees.Items = [];
+                AssignedEmployees.Items = [];
+                AvailableEmployees.Refresh(false, true);
+                AssignedEmployees.Refresh(false, true);
+            }
+        }
     }
     
     private JobPlannerEmployee GetEmployee(Guid id, List<JobPlannerEmployee> list)
@@ -551,11 +811,11 @@ public partial class JobResourcePlanner : UserControl
         }
     }
     
-    private void LoadAssignedEmployees(Guid jobid, DateTime[] dates)
+    private void LoadAssignedEmployees(Guid[] jobIDs, DateTime[] dates)
     {
         List<JobPlannerEmployee> assignedemployees = new List<JobPlannerEmployee>();
-        foreach (var assignment in _assignments.Where(x => dates.Contains(x.Date) && (x.JobID == jobid)))
-            GetEmployee(assignment.EmployeeID,assignedemployees).Time += assignment.BookedDuration;
+        foreach (var assignment in _assignments.Where(x => dates.Contains(x.Date) && jobIDs.Contains(x.JobID)))
+            GetEmployee(assignment.EmployeeID, assignedemployees).Time += assignment.BookedDuration;
         AssignedEmployees.Items = assignedemployees.OrderBy(x=>x.Name).ToList();
         AssignedEmployees.Refresh(false, true);
     }
@@ -696,7 +956,7 @@ public partial class JobResourcePlanner : UserControl
             return false;                
         }
         
-        if (ExtractSelection(out Guid jobid, out DateTime[] dates))
+        if (ExtractSelection(out var jobIDs, out var dates))
         {
 
             if (dataGrid.ItemsSource is DataTable table)
@@ -707,6 +967,8 @@ public partial class JobResourcePlanner : UserControl
                 {
                     foreach (var date in dates)
                     {
+                        var dateIdx = _dates.IndexOf(date);
+
                         var emp = _emps.FirstOrDefault(x => x.ID == available.ID);
                         if (emp != null)
                         {
@@ -725,8 +987,8 @@ public partial class JobResourcePlanner : UserControl
                             foreach (var assignment in assignments)
                                 CheckEdges(assignment.BookedStart, assignment.BookedFinish);
                             edges.Sort();
-                            
-                            double adjustment = 0.0F;
+
+                            var adjustment = new double[jobIDs.Length];
                             for (int i = 0; i < edges.Count - 1; i++)
                             {
                                 var start = edges[i];
@@ -737,26 +999,70 @@ public partial class JobResourcePlanner : UserControl
                                     && (!IsLeaveRequest(date,emp,start,finish))
                                     && !IsAssigned(assignments, start, finish))
                                 {
-                                    Assignment assignment = new Assignment();
-                                    assignment.ActivityLink.ID = Properties.ActivityType;
-                                    assignment.EmployeeLink.ID = emp.ID;
-                                    assignment.Date = date;
-                                    assignment.JobLink.ID = jobid;
-                                    assignment.Booked.Start = start;
-                                    assignment.Booked.Finish = finish;
-                                    assignment.Booked.Duration = finish - start;
-                                    updates.Add(assignment);
-                                    adjustment += assignment.Booked.Duration.TotalHours;
+                                    foreach(var (idx, jobid) in jobIDs.WithIndex())
+                                    {
+                                        Assignment assignment = new Assignment();
+                                        assignment.ActivityLink.ID = Properties.ActivityType;
+                                        assignment.EmployeeLink.ID = emp.ID;
+                                        assignment.Date = date;
+                                        assignment.JobLink.ID = jobid;
+                                        assignment.Booked.Start = start;
+                                        assignment.Booked.Finish = finish;
+                                        assignment.Booked.Duration = finish - start;
+                                        updates.Add(assignment);
+                                        adjustment[idx] += assignment.Booked.Duration.TotalHours;
+                                    }
+                                }
+                            }
+                            if(Settings.DisplayMode == JobPlannerDisplayMode.JobColumns)
+                            {
+                                System.Data.DataRow row = table.Rows[dateIdx];
+                                row.BeginEdit();
+                                foreach(var (idx, jobid) in jobIDs.WithIndex())
+                                {
+                                    var adj = adjustment[idx];
+
+                                    _available[dateIdx] = Math.Max(0F, _available[dateIdx] - (adj / Properties.HoursPerDay));
+                                    row["Available"] = _available[dateIdx];
+
+                                    double jobvalue = (row[jobid.ToString()] == DBNull.Value)
+                                        ? adj / Properties.HoursPerDay
+                                        : (double)row[jobid.ToString()] + (adj / Properties.HoursPerDay);
+                                    row[jobid.ToString()] = jobvalue <= 0F ? null : jobvalue;
+                                }
+                                row.EndEdit();
+                            }
+                            else
+                            {
+                                var availableRow = table.Rows[0];
+                                availableRow.BeginEdit();
+
+                                foreach(var (idx, jobid) in jobIDs.WithIndex())
+                                {
+                                    var job = _jobs.WithIndex().First(x => x.Value.ID == jobid);
+                                    var row = table.Rows[job.Key + 1];
+
+                                    var adj = adjustment[idx];
+
+                                    _available[dateIdx] = Math.Max(0F, _available[dateIdx] - (adj / Properties.HoursPerDay));
+                                    availableRow[dateIdx.ToString()] = _available[dateIdx];
+
+                                    row.BeginEdit();
+                                    double jobvalue = (row[dateIdx.ToString()] == DBNull.Value)
+                                        ? adj / Properties.HoursPerDay
+                                        : (double)row[dateIdx.ToString()] + (adj / Properties.HoursPerDay);
+                                    row[dateIdx.ToString()] = jobvalue <= 0F ? null : jobvalue;
+                                    row.EndEdit();
+                                }
+                                availableRow.EndEdit();
+
+                                foreach(var row in table.Rows.Cast<System.Data.DataRow>())
+                                {
+                                    row.BeginEdit();
+                                    row[dateIdx.ToString()] = row[dateIdx.ToString()];
+                                    row.EndEdit();
                                 }
                             }
-                            System.Data.DataRow row = table.Rows.Find(date);
-                            row.BeginEdit();
-                            double jobvalue = (row[jobid.ToString()] == DBNull.Value)
-                                ? adjustment / Properties.HoursPerDay
-                                : (double)row[jobid.ToString()] + (adjustment / Properties.HoursPerDay);
-                            row[jobid.ToString()] = jobvalue <= 0F ? null : jobvalue;
-                            row["Available"] = Math.Max(0F, (double)row["Available"] - (adjustment / Properties.HoursPerDay));
-                            row.EndEdit();
                         }
                     }
 
@@ -789,31 +1095,72 @@ public partial class JobResourcePlanner : UserControl
 
     private void AssignedEmployees_OnOnAction(object sender, JobPlannerEmployee[] employees)
     {
-        if (ExtractSelection(out Guid jobid, out DateTime[] dates))
+        if (ExtractSelection(out var jobIDs, out var dates))
         {
             if (dataGrid.ItemsSource is DataTable table)
             {
                 foreach (var date in dates)
                 {
+                    var dateIdx = _dates.IndexOf(date);
+
                     var emptimes = _assignments.Where(x =>
-                        (x.JobID == jobid)
+                        jobIDs.Contains(x.JobID)
                         && (x.Date == date)
                         && employees.Any(e => e.ID == x.EmployeeID)
                     ).ToArray();
                     var emptime = emptimes.Aggregate(TimeSpan.Zero, (time, ass) => time += ass.BookedDuration);
-                    System.Data.DataRow row = table.Rows.Find(date);
-                    row.BeginEdit();
-                    double value = (row[jobid.ToString()] == DBNull.Value)
-                        ? 0.0F
-                        : (double)row[jobid.ToString()] - (emptime.TotalHours / Properties.HoursPerDay);
-                    row[jobid.ToString()] = value <= 0F ? null : value;
-                    row["Available"] = (double)row["Available"] + (emptime.TotalHours/Properties.HoursPerDay);
-                    row.EndEdit();
+                    if(Settings.DisplayMode == JobPlannerDisplayMode.JobColumns)
+                    {
+                        System.Data.DataRow row = table.Rows[dateIdx];
+                        row.BeginEdit();
+                        foreach(var jobid in jobIDs)
+                        {
+                            _available[dateIdx] = _available[dateIdx] + (emptime.TotalHours / Properties.HoursPerDay);
+                            row["Available"] = _available[dateIdx];
+
+                            double value = (row[jobid.ToString()] == DBNull.Value)
+                                ? 0.0F
+                                : (double)row[jobid.ToString()] - (emptime.TotalHours / Properties.HoursPerDay);
+                            row[jobid.ToString()] = value <= 0F ? null : value;
+                        }
+                        row.EndEdit();
+                    }
+                    else
+                    {
+                        var availableRow = table.Rows[0];
+                        availableRow.BeginEdit();
+
+                        foreach(var jobid in jobIDs)
+                        {
+                            var jobIdx = _jobs.WithIndex().First(x => x.Value.ID == jobid).Key;
+
+                            _available[dateIdx] = _available[dateIdx] + (emptime.TotalHours/Properties.HoursPerDay);
+                            availableRow[dateIdx.ToString()] = _available[dateIdx];
+
+                            var row = table.Rows[jobIdx + 1];
+                            row.BeginEdit();
+
+                            double value = (row[dateIdx.ToString()] == DBNull.Value)
+                                ? 0.0F
+                                : (double)row[dateIdx.ToString()] - (emptime.TotalHours / Properties.HoursPerDay);
+                            row[dateIdx.ToString()] = value <= 0F ? null : value;
+                            row.EndEdit();
+                        }
+
+                        availableRow.EndEdit();
+
+                        foreach(var row in table.Rows.Cast<System.Data.DataRow>())
+                        {
+                            row.BeginEdit();
+                            row[dateIdx.ToString()] = row[dateIdx.ToString()];
+                            row.EndEdit();
+                        }
+                    }
                 }
             }
             
             var assignments = _assignments.Where(x =>
-                (x.JobID == jobid)
+                jobIDs.Contains(x.JobID)
                 && dates.Contains(x.Date)
                 && employees.Any(e => e.ID == x.EmployeeID)
             ).ToArray();