Procházet zdrojové kódy

Overhauled TasksByStatus screen

Kenric Nugteren před 1 rokem
rodič
revize
c9f7e17773

+ 4 - 0
prs.desktop/PRSDesktop.csproj

@@ -846,6 +846,10 @@
 
     <ItemGroup>
       <Page Remove="Panels\Schedule\**" />
+      <Compile Remove="Panels\Tasks\TasksByStatusControlOld.xaml.cs" />
+      <Compile Remove="Panels\Tasks\TasksByUserControl.xaml.cs" />
+      <Page Remove="Panels\Tasks\TasksByStatusControlOld.xaml" />
+      <Page Remove="Panels\Tasks\TasksByUserControl.xaml" />
       <Page Update="Panels\Suppliers\Bills\SupplierBillPanel.xaml">
         <Generator>MSBuild:Compile</Generator>
         <XamlRuntime>Wpf</XamlRuntime>

+ 2 - 1
prs.desktop/Panels/Tasks/ITaskControl.cs

@@ -13,9 +13,10 @@ namespace PRSDesktop
         IList<KanbanType> KanbanTypes { get; }
 
         KanbanSettings KanbanSettings { get; }
+
         void SaveSettings();
 
-        void PopulateMenu(ITaskControl control, ContextMenu menu);
+        void PopulateMenu(ITaskControl control, TaskModel task, ContextMenu menu);
 
         Kanban? CreateKanban(Action<Kanban> customise);
         IEnumerable<Kanban> LoadKanbans(IEnumerable<TaskModel> models, Columns<Kanban> columns);

+ 17 - 8
prs.desktop/Panels/Tasks/KanbanResources.xaml

@@ -1,8 +1,10 @@
 <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                     xmlns:wpf="clr-namespace:InABox.Wpf;assembly=InABox.Wpf"
+                    xmlns:local="clr-namespace:PRSDesktop"
                     x:Class="PRSDesktop.KanbanResources">
     <wpf:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
+    <local:ViewModeToKanbanTemplateConverter x:Key="TypeToTemplateConverter"/>
 
     <Style x:Key="employeeStyle" TargetType="{x:Type TextBlock}">
         <Style.Triggers>
@@ -44,9 +46,13 @@
         </Style.Triggers>
     </Style>
     
-    <DataTemplate x:Key="FullKanban">
-        <Border BorderBrush="Gray" BorderThickness="0.75" CornerRadius="5" Background="{Binding ColorKey}"
-                    Margin="0,2,0,2" MouseLeftButtonDown="Border_MouseLeftButtonDown" Tag="{Binding}">
+    <DataTemplate x:Key="FullKanban" DataType="{x:Type local:TaskModel}">
+        <Border BorderBrush="Gray" BorderThickness="0.75" CornerRadius="5"
+                Margin="0,2,0,2" MouseLeftButtonDown="Border_MouseLeftButtonDown" Tag="{Binding}"
+                MouseMove="Border_MouseMove" ContextMenuOpening="TaskMenu_Opened">
+            <Border.Background>
+                <SolidColorBrush Color="{Binding Color}"/>
+            </Border.Background>
             <Grid Margin="4">
                 <Grid.ColumnDefinitions>
                     <ColumnDefinition x:Name="colImage" Width="Auto" />
@@ -130,15 +136,18 @@
             </Border.ToolTip>
 
             <Border.ContextMenu>
-                <ContextMenu x:Name="TaskMenu" Tag="{Binding}" Opened="TaskMenu_Opened" />
-
+                <ContextMenu x:Name="TaskMenu" Tag="{Binding}"/>
             </Border.ContextMenu>
         </Border>
     </DataTemplate>
 
     <DataTemplate x:Key="CompactKanban">
-        <Border BorderBrush="Gray" BorderThickness="0.75" CornerRadius="5" Background="{Binding ColorKey}" Margin="0,2,0,2" 
-                MouseLeftButtonDown="Border_MouseLeftButtonDown" Tag="{Binding}">
+        <Border BorderBrush="Gray" BorderThickness="0.75" CornerRadius="5" Margin="0,2,0,2" 
+                MouseLeftButtonDown="Border_MouseLeftButtonDown" Tag="{Binding}"
+                MouseMove="Border_MouseMove" ContextMenuOpening="TaskMenu_Opened">
+            <Border.Background>
+                <SolidColorBrush Color="{Binding Color}"/>
+            </Border.Background>
 
             <DockPanel Margin="4">
                 <TextBlock DockPanel.Dock="Right" Text="{Binding DueDate, StringFormat='{}{0:dd MMM yy}'}" FontWeight="Bold" FontSize="12" VerticalAlignment="Center" HorizontalAlignment="Center" />
@@ -187,7 +196,7 @@
             </Border.ToolTip>
 
             <Border.ContextMenu>
-                <ContextMenu x:Name="TaskMenu" Tag="{Binding}" Opened="TaskMenu_Opened" />
+                <ContextMenu x:Name="TaskMenu" Tag="{Binding}"/>
             </Border.ContextMenu>
 
         </Border>

+ 74 - 13
prs.desktop/Panels/Tasks/KanbanResources.xaml.cs

@@ -1,41 +1,102 @@
 using System;
 using System.Collections.Generic;
+using System.Globalization;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
 using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
 using System.Windows.Input;
 
 namespace PRSDesktop;
 
 public partial class KanbanResources : ResourceDictionary
 {
-    private static ICommand editTask = new RoutedCommand();
-    private static ICommand selectTask = new RoutedCommand();
-    private static ICommand openTaskMenu = new RoutedCommand();
+    public static KanbanResources Resources = new KanbanResources();
 
-    public static ICommand EditTask => editTask;
-    public static ICommand SelectTask => selectTask;
-    public static ICommand OpenTaskMenu => openTaskMenu;
+    private static RoutedCommand editTask = new RoutedCommand();
+    private static RoutedCommand selectTask = new RoutedCommand();
+    private static RoutedCommand openTaskMenu = new RoutedCommand();
+
+    public class OpenTaskMenuCommandArgs
+    {
+        public TaskModel Model { get; set; }
+
+        public ContextMenu Menu { get; set; }
+
+        public OpenTaskMenuCommandArgs(TaskModel model, ContextMenu menu)
+        {
+            Model = model;
+            Menu = menu;
+        }
+    }
+
+    public static RoutedCommand EditTask => editTask;
+    public static RoutedCommand SelectTask => selectTask;
+    public static RoutedCommand OpenTaskMenu => openTaskMenu;
 
     private void Border_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
     {
-        if ((sender as FrameworkElement)?.Tag is not TaskModel model) return;
-        if(e.ClickCount > 2)
+        if (sender is not FrameworkElement element || element.Tag is not TaskModel model) return;
+        if(e.ClickCount > 1)
         {
-            EditTask.Execute(model);
+            EditTask.Execute(model, element);
         }
     }
 
     private void CheckBox_Checked(object sender, RoutedEventArgs e)
     {
-        if ((sender as FrameworkElement)?.Tag is not TaskModel model) return;
-        SelectTask.Execute(model);
+        if (sender is not FrameworkElement element || element.Tag is not TaskModel model) return;
+        SelectTask.Execute(model, element);
+    }
+
+    private void TaskMenu_Opened(object sender, ContextMenuEventArgs e)
+    {
+        if (sender is not FrameworkElement element || element.Tag is not TaskModel model) return;
+        OpenTaskMenu.Execute(new OpenTaskMenuCommandArgs(model, element.ContextMenu), element);
     }
 
-    private void TaskMenu_Opened(object sender, RoutedEventArgs e)
+    private void Border_MouseMove(object sender, MouseEventArgs e)
     {
+        if(sender is not Border border)
+        {
+            return;
+        }
         if ((sender as FrameworkElement)?.Tag is not TaskModel model) return;
-        OpenTaskMenu.Execute(model);
+        if (e.LeftButton == MouseButtonState.Pressed && !model.Locked)
+        {
+            DragDrop.DoDragDrop(border, model, DragDropEffects.Move);
+        }
     }
 }
