Prechádzať zdrojové kódy

Started on TasksByUser

Kenric Nugteren 1 rok pred
rodič
commit
f338d4624c

+ 2 - 2
prs.desktop/PRSDesktop.csproj

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

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

@@ -49,7 +49,7 @@ namespace PRSDesktop
 
         void Setup();
 
-        void Refresh(bool resetselection);
+        void Refresh();
     }
 
     public class KanbanReferences

+ 1 - 0
prs.desktop/Panels/Tasks/TaskModel.cs

@@ -28,6 +28,7 @@ public class TaskModel
     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; }

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

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

+ 6 - 1
prs.desktop/Panels/Tasks/TasksByStatusControl.xaml.cs

@@ -439,7 +439,6 @@ public partial class TasksByStatusControl : UserControl, ITaskControl, INotifyPr
         }
     }
 
-
     private void EditTask_Executed(object sender, ExecutedRoutedEventArgs e)
     {
         if (e.Parameter is not TaskModel model) return;
@@ -950,6 +949,8 @@ public partial class TasksByStatusControl : UserControl, ITaskControl, INotifyPr
             .OrderBy(x => x.EmployeeID == SelectedEmployee.ID ? 0 : 1)
             .ThenBy(x => x.DueDate);
 
+        SelectedTasks.Clear();
+
         foreach (var column in Columns)
         {
             column.Tasks.Clear();
@@ -958,6 +959,10 @@ public partial class TasksByStatusControl : UserControl, ITaskControl, INotifyPr
         {
             var column = GetColumn(task.Category);
             column?.Tasks.Add(task);
+            if (task.Checked)
+            {
+                SelectedTasks.Add(task);
+            }
         }
         Progress.Close();
     }

+ 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>

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

@@ -0,0 +1,359 @@
+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; }
+		
+
+        public void Refresh(bool resetselection)
+        {
+            var _swimlanes = new Dictionary<string, int>
+            {
+                { "Open", 0 },
+                { "In Progress", 1 },
+                { "Waiting", 2 },
+                { "Complete", 3 }
+            };
+
+            var filter = new Filter<KanbanSubscriber>(c => c.Kanban.Closed).IsEqualTo(DateTime.MinValue)
+                .And(x => x.Kanban.Locked).IsEqualTo(false);
+
+            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);
+
+            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();
+            }
+
+            if (!Host.KanbanSettings.UserSettings.IncludeCompleted)
+                filter = filter.And(new Filter<KanbanSubscriber>(x => x.Kanban.Completed).IsEqualTo(DateTime.MinValue));
+
+            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);
+            }
+
+            using (new WaitCursor())
+            {
+
+                UserTasksHeaderTimeConverter.Kanbans = models;
+
+                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();
+                
+            }
+        }
+
+
+        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;
+        }
+
+        private void FilterKanbans()
+        {
+            IEnumerable<TaskModel> Items = _models;
+            if (TaskType.SelectedItem is KanbanType kanbanType)
+            {
+                Items = Items.Where(x => x.Type.ID == kanbanType.ID);
+            }
+            if (!string.IsNullOrWhiteSpace(Search.Text))
+            {
+                var searches = Search.Text.Split();
+                Items = Items.Where(x => x.Search(searches));
+            }
+
+            if(object.Equals(Kanban.ItemsSource, Items))
+            {
+                // Triggers a refresh.
+                Kanban.ItemsSource = null;
+            }
+            Kanban.ItemsSource = Items;
+        }
+
+        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)
+            {
+                Host.KanbanSettings.StatusSettings.CompactView = ViewType.SelectedIndex > 0;
+                Host.SaveSettings();
+            }
+        }
+
+        private void TaskType_SelectionChanged(object sender, SelectionChangedEventArgs e)
+        {
+            FilterKanbans();
+        }
+
+        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;
+        }
+    }
+}

+ 105 - 282
prs.desktop/Panels/Tasks/TasksByUserControl.xaml

@@ -9,269 +9,26 @@
              xmlns:kanban="clr-namespace:Syncfusion.UI.Xaml.Kanban;assembly=Syncfusion.SfKanban.WPF"
              xmlns:local="clr-namespace:PRSDesktop"
              mc:Ignorable="d"
+             x:Name="Control"
              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>
-
-
+        <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">
 
@@ -398,30 +155,96 @@
                     </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 ElementName=Control,Path=Model.SectionHeaders}">
