Просмотр исходного кода

Merge branch 'kanban_overhaul' into kenric

Kenric Nugteren 1 год назад
Родитель
Сommit
1aaeee2d73

+ 28 - 0
prs.desktop/App.xaml

@@ -15,6 +15,34 @@
 
                 <ResourceDictionary>
 
+                    <Style x:Key="VirtualisedItemsControlStyle" TargetType="ItemsControl">
+                        <Setter Property="VirtualizingPanel.IsVirtualizing" Value="True"/>
+                        <Setter Property="ItemsPanel">
+                            <Setter.Value>
+                                <ItemsPanelTemplate>
+                                    <VirtualizingStackPanel/>
+                                </ItemsPanelTemplate>
+                            </Setter.Value>
+                        </Setter>
+                        <Setter Property="Template">
+                            <Setter.Value>
+                                <ControlTemplate TargetType="ItemsControl">
+                                    <Border BorderThickness="{TemplateBinding BorderThickness}"
+                                            BorderBrush="{TemplateBinding BorderBrush}"
+                                            Background="{TemplateBinding Background}"
+                                            SnapsToDevicePixels="True">
+                                        <ScrollViewer CanContentScroll="True"
+                                                      Padding="{TemplateBinding Padding}"
+                                                      Focusable="False"
+                                                      VerticalScrollBarVisibility="Auto">
+                                            <ItemsPresenter/>
+                                        </ScrollViewer>
+                                    </Border>
+                                </ControlTemplate>
+                            </Setter.Value>
+                        </Setter>
+                    </Style>
+
                     <Style x:Key="TemplateHeaderStyle" TargetType="{x:Type syncfusion:GridHeaderCellControl}">
                         <Setter Property="Background" Value="LightSkyBlue"/>
                         <Setter Property="Foreground" Value="Black"/>

+ 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 - Copy.xaml.cs" />
+      <Page Remove="Panels\Tasks\TasksByStatusControlOld.xaml" />
+      <Page Remove="Panels\Tasks\TasksByUserControl - Copy.xaml" />
       <Page Update="Panels\Suppliers\Bills\SupplierBillPanel.xaml">
         <Generator>MSBuild:Compile</Generator>
         <XamlRuntime>Wpf</XamlRuntime>

+ 4 - 3
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);
@@ -40,7 +41,7 @@ namespace PRSDesktop
 
         bool IsReady { get; set; }
 
-        IEnumerable<TaskModel> SelectedModels(TaskModel sender = null);
+        IEnumerable<TaskModel> SelectedModels(TaskModel? sender = null);
 
         string SectionName { get; }
 
@@ -48,7 +49,7 @@ namespace PRSDesktop
 
         void Setup();
 
-        void Refresh(bool resetselection);
+        void Refresh();
     }
 
     public class KanbanReferences

+ 215 - 0
prs.desktop/Panels/Tasks/KanbanResources.xaml

@@ -0,0 +1,215 @@
+<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>
+            <DataTrigger Binding="{Binding AssignedTo}" Value="">
+                <Setter Property="Visibility" Value="Collapsed" />
+            </DataTrigger>
+        </Style.Triggers>
+    </Style>
+
+    <Style x:Key="managerStyle" TargetType="{x:Type TextBlock}">
+        <Style.Triggers>
+            <DataTrigger Binding="{Binding Manager}" Value="">
+                <Setter Property="Visibility" Value="Collapsed"/>
+            </DataTrigger>
+        </Style.Triggers>
+    </Style>
+
+    <Style x:Key="jobStyle" TargetType="{x:Type TextBlock}">
+        <Style.Triggers>
+            <DataTrigger Binding="{Binding JobNumber}" Value="">
+                <Setter Property="Visibility" Value="Collapsed" />
+            </DataTrigger>
+        </Style.Triggers>
+    </Style>
+
+    <Style x:Key="descriptionStyle" TargetType="{x:Type TextBlock}">
+        <Style.Triggers>
+            <DataTrigger Binding="{Binding Path=Description}" Value="">
+                <Setter Property="Visibility" Value="Collapsed" />
+            </DataTrigger>
+        </Style.Triggers>
+    </Style>
+
+    <Style x:Key="notesStyle" TargetType="{x:Type TextBlock}">
+        <Style.Triggers>
+            <DataTrigger Binding="{Binding Path=Notes}" Value="">
+                <Setter Property="Visibility" Value="Collapsed" />
+            </DataTrigger>
+        </Style.Triggers>
+    </Style>
+    
+    <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">
+                        <ColumnDefinition.Style>
+                            <Style TargetType="ColumnDefinition">
+                                <Setter Property="Width" Value="Auto"/>
+                                <Style.Triggers>
+                                    <DataTrigger Binding="{Binding Path=Image}" Value="{x:Null}">
+                                        <Setter Property="Width" Value="0"/>
+                                    </DataTrigger>
+                                </Style.Triggers>
+                            </Style>
+                        </ColumnDefinition.Style>
+                    </ColumnDefinition>
+                    <ColumnDefinition x:Name="colCheckbox" Width="Auto" />
+                    <ColumnDefinition x:Name="colDescription" Width="*" />
+                    <ColumnDefinition x:Name="colType" Width="Auto" />
+                    <ColumnDefinition x:Name="colAttach" Width="Auto" />
+                    <ColumnDefinition x:Name="colDueDate" Width="Auto" />
+                </Grid.ColumnDefinitions>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto" />
+                    <RowDefinition Height="*" />
+                    <RowDefinition Height="Auto" />
+                </Grid.RowDefinitions>
+
+                <Border Grid.Column="0" Grid.Row="0" Grid.RowSpan="2" Width="40" Height="40" CornerRadius="20"
+                            Margin="0,0,4,0" BorderBrush="Black" BorderThickness="1" VerticalAlignment="Top">
+                    <Border.Background>
+                        <ImageBrush ImageSource="{Binding Path=Image}" Stretch="UniformToFill" />
+                    </Border.Background>
+                </Border>
+
+                <CheckBox Grid.Row="0" Grid.Column="1" Margin="0,0,4,0" VerticalAlignment="Center"
+                              IsChecked="{Binding Path=Checked}" Checked="CheckBox_Checked"
+                              Unchecked="CheckBox_Checked" Tag="{Binding}" />
+                <TextBlock Grid.Row="0" Grid.Column="2" Margin="0,0,0,0" FontWeight="DemiBold" FontSize="12"
+                               VerticalAlignment="Center">
+                        <Run Text="{Binding Path=Number}" /><Run Text=": " /><Run Text="{Binding Path=Title}" />
+                </TextBlock>
+                <TextBlock Grid.Row="0" Grid.Column="3" FontSize="12" Margin="4,0,0,0" Text="{Binding Path=Type.Code}"
+                               FontWeight="DemiBold" VerticalAlignment="Center" HorizontalAlignment="Center" />
+                <Image Grid.Row="0" Grid.Column="4" Margin="4,0,4,0"
+                           Source="pack://application:,,,Resources/attachment.png"
+                           Visibility="{Binding Path=Attachments, Converter={StaticResource BoolToVisibilityConverter}}"
+                           Width="16" Height="16" VerticalAlignment="Center" />
+                <TextBlock Grid.Row="0" Grid.Column="5" Text="{Binding DueDate, StringFormat='{}{0:dd MMM yy}'}"
+                               FontWeight="Bold" FontSize="12" VerticalAlignment="Center" HorizontalAlignment="Center" />
+
+                <TextBlock Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="5" Margin="0,0,0,0" FontSize="12"
+                               HorizontalAlignment="Left" Text="{Binding Path=Description}" VerticalAlignment="Stretch"
+                               TextWrapping="Wrap" />
+
+                <DockPanel Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="5">
+                    <TextBlock DockPanel.Dock="Right" x:Name="Job" Margin="0" FontWeight="DemiBold" FontSize="12"
+                                   VerticalAlignment="Center" TextAlignment="Right" Style="{StaticResource jobStyle}">
+                            <Run Text="{Binding JobNumber}" /><Run Text=": " /><Run Text="{Binding JobName}" />
+                    </TextBlock>
+                    <TextBlock DockPanel.Dock="Left" x:Name="Staff" FontWeight="DemiBold" FontSize="12"
+                                   VerticalAlignment="Center" Text="{Binding AssignedTo}"
+                                   Style="{StaticResource employeeStyle}" />
+                </DockPanel>
+                <Image Grid.Row="2" Grid.Column="0" Margin="0,0,4,0"
+                           Source="pack://application:,,,Resources/lock.png"
+                           Visibility="{Binding Path=Locked, Converter={StaticResource BoolToVisibilityConverter}}"
+                           Width="20" Height="20" VerticalAlignment="Center" HorizontalAlignment="Center" />
+
+
+            </Grid>
+
+            <Border.ToolTip>
+                <Grid>
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="Auto"/>
+                    </Grid.RowDefinitions>
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="Auto"/>
+                    </Grid.ColumnDefinitions>
+                    <TextBlock Grid.Row="0" Grid.Column="0">
+                            <Run FontWeight="DemiBold" Text="{Binding Path=Title}"/>
+                    </TextBlock>
+                    <TextBlock Grid.Row="1" Grid.Column="0" Style="{StaticResource descriptionStyle}">
+                            <Run Text="{Binding Path=Description}"/>
+                    </TextBlock>
+                    <TextBlock Grid.Row="2" Grid.Column="0" Style="{StaticResource notesStyle}">
+                            <LineBreak/>
+                            <Run Text="{Binding Path=Notes}"/>
+                    </TextBlock>
+                </Grid>
+            </Border.ToolTip>
+
+            <Border.ContextMenu>
+                <ContextMenu x:Name="TaskMenu" Tag="{Binding}"/>
+            </Border.ContextMenu>
+        </Border>
+    </DataTemplate>
+
+    <DataTemplate x:Key="CompactKanban">
+        <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" />
+
+                <CheckBox DockPanel.Dock="Left" Margin="0,0,4,0" VerticalAlignment="Center" IsChecked="{Binding Path=Checked}" Checked="CheckBox_Checked" Unchecked="CheckBox_Checked" Tag="{Binding}" />
+
+                <TextBlock DockPanel.Dock="Left" x:Name="JobNumber" Margin="0"  FontWeight="DemiBold" FontSize="12" VerticalAlignment="Center" Style="{StaticResource jobStyle}">
+                                    <Run Text="{Binding JobNumber}"/><Run Text="/"/>
+                </TextBlock>
+                <TextBlock DockPanel.Dock="Left" Margin="0,0,0,0" FontWeight="DemiBold" FontSize="12" VerticalAlignment="Center">
+                            <Run Text="{Binding Path=Number}"/><Run Text=": "/>
+                </TextBlock>
+
+                <TextBlock DockPanel.Dock="Left" Margin="0,0,0,0" FontWeight="DemiBold" FontSize="12" VerticalAlignment="Center" Text="{Binding Path=Title}" />
+
+                <TextBlock DockPanel.Dock="Left" x:Name="Manager" Margin="0"  FontWeight="DemiBold" FontSize="12" VerticalAlignment="Center" Style="{StaticResource managerStyle}">
+                        <Run Text=" ("/><Run Text="{Binding Manager}"/><Run Text=")"/>
+                </TextBlock>
+                <TextBlock DockPanel.Dock="Left" x:Name="Employee" Margin="0"  FontWeight="DemiBold" FontSize="12" VerticalAlignment="Center" Style="{StaticResource employeeStyle}">
+                        <Run Text=" ("/><Run Text="{Binding AssignedTo}"/><Run Text=")"/>
+                </TextBlock>
+
+            </DockPanel>
+
+            <Border.ToolTip>
+                <Grid>
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="Auto"/>
+                        <RowDefinition Height="Auto"/>
+                    </Grid.RowDefinitions>
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="Auto"/>
+                    </Grid.ColumnDefinitions>
+                    <TextBlock Grid.Row="0" Grid.Column="0">
+                            <Run FontWeight="DemiBold" Text="{Binding Path=Title}"/>
+                    </TextBlock>
+                    <TextBlock Grid.Row="1" Grid.Column="0" Style="{StaticResource descriptionStyle}">
+                            <Run Text="{Binding Path=Description}"/>
+                    </TextBlock>
+                    <TextBlock Grid.Row="2" Grid.Column="0" Style="{StaticResource notesStyle}">
+                            <LineBreak/>
+                            <Run Text="{Binding Path=Notes}"/>
+                    </TextBlock>
+                </Grid>
+            </Border.ToolTip>
+
+            <Border.ContextMenu>
+                <ContextMenu x:Name="TaskMenu" Tag="{Binding}"/>
+            </Border.ContextMenu>
+
+        </Border>
+    </DataTemplate>
+</ResourceDictionary>

+ 102 - 0
prs.desktop/Panels/Tasks/KanbanResources.xaml.cs

@@ -0,0 +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
+{
+    public static KanbanResources Resources = new KanbanResources();
+
+    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 is not FrameworkElement element || element.Tag is not TaskModel model) return;
+        if(e.ClickCount > 1)
+        {
+            EditTask.Execute(model, element);
+        }
+    }
+
+    private void CheckBox_Checked(object sender, RoutedEventArgs e)
+    {
+        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 Border_MouseMove(object sender, MouseEventArgs e)
+    {
+        if(sender is not Border border)
+        {
+            return;
+        }
+        if ((sender as FrameworkElement)?.Tag is not TaskModel model) return;
+        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();
+    }
+}

+ 19 - 3
prs.desktop/Panels/Tasks/TaskModel.cs

@@ -8,10 +8,14 @@ using Syncfusion.UI.Xaml.Kanban;
 namespace PRSDesktop;
 
 [DoNotNotify]
-public class TaskModel : KanbanModel
+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; }
@@ -23,15 +27,24 @@ public class TaskModel : KanbanModel
     public string JobNumber { get; set; }
     public string JobName { get; set; }
     public bool Checked { get; set; }
+    public string Category { get; set; }
+    public Guid EmployeeCategory { get; set; }
     public KanbanType Type { get; set; }
     public int Number { get; set; }
     public bool Locked { get; set; }
     public TimeSpan EstimatedTime { get; set; }
+
+    public bool ShowEmployeeImage { get; set; }
+
     /// <summary>
     /// A string representation of the Notes field of Kanban.
     /// </summary>
     public string Notes { get; set; }
 
+    public bool IsAssignee => EmployeeID != Guid.Empty && EmployeeCategory == EmployeeID;
+
+    public bool IsManager => ManagerID != Guid.Empty && EmployeeCategory == ManagerID;
+
     public bool Search(string[] searches)
     {
         foreach (var search in searches)
@@ -39,7 +52,10 @@ public class TaskModel : KanbanModel
             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;
         }

+ 85 - 110
prs.desktop/Panels/Tasks/TaskPanel.xaml.cs

@@ -32,12 +32,10 @@ namespace PRSDesktop
         private bool _bTabChanging;
         private KanbanType[] kanbanTypes = null!; // Initialized in Setup()
         
-        public Guid MyID { get; set; } = CoreUtils.FullGuid;
         public IList<KanbanType> KanbanTypes => kanbanTypes;
         public Job Job { get; set; }
         public JobPanelSettings Settings { get; set; }
         
-
         public TaskPanel()
         {
             InitializeComponent();
@@ -48,40 +46,10 @@ namespace PRSDesktop
                 _viewmap[panel.KanbanViewType] = tab;
                 panel.Host = this;
             }
-
-            if (MyID == CoreUtils.FullGuid)
-            {
-                var row = new Client<Employee>()
-                    .Query(new Filter<Employee>(x => x.UserLink.ID).IsEqualTo(ClientFactory.UserGuid), new Columns<Employee>(x => x.ID)).Rows
-                    .FirstOrDefault();
-                if (row != null)
-                    MyID = row.Get<Employee, Guid>(x => x.ID);
-            }
         }
 
-        
-        private void ChangeStatus(ITaskControl control, object o, RoutedEventArgs e, string status)
-        {
-            if (MessageBox.Show($"Are you sure you want to mark the selected tasks as {status}?", "Confirm Change Status",
-                    MessageBoxButton.YesNo) != MessageBoxResult.Yes)
-                return;
-            var tasks = (((MenuItem)e.Source).Tag as IEnumerable<TaskModel>)!;
-            Progress.ShowModal("Changing Status", progress =>
-            {
-                var kanbans = LoadKanbans(tasks, new Columns<Kanban>(x => x.ID, x => x.Completed, x => x.Category));
-                foreach (var kanban in kanbans)
-                {
-                    if(status == "Complete")
-                    {
-                        kanban.Completed = DateTime.Now;
-                    }
-                    kanban.Category = status;
-                }
+        #region Menu
 
-                new Client<Kanban>().Save(kanbans, $"Kanban Marked as {status}");
-            });
-            control.Refresh(true);
-        }
         private void CompleteTask(ITaskControl control, RoutedEventArgs e, DateTime completed)
         {
             if (MessageBox.Show($"Are you sure you want to complete the selected tasks?", "Confirm Completion",
@@ -99,23 +67,41 @@ namespace PRSDesktop
 
                 new Client<Kanban>().Save(kanbans, $"Kanban Marked as Complete");
             });
-            control.Refresh(true);
+            control.Refresh();
         }
         private void AddChangeStatusButton(ITaskControl control, TaskModel[] models, MenuItem menu, string header, string status)
         {
-            var item = new MenuItem
+            menu.AddItem(header, null, Tuple.Create(control, models, status), ChangeStatus_Click);
+        }
+
+        private void ChangeStatus_Click(Tuple<ITaskControl, TaskModel[], string> obj)
+        {
+            var (control, tasks, status) = obj;
+            if (MessageBox.Show($"Are you sure you want to mark the selected tasks as {status}?", "Confirm Change Status",
+                    MessageBoxButton.YesNo) != MessageBoxResult.Yes)
+                return;
+            Progress.ShowModal("Changing Status", progress =>
             {
-                Tag = models,
-                Header = header
-            };
-            item.Click += (o, e) => ChangeStatus(control, o, e, status);
-            menu.Items.Add(item);
+                var kanbans = LoadKanbans(tasks, new Columns<Kanban>(x => x.ID, x => x.Completed, x => x.Category));
+                foreach (var kanban in kanbans)
+                {
+                    if (status == "Complete")
+                    {
+                        kanban.Completed = DateTime.Now;
+                    }
+                    kanban.Category = status;
+                }
+
+                new Client<Kanban>().Save(kanbans, $"Kanban Marked as {status}");
+            });
+            control.Refresh();
         }
+
         public bool CanChangeTasks(IEnumerable<TaskModel> models)
         {
             foreach (var task in models)
             {
-                if (!MyID.Equals(task.ManagerID) && !MyID.Equals(task.EmployeeID))
+                if (!App.EmployeeID.Equals(task.ManagerID) && !App.EmployeeID.Equals(task.EmployeeID))
                 {
                     // If you can change others tasks, IsFullControl is true - but we don't check at the beginning of the function
                     // to save checking security tokens every time.
@@ -125,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();
@@ -158,7 +139,7 @@ namespace PRSDesktop
             {
                 var tasks = (((MenuItem)e.Source).Tag as IEnumerable<TaskModel>)!;
                 if (EditReferences(tasks))
-                    control.Refresh(true);
+                    control.Refresh();
                 e.Handled = true;
             };
             edit.IsEnabled = referencetypes.Length == 1;
@@ -169,13 +150,12 @@ 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.EmployeeID == MyID);
+                    model.ID,
+                    () => new Client<Kanban>().Load(new Filter<Kanban>(x => x.ID).IsEqualTo(model.ID)).First(),
+                    model.EmployeeID == App.EmployeeID);
 
                 menu.Items.Add(digitalForms);
             }
@@ -347,7 +327,7 @@ namespace PRSDesktop
                                 kanban.Title = kanban.Title + " (" + result.Number + ")";
                                 new Client<Kanban>().Save(kanban, "Converting Kanban to Setout");
                             });
-                            control.Refresh(true);
+                            control.Refresh();
                         }
                     });
                     menu.AddItem("Create Requisition from Task", null, models, tasks =>
@@ -405,7 +385,7 @@ namespace PRSDesktop
                                 new Client<Kanban>().Save(kanban, "Converted to Requisition", (_, __) => { });
                             });
                             MessageBox.Show(String.Format("Created Requisition {0}", result.Number));
-                            control.Refresh(true);
+                            control.Refresh();
                         }
                     });
                     menu.AddItem("Create Delivery from Task", null, models, tasks =>
@@ -418,7 +398,7 @@ namespace PRSDesktop
                             }
                         );
                         if (result != null)
-                            control.Refresh(true);
+                            control.Refresh();
                     });
                     menu.AddItem("Create Purchase Order from Task", null, models, tasks =>
                     {
@@ -430,7 +410,7 @@ namespace PRSDesktop
                             }
                         );
                         if (result != null)
-                            control.Refresh(true);
+                            control.Refresh();
                     });
                 }
             }
@@ -502,7 +482,7 @@ namespace PRSDesktop
                                 kanban.Closed = DateTime.Now;
                             new Client<Kanban>().Save(kanbans, "Kanban Marked as Closed");
                         });
-                        control.Refresh(true);
+                        control.Refresh();
                     });
                 }
 
@@ -524,7 +504,7 @@ namespace PRSDesktop
 
                             new Client<Kanban>().Save(kanbans, $"Kanban Task Type changed to {type}");
                         });
-                        control.Refresh(true);
+                        control.Refresh();
                     });
                 }
 
@@ -553,7 +533,7 @@ namespace PRSDesktop
 
                         new Client<Kanban>().Save(kanbans, $"Kanban Due Date changed to {selectedDate:dd MMM yyyy}");
                     });
-                    control.Refresh(true);
+                    control.Refresh();
 
                     menu.IsOpen = false;
                 };
@@ -645,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();
@@ -697,13 +649,42 @@ namespace PRSDesktop
                             foreach (var kanban in kanbans)
                                 kanban.JobLink.ID = row.Get<Job, Guid>(x => x.ID);
                             new Client<Kanban>().Save(kanbans, "Updated Job Number");
-                            control.Refresh(false);
+                            control.Refresh();
                         }
                     });
                 }
             }
         }
 
+        #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();
+                }
+            }
+            finally
+            {
+                _bTabChanging = false;
+            }
+        }
+
         #region Get/Save Settings
 
         private KanbanSettings? _settings;
@@ -825,7 +806,7 @@ namespace PRSDesktop
             else
                 TasksPlannerTabItem.Visibility = Visibility.Visible;
             
-            GetCurrentPanel()?.Refresh(false);
+            GetCurrentPanel()?.Refresh();
         }
 
         public string SectionName => GetCurrentPanel().SectionName;
@@ -879,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())
@@ -909,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;
@@ -946,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, "");
             });
@@ -980,8 +959,8 @@ namespace PRSDesktop
                     kanban.Private = false;
                     kanban.JobLink.ID = Job?.ID ?? Guid.Empty;
                     kanban.JobLink.Synchronise(Job ?? new Job());
-                    kanban.EmployeeLink.ID = MyID;
-                    kanban.ManagerLink.ID = MyID;
+                    kanban.EmployeeLink.ID = App.EmployeeID;
+                    kanban.ManagerLink.ID = App.EmployeeID;
                     customise?.Invoke(kanban);
                 });
             if (result != null)
@@ -1024,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);
         }
 
@@ -1039,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);
             }
 
@@ -1067,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);
             }
 
@@ -1094,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);
             }
 
@@ -1117,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);
             }
 
@@ -1161,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();
 

+ 83 - 240
prs.desktop/Panels/Tasks/TasksByStatusControl.xaml

@@ -1,223 +1,31 @@
-<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"
-    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
-    xmlns:local="clr-namespace:PRSDesktop"
-    xmlns:Kanban="clr-namespace:Syncfusion.UI.Xaml.Kanban;assembly=Syncfusion.SfKanban.WPF"
-    x:Class="PRSDesktop.TasksByStatusControl"
-    mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="400">
-
+<UserControl x:Class="PRSDesktop.TasksByStatusControl"
+             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" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:PRSDesktop"
+             mc:Ignorable="d" 
+             x:Name="Control"
+             d:DesignHeight="450" d:DesignWidth="800">
     <UserControl.Resources>