+
+public enum KanbanViewMode
+{
+    Full,
+    Compact
+}
+
+public class ViewModeToKanbanTemplateConverter : IValueConverter
+{
+    public object? Convert(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        if(value is not KanbanViewMode mode)
+        {
+            return null;
+        }
+        if(mode == KanbanViewMode.Full)
+        {
+            return KanbanResources.Resources["FullKanban"] as DataTemplate;
+        }
+        else
+        {
+            return KanbanResources.Resources["CompactKanban"] as DataTemplate;
+        }
+    }
+
+    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        throw new NotImplementedException();
+    }
+}

+ 9 - 2
prs.desktop/Panels/Tasks/TaskModel.cs

@@ -10,8 +10,12 @@ namespace PRSDesktop;
 [DoNotNotify]
 public class TaskModel
 {
-    public BitmapImage Image { get; set; }
+    public string Title { get; set; }
+    public Guid ID { get; set; }
+    public string Description { get; set; }
+    public BitmapImage? Image { get; set; }
     public Color Color { get; set; }
+
     public bool Attachments { get; set; }
     public DateTime DueDate { get; set; }
     public DateTime CompletedDate { get; set; }
@@ -40,7 +44,10 @@ public class TaskModel
             var bFound = JobNumber?.ToUpper().Contains(search.ToUpper()) == true
                          || JobName?.ToUpper().Contains(search.ToUpper()) == true
                          || Title?.ToUpper().Contains(search.ToUpper()) == true
-                         || Description?.ToUpper().Contains(search.ToUpper()) == true;
+                         || Description?.ToUpper().Contains(search.ToUpper()) == true
+                         || Manager?.ToUpper().Contains(search.ToUpper()) == true
+                         || AssignedTo?.ToUpper().Contains(search.ToUpper()) == true
+                         || Number.ToString().Contains(search.ToUpper()) == true;
             if (!bFound)
                 return false;
         }

+ 2 - 2
prs.desktop/Panels/Tasks/TaskPanel.xaml

@@ -33,9 +33,9 @@
             <local:TasksByStatusControl x:Name="TasksByStatus" />
         </dynamicGrid:DynamicTabItem>
 
-        <dynamicGrid:DynamicTabItem x:Name="TasksByUserTabItem" Header="By User">
+        <!--dynamicGrid:DynamicTabItem x:Name="TasksByUserTabItem" Header="By User">
             <local:TasksByUserControl x:Name="TasksByUser" />
-        </dynamicGrid:DynamicTabItem>
+        </dynamicGrid:DynamicTabItem-->
 
         <dynamicGrid:DynamicTabItem x:Name="TasksPlannerTabItem" Header="Planner">
             <local:TaskPlannerControl x:Name="TasksPlanner" />

+ 47 - 58
prs.desktop/Panels/Tasks/TaskPanel.xaml.cs

@@ -36,7 +36,6 @@ namespace PRSDesktop
         public Job Job { get; set; }
         public JobPanelSettings Settings { get; set; }
         
-
         public TaskPanel()
         {
             InitializeComponent();
@@ -49,7 +48,8 @@ namespace PRSDesktop
             }
         }
 
-        
+        #region Menu
+
         private void CompleteTask(ITaskControl control, RoutedEventArgs e, DateTime completed)
         {
             if (MessageBox.Show($"Are you sure you want to complete the selected tasks?", "Confirm Completion",
@@ -111,16 +111,11 @@ namespace PRSDesktop
             return true;
         }
 
-        /// <summary>
-        /// <paramref name="menu"/> should have <see cref="FrameworkElement.Tag"/> set to a <see cref="TaskModel"/>.
-        /// </summary>
-        /// <param name="control"></param>
-        /// <param name="menu"></param>
-        public void PopulateMenu(ITaskControl control, ContextMenu menu)
+        public void PopulateMenu(ITaskControl control, TaskModel task, ContextMenu menu)
         {
-           menu.Items.Clear();
+            menu.Items.Clear();
             
-            var models = control.SelectedModels((menu.Tag as TaskModel)!).ToArray();
+            var models = control.SelectedModels(task).ToArray();
             var references = GetReferences(models);
             var bLinks = references.Any(x => x.ReferenceType() != null);
             var referencetypes = references.Select(x => x.ReferenceType()).Distinct().ToArray();
@@ -155,12 +150,11 @@ namespace PRSDesktop
                 var digitalForms = new MenuItem { Header = "Digital Forms" };
 
                 var model = models.First();
-                Guid kanbanID = Guid.Parse(model.ID);
 
                 DynamicGridUtils.PopulateFormMenu<KanbanForm, Kanban, KanbanLink>(
                     digitalForms,
-                    kanbanID,
-                    () => new Client<Kanban>().Load(new Filter<Kanban>(x => x.ID).IsEqualTo(kanbanID)).First(),
+                    model.ID,
+                    () => new Client<Kanban>().Load(new Filter<Kanban>(x => x.ID).IsEqualTo(model.ID)).First(),
                     model.EmployeeID == App.EmployeeID);
 
                 menu.Items.Add(digitalForms);
@@ -631,34 +625,6 @@ namespace PRSDesktop
 
             return result;
         }
-
-        private void TaskPanels_SelectionChanged(object sender, SelectionChangedEventArgs e)
-        {
-            if (!IsReady)
-                return;
-
-            if (e.Source is not TabControl)
-                return;
-
-            if (_bTabChanging)
-                return;
-            try
-            {
-                _bTabChanging = true;
-                var panel = GetCurrentPanel();
-                if(panel is not null)
-                {
-                    KanbanSettings.ViewType = panel.KanbanViewType;
-                    new UserConfiguration<KanbanSettings>().Save(KanbanSettings);
-                    panel.Refresh(false);
-                }
-            }
-            finally
-            {
-                _bTabChanging = false;
-            }
-        }
-
         private void CreateJobSubMenu(ITaskControl control, MenuItem job, IEnumerable<TaskModel> tasks)
         {
             job.Items.Clear();
@@ -690,6 +656,35 @@ namespace PRSDesktop
             }
         }
 
+        #endregion
+
+        private void TaskPanels_SelectionChanged(object sender, SelectionChangedEventArgs e)
+        {
+            if (!IsReady)
+                return;
+
+            if (e.Source is not TabControl)
+                return;
+
+            if (_bTabChanging)
+                return;
+            try
+            {
+                _bTabChanging = true;
+                var panel = GetCurrentPanel();
+                if(panel is not null)
+                {
+                    KanbanSettings.ViewType = panel.KanbanViewType;
+                    new UserConfiguration<KanbanSettings>().Save(KanbanSettings);
+                    panel.Refresh(false);
+                }
+            }
+            finally
+            {
+                _bTabChanging = false;
+            }
+        }
+
         #region Get/Save Settings
 
         private KanbanSettings? _settings;
@@ -865,12 +860,11 @@ namespace PRSDesktop
             var load = new List<Guid>();
             foreach (var model in models)
             {
-                var id = Guid.Parse(model.ID);
-                var entity = _entitycache.FirstOrDefault(x => Equals(x.Item1, id) && x.Item2 is TEntity) as TEntity;
+                var entity = _entitycache.FirstOrDefault(x => Equals(x.Item1, model.ID) && x.Item2 is TEntity) as TEntity;
                 if (entity is not null)
                     result.Add(entity);
                 else
-                    load.Add(id);
+                    load.Add(model.ID);
             }
 
             if (load.Any())
@@ -895,12 +889,11 @@ namespace PRSDesktop
             if (!result.Any())
                 foreach (var model in models)
                 {
-                    var id = Guid.Parse(model.ID);
                     result = new Client<TEntity>().Load(
-                        new Filter<TEntity>(x => x.ID).InQuery(new Filter<TEntityKanban>(x => x.Kanban.ID).IsEqualTo(id),
+                        new Filter<TEntity>(x => x.ID).InQuery(new Filter<TEntityKanban>(x => x.Kanban.ID).IsEqualTo(model.ID),
                             x => x.Entity.ID));
                     foreach (var r in result)
-                        _entitycache.Add(new Tuple<Guid, Entity>(id, r));
+                        _entitycache.Add(new Tuple<Guid, Entity>(model.ID, r));
                 }
 
             return result;
@@ -932,7 +925,7 @@ namespace PRSDesktop
             var linktask = Task.Run(() =>
             {
                 var link = new TEntityKanban();
-                link.Kanban.ID = Guid.Parse(model.ID);
+                link.Kanban.ID = model.ID;
                 link.Entity.ID = entityid;
                 new Client<TEntityKanban>().Save(link, "");
             });
@@ -1010,7 +1003,7 @@ namespace PRSDesktop
 
         public void DeleteKanbans(IEnumerable<TaskModel> models, string auditnote)
         {
-            var kanbans = models.Select(x => new Kanban { ID = Guid.Parse(x.ID) }).ToList();
+            var kanbans = models.Select(x => new Kanban { ID = x.ID }).ToList();
             DoDelete(kanbans, auditnote);
         }
 
@@ -1025,8 +1018,7 @@ namespace PRSDesktop
                 });
             if (result != null)
             {
-                var id = Guid.Parse(model.ID);
-                DoCache(id, result);
+                DoCache(model.ID, result);
                 DoLink<RequisitionKanban, Requisition, RequisitionLink>(model, result.ID);
             }
 