+                    <ItemsControl.ItemsPanel>
+                        <ItemsPanelTemplate>
+                            <UniformGrid Rows="1"/>
+                        </ItemsPanelTemplate>
+                    </ItemsControl.ItemsPanel>
+                    <ItemsControl.ItemTemplate>
+                        <DataTemplate DataType="local:TasksByUserEmployeeHeader">
+                            <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>
+                        </DataTemplate>
+                    </ItemsControl.ItemTemplate>
+                </ItemsControl>
+                <ScrollViewer>
+                    <ItemsControl ItemsSource="{Binding ElementName=Control,Path=Model.Categories}">
+                        <ItemsControl.ItemTemplate>
+                            <DataTemplate DataType="local:TasksByUserCategory">
+                                <Grid>
+                                    <Grid.RowDefinitions>
+                                        <RowDefinition Height="Auto"/>
+                                        <RowDefinition Height="*"/>
+                                    </Grid.RowDefinitions>
+                                    <Grid.ColumnDefinitions>
+                                        <ColumnDefinition Width="*"/>
+                                        <ColumnDefinition Width="300"/>
+                                        <ColumnDefinition Width="*"/>
+                                    </Grid.ColumnDefinitions>
+                                    <Button Content="{Binding Category}"
+                                            Grid.Row="0"
+                                            Grid.Column="1"/>
+                                    <ItemsControl ItemsSource="{Binding EmployeeCategories}">
+                                        <ItemsControl.ItemsPanel>
+                                            <ItemsPanelTemplate>
+                                                <UniformGrid Rows="1"/>
+                                            </ItemsPanelTemplate>
+                                        </ItemsControl.ItemsPanel>
+                                        <ItemsControl.ItemTemplate>
+                                            <DataTemplate DataType="local:TasksByUserEmployeeCategory">
+                                                <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:TasksByUserControl}}}"
+                                                         Value="{x:Static local:KanbanViewMode.Compact}">
+                                                                    <Setter Property="ItemTemplate" Value="{StaticResource CompactKanban}"/>
+                                                                </DataTrigger>
+                                                            </Style.Triggers>
+                                                        </Style>
+                                                    </ItemsControl.Style>
+                                                </ItemsControl>
+                                            </DataTemplate>
+                                        </ItemsControl.ItemTemplate>
+                                    </ItemsControl>
+                                </Grid>
+                            </DataTemplate>
+                        </ItemsControl.ItemTemplate>
+                    </ItemsControl>
+                </ScrollViewer>
             </Grid>
-
-
         </dynamicgrid:DynamicSplitPanel.Detail>
-
     </dynamicgrid:DynamicSplitPanel>
 
 </UserControl>

+ 484 - 569
prs.desktop/Panels/Tasks/TasksByUserControl.xaml.cs

@@ -1,13 +1,16 @@
 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 Comal.Classes;
 using InABox.Clients;
 using InABox.Core;
@@ -16,711 +19,623 @@ using InABox.WPF;
 using Syncfusion.UI.Xaml.Kanban;
 using Syncfusion.Windows.Tools.Controls;
 