-        <local:StatusTasksHeaderTimeConverter x:Key="StatusTasksHeaderTimeConverter" />
-        <local:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
-        
-        <Style x:Key="employeeStyle" TargetType="{x:Type TextBlock}">
-            <Style.Triggers>
-                <DataTrigger Binding="{Binding AssignedTo}" Value="">
-                    <Setter Property="Visibility" Value="Collapsed" />
-                </DataTrigger>
-            </Style.Triggers>
-        </Style>
-        
-        <Style x:Key="managerStyle" TargetType="{x:Type TextBlock}">
-            <Style.Triggers>
-                <DataTrigger Binding="{Binding Manager}" Value="">
-                    <Setter Property="Visibility" Value="Collapsed"/>
-                </DataTrigger>
-            </Style.Triggers>
-        </Style>
-        
-        <Style x:Key="jobStyle" TargetType="{x:Type TextBlock}">
-            <Style.Triggers>
-                <DataTrigger Binding="{Binding JobNumber}" Value="">
-                    <Setter Property="Visibility" Value="Collapsed" />
-                </DataTrigger>
-            </Style.Triggers>
-        </Style>
-
-        <Style x:Key="descriptionStyle" TargetType="{x:Type TextBlock}">
-            <Style.Triggers>
-                <DataTrigger Binding="{Binding Path=Description}" Value="">
-                    <Setter Property="Visibility" Value="Collapsed" />
-                </DataTrigger>
-            </Style.Triggers>
-        </Style>
-
-        <Style x:Key="notesStyle" TargetType="{x:Type TextBlock}">
-            <Style.Triggers>
-                <DataTrigger Binding="{Binding Path=Notes}" Value="">
-                    <Setter Property="Visibility" Value="Collapsed" />
-                </DataTrigger>
-            </Style.Triggers>
-        </Style>
-
-        <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">-->
-            <StackPanel Orientation="Vertical">
-                <TextBlock Text="{Binding  Header}" FontSize="16" FontWeight="DemiBold" HorizontalAlignment="Left"
-                           Margin="10,0,10,0" />
-                <TextBlock FontSize="12" HorizontalAlignment="Left" Margin="10,0,5,0">
-                    <Run Text="{Binding CardCount}" />
-                    <Run Text="Tasks /" />
-                    <Run Text="{Binding Converter={StaticResource StatusTasksHeaderTimeConverter}, Mode=OneWay}" />
-                    <Run Text="Hours" />
-                </TextBlock>
-            </StackPanel>
-            <!--</Border>-->
-        </DataTemplate>
-
-        <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}">
-                <Grid Margin="4">
-                    <Grid.ColumnDefinitions>
-                        <ColumnDefinition x:Name="colImage" Width="Auto" />
-                        <ColumnDefinition x:Name="colCheckbox" Width="Auto" />
-                        <ColumnDefinition x:Name="colDescription" Width="*" />
-                        <ColumnDefinition x:Name="colType" Width="Auto" />
-                        <ColumnDefinition x:Name="colAttach" Width="Auto" />
-                        <ColumnDefinition x:Name="colDueDate" Width="Auto" />
-                    </Grid.ColumnDefinitions>
-                    <Grid.RowDefinitions>
-                        <RowDefinition Height="Auto" />
-                        <RowDefinition Height="*" />
-                        <RowDefinition Height="Auto" />
-                    </Grid.RowDefinitions>
-
-                    <Border Grid.Column="0" Grid.Row="0" Grid.RowSpan="2" Width="40" Height="40" CornerRadius="20"
-                            Margin="0,0,4,0" BorderBrush="Black" BorderThickness="1" VerticalAlignment="Top">
-                        <Border.Background>
-                            <ImageBrush ImageSource="{Binding Path=Image}" Stretch="UniformToFill" />
-                        </Border.Background>
-                    </Border>
-
-                    <CheckBox Grid.Row="0" Grid.Column="1" Margin="0,0,4,0" VerticalAlignment="Center"
-                              IsChecked="{Binding Path=Checked}" Checked="CheckBox_Checked"
-                              Unchecked="CheckBox_Checked" Tag="{Binding}" />
-                    <TextBlock Grid.Row="0" Grid.Column="2" Margin="0,0,0,0" FontWeight="DemiBold" FontSize="12"
-                               VerticalAlignment="Center">
-                        <Run Text="{Binding Path=Number}" /><Run Text=": " /><Run Text="{Binding Path=Title}" />
-                    </TextBlock>
-                    <TextBlock Grid.Row="0" Grid.Column="3" FontSize="12" Margin="4,0,0,0" Text="{Binding Path=Type.Code}"
-                               FontWeight="DemiBold" VerticalAlignment="Center" HorizontalAlignment="Center" />
-                    <Image Grid.Row="0" Grid.Column="4" Margin="4,0,4,0"
-                           Source="pack://application:,,,Resources/attachment.png"
-                           Visibility="{Binding Path=Attachments, Converter={StaticResource BoolToVisibilityConverter}}"
-                           Width="16" Height="16" VerticalAlignment="Center" />
-                    <TextBlock Grid.Row="0" Grid.Column="5" Text="{Binding DueDate, StringFormat='{}{0:dd MMM yy}'}"
-                               FontWeight="Bold" FontSize="12" VerticalAlignment="Center" HorizontalAlignment="Center" />
-
-                    <TextBlock Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="5" Margin="0,0,0,0" FontSize="12"
-                               HorizontalAlignment="Left" Text="{Binding Path=Description}" VerticalAlignment="Stretch"
-                               TextWrapping="Wrap" />
-
-                    <DockPanel Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="5">
-                        <TextBlock DockPanel.Dock="Right" x:Name="Job" Margin="0" FontWeight="DemiBold" FontSize="12"
-                                   VerticalAlignment="Center" TextAlignment="Right" Style="{StaticResource jobStyle}">
-                            <Run Text="{Binding JobNumber}" /><Run Text=": " /><Run Text="{Binding JobName}" />
-                        </TextBlock>
-                        <TextBlock DockPanel.Dock="Left" x:Name="Staff" FontWeight="DemiBold" FontSize="12"
-                                   VerticalAlignment="Center" Text="{Binding AssignedTo}"
-                                   Style="{StaticResource employeeStyle}" />
-                    </DockPanel>
-                    <Image Grid.Row="2" Grid.Column="0" Margin="0,0,4,0"
-                           Source="pack://application:,,,Resources/lock.png"
-                           Visibility="{Binding Path=Locked, Converter={StaticResource BoolToVisibilityConverter}}"
-                           Width="20" Height="20" VerticalAlignment="Center" HorizontalAlignment="Center" />
-
-
-                </Grid>
-
-                <Border.ToolTip>
-                    <Grid>
-                        <Grid.RowDefinitions>
-                            <RowDefinition Height="Auto"/>
-                            <RowDefinition Height="Auto"/>
-                            <RowDefinition Height="Auto"/>
-                        </Grid.RowDefinitions>
-                        <Grid.ColumnDefinitions>
-                            <ColumnDefinition Width="Auto"/>
-                        </Grid.ColumnDefinitions>
-                        <TextBlock Grid.Row="0" Grid.Column="0">
-                            <Run FontWeight="DemiBold" Text="{Binding Path=Title}"/>
-                        </TextBlock>
-                        <TextBlock Grid.Row="1" Grid.Column="0" Style="{StaticResource descriptionStyle}">
-                            <Run Text="{Binding Path=Description}"/>
-                        </TextBlock>
-                        <TextBlock Grid.Row="2" Grid.Column="0" Style="{StaticResource notesStyle}">
-                            <LineBreak/>
-                            <Run Text="{Binding Path=Notes}"/>
-                        </TextBlock>
-                    </Grid>
-                </Border.ToolTip>
-                
-                <Border.ContextMenu>
-                    <ContextMenu x:Name="TaskMenu" Tag="{Binding}" Opened="TaskMenu_Opened" />
-
-                </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}">
-            
-                <DockPanel Margin="4">
-                    <TextBlock DockPanel.Dock="Right" Text="{Binding DueDate, StringFormat='{}{0:dd MMM yy}'}" FontWeight="Bold" FontSize="12" VerticalAlignment="Center" HorizontalAlignment="Center" />
-
-                    <CheckBox DockPanel.Dock="Left" Margin="0,0,4,0" VerticalAlignment="Center" IsChecked="{Binding Path=Checked}" Checked="CheckBox_Checked" Unchecked="CheckBox_Checked" Tag="{Binding}" />
-
-                    <TextBlock DockPanel.Dock="Left" x:Name="JobNumber" Margin="0"  FontWeight="DemiBold" FontSize="12" VerticalAlignment="Center" Style="{StaticResource jobStyle}">
-                                    <Run Text="{Binding JobNumber}"/><Run Text="/"/>
-                    </TextBlock>
-                    <TextBlock DockPanel.Dock="Left" Margin="0,0,0,0" FontWeight="DemiBold" FontSize="12" VerticalAlignment="Center">
-                            <Run Text="{Binding Path=Number}"/><Run Text=": "/>
-                    </TextBlock>
-
-                    <TextBlock DockPanel.Dock="Left" Margin="0,0,0,0" FontWeight="DemiBold" FontSize="12" VerticalAlignment="Center" Text="{Binding Path=Title}" />
-
-                    <TextBlock DockPanel.Dock="Left" x:Name="Manager" Margin="0"  FontWeight="DemiBold" FontSize="12" VerticalAlignment="Center" Style="{StaticResource managerStyle}">
-                        <Run Text=" ("/><Run Text="{Binding Manager}"/><Run Text=")"/>
-                    </TextBlock>
-                    <TextBlock DockPanel.Dock="Left" x:Name="Employee" Margin="0"  FontWeight="DemiBold" FontSize="12" VerticalAlignment="Center" Style="{StaticResource employeeStyle}">
-                        <Run Text=" ("/><Run Text="{Binding AssignedTo}"/><Run Text=")"/>
-                    </TextBlock>
-
-                </DockPanel>
-
-                <Border.ToolTip>
-                    <Grid>
-                        <Grid.RowDefinitions>
-                            <RowDefinition Height="Auto"/>
-                            <RowDefinition Height="Auto"/>
-                            <RowDefinition Height="Auto"/>
-                        </Grid.RowDefinitions>
-                        <Grid.ColumnDefinitions>
-                            <ColumnDefinition Width="Auto"/>
-                        </Grid.ColumnDefinitions>
-                        <TextBlock Grid.Row="0" Grid.Column="0">
-                            <Run FontWeight="DemiBold" Text="{Binding Path=Title}"/>
-                        </TextBlock>
-                        <TextBlock Grid.Row="1" Grid.Column="0" Style="{StaticResource descriptionStyle}">
-                            <Run Text="{Binding Path=Description}"/>
-                        </TextBlock>
-                        <TextBlock Grid.Row="2" Grid.Column="0" Style="{StaticResource notesStyle}">
-                            <LineBreak/>
-                            <Run Text="{Binding Path=Notes}"/>
-                        </TextBlock>
-                    </Grid>
-                </Border.ToolTip>
-
-                <Border.ContextMenu>
-                    <ContextMenu x:Name="TaskMenu" Tag="{Binding}" Opened="TaskMenu_Opened" />
-                </Border.ContextMenu>
-            
-            </Border>
-        </DataTemplate>
-        
+        <ResourceDictionary>
+            <ResourceDictionary.MergedDictionaries>
+                <ResourceDictionary Source="KanbanResources.xaml"/>
+            </ResourceDictionary.MergedDictionaries>
+        </ResourceDictionary>
     </UserControl.Resources>
-
-    <Grid>
+    <UserControl.CommandBindings>
+        <CommandBinding Command="{x:Static local:KanbanResources.EditTask}"
+                        Executed="EditTask_Executed"
+                        CanExecute="CommandBinding_CanExecute"/>
+        <CommandBinding Command="{x:Static local:KanbanResources.OpenTaskMenu}"
+                        Executed="OpenTaskMenu_Executed"
+                        CanExecute="CommandBinding_CanExecute"/>
+        <CommandBinding Command="{x:Static local:KanbanResources.SelectTask}"
+                        Executed="SelectTask_Executed"
+                        CanExecute="CommandBinding_CanExecute"/>
+    </UserControl.CommandBindings>
+    <Grid x:Name="LayoutRoot">
         <Grid.ColumnDefinitions>
             <ColumnDefinition Width="Auto" x:Name="EmployeeListColumn" />
             <ColumnDefinition Width="*" />
@@ -233,7 +41,7 @@
             <Label Content="Employee List" HorizontalContentAlignment="Center" />
         </Border>
 
-        <ListView Grid.Column="0" Grid.Row="1" x:Name="Employees" HorizontalAlignment="Stretch"
+        <ListView Grid.Column="0" Grid.Row="1" x:Name="EmployeeList" HorizontalAlignment="Stretch"
                   SelectionChanged="Employees_SelectionChanged" Margin="0,0,4,0" Background="White" BorderBrush="Gray"
                   BorderThickness="0.75">
             <ListView.ItemTemplate>
@@ -285,25 +93,60 @@
 
             </DockPanel>
         </Border>
-
-        <Kanban:SfKanban
-            x:Name="Kanban"
-            Grid.Column="1"
-            Grid.Row="1"
-            Margin="0"
-            HorizontalAlignment="Stretch"
-            VerticalAlignment="Stretch"
-            HorizontalContentAlignment="Stretch"
-            VerticalContentAlignment="Stretch"
-            AutoGenerateColumns="False"
-            BorderBrush="Gray"
-            BorderThickness="0.75"
-            Background="WhiteSmoke"
-            CardTemplate="{StaticResource FullKanban}"
-            ColumnHeaderTemplate="{StaticResource KanbanHeader}"
-            SizeChanged="Kanban_SizeChanged"
-            CardDragStart="Kanban_CardDragStart"
-            CardDragEnd="Kanban_CardDragEnd" />
-
+        <ItemsControl x:Name="ColumnsList" ItemsSource="{Binding ElementName=Control,Path=Columns}"
+                      Grid.Row="1" Grid.Column="1">
+            <ItemsControl.ItemsPanel>
+                <ItemsPanelTemplate>
+                    <UniformGrid Rows="1"/>
+                </ItemsPanelTemplate>
+            </ItemsControl.ItemsPanel>
+            <ItemsControl.ItemTemplate>
+                <DataTemplate>
+                    <Border BorderBrush="LightGray" BorderThickness="0.75,0,0,0">
+                        <Grid ContextMenuOpening="Column_ContextMenuOpening"
+                          Tag="{Binding}">
+                            <Grid.ContextMenu>
+                                <ContextMenu/>
+                            </Grid.ContextMenu>
+                            <Grid.RowDefinitions>
+                                <RowDefinition Height="Auto"/>
+                                <RowDefinition Height="*"/>
+                            </Grid.RowDefinitions>
+                            <Border BorderBrush="LightGray" BorderThickness="0,0,0,1" Margin="0,0,0,5">
+                                <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>
+                            </Border>
+                            <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>
     </Grid>
-</UserControl>
+</UserControl>

+ 826 - 1365
prs.desktop/Panels/Tasks/TasksByStatusControl.xaml.cs

@@ -1,1600 +1,1061 @@
-using System;
+using Comal.Classes;
+using InABox.Clients;
+using InABox.Core;
+using InABox.WPF;
+using System;
 using System.Collections.Generic;
-using System.Collections.ObjectModel;
 using System.Globalization;
 using System.Linq;
 using System.Linq.Expressions;
 using System.Reflection;
-using System.Threading;
+using System.Text;
 using System.Threading.Tasks;
 using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Data;
+using System.Windows.Documents;
 using System.Windows.Input;
+using System.Drawing;
 using System.Windows.Media;
 using System.Windows.Media.Imaging;
-using Comal.Classes;
-using InABox.Clients;
-using InABox.Core;
+using Color = System.Drawing.Color;
 using InABox.DynamicGrid;
 using InABox.Wpf;
-using InABox.WPF;
-using org.omg.CORBA;
+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;
-using Color = System.Drawing.Color;
 
-namespace PRSDesktop
+namespace PRSDesktop;
+
+public class TasksByStatusColumn : INotifyPropertyChanged
 {
-    public class EmployeeModel
+    public string Category { get; }
+
+    public string Title { get; }
+
+    public int NumTasks { get => Tasks.Count; }
+
+    public double NumHours { get => Tasks.Sum(x => x.EstimatedTime.TotalHours); }
+
+    public ObservableCollection<TaskModel> Tasks { get; } = new();
+
+    public TasksByStatusColumn(string category, string title)
     {
-        public EmployeeModel(Guid id, string name, Guid thumbnail, BitmapImage image)
-        {
-            ID = id;
-            Name = name;
-            Image = image;
-            ThumbnailID = thumbnail;
-        }
+        Category = category;
+        Title = title;
 
-        public Guid ID { get; set; }
-        public string Name { get; set; }
-        public BitmapImage Image { get; set; }
-        public Guid ThumbnailID { get; set; }
+        Tasks.CollectionChanged += Tasks_CollectionChanged;
     }
 
-    public class StatusTasksHeaderTimeConverter : IValueConverter
+    private void Tasks_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
     {
-        public static IEnumerable<TaskModel> Tasks { get; set; }
+        OnPropertyChanged(nameof(Tasks));
+    }
+
+    public event PropertyChangedEventHandler? PropertyChanged;
 
-        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+    // 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))
         {
-            if (Tasks == null)
-                return "0:00";
+            OnPropertyChanged(nameof(NumTasks));
+            OnPropertyChanged(nameof(NumHours));
+        }
+    }
+}
 
-            var dataContext = value as ColumnTag;
-            if (dataContext == null)
-                return "0:00";
+public class EmployeeModel
+{
+    public Guid ID { get; set; }
 
-            var getter = dataContext.GetType().GetProperty("Column", BindingFlags.NonPublic | BindingFlags.Instance);
-            if (getter == null)
-                return "0:00";
+    public string Name { get; set; }
 
-            var column = (KanbanColumn)getter.GetValue(dataContext);
-            if (column == null)
-                return "0:00";
+    public BitmapImage? Image { get; set; }
 
-            double result = 0.0F;
-            foreach (var kanban in Tasks.Where(x => Equals(x.Category, column.Categories)))
-                result += kanban.EstimatedTime.TotalHours;
-            return string.Format("{0:F2}", result);
-        }
+    public Guid ThumbnailID { get; set; }
 
-        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
-        {
-            throw new NotImplementedException();
-        }
+    public EmployeeModel(Guid id, string name, Guid thumbnail, BitmapImage? image)
+    {
+        ID = id;
+        Name = name;
+        Image = image;
+        ThumbnailID = thumbnail;
     }
+}
 
-    public class BoolToVisibilityConverter : IValueConverter
+public class SuspendableObservableCollection<T> : ObservableCollection<T>
+{
+    private bool _notificationSupressed = false;
+    private bool _supressNotification = false;
+    public bool SupressNotification
     {
-        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        get
         {
-            if (Equals(value, true))
-                return Visibility.Visible;
-            return Visibility.Collapsed;
+            return _supressNotification;
         }
-
-        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        set
         {
-            throw new NotImplementedException();
+            _supressNotification = value;
+            if (_supressNotification == false && _notificationSupressed)
+            {
+                this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
+                _notificationSupressed = false;
+            }
         }
     }
 
-    /// <summary>
-    ///     Interaction logic for KanbanPanel.xaml
-    /// </summary>
-    public partial class TasksByStatusControl : UserControl, ITaskControl
+    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
     {
-
-        private enum Suppress
+        if (SupressNotification)
         {
-            This
+            _notificationSupressed = true;
+            return;
         }
+        base.OnCollectionChanged(e);
+    }
+}
 
-        private BitmapImage _attachimg = PRSDesktop.Resources.attachment.AsBitmapImage();
-
-        private readonly ObservableCollection<EmployeeModel> _employeelist = new();
 
-        private CoreTable _employees;
+/// <summary>
+/// Interaction logic for TasksByStatusControl.xaml
+/// </summary>
+public partial class TasksByStatusControl : UserControl, ITaskControl, INotifyPropertyChanged
+{
+    private enum Suppress
+    {
+        This
+    }
 
-        public CoreTable _kanbans;
+    public SuspendableObservableCollection<TasksByStatusColumn> Columns { get; private set; } = new();
 
-        private BitmapImage _lockimg = PRSDesktop.Resources.lock_sml.AsBitmapImage();
-        private ObservableCollection<TaskModel> _models = new();
-        private CoreTable _types;
+    private List<EmployeeModel> Employees = new();
 
-        public List<string> CheckedKanbans = new();
+    public TasksByStatusControl()
+    {
+        InitializeComponent();
+    }
 
-        // CoreUtils.FullGuid => All Staff
-        // Guid.Empty => Unallocated
-        // Anything Else => Actual Staff Member
-        private Guid EmployeeID = Guid.Empty;
+    #region INotifyPropertyChanged
 
-        private Guid? MyID;
-        private string MyName = "";
-        private string searchtext = "";
-        private Guid selectedtype = CoreUtils.FullGuid;
+    public event PropertyChangedEventHandler? PropertyChanged;
 
-        public TasksByStatusControl()
-        {
-            using (new EventSuppressor(Suppress.This))
-                InitializeComponent();
-        }
+    // 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));
+    }
 
-        public string SectionName => "Tasks By Status";
+    #endregion
 
-        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));
-        }
+    #region Setup
 
-        private void ResizeColumns()
-        {
-            //if (!bResizeRequired)
-            //    return;
+    public void Setup()
+    {
+        var employeesTask = Task.Run(() => LoadEmployees());
+        var kanbanTypesTask = Task.Run(() => LoadKanbanTypes());
 
-            using (var d = Dispatcher.DisableProcessing())
-            {
-                var CollapsedWidth = 50;
-                var CollapsedColumns = 0;
-                Array.ForEach(Kanban.Columns.ToArray(), x => { CollapsedColumns += x.IsExpanded ? 0 : 1; });
-                if (Kanban.Columns.Count > 0 && CollapsedColumns != Kanban.Columns.Count)
-                {
-                    var ColumnWidth = (Kanban.ActualWidth - CollapsedColumns * CollapsedWidth) / (Kanban.Columns.Count - CollapsedColumns) - 2;
-                    if (ColumnWidth != Kanban.ColumnWidth) Kanban.ColumnWidth = ColumnWidth;
-                    //bResizeRequired = false;
-                }
-            }
-        }
+        SetupToolbar();
 
+        SetupColumns();
 
-        private void TaskMenu_Opened(object sender, RoutedEventArgs e)
-        {
-            Host.PopulateMenu(this, sender as ContextMenu);
-        }
+        employeesTask.Wait();
+        SetupEmployeeList();
 
-        private void Kanban_SizeChanged(object sender, SizeChangedEventArgs e)
-        {
-            Kanban.ColumnWidth = Kanban.ActualWidth / Kanban.Columns.Count - 1.0F;
-        }
+        kanbanTypesTask.Wait();
+        SetupKanbanTypesLookup(kanbanTypesTask.Result);
 
-        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;
-        }
+        Mode = Host.KanbanSettings.StatusSettings.CompactView ? KanbanViewMode.Compact : KanbanViewMode.Full;
+    }
 
-        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;
-                }
+    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;
+    }
 
-                FilterKanbans();
-            }
-        }
+    #endregion
 
-        private void Search_KeyUp(object sender, KeyEventArgs e)
-        {
-            if (string.IsNullOrWhiteSpace(Search.Text) || e.Key == Key.Return)
-            {
-                searchtext = Search.Text;
-                Refresh(true);
-            }
-        }
+    #region Columns
 
-        private void TaskTypes_SelectionChanged(object sender, SelectionChangedEventArgs e)
-        {
+    private void SetupColumns()
+    {
+        Columns.SupressNotification = true;
 
-            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;
-            }
+        Columns.Clear();
 
-            Host.KanbanSettings.StatusSettings.SelectedType = selectedtype;
-            Host.SaveSettings();
-            ReloadKanbans();
-        }
+        Columns.Add(new TasksByStatusColumn("Open", "To Do"));
 
-        private void IncludeCompleted_Checked(object sender, RoutedEventArgs e)
-        {
-            if (!IsReady)
-                return;
-            Host.KanbanSettings.StatusSettings.IncludeCompleted = IncludeCompleted.IsChecked.Value;
-            Host.SaveSettings();
-            SetupColumns();
-            ReloadKanbans();
-        }
+        Columns.Add(new TasksByStatusColumn("In Progress", "In Progress"));
 
-        private void IncludeObserved_Checked(object sender, RoutedEventArgs e)
-        {
-            if (!IsReady)
-                return;
-            Host.KanbanSettings.StatusSettings.IncludeObserved = IncludeObserved.IsChecked.Value;
-            Host.SaveSettings();
-            ReloadKanbans();
-        }
+        Columns.Add(new TasksByStatusColumn("Waiting", "Waiting for Others"));
 
-        private void IncludeLocked_Checked(object sender, RoutedEventArgs e)
-        {
-            if (!IsReady)
-                return;
-            Host.KanbanSettings.StatusSettings.IncludeLocked = IncludeLocked.IsChecked.Value;
-            Host.SaveSettings();
-            ReloadKanbans();
-        }
+        if (Host.KanbanSettings.StatusSettings.IncludeCompleted)
+            Columns.Add(new TasksByStatusColumn("Complete", "Completed"));
 
-        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();
+        Columns.SupressNotification = false;
+    }
 
-        }
+    private TasksByStatusColumn? GetColumn(string category)
+    {
+        return Columns.FirstOrDefault(x => x.Category.Equals(category));
+    }
 
-        private static bool UpdatingEmployees = false;
+    private void Column_ContextMenuOpening(object sender, ContextMenuEventArgs e)
+    {
+        if (sender is not FrameworkElement element || element.Tag is not TasksByStatusColumn column) return;
 
-        private void Employees_SelectionChanged(object sender, SelectionChangedEventArgs e)
-        {
-            if (UpdatingEmployees || EventSuppressor.IsSet(Suppress.This))
-                return;
+        var menu = element.ContextMenu;
 
-            if (e.AddedItems.Count == 0)
-            {
-                EmployeeID = Guid.Empty;
-            }
-            else
-            {
-                var model = _employeelist[Employees.SelectedIndex];
-                EmployeeID = model.ID;
-            }
+        menu.Items.Clear();
 
-            CheckedKanbans.Clear();
-            if (IsReady)
-                Refresh(true);
-        }
+        menu.AddItem("New Task", null, CreateTask);
+        menu.AddItem($"Select all {column.Title} tasks", null, column, SelectAll);
+        menu.AddItem($"Deselect all {column.Title} tasks", null, column, DeselectAll);
+    }
 
-        private void Export_Click(object sender, RoutedEventArgs e)
+    private void SelectColumn(TasksByStatusColumn column, bool selected)
+    {
+        SelectedTasks.Clear();
+        if (selected)
         {
-            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
-                )
-            );
+            SelectedTasks.AddRange(column.Tasks);
+            foreach (var task in Tasks)
+                task.Checked = string.Equals(task.Category, column.Category) ? selected : !selected;
         }
 
-        #region ITaskControl Support
+        FilterKanbans();
+    }
 
-        public bool IsReady { get; set; }
+    private void DeselectAll(TasksByStatusColumn column)
+    {
+        SelectColumn(column, false);
+    }
 
-        public ITaskHost Host { get; set; }
+    private void SelectAll(TasksByStatusColumn column)
+    {
+        SelectColumn(column, true);
+    }
 
-        public KanbanViewType KanbanViewType => KanbanViewType.Status;
+    #endregion
 
-        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;
-        }
+    #region Kanban Types
 
-        #endregion
-
-        #region Setup
+    private IEnumerable<KanbanType> LoadKanbanTypes()
+    {
+        return Client.Query(
+            new Filter<KanbanType>(x => x.Hidden).IsEqualTo(false),
+            new Columns<KanbanType>(x => x.ID, x => x.Description),
+            new SortOrder<KanbanType>(x => x.Description))
+            .ToObjects<KanbanType>();
+    }
 
-        public void Setup()
+    private void SetupKanbanTypesLookup(IEnumerable<KanbanType> kanbanTypes)
+    {
+        if (ClientFactory.IsSupported<KanbanType>())
         {
-            using (new EventSuppressor(Suppress.This))
+            var tasktypes = new Dictionary<Guid, string>
+                {
+                    { CoreUtils.FullGuid, "All Types" },
+                    { Guid.Empty, "Unallocated Types" }
+                };
+            foreach(var type in kanbanTypes)
             {
-                SetupToolbar();
+                tasktypes.Add(type.ID, type.Description);
+            }
 
-                SetupColumns();
+            TaskTypes.ItemsSource = tasktypes;
+            if (tasktypes.ContainsKey(Host.KanbanSettings.StatusSettings.SelectedType))
+                TaskTypes.SelectedValue = Host.KanbanSettings.StatusSettings.SelectedType;
+            else
+                TaskTypes.SelectedValue = CoreUtils.FullGuid;
 
-                SetupData();
+            TaskTypesLabel.Visibility = Visibility.Visible;
+            TaskTypes.Visibility = Visibility.Visible;
+        }
+        else
+        {
+            TaskTypesLabel.Visibility = Visibility.Collapsed;
+            TaskTypes.Visibility = Visibility.Collapsed;
+        }
+    }
 
-                SetupKanbanTypesLookup(false);
+    #endregion
 
-                SetupMyEmployee();
+    #region Employees
 
-                SetupEmployeeList();
-            }
-        }
+    private EmployeeModel SelectedEmployee;
 
-        private void SetupMyEmployee()
+    private void LoadEmployees()
+    {
+        Employees.Clear();
+
+        var employeeFilter = Security.IsAllowed<CanViewOthersTasks>()
+            ? new Filter<Employee>(x => x.CanAllocateTasks).IsEqualTo(true)
+                .And(new Filter<Employee>(x => x.FinishDate).IsEqualTo(DateTime.MinValue)
+                    .Or(x => x.FinishDate).IsGreaterThan(DateTime.Today))
+            : new Filter<Employee>(x => x.ID).IsEqualTo(App.EmployeeID);
+
+        var employees = Client.Query<Employee>(
+            employeeFilter,
+            new Columns<Employee>(x => x.ID)
+                .Add(x => x.Thumbnail.ID)
+                .Add(x => x.Name));
+
+        var anonymous = PRSDesktop.Resources.anonymous.AsBitmapImage();
+        anonymous.Freeze();
+        if (Security.IsAllowed<CanViewOthersTasks>())
         {
-            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);
-            }
+            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));
         }
 
-        private void SetupEmployeeList()
+        foreach (var employee in employees.ToObjects<Employee>())
         {
-            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 name = employee.ID == App.EmployeeID ? "My Tasks" : employee.Name;
 
-                var model = new EmployeeModel(id, name, thumbnailid, anonymous);
-
-                if (userid.Equals(ClientFactory.UserGuid))
-                {
-                    _employeelist.Insert(0, model);
-                    selected = model;
-                }
-                else
-                {
-                    _employeelist.Add(model);
-                }
-            }
+            var model = new EmployeeModel(employee.ID, name, employee.Thumbnail.ID, anonymous);
 