@@ -1053,8 +1045,7 @@ namespace PRSDesktop
                 });
             if (result != null)
             {
-                var id = Guid.Parse(model.ID);
-                DoCache(id, result);
+                DoCache(model.ID, result);
                 //DoLink<SetoutKanban, Setout, SetoutLink>(model, result.ID);
             }
 
@@ -1080,8 +1071,7 @@ namespace PRSDesktop
                 });
             if (result != null)
             {
-                var id = Guid.Parse(model.ID);
-                DoCache(id, result);
+                DoCache(model.ID, result);
                 DoLink<DeliveryKanban, Delivery, DeliveryLink>(model, result.ID);
             }
 
@@ -1103,8 +1093,7 @@ namespace PRSDesktop
                 order => { customise?.Invoke(order); });
             if (result != null)
             {
-                var id = Guid.Parse(model.ID);
-                DoCache(id, result);
+                DoCache(model.ID, result);
                 DoLink<PurchaseOrderKanban, PurchaseOrder, PurchaseOrderLink>(model, result.ID);
             }
 
@@ -1147,7 +1136,7 @@ namespace PRSDesktop
         {
             var result = new List<KanbanReferences>();
 
-            var ids = models.Select(x => Guid.Parse(x.ID)).ToArray();
+            var ids = models.Select(x => x.ID).ToArray();
 
             var query = new MultiQuery();
 

+ 43 - 23
prs.desktop/Panels/Tasks/TasksByStatusControl.xaml

@@ -93,36 +93,56 @@
 
             </DockPanel>
         </Border>
-        <ItemsControl x:Name="ColumnsList" ItemsSource="{Binding ElementName=Control,Path=Columns}">
+        <ItemsControl x:Name="ColumnsList" ItemsSource="{Binding ElementName=Control,Path=Columns}"
+                      Grid.Row="1" Grid.Column="1">
             <ItemsControl.ItemsPanel>
                 <ItemsPanelTemplate>
-                    <StackPanel Orientation="Horizontal"/>
+                    <UniformGrid Rows="1"/>
                 </ItemsPanelTemplate>
             </ItemsControl.ItemsPanel>
             <ItemsControl.ItemTemplate>
                 <DataTemplate>
-                    <Grid ContextMenuOpening="Column_ContextMenuOpening"
+                    <Border BorderBrush="LightGray" BorderThickness="0.75,0,0,0">
+                        <Grid ContextMenuOpening="Column_ContextMenuOpening"
                           Tag="{Binding}">
-                        <Grid.RowDefinitions>
-                            <RowDefinition Height="Auto"/>
-                            <RowDefinition Height="*"/>
-                        </Grid.RowDefinitions>
-                        <StackPanel Orientation="Vertical"
-                                    Grid.Row="0">
-                            <TextBlock Text="{Binding Title}" FontSize="16" FontWeight="DemiBold" HorizontalAlignment="Left"
-                                   Margin="10,0,10,0" />
-                            <TextBlock FontSize="12" HorizontalAlignment="Left" Margin="10,0,5,0">
-                            <Run Text="{Binding NumTasks}" />
-                            <Run Text="Tasks /" />
-                            <Run Text="{Binding NumHours, StringFormat={}{0:F2}}"/>
-                            <Run Text="Hours" />
-                            </TextBlock>
-                        </StackPanel>
-                        <ItemsControl Grid.Row="1"
-                                      ItemsSource="{Binding Tasks}"
-                                      ItemTemplate="{StaticResource FullKanban}">
-                        </ItemsControl>
-                    </Grid>
+                            <Grid.ContextMenu>
+                                <ContextMenu/>
+                            </Grid.ContextMenu>
+                            <Grid.RowDefinitions>
+                                <RowDefinition Height="Auto"/>
+                                <RowDefinition Height="*"/>
+                            </Grid.RowDefinitions>
+                            <StackPanel Orientation="Vertical"
+                                        Grid.Row="0">
+                                <TextBlock Text="{Binding Title,Mode=OneWay}" FontSize="16" FontWeight="DemiBold" HorizontalAlignment="Left"
+                                           Margin="10,0,10,0" />
+                                <TextBlock FontSize="12" HorizontalAlignment="Left" Margin="10,0,5,0">
+                                    <Run Text="{Binding NumTasks,Mode=OneWay}" />
+                                    <Run Text="Tasks /" />
+                                    <Run Text="{Binding NumHours,Mode=OneWay, StringFormat={}{0:F2}}"/>
+                                    <Run Text="Hours" />
+                                </TextBlock>
+                            </StackPanel>
+                            <ItemsControl Grid.Row="1" Margin="5,0"
+                                          AllowDrop="True"
+                                          DragOver="ItemsControl_DragOver"
+                                          Drop="ItemsControl_Drop"
+                                          Tag="{Binding}"
+                                          ItemsSource="{Binding Tasks,Mode=OneWay}">
+                                <ItemsControl.Style>
+                                    <Style TargetType="{x:Type ItemsControl}" BasedOn="{StaticResource VirtualisedItemsControlStyle}">
+                                        <Setter Property="ItemTemplate" Value="{StaticResource FullKanban}"/>
+                                        <Style.Triggers>
+                                            <DataTrigger Binding="{Binding Mode,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type  local:TasksByStatusControl}}}"
+                                                         Value="{x:Static local:KanbanViewMode.Compact}">
+                                                <Setter Property="ItemTemplate" Value="{StaticResource CompactKanban}"/>
+                                            </DataTrigger>
+                                        </Style.Triggers>
+                                    </Style>
+                                </ItemsControl.Style>
+                            </ItemsControl>
+                        </Grid>
+                    </Border>
                 </DataTemplate>
             </ItemsControl.ItemTemplate>
         </ItemsControl>

+ 657 - 40
prs.desktop/Panels/Tasks/TasksByStatusControl.xaml.cs

@@ -2,12 +2,11 @@
 using InABox.Clients;
 using InABox.Core;
 using InABox.WPF;
-using NPOI.SS.Formula.Functions;
-using Syncfusion.UI.Xaml.Kanban;
 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;
@@ -16,14 +15,23 @@ 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 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;
 
-public class TasksByStatusColumn
+public class TasksByStatusColumn : INotifyPropertyChanged
 {
     public string Category { get; }
 
@@ -33,12 +41,34 @@ public class TasksByStatusColumn
 
     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)
     {
         Category = category;
         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>
 /// Interaction logic for TasksByStatusControl.xaml
 /// </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();
 
@@ -76,11 +143,24 @@ public partial class TasksByStatusControl : UserControl, ITaskControl
         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 = LoadEmployees();
+        var employeesTask = Task.Run(() => LoadEmployees());
         var kanbanTypesTask = Task.Run(() => LoadKanbanTypes());
 
         SetupToolbar();
@@ -92,6 +172,8 @@ public partial class TasksByStatusControl : UserControl, ITaskControl
 
         kanbanTypesTask.Wait();
         SetupKanbanTypesLookup(kanbanTypesTask.Result);
+
+        Mode = Host.KanbanSettings.StatusSettings.CompactView ? KanbanViewMode.Compact : KanbanViewMode.Full;
     }
 
     private void SetupToolbar()
@@ -108,6 +190,8 @@ public partial class TasksByStatusControl : UserControl, ITaskControl
 
     private void SetupColumns()
     {
+        Columns.SupressNotification = true;
+
         Columns.Clear();
 
         Columns.Add(new TasksByStatusColumn("Open", "To Do"));
@@ -118,6 +202,13 @@ public partial class TasksByStatusControl : UserControl, ITaskControl
 
         if (Host.KanbanSettings.StatusSettings.IncludeCompleted)
             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)
@@ -205,7 +296,7 @@ public partial class TasksByStatusControl : UserControl, ITaskControl
 
     private EmployeeModel SelectedEmployee;
 
-    private async Task LoadEmployees()
+    private void LoadEmployees()
     {
         Employees.Clear();
 
@@ -215,20 +306,22 @@ public partial class TasksByStatusControl : UserControl, ITaskControl
                     .Or(x => x.FinishDate).IsGreaterThan(DateTime.Today))
             : new Filter<Employee>(x => x.ID).IsEqualTo(App.EmployeeID);
 
-        var employees = await Task.Run(() => Client.Query<Employee>(
+        var employees = Client.Query<Employee>(
             employeeFilter,
             new Columns<Employee>(x => x.ID)
                 .Add(x => x.Thumbnail.ID)
-                .Add(x => x.Name)));
+                .Add(x => x.Name));
 
         var anonymous = PRSDesktop.Resources.anonymous.AsBitmapImage();
+        anonymous.Freeze();
         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));
         }
 
