|
@@ -2,12 +2,11 @@
|
|
using InABox.Clients;
|
|
using InABox.Clients;
|
|
using InABox.Core;
|
|
using InABox.Core;
|
|
using InABox.WPF;
|
|
using InABox.WPF;
|
|
-using NPOI.SS.Formula.Functions;
|
|
|
|
-using Syncfusion.UI.Xaml.Kanban;
|
|
|
|
using System;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Linq;
|
|
|
|
+using System.Linq.Expressions;
|
|
using System.Reflection;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using System.Threading.Tasks;
|
|
@@ -16,14 +15,23 @@ using System.Windows.Controls;
|
|
using System.Windows.Data;
|
|
using System.Windows.Data;
|
|
using System.Windows.Documents;
|
|
using System.Windows.Documents;
|
|
using System.Windows.Input;
|
|
using System.Windows.Input;
|
|
|
|
+using System.Drawing;
|
|
using System.Windows.Media;
|
|
using System.Windows.Media;
|
|
using System.Windows.Media.Imaging;
|
|
using System.Windows.Media.Imaging;
|
|
-using System.Windows.Navigation;
|
|
|
|
-using System.Windows.Shapes;
|
|
|
|
|
|
+using Color = System.Drawing.Color;
|
|
|
|
+using InABox.DynamicGrid;
|
|
|
|
+using InABox.Wpf;
|
|
|
|
+using MailKit.Search;
|
|
|
|
+using NPOI.SS.Formula.Functions;
|
|
|
|
+using System.ComponentModel;
|
|
|
|
+using System.Runtime.CompilerServices;
|
|
|
|
+using System.Collections.ObjectModel;
|
|
|
|
+using System.Collections.Specialized;
|
|
|
|
+using Syncfusion.UI.Xaml.Kanban;
|
|
|
|
|
|
namespace PRSDesktop;
|
|
namespace PRSDesktop;
|
|
|
|
|
|
-public class TasksByStatusColumn
|
|
|
|
|
|
+public class TasksByStatusColumn : INotifyPropertyChanged
|
|
{
|
|
{
|
|
public string Category { get; }
|
|
public string Category { get; }
|
|
|
|
|
|
@@ -33,12 +41,34 @@ public class TasksByStatusColumn
|
|
|
|
|
|
public double NumHours { get => Tasks.Sum(x => x.EstimatedTime.TotalHours); }
|
|
public double NumHours { get => Tasks.Sum(x => x.EstimatedTime.TotalHours); }
|
|
|
|
|
|
- public List<TaskModel> Tasks { get; }
|
|
|
|
|
|
+ public ObservableCollection<TaskModel> Tasks { get; } = new();
|
|
|
|
|
|
public TasksByStatusColumn(string category, string title)
|
|
public TasksByStatusColumn(string category, string title)
|
|
{
|
|
{
|
|
Category = category;
|
|
Category = category;
|
|
Title = title;
|
|
Title = title;
|
|
|
|
+
|
|
|
|
+ Tasks.CollectionChanged += Tasks_CollectionChanged;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private void Tasks_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
|
|
|
+ {
|
|
|
|
+ OnPropertyChanged(nameof(Tasks));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public event PropertyChangedEventHandler? PropertyChanged;
|
|
|
|
+
|
|
|
|
+ // Create the OnPropertyChanged method to raise the event
|
|
|
|
+ // The calling member's name will be used as the parameter.
|
|
|
|
+ protected void OnPropertyChanged([CallerMemberName] string? name = null)
|
|
|
|
+ {
|
|
|
|
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
|
|
|
|
+
|
|
|
|
+ if(name == nameof(Tasks))
|
|
|
|
+ {
|
|
|
|
+ OnPropertyChanged(nameof(NumTasks));
|
|
|
|
+ OnPropertyChanged(nameof(NumHours));
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -61,13 +91,50 @@ public class EmployeeModel
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+public class SuspendableObservableCollection<T> : ObservableCollection<T>
|
|
|
|
+{
|
|
|
|
+ private bool _notificationSupressed = false;
|
|
|
|
+ private bool _supressNotification = false;
|
|
|
|
+ public bool SupressNotification
|
|
|
|
+ {
|
|
|
|
+ get
|
|
|
|
+ {
|
|
|
|
+ return _supressNotification;
|
|
|
|
+ }
|
|
|
|
+ set
|
|
|
|
+ {
|
|
|
|
+ _supressNotification = value;
|
|
|
|
+ if (_supressNotification == false && _notificationSupressed)
|
|
|
|
+ {
|
|
|
|
+ this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
|
|
|
+ _notificationSupressed = false;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
|
|
|
+ {
|
|
|
|
+ if (SupressNotification)
|
|
|
|
+ {
|
|
|
|
+ _notificationSupressed = true;
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ base.OnCollectionChanged(e);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
/// Interaction logic for TasksByStatusControl.xaml
|
|
/// Interaction logic for TasksByStatusControl.xaml
|
|
/// </summary>
|
|
/// </summary>
|
|
-public partial class TasksByStatusControl : UserControl, ITaskControl
|
|
|
|
|
|
+public partial class TasksByStatusControl : UserControl, ITaskControl, INotifyPropertyChanged
|
|
{
|
|
{
|
|
- public List<TasksByStatusColumn> Columns = new();
|
|
|
|
|
|
+ private enum Suppress
|
|
|
|
+ {
|
|
|
|
+ This
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public SuspendableObservableCollection<TasksByStatusColumn> Columns { get; private set; } = new();
|
|
|
|
|
|
private List<EmployeeModel> Employees = new();
|
|
private List<EmployeeModel> Employees = new();
|
|
|
|
|
|
@@ -76,11 +143,24 @@ public partial class TasksByStatusControl : UserControl, ITaskControl
|
|
InitializeComponent();
|
|
InitializeComponent();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ #region INotifyPropertyChanged
|
|
|
|
+
|
|
|
|
+ public event PropertyChangedEventHandler? PropertyChanged;
|
|
|
|
+
|
|
|
|
+ // Create the OnPropertyChanged method to raise the event
|
|
|
|
+ // The calling member's name will be used as the parameter.
|
|
|
|
+ protected void OnPropertyChanged([CallerMemberName] string? name = null)
|
|
|
|
+ {
|
|
|
|
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ #endregion
|
|
|
|
+
|
|
#region Setup
|
|
#region Setup
|
|
|
|
|
|
public void Setup()
|
|
public void Setup()
|
|
{
|
|
{
|
|
- var employeesTask = LoadEmployees();
|
|
|
|
|
|
+ var employeesTask = Task.Run(() => LoadEmployees());
|
|
var kanbanTypesTask = Task.Run(() => LoadKanbanTypes());
|
|
var kanbanTypesTask = Task.Run(() => LoadKanbanTypes());
|
|
|
|
|
|
SetupToolbar();
|
|
SetupToolbar();
|
|
@@ -92,6 +172,8 @@ public partial class TasksByStatusControl : UserControl, ITaskControl
|
|
|
|
|
|
kanbanTypesTask.Wait();
|
|
kanbanTypesTask.Wait();
|
|
SetupKanbanTypesLookup(kanbanTypesTask.Result);
|
|
SetupKanbanTypesLookup(kanbanTypesTask.Result);
|
|
|
|
+
|
|
|
|
+ Mode = Host.KanbanSettings.StatusSettings.CompactView ? KanbanViewMode.Compact : KanbanViewMode.Full;
|
|
}
|
|
}
|
|
|
|
|
|
private void SetupToolbar()
|
|
private void SetupToolbar()
|
|
@@ -108,6 +190,8 @@ public partial class TasksByStatusControl : UserControl, ITaskControl
|
|
|
|
|
|
private void SetupColumns()
|
|
private void SetupColumns()
|
|
{
|
|
{
|
|
|
|
+ Columns.SupressNotification = true;
|
|
|
|
+
|
|
Columns.Clear();
|
|
Columns.Clear();
|
|
|
|
|
|
Columns.Add(new TasksByStatusColumn("Open", "To Do"));
|
|
Columns.Add(new TasksByStatusColumn("Open", "To Do"));
|
|
@@ -118,6 +202,13 @@ public partial class TasksByStatusControl : UserControl, ITaskControl
|
|
|
|
|
|
if (Host.KanbanSettings.StatusSettings.IncludeCompleted)
|
|
if (Host.KanbanSettings.StatusSettings.IncludeCompleted)
|
|
Columns.Add(new TasksByStatusColumn("Complete", "Completed"));
|
|
Columns.Add(new TasksByStatusColumn("Complete", "Completed"));
|
|
|
|
+
|
|
|
|
+ Columns.SupressNotification = false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private TasksByStatusColumn? GetColumn(string category)
|
|
|
|
+ {
|
|
|
|
+ return Columns.FirstOrDefault(x => x.Category.Equals(category));
|
|
}
|
|
}
|
|
|
|
|
|
private void Column_ContextMenuOpening(object sender, ContextMenuEventArgs e)
|
|
private void Column_ContextMenuOpening(object sender, ContextMenuEventArgs e)
|
|
@@ -205,7 +296,7 @@ public partial class TasksByStatusControl : UserControl, ITaskControl
|
|
|
|
|
|
private EmployeeModel SelectedEmployee;
|
|
private EmployeeModel SelectedEmployee;
|
|
|
|
|
|
- private async Task LoadEmployees()
|
|
|
|
|
|
+ private void LoadEmployees()
|
|
{
|
|
{
|
|
Employees.Clear();
|
|
Employees.Clear();
|
|
|
|
|
|
@@ -215,20 +306,22 @@ public partial class TasksByStatusControl : UserControl, ITaskControl
|
|
.Or(x => x.FinishDate).IsGreaterThan(DateTime.Today))
|
|
.Or(x => x.FinishDate).IsGreaterThan(DateTime.Today))
|
|
: new Filter<Employee>(x => x.ID).IsEqualTo(App.EmployeeID);
|
|
: new Filter<Employee>(x => x.ID).IsEqualTo(App.EmployeeID);
|
|
|
|
|
|
- var employees = await Task.Run(() => Client.Query<Employee>(
|
|
|
|
|
|
+ var employees = Client.Query<Employee>(
|
|
employeeFilter,
|
|
employeeFilter,
|
|
new Columns<Employee>(x => x.ID)
|
|
new Columns<Employee>(x => x.ID)
|
|
.Add(x => x.Thumbnail.ID)
|
|
.Add(x => x.Thumbnail.ID)
|
|
- .Add(x => x.Name)));
|
|
|
|
|
|
+ .Add(x => x.Name));
|
|
|
|
|
|
var anonymous = PRSDesktop.Resources.anonymous.AsBitmapImage();
|
|
var anonymous = PRSDesktop.Resources.anonymous.AsBitmapImage();
|
|
|
|
+ anonymous.Freeze();
|
|
if (Security.IsAllowed<CanViewOthersTasks>())
|
|
if (Security.IsAllowed<CanViewOthersTasks>())
|
|
{
|
|
{
|
|
- Employees.Add(new EmployeeModel(CoreUtils.FullGuid, "All Staff", Guid.Empty, PRSDesktop.Resources.everyone.AsBitmapImage()));
|
|
|
|
|
|
+ var everyone = PRSDesktop.Resources.everyone.AsBitmapImage();
|
|
|
|
+ everyone.Freeze();
|
|
|
|
+ Employees.Add(new EmployeeModel(CoreUtils.FullGuid, "All Staff", Guid.Empty, everyone));
|
|
Employees.Add(new EmployeeModel(Guid.Empty, "Unallocated", Guid.Empty, null));
|
|
Employees.Add(new EmployeeModel(Guid.Empty, "Unallocated", Guid.Empty, null));
|
|
}
|
|
}
|
|
|
|
|
|
- EmployeeModel? selected = null;
|
|
|
|
foreach (var employee in employees.ToObjects<Employee>())
|
|
foreach (var employee in employees.ToObjects<Employee>())
|
|
{
|
|
{
|
|
var name = employee.ID == App.EmployeeID ? "My Tasks" : employee.Name;
|
|
var name = employee.ID == App.EmployeeID ? "My Tasks" : employee.Name;
|
|
@@ -238,7 +331,6 @@ public partial class TasksByStatusControl : UserControl, ITaskControl
|
|
if (employee.ID == App.EmployeeID)
|
|
if (employee.ID == App.EmployeeID)
|
|
{
|
|
{
|
|
Employees.Insert(0, model);
|
|
Employees.Insert(0, model);
|
|
- selected = model;
|
|
|
|
}
|
|
}
|
|
else
|
|
else
|
|
{
|
|
{
|
|
@@ -304,10 +396,23 @@ public partial class TasksByStatusControl : UserControl, ITaskControl
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ private void Employees_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
|
|
+ {
|
|
|
|
+ if (_updatingEmployees || EventSuppressor.IsSet(Suppress.This))
|
|
|
|
+ return;
|
|
|
|
+
|
|
|
|
+ SelectedEmployee = (EmployeeList.SelectedItem as EmployeeModel)!;
|
|
|
|
+
|
|
|
|
+ SelectedTasks.Clear();
|
|
|
|
+ if (IsReady)
|
|
|
|
+ Refresh(true);
|
|
|
|
+ }
|
|
|
|
+
|
|
#endregion
|
|
#endregion
|
|
|
|
|
|
#region Kanbans
|
|
#region Kanbans
|
|
|
|
|
|
|
|
+ private List<TaskModel> AllTasks { get; set; } = new();
|
|
private IEnumerable<TaskModel> Tasks => Columns.SelectMany(x => x.Tasks);
|
|
private IEnumerable<TaskModel> Tasks => Columns.SelectMany(x => x.Tasks);
|
|
|
|
|
|
private readonly List<TaskModel> SelectedTasks = new();
|
|
private readonly List<TaskModel> SelectedTasks = new();
|
|
@@ -344,9 +449,9 @@ public partial class TasksByStatusControl : UserControl, ITaskControl
|
|
|
|
|
|
private void OpenTaskMenu_Executed(object sender, ExecutedRoutedEventArgs e)
|
|
private void OpenTaskMenu_Executed(object sender, ExecutedRoutedEventArgs e)
|
|
{
|
|
{
|
|
- if (e.Parameter is not TaskModel model) return;
|
|
|
|
|
|
+ if (e.Parameter is not KanbanResources.OpenTaskMenuCommandArgs args) return;
|
|
|
|
|
|
- Host.PopulateMenu(this, sender as ContextMenu);
|
|
|
|
|
|
+ Host.PopulateMenu(this, args.Model, args.Menu);
|
|
}
|
|
}
|
|
|
|
|
|
private void SelectTask_Executed(object sender, ExecutedRoutedEventArgs e)
|
|
private void SelectTask_Executed(object sender, ExecutedRoutedEventArgs e)
|
|
@@ -361,85 +466,597 @@ public partial class TasksByStatusControl : UserControl, ITaskControl
|
|
|
|
|
|
private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
|
|
private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
|
|
{
|
|
{
|
|
- e.CanExecute = e.Parameter is TaskModel;
|
|
|
|
|
|
+ e.CanExecute = true;
|
|
}
|
|
}
|
|
|
|
|
|
- #endregion
|
|
|
|
|
|
+ private void ItemsControl_DragOver(object sender, DragEventArgs e)
|
|
|
|
+ {
|
|
|
|
+ if (sender is not FrameworkElement element || element.Tag is not TasksByStatusColumn column) return;
|
|
|
|
|
|
- public void Refresh(bool resetselection)
|
|
|
|
|
|
+ e.Effects = DragDropEffects.None;
|
|
|
|
+ if (e.Data.GetDataPresent(typeof(TaskModel)))
|
|
|
|
+ {
|
|
|
|
+ var model = e.Data.GetData(typeof(TaskModel)) as TaskModel;
|
|
|
|
+ if(model is not null && model.Category != column.Category && !SelectedTasks.Any(x => x.Locked))
|
|
|
|
+ {
|
|
|
|
+ e.Effects = DragDropEffects.Move;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private void ChangeStatus(IEnumerable<TaskModel> tasks, string status)
|
|
{
|
|
{
|
|
|
|
+ var models = tasks
|
|
|
|
+ .Where(x => !x.Category.Equals(status))
|
|
|
|
+ .Where(x => !x.Locked)
|
|
|
|
+ .ToList();
|
|
|
|
+ if (!models.Any())
|
|
|
|
+ {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ if(status.Equals(Kanban.COMPLETE))
|
|
|
|
+ {
|
|
|
|
+ if (MessageBox.Show($"Are you sure you want to complete the selected tasks?", "Confirm Completion",
|
|
|
|
+ MessageBoxButton.YesNo) != MessageBoxResult.Yes)
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var completed = DateTime.Now;
|
|
|
|
+
|
|
|
|
+ var kanbans = Host.LoadKanbans(tasks, new Columns<Kanban>(x => x.ID).Add(x => x.Category));
|
|
|
|
+ foreach (var kanban in kanbans)
|
|
|
|
+ {
|
|
|
|
+ kanban.Category = status;
|
|
|
|
+ }
|
|
|
|
+ if (status.Equals(Kanban.COMPLETE))
|
|
|
|
+ {
|
|
|
|
+ foreach (var kanban in kanbans)
|
|
|
|
+ {
|
|
|
|
+ kanban.Completed = completed;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ Client.Save(kanbans, $"Task Status Updated to {status}", (o, err) =>
|
|
|
|
+ {
|
|
|
|
+ if (err is not null)
|
|
|
|
+ {
|
|
|
|
+ CoreUtils.LogException("", err);
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ foreach (var model in models)
|
|
|
|
+ {
|
|
|
|
+ model.Checked = false;
|
|
|
|
+ model.Category = status;
|
|
|
|
+ }
|
|
|
|
+ if (status.Equals(Kanban.COMPLETE))
|
|
|
|
+ {
|
|
|
|
+ foreach (var model in models)
|
|
|
|
+ {
|
|
|
|
+ model.CompletedDate = completed;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ FilterKanbans();
|
|
}
|
|
}
|
|
|
|
|
|
- #region ITaskControl
|
|
|
|
|
|
+ private void ItemsControl_Drop(object sender, DragEventArgs e)
|
|
|
|
+ {
|
|
|
|
+ if (sender is not FrameworkElement element || element.Tag is not TasksByStatusColumn column) return;
|
|
|
|
|
|
- public ITaskHost Host { get; set; }
|
|
|
|
|
|
+ e.Effects = DragDropEffects.None;
|
|
|
|
+ if (e.Data.GetDataPresent(typeof(TaskModel)))
|
|
|
|
+ {
|
|
|
|
+ ChangeStatus(SelectedModels(e.Data.GetData(typeof(TaskModel)) as TaskModel), column.Category);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
- public KanbanViewType KanbanViewType => KanbanViewType.Status;
|
|
|
|
|
|
+ #endregion
|
|
|
|
|
|
- public bool IsReady { get; set; }
|
|
|
|
|
|
+ #region Filters
|
|
|
|
|
|
- public string SectionName => "Tasks By Status";
|
|
|
|
|
|
+ private Guid JobFilterID = Guid.Empty;
|
|
|
|
+ private string SearchText = "";
|
|
|
|
+ private Guid SelectedType = CoreUtils.FullGuid;
|
|
|
|
|
|
- public DataModel DataModel(Selection selection)
|
|
|
|
|
|
+ private static Filter<T> GetSearchFilter<T>(Expression<Func<T, object?>> expression, string searchText) where T : Entity, new()
|
|
{
|
|
{
|
|
- var ids = SelectedModels().Select(x => Guid.Parse(x.ID)).ToArray();
|
|
|
|
- return new AutoDataModel<Kanban>(new Filter<Kanban>(x => x.ID).InList(ids));
|
|
|
|
|
|
+ Filter<T>? result = null;
|
|
|
|
+ var comps = searchText.Trim().Split(' ');
|
|
|
|
+ foreach (var comp in comps)
|
|
|
|
+ result = result == null ? new Filter<T>(expression).Contains(comp) : result.And(expression).Contains(comp);
|
|
|
|
+ return result ?? new Filter<T>().All();
|
|
}
|
|
}
|
|
|
|
|
|
- public IEnumerable<TaskModel> SelectedModels(TaskModel? sender = null)
|
|
|
|
|
|
+ private Filter<KanbanSubscriber> GetKanbanSubscriberFilter()
|
|
{
|
|
{
|
|
- return Enumerable.Empty<TaskModel>();
|
|
|
|
- }
|
|
|
|
|
|
+ var filter = new Filter<KanbanSubscriber>(x => x.Kanban.Closed).IsEqualTo(DateTime.MinValue);
|
|
|
|
|
|
- #endregion
|
|
|
|
|
|
+ if (Host.Job != null)
|
|
|
|
+ {
|
|
|
|
+ if (Host.Job.ID != Guid.Empty)
|
|
|
|
+ filter = filter.And(x => x.Kanban.JobLink.ID).IsEqualTo(Host.Job.ID);
|
|
|
|
+ else
|
|
|
|
+ filter = filter.And(x => x.Kanban.JobLink.ID).None();
|
|
|
|
+ }
|
|
|
|
|
|
- private void Employees_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
|
|
|
|
+ // All Tasks (EmployeeID.HasValue == false) or Unallocated (EmployeeID = Guid.Empty) are retrieved directly from the Kanban Table
|
|
|
|
+ // so if we are here, we can assume that we are pulling subscriber data
|
|
|
|
+ var empfilter = new Filter<KanbanSubscriber>(x => x.Employee.ID).IsEqualTo(SelectedEmployee.ID);
|
|
|
|
+ filter.Ands.Add(empfilter);
|
|
|
|
+
|
|
|
|
+ if (SelectedEmployee.ID != App.EmployeeID)
|
|
|
|
+ filter = filter.And(x => x.Kanban.Private).IsEqualTo(false);
|
|
|
|
+
|
|
|
|
+ return filter;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private Filter<Kanban> GetKanbanFilter()
|
|
{
|
|
{
|
|
|
|
+ var filter = new Filter<Kanban>(x => x.Closed).IsEqualTo(DateTime.MinValue);
|
|
|
|
+
|
|
|
|
+ if (Host.Job != null)
|
|
|
|
+ {
|
|
|
|
+ if (Host.Job.ID != Guid.Empty)
|
|
|
|
+ filter = filter.And(x => x.JobLink.ID).IsEqualTo(Host.Job.ID);
|
|
|
|
+ else
|
|
|
|
+ filter = filter.And(x => x.JobLink.ID).None();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (SelectedEmployee.ID != CoreUtils.FullGuid)
|
|
|
|
+ {
|
|
|
|
+ if (SelectedEmployee.ID != Guid.Empty)
|
|
|
|
+ {
|
|
|
|
+ var empfilter = new Filter<Kanban>(x => x.EmployeeLink.ID).IsEqualTo(SelectedEmployee.ID)
|
|
|
|
+ .Or(x => x.ManagerLink.ID).IsEqualTo(SelectedEmployee.ID);
|
|
|
|
+ filter.Ands.Add(empfilter);
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ filter = filter.And(x => x.EmployeeLink.ID).IsEqualTo(SelectedEmployee.ID);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
|
|
+ if (SelectedEmployee.ID != App.EmployeeID)
|
|
|
|
+ filter = filter.And(x => x.Private).IsEqualTo(false);
|
|
|
|
+
|
|
|
|
+ return filter;
|
|
}
|
|
}
|
|
|
|
|
|
private void TaskTypesLabel_OnClick(object sender, RoutedEventArgs e)
|
|
private void TaskTypesLabel_OnClick(object sender, RoutedEventArgs e)
|
|
{
|
|
{
|
|
-
|
|
|
|
|
|
+ var list = new MasterList(typeof(KanbanType));
|
|
|
|
+ list.ShowDialog();
|
|
|
|
+ SetupKanbanTypesLookup(LoadKanbanTypes());
|
|
}
|
|
}
|
|
|
|
|
|
private void TaskTypes_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
private void TaskTypes_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
{
|
|
{
|
|
|
|
+ if (!IsReady || EventSuppressor.IsSet(Suppress.This))
|
|
|
|
+ return;
|
|
|
|
+ if (e.AddedItems.Count > 0)
|
|
|
|
+ {
|
|
|
|
+ var item = (KeyValuePair<Guid, string>)e.AddedItems[0];
|
|
|
|
+ SelectedType = item.Key;
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ SelectedType = CoreUtils.FullGuid;
|
|
|
|
+ }
|
|
|
|
|
|
|
|
+ Host.KanbanSettings.StatusSettings.SelectedType = SelectedType;
|
|
|
|
+ Host.SaveSettings();
|
|
|
|
+ FilterKanbans();
|
|
}
|
|
}
|
|
|
|
|
|
private void JobFilterBtn_OnClick(object sender, RoutedEventArgs e)
|
|
private void JobFilterBtn_OnClick(object sender, RoutedEventArgs e)
|
|
{
|
|
{
|
|
|
|
+ if (JobFilterID != Guid.Empty)
|
|
|
|
+ {
|
|
|
|
+ JobFilterBtn.Content = "Filter Job";
|
|
|
|
+ JobFilterID = Guid.Empty;
|
|
|
|
+ FilterKanbans();
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var window = new ThemableWindow();
|
|
|
|
+ var grid = new JobGrid();
|
|
|
|
+ grid.Reconfigure(options =>
|
|
|
|
+ {
|
|
|
|
+ options.Remove(DynamicGridOption.EditRows);
|
|
|
|
+ options.Remove(DynamicGridOption.DeleteRows);
|
|
|
|
+ options.Remove(DynamicGridOption.AddRows);
|
|
|
|
+ options.Remove(DynamicGridOption.MultiSelect);
|
|
|
|
+ options.Remove(DynamicGridOption.ExportData);
|
|
|
|
+ options.Remove(DynamicGridOption.ImportData);
|
|
|
|
+ });
|
|
|
|
+ grid.OnSelectItem += (object sender, DynamicGridSelectionEventArgs e) =>
|
|
|
|
+ {
|
|
|
|
+ if (grid.SelectedRows.Count() == 0)
|
|
|
|
+ return;
|
|
|
|
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ var item = grid.SelectedRows[0];
|
|
|
|
+ AddJobFilter(item);
|
|
|
|
+ window.Close();
|
|
|
|
+ }
|
|
|
|
+ };
|
|
|
|
+ grid.Refresh(true, true);
|
|
|
|
+ window.Content = grid;
|
|
|
|
+ window.ShowDialog();
|
|
|
|
+ }
|
|
|
|
+ private void AddJobFilter(CoreRow item)
|
|
|
|
+ {
|
|
|
|
+ JobFilterID = item.Get<Job, Guid>(x => x.ID);
|
|
|
|
+ JobFilterBtn.Content = item.Get<Job, string>(x => x.JobNumber) + " (click to cancel)";
|
|
|
|
+ FilterKanbans();
|
|
}
|
|
}
|
|
|
|
|
|
- private void Export_Click(object sender, RoutedEventArgs e)
|
|
|
|
|
|
+ private void IncludeCompleted_Checked(object sender, RoutedEventArgs e)
|
|
|
|
+ {
|
|
|
|
+ if (!IsReady)
|
|
|
|
+ return;
|
|
|
|
+ Host.KanbanSettings.StatusSettings.IncludeCompleted = IncludeCompleted.IsChecked ?? false;
|
|
|
|
+ Host.SaveSettings();
|
|
|
|
+ SetupColumns();
|
|
|
|
+ FilterKanbans();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private void IncludeObserved_Checked(object sender, RoutedEventArgs e)
|
|
{
|
|
{
|
|
|
|
+ if (!IsReady)
|
|
|
|
+ return;
|
|
|
|
+ Host.KanbanSettings.StatusSettings.IncludeObserved = IncludeObserved.IsChecked ?? false;
|
|
|
|
+ Host.SaveSettings();
|
|
|
|
+ FilterKanbans();
|
|
|
|
+ }
|
|
|
|
|
|
|
|
+ private void IncludeLocked_Checked(object sender, RoutedEventArgs e)
|
|
|
|
+ {
|
|
|
|
+ if (!IsReady)
|
|
|
|
+ return;
|
|
|
|
+ Host.KanbanSettings.StatusSettings.IncludeLocked = IncludeLocked.IsChecked ?? false;
|
|
|
|
+ Host.SaveSettings();
|
|
|
|
+ FilterKanbans();
|
|
}
|
|
}
|
|
|
|
|
|
- private void IncludeCompleted_Checked(object sender, RoutedEventArgs e)
|
|
|
|
|
|
+ private void Search_KeyUp(object sender, KeyEventArgs e)
|
|
{
|
|
{
|
|
|
|
+ if (string.IsNullOrWhiteSpace(Search.Text) || e.Key == Key.Return)
|
|
|
|
+ {
|
|
|
|
+ SearchText = Search.Text;
|
|
|
|
+ FilterKanbans();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ #endregion
|
|
|
|
+
|
|
|
|
+ #region Refresh
|
|
|
|
|
|
|
|
+ private static Columns<IKanban> GetKanbanColumns()
|
|
|
|
+ {
|
|
|
|
+ return new Columns<IKanban>(
|
|
|
|
+ x => x.ID,
|
|
|
|
+ x => x.DueDate,
|
|
|
|
+ x => x.Completed,
|
|
|
|
+ x => x.Summary,
|
|
|
|
+ x => x.Category,
|
|
|
|
+ x => x.EmployeeLink.ID,
|
|
|
|
+ x => x.ManagerLink.ID,
|
|
|
|
+ x => x.Notes,
|
|
|
|
+ x => x.Title,
|
|
|
|
+ x => x.JobLink.ID,
|
|
|
|
+ x => x.JobLink.JobNumber,
|
|
|
|
+ x => x.JobLink.Name,
|
|
|
|
+ x => x.Type.ID,
|
|
|
|
+ x => x.Type.Code,
|
|
|
|
+ x => x.Number,
|
|
|
|
+ x => x.Attachments,
|
|
|
|
+ x => x.Locked);
|
|
}
|
|
}
|
|
|
|
|
|
- private void IncludeObserved_Checked(object sender, RoutedEventArgs e)
|
|
|
|
|
|
+ public void Refresh(bool resetselection)
|
|
{
|
|
{
|
|
|
|
+ using var cursor = new WaitCursor();
|
|
|
|
+
|
|
|
|
+ IEnumerable<IKanban> kanbans;
|
|
|
|
+ if (SelectedEmployee.ID != CoreUtils.FullGuid && SelectedEmployee.ID != Guid.Empty)
|
|
|
|
+ {
|
|
|
|
+ var columns = new Columns<KanbanSubscriber>();
|
|
|
|
+ var kanbanColumn = new Column<KanbanSubscriber>(x => x.Kanban);
|
|
|
|
+ foreach(var column in GetKanbanColumns().ColumnNames())
|
|
|
|
+ {
|
|
|
|
+ columns.Add(new Column<KanbanSubscriber>($"{kanbanColumn.Property}.{column}"));
|
|
|
|
+ }
|
|
|
|
+ kanbans = new Client<KanbanSubscriber>().Query(
|
|
|
|
+ GetKanbanSubscriberFilter(),
|
|
|
|
+ columns,
|
|
|
|
+ new SortOrder<KanbanSubscriber>(x => x.Kanban.DueDate) { Direction = SortDirection.Ascending }
|
|
|
|
+ ).ToObjects<KanbanSubscriber>().Select(x => x.Kanban);
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ kanbans = new Client<Kanban>().Query(
|
|
|
|
+ GetKanbanFilter(),
|
|
|
|
+ GetKanbanColumns().Cast<Kanban>(),
|
|
|
|
+ new SortOrder<Kanban>(x => x.DueDate) { Direction = SortDirection.Ascending }
|
|
|
|
+ ).ToObjects<Kanban>();
|
|
|
|
+ }
|
|
|
|
|
|
|
|
+ AllTasks = CreateModels(kanbans).ToList();
|
|
|
|
+ FilterKanbans();
|
|
}
|
|
}
|
|
|
|
+ private IEnumerable<TaskModel> CreateModels(IEnumerable<IKanban> kanbans)
|
|
|
|
+ {
|
|
|
|
+ foreach(var kanban in kanbans)
|
|
|
|
+ {
|
|
|
|
+ TaskModel? model = null;
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ model = new TaskModel();
|
|
|
|
+
|
|
|
|
+ var employee = Employees.FirstOrDefault(x => x.ID == kanban.EmployeeLink.ID);
|
|
|
|
+ var manager = Employees.FirstOrDefault(x => x.ID == kanban.ManagerLink.ID);
|
|
|
|
+
|
|
|
|
+ model.Title = kanban.Title;
|
|
|
|
+ model.ID = kanban.ID;
|
|
|
|
+ model.Description = kanban.Summary ?? "";
|
|
|
|
+ model.Category = kanban.Category;
|
|
|
|
+
|
|
|
|
+ var colour = SelectedEmployee.ID == Guid.Empty
|
|
|
|
+ || SelectedEmployee.ID == CoreUtils.FullGuid
|
|
|
|
+ || kanban.EmployeeLink.ID == SelectedEmployee.ID
|
|
|
|
+ ? TaskModel.KanbanColor(kanban.DueDate, kanban.Completed)
|
|
|
|
+ : (kanban.ManagerLink.ID == SelectedEmployee.ID
|
|
|
|
+ ? Color.Silver
|
|
|
|
+ : Color.Plum);
|
|
|
|
+ if (kanban.Locked)
|
|
|
|
+ {
|
|
|
|
+ colour = colour.MixColors(0.5F, Color.White);
|
|
|
|
+ }
|
|
|
|
+ model.Color = System.Windows.Media.Color.FromArgb(colour.A, colour.R, colour.G, colour.B);
|
|
|
|
+ model.Image = employee?.Image;
|
|
|
|
+ model.Attachments = kanban.Attachments > 0;
|
|
|
|
+ model.DueDate = kanban.DueDate;
|
|
|
|
+ model.CompletedDate = kanban.Completed;
|
|
|
|
+ model.Locked = kanban.Locked;
|
|
|
|
+
|
|
|
|
+ var notes = new List<List<string>> { new() };
|
|
|
|
+ if (kanban.Notes is not null)
|
|
|
|
+ {
|
|
|
|
+ foreach (var line in kanban.Notes)
|
|
|
|
+ {
|
|
|
|
+ if (line.Equals("==================================="))
|
|
|
|
+ {
|
|
|
|
+ notes.Add(new());
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ notes.Last().Add(line);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ model.Notes = string.Join(
|
|
|
|
+ "\n===================================\n",
|
|
|
|
+ notes.Reverse<List<string>>().Select(x => string.Join('\n', x)));
|
|
|
|
+ }
|
|
|
|
|
|
- private void IncludeLocked_Checked(object sender, RoutedEventArgs e)
|
|
|
|
|
|
+ model.EmployeeID = kanban.EmployeeLink.ID;
|
|
|
|
+ model.ManagerID = kanban.ManagerLink.ID;
|
|
|
|
+
|
|
|
|
+ var employeeString = "";
|
|
|
|
+ if (kanban.EmployeeLink.ID != SelectedEmployee.ID)
|
|
|
|
+ {
|
|
|
|
+ if (kanban.EmployeeLink.ID == Guid.Empty)
|
|
|
|
+ {
|
|
|
|
+ employeeString = "Unallocated";
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ employeeString = employee is not null
|
|
|
|
+ ? (employee.ID == App.EmployeeID
|
|
|
|
+ ? App.EmployeeName
|
|
|
|
+ : employee.Name)
|
|
|
|
+ : "";
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var managerString = "";
|
|
|
|
+ if (kanban.ManagerLink.ID != SelectedEmployee.ID)
|
|
|
|
+ {
|
|
|
|
+ if (kanban.ManagerLink.ID != Guid.Empty)
|
|
|
|
+ {
|
|
|
|
+ managerString = manager is not null
|
|
|
|
+ ? (manager.ID == App.EmployeeID
|
|
|
|
+ ? App.EmployeeName
|
|
|
|
+ : manager.Name)
|
|
|
|
+ : "";
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ model.Manager = managerString;
|
|
|
|
+
|
|
|
|
+ if (!string.IsNullOrEmpty(employeeString))
|
|
|
|
+ {
|
|
|
|
+ if (!managerString.IsNullOrWhiteSpace() && !managerString.Equals(employeeString))
|
|
|
|
+ {
|
|
|
|
+ model.AssignedTo = $"Assigned to {employeeString} by {managerString}";
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ model.AssignedTo = $"Assigned to {employeeString}";
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ if (!managerString.IsNullOrWhiteSpace())
|
|
|
|
+ {
|
|
|
|
+ model.AssignedTo = $"Allocated by {managerString}";
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ model.JobID = kanban.JobLink.ID;
|
|
|
|
+ model.JobNumber = kanban.JobLink.JobNumber.NotWhiteSpaceOr("");
|
|
|
|
+ model.JobName = kanban.JobLink.Name;
|
|
|
|
+ model.Checked = SelectedTasks.Any(x => x.ID == model.ID);
|
|
|
|
+ model.Type = new KanbanType
|
|
|
|
+ {
|
|
|
|
+ ID = kanban.Type.ID,
|
|
|
|
+ Code = kanban.Type.Code
|
|
|
|
+ };
|
|
|
|
+ model.Number = kanban.Number;
|
|
|
|
+ }
|
|
|
|
+ catch(Exception e)
|
|
|
|
+ {
|
|
|
|
+ CoreUtils.LogException("", e);
|
|
|
|
+ }
|
|
|
|
+ if(model is not null)
|
|
|
|
+ {
|
|
|
|
+ yield return model;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// Take the full list of kanbans loaded from the database, and filter based on the search UI elements, filtering into the columns.
|
|
|
|
+ /// </summary>
|
|
|
|
+ private void FilterKanbans()
|
|
{
|
|
{
|
|
|
|
+ Progress.Show("Loading");
|
|
|
|
+ IEnumerable<TaskModel> filtered;
|
|
|
|
+ if (JobFilterID == Guid.Empty)
|
|
|
|
+ {
|
|
|
|
+ filtered = AllTasks;
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ filtered = AllTasks.Where(x => x.JobSearch(JobFilterID));
|
|
|
|
+ }
|
|
|
|
+ if (!Host.KanbanSettings.StatusSettings.IncludeLocked)
|
|
|
|
+ {
|
|
|
|
+ filtered = filtered.Where(x => !x.Locked);
|
|
|
|
+ }
|
|
|
|
+ if (!Host.KanbanSettings.StatusSettings.IncludeObserved
|
|
|
|
+ && SelectedEmployee.ID != CoreUtils.FullGuid)
|
|
|
|
+ {
|
|
|
|
+ filtered = filtered.Where(x => x.EmployeeID == SelectedEmployee.ID || x.ManagerID == SelectedEmployee.ID);
|
|
|
|
+ }
|
|
|
|
+ if (!Host.KanbanSettings.StatusSettings.IncludeCompleted)
|
|
|
|
+ {
|
|
|
|
+ filtered = filtered.Where(x => x.CompletedDate.IsEmpty());
|
|
|
|
+ }
|
|
|
|
+ if (SelectedType != CoreUtils.FullGuid)
|
|
|
|
+ {
|
|
|
|
+ filtered = filtered.Where(x => x.Type.ID == SelectedType);
|
|
|
|
+ }
|
|
|
|
+ filtered = filtered.Where(x => x.Search(SearchText.Split()))
|
|
|
|
+ .OrderBy(x => x.EmployeeID == SelectedEmployee.ID ? 0 : 1)
|
|
|
|
+ .ThenBy(x => x.DueDate);
|
|
|
|
|
|
|
|
+ foreach (var column in Columns)
|
|
|
|
+ {
|
|
|
|
+ column.Tasks.Clear();
|
|
|
|
+ }
|
|
|
|
+ foreach (var task in filtered)
|
|
|
|
+ {
|
|
|
|
+ var column = GetColumn(task.Category);
|
|
|
|
+ column?.Tasks.Add(task);
|
|
|
|
+ }
|
|
|
|
+ Progress.Close();
|
|
}
|
|
}
|
|
|
|
|
|
- private void ViewType_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
|
|
|
|
+ #endregion
|
|
|
|
+
|
|
|
|
+ #region ITaskControl
|
|
|
|
+
|
|
|
|
+ public ITaskHost Host { get; set; }
|
|
|
|
+
|
|
|
|
+ public KanbanViewType KanbanViewType => KanbanViewType.Status;
|
|
|
|
+
|
|
|
|
+ public bool IsReady { get; set; }
|
|
|
|
+
|
|
|
|
+ public string SectionName => "Tasks By Status";
|
|
|
|
+
|
|
|
|
+ public DataModel DataModel(Selection selection)
|
|
{
|
|
{
|
|
|
|
+ var ids = SelectedModels().Select(x => x.ID).ToArray();
|
|
|
|
+ return new AutoDataModel<Kanban>(new Filter<Kanban>(x => x.ID).InList(ids));
|
|
|
|
+ }
|
|
|
|
|
|
|
|
+ public IEnumerable<TaskModel> SelectedModels(TaskModel? sender = null)
|
|
|
|
+ {
|
|
|
|
+ if(sender is null)
|
|
|
|
+ {
|
|
|
|
+ return SelectedTasks;
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ var result = SelectedTasks.ToList();
|
|
|
|
+ if (!result.Contains(sender))
|
|
|
|
+ {
|
|
|
|
+ result.Add(sender);
|
|
|
|
+ }
|
|
|
|
+ return result;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
- private void Search_KeyUp(object sender, KeyEventArgs e)
|
|
|
|
|
|
+ #endregion
|
|
|
|
+
|
|
|
|
+ private void Export_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
{
|
|
|
|
+ var form = new DynamicExportForm(typeof(Kanban), GetKanbanColumns().ColumnNames());
|
|
|
|
+ if (form.ShowDialog() != true)
|
|
|
|
+ return;
|
|
|
|
+ var export = new Client<Kanban>().Query(
|
|
|
|
+ GetKanbanFilter(),
|
|
|
|
+ new Columns<Kanban>(form.Fields),
|
|
|
|
+ LookupFactory.DefineSort<Kanban>()
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+ var employee = "Tasks for All Staff";
|
|
|
|
+ if (SelectedEmployee.ID != CoreUtils.FullGuid)
|
|
|
|
+ {
|
|
|
|
+ if (SelectedEmployee.ID == Guid.Empty)
|
|
|
|
+ {
|
|
|
|
+ employee = "Unallocated Tasks";
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ var model = Employees.FirstOrDefault(x => x.ID == SelectedEmployee.ID);
|
|
|
|
+ employee = model == null ? "Tasks for (Unknown)" : "Tasks for " + (model.ID == App.EmployeeID ? App.EmployeeName : model.Name);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ ExcelExporter.DoExport<Kanban>(
|
|
|
|
+ export,
|
|
|
|
+ string.Format(
|
|
|
|
+ "{0} ({1:dd-MMM-yy})",
|
|
|
|
+ employee,
|
|
|
|
+ DateTime.Today
|
|
|
|
+ )
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private KanbanViewMode _mode;
|
|
|
|
+ public KanbanViewMode Mode
|
|
|
|
+ {
|
|
|
|
+ get => _mode;
|
|
|
|
+ set
|
|
|
|
+ {
|
|
|
|
+ _mode = value;
|
|
|
|
+ OnPropertyChanged();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private void ViewType_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
|
|
+ {
|
|
|
|
+ if (!IsReady || EventSuppressor.IsSet(Suppress.This))
|
|
|
|
+ return;
|
|
|
|
+ Mode = ViewType.SelectedIndex switch
|
|
|
|
+ {
|
|
|
|
+ 0 => KanbanViewMode.Full,
|
|
|
|
+ 1 => KanbanViewMode.Compact,
|
|
|
|
+ _ => KanbanViewMode.Full
|
|
|
|
+ };
|
|
|
|
|
|
|
|
+ Host.KanbanSettings.StatusSettings.CompactView = Mode == KanbanViewMode.Compact;
|
|
|
|
+ Host.SaveSettings();
|
|
}
|
|
}
|
|
}
|
|
}
|