-            if (Security.IsAllowed<CanViewOthersTasks>())
+            if (employee.ID == App.EmployeeID)
             {
-                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);
-                        }
-                    );
+                Employees.Insert(0, model);
             }
             else
             {
-                EmployeeListColumn.Width = new GridLength(0.0F, GridUnitType.Pixel);
-                Employees.ItemsSource = _employeelist;
-                Employees.SelectedItem = _employeelist.First();
-                EmployeeID = _employeelist.First().ID;
+                Employees.Add(model);
             }
         }
+    }
 
-        private void ProcessThumbnails(CoreTable data)
+    private void SetupEmployeeList()
+    {
+        if (Security.IsAllowed<CanViewOthersTasks>())
         {
-            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)
+            EmployeeListColumn.Width = new GridLength(1.0F, GridUnitType.Auto);
+            var thumbnails = Employees
+                .Select(e => e.ThumbnailID)
+                .Where(x => x != Guid.Empty).ToArray();
+            EmployeeList.ItemsSource = Employees;
+            SelectedEmployee = Employees.First();
+            EmployeeList.SelectedItem = SelectedEmployee;
+            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) =>
                     {
-                        model.Image = new BitmapImage();
-                        model.Image.LoadImage(row.Get<Document, byte[]>(x => x.Data));
+                        if (data != null)
+                            ProcessThumbnails(data);
                     }
-                }
-
-                UpdatingEmployees = true;
-                Employees.ItemsSource = null;
-                Employees.ItemsSource = _employeelist;
-                Employees.SelectedItem = _employeelist.First();
-                EmployeeID = _employeelist.First().ID;
-                UpdatingEmployees = false;
-            });
+                );
         }
+        else
+        {
+            EmployeeListColumn.Width = new GridLength(0.0F, GridUnitType.Pixel);
+            EmployeeList.ItemsSource = Employees;
+            SelectedEmployee = Employees.First();
+            EmployeeList.SelectedItem = SelectedEmployee;
+        }
+    }
 
-        private void SetupKanbanTypesLookup(bool reload)
+    private bool _updatingEmployees = false;
+    private void ProcessThumbnails(CoreTable data)
+    {
+        Dispatcher.Invoke(() =>
         {
-            if (ClientFactory.IsSupported<KanbanType>())
+            foreach (var row in data.Rows)
             {
-
-                if (reload)
+                var id = row.Get<Document, Guid>(x => x.ID);
+                var model = Employees.FirstOrDefault(x => x.ThumbnailID.Equals(id));
+                if (model != null)
                 {
-                    _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)
-                    );
+                    model.Image = new BitmapImage();
+                    model.Image.LoadImage(row.Get<Document, byte[]>(x => x.Data));
                 }
+            }
 
-                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));
+            _updatingEmployees = true;
+            EmployeeList.ItemsSource = null;
+            EmployeeList.ItemsSource = Employees;
+            SelectedEmployee = Employees.First();
+            EmployeeList.SelectedItem = SelectedEmployee;
+            _updatingEmployees = false;
+        });
+    }
 
-                TaskTypes.ItemsSource = tasktypes;
-                if (tasktypes.ContainsKey(Host.KanbanSettings.StatusSettings.SelectedType))
-                    TaskTypes.SelectedValue = Host.KanbanSettings.StatusSettings.SelectedType;
-                else
-                    TaskTypes.SelectedValue = CoreUtils.FullGuid;
+    private void Employees_SelectionChanged(object sender, SelectionChangedEventArgs e)
+    {
+        if (_updatingEmployees || EventSuppressor.IsSet(Suppress.This))
+            return;
 
-                TaskTypesLabel.Visibility = Visibility.Visible;
-                TaskTypes.Visibility = Visibility.Visible;
-            }
-            else
-            {
-                TaskTypesLabel.Visibility = Visibility.Collapsed;
-                TaskTypes.Visibility = Visibility.Collapsed;
-            }
-        }
+        SelectedEmployee = (EmployeeList.SelectedItem as EmployeeModel)!;
 
-        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;
-        }
+        SelectedTasks.Clear();
+        if (IsReady)
+            Refresh();
+    }
 
-        private void SetupColumns()
-        {
-            Kanban.Columns.Clear();
+    #endregion
 
-            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;
+    #region Kanbans
 
-            Kanban.Columns.Add(new KanbanColumn
-            {
-                Categories = "Open",
-                Title = "To Do"
-            });
+    private List<TaskModel> AllTasks { get; set; } = new();
+    private IEnumerable<TaskModel> Tasks => Columns.SelectMany(x => x.Tasks);
 
-            Kanban.Columns.Add(new KanbanColumn
-            {
-                Categories = "In Progress",
-                Title = "In Progress"
-            });
+    private readonly List<TaskModel> SelectedTasks = new();
 
-            Kanban.Columns.Add(new KanbanColumn
+    private void CreateTask()
+    {
+        var result = Host.CreateKanban(
+            kanban =>
             {
-                Categories = "Waiting",
-                Title = "Waiting for Others"
+                kanban.EmployeeLink.ID = SelectedEmployee.ID != CoreUtils.FullGuid ? SelectedEmployee.ID : App.EmployeeID;
+                kanban.ManagerLink.ID = App.EmployeeID;
+                kanban.ManagerLink.UserLink.ID = ClientFactory.UserGuid;
             });
+        if (result != null)
+            Refresh();
+    }
 
-            if (Host.KanbanSettings.StatusSettings.IncludeCompleted)
-                Kanban.Columns.Add(new KanbanColumn
-                {
-                    Categories = "Complete",
-                    Title = "Completed"
-                });
-            Kanban.InvalidateVisual();
+    private void DoEdit(TaskModel task)
+    {
+        var result = Host.EditReferences(new[] { task });
+        if (result)
+        {
+            Refresh();
+        }
+    }
 
-            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;
-            }
+    private void EditTask_Executed(object sender, ExecutedRoutedEventArgs e)
+    {
+        if (e.Parameter is not TaskModel model) return;
 
-            Kanban.ColumnWidth = Kanban.ActualWidth / Kanban.Columns.Count - 1.0F;
-        }
+        DoEdit(model);
+    }
 
-        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)
-                );
+    private void OpenTaskMenu_Executed(object sender, ExecutedRoutedEventArgs e)
+    {
+        if (e.Parameter is not KanbanResources.OpenTaskMenuCommandArgs args) return;
 
-            query.Query();
+        Host.PopulateMenu(this, args.Model, args.Menu);
+    }
 
-            _employees = query.Get<Employee>();
-            _types = ClientFactory.IsSupported<KanbanType>()
-                ? query.Get<KanbanType>()
-                : null;
+    private void SelectTask_Executed(object sender, ExecutedRoutedEventArgs e)
+    {
+        if (e.Parameter is not TaskModel model) return;
+
+        if (!SelectedTasks.Remove(model))
+        {
+            SelectedTasks.Add(model);
         }
+    }
 
-        #endregion
+    private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
+    {
+        e.CanExecute = true;
+    }
 
-        #region Refresh / Reload
+    private void ItemsControl_DragOver(object sender, DragEventArgs e)
+    {
+        if (sender is not FrameworkElement element || element.Tag is not TasksByStatusColumn column) return;
 
-        private Filter<T> GetSearchFilter<T>(Expression<Func<T, object>> expression) where T : Entity, new()
+        e.Effects = DragDropEffects.None;
+        if (e.Data.GetDataPresent(typeof(TaskModel)))
         {
-            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;
+            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 Filter<KanbanSubscriber> GetKanbanSubscriberFilter()
+    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))
         {
-            var filter = new Filter<KanbanSubscriber>(x => x.Kanban.Closed).IsEqualTo(DateTime.MinValue);
+            if (MessageBox.Show($"Are you sure you want to complete the selected tasks?", "Confirm Completion",
+                    MessageBoxButton.YesNo) != MessageBoxResult.Yes)
+                return;
+        }
+
+        var completed = DateTime.Now;
 
-            if (Host.Job != null)
+        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)
             {
-                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();
+                kanban.Completed = completed;
             }
+        }
+        Client.Save(kanbans, $"Task Status Updated to {status}", (o, err) =>
+        {
+            if (err is not null)
+            {
+                CoreUtils.LogException("", err);
+            }
+        });
 
-            // 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);
+        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();
+    }
 
-            if (EmployeeID != MyID)
-                filter = filter.And(x => x.Kanban.Private).IsEqualTo(false);
+    private void ItemsControl_Drop(object sender, DragEventArgs e)
+    {
+        if (sender is not FrameworkElement element || element.Tag is not TasksByStatusColumn column) return;
 
-            //if (!includeobserved)
-            //    filter = filter.And(new Filter<KanbanSubscriber>(x => x.Assignee).IsEqualTo(true).Or(x => x.Manager).IsEqualTo(true));
+        if (e.Data.GetDataPresent(typeof(TaskModel)))
+        {
+            ChangeStatus(SelectedModels(e.Data.GetData(typeof(TaskModel)) as TaskModel), column.Category);
+        }
+    }
 
-            //if (!includecompleted)
-            //    filter = filter.And(x => x.Kanban.Completed).IsEqualTo(DateTime.MinValue);
+    #endregion
 
-            //if (selectedtype != CoreUtils.FullGuid)
-            //    filter = filter.And(x => x.Kanban.Type.ID).IsEqualTo(selectedtype);
+    #region Filters
 
-            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));
+    private Guid JobFilterID = Guid.Empty;
+    private string SearchText = "";
+    private Guid SelectedType = CoreUtils.FullGuid;
 
-                if (int.TryParse(searchtext.Trim(), out var tasknumber))
-                    search.Ors.Add(new Filter<KanbanSubscriber>(x => x.Kanban.Number).IsEqualTo(tasknumber));
+    private static Filter<T> GetSearchFilter<T>(Expression<Func<T, object?>> expression, string searchText) 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 ?? new Filter<T>().All();
+    }
 
-                filter.Ands.Add(search);
-            }
+    private Filter<KanbanSubscriber> GetKanbanSubscriberFilter()
+    {
+        var filter = new Filter<KanbanSubscriber>(x => x.Kanban.Closed).IsEqualTo(DateTime.MinValue);
 
-            return filter;
+        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 Filter<Kanban> GetKanbanFilter()
+        // 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)
         {
-            var filter = new Filter<Kanban>(x => x.Closed).IsEqualTo(DateTime.MinValue);
+            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 (Host.Job != null)
+        if (SelectedEmployee.ID != CoreUtils.FullGuid)
+        {
+            if (SelectedEmployee.ID != Guid.Empty)
             {
-                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();
+                var empfilter = new Filter<Kanban>(x => x.EmployeeLink.ID).IsEqualTo(SelectedEmployee.ID)
+                    .Or(x => x.ManagerLink.ID).IsEqualTo(SelectedEmployee.ID);
+                filter.Ands.Add(empfilter);
             }
-
-            if (EmployeeID != CoreUtils.FullGuid)
+            else
             {
-                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);
-                }
+                filter = filter.And(x => x.EmployeeLink.ID).IsEqualTo(SelectedEmployee.ID);
             }
+        }
 
-            if (EmployeeID != MyID)
-                filter = filter.And(x => x.Private).IsEqualTo(false);
+        if (SelectedEmployee.ID != App.EmployeeID)
+            filter = filter.And(x => x.Private).IsEqualTo(false);
 
-            //if (!includecompleted)
-            //    filter = filter.And(x => x.Completed).IsEqualTo(DateTime.MinValue);
+        return filter;
+    }
 
-            //if (selectedtype != CoreUtils.FullGuid)
-            //    filter = filter.And(x => x.Type.ID).IsEqualTo(selectedtype);
+    private void TaskTypesLabel_OnClick(object sender, RoutedEventArgs e)
+    {
+        var list = new MasterList(typeof(KanbanType));
+        list.ShowDialog();
+        SetupKanbanTypesLookup(LoadKanbanTypes());
+    }
 
-            return filter;
+    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;
         }
 
-        public void Refresh(bool resetselection)
+        Host.KanbanSettings.StatusSettings.SelectedType = SelectedType;
+        Host.SaveSettings();
+        FilterKanbans();
+    }
+
+    private void JobFilterBtn_OnClick(object sender, RoutedEventArgs e)
+    {
+        if (JobFilterID != Guid.Empty)
         {
-            Application.Current.Dispatcher.Invoke(() => { Mouse.OverrideCursor = Cursors.Wait; });
+            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;
 
-            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 }
-                );
+                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();
+    }
 
-            ReloadKanbans();
-
-            Application.Current.Dispatcher.Invoke(() => { Mouse.OverrideCursor = null; });
-        }
+    private void IncludeCompleted_Checked(object sender, RoutedEventArgs e)
+    {
+        if (!IsReady)
+            return;
+        Host.KanbanSettings.StatusSettings.IncludeCompleted = IncludeCompleted.IsChecked ?? false;
+        Host.SaveSettings();
+        SetupColumns();
+        FilterKanbans();
+    }
 
-        private void ReloadKanbans()
-        {
-            //SetupColumns();
-            //ResizeColumns();
+    private void IncludeObserved_Checked(object sender, RoutedEventArgs e)
+    {
+        if (!IsReady)
+            return;
+        Host.KanbanSettings.StatusSettings.IncludeObserved = IncludeObserved.IsChecked ?? false;
+        Host.SaveSettings();
+        FilterKanbans();
+    }
 
-            _models = new ObservableCollection<TaskModel>();
+    private void IncludeLocked_Checked(object sender, RoutedEventArgs e)
+    {
+        if (!IsReady)
+            return;
+        Host.KanbanSettings.StatusSettings.IncludeLocked = IncludeLocked.IsChecked ?? false;
+        Host.SaveSettings();
+        FilterKanbans();
+    }
 
-            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))
-                        {
+    private void Search_KeyUp(object sender, KeyEventArgs e)
+    {
+        if (string.IsNullOrWhiteSpace(Search.Text) || e.Key == Key.Return)
+        {
+            SearchText = Search.Text;
+            FilterKanbans();
+        }
+    }
 
+    #endregion
 
-                            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)));
-                        }
+    #region Refresh
 
-                        model.EmployeeID = empid;
-                        model.ManagerID = mgrid;
+    private static Columns<Kanban> GetKanbanColumns()
+    {
+        return new Columns<Kanban>(
+            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.EstimatedTime,
+            x => x.Locked);
+    }
 
-                        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 : "";
-                            }
-                        }
+    public void Refresh()
+    {
+        using var cursor = new WaitCursor();
 
-                        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 : "";
-                            }
+        IEnumerable<Kanban> kanbans;
+        if (SelectedEmployee.ID != CoreUtils.FullGuid && SelectedEmployee.ID != Guid.Empty)
+        {
+            kanbans = Client.Query(
+                new Filter<Kanban>(x => x.ID).InQuery(GetKanbanSubscriberFilter(), x => x.Kanban.ID),
+                GetKanbanColumns().Cast<Kanban>(),
+                new SortOrder<Kanban>(x => x.DueDate) { Direction = SortDirection.Ascending }
+            ).ToObjects<Kanban>();
+        }
+        else
+        {
+            kanbans = new Client<Kanban>().Query(
+                GetKanbanFilter(),
+                GetKanbanColumns().Cast<Kanban>(),
+                new SortOrder<Kanban>(x => x.DueDate) { Direction = SortDirection.Ascending }
+            ).ToObjects<Kanban>();
+        }
 
-                        if (!string.IsNullOrEmpty(sEmp))
+        AllTasks = CreateModels(kanbans).ToList();
+        FilterKanbans();
+    }
+    private IEnumerable<TaskModel> CreateModels(IEnumerable<Kanban> 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;
+                model.EstimatedTime = kanban.EstimatedTime;
+
+                var notes = new List<List<string>> { new() };
+                if (kanban.Notes is not null)
+                {
+                    foreach (var line in kanban.Notes)
+                    {
+                        if (line.Equals("==================================="))
                         {
-                            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);
+                            notes.Add(new());
                         }
                         else
                         {
-                            if (!string.IsNullOrWhiteSpace(sMgr))
-                                model.AssignedTo = string.Format("Allocated by {0} ", sMgr);
+                            notes.Last().Add(line);
                         }
+                    }
+                    model.Notes = string.Join(
+                        "\n===================================\n",
+                        notes.Reverse<List<string>>().Select(x => string.Join('\n', x)));
+                }
 
-                        //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);
+                model.EmployeeID = kanban.EmployeeLink.ID;
+                model.ManagerID = kanban.ManagerLink.ID;
 
-                        _models.Add(model);
+                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)
+                            : "";
                     }
                 }
-                catch (Exception e)
+
+                var managerString = "";
+                if (kanban.ManagerLink.ID != SelectedEmployee.ID)
                 {
-                    Logger.Send(LogType.Error, "", string.Format("*** Unknown Error: {0}\n{1}", e.Message, e.StackTrace));
+                    if (kanban.ManagerLink.ID != Guid.Empty)
+                    {
+                        managerString = manager is not null
+                            ? (manager.ID == App.EmployeeID
+                                ? App.EmployeeName
+                                : manager.Name)
+                            : "";
+                    }
                 }
+                model.Manager = managerString;
 
-            StatusTasksHeaderTimeConverter.Tasks = _models;
-            FilterKanbans();
-        }
-
-        private void FilterKanbans()
-        {
-            Progress.ShowModal("Loading", (progress) =>
-            {
-                Dispatcher.BeginInvoke(() => { Progress.Show("Loading"); });
-                if (JobFilterID == Guid.Empty)
+                if (!string.IsNullOrEmpty(employeeString))
                 {
-                    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; });
+                    if (!managerString.IsNullOrWhiteSpace() && !managerString.Equals(employeeString))
+                    {
+                        model.AssignedTo = $"Assigned to {employeeString} by {managerString}";
+                    }
+                    else
+                    {
+                        model.AssignedTo = $"Assigned to {employeeString}";
+                    }
                 }
                 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(() =>
+                    if (!managerString.IsNullOrWhiteSpace())
                     {
-                        Progress.Close();
-                    });
-                });               
-            });
-        }
-
-        #endregion
-
-        #region Kanban Selection Stuff
+                        model.AssignedTo = $"Allocated by {managerString}";
+                    }
+                }
 
-        private void SelectColumn(KanbanColumn column, bool selected)
-        {
-            CheckedKanbans.Clear();
-            if (selected)
+                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)
             {
-                CheckedKanbans.AddRange(_models.Where(x => Equals(x.Category, column.Categories)).Select(x => x.ID));
-                foreach (var model in _models)
-                    model.Checked = Equals(model.Category, column.Categories);
+                CoreUtils.LogException("", e);
+            }
+            if(model is not null)
+            {
+                yield return model;
             }
-
-            FilterKanbans();
         }
+    }
 
-        private void UnSelectAll_Click(object sender, RoutedEventArgs e)
+    /// <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()
+    {
+        IEnumerable<TaskModel> filtered;
+        if (JobFilterID == Guid.Empty)
         {
-            var column = ((MenuItem)sender).Tag as KanbanColumn;
-            SelectColumn(column, false);
+            filtered = AllTasks;
         }
-
-        private void SelectAll_Click(object sender, RoutedEventArgs e)
+        else
         {
-            var column = ((MenuItem)sender).Tag as KanbanColumn;
-            SelectColumn(column, true);
+            filtered = AllTasks.Where(x => x.JobSearch(JobFilterID));
         }
-
-        //private IEnumerable<Guid> GetSelectedKanbanIDs(String currentid)
-        //{
-        //    List<Guid> result = new List<Guid>();
-        //    if (!CheckedKanbans.Contains(currentid))
-        //        result.Add(Guid.Parse(currentid));
-        //    CheckedKanbans.ForEach((id) => result.Add(Guid.Parse(id)));
-        //    return result;
-        //}
-
-        //private Kanban[] GetSelectedKanbans(String currentid)
-        //{
-        //    var ids = GetSelectedKanbanIDs(currentid);
-        //    return Host.LoadKanbans(ids).ToArray();
-        //}
-
-        private void CheckBox_Checked(object sender, RoutedEventArgs e)
+        if (!Host.KanbanSettings.StatusSettings.IncludeLocked)
         {
-            var task = ((CheckBox)sender).Tag as TaskModel;
-            if (task == null)
-                return;
-            if (CheckedKanbans.Contains(task.ID))
-                CheckedKanbans.Remove(task.ID);
-            else
-                CheckedKanbans.Add(task.ID);
+            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);
 
-        #endregion
-
-        #region Context Menu Actions
+        SelectedTasks.Clear();
 
-        private void CreateTask(object sender, RoutedEventArgs e)
+        foreach (var column in Columns)
         {
-            CreateKanban();
+            column.Tasks.Clear();
         }
 
-        private void Border_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
+        foreach (var task in filtered)
         {
-            if (e.ClickCount > 1)
+            var column = GetColumn(task.Category);
+            column?.Tasks.Add(task);
+            if (task.Checked)
             {
-                var task = ((Border)sender).Tag as TaskModel;
-                DoEdit(task);
-                e.Handled = true;
+                SelectedTasks.Add(task);
             }
         }
+    }
 
-        private void EditTask_Click(object sender, RoutedEventArgs e)
-        {
-            var task = ((MenuItem)e.Source).Tag as TaskModel;
-            DoEdit(task);
-            e.Handled = true;
-        }
+    #endregion
 