-        EmployeeModel? selected = null;
         foreach (var employee in employees.ToObjects<Employee>())
         {
             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)
             {
                 Employees.Insert(0, model);
-                selected = model;
             }
             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
 
     #region Kanbans
 
+    private List<TaskModel> AllTasks { get; set; } = new();
     private IEnumerable<TaskModel> Tasks => Columns.SelectMany(x => x.Tasks);
 
     private readonly List<TaskModel> SelectedTasks = new();
@@ -344,9 +449,9 @@ public partial class TasksByStatusControl : UserControl, ITaskControl
 
     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)
@@ -361,85 +466,597 @@ public partial class TasksByStatusControl : UserControl, ITaskControl
 
     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)
     {
-
+        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<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)
     {
+        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();
     }
 }

+ 4 - 4
prs.desktop/Panels/Tasks/TasksByStatusControlOld.xaml

@@ -1,4 +1,4 @@
-<UserControl
+<!--UserControl
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@@ -14,7 +14,7 @@
         <wpf:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
 
         <DataTemplate x:Key="KanbanHeader">
-            <!--<Border BorderThickness="0.75" BorderBrush="Gray" CornerRadius="5,5,0,0" Margin="0,10,0,0" Padding="5" Width="{Binding Converter={StaticResource TaskHeaderWidthConverter}}" Background="WhiteSmoke">-->
+            <!-<Border BorderThickness="0.75" BorderBrush="Gray" CornerRadius="5,5,0,0" Margin="0,10,0,0" Padding="5" Width="{Binding Converter={StaticResource TaskHeaderWidthConverter}}" Background="WhiteSmoke">->
             <StackPanel Orientation="Vertical">
                 <TextBlock Text="{Binding  Header}" FontSize="16" FontWeight="DemiBold" HorizontalAlignment="Left"
                            Margin="10,0,10,0" />
@@ -25,7 +25,7 @@
                     <Run Text="Hours" />
                 </TextBlock>
             </StackPanel>
-            <!--</Border>-->
+            <!-</Border>->
         </DataTemplate>
 
         
@@ -120,4 +120,4 @@
             CardDragEnd="Kanban_CardDragEnd" />
 
     </Grid>
-</UserControl>
+</UserControl-->

+ 3 - 837
prs.desktop/Panels/Tasks/TasksByStatusControlOld.xaml.cs

@@ -22,7 +22,7 @@ using InABox.WPF;
 using org.omg.CORBA;
 using Syncfusion.UI.Xaml.Kanban;
 using Color = System.Drawing.Color;