-namespace PRSDesktop
+namespace PRSDesktop;
+
+public class TasksByUserEmployeeHeader
+{
+    public Guid EmployeeID { get; set; }
+
+    public string Name { get; set; }
+
+    public BitmapImage Image { get; set; }
+
+    public int NumTasks { get => Tasks.Count(); }
+
+    public double NumHours { get => Tasks.Sum(x => x.EstimatedTime.TotalHours); }
+
+    public IEnumerable<TaskModel> Tasks => Model.Categories
+        .SelectMany(x => x.EmployeeCategoryDictionary.GetValueOrDefault(EmployeeID)?.Tasks ?? Enumerable.Empty<TaskModel>());
+
+    private TasksByUserModel Model;
+
+    public TasksByUserEmployeeHeader(Guid employeeID, string name, BitmapImage image, TasksByUserModel model)
+    {
+        EmployeeID = employeeID;
+        Name = name;
+        Image = image;
+        Model = model;
+    }
+}
+
+public class TasksByUserEmployeeCategory
 {
-    /*
-    public class UserTasksHeaderImageConverter : IValueConverter
+    public Guid EmployeeID { get; set; }
+
+    public List<TaskModel> Tasks { get; set; } = new();
+
+    public TasksByUserEmployeeCategory(Guid employeeID)
     {
-        public static Dictionary<Guid, byte[]> Images { get; set; }
-        public static Dictionary<Guid, Guid> Employees { get; set; }
+        EmployeeID = employeeID;
+    }
+}
 
-        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
-        {
-            var anonymous = Resources.anonymous.AsBitmapImage();
+public class TasksByUserCategory
+{
+    public string Category { get; set; }
+
+    public IEnumerable<TasksByUserEmployeeCategory> EmployeeCategories => EmployeeCategoryDictionary.Values;
 
-            if (Images == null)
-                return anonymous;
+    public Dictionary<Guid, TasksByUserEmployeeCategory> EmployeeCategoryDictionary { get; set; } = new();
+
+    public TasksByUserCategory(string category)
+    {
+        Category = category;
+    }
+}
 
-            if (Employees == null)
-                return anonymous;
+public class TasksByUserModel
+{
+    public List<TasksByUserEmployeeHeader> SectionHeaders { get; set; } = new();
 
-            var dataContext = value as ColumnTag;
-            if (dataContext == null)
-                return anonymous;
+    public List<TasksByUserCategory> Categories { get; set; } = new();
+}
 
-            var getter = dataContext.GetType().GetProperty("Column", BindingFlags.NonPublic | BindingFlags.Instance);
-            if (getter == null)
-                return anonymous;
+public partial class TasksByUserControl : UserControl, INotifyPropertyChanged, ITaskControl
+{
+    private static readonly BitmapImage anonymous = PRSDesktop.Resources.anonymous.AsBitmapImage();
 
-            var column = (KanbanColumn)getter.GetValue(dataContext);
-            if (column == null)
-                return anonymous;
+    public TasksByUserModel Model { get; set; }
 
-            if (!Guid.TryParse(column.Categories, out var empid))
-                return anonymous;
+    private ILookup<Guid, Guid> TeamEmployees;
 
-            if (!Employees.TryGetValue(empid, out var imageid))
-                return anonymous;
+    private Dictionary<Guid, EmployeeModel> Employees;
 
-            if (!Images.TryGetValue(imageid, out var data))
-                return anonymous;
+    private KanbanViewMode _mode;
 
-            return ImageUtils.LoadImage(data);
-        }
+    private bool bPopulating;
 
-        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+    public KanbanViewMode Mode
+    {
+        get => _mode;
+        set
         {
-            throw new NotImplementedException();
+            _mode = value;
+            OnPropertyChanged();
         }
     }
 
-    public class UserTasksHeaderTimeConverter : IValueConverter
+    public TasksByUserControl()
     {
-        public static IEnumerable<TaskModel> Kanbans { get; set; }
+        InitializeComponent();
+    }
 
-        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
-        {
-            if (Kanbans == null)
-                return "0:00";
+    #region INotifyPropertyChanged
+
+    public event PropertyChangedEventHandler? PropertyChanged;
 
-            var dataContext = value as ColumnTag;
-            if (dataContext == null)
-                return "0:00";
+    // 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));
+    }
 
-            var getter = dataContext.GetType().GetProperty("Column", BindingFlags.NonPublic | BindingFlags.Instance);
-            if (getter == null)
-                return "0:00";
+    #endregion
 
-            var column = (KanbanColumn)getter.GetValue(dataContext);
-            if (column == null)
-                return "0:00";
+    #region Setup
+
+    private void LoadEmployees()
+    {
+        var empfilter = LookupFactory.DefineFilter<Employee>();
 
-            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);
+        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)));
+
+        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));
+
+        var teams = results.GetObjects<Team>().ToDictionary(x => x.ID, x => x.Name);
+        SelectedTeams.ItemsSource = teams;
+        foreach (var team in Host.KanbanSettings.UserSettings.SelectedTeams)
+        {
+            SelectedTeams.SelectedItems.Add(teams.Where(x => x.Key == team));
         }
+    }
 
-        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+    private void PopulateEmployees()
+    {
+        bPopulating = true;
+        try
         {
-            throw new NotImplementedException();
+            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)));
+
+            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)));
         }
+        catch (Exception e)
+        {
+        }
+
+        bPopulating = false;
     }
 
-//public class TaskHeaderWidthConverter : IValueConverter
-//{
+    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;
+    }
 
-//    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
-//    {
-//        var dataContext = (value as ColumnTag);
-//        if (dataContext == null)
-//            return 150;
+    private void PopulateKanbanTypes()
+    {
+        TaskType.Items.Add("");
+        foreach (var kanbanType in Host.KanbanTypes)
+        {
+            TaskType.Items.Add(kanbanType);
+        }
+    }
 
-//        PropertyInfo getter = dataContext.GetType().GetProperty("Column", BindingFlags.NonPublic | BindingFlags.Instance);
-//        if (getter == null)
-//            return 150;
+    public void Setup()
+    {
+        SetupToolbar();
 
-//        KanbanColumn column = (KanbanColumn)getter.GetValue(dataContext);
-//        if (column == null)
-//            return 150;
+        SplitPanel.AnchorWidth = Host.KanbanSettings.UserSettings.AnchorWidth;
+        TeamsRow.Height = new GridLength(Host.KanbanSettings.UserSettings.TeamsHeight);
 
-//        return Math.Max(150, column.Width) - 20 ;
+        LoadEmployees();
+        PopulateEmployees();
 
-//    }
+        Mode = Host.KanbanSettings.StatusSettings.CompactView ? KanbanViewMode.Compact : KanbanViewMode.Full;
 
-//    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
-//    {
-//        throw new NotImplementedException();
-//    }
-//}
+        PopulateKanbanTypes();
+    }
 
-    /// <summary>
-    ///     Interaction logic for TaskListPanel.xaml
-    /// </summary>
-    public partial class TasksByUserControl : UserControl, ITaskControl
-    {
-        private Dictionary<Guid, string> _employees;
+    #endregion
 
-        private List<TaskModel> _models = new();
-        private ILookup<Guid, Guid> _teamemployees;
-        private Dictionary<Guid, string> _teams;
+    #region Refresh
 
-        private bool bPopulating;
+    private Filter<KanbanSubscriber> GetKanbanSubscriberFilter()
+    {
+        var filter = new Filter<KanbanSubscriber>(c => c.Kanban.Closed).IsEqualTo(DateTime.MinValue)
+            .And(x => x.Kanban.Locked).IsEqualTo(false);
 
-        public TasksByUserControl()
+        var privateFilter = new Filter<KanbanSubscriber>(x => x.Kanban.Private).IsEqualTo(false);
+        if (App.EmployeeID != Guid.Empty)
         {
-            InitializeComponent();
+            privateFilter = privateFilter.Or(x => x.Employee.ID).IsEqualTo(App.EmployeeID);
         }
-        
-        public bool IsReady { get; set; }
+        filter.And(privateFilter);
 
-        #region Setup
-
-        public void Setup()
+        if (Host.Job != null)
         {
-            SetupToolbar();
-
-            SplitPanel.AnchorWidth = Host.KanbanSettings.UserSettings.AnchorWidth;
-
-            TeamsRow.Height = new GridLength(Host.KanbanSettings.UserSettings.TeamsHeight);
+            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();
+        }
 
-            LoadEmployees();
+        if (!Host.KanbanSettings.UserSettings.IncludeCompleted)
+            filter = filter.And(new Filter<KanbanSubscriber>(x => x.Kanban.Completed).IsEqualTo(DateTime.MinValue));
 
-            foreach (var team in Host.KanbanSettings.UserSettings.SelectedTeams)
-                SelectedTeams.SelectedItems.Add(_teams.FirstOrDefault(x => Equals(x.Key, team)));
+        var emps = Employees.Where(x => Host.KanbanSettings.UserSettings.SelectedEmployees.Contains(x.Key));
 
-            PopulateEmployees();
-            PopulateKanbanTypes();
+        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);
         }
+        return filter;
+    }
 
-        private void PopulateKanbanTypes()
+    private void ReloadColumns()
+    {
+        Model.SectionHeaders.Clear();
+        foreach (var employeeID in Host.KanbanSettings.UserSettings.SelectedEmployees)
         {
-            TaskType.Items.Add("");
-            foreach(var kanbanType in Host.KanbanTypes)
+            if (Employees.TryGetValue(employeeID, out var employee))
             {
-                TaskType.Items.Add(kanbanType);
+                Model.SectionHeaders.Add(new TasksByUserEmployeeHeader(employeeID, employee.Name, employee.Image ?? anonymous, Model));
             }
         }
+    }
+
+    public void Refresh()
+    {
+        var categoryOrder = new Dictionary<string, int>
+            {
+                { Kanban.OPEN, 0 },
+                { Kanban.INPROGRESS, 1 },
+                { Kanban.WAITING, 2 },
+                { Kanban.COMPLETE, 3 }
+            };
+        var filter = GetKanbanSubscriberFilter();
+
+        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 = CreateModels(kanbans.ToObjects<KanbanSubscriber>()).ToList();
 
-        #endregion
+            ReloadColumns();
 
-        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;
+            AllTasks = models.OrderBy(x => categoryOrder[x.Category]).ThenBy(x => x.DueDate).ToList();
+            FilterKanbans();
         }
+    }
 
-        public void Refresh(bool resetselection)
-        {
-            var _swimlanes = new Dictionary<string, int>
+    private void FilterKanbans()
+    {
+        var categoryOrder = new Dictionary<string, int>
             {
-                { "Open", 0 },
-                { "In Progress", 1 },
-                { "Waiting", 2 },
-                { "Complete", 3 }
+                { Kanban.OPEN, 0 },
+                { Kanban.INPROGRESS, 1 },
+                { Kanban.WAITING, 2 },
+                { Kanban.COMPLETE, 3 }
             };
 
-            var filter = new Filter<KanbanSubscriber>(c => c.Kanban.Closed).IsEqualTo(DateTime.MinValue)
-                .And(x => x.Kanban.Locked).IsEqualTo(false);
+        IEnumerable<TaskModel> filtered = AllTasks;
+        if (TaskType.SelectedItem is KanbanType kanbanType)
+        {
+            filtered = filtered.Where(x => x.Type.ID == kanbanType.ID);
+        }
+        if (!string.IsNullOrWhiteSpace(Search.Text))
+        {
+            var searches = Search.Text.Split();
+            filtered = filtered.Where(x => x.Search(searches));
+        }
+
+        var categories = new Dictionary<string, TasksByUserCategory>();
 
-            var privateFilter = new Filter<KanbanSubscriber>(x => x.Kanban.Private).IsEqualTo(false);
-            if (App.EmployeeID != Guid.Empty)
+        foreach (var task in filtered)
+        {
+            if(!categories.TryGetValue(task.Category, out var category))
             {
-                privateFilter = privateFilter.Or(x => x.Employee.ID).IsEqualTo(App.EmployeeID);
+                category = new TasksByUserCategory(task.Category);
+                categories.Add(task.Category, category);
             }
-            filter.And(privateFilter);
 
-            if (Host.Job != null)
+            if(!category.EmployeeCategoryDictionary.TryGetValue(task.EmployeeCategory, out var employeeCategory))
             {
-                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();
+                employeeCategory = new TasksByUserEmployeeCategory(task.EmployeeCategory);
+                category.EmployeeCategoryDictionary.Add(task.EmployeeCategory, employeeCategory);
             }
 
-            if (!Host.KanbanSettings.UserSettings.IncludeCompleted)
-                filter = filter.And(new Filter<KanbanSubscriber>(x => x.Kanban.Completed).IsEqualTo(DateTime.MinValue));
+            employeeCategory.Tasks.Add(task);
+            if (task.Checked)
+            {
+                SelectedTasks.Add(task);
+            }
+        }
 
-            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)
+        Model.Categories.AddRange(categories.Values.OrderBy(x => categoryOrder[x.Category]));
+    }
+
+    private IEnumerable<TaskModel> CreateModels(IEnumerable<KanbanSubscriber> subscribers)
+    {
+        foreach(var subscriber in subscribers)
+        {
+            var kanban = subscriber.Kanban;
+
+            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 = kanban.Title;
+            model.ID = kanban.ID;
+            model.Description = kanban.Summary ?? "";
+            model.EmployeeCategory = subscriber.Employee.ID;
+            model.Category = kanban.Category;
+
+            if (model.Category.IsNullOrWhiteSpace())
+                model.Category = "Open";
+
+            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)
             {
-                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);
+                colour = colour.MixColors(0.5F, Color.White);
             }
+            model.Color = System.Windows.Media.Color.FromArgb(colour.A, colour.R, colour.G, colour.B);
+
+            model.Attachments = kanban.Attachments > 0;
+            model.DueDate = kanban.DueDate;
+            model.CompletedDate = kanban.Completed;
+            model.Locked = kanban.Locked;
+            model.EstimatedTime = kanban.EstimatedTime;
 
-            using (new WaitCursor())
+            var notes = new List<List<string>> { new() };
+            var kanbanNotes = kanban.Notes;
+            if (kanbanNotes != null)
             {
-                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)
+                foreach (var line in kanbanNotes)
                 {
-                    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)
+                    if (line == "===================================")
                     {
-                        foreach (var line in kanbanNotes)
-                        {
-                            if (line == "===================================")
-                            {
-                                notes.Add(new());
-                            }
-                            else
-                            {
-                                notes.Last().Add(line);
-                            }
-                        }
+                        notes.Add(new());
                     }
-                    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
+                    else
                     {
-                        ID = typeID,
-                        Code = typeCode
-                    };
-                    model.Number = row.Get<KanbanSubscriber, int>(x => x.Kanban.Number);
-
-                    model.PropertyChanged += Model_PropertyChanged;
-
-                    models.Add(model);
+                        notes.Last().Add(line);
+                    }
                 }
-
-                UserTasksHeaderTimeConverter.Kanbans = models;
-
-                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();
-                
             }
-        }
-
-        private void Model_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
-        {
-            if(e.PropertyName == nameof(TaskModel.Assignee))
+            model.Notes = string.Join("\n===================================\n", notes.Reverse<List<string>>().Select(x => string.Join('\n', x)));
+
+            model.EmployeeID = kanban.EmployeeLink.ID;
+            model.ManagerID = kanban.ManagerLink.ID;
+
+            var employeeString = kanban.EmployeeLink.ID == subscriber.Employee.ID
+                ? ""
+                : kanban.EmployeeLink.ID == Guid.Empty
+                    ? " to (Unallocated)"
+                    : " to " + kanban.EmployeeLink.Name;
+
+            var managerString = kanban.ManagerLink.ID == Guid.Empty || kanban.ManagerLink.ID == subscriber.Employee.ID
+                ? ""
+                : " by " + kanban.ManagerLink.Name;
+
+            model.AssignedTo = !string.IsNullOrEmpty(employeeString) || !managerString.IsNullOrWhiteSpace()
+                ? $"Assigned{employeeString}{managerString}"
+                : "";
+
+            model.JobID = kanban.JobLink.ID;
+            model.JobNumber = kanban.JobLink.JobNumber?.Trim() ?? "";
+            model.JobName = kanban.JobLink.Name;
+            model.Checked = SelectedTasks.Any(x => x.ID == model.ID);
+            model.Type = new KanbanType
             {
-                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;
-
-                    new Client<Kanban>().Save(kanban, string.Format("Task Category Updated to {0}", model.Assignee), (o, err) => { });
-                    model.Checked = false;
+                ID = typeID,
+                Code = typeCode
+            };
+            model.Number = kanban.Number;
 
-                    FilterKanbans();
-                }
-            }
+            yield return model;
         }
+    }
 
-        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, 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 Kanban
 
-            Host.SaveSettings();
-        }
+    private readonly List<TaskModel> SelectedTasks = new();
+    private List<TaskModel> AllTasks { get; set; } = new();
 
-        private void Kanban_SizeChanged(object sender, SizeChangedEventArgs e)
+    private void DoEdit(TaskModel task)
+    {
+        var result = Host.EditReferences(new[] { task });
+        if (result)
         {
-            Kanban.ColumnWidth = Kanban.ActualWidth / Kanban.Columns.Count - 1.0F;
+            Refresh();
         }
+    }
 
-        private void SplitPanel_OnChanged(object sender, DynamicSplitPanelSettings e)
-        {
-            if (!IsReady || Equals(Host.KanbanSettings.UserSettings.AnchorWidth, e.AnchorWidth))
-                return;
-            SaveSettings();
-        }
+    private void EditTask_Executed(object sender, ExecutedRoutedEventArgs e)
+    {
+        if (e.Parameter is not TaskModel model) return;
 
-        private void LoadEmployees()
-        {
-            var empfilter = LookupFactory.DefineFilter<Employee>();
+        DoEdit(model);
+    }
 
-            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);
+    private void OpenTaskMenu_Executed(object sender, ExecutedRoutedEventArgs e)
+    {
+        if (e.Parameter is not KanbanResources.OpenTaskMenuCommandArgs args) return;
 
-            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)
-            );
+        Host.PopulateMenu(this, args.Model, args.Menu);
+    }
 
-            query.Query();
+    private void SelectTask_Executed(object sender, ExecutedRoutedEventArgs e)
+    {
+        if (e.Parameter is not TaskModel model) return;
 
-            _teams = query.Get<Team>().ToDictionary<Team, Guid, string>(x => x.ID, x => x.Name);
+        if (!SelectedTasks.Remove(model))
+        {
+            SelectedTasks.Add(model);
+        }
+    }
 
-            _employees = query.Get<Employee>().ToDictionary<Employee, Guid, string>(x => x.ID, x => x.Name, x => x.Name);
+    private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
+    {
+        e.CanExecute = true;
+    }
 
-            UserTasksHeaderImageConverter.Employees = query.Get<Employee>().ToDictionary<Employee, Guid, Guid>(x => x.ID, x => x.Thumbnail.ID);
+    #endregion
 
-            _teamemployees = query.Get<EmployeeTeam>().ToLookup<EmployeeTeam, Guid, Guid>(x => x.TeamLink.ID, x => x.EmployeeLink.ID);
+    #region ITaskControl
 
-            SelectedTeams.ItemsSource = _teams;
-        }
+    public ITaskHost Host { get; set; }
 
-        private void PopulateEmployees()
-        {
-            bPopulating = true;
-            try
-            {
-                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)));
-            }
-            catch (Exception e)
-            {
-            }
+    public KanbanViewType KanbanViewType => KanbanViewType.User;
 
-            bPopulating = false;
-        }
+    public bool IsReady { get; set; }
 
+    public string SectionName => "Tasks By User";
 
-        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;
-        }
+    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 FilterKanbans()
+    public IEnumerable<TaskModel> SelectedModels(TaskModel? sender = null)
+    {
+        if (sender is null)
         {
-            IEnumerable<TaskModel> Items = _models;
-            if (TaskType.SelectedItem is KanbanType kanbanType)
-            {
-                Items = Items.Where(x => x.Type.ID == kanbanType.ID);
-            }
-            if (!string.IsNullOrWhiteSpace(Search.Text))
-            {
-                var searches = Search.Text.Split();
-                Items = Items.Where(x => x.Search(searches));
-            }
-
-            if(object.Equals(Kanban.ItemsSource, Items))
-            {
-                // Triggers a refresh.
-                Kanban.ItemsSource = null;
-            }
-            Kanban.ItemsSource = Items;
+            return SelectedTasks;
         }
-
-        private void ViewType_SelectionChanged(object sender, SelectionChangedEventArgs e)
+        else
         {
-            if (Kanban != null)
-                Kanban.CardTemplate = ViewType.SelectedIndex > 0
-                    ? Resources["CompactKanban"] as DataTemplate
-                    : Resources["FullKanban"] as DataTemplate;
-            if (IsReady)
+            var result = SelectedTasks.ToList();
+            if (!result.Contains(sender))
             {
-                Host.KanbanSettings.StatusSettings.CompactView = ViewType.SelectedIndex > 0;
-                Host.SaveSettings();
+                result.Add(sender);
             }
+            return result;
         }
+    }
 
-        private void TaskType_SelectionChanged(object sender, SelectionChangedEventArgs e)
-        {
-            FilterKanbans();
-        }
-
-        private void IncludeLocked_Checked(object sender, RoutedEventArgs e)
-        {
-            if (!IsReady)
-                return;
-
-            SaveSettings();
-            Refresh(true);
-        }
+    #endregion
 
-        private void IncludeManaged_Checked(object sender, RoutedEventArgs e)
-        {
-            if (!IsReady)
-                return;
+    #region Settings
 
-            SaveSettings();
-            Refresh(true);
-        }
-        private void IncludeObserved_Checked(object sender, RoutedEventArgs e)
-        {
-            if (!IsReady)
-                return;
+    private void SaveSettings()
+    {
+        Host.KanbanSettings.UserSettings.AnchorWidth = SplitPanel.AnchorWidth;
 
-            SaveSettings();
-            Refresh(true);
-        }
+        Host.KanbanSettings.UserSettings.TeamsHeight = SelectedTeams.ActualHeight;
 
-        private void IncludeCompleted_Checked(object sender, RoutedEventArgs e)
-        {
-            if (!IsReady)
-                return;
+        var teams = SelectedTeams.SelectedItems.Select(x => ((KeyValuePair<Guid, string>)x).Key);
+        Host.KanbanSettings.UserSettings.SelectedTeams = teams.ToArray();
 
-            SaveSettings();
-            Refresh(true);
-        }
+        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();
 
-        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;
-        }
+        Host.KanbanSettings.UserSettings.IncludeCompleted = IncludeCompleted.IsChecked == true;
+        Host.KanbanSettings.UserSettings.IncludeObserved = IncludeObserved.IsChecked == true;
+        Host.KanbanSettings.UserSettings.IncludeManaged = IncludeManaged.IsChecked == true;
 
+        Host.SaveSettings();
+    }
 
-        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());
-                    }
-                }
+    private void SplitPanel_OnChanged(object sender, DynamicSplitPanelSettings e)
+    {
+        if (!IsReady || Equals(Host.KanbanSettings.UserSettings.AnchorWidth, e.AnchorWidth))
+            return;
+        SaveSettings();
+    }
 
-                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;
-                }
+    private void SelectedTeams_ItemChecked(object sender, ItemCheckedEventArgs e)
+    {
+        if (!IsReady)
+            return;
+        PopulateEmployees();
+        SaveSettings();
+        Refresh();
+    }
 
-                FilterKanbans();
-            }
-        }
+    private void SelectedTeams_SizeChanged(object sender, SizeChangedEventArgs e)
+    {
+        if (!IsReady || Equals(Host.KanbanSettings.UserSettings.TeamsHeight, SelectedTeams.ActualHeight))
+            return;
+        SaveSettings();
+    }
 
-        #region ITaskControl Support
+    private void EmployeesSelectionChanged(object sender, SelectionChangedEventArgs e)
+    {
+        if (!IsReady || bPopulating)
+            return;
+        SaveSettings();
+        Refresh();
+    }
 
-        public ITaskHost Host { get; set; }
+    #endregion
 
-        public KanbanViewType KanbanViewType => KanbanViewType.User;
+    private void Export_Click(object sender, RoutedEventArgs e)
+    {
 
-        public IEnumerable<TaskModel> SelectedModels(TaskModel? sender = null)
-        {
-            var result = _models.Where(x => x.Checked).ToList();
-            if (sender != null && !result.Contains(sender))
-                result.Add(sender);
-            return result;
-        }
+    }
 
-        #endregion
+    private void IncludeCompleted_Checked(object sender, RoutedEventArgs e)
+    {
 
-        #region Kanban Actions
+    }
 
-        private void DoEdit(TaskModel task)
-        {
-            if (task == null)
-                return;
+    private void IncludeObserved_Checked(object sender, RoutedEventArgs e)
+    {
 
-            var result = Host.EditReferences(new[] { task });
-            if (result)
-                Refresh(true);
-        }
+    }
 
-        private void TaskMenu_Opened(object sender, RoutedEventArgs e)
-        {
-            Host.PopulateMenu(this, (sender as ContextMenu)!);
-        }
+    private void IncludeManaged_Checked(object sender, RoutedEventArgs e)
+    {
 
-        private void Border_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
-        {
-            if (e.ClickCount > 1)
-            {
-                var task = ((Border)sender).Tag as TaskModel;
-                DoEdit(task);
-                e.Handled = true;
-            }
-        }
+    }
 
-        private void CheckBox_Checked(object sender, RoutedEventArgs e)
-        {
-        }
+    private void ViewType_SelectionChanged(object sender, SelectionChangedEventArgs e)
+    {
 
-        #endregion
+    }
 
-        #region Employee List Actions
+    private void TaskType_SelectionChanged(object sender, SelectionChangedEventArgs e)
+    {
 
-        private void SelectedTeams_ItemChecked(object sender, ItemCheckedEventArgs e)
-        {
-            if (!IsReady)
-                return;
-            PopulateEmployees();
-            SaveSettings();
-            Refresh(true);
-        }
+    }
 
-        private void SelectedTeams_SizeChanged(object sender, SizeChangedEventArgs e)
-        {
-            if (!IsReady || Equals(Host.KanbanSettings.UserSettings.TeamsHeight, SelectedTeams.ActualHeight))
-                return;
-            SaveSettings();
-        }
+    private void Search_KeyUp(object sender, KeyEventArgs e)
+    {
 
-        private void EmployeesSelectionChanged(object sender, SelectionChangedEventArgs e)
-        {
-            if (!IsReady || bPopulating)
-                return;
-            SaveSettings();
-            Refresh(true);
-        }
+    }
 
-        public string SectionName => "Tasks By User";
+    private void ItemsControl_DragOver(object sender, DragEventArgs e)
+    {
 
-        public DataModel DataModel(Selection selection)
-        {
-            return new AutoDataModel<Kanban>(new Filter<Kanban>(x => x.ID).IsEqualTo(Guid.Empty));
-        }
+    }
 
-        #endregion
+    private void ItemsControl_Drop(object sender, DragEventArgs e)
+    {
 
-        private void Kanban_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
-        {
-            e.Handled = true;
-        }
-    }*/
+    }
 }