-        //private void CreateSetout_Click(object sender, RoutedEventArgs e)
-        //{
-        //    MenuItem menu = sender as MenuItem;
-        //    TaskModel task = menu.Tag as TaskModel;
-
-        //    if (task.JobID.Equals(Guid.Empty))
-        //    {
-        //        MessageBox.Show("Please link this task to a job before creating a setout!");
-        //        return;
-        //    }
-
-        //    if (MessageBox.Show("This will convert this task into a Setout.\n\nDo you wish to continue?", "Confirmation", MessageBoxButton.YesNo) != MessageBoxResult.Yes)
-        //        return;
-
-        //    ManufacturingTemplate template = new Client<ManufacturingTemplate>().Load(new Filter<ManufacturingTemplate>(x => x.Code).IsEqualTo("PRS")).FirstOrDefault();
-        //    if (template == null)
-        //    {
-        //        MessageBox.Show("[Pressing] Template does not exist!");
-        //        return;
-        //    }
-
-        //    String setoutnumber = "";
-
-        //    Progress.ShowModal("Creating Setout", (progress) =>
-        //    {
-        //        MultiQuery query = new MultiQuery();
-
-        //        query.Add<ManufacturingTemplateStage>(
-        //            new Filter<ManufacturingTemplateStage>(x => x.Template.ID).IsEqualTo(template.ID),
-        //            null,
-        //            new SortOrder<ManufacturingTemplateStage>(x => x.Sequence)
-        //        );
-
-        //        query.Add<Kanban>(
-        //            new Filter<Kanban>(x => x.ID).IsEqualTo(task.ID),
-        //            null,
-        //            null
-        //        );
-
-        //        query.Query();
-
-        //        ManufacturingTemplateStage[] tstages = query.Get<ManufacturingTemplateStage>().Rows.Select(x => x.ToObject<ManufacturingTemplateStage>()).ToArray();
-
-        //        Kanban kanban = query.Get<Kanban>().Rows.FirstOrDefault()?.ToObject<Kanban>();
-
-        //        progress.Report("Creating Setouts");
-        //        CoreTable setouts = new Client<Setout>().Query(
-        //            new Filter<Setout>(x => x.JobLink.ID).IsEqualTo(kanban.JobLink.ID),
-        //            new Columns<Setout>(x => x.JobLink.JobNumber, x => x.Number),
-        //            null
-        //         );
-
-        //        int ireq = 0;
-        //        String sreq = "";
-        //        while (true)
-        //        {
-        //            ireq++;
-        //            sreq = String.Format("{0}-{1:yyMMdd}-{2}", kanban.JobLink.JobNumber, DateTime.Now, ireq);
-        //            if (!setouts.Rows.Any(r => sreq.Equals(r.Get<Setout, String>(c => c.Number))))
-        //                break;
-        //        }
-
-        //        Setout setout = new Setout();
-        //        setout.Number = sreq;
-        //        setout.JobLink.ID = kanban.JobLink.ID; // = new Client<Job>().Load(new Filter<Job>(x => x.ID).IsEqualTo(kanban.JobLink.ID)).FirstOrDefault();
-        //        setout.Reference = kanban.Title;
-
-        //        var notes = kanban.Notes.ToList();
-        //        var description = kanban.Summary;
-        //        if (String.IsNullOrWhiteSpace(description))
-        //            description = CoreUtils.StripHTML(kanban.Description);
-        //        if (!String.IsNullOrWhiteSpace(description))
-        //            notes.Insert(0, description);
-        //        setout.Description = String.Join("\n==========================================\n", notes);
-
-        //        new Client<Setout>().Save(setout, "Created from Task");
-        //        setoutnumber = setout.Number;
-
-        //        progress.Report("Creating Manufacturing Packet");
-
-        //        ManufacturingPacket packet = new ManufacturingPacket()
-        //        {
-        //            Group = template.Factory.Name,
-        //            Serial = template.Code,
-        //            Title = kanban.Title,
-        //            Quantity = 1,
-        //            BarcodeQty = 1,
-        //            DueDate = kanban.DueDate
-        //        };
-        //        packet.SetoutLink.ID = setout.ID;
-        //        //packet.JobLink.ID = setout.JobLink.ID;
-        //        packet.ManufacturingTemplateLink.ID = template.ID;
-        //        packet.ManufacturingTemplateLink.Code = template.Code;
-        //        new Client<ManufacturingPacket>().Save(packet, "Created from Task");
-
-        //        List<ManufacturingPacketStage> pstages = new List<ManufacturingPacketStage>();
-        //        foreach (var tstage in tstages)
-        //        {
-        //            var pstage = new ManufacturingPacketStage()
-        //            {
-        //                Time = tstage.Time,
-        //                Sequence = tstage.Sequence,
-        //                SequenceType = tstage.SequenceType,
-        //                Started = DateTime.MinValue,
-        //                PercentageComplete = 0.0F,
-        //                Completed = DateTime.MinValue,
-        //                QualityChecks = tstage.QualityChecks,
-        //                QualityStatus = QualityStatus.NotChecked,
-        //                QualityNotes = "",
-        //            };
-        //            pstage.Parent.ID = packet.ID;
-        //            pstage.ManufacturingSectionLink.ID = tstage.Section.ID;
-        //            pstage.ManufacturingSectionLink.Name = tstage.Section.Name;
-        //            pstages.Add(pstage);
-        //        }
-        //        new Client<ManufacturingPacketStage>().Save(pstages, "Created from Task", (_, __) => { });
-
-        //        progress.Report("Processing Documents");
-
-
-        //        List<SetoutDocument> _setoutdocuments = new List<SetoutDocument>();
-        //        List<KanbanDocument> _kanbandocuments = new List<KanbanDocument>();
-        //        KanbanDocument[] docrefs = new Client<KanbanDocument>().Load(new Filter<KanbanDocument>(x => x.EntityLink.ID).IsEqualTo(kanban.ID));
-        //        foreach (var docref in docrefs)
-        //        {
-        //            Guid docid = ProcessSetoutDocument(docref);
-        //            var newdoc = new SetoutDocument();
-        //            newdoc.EntityLink.ID = setout.ID;
-        //            newdoc.DocumentLink.ID = docid;
-        //            _setoutdocuments.Add(newdoc);
-
-        //            if (docid != docref.DocumentLink.ID)
-        //            {
-        //                docref.DocumentLink.ID = docid;
-        //                _kanbandocuments.Add(docref);
-        //            }
-        //        }
-        //        new Client<SetoutDocument>().Save(_setoutdocuments, "Converted from Task", (_, __) => { });
-        //        new Client<KanbanDocument>().Save(_kanbandocuments, "Converted to PDF", (_, __) => { });
-
-        //        SetoutKanban link = new SetoutKanban();
-        //        link.Entity.ID = setout.ID;
-        //        link.Kanban.ID = kanban.ID;
-        //        new Client<SetoutKanban>().Save(link, "Converting Task -> Setout", (_, __) => { });
-
-        //        progress.Report("Updating Task");
-        //        kanban.Title = kanban.Title + " (" + setoutnumber + ")";
-        //        kanban.Locked = true;
-        //        new Client<Kanban>().Save(kanban, "Converting Kanban to Setout");
-
-
-        //    });
-
-        //    MessageBox.Show(String.Format("Created Setout {0}", setoutnumber));
-
-        //    Refresh();
-
-        //}
-
-        //private Guid ProcessSetoutDocument(KanbanDocument docref)
-        //{
-        //    Guid result = docref.DocumentLink.ID;
-
-        //    String ext = System.IO.Path.GetExtension(docref.DocumentLink.FileName).ToLower();
-
-        //    if (ext.EndsWith("txt"))
-        //    {
-        //        var doc = new Client<Document>().Load(new Filter<Document>(x => x.ID).IsEqualTo(docref.DocumentLink.ID)).FirstOrDefault();
-        //        PdfDocument pdf = new PdfDocument();
-        //        PdfPage page = pdf.Pages.Add();
-        //        PdfGraphics graphics = page.Graphics;
-        //        PdfFont font = new PdfStandardFont(PdfFontFamily.Courier, 12);
-        //        String text = System.Text.Encoding.UTF8.GetString(doc.Data);
-        //        graphics.DrawString(text, font, PdfBrushes.Black, new PointF(0, 0));
-        //        MemoryStream ms = new MemoryStream();
-        //        pdf.Save(ms);
-        //        pdf.Close(true);
-        //        byte[] data = ms.ToArray();
-        //        var newdoc = new Document()
-        //        {
-        //            Data = data,
-        //            FileName = System.IO.Path.ChangeExtension(docref.DocumentLink.FileName, "pdf"),
-        //            CRC = CoreUtils.CalculateCRC(data),
-        //            TimeStamp = DateTime.Now,
-        //        };
-        //        new Client<Document>().Save(newdoc, "Converted from Text");
-        //        return newdoc.ID;
-        //    }
-
-        //    else if (ext.EndsWith("png") || ext.EndsWith("bmp") || ext.EndsWith("jpg") || ext.EndsWith("jpeg"))
-        //    {
-        //        var doc = new Client<Document>().Load(new Filter<Document>(x => x.ID).IsEqualTo(docref.DocumentLink.ID)).FirstOrDefault();
-
-        //        PdfBitmap image = new PdfBitmap(new MemoryStream(doc.Data));
-        //        PdfDocument pdf = new PdfDocument();
-        //        pdf.PageSettings.Orientation = image.Height > image.Width ? PdfPageOrientation.Portrait : PdfPageOrientation.Landscape;
-        //        pdf.PageSettings.Size = new SizeF(image.Width, image.Height);
-        //        PdfPage page = pdf.Pages.Add();
-        //        PdfGraphics graphics = page.Graphics;
-        //        graphics.DrawImage(image, 0.0F, 0.0F);
-        //        MemoryStream ms = new MemoryStream();
-        //        pdf.Save(ms);
-        //        pdf.Close(true);
-        //        byte[] data = ms.ToArray();
-        //        var newdoc = new Document()
-        //        {
-        //            Data = data,
-        //            FileName = System.IO.Path.ChangeExtension(docref.DocumentLink.FileName, "pdf"),
-        //            CRC = CoreUtils.CalculateCRC(data),
-        //            TimeStamp = DateTime.Now,
-        //        };
-        //        new Client<Document>().Save(newdoc, "Converted from Image");
-        //        return newdoc.ID;
-        //    }
-
-        //    return result;
-
-        //}
-
-        //private void CreateRequisition_Click(object sender, RoutedEventArgs e)
-        //{
-        //    MenuItem menu = sender as MenuItem;
-        //    TaskModel task = menu.Tag as TaskModel;
-
-        //    if (task.JobID.Equals(Guid.Empty))
-        //    {
-        //        MessageBox.Show("Please link this task to a job before creating a requisition!");
-        //        return;
-        //    }
-
-        //    if (MessageBox.Show("This will convert this task into a Requisition.\n\nDo you wish to continue?", "Confirmation", MessageBoxButton.YesNo) != MessageBoxResult.Yes)
-        //        return;
-
-        //    int requinumber = 0;
-        //    Progress.ShowModal("Creating Requisition", (progress) =>
-        //    {
-        //        Kanban kanban = new Client<Kanban>().Load(new Filter<Kanban>(x => x.ID).IsEqualTo(task.ID)).FirstOrDefault();
-
-        //        Requisition requi = new Requisition();
-        //        requi.JobLink.ID = kanban.JobLink.ID;
-
-        //        requi.RequestedBy.ID = kanban.ManagerLink.ID;
-        //        requi.Employee.ID = Guid.Empty;
-        //        requi.Title = kanban.Title;
-
-        //        requi.Request = String.IsNullOrWhiteSpace(kanban.Summary)
-        //            ? String.IsNullOrWhiteSpace(kanban.Summary)
-        //                ? String.Join("\n", kanban.Notes)
-        //                : CoreUtils.StripHTML(kanban.Description)
-        //            : kanban.Summary;
-
-        //        requi.Notes = kanban.Notes;
-        //        requi.Due = kanban.DueDate;
-        //        new Client<Requisition>().Save(requi, "Created from Task");
-        //        requinumber = requi.Number;
-        //        progress.Report("Updating Documents");
-
-        //        List<RequisitionDocument> _documents = new List<RequisitionDocument>();
-        //        KanbanDocument[] documents = new Client<KanbanDocument>().Load(new Filter<KanbanDocument>(x => x.EntityLink.ID).IsEqualTo(kanban.ID));
-        //        foreach (var document in documents)
-        //        {
-        //            var newdoc = new RequisitionDocument();
-        //            newdoc.EntityLink.ID = requi.ID;
-        //            newdoc.DocumentLink.ID = document.DocumentLink.ID;
-        //            _documents.Add(newdoc);
-
-        //        }
-        //        new Client<RequisitionDocument>().Save(_documents, "Converted from Task", (_, __) => { });
-
-        //        RequisitionKanban link = new RequisitionKanban();
-        //        link.Entity.ID = requi.ID;
-        //        link.Kanban.ID = kanban.ID;
-        //        new Client<RequisitionKanban>().Save(link, "Converting Task -> Requisition", (_, __) => { });
-
-        //        progress.Report("Updating Task");
-        //        kanban.Category = "Open";
-        //        kanban.Completed = DateTime.MinValue;
-        //        kanban.Locked = true;
-        //        kanban.Title = kanban.Title + " (Requi #" + requi.Number.ToString() + ")";
-        //        new Client<Kanban>().Save(kanban, "Converted to Requisition", (_, __) => { });
-
-        //    });
-
-        //    MessageBox.Show(String.Format("Created Requisition {0}", requinumber));
-        //    Refresh();
-
-        //}
-
-        //private void CreatePurchaseOrder_Click(object sender, RoutedEventArgs e)
-        //{
-        //    MenuItem menu = sender as MenuItem;
-        //    TaskModel task = menu.Tag as TaskModel;
-
-        //    if (MessageBox.Show("This will convert this task into a Purchase Order.\n\nDo you wish to continue?", "Confirmation", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
-        //    {
-        //        String ponumber = "";
-        //        Progress.ShowModal("Creating Purchase Order", (progress) =>
-        //        {
-
-        //            Kanban kanban = new Client<Kanban>().Load(new Filter<Kanban>(x => x.ID).IsEqualTo(task.ID)).FirstOrDefault();
-
-        //            PurchaseOrder order = new PurchaseOrder();
-        //            order.Notes = kanban.Summary;
-        //            order.RaisedBy.ID = kanban.EmployeeLink.ID;
-        //            new Client<PurchaseOrder>().Save(order, "Created from Task Screen");
-        //            ponumber = order.PONumber;
-
-        //            progress.Report("Updating Documents");
-        //            List<PurchaseOrderDocument> docs = new List<PurchaseOrderDocument>();
-        //            var taskdocs = new Client<KanbanDocument>().Load(new Filter<KanbanDocument>(x => x.EntityLink.ID).IsEqualTo(Guid.Parse(task.ID)));
-        //            foreach (var taskdoc in taskdocs)
-        //            {
-        //                PurchaseOrderDocument doc = new PurchaseOrderDocument();
-        //                doc.DocumentLink.ID = taskdoc.DocumentLink.ID;
-        //                doc.EntityLink.ID = order.ID;
-        //                docs.Add(doc);
-        //            }
-        //            new Client<PurchaseOrderDocument>().Save(docs, "", (_, __) => { });
-
-        //            progress.Report("Creating Links");
-        //            PurchaseOrderKanban link = new PurchaseOrderKanban();
-        //            link.Entity.ID = order.ID;
-        //            link.Kanban.ID = kanban.ID;
-        //            new Client<PurchaseOrderKanban>().Save(link, "Converting Task -> Purchase Order", (_, __) => { });
-
-        //            progress.Report("Updating Task");
-        //            kanban.Category = "Open";
-        //            kanban.Completed = DateTime.MinValue;
-        //            kanban.Locked = true;
-        //            kanban.Title = "(PO#" + order.PONumber.ToString() + ") " + kanban.Title;
-        //            new Client<Kanban>().Save(kanban, "Converted to Purchase Order", (_, __) => { });
-
-        //        });
-
-        //        MessageBox.Show(String.Format("Created Purchase Order {0}", ponumber));
-        //        Refresh();
-
-
-        //    }
-
-        //}
-
-        //private void CreateDelivery_Click(object sender, RoutedEventArgs e)
-        //{
-        //    MenuItem menu = sender as MenuItem;
-        //    TaskModel task = menu.Tag as TaskModel;
-
-        //    if (MessageBox.Show("This will convert this task into a Delivery.\n\nDo you wish to continue?", "Confirmation", MessageBoxButton.YesNo) != MessageBoxResult.Yes)
-        //        return;
-
-        //    int deliverynumber = 0;
-        //    Progress.ShowModal("Creating Delivery", (progress) =>
-        //    {
-        //        Kanban kanban = new Client<Kanban>().Query(new Filter<Kanban>(x => x.ID).IsEqualTo(task.ID)).Rows.FirstOrDefault()?.ToObject<Kanban>();
-
-        //        Delivery delivery = new Delivery();
-        //        delivery.Due = kanban.DueDate;
-        //        delivery.Job.ID = kanban.JobLink.ID;
-        //        delivery.Job.Synchronise(kanban.JobLink);
-        //        delivery.Notes = kanban.Summary;
-        //        delivery.Employee.ID = kanban.ManagerLink.ID;
-        //        delivery.Employee.Synchronise(kanban.ManagerLink);
-        //        new Client<Delivery>().Save(delivery, "Created From Task");
-        //        deliverynumber = delivery.Number;
-
-        //        progress.Report("Updating Documents");
-        //        List<DeliveryDocument> docs = new List<DeliveryDocument>();
-        //        var taskdocs = new Client<KanbanDocument>().Load(new Filter<KanbanDocument>(x => x.EntityLink.ID).IsEqualTo(Guid.Parse(task.ID)));
-        //        foreach (var taskdoc in taskdocs)
-        //        {
-        //            DeliveryDocument doc = new DeliveryDocument();
-        //            doc.DocumentLink.ID = taskdoc.DocumentLink.ID;
-        //            doc.EntityLink.ID = delivery.ID;
-        //            docs.Add(doc);
-        //        }
-        //        new Client<DeliveryDocument>().Save(docs, "", (_, __) => { });
-
-        //        progress.Report("Creating Links");
-        //        DeliveryKanban link = new DeliveryKanban();
-        //        link.Entity.ID = delivery.ID;
-        //        link.Kanban.ID = kanban.ID;
-        //        new Client<DeliveryKanban>().Save(link, "Converting Task -> Delivery", (_, __) => { });
-
-        //        progress.Report("Updating Task");
-        //        kanban.Category = "Open";
-        //        kanban.Completed = DateTime.MinValue;
-        //        kanban.Locked = true;
-        //        kanban.Title = "(Del#" + delivery.Number.ToString() + ") " + kanban.Title;
-        //        new Client<Kanban>().Save(kanban, "Converted to Delivery", (_, __) => { });
-
-        //    });
-
-        //    MessageBox.Show(String.Format("Created Delivery {0}", deliverynumber));
-        //    Refresh();
-
-        //}
-
-        //private void CloseTask_Click(object sender, RoutedEventArgs e)
-        //{
-        //    if (MessageBox.Show("Are you sure you want to remove the selected tasks from the list?", "Confirm removal", MessageBoxButton.YesNo) != MessageBoxResult.Yes)
-        //        return;
-        //    Progress.ShowModal("Closing Kanbans", (progress) =>
-        //    {
-        //        TaskModel task = ((MenuItem)e.Source).Tag as TaskModel;
-
-        //        Kanban[] kanbans = GetSelectedKanbans(task.ID);
-        //        for (int i = 0; i < kanbans.Length; i++)
-        //        {
-        //            Kanban kanban = kanbans[i];
-        //            kanban.Closed = DateTime.Now;
-        //        }
-        //        new Client<Kanban>().Save(kanbans, "Kanban Marked as Closed");
-        //        CheckedKanbans.Clear();
-
-        //    });
-        //    Refresh();
-
-        //}
-
-        //private void EmailTask_Click(object sender, RoutedEventArgs e)
-        //{
-
-        //    MenuItem menu = sender as MenuItem;
-        //    TaskModel task = menu.Tag as TaskModel;
-        //    Kanban kanban = new Client<Kanban>().Load(new Filter<Kanban>(x => x.ID).IsEqualTo(task.ID)).FirstOrDefault();
-
-        //    List<String> to = new List<string>();
-
-        //    String from = "";
-        //    String salutation = "";
-
-        //    if (MyID.HasValue)
-        //    {
-
-        //        CoreRow me = _employees.Rows.FirstOrDefault(r => r.Get<Employee, Guid>(c => c.ID).Equals(MyID));
-        //        if (me != null)
-        //            from = me.Get<Employee, String>(c => c.Name).Split(' ').FirstOrDefault();
-
-        //        if (kanban.EmployeeLink.ID != MyID.Value)
-        //        {
-        //            CoreRow emp = _employees.Rows.FirstOrDefault(r => r.Get<Employee, Guid>(c => c.ID).Equals(kanban.EmployeeLink.ID));
-        //            if (emp != null)
-        //            {
-        //                String email = emp.Get<Employee, String>(c => c.Email);
-        //                if (!string.IsNullOrEmpty(email))
-        //                {
-        //                    to.Add(email);
-        //                    String name = emp.Get<Employee, String>(c => c.Name).Split(' ').FirstOrDefault();
-        //                    salutation = salutation + (String.IsNullOrEmpty(salutation) ? "Hi " : " and ") + name;
-        //                }
-
-        //            }
-        //        }
-
-        //        if (kanban.ManagerLink.ID != MyID.Value)
-        //        {
-        //            CoreRow emp = _employees.Rows.FirstOrDefault(r => r.Get<Employee, Guid>(c => c.ID).Equals(kanban.ManagerLink.ID));
-        //            if (emp != null)
-        //            {
-        //                String email = emp.Get<Employee, String>(c => c.Email);
-        //                if (!string.IsNullOrEmpty(email))
-        //                {
-        //                    to.Add(email);
-        //                    String name = emp.Get<Employee, String>(c => c.Name).Split(' ').FirstOrDefault();
-        //                    salutation = salutation + (String.IsNullOrEmpty(salutation) ? "Hi " : " and ") + name;
-        //                }
-        //            }
-        //        }
-        //    }
-
-        //    Outlook.Application outlookApp = new Outlook.Application();
-        //    Outlook.MailItem mailItem = (Outlook.MailItem)outlookApp.CreateItem(Outlook.OlItemType.olMailItem);
-        //    mailItem.Subject = "PRS Task: " + kanban.Title;
-        //    mailItem.To = String.Join("; ", to);
-        //    mailItem.HTMLBody = String.Format("{0},<br><br>Please see the above task in PRS.<br><br>Regards,<br><br>{1}<br><br><b><u>Task Description:</u></b><br><i>{2}</i><br><br><b><u>Additional Notes:</u></b><br>{3}", salutation, from, CoreUtils.StripHTML(kanban.Description), String.Join("\r\n", kanban.Notes));
-        //    //mailItem.Attachments.Add(filename, Outlook.OlAttachmentType.olByValue, Type.Missing, Type.Missing);
-        //    mailItem.Display(false);
-        //}
-
-        //private void CompleteTask_Click(object sender, RoutedEventArgs e)
-        //{
-        //    if (MessageBox.Show("Are you sure you want to mark the selected tasks as complete?", "Confirm Completion", MessageBoxButton.YesNo) != MessageBoxResult.Yes)
-        //        return;
-        //    TaskModel task = ((MenuItem)e.Source).Tag as TaskModel;
-        //    Progress.ShowModal("Completing Task", (progress) =>
-        //    {
-
-        //        Kanban[] kanbans = GetSelectedKanbans(task.ID);
-        //        for (int i = 0; i < kanbans.Length; i++)
-        //        {
-        //            Kanban kanban = kanbans[i];
-        //            kanban.Completed = DateTime.Now;
-        //            kanban.Category = "Complete";
-        //        }
-        //        new Client<Kanban>().Save(kanbans, "Kanban Marked as Completed");
-        //        CheckedKanbans.Clear();
-
-        //    });
-        //    Refresh();
-        //}
-
-        #endregion
-
-        #region Kanban Creation / Editing
-
-        public void CreateKanban()
-        {
-            var result = Host.CreateKanban(
-                kanban =>
-                {
-                    kanban.EmployeeLink.ID = EmployeeID != CoreUtils.FullGuid ? EmployeeID : MyID.Value;
-                    kanban.ManagerLink.ID = MyID.Value;
-                    kanban.ManagerLink.UserLink.ID = ClientFactory.UserGuid;
-                });
-            if (result != null)
-                Refresh(true);
-        }
+    #region ITaskControl
 
-        private void DoEdit(TaskModel task)
-        {
-            if (task == null)
-                return;
+    public ITaskHost Host { get; set; }
 
-            var result = Host.EditReferences(new[] { task });
-            if (result)
-                Refresh(true);
-        }
+    public KanbanViewType KanbanViewType => KanbanViewType.Status;
+
+    public bool IsReady { get; set; }
 
-        #endregion
+    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));
+    }
 
-        private void TaskTypesLabel_OnClick(object sender, RoutedEventArgs e)
+    public IEnumerable<TaskModel> SelectedModels(TaskModel? sender = null)
+    {
+        if(sender is null)
         {
-            var list = new MasterList(typeof(KanbanType));
-            list.ShowDialog();
-            SetupKanbanTypesLookup(true);
+            return SelectedTasks;
         }
-
-        private void JobFilterBtn_OnClick(object sender, RoutedEventArgs e)
+        else
         {
-            if (JobFilterID != Guid.Empty)
+            var result = SelectedTasks.ToList();
+            if (!result.Contains(sender))
             {
-                JobFilterBtn.Content = "Filter Job";
-                JobFilterID = Guid.Empty;
-                FilterKanbans();
-                return;
+                result.Add(sender);
             }
+            return result;
+        }
+    }
 
-            var window = new ThemableWindow();
-            var grid = new JobGrid();
-            grid.Reconfigure(options =>
+    #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)
             {
-                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) =>
+                employee = "Unallocated Tasks";
+            }
+            else
             {
-                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();
+                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);
+            }
         }
 
-        Guid JobFilterID = Guid.Empty;
+        ExcelExporter.DoExport<Kanban>(
+            export,
+            string.Format(
+                "{0} ({1:dd-MMM-yy})",
+                employee,
+                DateTime.Today
+            )
+        );
+    }
 
-        private void AddJobFilter(CoreRow item)
+    private KanbanViewMode _mode;
+    public KanbanViewMode Mode
+    {
+        get => _mode;
+        set
         {
-            JobFilterID = item.Get<Job, Guid>(x => x.ID);
-            JobFilterBtn.Content = item.Get<Job, string>(x => x.JobNumber) + " (click to cancel)";
-            FilterKanbans();
+            _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();
+    }
+}

+ 123 - 0
prs.desktop/Panels/Tasks/TasksByStatusControlOld.xaml

@@ -0,0 +1,123 @@
+<!--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"
+    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+    xmlns:local="clr-namespace:PRSDesktop"
+    xmlns:wpf="clr-namespace:InABox.Wpf;assembly=InABox.Wpf"
+    xmlns:Kanban="clr-namespace:Syncfusion.UI.Xaml.Kanban;assembly=Syncfusion.SfKanban.WPF"
+    x:Class="PRSDesktop.TasksByStatusControlOld"
+    mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="400">
+
+    <UserControl.Resources>
+        <local:StatusTasksHeaderTimeConverter x:Key="StatusTasksHeaderTimeConverter" />
+        <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">->
+            <StackPanel Orientation="Vertical">
+                <TextBlock Text="{Binding  Header}" FontSize="16" FontWeight="DemiBold" HorizontalAlignment="Left"
+                           Margin="10,0,10,0" />
+                <TextBlock FontSize="12" HorizontalAlignment="Left" Margin="10,0,5,0">
+                    <Run Text="{Binding CardCount}" />
+                    <Run Text="Tasks /" />
+                    <Run Text="{Binding Converter={StaticResource StatusTasksHeaderTimeConverter}, Mode=OneWay}" />
+                    <Run Text="Hours" />
+                </TextBlock>
+            </StackPanel>
+            <!-</Border>->
+        </DataTemplate>
+
+        
+    </UserControl.Resources>
+
+    <Grid>
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition Width="Auto" x:Name="EmployeeListColumn" />
+            <ColumnDefinition Width="*" />
+        </Grid.ColumnDefinitions>
+
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto" />
+            <RowDefinition Height="*" />
+        </Grid.RowDefinitions>
+
+        <Border Grid.Row="0" Grid.Column="0" CornerRadius="5,5,0,0" BorderBrush="Gray" BorderThickness="0.75"
+                Background="WhiteSmoke" Padding="5" Margin="0,0,4,2">
+            <Label Content="Employee List" HorizontalContentAlignment="Center" />
+        </Border>
+
+        <ListView Grid.Column="0" Grid.Row="1" x:Name="Employees" HorizontalAlignment="Stretch"
+                  SelectionChanged="Employees_SelectionChanged" Margin="0,0,4,0" Background="White" BorderBrush="Gray"
+                  BorderThickness="0.75">
+            <ListView.ItemTemplate>
+                <DataTemplate>
+                    <Grid HorizontalAlignment="Stretch" Margin="4,2,4,2">
+                        <StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch">
+                            <Border Width="40" Height="40" CornerRadius="20" BorderBrush="Black" BorderThickness="1"
+                                    VerticalAlignment="Center" HorizontalAlignment="Center">
+                                <Border.Background>
+                                    <ImageBrush ImageSource="{Binding Path=Image}" Stretch="UniformToFill" />
+                                </Border.Background>
+                            </Border>
+                            <TextBlock x:Name="PCName" Height="Auto" TextWrapping="Wrap" Width="80" Margin="4,0,0,0"
+                                       TextAlignment="Left" Text="{Binding Name}" VerticalAlignment="Center" />
+                        </StackPanel>
+                    </Grid>
+                </DataTemplate>
+            </ListView.ItemTemplate>
+        </ListView>
+
+        <Border Grid.Row="0" Grid.Column="1" CornerRadius="5,5,0,0" BorderBrush="Gray" BorderThickness="0.75"
+                Background="WhiteSmoke" Padding="5" Margin="0,0,0,2">
+            <DockPanel>
+                <Button DockPanel.Dock="Left" x:Name="TaskTypesLabel" Content="Filter" Padding="4,0" Click="TaskTypesLabel_OnClick" />
+                <ComboBox DockPanel.Dock="Left" x:Name="TaskTypes" Margin="5,0,0,0" Width="150" SelectedIndex="0"
+                          SelectionChanged="TaskTypes_SelectionChanged" VerticalContentAlignment="Center"
+                          SelectedValuePath="Key" DisplayMemberPath="Value" />
+                <Button DockPanel.Dock="Left" x:Name="JobFilterBtn" Content="Filter Job" Padding="4,0" Margin="5,0,0,0" Click="JobFilterBtn_OnClick" />
+                <Label DockPanel.Dock="Left" Content="Search" Margin="5,0,0,0" />
+                <Button DockPanel.Dock="Right" x:Name="Export" Padding="10,0" Margin="5,0,0,0" Content="Export"
+                        Click="Export_Click" />
+                <CheckBox DockPanel.Dock="Right" x:Name="IncludeCompleted" Margin="10,0,5,0"
+                          Content="Include Completed" VerticalAlignment="Center" HorizontalAlignment="Right"
+                          Checked="IncludeCompleted_Checked" Unchecked="IncludeCompleted_Checked" />
+                <CheckBox DockPanel.Dock="Right" x:Name="IncludeObserved" Margin="10,0,5,0" Content="Include Observing"
+                          VerticalAlignment="Center" HorizontalAlignment="Right" Checked="IncludeObserved_Checked"
+                          Unchecked="IncludeObserved_Checked" />
+                <CheckBox DockPanel.Dock="Right" x:Name="IncludeLocked" Margin="10,0,5,0" Content="Include Locked"
+                          VerticalAlignment="Center" HorizontalAlignment="Right" Checked="IncludeLocked_Checked"
+                          Unchecked="IncludeLocked_Checked" />
+                <ComboBox DockPanel.Dock="Right" x:Name="ViewType" Margin="5,0,0,0" Width="100" SelectedIndex="0"
+                          SelectionChanged="ViewType_SelectionChanged" VerticalContentAlignment="Center">
+                    <ComboBoxItem Content="Full" />
+                    <ComboBoxItem Content="Compact" />
+                </ComboBox>
+                <Label DockPanel.Dock="Right" x:Name="ViewTypeLabel" Content="View" Margin="5,0,0,0" />
+                <TextBox DockPanel.Dock="Left" x:Name="Search" Margin="5,0,0,0" HorizontalAlignment="Stretch"
+                         VerticalContentAlignment="Center" KeyUp="Search_KeyUp" />
+
+            </DockPanel>
+        </Border>
+
+        <Kanban:SfKanban
+            x:Name="Kanban"
+            Grid.Column="1"
+            Grid.Row="1"
+            Margin="0"
+            HorizontalAlignment="Stretch"
+            VerticalAlignment="Stretch"
+            HorizontalContentAlignment="Stretch"
+            VerticalContentAlignment="Stretch"
+            AutoGenerateColumns="False"
+            BorderBrush="Gray"
+            BorderThickness="0.75"
+            Background="WhiteSmoke"
+            CardTemplate="{StaticResource FullKanban}"
+            ColumnHeaderTemplate="{StaticResource KanbanHeader}"
+            SizeChanged="Kanban_SizeChanged"
+            CardDragStart="Kanban_CardDragStart"
+            CardDragEnd="Kanban_CardDragEnd" />
+
+    </Grid>
+</UserControl-->

+ 126 - 0
prs.desktop/Panels/Tasks/TasksByStatusControlOld.xaml.cs

