using Comal.Classes; using InABox.Clients; using InABox.Core; using InABox.WPF; using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Linq.Expressions; using System.Reflection; 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.Drawing; using System.Windows.Media; using System.Windows.Media.Imaging; 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; public class TasksByStatusColumn : INotifyPropertyChanged { public KanbanStatus Status { get; } public string Title { get; } public int NumTasks { get => Tasks.Count; } public double NumHours { get => Tasks.Sum(x => x.EstimatedTime.TotalHours); } public ObservableCollection Tasks { get; } = new(); public TasksByStatusColumn(KanbanStatus status, string title) { Status = status; 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)); } } } public class EmployeeModel { public Guid ID { get; set; } public string Name { get; set; } public BitmapImage? Image { get; set; } public Guid ThumbnailID { get; set; } public EmployeeModel(Guid id, string name, Guid thumbnail, BitmapImage? image) { ID = id; Name = name; Image = image; ThumbnailID = thumbnail; } } public class SuspendableObservableCollection : ObservableCollection { 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); } } /// /// Interaction logic for TasksByStatusControl.xaml /// public partial class TasksByStatusControl : UserControl, ITaskControl, INotifyPropertyChanged { private enum Suppress { This } public SuspendableObservableCollection Columns { get; private set; } = new(); private List Employees = new(); public TasksByStatusControl() { 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 public void Setup() { var employeesTask = Task.Run(() => LoadEmployees()); var kanbanTypesTask = Task.Run(() => LoadKanbanTypes()); SetupToolbar(); SetupColumns(); employeesTask.Wait(); SetupEmployeeList(); kanbanTypesTask.Wait(); SetupKanbanTypesLookup(kanbanTypesTask.Result); Mode = Host.KanbanSettings.StatusSettings.CompactView ? KanbanViewMode.Compact : KanbanViewMode.Full; FilterButton.SetSettings(Host.KanbanSettings.Filters, false); } private void SetupToolbar() { IncludeCompleted.Visibility = Security.IsAllowed() ? Visibility.Visible : Visibility.Collapsed; IncludeCompleted.IsChecked = IncludeCompleted.Visibility == Visibility.Visible ? Host.KanbanSettings.StatusSettings.IncludeCompleted : true; IncludeObserved.IsChecked = Host.KanbanSettings.StatusSettings.IncludeObserved; ViewType.SelectedIndex = Host.KanbanSettings.StatusSettings.CompactView ? 1 : 0; } #endregion #region Columns private void SetupColumns() { Columns.SupressNotification = true; Columns.Clear(); Columns.Add(new TasksByStatusColumn(KanbanStatus.Open, "To Do")); Columns.Add(new TasksByStatusColumn(KanbanStatus.InProgress, "In Progress")); Columns.Add(new TasksByStatusColumn(KanbanStatus.Waiting, "Waiting for Others")); if (Host.KanbanSettings.StatusSettings.IncludeCompleted) Columns.Add(new TasksByStatusColumn(KanbanStatus.Complete, "Completed")); Columns.SupressNotification = false; } private TasksByStatusColumn? GetColumn(KanbanStatus category) { return Columns.FirstOrDefault(x => x.Status.Equals(category)); } private void Column_ContextMenuOpening(object sender, ContextMenuEventArgs e) { if (sender is not FrameworkElement element || element.Tag is not TasksByStatusColumn column) return; var menu = element.ContextMenu; menu.Items.Clear(); menu.AddItem("New Task", null, CreateTask); menu.AddItem($"Select all {column.Title} tasks", null, column, SelectAll); menu.AddItem($"Deselect all {column.Title} tasks", null, column, DeselectAll); } private void SelectColumn(TasksByStatusColumn column, bool selected) { SelectedTasks.Clear(); if (selected) { SelectedTasks.AddRange(column.Tasks); foreach (var task in Tasks) task.Checked = task.Status == column.Status ? selected : !selected; } FilterKanbans(); } private void DeselectAll(TasksByStatusColumn column) { SelectColumn(column, false); } private void SelectAll(TasksByStatusColumn column) { SelectColumn(column, true); } #endregion #region Kanban Types private IEnumerable LoadKanbanTypes() { return Client.Query( new Filter(x => x.Hidden).IsEqualTo(false), InABox.Core.Columns.None().Add(x => x.ID, x => x.Description), new SortOrder(x => x.Description)) .ToObjects(); } private void SetupKanbanTypesLookup(IEnumerable kanbanTypes) { if (ClientFactory.IsSupported()) { var tasktypes = new Dictionary { { CoreUtils.FullGuid, "All Types" }, { Guid.Empty, "Unallocated Types" } }; foreach(var type in kanbanTypes) { tasktypes.Add(type.ID, type.Description); } TaskTypes.ItemsSource = tasktypes; if (tasktypes.ContainsKey(Host.KanbanSettings.StatusSettings.SelectedType)) TaskTypes.SelectedValue = Host.KanbanSettings.StatusSettings.SelectedType; else TaskTypes.SelectedValue = CoreUtils.FullGuid; TaskTypesLabel.Visibility = Visibility.Visible; TaskTypes.Visibility = Visibility.Visible; } else { TaskTypesLabel.Visibility = Visibility.Collapsed; TaskTypes.Visibility = Visibility.Collapsed; } } #endregion #region Employees private EmployeeModel SelectedEmployee; private void LoadEmployees() { Employees.Clear(); var employeeFilter = Security.IsAllowed() ? new Filter(x => x.CanAllocateTasks).IsEqualTo(true) .And(new Filter(x => x.FinishDate).IsEqualTo(DateTime.MinValue) .Or(x => x.FinishDate).IsGreaterThan(DateTime.Today)) : new Filter(x => x.ID).IsEqualTo(App.EmployeeID); var employees = Client.Query( employeeFilter, InABox.Core.Columns.None().Add(x => x.ID) .Add(x => x.Thumbnail.ID) .Add(x => x.Name)); var anonymous = PRSDesktop.Resources.anonymous.AsBitmapImage(); anonymous.Freeze(); if (Security.IsAllowed()) { 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)); } foreach (var employee in employees.ToObjects()) { var name = employee.ID == App.EmployeeID ? "My Tasks" : employee.Name; var model = new EmployeeModel(employee.ID, name, employee.Thumbnail.ID, anonymous); if (employee.ID == App.EmployeeID) { Employees.Insert(0, model); } else { Employees.Add(model); } } } private void SetupEmployeeList() { if (Security.IsAllowed()) { EmployeeListColumn.Width = new GridLength(1.0F, GridUnitType.Auto); var thumbnails = Employees .Select(e => e.ThumbnailID) .Where(x => x != Guid.Empty).ToArray(); EmployeeList.ItemsSource = Employees; SelectedEmployee = Employees.FirstOrDefault(x => x.ID == Host.KanbanSettings.StatusSettings.SelectedEmployee) ?? Employees.First(); EmployeeList.SelectedItem = SelectedEmployee; if (thumbnails.Any()) new Client().Query( new Filter(x => x.ID).InList(thumbnails), InABox.Core.Columns.None().Add(x => x.ID, x => x.Data), null, CoreRange.All, (data, error) => { if (data != null) ProcessThumbnails(data); } ); } else { EmployeeListColumn.Width = new GridLength(0.0F, GridUnitType.Pixel); EmployeeList.ItemsSource = Employees; SelectedEmployee = Employees.FirstOrDefault(x => x.ID == Host.KanbanSettings.StatusSettings.SelectedEmployee) ?? Employees.First(); EmployeeList.SelectedItem = SelectedEmployee; } } private bool _updatingEmployees = false; private void ProcessThumbnails(CoreTable data) { Dispatcher.Invoke(() => { foreach (var row in data.Rows) { var id = row.Get(x => x.ID); var model = Employees.FirstOrDefault(x => x.ThumbnailID.Equals(id)); if (model != null) { model.Image = ImageUtils.LoadImage(row.Get(x => x.Data)); } } _updatingEmployees = true; EmployeeList.ItemsSource = null; EmployeeList.ItemsSource = Employees; SelectedEmployee = Employees.FirstOrDefault(x => x.ID == Host.KanbanSettings.StatusSettings.SelectedEmployee) ?? Employees.First(); EmployeeList.SelectedItem = SelectedEmployee; _updatingEmployees = false; }); } private void Employees_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (_updatingEmployees || EventSuppressor.IsSet(Suppress.This)) return; SelectedEmployee = (EmployeeList.SelectedItem as EmployeeModel)!; Host.KanbanSettings.StatusSettings.SelectedEmployee = SelectedEmployee.ID; Host.SaveSettings(); SelectedTasks.Clear(); if (IsReady) Refresh(); } #endregion #region Kanbans private List AllTasks { get; set; } = new(); private IEnumerable Tasks => Columns.SelectMany(x => x.Tasks); private readonly List SelectedTasks = new(); private void CreateTask() { var result = Host.CreateKanban( kanban => { kanban.EmployeeLink.ID = SelectedEmployee.ID != CoreUtils.FullGuid ? SelectedEmployee.ID : App.EmployeeID; kanban.ManagerLink.ID = App.EmployeeID; kanban.ManagerLink.UserLink.ID = ClientFactory.UserGuid; }); if (result != null) Refresh(); } private void DoEdit(TaskModel task) { var result = Host.EditReferences(new[] { task }); if (result) { Refresh(); } } private void EditTask_Executed(object sender, ExecutedRoutedEventArgs e) { if (e.Parameter is not TaskModel model) return; DoEdit(model); } private void OpenTaskMenu_Executed(object sender, ExecutedRoutedEventArgs e) { if (e.Parameter is not KanbanResources.OpenTaskMenuCommandArgs args) return; Host.PopulateMenu(this, args.Model, args.Menu); } private void SelectTask_Executed(object sender, ExecutedRoutedEventArgs e) { if (e.Parameter is not TaskModel model) return; if (!SelectedTasks.Remove(model)) { SelectedTasks.Add(model); } } private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e) { e.CanExecute = true; } private void ItemsControl_DragOver(object sender, DragEventArgs e) { if (sender is not FrameworkElement element || element.Tag is not TasksByStatusColumn column) return; 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.Status != column.Status && !SelectedTasks.Any(x => x.Locked)) { e.Effects = DragDropEffects.Move; } } } private void ChangeStatus(IEnumerable tasks, KanbanStatus status) { var models = tasks .Where(x => !x.Status.Equals(status)) .Where(x => !x.Locked) .ToList(); if (!models.Any()) { return; } if(status == KanbanStatus.Complete) { if (!MessageWindow.ShowYesNo($"Are you sure you want to complete the selected tasks?", "Confirm Completion")) return; } var completed = DateTime.Now; var kanbans = Host.LoadKanbans(tasks, InABox.Core.Columns.Required().Add(x => x.ID).Add(x => x.Status)); foreach (var kanban in kanbans) { kanban.Status = status; } if (status == KanbanStatus.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.Status = status; } if (status == KanbanStatus.Complete) { foreach (var model in models) { model.CompletedDate = completed; } } FilterKanbans(); } private void ItemsControl_Drop(object sender, DragEventArgs e) { if (sender is not FrameworkElement element || element.Tag is not TasksByStatusColumn column) return; if (e.Data.GetDataPresent(typeof(TaskModel))) { ChangeStatus(SelectedModels(e.Data.GetData(typeof(TaskModel)) as TaskModel), column.Status); } } #endregion #region Filters private Guid JobFilterID = Guid.Empty; private string SearchText = ""; private Guid SelectedType = CoreUtils.FullGuid; private void FilterButton_OnFilterRefresh() { Refresh(); } private void FilterButton_OnFiltersSelected(DynamicGridSelectedFilterSettings filters) { Host.KanbanSettings.Filters = filters; Host.SaveSettings(); } private static Filter GetSearchFilter(Expression> expression, string searchText) where T : Entity, new() { Filter? result = null; var comps = searchText.Trim().Split(' '); foreach (var comp in comps) result = result == null ? new Filter(expression).Contains(comp) : result.And(expression).Contains(comp); return result ?? new Filter().All(); } private Filter GetKanbanSubscriberFilter() { var filter = new Filter(x => x.Kanban.Closed).IsEqualTo(DateTime.MinValue); if (Host.Master != null) filter = filter.And(x => x.Kanban.JobLink.ID).IsEqualTo(Host.Master.ID); // 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(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? GetKanbanFilter() { var filters = new Filters(); filters.Add(new Filter(x => x.Closed).IsEqualTo(DateTime.MinValue)); filters.Add(FilterButton.GetFilter()); if (Host.Master != null) filters.Add(new Filter(x => x.JobLink.ID).IsEqualTo(Host.Master.ID)); if (SelectedEmployee.ID != CoreUtils.FullGuid) { if (SelectedEmployee.ID != Guid.Empty) { var empfilter = new Filter(x => x.EmployeeLink.ID).IsEqualTo(SelectedEmployee.ID) .Or(x => x.ManagerLink.ID).IsEqualTo(SelectedEmployee.ID); filters.Add(empfilter); } else { filters.Add(new Filter(x => x.EmployeeLink.ID).IsEqualTo(SelectedEmployee.ID)); } } if (SelectedEmployee.ID != App.EmployeeID) filters.Add(new Filter(x => x.Private).IsEqualTo(false)); return filters.Combine(); } 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) { if (!IsReady || EventSuppressor.IsSet(Suppress.This)) return; if (e.AddedItems.Count > 0) { var item = (KeyValuePair)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) { if (JobFilterID != Guid.Empty) { JobFilterBtn.Content = "Filter Job"; JobFilterID = Guid.Empty; FilterKanbans(); return; } var window = new ThemableWindow(); var grid = new ProjectsGrid(); grid.Reconfigure(options => { options.EditRows = false; options.DeleteRows = false; options.AddRows = false; options.MultiSelect = false; options.ExportData = false; options.ImportData = false; }); 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(x => x.ID); JobFilterBtn.Content = item.Get(x => x.JobNumber) + " (click to cancel)"; FilterKanbans(); } 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 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 GetKanbanColumns() { return InABox.Core.Columns.None().Add( x => x.ID, x => x.DueDate, x => x.Completed, x => x.Summary, x => x.Status, 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.EstimatedTime, x => x.Locked); } public void Refresh() { using var cursor = new WaitCursor(); IEnumerable kanbans; if (SelectedEmployee.ID != CoreUtils.FullGuid && SelectedEmployee.ID != Guid.Empty) { var filter = new Filter(x => x.ID).InQuery(GetKanbanSubscriberFilter(), x => x.Kanban.ID); filter.And(FilterButton.GetFilter() ?? new Filter().All()); kanbans = Client.Query( filter, GetKanbanColumns().Cast(), new SortOrder(x => x.DueDate) { Direction = SortDirection.Ascending } ).ToObjects(); } else { kanbans = new Client().Query( GetKanbanFilter(), GetKanbanColumns().Cast(), new SortOrder(x => x.DueDate) { Direction = SortDirection.Ascending } ).ToObjects(); } AllTasks = CreateModels(kanbans).ToList(); FilterKanbans(); } private IEnumerable CreateModels(IEnumerable 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.Status = kanban.Status; 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; model.EstimatedTime = kanban.EstimatedTime; if (kanban.Notes is not null) { model.Notes = string.Join( "\n===================================\n", kanban.Notes.Reverse()); } 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}"; } else { model.AssignedTo = ""; } } 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; } } } /// /// Take the full list of kanbans loaded from the database, and filter based on the search UI elements, filtering into the columns. /// private void FilterKanbans() { IEnumerable 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); SelectedTasks.Clear(); foreach (var column in Columns) { column.Tasks.Clear(); } foreach (var task in filtered) { var column = GetColumn(task.Status); column?.Tasks.Add(task); if (task.Checked) { SelectedTasks.Add(task); } } } #endregion #region ITaskControl public ITaskHost Host { get; set; } public KanbanViewType KanbanViewType => KanbanViewType.Status; public bool IsReady { get; set; } public string SectionName => "Task List"; public DataModel DataModel(Selection selection) { var ids = SelectedModels().Select(x => x.ID).ToArray(); return new AutoDataModel(new Filter(x => x.ID).InList(ids)); } public IEnumerable SelectedModels(TaskModel? sender = null) { if(sender is null) { return SelectedTasks; } else { var result = SelectedTasks.ToList(); if (!result.Contains(sender)) { result.Add(sender); } return result; } } #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().Query( GetKanbanFilter(), InABox.Core.Columns.None().Add(form.Fields), LookupFactory.DefineSort() ); 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( 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(); } }