-
+/*
 namespace PRSDesktop
 {
     public class StatusTasksHeaderTimeConverter : IValueConverter
@@ -99,15 +99,7 @@ namespace PRSDesktop
                 InitializeComponent();
         }
 
-        public string SectionName => "Tasks By Status";
-
-        public DataModel DataModel(Selection selection)
-        {
-            var ids = SelectedModels().Select(x => Guid.Parse(x.ID)).ToArray();
-            return new AutoDataModel<Kanban>(new Filter<Kanban>(x => x.ID).InList(ids));
-        }
-
-        private void ResizeColumns()
+        /*private void ResizeColumns()
         {
             //if (!bResizeRequired)
             //    return;
@@ -126,835 +118,9 @@ namespace PRSDesktop
             }
         }
 
-
-        private void TaskMenu_Opened(object sender, RoutedEventArgs e)
-        {
-            Host.PopulateMenu(this, sender as ContextMenu);
-        }
-
         private void Kanban_SizeChanged(object sender, SizeChangedEventArgs e)
         {
             Kanban.ColumnWidth = Kanban.ActualWidth / Kanban.Columns.Count - 1.0F;
         }
-
-        private void Kanban_CardDragStart(object sender, KanbanDragStartEventArgs e)
-        {
-            var models = SelectedModels(e.SelectedCard.Content as TaskModel);
-            if (models.Any(x => x.Locked))
-                e.IsCancel = true;
-        }
-
-        private void Kanban_CardDragEnd(object sender, KanbanDragEndEventArgs e)
-        {
-            using (new WaitCursor())
-            {
-                var target = e.TargetColumn.Categories;
-                var models = SelectedModels(e.SelectedCard.Content as TaskModel).Where(x => !Equals(x.Category, target)).ToArray();
-                if (!models.Any())
-                    return;
-                var kanbans = Host.LoadKanbans(models, new Columns<Kanban>(x => x.ID, x => x.Category));
-                foreach (var kanban in kanbans)
-                    kanban.Category = target;
-                new Client<Kanban>().Save(kanbans, string.Format("Task Status Updated to {0}", target), (o, err) => { });
-                foreach (var model in models)
-                {
-                    model.Checked = false;
-                    model.Category = target;
-                }
-
-                FilterKanbans();
-            }
-        }
-
-        private void Search_KeyUp(object sender, KeyEventArgs e)
-        {
-            if (string.IsNullOrWhiteSpace(Search.Text) || e.Key == Key.Return)
-            {
-                searchtext = Search.Text;
-                Refresh(true);
-            }
-        }
-
-        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();
-            ReloadKanbans();
-        }
-
-        private void IncludeCompleted_Checked(object sender, RoutedEventArgs e)
-        {
-            if (!IsReady)
-                return;
-            Host.KanbanSettings.StatusSettings.IncludeCompleted = IncludeCompleted.IsChecked.Value;
-            Host.SaveSettings();
-            SetupColumns();
-            ReloadKanbans();
-        }
-
-        private void IncludeObserved_Checked(object sender, RoutedEventArgs e)
-        {
-            if (!IsReady)
-                return;
-            Host.KanbanSettings.StatusSettings.IncludeObserved = IncludeObserved.IsChecked.Value;
-            Host.SaveSettings();
-            ReloadKanbans();
-        }
-
-        private void IncludeLocked_Checked(object sender, RoutedEventArgs e)
-        {
-            if (!IsReady)
-                return;
-            Host.KanbanSettings.StatusSettings.IncludeLocked = IncludeLocked.IsChecked.Value;
-            Host.SaveSettings();
-            ReloadKanbans();
-        }
-
-        private void ViewType_SelectionChanged(object sender, SelectionChangedEventArgs e)
-        {
-            if (Kanban != null)
-                Kanban.CardTemplate = ViewType.SelectedIndex > 0
-                    ? Resources["CompactKanban"] as DataTemplate
-                    : Resources["FullKanban"] as DataTemplate;
-            if (!IsReady || EventSuppressor.IsSet(Suppress.This))
-                return;
-            Host.KanbanSettings.StatusSettings.CompactView = ViewType.SelectedIndex > 0;
-            Host.SaveSettings();
-
-        }
-
-        private static bool UpdatingEmployees = false;
-
-        private void Employees_SelectionChanged(object sender, SelectionChangedEventArgs e)
-        {
-            if (UpdatingEmployees || EventSuppressor.IsSet(Suppress.This))
-                return;
-
-            if (e.AddedItems.Count == 0)
-            {
-                EmployeeID = Guid.Empty;
-            }
-            else
-            {
-                var model = _employeelist[Employees.SelectedIndex];
-                EmployeeID = model.ID;
-            }
-
-            CheckedKanbans.Clear();
-            if (IsReady)
-                Refresh(true);
-        }
-
-        private void Export_Click(object sender, RoutedEventArgs e)
-        {
-            var form = new DynamicExportForm(typeof(Kanban), _kanbans.Columns.Select(x => x.ColumnName));
-            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 (EmployeeID != CoreUtils.FullGuid)
-            {
-                if (EmployeeID == Guid.Empty)
-                {
-                    employee = "Unallocated Tasks";
-                }
-                else
-                {
-                    var model = _employeelist.FirstOrDefault(x => x.ID.Equals(EmployeeID));
-                    employee = model == null ? "Tasks for (Unknown)" : "Tasks for " + (model.ID == MyID ? MyName : model.Name);
-                }
-            }
-
-            ExcelExporter.DoExport<Kanban>(
-                export,
-                string.Format(
-                    "{0} ({1:dd-MMM-yy})",
-                    employee,
-                    DateTime.Today
-                )
-            );
-        }
-
-        #region ITaskControl Support
-
-        public bool IsReady { get; set; }
-
-        public ITaskHost Host { get; set; }
-
-        public KanbanViewType KanbanViewType => KanbanViewType.Status;
-
-        public IEnumerable<TaskModel> SelectedModels(TaskModel sender = null)
-        {
-            var result = _models.Where(x => x.Checked).ToList();
-            if (sender != null && !result.Contains(sender))
-                result.Add(sender);
-            return result;
-        }
-
-        #endregion
-
-        #region Setup
-
-        public void Setup()
-        {
-            using (new EventSuppressor(Suppress.This))
-            {
-                SetupToolbar();
-
-                SetupColumns();
-
-                SetupData();
-
-                SetupKanbanTypesLookup(false);
-
-                SetupMyEmployee();
-
-                SetupEmployeeList();
-            }
-        }
-
-        private void SetupMyEmployee()
-        {
-            var row = _employees.Rows.FirstOrDefault(r => r.Get<Employee, Guid>(c => c.UserLink.ID) == ClientFactory.UserGuid);
-            if (row != null)
-            {
-                MyID = row.Get<Employee, Guid>(c => c.ID);
-                MyName = row.Get<Employee, string>(x => x.Name);
-            }
-        }
-
-        private void SetupEmployeeList()
-        {
-            IEnumerable<CoreRow> active = null;
-            var anonymous = PRSDesktop.Resources.anonymous.AsBitmapImage();
-            if (Security.IsAllowed<CanViewOthersTasks>())
-            {
-                active = _employees.Rows.Where(r =>
-                    r.Get<Employee, bool>(x => x.CanAllocateTasks) && (r.Get<Employee, DateTime>(x => x.FinishDate).IsEmpty() ||
-                                                                       r.Get<Employee, DateTime>(x => x.FinishDate) > DateTime.Today));
-                _employeelist.Add(new EmployeeModel(CoreUtils.FullGuid, "All Staff", Guid.Empty, PRSDesktop.Resources.everyone.AsBitmapImage()));
-                _employeelist.Add(new EmployeeModel(Guid.Empty, "Unallocated", Guid.Empty, null));
-            }
-            else
-            {
-                active = _employees.Rows.Where(r => r.Get<Employee, Guid>(c => c.UserLink.ID).Equals(ClientFactory.UserGuid));
-            }
-
-            EmployeeModel selected = null;
-            foreach (var row in active)
-            {
-                var id = row.Get<Employee, Guid>(x => x.ID);
-                var userid = row.Get<Employee, Guid>(x => x.UserLink.ID);
-                var thumbnailid = row.Get<Employee, Guid>(x => x.Thumbnail.ID);
-                var name = userid.Equals(ClientFactory.UserGuid) ? "My Tasks" : row.Get<Employee, string>(x => x.Name);
-
-                var model = new EmployeeModel(id, name, thumbnailid, anonymous);
-
-                if (userid.Equals(ClientFactory.UserGuid))
-                {
-                    _employeelist.Insert(0, model);
-                    selected = model;
-                }
-                else
-                {
-                    _employeelist.Add(model);
-                }
-            }
-
-            if (Security.IsAllowed<CanViewOthersTasks>())
-            {
-                EmployeeListColumn.Width = new GridLength(1.0F, GridUnitType.Auto);
-                var thumbnails = active
-                    .Select(r => r.EntityLinkID<Employee, ImageDocumentLink>(x => x.Thumbnail) ?? Guid.Empty)
-                    .Where(x => x != Guid.Empty).ToArray();
-                Employees.ItemsSource = _employeelist;
-                Employees.SelectedItem = _employeelist.First();
-                EmployeeID = _employeelist.First().ID;
-                if (thumbnails.Any())
-                    new Client<Document>().Query(
-                        new Filter<Document>(x => x.ID).InList(thumbnails),
-                        new Columns<Document>(x => x.ID, x => x.Data),
-                        null,
-                        (data, error) =>
-                        {
-                            if (data != null)
-                                ProcessThumbnails(data);
-                        }
-                    );
-            }
-            else
-            {
-                EmployeeListColumn.Width = new GridLength(0.0F, GridUnitType.Pixel);
-                Employees.ItemsSource = _employeelist;
-                Employees.SelectedItem = _employeelist.First();
-                EmployeeID = _employeelist.First().ID;
-            }
-        }
-
-        private void ProcessThumbnails(CoreTable data)
-        {
-            Dispatcher.Invoke(() =>
-            {
-                foreach (var row in data.Rows)
-                {
-                    var id = row.Get<Document, Guid>(x => x.ID);
-                    var model = _employeelist.FirstOrDefault(x => x.ThumbnailID.Equals(id));
-                    if (model != null)
-                    {
-                        model.Image = new BitmapImage();
-                        model.Image.LoadImage(row.Get<Document, byte[]>(x => x.Data));
-                    }
-                }
-
-                UpdatingEmployees = true;
-                Employees.ItemsSource = null;
-                Employees.ItemsSource = _employeelist;
-                Employees.SelectedItem = _employeelist.First();
-                EmployeeID = _employeelist.First().ID;
-                UpdatingEmployees = false;
-            });
-        }
-
-        private void SetupKanbanTypesLookup(bool reload)
-        {
-            if (ClientFactory.IsSupported<KanbanType>())
-            {
-
-                if (reload)
-                {
-                    _types = new Client<KanbanType>().Query(
-                        new Filter<KanbanType>(x => x.Hidden).IsEqualTo(false),
-                        new Columns<KanbanType>(x => x.ID, x => x.Description),
-                        new SortOrder<KanbanType>(x => x.Description)
-                    );
-                }
-
-                var tasktypes = new Dictionary<Guid, string>
-                {
-                    { CoreUtils.FullGuid, "All Types" },
-                    { Guid.Empty, "Unallocated Types" }
-                };
-                _types.IntoDictionary<KanbanType, Guid, string>(tasktypes, x => x.ID, row => row.Get<KanbanType, string>(x => x.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;
-            }
-        }
-
-        private void SetupToolbar()
-        {
-            IncludeCompleted.Visibility = Security.IsAllowed<CanHideTaskCompletedColumn>() ? 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;
-        }
-
-        private void SetupColumns()
-        {
-            Kanban.Columns.Clear();
-
-            var indicatorColorPalette = new IndicatorColorPalette();
-            indicatorColorPalette.Add(new ColorMapping { Key = "Red", Color = Colors.LightSalmon });
-            indicatorColorPalette.Add(new ColorMapping { Key = "Orange", Color = Colors.Orange });
-            indicatorColorPalette.Add(new ColorMapping { Key = "Yellow", Color = Colors.LightYellow });
-            indicatorColorPalette.Add(new ColorMapping { Key = "Green", Color = Colors.LightGreen });
-            Kanban.IndicatorColorPalette = indicatorColorPalette;
-
-            Kanban.Columns.Add(new KanbanColumn
-            {
-                Categories = "Open",
-                Title = "To Do"
-            });
-
-            Kanban.Columns.Add(new KanbanColumn
-            {
-                Categories = "In Progress",
-                Title = "In Progress"
-            });
-
-            Kanban.Columns.Add(new KanbanColumn
-            {
-                Categories = "Waiting",
-                Title = "Waiting for Others"
-            });
-
-            if (Host.KanbanSettings.StatusSettings.IncludeCompleted)
-                Kanban.Columns.Add(new KanbanColumn
-                {
-                    Categories = "Complete",
-                    Title = "Completed"
-                });
-            Kanban.InvalidateVisual();
-
-            foreach (var column in Kanban.Columns)
-            {
-                var menu = new ContextMenu();
-                menu.Tag = column;
-                var item = new MenuItem { Header = "New Task" };
-                item.Click += CreateTask;
-                menu.Items.Add(item);
-                menu.Items.Add(new Separator());
-                item = new MenuItem { Header = "Select All " + column.Title + " Tasks", Tag = column };
-                item.Click += SelectAll_Click;
-                menu.Items.Add(item);
-                item = new MenuItem { Header = "Unselect All " + column.Title + " Tasks", Tag = column };
-                item.Click += UnSelectAll_Click;
-                menu.Items.Add(item);
-                column.ContextMenu = menu;
-            }
-
-            Kanban.ColumnWidth = Kanban.ActualWidth / Kanban.Columns.Count - 1.0F;
-        }
-
-        private void SetupData()
-        {
-            var query = new MultiQuery();
-
-            query.Add(
-                null,
-                new Columns<Employee>(x => x.ID, x => x.Name, x => x.Thumbnail.ID, x => x.CanAllocateTasks, x => x.Email, x => x.Mobile,
-                    x => x.FinishDate, x => x.UserLink.ID),
-                new SortOrder<Employee>(x => x.Name)
-            );
-
-            if (ClientFactory.IsSupported<KanbanType>())
-                query.Add(
-                    new Filter<KanbanType>(x => x.Hidden).IsEqualTo(false),
-                    new Columns<KanbanType>(x => x.ID, x => x.Description),
-                    new SortOrder<KanbanType>(x => x.Description)
-                );
-
-            query.Query();
-
-            _employees = query.Get<Employee>();
-            _types = ClientFactory.IsSupported<KanbanType>()
-                ? query.Get<KanbanType>()
-                : null;
-        }
-
-        #endregion
-
-        #region Refresh / Reload
-
-        private Filter<T> GetSearchFilter<T>(Expression<Func<T, object>> expression) where T : Entity, new()
-        {
-            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;
-        }
-
-        private Filter<KanbanSubscriber> GetKanbanSubscriberFilter()
-        {
-            var filter = new Filter<KanbanSubscriber>(x => x.Kanban.Closed).IsEqualTo(DateTime.MinValue);
-
-            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();
-            }
-
-            // 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(EmployeeID);
-            filter.Ands.Add(empfilter);
-
-            if (EmployeeID != MyID)
-                filter = filter.And(x => x.Kanban.Private).IsEqualTo(false);
-
-            //if (!includeobserved)
-            //    filter = filter.And(new Filter<KanbanSubscriber>(x => x.Assignee).IsEqualTo(true).Or(x => x.Manager).IsEqualTo(true));
-
-            //if (!includecompleted)
-            //    filter = filter.And(x => x.Kanban.Completed).IsEqualTo(DateTime.MinValue);
-
-            //if (selectedtype != CoreUtils.FullGuid)
-            //    filter = filter.And(x => x.Kanban.Type.ID).IsEqualTo(selectedtype);
-
-            if (!string.IsNullOrWhiteSpace(searchtext))
-            {
-                var search = GetSearchFilter<KanbanSubscriber>(x => x.Kanban.JobLink.Name);
-                search.Ors.Add(GetSearchFilter<KanbanSubscriber>(x => x.Kanban.JobLink.JobNumber));
-                search.Ors.Add(GetSearchFilter<KanbanSubscriber>(x => x.Kanban.Summary));
-                search.Ors.Add(GetSearchFilter<KanbanSubscriber>(x => x.Kanban.Title));
-                search.Ors.Add(GetSearchFilter<KanbanSubscriber>(x => x.Kanban.ManagerLink.Name));
-                search.Ors.Add(GetSearchFilter<KanbanSubscriber>(x => x.Kanban.EmployeeLink.Name));
-
-                if (int.TryParse(searchtext.Trim(), out var tasknumber))
-                    search.Ors.Add(new Filter<KanbanSubscriber>(x => x.Kanban.Number).IsEqualTo(tasknumber));
-
-                filter.Ands.Add(search);
-            }
-
-            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 (EmployeeID != CoreUtils.FullGuid)
-            {
-                if (EmployeeID != Guid.Empty)
-                {
-                    var empfilter = new Filter<Kanban>(x => x.EmployeeLink.ID).IsEqualTo(EmployeeID).Or(x => x.ManagerLink.ID).IsEqualTo(EmployeeID);
-                    filter.Ands.Add(empfilter);
-                }
-                else
-                {
-                    filter = filter.And(x => x.EmployeeLink.ID).IsEqualTo(EmployeeID);
-                }
-            }
-
-            if (EmployeeID != MyID)
-                filter = filter.And(x => x.Private).IsEqualTo(false);
-
-            //if (!includecompleted)
-            //    filter = filter.And(x => x.Completed).IsEqualTo(DateTime.MinValue);
-
-            //if (selectedtype != CoreUtils.FullGuid)
-            //    filter = filter.And(x => x.Type.ID).IsEqualTo(selectedtype);
-
-            return filter;
-        }
-
-        public void Refresh(bool resetselection)
-        {
-            Application.Current.Dispatcher.Invoke(() => { Mouse.OverrideCursor = Cursors.Wait; });
-
-            if (EmployeeID != CoreUtils.FullGuid && EmployeeID != Guid.Empty)
-            {
-                _kanbans = new Client<KanbanSubscriber>().Query(
-                    GetKanbanSubscriberFilter(),
-                    new Columns<KanbanSubscriber>
-                    (
-                        x => x.Kanban.ID,
-                        x => x.Kanban.DueDate,
-                        x => x.Kanban.Completed,
-                        //x => x.Kanban.Description,
-                        x => x.Kanban.Summary,
-                        x => x.Kanban.Category,
-                        x => x.Kanban.EmployeeLink.ID,
-                        x => x.Kanban.ManagerLink.ID,
-                        x => x.Kanban.Notes,
-                        x => x.Kanban.Title,
-                        x => x.Kanban.JobLink.ID,
-                        x => x.Kanban.JobLink.JobNumber,
-                        x => x.Kanban.JobLink.Name,
-                        x => x.Kanban.Type.ID,
-                        x => x.Kanban.Type.Code,
-                        x => x.Kanban.Number,
-                        x => x.Kanban.Attachments,
-                        x => x.Kanban.Locked
-                    ),
-                    new SortOrder<KanbanSubscriber>(x => x.Kanban.DueDate) { Direction = SortDirection.Ascending }
-                );
-                foreach (var column in _kanbans.Columns)
-                    column.ColumnName = column.ColumnName.Replace("Kanban.", "");
-            }
-            else
-            {
-                _kanbans = new Client<Kanban>().Query(
-                    GetKanbanFilter(),
-                    new Columns<Kanban>
-                    (
-                        x => x.ID,
-                        x => x.DueDate,
-                        x => x.Completed,
-                        //x => x.Description,
-                        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
-                    ),
-                    new SortOrder<Kanban>(x => x.DueDate) { Direction = SortDirection.Ascending }
-                );
-            }
-
-            ReloadKanbans();
-
-            Application.Current.Dispatcher.Invoke(() => { Mouse.OverrideCursor = null; });
-        }
-
-        private void ReloadKanbans()
-        {
-            //SetupColumns();
-            //ResizeColumns();
-
-            _models = new ObservableCollection<TaskModel>();
-
-            foreach (var row in _kanbans.Rows)
-                try
-                {
-                    var empid = row.Get<IKanban, Guid>(e => e.EmployeeLink.ID);
-                    var mgrid = row.EntityLinkID<IKanban, EmployeeLink>(x => x.ManagerLink) ?? Guid.Empty;
-                    var completed = row.Get<IKanban, DateTime>(e => e.Completed);
-                    var locked = row.Get<IKanban, bool>(e => e.Locked);
-                    var type = row.Get<IKanban, Guid>(e => e.Type.ID);
-                    var category = row.Get<IKanban, string>(x => x.Category);
-                    if (string.IsNullOrWhiteSpace(category))
-                        category = "Open";
-
-
-                    var bLockedOK = Host.KanbanSettings.StatusSettings.IncludeLocked || locked == false;
-                    var bObserveOK = EmployeeID == CoreUtils.FullGuid || Host.KanbanSettings.StatusSettings.IncludeObserved || empid == EmployeeID || mgrid == EmployeeID;
-                    var bCompleteOK = Host.KanbanSettings.StatusSettings.IncludeCompleted || completed.IsEmpty();
-                    var bTypeOK = selectedtype == CoreUtils.FullGuid || type == selectedtype;
-                    if (bLockedOK && bCompleteOK && bObserveOK && bTypeOK)
-                    {
-                        var model = new TaskModel();
-                        var EmployeeEntry = _employeelist.Where(x => x.ID.Equals(empid)).FirstOrDefault();
-                        var empimg = EmployeeEntry?.Image;
-
-                        var ManagerEntry = _employeelist.Where(x => x.ID.Equals(mgrid)).FirstOrDefault();
-
-                        //var description = row.Get<IKanban, String>(x => x.Summary);
-                        //if (String.IsNullOrWhiteSpace(description))
-                        //    description = String.Join("\r\n", row.Get<IKanban, String[]>(x => x.Notes)).Split(new String[] { "===============" }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault();
-                        //if (String.IsNullOrWhiteSpace(description))
-                        //    description = CoreUtils.StripHTML(row.Get<IKanban, String>(x => x.Description));
-                        //if (String.IsNullOrWhiteSpace(description))
-                        //    description = "";
-                        var job = row.Get<IKanban, string>(x => x.JobLink.JobNumber);
-
-                        model.Title = row.Get<IKanban, string>(x => x.Title);
-                        model.ID = row.Get<IKanban, Guid>(x => x.ID).ToString();
-                        model.Description = row.Get<IKanban, string>(x => x.Summary) ?? "";
-                        model.Category = category;
-                        var color = EmployeeID == Guid.Empty || empid == EmployeeID || EmployeeID == CoreUtils.FullGuid
-                            ? TaskModel.KanbanColor(
-                                row.Get<IKanban, DateTime>(x => x.DueDate),
-                                row.Get<IKanban, DateTime>(x => x.Completed))
-                            : mgrid == EmployeeID
-                                ? Color.Silver
-                                : Color.Plum;
-                        if (row.Get<IKanban, bool>(x => x.Locked))
-                            color = color.MixColors(0.5F, Color.White);
-                        model.ColorKey = ImageUtils.ColorToString(color);
-                        model.Image = empimg;
-                        model.ImageURL = null;
-                        model.Attachments = row.Get<IKanban, int>(x => x.Attachments) > 0; // ? _attachimg : null;
-                        model.DueDate = row.Get<IKanban, DateTime>(x => x.DueDate);
-                        model.CompletedDate = row.Get<IKanban, DateTime>(x => x.Completed);
-                        model.Locked = row.Get<IKanban, bool>(x => x.Locked); // ? _lockimg : null;
-
-                        var notes = new List<List<string>> { new() };
-                        if ((row.Get<IKanban, string[]>(x => x.Notes) != null))
-                        {
-
-
-                            foreach (var line in row.Get<IKanban, string[]>(x => x.Notes))
-                            {
-                                if (line == "===================================")
-                                {
-                                    notes.Add(new());
-                                }
-                                else
-                                {
-                                    notes.Last().Add(line);
-                                }
-                            }
-                            model.Notes = string.Join("\n===================================\n", notes.Reverse<List<string>>().Select(x => string.Join('\n', x)));
-                        }
-
-                        model.EmployeeID = empid;
-                        model.ManagerID = mgrid;
-
-                        var sEmp = "";
-                        if (empid != EmployeeID)
-                        {
-                            if (!Entity.IsEntityLinkValid<IKanban, EmployeeLink>(x => x.EmployeeLink, row))
-                            {
-                                sEmp = "Unallocated";
-                            }
-                            else
-                            {
-                                var tuple = _employeelist.FirstOrDefault(x => x.ID.Equals(empid));
-                                sEmp = tuple != null ? tuple.ID == MyID ? MyName : tuple.Name : "";
-                            }
-                        }
-
-                        var sMgr = "";
-                        if (mgrid != EmployeeID)
-                            if (mgrid != Guid.Empty)
-                            {
-                                var tuple = _employeelist.FirstOrDefault(x => x.ID.Equals(mgrid));
-                                sMgr = tuple != null ? tuple.ID == MyID ? MyName : tuple.Name : "";
-                            }
-
-                        if (!string.IsNullOrEmpty(sEmp))
-                        {
-                            if (!string.IsNullOrWhiteSpace(sMgr) && !string.Equals(sMgr, sEmp))
-                                model.AssignedTo = string.Format("Assigned to {0} by {1}", sEmp, sMgr);
-                            else
-                                model.AssignedTo = string.Format("Assigned to {0} ", sEmp);
-                        }
-                        else
-                        {
-                            if (!string.IsNullOrWhiteSpace(sMgr))
-                                model.AssignedTo = string.Format("Allocated by {0} ", sMgr);
-                        }
-
-                        //model.AssignedTo = String.Format("M: {0} / E: {1}", sMgr, sEmp);
-                        model.JobID = row.Get<IKanban, Guid>(x => x.JobLink.ID);
-                        model.JobNumber = row.Get<IKanban, string>(x => x.JobLink.JobNumber);
-                        if (string.IsNullOrWhiteSpace(model.JobNumber))
-                            model.JobNumber = "";
-                        model.JobName = row.Get<IKanban, string>(x => x.JobLink.Name);
-                        model.Checked = CheckedKanbans.Contains(row.Get<IKanban, Guid>(x => x.ID).ToString());
-                        model.Type = new KanbanType
-                        {
-                            ID = row.Get<IKanban, Guid>(x => x.Type.ID),
-                            Code = row.Get<IKanban, string>(x => x.Type.Code)
-                        };
-                        model.Number = row.Get<IKanban, int>(x => x.Number);
-
-                        _models.Add(model);
-                    }
-                }
-                catch (Exception e)
-                {
-                    Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
-                }
-
-            StatusTasksHeaderTimeConverter.Tasks = _models;
-            FilterKanbans();
-        }
-
-        private void FilterKanbans()
-        {
-            Progress.ShowModal("Loading", (progress) =>
-            {
-                Dispatcher.BeginInvoke(() => { Progress.Show("Loading"); });
-                if (JobFilterID == Guid.Empty)
-                {
-                    var list = _models
-                         .Where(x => x.Search(Search.Text.Split()))
-                         .OrderBy(x => x.EmployeeID == EmployeeID ? 0 : 1).ThenBy(x => x.DueDate);
-                    Dispatcher.BeginInvoke(() => { Kanban.ItemsSource = list; });
-                }
-                else
-                {
-                    var list = _models
-                        .Where(x => x.Search(Search.Text.Split()) && x.JobSearch(JobFilterID))
-                        .OrderBy(x => x.EmployeeID == EmployeeID ? 0 : 1).ThenBy(x => x.DueDate);
-                    Dispatcher.BeginInvoke(() => { Kanban.ItemsSource = list; });
-                }
-                Task.Run(() => 
-                {
-                    Thread.Sleep(500);
-                    Dispatcher.BeginInvoke(() =>
-                    {
-                        Progress.Close();
-                    });
-                });               
-            });
-        }
-
-        #endregion
-
-        private void TaskTypesLabel_OnClick(object sender, RoutedEventArgs e)
-        {
-            var list = new MasterList(typeof(KanbanType));
-            list.ShowDialog();
-            SetupKanbanTypesLookup(true);
-        }
-
-        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 ((sender as JobGrid).SelectedRows.Count() == 0)
-                    return;
-
-                else
-                {
-                    var item = (sender as JobGrid).SelectedRows[0];
-                    AddJobFilter(item);
-                    window.Close();
-                }
-            };
-            grid.Refresh(true, true);
-            window.Content = grid;
-            window.ShowDialog();
-        }
-
-        Guid JobFilterID = Guid.Empty;
-
-        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();
-        }
     }
-}
+}*/

+ 2 - 1
prs.desktop/Panels/Tasks/TasksByUserControl.xaml.cs

@@ -18,6 +18,7 @@ using Syncfusion.Windows.Tools.Controls;
 
 namespace PRSDesktop
 {
+    /*
     public class UserTasksHeaderImageConverter : IValueConverter
     {
         public static Dictionary<Guid, byte[]> Images { get; set; }
@@ -721,5 +722,5 @@ namespace PRSDesktop
         {
             e.Handled = true;
         }
-    }
+    }*/
 }