@@ -0,0 +1,126 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Globalization;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using Comal.Classes;
+using InABox.Clients;
+using InABox.Core;
+using InABox.DynamicGrid;
+using InABox.Wpf;
+using InABox.WPF;
+using org.omg.CORBA;
+using Syncfusion.UI.Xaml.Kanban;
+using Color = System.Drawing.Color;
+/*
+namespace PRSDesktop
+{
+    public class StatusTasksHeaderTimeConverter : IValueConverter
+    {
+        public static IEnumerable<TaskModel> Tasks { get; set; }
+
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (Tasks == null)
+                return "0:00";
+
+            var dataContext = value as ColumnTag;
+            if (dataContext == null)
+                return "0:00";
+
+            var getter = dataContext.GetType().GetProperty("Column", BindingFlags.NonPublic | BindingFlags.Instance);
+            if (getter == null)
+                return "0:00";
+
+            var column = (KanbanColumn)getter.GetValue(dataContext);
+            if (column == null)
+                return "0:00";
+
+            double result = 0.0F;
+            foreach (var kanban in Tasks.Where(x => Equals(x.Category, column.Categories)))
+                result += kanban.EstimatedTime.TotalHours;
+            return string.Format("{0:F2}", result);
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+
+    /// <summary>
+    ///     Interaction logic for KanbanPanel.xaml
+    /// </summary>
+    public partial class TasksByStatusControlOld : UserControl, ITaskControl
+    {
+
+        private enum Suppress
+        {
+            This
+        }
+
+        private BitmapImage _attachimg = PRSDesktop.Resources.attachment.AsBitmapImage();
+
+        private readonly ObservableCollection<EmployeeModel> _employeelist = new();
+
+        private CoreTable _employees;
+
+        public CoreTable _kanbans;
+
+        private BitmapImage _lockimg = PRSDesktop.Resources.lock_sml.AsBitmapImage();
+        private ObservableCollection<TaskModel> _models = new();
+        private CoreTable _types;
+
+        public List<string> CheckedKanbans = new();
+
+        // CoreUtils.FullGuid => All Staff
+        // Guid.Empty => Unallocated
+        // Anything Else => Actual Staff Member
+        private Guid EmployeeID = Guid.Empty;
+
+        private Guid? MyID;
+        private string MyName = "";
+        private string searchtext = "";
+        private Guid selectedtype = CoreUtils.FullGuid;
+
+        public TasksByStatusControlOld()
+        {
+            using (new EventSuppressor(Suppress.This))
+                InitializeComponent();
+        }
+
+        /*private void ResizeColumns()
+        {
+            //if (!bResizeRequired)
+            //    return;
+
+            using (var d = Dispatcher.DisableProcessing())
+            {
+                var CollapsedWidth = 50;
+                var CollapsedColumns = 0;
+                Array.ForEach(Kanban.Columns.ToArray(), x => { CollapsedColumns += x.IsExpanded ? 0 : 1; });
+                if (Kanban.Columns.Count > 0 && CollapsedColumns != Kanban.Columns.Count)
+                {
+                    var ColumnWidth = (Kanban.ActualWidth - CollapsedColumns * CollapsedWidth) / (Kanban.Columns.Count - CollapsedColumns) - 2;
+                    if (ColumnWidth != Kanban.ColumnWidth) Kanban.ColumnWidth = ColumnWidth;
+                    //bResizeRequired = false;
+                }
+            }
+        }
+
+        private void Kanban_SizeChanged(object sender, SizeChangedEventArgs e)
+        {
+            Kanban.ColumnWidth = Kanban.ActualWidth / Kanban.Columns.Count - 1.0F;
+        }
+    }
+}*/

+ 427 - 0
prs.desktop/Panels/Tasks/TasksByUserControl - Copy.xaml

@@ -0,0 +1,427 @@
+<UserControl x:Class="PRSDesktop.TasksByUserControlCopy"
+             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"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:dynamicgrid="clr-namespace:InABox.DynamicGrid;assembly=InABox.Wpf"
+             xmlns:wpf="clr-namespace:InABox.Wpf;assembly=InABox.Wpf"
+             xmlns:syncfusion="http://schemas.syncfusion.com/wpf"
+             xmlns:kanban="clr-namespace:Syncfusion.UI.Xaml.Kanban;assembly=Syncfusion.SfKanban.WPF"
+             xmlns:local="clr-namespace:PRSDesktop"
+             mc:Ignorable="d"
+             d:DesignHeight="450" d:DesignWidth="800">
+    <UserControl.Resources>
+        <local:UserTasksHeaderImageConverter x:Key="UserTasksHeaderImageConverter" />
+        <local:UserTasksHeaderTimeConverter x:Key="UserTasksHeaderTimeConverter" />
+        <wpf:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
+        <!--<local:TaskHeaderWidthConverter x:Key="TaskHeaderWidthConverter"/>-->
+
+        <Style x:Key="employeeStyle" TargetType="{x:Type TextBlock}">
+            <Style.Triggers>
+                <DataTrigger Binding="{Binding AssignedTo}" Value="">
+                    <Setter Property="Visibility" Value="Collapsed" />
+                </DataTrigger>
+            </Style.Triggers>
+        </Style>
+        
+        <Style x:Key="managerStyle" TargetType="{x:Type TextBlock}">
+            <Style.Triggers>
+                <DataTrigger Binding="{Binding Manager}" Value="">
+                    <Setter Property="Visibility" Value="Collapsed"/>
+                </DataTrigger>
+            </Style.Triggers>
+        </Style>
+
+        <Style x:Key="jobStyle" TargetType="{x:Type TextBlock}">
+            <Style.Triggers>
+                <DataTrigger Binding="{Binding JobNumber}" Value="">
+                    <Setter Property="Visibility" Value="Collapsed" />
+                </DataTrigger>
+            </Style.Triggers>
+        </Style>
+
+        <Style x:Key="descriptionStyle" TargetType="{x:Type TextBlock}">
+            <Style.Triggers>
+                <DataTrigger Binding="{Binding Path=Description}" Value="">
+                    <Setter Property="Visibility" Value="Collapsed" />
+                </DataTrigger>
+            </Style.Triggers>
+        </Style>
+
+        <Style x:Key="notesStyle" TargetType="{x:Type TextBlock}">
+            <Style.Triggers>
+                <DataTrigger Binding="{Binding Path=Notes}" Value="">
+                    <Setter Property="Visibility" Value="Collapsed" />
+                </DataTrigger>
+            </Style.Triggers>
+        </Style>
+
+        <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">-->
+            <Grid>
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="Auto" />
+                    <ColumnDefinition Width="*" />
+                </Grid.ColumnDefinitions>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="*" />
+                    <RowDefinition Height="*" />
+                </Grid.RowDefinitions>
+                <Border Grid.Column="0" Grid.Row="0" Grid.RowSpan="2" Width="40" Height="40" CornerRadius="20"
+                        Margin="5" BorderBrush="Gray" BorderThickness="0.75" VerticalAlignment="Center"
+                        HorizontalAlignment="Center">
+                    <Border.Background>
+                        <ImageBrush ImageSource="{Binding Converter={StaticResource UserTasksHeaderImageConverter}}"
+                                    Stretch="UniformToFill" />
+                    </Border.Background>
+                </Border>
+                <TextBlock Grid.Column="1" Grid.Row="0" Text="{Binding  Header}" FontSize="16" FontWeight="DemiBold"
+                           HorizontalAlignment="Left" Margin="10,0,10,0" />
+                <TextBlock Grid.Column="1" Grid.Row="1" FontSize="12" HorizontalAlignment="Left" Margin="10,0,5,0">
+                    <Run Text="{Binding CardCount}" />
+                    <Run Text="Tasks /" />
+                    <Run Text="{Binding Converter={StaticResource UserTasksHeaderTimeConverter}, Mode=OneWay}" />
+                    <Run Text="Hours" />
+                </TextBlock>
+            </Grid>
+            <!--</Border>-->
+        </DataTemplate>
+
+        <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}">
+                <Grid Margin="4">
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition x:Name="colCheckbox" Width="Auto" />
+                        <ColumnDefinition x:Name="colDescription" Width="*" />
+                        <ColumnDefinition x:Name="colType" Width="Auto" />
+                        <ColumnDefinition x:Name="colAttach" Width="Auto" />
+                        <ColumnDefinition x:Name="colDueDate" Width="Auto" />
+                    </Grid.ColumnDefinitions>
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto" />
+                        <RowDefinition Height="*" />
+                        <RowDefinition Height="Auto" />
+                    </Grid.RowDefinitions>
+
+                    <CheckBox Grid.Row="0" Grid.Column="0" Margin="0,0,4,0" VerticalAlignment="Center"
+                              IsChecked="{Binding Path=Checked}" Checked="CheckBox_Checked"
+                              Unchecked="CheckBox_Checked" Tag="{Binding}" />
+
+                    <TextBlock Grid.Row="0" Grid.Column="1" Margin="0,0,0,0" FontWeight="DemiBold" FontSize="12"
+                               VerticalAlignment="Center" TextTrimming="CharacterEllipsis">
+                        <Run Text="{Binding Path=Number}" /><Run Text=": " /><Run Text="{Binding Path=Title}" />
+                    </TextBlock>
+
+                    <TextBlock Grid.Row="1" Grid.Column="4" FontSize="12" Margin="4,0,0,0" Text="{Binding Path=Type.Code}"
+                               FontWeight="DemiBold" VerticalAlignment="Center" HorizontalAlignment="Right" />
+
+                    <Image Grid.Row="0" Grid.Column="3" Margin="4,0,4,0"
+                           Source="pack://application:,,,Resources/attachment.png"
+                           Visibility="{Binding Path=Attachments, Converter={StaticResource BoolToVisibilityConverter}}"
+                           Width="16" Height="16" VerticalAlignment="Center" />
+
+                    <TextBlock Grid.Row="0" Grid.Column="4" Text="{Binding DueDate, StringFormat='{}{0:dd MMM yy}'}" Margin="5,0,0,0"
+                               FontWeight="Bold" FontSize="12" VerticalAlignment="Center" HorizontalAlignment="Center" />
+
+                    <TextBlock Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="4" Margin="0,0,0,0" FontSize="12"
+                               HorizontalAlignment="Left" Text="{Binding Path=Description}" VerticalAlignment="Stretch"
+                               TextWrapping="Wrap" />
+
+                    <DockPanel Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="5">
+                        <TextBlock DockPanel.Dock="Right" x:Name="Job" Margin="0" FontWeight="DemiBold" FontSize="12"
+                                   VerticalAlignment="Center" TextAlignment="Right" Style="{StaticResource jobStyle}">
+                            <Run Text="{Binding JobNumber}" /><Run Text=": " /><Run Text="{Binding JobName}" />
+                        </TextBlock>
+                        <TextBlock DockPanel.Dock="Left" x:Name="Staff" FontWeight="DemiBold" FontSize="12"
+                                   VerticalAlignment="Center" Text="{Binding AssignedTo}"
+                                   Style="{StaticResource employeeStyle}" />
+                    </DockPanel>
+
+                </Grid>
+
+                <Border.ToolTip>
+                    <Grid>
+                        <Grid.RowDefinitions>
+                            <RowDefinition Height="Auto"/>
+                            <RowDefinition Height="Auto"/>
+                            <RowDefinition Height="Auto"/>
+                        </Grid.RowDefinitions>
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="Auto"/>
+                        </Grid.ColumnDefinitions>
+                        <TextBlock Grid.Row="0" Grid.Column="0">
+                            <Run FontWeight="DemiBold" Text="{Binding Path=Title}"/>
+                        </TextBlock>
+                        <TextBlock Grid.Row="1" Grid.Column="0" Style="{StaticResource descriptionStyle}">
+                            <Run Text="{Binding Path=Description}"/>
+                        </TextBlock>
+                        <TextBlock Grid.Row="2" Grid.Column="0" Style="{StaticResource notesStyle}">
+                            <LineBreak/>
+                            <Run Text="{Binding Path=Notes}"/>
+                        </TextBlock>
+                    </Grid>
+                </Border.ToolTip>
+
+                <Border.ContextMenu>
+                    <ContextMenu x:Name="TaskMenu" Tag="{Binding}" Opened="TaskMenu_Opened" />
+                </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}">
+            
+                <DockPanel Margin="4">
+                    <TextBlock DockPanel.Dock="Right" Text="{Binding DueDate, StringFormat='{}{0:dd MMM yy}'}" FontWeight="Bold" FontSize="12" VerticalAlignment="Center" HorizontalAlignment="Center" />
+
+                    <CheckBox DockPanel.Dock="Left" Margin="0,0,4,0" VerticalAlignment="Center" IsChecked="{Binding Path=Checked}" Checked="CheckBox_Checked" Unchecked="CheckBox_Checked" Tag="{Binding}" />
+
+                    <TextBlock DockPanel.Dock="Left" x:Name="JobNumber" Margin="0" FontWeight="DemiBold" FontSize="12" VerticalAlignment="Center" Style="{StaticResource jobStyle}">
+                                    <Run Text="{Binding JobNumber}"/><Run Text="/"/>
+                    </TextBlock>
+                    <TextBlock DockPanel.Dock="Left" Margin="0,0,0,0" FontWeight="DemiBold" FontSize="12" VerticalAlignment="Center">
+                            <Run Text="{Binding Path=Number}"/><Run Text=": "/>
+                    </TextBlock>
+
+                    <TextBlock DockPanel.Dock="Left" Margin="0,0,0,0" FontWeight="DemiBold" FontSize="12" VerticalAlignment="Center" Text="{Binding Path=Title}" />
+
+                    <TextBlock DockPanel.Dock="Left" x:Name="Manager" Margin="0"  FontWeight="DemiBold" FontSize="12" VerticalAlignment="Center" Style="{StaticResource managerStyle}">
+                        <Run Text=" ("/><Run Text="{Binding Manager}"/><Run Text=")"/>
+                    </TextBlock>
+                    <TextBlock DockPanel.Dock="Left" x:Name="Employee" Margin="0"  FontWeight="DemiBold" FontSize="12" VerticalAlignment="Center" Style="{StaticResource employeeStyle}">
+                        <Run Text=" ("/><Run Text="{Binding AssignedTo}"/><Run Text=")"/>
+                    </TextBlock>
+
+                </DockPanel>
+
+                <Border.ToolTip>
+                    <Grid>
+                        <Grid.RowDefinitions>
+                            <RowDefinition Height="Auto"/>
+                            <RowDefinition Height="Auto"/>
+                            <RowDefinition Height="Auto"/>
+                        </Grid.RowDefinitions>
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="Auto"/>
+                        </Grid.ColumnDefinitions>
+                        <TextBlock Grid.Row="0" Grid.Column="0">
+                            <Run FontWeight="DemiBold" Text="{Binding Path=Title}"/>
+                        </TextBlock>
+                        <TextBlock Grid.Row="1" Grid.Column="0" Style="{StaticResource descriptionStyle}">
+                            <Run Text="{Binding Path=Description}"/>
+                        </TextBlock>
+                        <TextBlock Grid.Row="2" Grid.Column="0" Style="{StaticResource notesStyle}">
+                            <LineBreak/>
+                            <Run Text="{Binding Path=Notes}"/>
+                        </TextBlock>
+                    </Grid>
+                </Border.ToolTip>
+
+                <Border.ContextMenu>
+                    <ContextMenu x:Name="TaskMenu" Tag="{Binding}" Opened="TaskMenu_Opened" />
+                </Border.ContextMenu>
+            
+            </Border>
+        </DataTemplate>
+        
+        <DataTemplate x:Key="SwimLaneHeaderTemplate">
+            <Grid>
+                <Border BorderBrush="Gray" BorderThickness="0.75" Height="0.75" Margin="10,0,15,0"
+                        HorizontalAlignment="Stretch" />
+                <Border BorderBrush="Gray" BorderThickness="0.75" Margin="0,5,0,0" HorizontalAlignment="Center"
+                        Width="300" Background="WhiteSmoke">
+                    <StackPanel Background="Transparent" x:Name="SwimlaneHeaderPanel" Orientation="Horizontal">
+                        <Grid x:Name="CollapsedIcon" Background="Transparent" Height="30" Width="30">
+                            <Path x:Name="ExpandedPath" IsHitTestVisible="False"
+                                  Data="M30.587915,0L31.995998,1.4199842 15.949964,17.351 0,1.4979873 1.4099131,0.078979151 15.949964,14.53102z"
+                                  Stretch="Uniform" Fill="#FF000000" Width="14" Height="14" Margin="0,0,0,0"
+                                  RenderTransformOrigin="0.5,0.5">
+                                <Path.RenderTransform>
+                                    <TransformGroup>
+                                        <TransformGroup.Children>
+                                            <RotateTransform Angle="0" />
+                                            <ScaleTransform ScaleX="1" ScaleY="1" />
+                                        </TransformGroup.Children>
+                                    </TransformGroup>
+                                </Path.RenderTransform>
+                            </Path>
+                            <Path x:Name="CollapsedPath" Visibility="Collapsed" IsHitTestVisible="False"
+                                  Data="M1.4200482,0L17.351001,16.046996 1.4980513,31.996001 0.078979631,30.585997 14.531046,16.046019 0,1.4089964z"
+                                  Stretch="Uniform" Fill="#FF000000" Width="14" Height="14" Margin="0,0,0,0"
+                                  RenderTransformOrigin="0.5,0.5">
+                                <Path.RenderTransform>
+                                    <TransformGroup>
+                                        <TransformGroup.Children>
+                                            <RotateTransform Angle="0" />
+                                            <ScaleTransform ScaleX="1" ScaleY="1" />
+                                        </TransformGroup.Children>
+                                    </TransformGroup>
+                                </Path.RenderTransform>
+                            </Path>
+                        </Grid>
+                        <TextBlock FontWeight="Medium" IsHitTestVisible="False" FontSize="15" FontStretch="Expanded"
+                                   TextWrapping="NoWrap" VerticalAlignment="Center" Text="{Binding Title}"
+                                   HorizontalAlignment="Center" TextAlignment="Center" />
+                    </StackPanel>
+                </Border>
+            </Grid>
+        </DataTemplate>
+
+
+    </UserControl.Resources>
+
+    <dynamicgrid:DynamicSplitPanel x:Name="SplitPanel" View="Combined" AnchorWidth="100" AllowableViews="Combined"
+                                   OnChanged="SplitPanel_OnChanged">
+
+        <dynamicgrid:DynamicSplitPanel.Master>
+
+            <Grid x:Name="EmployeeGrid" Margin="0" Grid.Column="1" Grid.Row="0">
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto" />
+                    <RowDefinition x:Name="TeamsRow" Height="200" />
+                    <RowDefinition x:Name="SplitterRow" Height="4" />
+                    <RowDefinition Height="Auto" />
+                    <RowDefinition Height="*" />
+                </Grid.RowDefinitions>
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="*" />
+                </Grid.ColumnDefinitions>
+
+                <Border
+                    Grid.Row="0"
+                    BorderBrush="Gray"
+                    BorderThickness="0.75,0.75,0.75,0"
+                    CornerRadius="5,5,0,0"
+                    Background="WhiteSmoke"
+                    Height="30">
+                    <Label Content="Teams" HorizontalContentAlignment="Center" FontWeight="Bold"
+                           VerticalContentAlignment="Center" />
+                </Border>
+
+                <syncfusion:CheckListBox
+                    Grid.Row="1"
+                    x:Name="SelectedTeams"
+                    DisplayMemberPath="Value"
+                    SelectedValuePath="Key"
+                    IsSelectAllEnabled="False"
+                    IsCheckOnFirstClick="True"
+                    Margin="0"
+                    ItemChecked="SelectedTeams_ItemChecked"
+                    SizeChanged="SelectedTeams_SizeChanged"
+                    VerticalAlignment="Stretch"
+                    BorderBrush="Gray"
+                    BorderThickness="0.75"
+                    Background="White" />
+
+                <syncfusion:SfGridSplitter
+                    Grid.Row="2"
+                    x:Name="TeamSplitter"
+                    ResizeBehavior="PreviousAndNext"
+                    Height="4"
+                    HorizontalAlignment="Stretch"
+                    Background="Transparent"
+                    Template="{StaticResource HorizontalSplitter}"
+                    PreviewStyle="{StaticResource HorizontalSplitterPreview}" />
+
+                <Border
+                    Grid.Row="3"
+                    BorderBrush="Gray"
+                    BorderThickness="0.75,0.75,0.75,0"
+                    CornerRadius="5,5,0,0"
+                    Background="WhiteSmoke"
+                    Height="30">
+                    <Label Content="Employees" HorizontalContentAlignment="Center" FontWeight="Bold"
+                           VerticalContentAlignment="Center" />
+                </Border>
+
+                <syncfusion:CheckListBox
+                    Grid.Row="4"
+                    x:Name="SelectedEmployees"
+                    DisplayMemberPath="Value"
+                    SelectedValuePath="Key"
+                    IsCheckOnFirstClick="True"
+                    SelectionChanged="EmployeesSelectionChanged"
+                    IsSelectAllEnabled="False"
+                    Background="White"
+                    BorderBrush="Gray"
+                    BorderThickness="0.75"
+                    Margin="0" />
+
+            </Grid>
+
+        </dynamicgrid:DynamicSplitPanel.Master>
+
+        <dynamicgrid:DynamicSplitPanel.Detail>
+            <Grid>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto" />
+                    <RowDefinition Height="*" />
+                </Grid.RowDefinitions>
+                <Border
+                    Background="WhiteSmoke"
+                    BorderThickness="0.75,0.75,0.75,0"
+                    BorderBrush="Gray"
+                    CornerRadius="5,5,0,0"
+                    Grid.Row="0"
+                    Height="30"
+                    Padding="2">
+                    <DockPanel>
+                        <Label DockPanel.Dock="Left" Content="Search" Margin="5,0,0,0" />
+                        <Button DockPanel.Dock="Right" x:Name="Export" Padding="10,0" Margin="5,0,0,0" Content="Export"
+                                Click="Export_Click" />
+                        <CheckBox DockPanel.Dock="Right" x:Name="IncludeCompleted" Margin="10,0,5,0"
+                                  Content="Include Completed" VerticalAlignment="Center" HorizontalAlignment="Right"
+                                  Checked="IncludeCompleted_Checked" Unchecked="IncludeCompleted_Checked" />
+                        <CheckBox DockPanel.Dock="Right" x:Name="IncludeObserved" Margin="10,0,5,0"
+                                  Content="Include Observing" VerticalAlignment="Center" HorizontalAlignment="Right"
+                                  Checked="IncludeObserved_Checked" Unchecked="IncludeObserved_Checked" />
+                        <CheckBox DockPanel.Dock="Right" x:Name="IncludeManaged" Margin="10,0,5,0"
+                                  Content="Include Managed" VerticalAlignment="Center" HorizontalAlignment="Right"
+                                  Checked="IncludeManaged_Checked" Unchecked="IncludeManaged_Checked" />
+                        <ComboBox DockPanel.Dock="Right" x:Name="ViewType" Margin="5,0,0,0" Width="100"
+                                  SelectedIndex="0" SelectionChanged="ViewType_SelectionChanged"
+                                  VerticalContentAlignment="Center">
+                            <ComboBoxItem Content="Full" />
+                            <ComboBoxItem Content="Compact" />
+                        </ComboBox>
+                        <Label DockPanel.Dock="Right" x:Name="ViewTypeLabel" Content="View" Margin="5,0,0,0" />
+                        <ComboBox DockPanel.Dock="Right" x:Name="TaskType" Margin="5,0,0,0" Width="150"
+                                  SelectedIndex="0" SelectionChanged="TaskType_SelectionChanged"
+                                  VerticalContentAlignment="Center">
+                        </ComboBox>
+                        <Label DockPanel.Dock="Right" x:Name="TaskTypeLabel" Content="Type" Margin="5,0,0,0" />
+                        <TextBox DockPanel.Dock="Left" x:Name="Search" Margin="5,0,0,0" HorizontalAlignment="Stretch"
+                                 VerticalContentAlignment="Center" KeyUp="Search_KeyUp" />
+
+                    </DockPanel>
+                </Border>
+
+                <kanban:SfKanban
+                    SizeChanged="Kanban_SizeChanged"
+                    Grid.Row="1"
+                    Margin="0"
+                    x:Name="Kanban"
+                    HorizontalAlignment="Stretch"
+                    VerticalAlignment="Stretch"
+                    HorizontalContentAlignment="Stretch"
+                    VerticalContentAlignment="Stretch"
+                    AutoGenerateColumns="False"
+                    BorderThickness="0.75"
+                    BorderBrush="Gray"
+                    Background="White"
+                    CardTemplate="{StaticResource FullKanban}"
+                    ColumnHeaderTemplate="{StaticResource KanbanHeader}"
+                    SwimlaneKey="Assignee"
+                    SwimlaneHeaderTemplate="{StaticResource SwimLaneHeaderTemplate}"
+                    CardDragStart="Kanban_CardDragStart"
+                    CardDragEnd="Kanban_CardDragEnd"/>
+            </Grid>
+
+
+        </dynamicgrid:DynamicSplitPanel.Detail>
+
+    </dynamicgrid:DynamicSplitPanel>
+
+</UserControl>

+ 243 - 0
prs.desktop/Panels/Tasks/TasksByUserControl - Copy.xaml.cs

@@ -0,0 +1,243 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Globalization;
+using System.Linq;
+using System.Reflection;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Input;
+using Comal.Classes;
+using InABox.Clients;
+using InABox.Core;
+using InABox.DynamicGrid;
+using InABox.WPF;
+using Syncfusion.UI.Xaml.Kanban;
+using Syncfusion.Windows.Tools.Controls;
+
+namespace PRSDesktop
+{
+    public class UserTasksHeaderImageConverter : IValueConverter
+    {
+        public static Dictionary<Guid, byte[]> Images { get; set; }
+        public static Dictionary<Guid, Guid> Employees { get; set; }
+
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            var anonymous = Resources.anonymous.AsBitmapImage();
+
+            if (Images == null)
+                return anonymous;
+
+            if (Employees == null)
+                return anonymous;
+
+            var dataContext = value as ColumnTag;
+            if (dataContext == null)
+                return anonymous;
+
+            var getter = dataContext.GetType().GetProperty("Column", BindingFlags.NonPublic | BindingFlags.Instance);
+            if (getter == null)
+                return anonymous;
+
+            var column = (KanbanColumn)getter.GetValue(dataContext);
+            if (column == null)
+                return anonymous;
+
+            if (!Guid.TryParse(column.Categories, out var empid))
+                return anonymous;
+
+            if (!Employees.TryGetValue(empid, out var imageid))
+                return anonymous;
+
+            if (!Images.TryGetValue(imageid, out var data))
+                return anonymous;
+
+            return ImageUtils.LoadImage(data);
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+
+    public class UserTasksHeaderTimeConverter : IValueConverter
+    {
+        public static IEnumerable<TaskModel> Kanbans { get; set; }
+
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (Kanbans == null)
+                return "0:00";
+
+            var dataContext = value as ColumnTag;
+            if (dataContext == null)
+                return "0:00";
+
+            var getter = dataContext.GetType().GetProperty("Column", BindingFlags.NonPublic | BindingFlags.Instance);
+            if (getter == null)
+                return "0:00";
+
+            var column = (KanbanColumn)getter.GetValue(dataContext);
+            if (column == null)
+                return "0:00";
+
+            double result = 0.0F;
+            foreach (var kanban in Kanbans.Where(x => Equals(x.Category, column.Categories)))
+                result += kanban.EstimatedTime.TotalHours;
+            return string.Format("{0:F2}", result);
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+
+//public class TaskHeaderWidthConverter : IValueConverter
+//{
+
+//    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+//    {
+//        var dataContext = (value as ColumnTag);
+//        if (dataContext == null)
+//            return 150;
+
+//        PropertyInfo getter = dataContext.GetType().GetProperty("Column", BindingFlags.NonPublic | BindingFlags.Instance);
+//        if (getter == null)
+//            return 150;
+
+//        KanbanColumn column = (KanbanColumn)getter.GetValue(dataContext);
+//        if (column == null)
+//            return 150;
+
+//        return Math.Max(150, column.Width) - 20 ;
+
+//    }
+
+//    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+//    {
+//        throw new NotImplementedException();
+//    }
+//}
+
+    /// <summary>
+    ///     Interaction logic for TaskListPanel.xaml
+    /// </summary>
+    public partial class TasksByUserControl : UserControl, ITaskControl
+    {
+        private List<TaskModel> _models = new();
+
+        public TasksByUserControl()
+        {
+            InitializeComponent();
+        }
+        
+        public bool IsReady { get; set; }
+
+        private void TaskType_SelectionChanged(object sender, SelectionChangedEventArgs e)
+        {
+        }
+
+        private void IncludeLocked_Checked(object sender, RoutedEventArgs e)
+        {
+            if (!IsReady)
+                return;
+
+            SaveSettings();
+            Refresh(true);
+        }
+
+        private void IncludeManaged_Checked(object sender, RoutedEventArgs e)
+        {
+            if (!IsReady)
+                return;
+
+            SaveSettings();
+            Refresh(true);
+        }
+        private void IncludeObserved_Checked(object sender, RoutedEventArgs e)
+        {
+            if (!IsReady)
+                return;
+
+            SaveSettings();
+            Refresh(true);
+        }
+
+        private void IncludeCompleted_Checked(object sender, RoutedEventArgs e)
+        {
+            if (!IsReady)
+                return;
+
+            SaveSettings();
+            Refresh(true);
+        }
+
+        private void Search_KeyUp(object sender, KeyEventArgs e)
+        {
+            FilterKanbans();
+        }
+
+        private void Export_Click(object sender, RoutedEventArgs e)
+        {
+        }
+
+        private void Kanban_CardDragStart(object sender, KanbanDragStartEventArgs e)
+        {
+            var models = SelectedModels(e.SelectedCard.Content as TaskModel).ToList();
+            if (models.Any(x => x.Locked || x.EmployeeID != Guid.Parse(x.Category.ToString())) || !Host.CanChangeTasks(models))
+                e.IsCancel = true;
+        }
+
+
+        private void Kanban_CardDragEnd(object sender, KanbanDragEndEventArgs e)
+        {
+            using (new WaitCursor())
+            {
+                var target = e.TargetColumn.Categories;
+                var targetCategory = e.TargetKey;
+                var models = SelectedModels(e.SelectedCard.Content as TaskModel).Where(x => !Equals(x.Category, target)).ToList();
+                if (!models.Any())
+                    return;
+                var kanbans = Host.LoadKanbans(models, new Columns<Kanban>(x => x.ID, x => x.EmployeeLink.ID, x => x.Private, x => x.Number));
+                var subscribers = new ClientKanbanSubscriberSet(kanbans.Select(x => x.ID));
+                var targetID = Guid.Parse(target);
+
+                var updated = new List<Kanban>();
+                foreach (var kanban in kanbans)
+                {
+                    if (!kanban.Private)
+                    {
+                        kanban.EmployeeLink.ID = targetID;
+                        subscribers.EnsureAssignee(kanban.ID, kanban.EmployeeLink.ID);
+                        updated.Add(kanban);
+                    }
+                    else
+                    {
+                        MessageBox.Show($"Cannot change assignee for task {kanban.Number} because it is private.");
+                        models.RemoveAll(x => x.ID == kanban.ID.ToString());
+                    }
+                }
+
+                new Client<Kanban>().Save(updated, string.Format("Task Employee Updated to {0}", target), (o, err) => { });
+                subscribers.Save(false);
+                foreach (var model in models)
+                {
+                    model.Checked = false;
+                    model.Category = target;
+                    model.EmployeeID = targetID;
+                }
+
+                FilterKanbans();
+            }
+        }
+
+        private void Kanban_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
+        {
+            e.Handled = true;
+        }
+    }
+}

+ 199 - 298
prs.desktop/Panels/Tasks/TasksByUserControl.xaml

@@ -4,275 +4,35 @@
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
              xmlns:dynamicgrid="clr-namespace:InABox.DynamicGrid;assembly=InABox.Wpf"
+             xmlns:wpf="clr-namespace:InABox.Wpf;assembly=InABox.Wpf"
              xmlns:syncfusion="http://schemas.syncfusion.com/wpf"
              xmlns:kanban="clr-namespace:Syncfusion.UI.Xaml.Kanban;assembly=Syncfusion.SfKanban.WPF"
              xmlns:local="clr-namespace:PRSDesktop"
+             d:DataContext="{d:DesignInstance Type=local:TasksByStatusControl}"
              mc:Ignorable="d"
+             x:Name="Control"
              d:DesignHeight="450" d:DesignWidth="800">
     <UserControl.Resources>
-        <local:UserTasksHeaderImageConverter x:Key="UserTasksHeaderImageConverter" />
-        <local:UserTasksHeaderTimeConverter x:Key="UserTasksHeaderTimeConverter" />
-        <local:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
-        <!--<local:TaskHeaderWidthConverter x:Key="TaskHeaderWidthConverter"/>-->
-
-        <Style x:Key="employeeStyle" TargetType="{x:Type TextBlock}">
-            <Style.Triggers>
-                <DataTrigger Binding="{Binding AssignedTo}" Value="">
-                    <Setter Property="Visibility" Value="Collapsed" />
-                </DataTrigger>
-            </Style.Triggers>
-        </Style>
-        
-        <Style x:Key="managerStyle" TargetType="{x:Type TextBlock}">
-            <Style.Triggers>
-                <DataTrigger Binding="{Binding Manager}" Value="">
-                    <Setter Property="Visibility" Value="Collapsed"/>
-                </DataTrigger>
-            </Style.Triggers>
-        </Style>
-
-        <Style x:Key="jobStyle" TargetType="{x:Type TextBlock}">
-            <Style.Triggers>
-                <DataTrigger Binding="{Binding JobNumber}" Value="">
-                    <Setter Property="Visibility" Value="Collapsed" />
-                </DataTrigger>
-            </Style.Triggers>
-        </Style>
-
-        <Style x:Key="descriptionStyle" TargetType="{x:Type TextBlock}">
-            <Style.Triggers>
-                <DataTrigger Binding="{Binding Path=Description}" Value="">
-                    <Setter Property="Visibility" Value="Collapsed" />
-                </DataTrigger>
-            </Style.Triggers>
-        </Style>
-
-        <Style x:Key="notesStyle" TargetType="{x:Type TextBlock}">
-            <Style.Triggers>
-                <DataTrigger Binding="{Binding Path=Notes}" Value="">
-                    <Setter Property="Visibility" Value="Collapsed" />
-                </DataTrigger>
-            </Style.Triggers>
-        </Style>
-
-        <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">-->
-            <Grid>
-                <Grid.ColumnDefinitions>
-                    <ColumnDefinition Width="Auto" />
-                    <ColumnDefinition Width="*" />
-                </Grid.ColumnDefinitions>
-                <Grid.RowDefinitions>
-                    <RowDefinition Height="*" />
-                    <RowDefinition Height="*" />
-                </Grid.RowDefinitions>
-                <Border Grid.Column="0" Grid.Row="0" Grid.RowSpan="2" Width="40" Height="40" CornerRadius="20"
-                        Margin="5" BorderBrush="Gray" BorderThickness="0.75" VerticalAlignment="Center"
-                        HorizontalAlignment="Center">
-                    <Border.Background>
-                        <ImageBrush ImageSource="{Binding Converter={StaticResource UserTasksHeaderImageConverter}}"
-                                    Stretch="UniformToFill" />
-                    </Border.Background>
-                </Border>
-                <TextBlock Grid.Column="1" Grid.Row="0" Text="{Binding  Header}" FontSize="16" FontWeight="DemiBold"
-                           HorizontalAlignment="Left" Margin="10,0,10,0" />
-                <TextBlock Grid.Column="1" Grid.Row="1" FontSize="12" HorizontalAlignment="Left" Margin="10,0,5,0">
-                    <Run Text="{Binding CardCount}" />
-                    <Run Text="Tasks /" />
-                    <Run Text="{Binding Converter={StaticResource UserTasksHeaderTimeConverter}, Mode=OneWay}" />
-                    <Run Text="Hours" />
-                </TextBlock>
-            </Grid>
-            <!--</Border>-->
-        </DataTemplate>
-
-        <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}">
-                <Grid Margin="4">
-                    <Grid.ColumnDefinitions>
-                        <ColumnDefinition x:Name="colCheckbox" Width="Auto" />
-                        <ColumnDefinition x:Name="colDescription" Width="*" />
-                        <ColumnDefinition x:Name="colType" Width="Auto" />
-                        <ColumnDefinition x:Name="colAttach" Width="Auto" />
-                        <ColumnDefinition x:Name="colDueDate" Width="Auto" />
-                    </Grid.ColumnDefinitions>
-                    <Grid.RowDefinitions>
-                        <RowDefinition Height="Auto" />
-                        <RowDefinition Height="*" />
-                        <RowDefinition Height="Auto" />
-                    </Grid.RowDefinitions>
-
-                    <CheckBox Grid.Row="0" Grid.Column="0" Margin="0,0,4,0" VerticalAlignment="Center"
-                              IsChecked="{Binding Path=Checked}" Checked="CheckBox_Checked"
-                              Unchecked="CheckBox_Checked" Tag="{Binding}" />
-
-                    <TextBlock Grid.Row="0" Grid.Column="1" Margin="0,0,0,0" FontWeight="DemiBold" FontSize="12"
-                               VerticalAlignment="Center" TextTrimming="CharacterEllipsis">
-                        <Run Text="{Binding Path=Number}" /><Run Text=": " /><Run Text="{Binding Path=Title}" />
-                    </TextBlock>
-
-                    <TextBlock Grid.Row="1" Grid.Column="4" FontSize="12" Margin="4,0,0,0" Text="{Binding Path=Type.Code}"
-                               FontWeight="DemiBold" VerticalAlignment="Center" HorizontalAlignment="Right" />
-
-                    <Image Grid.Row="0" Grid.Column="3" Margin="4,0,4,0"
-                           Source="pack://application:,,,Resources/attachment.png"
-                           Visibility="{Binding Path=Attachments, Converter={StaticResource BoolToVisibilityConverter}}"
-                           Width="16" Height="16" VerticalAlignment="Center" />
-
-                    <TextBlock Grid.Row="0" Grid.Column="4" Text="{Binding DueDate, StringFormat='{}{0:dd MMM yy}'}" Margin="5,0,0,0"
-                               FontWeight="Bold" FontSize="12" VerticalAlignment="Center" HorizontalAlignment="Center" />
-
-                    <TextBlock Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="4" Margin="0,0,0,0" FontSize="12"
-                               HorizontalAlignment="Left" Text="{Binding Path=Description}" VerticalAlignment="Stretch"
-                               TextWrapping="Wrap" />
-
-                    <DockPanel Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="5">
-                        <TextBlock DockPanel.Dock="Right" x:Name="Job" Margin="0" FontWeight="DemiBold" FontSize="12"
-                                   VerticalAlignment="Center" TextAlignment="Right" Style="{StaticResource jobStyle}">
-                            <Run Text="{Binding JobNumber}" /><Run Text=": " /><Run Text="{Binding JobName}" />
-                        </TextBlock>
-                        <TextBlock DockPanel.Dock="Left" x:Name="Staff" FontWeight="DemiBold" FontSize="12"
-                                   VerticalAlignment="Center" Text="{Binding AssignedTo}"
-                                   Style="{StaticResource employeeStyle}" />
-                    </DockPanel>
-
-                </Grid>
-
-                <Border.ToolTip>
-                    <Grid>
-                        <Grid.RowDefinitions>
-                            <RowDefinition Height="Auto"/>
-                            <RowDefinition Height="Auto"/>
-                            <RowDefinition Height="Auto"/>
-                        </Grid.RowDefinitions>
-                        <Grid.ColumnDefinitions>
-                            <ColumnDefinition Width="Auto"/>
-                        </Grid.ColumnDefinitions>
-                        <TextBlock Grid.Row="0" Grid.Column="0">
-                            <Run FontWeight="DemiBold" Text="{Binding Path=Title}"/>
-                        </TextBlock>
-                        <TextBlock Grid.Row="1" Grid.Column="0" Style="{StaticResource descriptionStyle}">
-                            <Run Text="{Binding Path=Description}"/>
-                        </TextBlock>
-                        <TextBlock Grid.Row="2" Grid.Column="0" Style="{StaticResource notesStyle}">
-                            <LineBreak/>
-                            <Run Text="{Binding Path=Notes}"/>
-                        </TextBlock>
-                    </Grid>
-                </Border.ToolTip>
-
-                <Border.ContextMenu>
-                    <ContextMenu x:Name="TaskMenu" Tag="{Binding}" Opened="TaskMenu_Opened" />
-                </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}">
-            
-                <DockPanel Margin="4">
-                    <TextBlock DockPanel.Dock="Right" Text="{Binding DueDate, StringFormat='{}{0:dd MMM yy}'}" FontWeight="Bold" FontSize="12" VerticalAlignment="Center" HorizontalAlignment="Center" />
-
-                    <CheckBox DockPanel.Dock="Left" Margin="0,0,4,0" VerticalAlignment="Center" IsChecked="{Binding Path=Checked}" Checked="CheckBox_Checked" Unchecked="CheckBox_Checked" Tag="{Binding}" />
-
-                    <TextBlock DockPanel.Dock="Left" x:Name="JobNumber" Margin="0" FontWeight="DemiBold" FontSize="12" VerticalAlignment="Center" Style="{StaticResource jobStyle}">
-                                    <Run Text="{Binding JobNumber}"/><Run Text="/"/>
-                    </TextBlock>
-                    <TextBlock DockPanel.Dock="Left" Margin="0,0,0,0" FontWeight="DemiBold" FontSize="12" VerticalAlignment="Center">
-                            <Run Text="{Binding Path=Number}"/><Run Text=": "/>
-                    </TextBlock>
-
-                    <TextBlock DockPanel.Dock="Left" Margin="0,0,0,0" FontWeight="DemiBold" FontSize="12" VerticalAlignment="Center" Text="{Binding Path=Title}" />
-
-                    <TextBlock DockPanel.Dock="Left" x:Name="Manager" Margin="0"  FontWeight="DemiBold" FontSize="12" VerticalAlignment="Center" Style="{StaticResource managerStyle}">
-                        <Run Text=" ("/><Run Text="{Binding Manager}"/><Run Text=")"/>
-                    </TextBlock>
-                    <TextBlock DockPanel.Dock="Left" x:Name="Employee" Margin="0"  FontWeight="DemiBold" FontSize="12" VerticalAlignment="Center" Style="{StaticResource employeeStyle}">
-                        <Run Text=" ("/><Run Text="{Binding AssignedTo}"/><Run Text=")"/>
-                    </TextBlock>
-
-                </DockPanel>
-
-                <Border.ToolTip>
-                    <Grid>
-                        <Grid.RowDefinitions>
-                            <RowDefinition Height="Auto"/>
-                            <RowDefinition Height="Auto"/>
-                            <RowDefinition Height="Auto"/>
-                        </Grid.RowDefinitions>
-                        <Grid.ColumnDefinitions>
-                            <ColumnDefinition Width="Auto"/>
-                        </Grid.ColumnDefinitions>
-                        <TextBlock Grid.Row="0" Grid.Column="0">
-                            <Run FontWeight="DemiBold" Text="{Binding Path=Title}"/>
-                        </TextBlock>
-                        <TextBlock Grid.Row="1" Grid.Column="0" Style="{StaticResource descriptionStyle}">
-                            <Run Text="{Binding Path=Description}"/>
-                        </TextBlock>
-                        <TextBlock Grid.Row="2" Grid.Column="0" Style="{StaticResource notesStyle}">
-                            <LineBreak/>
-                            <Run Text="{Binding Path=Notes}"/>
-                        </TextBlock>
-                    </Grid>
-                </Border.ToolTip>
-
-                <Border.ContextMenu>
-                    <ContextMenu x:Name="TaskMenu" Tag="{Binding}" Opened="TaskMenu_Opened" />
-                </Border.ContextMenu>
-            
-            </Border>
-        </DataTemplate>
-        
-        <DataTemplate x:Key="SwimLaneHeaderTemplate">
-            <Grid>
-                <Border BorderBrush="Gray" BorderThickness="0.75" Height="0.75" Margin="10,0,15,0"
-                        HorizontalAlignment="Stretch" />
-                <Border BorderBrush="Gray" BorderThickness="0.75" Margin="0,5,0,0" HorizontalAlignment="Center"
-                        Width="300" Background="WhiteSmoke">
-                    <StackPanel Background="Transparent" x:Name="SwimlaneHeaderPanel" Orientation="Horizontal">
-                        <Grid x:Name="CollapsedIcon" Background="Transparent" Height="30" Width="30">
-                            <Path x:Name="ExpandedPath" IsHitTestVisible="False"
-                                  Data="M30.587915,0L31.995998,1.4199842 15.949964,17.351 0,1.4979873 1.4099131,0.078979151 15.949964,14.53102z"
-                                  Stretch="Uniform" Fill="#FF000000" Width="14" Height="14" Margin="0,0,0,0"
-                                  RenderTransformOrigin="0.5,0.5">
-                                <Path.RenderTransform>
-                                    <TransformGroup>
-                                        <TransformGroup.Children>
-                                            <RotateTransform Angle="0" />
-                                            <ScaleTransform ScaleX="1" ScaleY="1" />
-                                        </TransformGroup.Children>
-                                    </TransformGroup>
-                                </Path.RenderTransform>
-                            </Path>
-                            <Path x:Name="CollapsedPath" Visibility="Collapsed" IsHitTestVisible="False"
-                                  Data="M1.4200482,0L17.351001,16.046996 1.4980513,31.996001 0.078979631,30.585997 14.531046,16.046019 0,1.4089964z"
-                                  Stretch="Uniform" Fill="#FF000000" Width="14" Height="14" Margin="0,0,0,0"
-                                  RenderTransformOrigin="0.5,0.5">
-                                <Path.RenderTransform>
-                                    <TransformGroup>
-                                        <TransformGroup.Children>
-                                            <RotateTransform Angle="0" />
-                                            <ScaleTransform ScaleX="1" ScaleY="1" />
-                                        </TransformGroup.Children>
-                                    </TransformGroup>
-                                </Path.RenderTransform>
-                            </Path>
-                        </Grid>
-                        <TextBlock FontWeight="Medium" IsHitTestVisible="False" FontSize="15" FontStretch="Expanded"
-                                   TextWrapping="NoWrap" VerticalAlignment="Center" Text="{Binding Title}"
-                                   HorizontalAlignment="Center" TextAlignment="Center" />
-                    </StackPanel>
-                </Border>
-            </Grid>
-        </DataTemplate>
-
-
+        <ResourceDictionary>
+            <ResourceDictionary.MergedDictionaries>
+                <ResourceDictionary Source="KanbanResources.xaml"/>
+            </ResourceDictionary.MergedDictionaries>
+        </ResourceDictionary>
     </UserControl.Resources>
-
+    <UserControl.CommandBindings>
+        <CommandBinding Command="{x:Static local:KanbanResources.EditTask}"
+                        Executed="EditTask_Executed"
+                        CanExecute="CommandBinding_CanExecute"/>
+        <CommandBinding Command="{x:Static local:KanbanResources.OpenTaskMenu}"
+                        Executed="OpenTaskMenu_Executed"
+                        CanExecute="CommandBinding_CanExecute"/>
+        <CommandBinding Command="{x:Static local:KanbanResources.SelectTask}"
+                        Executed="SelectTask_Executed"
+                        CanExecute="CommandBinding_CanExecute"/>
+    </UserControl.CommandBindings>
     <dynamicgrid:DynamicSplitPanel x:Name="SplitPanel" View="Combined" AnchorWidth="100" AllowableViews="Combined"
-                                   OnChanged="SplitPanel_OnChanged">
+                                   OnChanged="SplitPanel_OnChanged"
+                                   DataContext="{Binding ElementName=Control}">
 
         <dynamicgrid:DynamicSplitPanel.Master>
 
@@ -335,18 +95,17 @@
                            VerticalContentAlignment="Center" />
                 </Border>
 
-                <syncfusion:CheckListBox
-                    Grid.Row="4"
-                    x:Name="SelectedEmployees"
-                    DisplayMemberPath="Value"
-                    SelectedValuePath="Key"
-                    IsCheckOnFirstClick="True"
-                    SelectionChanged="EmployeesSelectionChanged"
-                    IsSelectAllEnabled="False"
-                    Background="White"
-                    BorderBrush="Gray"
-                    BorderThickness="0.75"
-                    Margin="0" />
+                <syncfusion:CheckListBox Grid.Row="4"
+                                         x:Name="SelectedEmployees"
+                                         DisplayMemberPath="Value.Name"
+                                         SelectedValuePath="Key"
+                                         IsCheckOnFirstClick="True"
+                                         ItemChecked="SelectedEmployees_ItemChecked"
+                                         IsSelectAllEnabled="False"
+                                         Background="White"
+                                         BorderBrush="Gray"
+                                         BorderThickness="0.75"
+                                         Margin="0" />
 
             </Grid>
 
@@ -355,17 +114,17 @@
         <dynamicgrid:DynamicSplitPanel.Detail>
             <Grid>
                 <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto" />
                     <RowDefinition Height="Auto" />
                     <RowDefinition Height="*" />
                 </Grid.RowDefinitions>
                 <Border
                     Background="WhiteSmoke"
-                    BorderThickness="0.75,0.75,0.75,0"
+                    BorderThickness="0.75,0.75,0.75,0.75"
                     BorderBrush="Gray"
                     CornerRadius="5,5,0,0"
                     Grid.Row="0"
-                    Height="30"
-                    Padding="2">
+                    Padding="5">
                     <DockPanel>
                         <Label DockPanel.Dock="Left" Content="Search" Margin="5,0,0,0" />
                         <Button DockPanel.Dock="Right" x:Name="Export" Padding="10,0" Margin="5,0,0,0" Content="Export"
@@ -397,30 +156,172 @@
                     </DockPanel>
                 </Border>
 
-                <kanban:SfKanban
-                    SizeChanged="Kanban_SizeChanged"
-                    Grid.Row="1"
-                    Margin="0"
-                    x:Name="Kanban"
-                    HorizontalAlignment="Stretch"
-                    VerticalAlignment="Stretch"
-                    HorizontalContentAlignment="Stretch"
-                    VerticalContentAlignment="Stretch"
-                    AutoGenerateColumns="False"
-                    BorderThickness="0.75"
-                    BorderBrush="Gray"
-                    Background="White"
-                    CardTemplate="{StaticResource FullKanban}"
-                    ColumnHeaderTemplate="{StaticResource KanbanHeader}"
-                    SwimlaneKey="Assignee"
-                    SwimlaneHeaderTemplate="{StaticResource SwimLaneHeaderTemplate}"
-                    CardDragStart="Kanban_CardDragStart"
-                    CardDragEnd="Kanban_CardDragEnd"/>
+                <ItemsControl Grid.Row="1" ItemsSource="{Binding Model.SectionHeaders}">
+                    <ItemsControl.ItemsPanel>
+                        <ItemsPanelTemplate>
+                            <UniformGrid Rows="1"/>
+                        </ItemsPanelTemplate>
+                    </ItemsControl.ItemsPanel>
+                    <ItemsControl.ItemTemplate>
+                        <DataTemplate DataType="local:TasksByUserEmployeeHeader">
+                            <Border BorderBrush="LightGray" BorderThickness="0,0,0,1">
+                                <Grid>
+                                    <Grid.ColumnDefinitions>
+                                        <ColumnDefinition Width="Auto" />
+                                        <ColumnDefinition Width="*" />
+                                    </Grid.ColumnDefinitions>
+                                    <Grid.RowDefinitions>
+                                        <RowDefinition Height="*" />
+                                        <RowDefinition Height="*" />
+                                    </Grid.RowDefinitions>
+                                    <Border Grid.Column="0" Grid.Row="0" Grid.RowSpan="2" Width="40" Height="40" CornerRadius="20"
+                                            Margin="5" BorderBrush="Gray" BorderThickness="0.75" VerticalAlignment="Center"
+                                            HorizontalAlignment="Center">
+                                        <Border.Background>
+                                            <ImageBrush ImageSource="{Binding Image}"
+                                                        Stretch="UniformToFill"/>
+                                        </Border.Background>
+                                    </Border>
+                                    <TextBlock Grid.Column="1" Grid.Row="0" Text="{Binding Name}" FontSize="16" FontWeight="DemiBold"
+                                               HorizontalAlignment="Left" Margin="10,0,10,0" />
+                                    <TextBlock Grid.Column="1" Grid.Row="1" 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>
+                                </Grid>
+                            </Border>
+                        </DataTemplate>
+                    </ItemsControl.ItemTemplate>
+                </ItemsControl>
+                <ScrollViewer Grid.Row="2">
+                    <ItemsControl ItemsSource="{Binding Path=Model.Categories}">
+                        <ItemsControl.ItemTemplate>
+                            <DataTemplate DataType="local:TasksByUserCategory">
+                                <Grid>
+                                    <Grid.RowDefinitions>
+                                        <RowDefinition Height="Auto"/>
+                                        <RowDefinition>
+                                            <RowDefinition.Style>
+                                                <Style TargetType="RowDefinition">
+                                                    <Setter Property="Height" Value="0"/>
+                                                    <Style.Triggers>
+                                                        <DataTrigger Binding="{Binding Collapsed}" Value="False">
+                                                            <Setter Property="Height" Value="*"/>
+                                                        </DataTrigger>
+                                                    </Style.Triggers>
+                                                </Style>
+                                            </RowDefinition.Style>
+                                        </RowDefinition>
+                                    </Grid.RowDefinitions>
+                                    <Grid.ColumnDefinitions>
+                                        <ColumnDefinition Width="*"/>
+                                        <ColumnDefinition Width="300"/>
+                                        <ColumnDefinition Width="*"/>
+                                    </Grid.ColumnDefinitions>
+                                    <Rectangle Grid.Row="0" Grid.Column="0"
+                                               HorizontalAlignment="Stretch"
+                                               VerticalAlignment="Center"
+                                               Fill="LightGray"
+                                               Height="1"/>
+                                    <Rectangle Grid.Row="0" Grid.Column="2"
+                                               HorizontalAlignment="Stretch"
+                                               VerticalAlignment="Center"
+                                               Fill="LightGray"
+                                               Height="1"/>
+                                    <Button x:Name="FoldButton"
+                                            Grid.Row="0"
+                                            Grid.Column="1"
+                                            Click="FoldButton_Click"
+                                            Tag="{Binding}"
+                                            Margin="5">
+                                        <DockPanel>
+                                            <Image Width="32" Height="32"
+                                                   RenderTransformOrigin="0.5,0.5" Source="pack://application:,,,/Resources/rightarrow.png"
+                                                   DockPanel.Dock="Left">
+                                                <Image.Style>
+                                                    <Style TargetType="Image">
+                                                        <Style.Triggers>
+                                                            <DataTrigger Binding="{Binding Collapsed}" Value="False">
+                                                                <Setter Property="RenderTransform">
+                                                                    <Setter.Value>
+                                                                        <RotateTransform Angle="90"/>
+                                                                    </Setter.Value>
+                                                                </Setter>
+                                                            </DataTrigger>
+                                                        </Style.Triggers>
+                                                    </Style>
+                                                </Image.Style>
+                                            </Image>
+                                            <Label Content="{Binding Category}"
+                                                   VerticalContentAlignment="Center"
+                                                   HorizontalContentAlignment="Left"
+                                                   DockPanel.Dock="Right"/>
+                                        </DockPanel>
+                                    </Button>
+                                    <Border Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3">
+                                        <ItemsControl Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3"
+                                                  ItemsSource="{Binding EmployeeCategories}">
+                                            <ItemsControl.ItemsPanel>
+                                                <ItemsPanelTemplate>
+                                                    <UniformGrid Rows="1"/>
+                                                </ItemsPanelTemplate>
+                                            </ItemsControl.ItemsPanel>
+                                            <ItemsControl.ItemTemplate>
+                                                <DataTemplate DataType="local:TasksByUserEmployeeCategory">
+                                                    <Border BorderBrush="LightGray"
+                                                            BorderThickness="0,0,1,0"
+                                                            Padding="5,0">
+                                                        <Grid Tag="{Binding}"
+                                                              Background="Transparent"
+                                                              AllowDrop="True"
+                                                              DragOver="ItemsControl_DragOver"
+                                                              Drop="ItemsControl_Drop">
+                                                            <Grid.RowDefinitions>
+                                                                <RowDefinition Height="Auto"/>
+                                                                <RowDefinition Height="*"/>
+                                                                <RowDefinition>
+                                                                    <RowDefinition.Style>
+                                                                        <Style TargetType="{x:Type RowDefinition}">
+                                                                            <Setter Property="Height" Value="75"/>
+                                                                            <Style.Triggers>
+                                                                                <DataTrigger Binding="{Binding Mode,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:TasksByUserControl}}}"
+                                                                                             Value="{x:Static local:KanbanViewMode.Compact}">
+                                                                                    <Setter Property="Height" Value="30"/>
+                                                                                </DataTrigger>
+                                                                            </Style.Triggers>
+                                                                        </Style>
+                                                                    </RowDefinition.Style>
+                                                                </RowDefinition>
+                                                            </Grid.RowDefinitions>
+                                                            <ItemsControl Grid.Row="0"
+                                                                          ItemsSource="{Binding Tasks,Mode=OneWay}">
+                                                                <ItemsControl.Style>
+                                                                    <Style TargetType="{x:Type ItemsControl}">
+                                                                        <Setter Property="ItemTemplate" Value="{StaticResource FullKanban}"/>
+                                                                        <Style.Triggers>
+                                                                            <DataTrigger Binding="{Binding Mode,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:TasksByUserControl}}}"
+                                                             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>
+                                    </Border>
+                                </Grid>
+                            </DataTemplate>
+                        </ItemsControl.ItemTemplate>
+                    </ItemsControl>
+                </ScrollViewer>
             </Grid>
-
-
         </dynamicgrid:DynamicSplitPanel.Detail>
-
     </dynamicgrid:DynamicSplitPanel>
 
 </UserControl>

+ 670 - 538
prs.desktop/Panels/Tasks/TasksByUserControl.xaml.cs

@@ -1,13 +1,17 @@
 using System;
 using System.Collections.Generic;
+using System.ComponentModel;
 using System.Drawing;
 using System.Globalization;
 using System.Linq;
 using System.Reflection;
+using System.Runtime.CompilerServices;
 using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Data;
 using System.Windows.Input;
+using System.Windows.Media.Imaging;
+using AvalonDock.Layout;
 using Comal.Classes;
 using InABox.Clients;
 using InABox.Core;
@@ -15,711 +19,839 @@ using InABox.DynamicGrid;
 using InABox.WPF;
 using Syncfusion.UI.Xaml.Kanban;
 using Syncfusion.Windows.Tools.Controls;
+using static com.sun.tools.javac.code.Symbol;
 
-namespace PRSDesktop
-{
-    public class UserTasksHeaderImageConverter : IValueConverter
-    {
-        public static Dictionary<Guid, byte[]> Images { get; set; }
-        public static Dictionary<Guid, Guid> Employees { get; set; }
+namespace PRSDesktop;
 
-        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
-        {
-            var anonymous = Resources.anonymous.AsBitmapImage();
-
-            if (Images == null)
-                return anonymous;
+public class TasksByUserEmployeeHeader : INotifyPropertyChanged
+{
+    public Guid EmployeeID { get; set; }
 
-            if (Employees == null)
-                return anonymous;
+    public string Name { get; set; }
 
-            var dataContext = value as ColumnTag;
-            if (dataContext == null)
-                return anonymous;
+    public BitmapImage Image { get; set; }
 
-            var getter = dataContext.GetType().GetProperty("Column", BindingFlags.NonPublic | BindingFlags.Instance);
-            if (getter == null)
-                return anonymous;
+    public int NumTasks { get => Tasks.Count(); }
 
-            var column = (KanbanColumn)getter.GetValue(dataContext);
-            if (column == null)
-                return anonymous;
+    public double NumHours { get => Tasks.Sum(x => x.EstimatedTime.TotalHours); }
 
-            if (!Guid.TryParse(column.Categories, out var empid))
-                return anonymous;
+    public IEnumerable<TaskModel> Tasks => Model.Categories
+        .SelectMany(x => x.EmployeeCategoryDictionary.GetValueOrDefault(EmployeeID)?.Tasks ?? Enumerable.Empty<TaskModel>());
 
-            if (!Employees.TryGetValue(empid, out var imageid))
-                return anonymous;
+    private TasksByUserModel Model;
 
-            if (!Images.TryGetValue(imageid, out var data))
-                return anonymous;
+    public event PropertyChangedEventHandler? PropertyChanged;
 
-            return ImageUtils.LoadImage(data);
-        }
+    // 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));
 
-        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        if (name.Equals(nameof(Tasks)))
         {
-            throw new NotImplementedException();
+            OnPropertyChanged(nameof(NumTasks));
+            OnPropertyChanged(nameof(NumHours));
         }
     }
 
-    public class UserTasksHeaderTimeConverter : IValueConverter
+    public TasksByUserEmployeeHeader(Guid employeeID, string name, BitmapImage image, TasksByUserModel model)
     {
-        public static IEnumerable<TaskModel> Kanbans { get; set; }
+        EmployeeID = employeeID;
+        Name = name;
+        Image = image;
+        Model = model;
+    }
 
-        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
-        {
-            if (Kanbans == null)
-                return "0:00";
+    public void UpdateTasks()
+    {
+        OnPropertyChanged(nameof(Tasks));
+    }
+}
 
-            var dataContext = value as ColumnTag;
-            if (dataContext == null)
-                return "0:00";
+public class TasksByUserEmployeeCategory
+{
+    public Guid EmployeeID { get; set; }
 
-            var getter = dataContext.GetType().GetProperty("Column", BindingFlags.NonPublic | BindingFlags.Instance);
-            if (getter == null)
-                return "0:00";
+    public string Category { get; set; }
 
-            var column = (KanbanColumn)getter.GetValue(dataContext);
-            if (column == null)
-                return "0:00";
+    public SuspendableObservableCollection<TaskModel> Tasks { get; set; } = new();
 
-            double result = 0.0F;
-            foreach (var kanban in Kanbans.Where(x => Equals(x.Category, column.Categories)))
-                result += kanban.EstimatedTime.TotalHours;
-            return string.Format("{0:F2}", result);
-        }
+    public TasksByUserEmployeeCategory(Guid employeeID, string category)
+    {
+        EmployeeID = employeeID;
+        Category = category;
+    }
+}
 
-        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+public class TasksByUserCategory : INotifyPropertyChanged
+{
+    private bool _collapsed;
+    public bool Collapsed
+    {
+        get => _collapsed;
+        set
         {
-            throw new NotImplementedException();
+            _collapsed = value;
+            OnPropertyChanged();
         }
     }
 
-//public class TaskHeaderWidthConverter : IValueConverter
-//{
+    public string Category { get; set; }
+
+    public IEnumerable<TasksByUserEmployeeCategory> EmployeeCategories => EmployeeCategoryDictionary.Values;
+
+    public Dictionary<Guid, TasksByUserEmployeeCategory> EmployeeCategoryDictionary { get; set; } = new();
+
+    public TasksByUserCategory(string category, bool collapsed)
+    {
+        Category = category;
+        Collapsed = collapsed;
+    }
+
+    public event PropertyChangedEventHandler? PropertyChanged;
 
-//    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
-//    {
-//        var dataContext = (value as ColumnTag);
-//        if (dataContext == null)
-//            return 150;
+    // 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));
+    }
+}
 
-//        PropertyInfo getter = dataContext.GetType().GetProperty("Column", BindingFlags.NonPublic | BindingFlags.Instance);
-//        if (getter == null)
-//            return 150;
+public class TasksByUserModel
+{
+    public SuspendableObservableCollection<TasksByUserEmployeeHeader> SectionHeaders { get; set; } = new();
 
-//        KanbanColumn column = (KanbanColumn)getter.GetValue(dataContext);
-//        if (column == null)
-//            return 150;
+    public SuspendableObservableCollection<TasksByUserCategory> Categories { get; set; } = new();
+}
 
-//        return Math.Max(150, column.Width) - 20 ;
+public partial class TasksByUserControl : UserControl, INotifyPropertyChanged, ITaskControl
+{
+    private static readonly BitmapImage anonymous = PRSDesktop.Resources.anonymous.AsBitmapImage();
 
-//    }
+    public TasksByUserModel Model { get; set; } = new();
 
-//    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
-//    {
-//        throw new NotImplementedException();
-//    }
-//}
+    private ILookup<Guid, Guid> TeamEmployees;
 
-    /// <summary>
-    ///     Interaction logic for TaskListPanel.xaml
-    /// </summary>
-    public partial class TasksByUserControl : UserControl, ITaskControl
-    {
-        private Dictionary<Guid, string> _employees;
+    private Dictionary<Guid, EmployeeModel> Employees;
 
-        private List<TaskModel> _models = new();
-        private ILookup<Guid, Guid> _teamemployees;
-        private Dictionary<Guid, string> _teams;
+    private bool bPopulating;
 
-        private bool bPopulating;
+    private KanbanViewMode _mode;
 
-        public TasksByUserControl()
+    public KanbanViewMode Mode
+    {
+        get => _mode;
+        set
         {
-            InitializeComponent();
+            _mode = value;
+            OnPropertyChanged();
         }
-        
-        public bool IsReady { get; set; }
+    }
 
-        #region Setup
+    public TasksByUserControl()
+    {
+        InitializeComponent();
+    }
 
-        public void Setup()
-        {
-            SetupToolbar();
+    #region INotifyPropertyChanged
 
-            SplitPanel.AnchorWidth = Host.KanbanSettings.UserSettings.AnchorWidth;
+    public event PropertyChangedEventHandler? PropertyChanged;
 
-            TeamsRow.Height = new GridLength(Host.KanbanSettings.UserSettings.TeamsHeight);
+    // 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));
+    }
 
-            LoadEmployees();
+    #endregion
 
-            foreach (var team in Host.KanbanSettings.UserSettings.SelectedTeams)
-                SelectedTeams.SelectedItems.Add(_teams.FirstOrDefault(x => Equals(x.Key, team)));
+    #region Setup
 
-            PopulateEmployees();
-            PopulateKanbanTypes();
-        }
+    private void LoadEmployees()
+    {
+        var empfilter = LookupFactory.DefineFilter<Employee>();
 
-        private void PopulateKanbanTypes()
-        {
-            TaskType.Items.Add("");
-            foreach(var kanbanType in Host.KanbanTypes)
-            {
-                TaskType.Items.Add(kanbanType);
-            }
-        }
+        var results = Client.QueryMultiple(
+            new KeyedQueryDef<Employee>(
+                LookupFactory.DefineFilter<Employee>(),
+                new Columns<Employee>(x => x.ID)
+                    .Add(x => x.Name)
+                    .Add(x => x.Thumbnail.ID),
+                new SortOrder<Employee>(x => x.Name)),
+            new KeyedQueryDef<Team>(
+                LookupFactory.DefineFilter<Team>(),
+                new Columns<Team>(x => x.ID)
+                    .Add(x => x.Name),
+                new SortOrder<Team>(x => x.Name)),
+            new KeyedQueryDef<EmployeeTeam>(
+                LookupFactory.DefineFilter<EmployeeTeam>(),
+                new Columns<EmployeeTeam>(x => x.EmployeeLink.ID)
+                    .Add(x => x.TeamLink.ID)));
 
-        #endregion
+        TeamEmployees = results.Get<EmployeeTeam>().ToLookup<EmployeeTeam, Guid, Guid>(x => x.TeamLink.ID, x => x.EmployeeLink.ID);
+        Employees = results.GetObjects<Employee>().ToDictionary(
+            x => x.ID,
+            x => new EmployeeModel(x.ID, x.Name, x.Thumbnail.ID, null));
 
-        private void SetupToolbar()
+        var teams = results.GetObjects<Team>().ToDictionary(x => x.ID, x => x.Name);
+        SelectedTeams.ItemsSource = teams;
+        foreach (var team in Host.KanbanSettings.UserSettings.SelectedTeams)
         {
-            IncludeCompleted.Visibility = Security.IsAllowed<CanHideTaskCompletedColumn>() ? Visibility.Visible : Visibility.Collapsed;
-            IncludeCompleted.IsChecked = IncludeCompleted.Visibility == Visibility.Visible ? Host.KanbanSettings.UserSettings.IncludeCompleted : true;
-            IncludeObserved.IsChecked = Host.KanbanSettings.UserSettings.IncludeObserved;
-            IncludeManaged.IsChecked = Host.KanbanSettings.UserSettings.IncludeManaged;
-            ViewType.SelectedIndex = Host.KanbanSettings.UserSettings.CompactView ? 1 : 0;
+            SelectedTeams.SelectedItems.Add(teams.FirstOrDefault(x => x.Key == team));
         }
+    }
 
-        public void Refresh(bool resetselection)
+    private void PopulateEmployees()
+    {
+        bPopulating = true;
+        try
         {
-            var _swimlanes = new Dictionary<string, int>
-            {
-                { "Open", 0 },
-                { "In Progress", 1 },
-                { "Waiting", 2 },
-                { "Complete", 3 }
-            };
+            var availableemployees = new List<Guid>();
+            foreach (var team in SelectedTeams.SelectedItems.Select(v => (KeyValuePair<Guid, string>)v))
+                availableemployees.AddRange(TeamEmployees[team.Key].Where(x => !availableemployees.Contains(x)));
 
-            var filter = new Filter<KanbanSubscriber>(c => c.Kanban.Closed).IsEqualTo(DateTime.MinValue)
-                .And(x => x.Kanban.Locked).IsEqualTo(false);
+            SelectedEmployees.ItemsSource = Employees.Where(x => availableemployees.Contains(x.Key));
+            SelectedEmployees.SelectedItems.Clear();
+            foreach (var employee in Host.KanbanSettings.UserSettings.SelectedEmployees.Where(availableemployees.Contains))
+                SelectedEmployees.SelectedItems.Add(Employees.FirstOrDefault(x => Equals(x.Key, employee)));
+        }
+        catch (Exception e)
+        {
+        }
 
-            var privateFilter = new Filter<KanbanSubscriber>(x => x.Kanban.Private).IsEqualTo(false);
-            if (App.EmployeeID != Guid.Empty)
-            {
-                privateFilter = privateFilter.Or(x => x.Employee.ID).IsEqualTo(App.EmployeeID);
-            }
-            filter.And(privateFilter);
+        bPopulating = false;
+    }
 
-            if (Host.Job != null)
-            {
-                if (Host.Job.ID != Guid.Empty)
-                    filter = filter.And(c => c.Kanban.JobLink.ID).IsEqualTo(Host.Job.ID);
-                else
-                    filter = filter.And(c => c.Kanban.JobLink.ID).None();
-            }
+    private void SetupToolbar()
+    {
+        IncludeCompleted.Visibility = Security.IsAllowed<CanHideTaskCompletedColumn>() ? Visibility.Visible : Visibility.Collapsed;
+        IncludeCompleted.IsChecked = IncludeCompleted.Visibility == Visibility.Visible ? Host.KanbanSettings.UserSettings.IncludeCompleted : true;
+        IncludeObserved.IsChecked = Host.KanbanSettings.UserSettings.IncludeObserved;
+        IncludeManaged.IsChecked = Host.KanbanSettings.UserSettings.IncludeManaged;
+        ViewType.SelectedIndex = Host.KanbanSettings.UserSettings.CompactView ? 1 : 0;
+    }
 
-            if (!Host.KanbanSettings.UserSettings.IncludeCompleted)
-                filter = filter.And(new Filter<KanbanSubscriber>(x => x.Kanban.Completed).IsEqualTo(DateTime.MinValue));
+    private void PopulateKanbanTypes()
+    {
+        TaskType.Items.Add("");
+        foreach (var kanbanType in Host.KanbanTypes)
+        {
+            TaskType.Items.Add(kanbanType);
+        }
+    }
 
-            var emps = _employees.Where(x => Host.KanbanSettings.UserSettings.SelectedEmployees.Contains(x.Key));
-            //if (Host.Settings.UserSettings.IncludeObserved)
-            //    filter = filter.And(x => x.Manager).IsEqualTo(false);
-            //else
-            //    filter = filter.And(x => x.Assignee).IsEqualTo(true);
-            filter = filter.And(c => c.Employee.ID).InList(emps.Select(x => x.Key).ToArray());
-            if (!Host.KanbanSettings.UserSettings.IncludeObserved)
-            {
-                if (Host.KanbanSettings.UserSettings.IncludeManaged)
-                    filter = filter.And(new Filter<KanbanSubscriber>(x => x.Manager).IsEqualTo(true).Or(x => x.Assignee).IsEqualTo(true));
-                else
-                    filter = filter.And(x => x.Assignee).IsEqualTo(true);
-            }
+    public void Setup()
+    {
+        SetupToolbar();
 
-            using (new WaitCursor())
-            {
-                var kanbans = new Client<KanbanSubscriber>().Query(
-                    filter,
-                    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.EmployeeLink.Name,
-                        x => x.Kanban.ManagerLink.ID,
-                        x => x.Kanban.ManagerLink.Name,
-                        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,
-                        x => x.Employee.ID,
-                        x => x.Kanban.EstimatedTime
-                    ),
-                    new SortOrder<KanbanSubscriber>(x => x.Kanban.DueDate) { Direction = SortDirection.Ascending }
-                );
-                var models = new List<TaskModel>();
-                foreach (var row in kanbans.Rows)
-                {
-                    var empid = row.Get<KanbanSubscriber, Guid>(e => e.Kanban.EmployeeLink.ID);
-                    var mgrid = row.Get<KanbanSubscriber, Guid>(e => e.Kanban.ManagerLink.ID);
-                    var subid = row.Get<KanbanSubscriber, Guid>(e => e.Employee.ID);
-                    var empValid = Entity.IsEntityLinkValid<KanbanSubscriber, EmployeeLink>(x => x.Kanban.EmployeeLink, row);
-                    var mgrValid = Entity.IsEntityLinkValid<KanbanSubscriber, EmployeeLink>(x => x.Kanban.ManagerLink, row);
-
-                    var completed = row.Get<KanbanSubscriber, DateTime>(e => e.Kanban.Completed);
-                    var locked = row.Get<KanbanSubscriber, bool>(e => e.Kanban.Locked);
-                    var typeID = row.Get<KanbanSubscriber, Guid>(e => e.Kanban.Type.ID);
-                    var typeCode = row.Get<KanbanSubscriber, string>(e => e.Kanban.Type.Code);
-                    var job = row.Get<KanbanSubscriber, string>(x => x.Kanban.JobLink.JobNumber);
-
-                    var model = new TaskModel();
-
-                    model.Title = row.Get<KanbanSubscriber, string>(x => x.Kanban.Title);
-                    model.ID = row.Get<KanbanSubscriber, Guid>(x => x.Kanban.ID).ToString();
-                    model.Description = row.Get<KanbanSubscriber, string>(x => x.Kanban.Summary) ?? "";
-                    model.Category = row.Get<KanbanSubscriber, Guid>(c => c.Employee.ID).ToString();
-                    model.Assignee = row.Get<KanbanSubscriber, string>(c => c.Kanban.Category);
-                    
-                    if (string.IsNullOrWhiteSpace(model.Assignee) || !_swimlanes.ContainsKey(model.Assignee))
-                        model.Assignee = "Open";
-
-                    var kanbancolor = subid == empid
-                        ? TaskModel.KanbanColor(
-                            row.Get<KanbanSubscriber, DateTime>(x => x.Kanban.DueDate),
-                            row.Get<KanbanSubscriber, DateTime>(x => x.Kanban.Completed))
-                        : subid == mgrid
-                            ? Color.Silver
-                            : Color.Plum;
-                    if (row.Get<KanbanSubscriber, bool>(x => x.Kanban.Locked))
-                        kanbancolor = kanbancolor.MixColors(0.5F, Color.White);
-                    model.ColorKey = ImageUtils.ColorToString(kanbancolor);
-
-                    model.Image = null;
-                    model.ImageURL = null;
-                    model.Attachments =
-                        row.Get<KanbanSubscriber, int>(x => x.Kanban.Attachments) > 0; // ? PRSDesktop.Resources.attachment.AsBitmapImage() : null;
-                    model.DueDate = row.Get<KanbanSubscriber, DateTime>(x => x.Kanban.DueDate);
-                    model.CompletedDate = row.Get<KanbanSubscriber, DateTime>(x => x.Kanban.Completed);
-                    model.Locked = row.Get<KanbanSubscriber, bool>(x => x.Kanban.Locked); // ? PRSDesktop.Resources.locked.AsBitmapImage() : null;
-                    model.EstimatedTime = row.Get<KanbanSubscriber, TimeSpan>(x => x.Kanban.EstimatedTime);
-
-                    var notes = new List<List<string>> { new() };
-                    var kanbanNotes = row.Get<KanbanSubscriber, string[]>(x => x.Kanban.Notes);
-                    if(kanbanNotes != null)
-                    {
-                        foreach (var line in kanbanNotes)
-                        {
-                            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 = empid == row.Get<KanbanSubscriber, Guid>(c => c.Employee.ID)
-                        ? ""
-                        : !empValid
-                            ? " to (Unallocated)"
-                            : " to " + row.Get<KanbanSubscriber, string>(x => x.Kanban.EmployeeLink.Name);
-
-                    var sMgr = !mgrValid || mgrid == empid
-                        ? ""
-                        : " by " + row.Get<KanbanSubscriber, string>(x => x.Kanban.ManagerLink.Name);
-
-                    model.AssignedTo = !string.IsNullOrEmpty(sEmp) || !string.IsNullOrWhiteSpace(sMgr)
-                        ? string.Format("Assigned{0}{1}", sEmp, sMgr)
-                        : "";
-
-                    model.JobID = row.Get<KanbanSubscriber, Guid>(x => x.Kanban.JobLink.ID);
-                    model.JobNumber = row.Get<KanbanSubscriber, string>(x => x.Kanban.JobLink.JobNumber)?.Trim() ?? "";
-                    model.JobName = row.Get<KanbanSubscriber, string>(x => x.Kanban.JobLink.Name);
-                    model.Checked = false; //ischecked?.Invoke() == true;
-                    model.Type = new KanbanType
-                    {
-                        ID = typeID,
-                        Code = typeCode
-                    };
-                    model.Number = row.Get<KanbanSubscriber, int>(x => x.Kanban.Number);
+        SplitPanel.AnchorWidth = Host.KanbanSettings.UserSettings.AnchorWidth;
+        TeamsRow.Height = new GridLength(Host.KanbanSettings.UserSettings.TeamsHeight);
 
-                    model.PropertyChanged += Model_PropertyChanged;
+        LoadEmployees();
+        PopulateEmployees();
 
-                    models.Add(model);
-                }
+        Mode = Host.KanbanSettings.UserSettings.CompactView ? KanbanViewMode.Compact : KanbanViewMode.Full;
 
-                UserTasksHeaderTimeConverter.Kanbans = models;
+        PopulateKanbanTypes();
+    }
 
-                Kanban.Columns.Clear();
-                foreach (var employee in emps)
-                    Kanban.Columns.Add(new KanbanColumn
-                    {
-                        Categories = employee.Key.ToString(),
-                        Title = employee.Value,
-                        Width = Math.Max(150, Kanban.ActualWidth / emps.ToArray().Length - 1.0F)
-                    });
-                //var template = Resources["SimpleHeader"] as DataTemplate;
-                //var boundary = template.FindName("Boundary", null);
-
-                if (Kanban.Columns.Count > 0)
-                    Kanban.ColumnWidth = Math.Max(150, (Kanban.ActualWidth - 20F) / Kanban.Columns.Count - 1.0F);
-
-                _models = models.OrderBy(x => _swimlanes[x.Assignee]).ThenBy(x => x.DueDate).ToList();
-                FilterKanbans();
-                
-            }
-        }
+    #endregion
 
-        private void Model_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
-        {
-            if(e.PropertyName == nameof(TaskModel.Assignee))
-            {
-                using (new WaitCursor())
-                {
-                    var model = sender as TaskModel;
-                    var models = new TaskModel[] { model };
-                    var kanban = Host.LoadKanbans(models, new Columns<Kanban>(x => x.ID, x => x.Category)).First();
-                    kanban.Category = model.Assignee;
+    #region Filters 
 
-                    new Client<Kanban>().Save(kanban, string.Format("Task Category Updated to {0}", model.Assignee), (o, err) => { });
-                    model.Checked = false;
+    private void IncludeCompleted_Checked(object sender, RoutedEventArgs e)
+    {
+        if (!IsReady)
+            return;
+        SaveSettings();
+        Refresh();
+    }
 
-                    FilterKanbans();
-                }
-            }
-        }
+    private void IncludeObserved_Checked(object sender, RoutedEventArgs e)
+    {
+        if (!IsReady)
+            return;
+        SaveSettings();
+        Refresh();
+    }
 
-        private void SaveSettings()
-        {
-            Host.KanbanSettings.UserSettings.AnchorWidth = SplitPanel.AnchorWidth;
+    private void IncludeManaged_Checked(object sender, RoutedEventArgs e)
+    {
+        if (!IsReady)
+            return;
+        SaveSettings();
+        Refresh();
+    }
 
-            Host.KanbanSettings.UserSettings.TeamsHeight = SelectedTeams.ActualHeight;
+    private void TaskType_SelectionChanged(object sender, SelectionChangedEventArgs e)
+    {
+        if (!IsReady)
+            return;
+        FilterKanbans();
+    }
 
-            var teams = SelectedTeams.SelectedItems.Select(x => ((KeyValuePair<Guid, string>)x).Key);
-            Host.KanbanSettings.UserSettings.SelectedTeams = teams.ToArray();
+    private void Search_KeyUp(object sender, KeyEventArgs e)
+    {
+        FilterKanbans();
+    }
 
-            var emps = SelectedEmployees.SelectedItems.Select(x => ((KeyValuePair<Guid, string>)x).Key);
-            emps = emps.Where(e => _teamemployees.Any(t => t.Contains(e)));
-            Host.KanbanSettings.UserSettings.SelectedEmployees = emps.ToArray();
+    #endregion
 
-            Host.KanbanSettings.UserSettings.IncludeCompleted = IncludeCompleted.IsChecked == true;
-            Host.KanbanSettings.UserSettings.IncludeObserved = IncludeObserved.IsChecked == true;
-            Host.KanbanSettings.UserSettings.IncludeManaged = IncludeManaged.IsChecked == true;
+    #region Refresh
 
-            Host.SaveSettings();
-        }
+    private Filter<KanbanSubscriber> GetKanbanSubscriberFilter(Filter<KanbanSubscriber>? additional = null)
+    {
+        var filter = new Filter<KanbanSubscriber>(c => c.Kanban.Closed).IsEqualTo(DateTime.MinValue)
+            .And(x => x.Kanban.Locked).IsEqualTo(false);
 
-        private void Kanban_SizeChanged(object sender, SizeChangedEventArgs e)
+        var privateFilter = new Filter<KanbanSubscriber>(x => x.Kanban.Private).IsEqualTo(false);
+        if (App.EmployeeID != Guid.Empty)
         {
-            Kanban.ColumnWidth = Kanban.ActualWidth / Kanban.Columns.Count - 1.0F;
+            privateFilter = privateFilter.Or(x => x.Employee.ID).IsEqualTo(App.EmployeeID);
         }
+        filter.And(privateFilter);
 
-        private void SplitPanel_OnChanged(object sender, DynamicSplitPanelSettings e)
+        if (Host.Job != null)
         {
-            if (!IsReady || Equals(Host.KanbanSettings.UserSettings.AnchorWidth, e.AnchorWidth))
-                return;
-            SaveSettings();
+            if (Host.Job.ID != Guid.Empty)
+                filter = filter.And(c => c.Kanban.JobLink.ID).IsEqualTo(Host.Job.ID);
+            else
+                filter = filter.And(c => c.Kanban.JobLink.ID).None();
         }
 
-        private void LoadEmployees()
-        {
-            var empfilter = LookupFactory.DefineFilter<Employee>();
-
-            UserTasksHeaderImageConverter.Images = new Client<Document>().Query(
-                new Filter<Document>(x => x.ID).InQuery(empfilter, x => x.Thumbnail.ID),
-                new Columns<Document>(x => x.ID).Add(x => x.Data)).ToDictionary<Document, Guid, byte[]>(x => x.ID, x => x.Data);
-
-            var query = new MultiQuery();
-            query.Add(
-                LookupFactory.DefineFilter<Employee>(),
-                new Columns<Employee>(x => x.ID)
-                    .Add(x => x.Name)
-                    .Add(x => x.Thumbnail.ID),
-                new SortOrder<Employee>(x => x.Name)
-            );
-            query.Add(
-                LookupFactory.DefineFilter<Team>(),
-                new Columns<Team>(x => x.ID)
-                    .Add(x => x.Name),
-                new SortOrder<Team>(x => x.Name)
-            );
-            query.Add(
-                LookupFactory.DefineFilter<EmployeeTeam>(),
-                new Columns<EmployeeTeam>(x => x.EmployeeLink.ID)
-                    .Add(x => x.TeamLink.ID)
-            );
-
-            query.Query();
+        if (!Host.KanbanSettings.UserSettings.IncludeCompleted)
+            filter = filter.And(new Filter<KanbanSubscriber>(x => x.Kanban.Completed).IsEqualTo(DateTime.MinValue));
 
-            _teams = query.Get<Team>().ToDictionary<Team, Guid, string>(x => x.ID, x => x.Name);
+        var emps = Employees.Where(x => Host.KanbanSettings.UserSettings.SelectedEmployees.Contains(x.Key));
 
-            _employees = query.Get<Employee>().ToDictionary<Employee, Guid, string>(x => x.ID, x => x.Name, x => x.Name);
-
-            UserTasksHeaderImageConverter.Employees = query.Get<Employee>().ToDictionary<Employee, Guid, Guid>(x => x.ID, x => x.Thumbnail.ID);
+        filter = filter.And(c => c.Employee.ID).InList(emps.Select(x => x.Key).ToArray());
+        if (!Host.KanbanSettings.UserSettings.IncludeObserved)
+        {
+            if (Host.KanbanSettings.UserSettings.IncludeManaged)
+                filter = filter.And(new Filter<KanbanSubscriber>(x => x.Manager).IsEqualTo(true).Or(x => x.Assignee).IsEqualTo(true));
+            else
+                filter = filter.And(x => x.Assignee).IsEqualTo(true);
+        }
+        if (additional is not null)
+        {
+            return additional.And(filter);
+        }
+        else
+        {
+            return filter;
+        }
+    }
 
-            _teamemployees = query.Get<EmployeeTeam>().ToLookup<EmployeeTeam, Guid, Guid>(x => x.TeamLink.ID, x => x.EmployeeLink.ID);
+    private void ReloadColumns()
+    {
+        Model.SectionHeaders.SupressNotification = true;
+        Model.SectionHeaders.Clear();
 
-            SelectedTeams.ItemsSource = _teams;
-        }
+        var emps = Host.KanbanSettings.UserSettings.SelectedEmployees.OrderBy(x => Employees[x].Name).ToArray();
 
-        private void PopulateEmployees()
+        foreach (var employeeID in emps)
         {
-            bPopulating = true;
-            try
+            if (Employees.TryGetValue(employeeID, out var employee))
             {
-                var availableemployees = new List<Guid>();
-                foreach (KeyValuePair<Guid, string> team in SelectedTeams.SelectedItems)
-                    availableemployees.AddRange(_teamemployees[team.Key].Where(x => !availableemployees.Contains(x)));
-
-                SelectedEmployees.ItemsSource = _employees.Where(x => availableemployees.Contains(x.Key));
-                SelectedEmployees.SelectedItems.Clear();
-                foreach (var employee in Host.KanbanSettings.UserSettings.SelectedEmployees.Where(x => availableemployees.Contains(x)))
-                    SelectedEmployees.SelectedItems.Add(_employees.FirstOrDefault(x => Equals(x.Key, employee)));
+                Model.SectionHeaders.Add(new TasksByUserEmployeeHeader(employeeID, employee.Name, employee.Image ?? anonymous, Model));
             }
-            catch (Exception e)
-            {
-            }
-
-            bPopulating = false;
         }
+        Model.SectionHeaders.SupressNotification = false;
+        Model.Categories.SupressNotification = true;
 
+        var oldCategories = Model.Categories.ToDictionary(x => x.Category);
 
-        private bool FilterKanban(TaskModel model, string searches, params Func<TaskModel, string>[] properties)
-        {
-            foreach (var search in searches.Split(' '))
-            foreach (var property in properties)
-                if (!property(model).Contains(search))
-                    return false;
-            return true;
-        }
+        Model.Categories.Clear();
 
-        private void FilterKanbans()
+        foreach (var category in _categoryOrder)
         {
-            IEnumerable<TaskModel> Items = _models;
-            if (TaskType.SelectedItem is KanbanType kanbanType)
+            if (category.Equals(Kanban.COMPLETE) && !Host.KanbanSettings.UserSettings.IncludeCompleted)
             {
-                Items = Items.Where(x => x.Type.ID == kanbanType.ID);
+                continue;
             }
-            if (!string.IsNullOrWhiteSpace(Search.Text))
+            var newCategory = new TasksByUserCategory(category, oldCategories.GetValueOrDefault(category)?.Collapsed ?? false);
+            foreach (var employeeID in emps)
             {
-                var searches = Search.Text.Split();
-                Items = Items.Where(x => x.Search(searches));
+                if (Employees.TryGetValue(employeeID, out var employee))
+                {
+                    var cat = new TasksByUserEmployeeCategory(employeeID, category);
+                    newCategory.EmployeeCategoryDictionary[employeeID] = cat;
+                    var header = Model.SectionHeaders.First(x => x.EmployeeID == employeeID);
+                    cat.Tasks.CollectionChanged += (s, e) =>
+                    {
+                        header.UpdateTasks();
+                    };
+                }
             }
+            Model.Categories.Add(newCategory);
+        }
+        Model.Categories.SupressNotification = false;
+    }
 
-            if(object.Equals(Kanban.ItemsSource, Items))
+    private static readonly string[] _categoryOrder = new[]
             {
-                // Triggers a refresh.
-                Kanban.ItemsSource = null;
-            }
-            Kanban.ItemsSource = Items;
-        }
+                Kanban.OPEN,
+                Kanban.INPROGRESS,
+                Kanban.WAITING,
+                Kanban.COMPLETE
+            };
+
+    private Columns<Kanban> GetKanbanColumns()
+    {
+        return 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.EmployeeLink.Name,
+            x => x.ManagerLink.ID,
+            x => x.ManagerLink.Name,
+            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,
+            x => x.EstimatedTime);
+    }
+
+    private IEnumerable<KanbanSubscriber> LoadSubscribers(Filter<KanbanSubscriber>? filter = null)
+    {
+        filter = GetKanbanSubscriberFilter(filter);
 
-        private void ViewType_SelectionChanged(object sender, SelectionChangedEventArgs e)
+        var results = Client.QueryMultiple(
+            new KeyedQueryDef<KanbanSubscriber>(
+                filter,
+                new Columns<KanbanSubscriber>(x => x.ID, x => x.Employee.ID, x => x.Kanban.ID),
+                new SortOrder<KanbanSubscriber>(x => x.Kanban.DueDate) { Direction = SortDirection.Ascending }),
+            new KeyedQueryDef<Kanban>(
+                new Filter<Kanban>(x => x.ID).InQuery(filter, x => x.Kanban.ID),
+                GetKanbanColumns()));
+
+        var kanbans = results.GetObjects<Kanban>().ToDictionary(x => x.ID);
+        return results.GetObjects<KanbanSubscriber>().Select(x =>
         {
-            if (Kanban != null)
-                Kanban.CardTemplate = ViewType.SelectedIndex > 0
-                    ? Resources["CompactKanban"] as DataTemplate
-                    : Resources["FullKanban"] as DataTemplate;
-            if (IsReady)
+            if (kanbans.TryGetValue(x.Kanban.ID, out var kanban))
             {
-                Host.KanbanSettings.StatusSettings.CompactView = ViewType.SelectedIndex > 0;
-                Host.SaveSettings();
+                x.Kanban.Synchronise(kanban);
             }
-        }
+            return x;
+        });
+    }
 
-        private void TaskType_SelectionChanged(object sender, SelectionChangedEventArgs e)
+    public void Refresh()
+    {
+        using (new WaitCursor())
         {
-            FilterKanbans();
-        }
+            var models = CreateModels(LoadSubscribers()).ToList();
 
-        private void IncludeLocked_Checked(object sender, RoutedEventArgs e)
-        {
-            if (!IsReady)
-                return;
+            ReloadColumns();
 
-            SaveSettings();
-            Refresh(true);
+            AllTasks = models.OrderBy(x => x.DueDate).ToList();
+            FilterKanbans();
         }
+    }
 
-        private void IncludeManaged_Checked(object sender, RoutedEventArgs e)
+    /// <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()
+    {
+        IEnumerable<TaskModel> filtered = AllTasks;
+        if (TaskType.SelectedItem is KanbanType kanbanType)
         {
-            if (!IsReady)
-                return;
-
-            SaveSettings();
-            Refresh(true);
+            filtered = filtered.Where(x => x.Type.ID == kanbanType.ID);
         }
-        private void IncludeObserved_Checked(object sender, RoutedEventArgs e)
+        if (!string.IsNullOrWhiteSpace(Search.Text))
         {
-            if (!IsReady)
-                return;
-
-            SaveSettings();
-            Refresh(true);
+            var searches = Search.Text.Split();
+            filtered = filtered.Where(x => x.Search(searches));
         }
 
-        private void IncludeCompleted_Checked(object sender, RoutedEventArgs e)
+        var categoryMap = Model.Categories.ToDictionary(x => x.Category, x => x.EmployeeCategoryDictionary);
+        foreach(var category in Model.Categories)
         {
-            if (!IsReady)
-                return;
-
-            SaveSettings();
-            Refresh(true);
+            foreach(var empCat in category.EmployeeCategories)
+            {
+                empCat.Tasks.Clear();
+            }
         }
 
-        private void Search_KeyUp(object sender, KeyEventArgs e)
-        {
-            FilterKanbans();
-        }
+        SelectedTasks.Clear();
 
-        private void Export_Click(object sender, RoutedEventArgs e)
+        foreach (var task in filtered)
         {
+            if(categoryMap.TryGetValue(task.Category, out var categoryDict))
+            {
+                if (categoryDict.TryGetValue(task.EmployeeCategory, out var employeeCategory))
+                {
+                    employeeCategory.Tasks.Add(task);
+                    if (task.Checked)
+                    {
+                        SelectedTasks.Add(task);
+                    }
+                }
+            }
         }
+    }
 
-        private void Kanban_CardDragStart(object sender, KanbanDragStartEventArgs e)
+    private IEnumerable<TaskModel> CreateModels(IEnumerable<KanbanSubscriber> subscribers)
+    {
+        foreach(var subscriber in subscribers)
         {
-            var models = SelectedModels(e.SelectedCard.Content as TaskModel).ToList();
-            if (models.Any(x => x.Locked || x.EmployeeID != Guid.Parse(x.Category.ToString())) || !Host.CanChangeTasks(models))
-                e.IsCancel = true;
-        }
-
+            var kanban = subscriber.Kanban;
+            var model = new TaskModel
+            {
+                Title = kanban.Title,
+                ID = kanban.ID,
+                Description = kanban.Summary ?? "",
+                EmployeeCategory = subscriber.Employee.ID,
+                Category = kanban.Category.NotWhiteSpaceOr("Open"),
+                Attachments = kanban.Attachments > 0,
+                DueDate = kanban.DueDate,
+                CompletedDate = kanban.Completed,
+                Locked = kanban.Locked,
+                EstimatedTime = kanban.EstimatedTime,
+                EmployeeID = kanban.EmployeeLink.ID,
+                ManagerID = kanban.ManagerLink.ID,
+                JobID = kanban.JobLink.ID,
+                JobNumber = kanban.JobLink.JobNumber?.Trim() ?? "",
+                JobName = kanban.JobLink.Name,
+                Type = new KanbanType
+                {
+                    ID = kanban.Type.ID,
+                    Code = kanban.Type.Code
+                },
+                Number = kanban.Number,
+                Checked = SelectedTasks.Any(x => x.ID == kanban.ID)
+            };
 
-        private void Kanban_CardDragEnd(object sender, KanbanDragEndEventArgs e)
-        {
-            using (new WaitCursor())
+            var colour = subscriber.Employee.ID == kanban.EmployeeLink.ID
+                ? TaskModel.KanbanColor(
+                    kanban.DueDate,
+                    kanban.Completed)
+                : subscriber.Employee.ID == kanban.ManagerLink.ID
+                    ? Color.Silver
+                    : Color.Plum;
+            if (kanban.Locked)
             {
-                var target = e.TargetColumn.Categories;
-                var targetCategory = e.TargetKey;
-                var models = SelectedModels(e.SelectedCard.Content as TaskModel).Where(x => !Equals(x.Category, target)).ToList();
-                if (!models.Any())
-                    return;
-                var kanbans = Host.LoadKanbans(models, new Columns<Kanban>(x => x.ID, x => x.EmployeeLink.ID, x => x.Private, x => x.Number));
-                var subscribers = new ClientKanbanSubscriberSet(kanbans.Select(x => x.ID));
-                var targetID = Guid.Parse(target);
+                colour = colour.MixColors(0.5F, Color.White);
+            }
+            model.Color = System.Windows.Media.Color.FromArgb(colour.A, colour.R, colour.G, colour.B);
 
-                var updated = new List<Kanban>();
-                foreach (var kanban in kanbans)
+            var notes = new List<List<string>> { new() };
+            var kanbanNotes = kanban.Notes;
+            if (kanbanNotes != null)
+            {
+                foreach (var line in kanbanNotes)
                 {
-                    if (!kanban.Private)
+                    if (line == "===================================")
                     {
-                        kanban.EmployeeLink.ID = targetID;
-                        subscribers.EnsureAssignee(kanban.ID, kanban.EmployeeLink.ID);
-                        updated.Add(kanban);
+                        notes.Add(new());
                     }
                     else
                     {
-                        MessageBox.Show($"Cannot change assignee for task {kanban.Number} because it is private.");
-                        models.RemoveAll(x => x.ID == kanban.ID.ToString());
+                        notes.Last().Add(line);
                     }
                 }
+            }
+            model.Notes = string.Join("\n===================================\n", notes.Reverse<List<string>>().Select(x => string.Join('\n', x)));
 
-                new Client<Kanban>().Save(updated, string.Format("Task Employee Updated to {0}", target), (o, err) => { });
-                subscribers.Save(false);
-                foreach (var model in models)
-                {
-                    model.Checked = false;
-                    model.Category = target;
-                    model.EmployeeID = targetID;
-                }
+            SetTaskModelAssignedTo(model, kanban, subscriber.Employee.ID);
 
-                FilterKanbans();
-            }
+            yield return model;
         }
+    }
 
-        #region ITaskControl Support
+    private static void SetTaskModelAssignedTo(TaskModel model, IKanban kanban, Guid subscriberID)
+    {
+        var employeeString = kanban.EmployeeLink.ID == subscriberID
+            ? ""
+            : kanban.EmployeeLink.ID == Guid.Empty
+                ? " to (Unallocated)"
+                : " to " + kanban.EmployeeLink.Name;
+
+        var managerString = kanban.ManagerLink.ID == Guid.Empty || kanban.ManagerLink.ID == subscriberID
+            ? ""
+            : " by " + kanban.ManagerLink.Name;
+
+        model.AssignedTo = !string.IsNullOrEmpty(employeeString) || !managerString.IsNullOrWhiteSpace()
+            ? $"Assigned{employeeString}{managerString}"
+            : "";
+    }
 
-        public ITaskHost Host { get; set; }
+    #endregion
 
-        public KanbanViewType KanbanViewType => KanbanViewType.User;
+    #region Kanban
 
-        public IEnumerable<TaskModel> SelectedModels(TaskModel? sender = null)
+    private readonly List<TaskModel> SelectedTasks = new();
+    private List<TaskModel> AllTasks { get; set; } = new();
+
+    private void DoEdit(TaskModel task)
+    {
+        var result = Host.EditReferences(new[] { task });
+        if (result)
         {
-            var result = _models.Where(x => x.Checked).ToList();
-            if (sender != null && !result.Contains(sender))
-                result.Add(sender);
-            return result;
+            Refresh();
         }
+    }
 
-        #endregion
+    private void EditTask_Executed(object sender, ExecutedRoutedEventArgs e)
+    {
+        if (e.Parameter is not TaskModel model) return;
 
-        #region Kanban Actions
+        DoEdit(model);
+    }
 
-        private void DoEdit(TaskModel task)
-        {
-            if (task == null)
-                return;
+    private void OpenTaskMenu_Executed(object sender, ExecutedRoutedEventArgs e)
+    {
+        if (e.Parameter is not KanbanResources.OpenTaskMenuCommandArgs args) return;
 
-            var result = Host.EditReferences(new[] { task });
-            if (result)
-                Refresh(true);
-        }
+        Host.PopulateMenu(this, args.Model, args.Menu);
+    }
+
+    private void SelectTask_Executed(object sender, ExecutedRoutedEventArgs e)
+    {
+        if (e.Parameter is not TaskModel model) return;
 
-        private void TaskMenu_Opened(object sender, RoutedEventArgs e)
+        if (!SelectedTasks.Remove(model))
         {
-            Host.PopulateMenu(this, (sender as ContextMenu)!);
+            SelectedTasks.Add(model);
         }
+    }
+
+    private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
+    {
+        e.CanExecute = true;
+    }
 
-        private void Border_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
+
+    private void ItemsControl_DragOver(object sender, DragEventArgs e)
+    {
+        if (sender is not FrameworkElement element || element.Tag is not TasksByUserEmployeeCategory category) return;
+
+        e.Effects = DragDropEffects.None;
+        if (e.Data.GetDataPresent(typeof(TaskModel)))
         {
-            if (e.ClickCount > 1)
+            var model = e.Data.GetData(typeof(TaskModel)) as TaskModel;
+            if (model is not null
+                && (model.Category != category.Category || model.EmployeeCategory != category.EmployeeID)
+                && !SelectedTasks.Any(x => x.Locked)
+                && SelectedTasks.Concat(CoreUtils.One(model)).Any(x => x.IsAssignee))
             {
-                var task = ((Border)sender).Tag as TaskModel;
-                DoEdit(task);
-                e.Handled = true;
+                e.Effects = DragDropEffects.Move;
             }
         }
+    }
 
-        private void CheckBox_Checked(object sender, RoutedEventArgs e)
+    private void ItemsControl_Drop(object sender, DragEventArgs e)
+    {
+        if (sender is not FrameworkElement element || element.Tag is not TasksByUserEmployeeCategory category) return;
+
+        if (e.Data.GetDataPresent(typeof(TaskModel)))
         {
-        }
+            var models = SelectedModels(e.Data.GetData(typeof(TaskModel)) as TaskModel)
+                .Where(x => (!x.Category.Equals(category.Category) || x.EmployeeID != category.EmployeeID) && x.IsAssignee)
+                .ToList();
+            if (!models.Any())
+            {
+                return;
+            }
+            var changingCategory = models.Any(x => !x.Category.Equals(category.Category));
+            var completing = changingCategory && category.Category.Equals(Kanban.COMPLETE);
+            var completed = DateTime.Now;
 
-        #endregion
+            if (completing)
+            {
+                if (MessageBox.Show($"Are you sure you want to complete the selected tasks?", "Confirm Completion",
+                        MessageBoxButton.YesNo) != MessageBoxResult.Yes)
+                    return;
+            }
 
-        #region Employee List Actions
+            var kanbans = Host.LoadKanbans(models, new Columns<Kanban>(x => x.ID, x => x.EmployeeLink.ID, x => x.Private, x => x.Number));
+            var subscribers = new ClientKanbanSubscriberSet(kanbans.Select(x => x.ID));
 
-        private void SelectedTeams_ItemChecked(object sender, ItemCheckedEventArgs e)
-        {
-            if (!IsReady)
-                return;
-            PopulateEmployees();
-            SaveSettings();
-            Refresh(true);
+            foreach(var kanban in kanbans)
+            {
+                if (!kanban.Private)
+                {
+                    kanban.EmployeeLink.ID = category.EmployeeID;
+                    subscribers.EnsureAssignee(kanban.ID, kanban.EmployeeLink.ID);
+
+                    kanban.Category = category.Category;
+                    if (completing)
+                    {
+                        kanban.Completed = completed;
+                    }
+                }
+                else
+                {
+                    MessageBox.Show($"Cannot change assignee for task {kanban.Number} because it is private.");
+                    models.RemoveAll(x => x.ID == kanban.ID);
+                }
+            }
+
+            Client.Save(kanbans.Where(x => x.IsChanged()), $"Task Employee Updated");
+            subscribers.Save(true);
+
+            var kanbanIDs = models.Select(x => x.ID).ToArray();
+            AllTasks.RemoveAll(x => kanbanIDs.Contains(x.ID));
+
+            AllTasks.AddRange(CreateModels(LoadSubscribers(new Filter<KanbanSubscriber>(x => x.Kanban.ID).InList(kanbanIDs))));
+            AllTasks.Sort((x, y) => x.DueDate.CompareTo(y.DueDate));
+
+            FilterKanbans();
         }
+    }
+
+    #endregion
+
+    #region ITaskControl
+
+    public ITaskHost Host { get; set; }
+
+    public KanbanViewType KanbanViewType => KanbanViewType.User;
+
+    public bool IsReady { get; set; }
+
+    public string SectionName => "Tasks By User";
 
-        private void SelectedTeams_SizeChanged(object sender, SizeChangedEventArgs e)
+    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)
         {
-            if (!IsReady || Equals(Host.KanbanSettings.UserSettings.TeamsHeight, SelectedTeams.ActualHeight))
-                return;
-            SaveSettings();
+            return SelectedTasks;
         }
-
-        private void EmployeesSelectionChanged(object sender, SelectionChangedEventArgs e)
+        else
         {
-            if (!IsReady || bPopulating)
-                return;
-            SaveSettings();
-            Refresh(true);
+            var result = SelectedTasks.ToList();
+            if (!result.Contains(sender))
+            {
+                result.Add(sender);
+            }
+            return result;
         }
+    }
 
-        public string SectionName => "Tasks By User";
+    #endregion
 
-        public DataModel DataModel(Selection selection)
-        {
-            return new AutoDataModel<Kanban>(new Filter<Kanban>(x => x.ID).IsEqualTo(Guid.Empty));
-        }
+    #region Settings
+
+    private void SaveSettings()
+    {
+        Host.KanbanSettings.UserSettings.AnchorWidth = SplitPanel.AnchorWidth;
+
+        Host.KanbanSettings.UserSettings.TeamsHeight = SelectedTeams.ActualHeight;
+
+        var teams = SelectedTeams.SelectedItems.Select(x => ((KeyValuePair<Guid, string>)x).Key);
+        Host.KanbanSettings.UserSettings.SelectedTeams = teams.ToArray();
+
+        var emps = SelectedEmployees.SelectedItems.Select(x => ((KeyValuePair<Guid, EmployeeModel>)x).Key);
+        emps = emps.Where(e => TeamEmployees.Any(t => t.Contains(e)));
+        Host.KanbanSettings.UserSettings.SelectedEmployees = emps.ToArray();
 
-        #endregion
+        Host.KanbanSettings.UserSettings.IncludeCompleted = IncludeCompleted.IsChecked == true;
+        Host.KanbanSettings.UserSettings.IncludeObserved = IncludeObserved.IsChecked == true;
+        Host.KanbanSettings.UserSettings.IncludeManaged = IncludeManaged.IsChecked == true;
 
-        private void Kanban_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
+        Host.SaveSettings();
+    }
+
+    private void SplitPanel_OnChanged(object sender, DynamicSplitPanelSettings e)
+    {
+        if (!IsReady || Equals(Host.KanbanSettings.UserSettings.AnchorWidth, e.AnchorWidth))
+            return;
+        SaveSettings();
+    }
+
+    private void SelectedTeams_ItemChecked(object sender, ItemCheckedEventArgs e)
+    {
+        if (!IsReady)
+            return;
+        PopulateEmployees();
+        SaveSettings();
+        Refresh();
+    }
+
+    private void SelectedTeams_SizeChanged(object sender, SizeChangedEventArgs e)
+    {
+        if (!IsReady || Equals(Host.KanbanSettings.UserSettings.TeamsHeight, SelectedTeams.ActualHeight))
+            return;
+        SaveSettings();
+    }
+
+    private void SelectedEmployees_ItemChecked(object sender, ItemCheckedEventArgs e)
+    {
+        if (!IsReady || bPopulating || sender != SelectedEmployees)
+            return;
+        SaveSettings();
+        Refresh();
+    }
+
+    private void ViewType_SelectionChanged(object sender, SelectionChangedEventArgs e)
+    {
+        if (!IsReady)
+            return;
+
+        Mode = ViewType.SelectedIndex switch
+        {
+            0 => KanbanViewMode.Full,
+            1 => KanbanViewMode.Compact,
+            _ => KanbanViewMode.Full
+        };
+
+        if (IsReady)
         {
-            e.Handled = true;
+            Host.KanbanSettings.UserSettings.CompactView = Mode == KanbanViewMode.Compact;
+            Host.SaveSettings();
         }
     }
+
+    #endregion
+
+    private void Export_Click(object sender, RoutedEventArgs e)
+    {
+        var form = new DynamicExportForm(typeof(Kanban), GetKanbanColumns().ColumnNames());
+        if (form.ShowDialog() != true)
+            return;
+        var export = Client.Query(
+            new Filter<Kanban>(x => x.ID).InQuery(GetKanbanSubscriberFilter(), x => x.Kanban.ID),
+            new Columns<Kanban>(form.Fields),
+            LookupFactory.DefineSort<Kanban>()
+        );
+
+        var employee = "Tasks for " + string.Join(',', Employees.Where(x => Host.KanbanSettings.UserSettings.SelectedEmployees.Contains(x.Key)).Select(x => x.Value.Name));
+
+        ExcelExporter.DoExport<Kanban>(
+            export,
+            string.Format(
+                "{0} ({1:dd-MMM-yy})",
+                employee,
+                DateTime.Today
+            )
+        );
+    }
+
+    private void FoldButton_Click(object sender, RoutedEventArgs e)
+    {
+        if (sender is not FrameworkElement element || element.Tag is not TasksByUserCategory category) return;
+
+        category.Collapsed = !category.Collapsed;
+    }
 }