Bladeren bron

Fixed annoying refreshing for digital form dock.
Reworked some digital forms dock and added button to view attached entity.

Kenric Nugteren 1 jaar geleden
bovenliggende
commit
26703106d1

+ 0 - 0
prs.desktop/DockPanels/DeletedFormWindow.xaml → prs.desktop/DockPanels/DigitalForms/DeletedFormWindow.xaml


+ 0 - 0
prs.desktop/DockPanels/DeletedFormWindow.xaml.cs → prs.desktop/DockPanels/DigitalForms/DeletedFormWindow.xaml.cs


+ 489 - 0
prs.desktop/DockPanels/DigitalForms/DigitalFormDockGrid.cs

@@ -0,0 +1,489 @@
+using Comal.Classes;
+using InABox.Clients;
+using InABox.Core;
+using InABox.DynamicGrid;
+using InABox.Scripting;
+using InABox.Wpf.Reports;
+using InABox.WPF;
+using PRSDesktop.Configuration;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+
+namespace PRSDesktop;
+
+public class DigitalFormDockGrid : DynamicGrid<DigitalFormDockModel>
+{
+    private static readonly CoreFieldMap<IDigitalFormInstance, DigitalFormDockModel> _mappings =
+        new CoreFieldMap<IDigitalFormInstance, DigitalFormDockModel>()
+            .Add(x => x.ID, x => x.ID)
+            .Add(x => x.Form.ID, x => x.FormID)
+            .Add(x => x.Number, x => x.Number)
+            .Add(x => x.Form.Description, x => x.FormName)
+            .Add(x => x.FormCompleted, x => x.Completed)
+            .Add(x => x.FormCompletedBy.UserID, x => x.CompletedBy)
+            .Add(x => x.FormProcessed, x => x.Processed)
+            .Add(x => x.Parent.ID, x => x.ParentID);
+
+    public static readonly Dictionary<Type, System.Drawing.Bitmap> Images = new()
+        {
+            { typeof(AssignmentForm), PRSDesktop.Resources.assignments },
+            { typeof(KanbanForm), PRSDesktop.Resources.kanban },
+            { typeof(JobForm), PRSDesktop.Resources.project },
+            { typeof(JobITPForm), PRSDesktop.Resources.checklist },
+            { typeof(EmployeeForm), PRSDesktop.Resources.employees },
+            { typeof(LeaveRequestForm), PRSDesktop.Resources.leave },
+            { typeof(ManufacturingPacketStage), PRSDesktop.Resources.factory },
+            { typeof(TimeSheetForm), PRSDesktop.Resources.time },
+            { typeof(PurchaseOrderItemForm), PRSDesktop.Resources.purchase },
+            { typeof(DeliveryForm), PRSDesktop.Resources.truck },
+        };
+
+    public List<Type> ExcludedTypes { get; private set; }
+
+    public DateTime StartDate { get; set; }
+
+    public DigitalFormDockGrid()
+    {
+        StartDate = DateTime.Today;
+        ExcludedTypes = new List<Type>();
+
+        ActionColumns.Add(new DynamicImageColumn(TypeImage)
+        {
+            Position = DynamicActionColumnPosition.Start,
+            Filters = Images.Keys.Select(x => x.EntityName().Split('.').Last().SplitCamelCase()).ToArray(),
+            FilterRecord = TypeFilter
+        });
+
+        ActionColumns.Add(new DynamicMenuColumn(MenuBuild, MenuStatus) { Position = DynamicActionColumnPosition.End });
+    }
+
+    protected override void Init()
+    {
+    }
+
+    protected override void DoReconfigure(FluentList<DynamicGridOption> options)
+    {
+        options.BeginUpdate()
+            .Clear()
+            .Add(DynamicGridOption.FilterRows)
+            .EndUpdate();
+    }
+
+    protected override DynamicGridStyle GetRowStyle(CoreRow row, DynamicGridStyle style)
+    {
+        var result = base.GetRowStyle(row, style);
+        if (!row.Get<DigitalFormDockModel, DateTime>(x => x.Processed).IsEmpty())
+            result = new DynamicGridRowStyle(result)
+            {
+                Background = new SolidColorBrush(Colors.LightGray),
+            };
+        return result;
+    }
+
+    private bool TypeFilter(CoreRow row, string[] filter)
+    {
+        string typename = row.Get<DigitalFormDockModel, Type>(x => x.FormType).EntityName().Split('.').Last().SplitCamelCase();
+        return filter.Contains(typename);
+    }
+
+    private BitmapImage? TypeImage(CoreRow? arg)
+    {
+        if (arg is null)
+            return null;
+        var type = arg.Get<DigitalFormDockModel, Type>(x => x.FormType);
+        return Images.GetValueOrDefault(type)?.AsBitmapImage();
+    }
+
+    private static readonly MethodInfo MenuBuildGenericMethod = typeof(DigitalFormDockGrid)
+        .GetMethod(nameof(MenuBuildGeneric), BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)!;
+    private void MenuBuildGeneric<TEntityForm, TEntity, TEntityLink>(ContextMenu menu, DigitalFormDockModel formModel)
+            where TEntityForm : Entity, IDigitalFormInstance<TEntityLink>, IRemotable, IPersistent, new()
+            where TEntity : Entity, IRemotable, IPersistent, new()
+            where TEntityLink : IEntityLink<TEntity>, new()
+    {
+        var filter = new Filter<TEntityForm>(x => x.ID).IsEqualTo(formModel.ID);
+        var model = new DigitalFormReportDataModel<TEntityForm>(filter, formModel.FormID);
+
+        if (Security.CanView<TEntity>())
+        {
+            menu.AddItem($"View {typeof(TEntity).Name}", null, formModel, ViewEntity_Click<TEntity>);
+        }
+
+        var moduleTask = Task.Run(() =>
+        {
+            return new Client<CustomModule>().Query(
+                new Filter<CustomModule>(x => x.DataModel).IsEqualTo(model.Name)
+                    .And(x => x.Section).IsEqualTo(formModel.FormID.ToString())
+                    .And(x => x.Visible).IsEqualTo(true)
+                ).ToObjects<CustomModule>();
+        });
+
+        var modulesSeparator = menu.AddSeparatorIfNeeded();
+        var modulesItem = menu.AddItem("Loading...", null, null, enabled: false);
+        moduleTask.ContinueWith((task) =>
+        {
+            try
+            {
+                var index = menu.Items.IndexOf(modulesItem);
+                menu.Items.Remove(modulesItem);
+                var any = false;
+                foreach (var module in task.Result)
+                {
+                    any = true;
+                    menu.AddItem(
+                        module.Name,
+                        PRSDesktop.Resources.edit,
+                        () =>
+                        {
+                            try
+                            {
+                                if (ScriptDocument.RunCustomModule(model, new Dictionary<string, object[]>(), module.Script))
+                                {
+                                    Refresh(false, true);
+                                }
+                            }
+                            catch (CompileException c)
+                            {
+                                MessageBox.Show(c.Message);
+                            }
+                            catch (Exception e)
+                            {
+                                MessageBox.Show(CoreUtils.FormatException(e));
+                            }
+                        },
+                        enabled: formModel.Processed.IsEmpty(),
+                        index: index);
+                    ++index;
+                }
+                if (!any && modulesSeparator is not null)
+                {
+                    menu.Items.Remove(modulesSeparator);
+                }
+            }
+            catch (Exception ex)
+            {
+                Logger.Send(LogType.Error, ClientFactory.UserID, $"Error in digital form dock while loading custom modules: {CoreUtils.FormatException(ex)}");
+                MessageBox.Show($"Error: {ex.Message}", "Error");
+            }
+        }, TaskScheduler.FromCurrentSynchronizationContext());
+
+        menu.AddSeparator();
+
+        if (formModel.Processed.IsEmpty())
+        {
+            menu.AddItem(
+                "Mark as Processed",
+                PRSDesktop.Resources.lock_sml,
+                () =>
+                {
+                    var form = new TEntityForm
+                    {
+                        ID = formModel.ID,
+                        FormProcessed = formModel.Processed
+                    };
+                    form.CommitChanges();
+                    form.FormProcessed = DateTime.Now;
+                    using (new WaitCursor())
+                    {
+                        Client.Save(form, "Marked As Processed");
+                        Refresh(false, true);
+                    }
+                });
+        }
+        else
+        {
+            menu.AddItem(
+                "Clear Processed Flag",
+                PRSDesktop.Resources.lock_sml,
+                () =>
+                {
+                    var form = new TEntityForm
+                    {
+                        ID = formModel.ID,
+                        FormProcessed = formModel.Processed
+                    };
+                    form.CommitChanges();
+                    form.FormProcessed = DateTime.MinValue;
+                    using (new WaitCursor())
+                    {
+                        Client.Save(form, "Processed Flag Cleared");
+                        Refresh(false, true);
+                    }
+                });
+        }
+
+        if (typeof(TEntity).HasInterface<IJobScopedItem>())
+        {
+            menu.AddSeparatorIfNeeded();
+            menu.AddItem("Set Job", PRSDesktop.Resources.project, formModel,
+                SetJob_ClickMethod.MakeGenericMethod(typeof(TEntity)).CreateDelegate<Action<DigitalFormDockModel>>());
+            menu.AddItem("Set Job Scope", PRSDesktop.Resources.project, formModel,
+                SetJobScope_ClickMethod.MakeGenericMethod(typeof(TEntity)).CreateDelegate<Action<DigitalFormDockModel>>());
+        }
+        else if (typeof(TEntity) == typeof(Job) && typeof(TEntity) == typeof(JobForm))
+        {
+            menu.AddSeparatorIfNeeded();
+            menu.AddItem("Set Job Scope", PRSDesktop.Resources.project, formModel, SetJobScopeFromJob_Click);
+        }
+
+        if (Security.IsAllowed<CanCustomiseModules>())
+        {
+            menu.AddSeparatorIfNeeded();
+            menu.AddItem("Customise Modules", PRSDesktop.Resources.script, () =>
+            {
+                var manager = new CustomModuleManager
+                {
+                    Section = formModel.FormID.ToString(),
+                    DataModel = model
+                };
+                manager.ShowDialog();
+            });
+        }
+
+        if (Security.IsAllowed<CanPrintReports>())
+        {
+            menu.AddSeparatorIfNeeded();
+            var printItem = menu.AddItem("Print", PRSDesktop.Resources.printer, null);
+
+            ReportUtils.PopulateMenu(printItem, formModel.FormID.ToString(), model, Security.IsAllowed<CanDesignReports>(), true);
+        }
+    }
+
+    private static void ViewEntity_Click<TEntity>(DigitalFormDockModel model)
+        where TEntity : Entity, IRemotable, IPersistent, new()
+    {
+        var entity = Client.Query(
+            new Filter<TEntity>(x => x.ID).IsEqualTo(model.ParentID),
+            DynamicGridUtils.LoadEditorColumns<TEntity>(new Columns<TEntity>(x => x.ID)))
+            .ToObjects<TEntity>().First();
+        var grid = (DynamicGridUtils.CreateDynamicGrid(typeof(DynamicGrid<>), typeof(TEntity)) as DynamicGrid<TEntity>)!;
+        grid.EditItems(new TEntity[] { entity });
+    }
+
+    private static void SetJobScopeFromJob_Click(DigitalFormDockModel formModel)
+    {
+        var instance = Client.Query(
+            new Filter<JobForm>(x => x.ID).IsEqualTo(formModel.ID),
+            LookupFactory.DefineFilterColumns<JobForm, JobScope>()
+                .Add(x => x.ID)
+                .Add(x => x.Parent.ID))
+            .ToObjects<JobForm>().First();
+        var job = Client.Query(
+            new Filter<Job>(x => x.ID).IsEqualTo(instance.Parent.ID),
+            LookupFactory.DefineFilterColumns<Job, JobScope>()).ToObjects<Job>().First();
+
+        var window = new MultiSelectDialog<JobScope>(
+            new Filters<JobScope>()
+                .Add(LookupFactory.DefineFilter<Job, JobScope>(new Job[] { job }))
+                .Add(new Filter<JobScope>(x => x.Job.ID).IsEqualTo(instance.Parent.ID))
+                .Combine(),
+            new Columns<JobScope>(x => x.ID).Add(x => x.Number), multiselect: false);
+        if (!window.ShowDialog())
+        {
+            return;
+        }
+        var scope = window.Data().ToObjects<JobScope>().First();
+        instance.JobScope.ID = scope.ID;
+
+        Client.Save(instance, "Linked scope set by user from Digital forms dock.");
+        MessageBox.Show($"Form has been assigned to scope {scope.Number}.");
+    }
+
+
+    private static readonly MethodInfo SetJobScope_ClickMethod = typeof(DigitalFormDockGrid).GetMethod(nameof(SetJobScope_Click), BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)!;
+    private static void SetJobScope_Click<TEntity>(DigitalFormDockModel formModel)
+        where TEntity : Entity, IJobScopedItem, IRemotable, IPersistent, new()
+    {
+        var entity = Client.Query(
+            new Filter<TEntity>(x => x.ID).IsEqualTo(formModel.ParentID),
+            LookupFactory.DefineFilterColumns<TEntity, JobScope>()
+                .Add(x => x.ID)
+                .Add(x => x.JobLink.ID)
+                .Add(x => x.JobScope.ID)
+                .Add(x => x.JobScope.Number))
+            .ToObjects<TEntity>().First();
+
+        if (entity.JobLink.ID == Guid.Empty)
+        {
+            MessageBox.Show($"{typeof(TEntity).Name} is not linked to a job. Please select a job first.");
+            return;
+        }
+
+        var window = new MultiSelectDialog<JobScope>(
+            new Filters<JobScope>()
+                .Add(LookupFactory.DefineFilter<TEntity, JobScope>(new TEntity[] { entity }))
+                .Add(new Filter<JobScope>(x => x.Job.ID).IsEqualTo(entity.JobLink.ID))
+                .Combine(),
+            new Columns<JobScope>(x => x.ID).Add(x => x.Number),
+            multiselect: false);
+        if (!window.ShowDialog(nameof(JobScope.Number), entity.JobScope.Number, Syncfusion.Data.FilterType.Equals))
+        {
+            return;
+        }
+        var scope = window.Data().ToObjects<JobScope>().First();
+        entity.JobScope.ID = scope.ID;
+
+        Client.Save(entity, "Linked scope set by user from Digital forms dock.");
+        MessageBox.Show($"{typeof(TEntity).Name} has been assigned to scope {scope.Number}.");
+    }
+
+    private static readonly MethodInfo SetJob_ClickMethod = typeof(DigitalFormDockGrid).GetMethod(nameof(SetJob_Click), BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)!;
+    private static void SetJob_Click<TEntity>(DigitalFormDockModel formModel)
+        where TEntity: Entity, IJobScopedItem, IRemotable, IPersistent, new()
+    {
+        var entity = Client.Query(
+            new Filter<TEntity>(x => x.ID).IsEqualTo(formModel.ParentID),
+            LookupFactory.DefineFilterColumns<TEntity, Job>()
+                .Add(x => x.ID)
+                .Add(x => x.JobLink.ID)
+                .Add(x => x.JobLink.JobNumber))
+            .ToObjects<TEntity>().First();
+
+        var window = new MultiSelectDialog<Job>(
+            LookupFactory.DefineFilter<TEntity, Job>(new TEntity[] { entity }),
+            new Columns<Job>(x => x.DefaultScope.ID).Add(x => x.JobNumber),
+            multiselect: false);
+        if (!window.ShowDialog(nameof(Job.JobNumber), entity.JobLink.JobNumber, Syncfusion.Data.FilterType.Equals))
+        {
+            return;
+        }
+        var job = window.Data().ToObjects<Job>().First();
+        entity.JobLink.ID = job.ID;
+        entity.JobLink.Synchronise(job);
+
+        Client.Save(entity, "Linked job set by user from Digital forms dock.");
+        MessageBox.Show($"{typeof(TEntity).Name} has been assigned to job {job.JobNumber}.");
+    }
+
+    private void MenuBuild(DynamicMenuColumn column, CoreRow? row)
+    {
+        if (row is null) return;
+
+        var form = row.ToObject<DigitalFormDockModel>();
+        var linkType = DFUtils.FormEntityLinkType(form.FormType);
+        var entityType = DFUtils.FormEntityType(form.FormType);
+
+        MenuBuildGenericMethod.MakeGenericMethod(form.FormType, entityType, linkType).Invoke(this, new object?[] { column.GetMenu(), form });
+    }
+
+    private DynamicMenuStatus MenuStatus(CoreRow row)
+    {
+        if (row == null) return DynamicMenuStatus.Hidden;
+        return DynamicMenuStatus.Enabled;
+    }
+
+    protected override void Reload(Filters<DigitalFormDockModel> criteria, Columns<DigitalFormDockModel> columns, ref SortOrder<DigitalFormDockModel>? sort, Action<CoreTable, Exception?> action)
+    {
+        var queryDefs = new Dictionary<string, IQueryDef>();
+
+        var types = DFUtils.GetFormInstanceTypes().Except(ExcludedTypes).ToList();
+        foreach (var type in types)
+        {
+            var filter = Filter.Create<IDigitalFormInstance>(type, x => x.FormCompleted).IsGreaterThanOrEqualTo(StartDate)
+                .And<IDigitalFormInstance>(x => x.FormCancelled).IsEqualTo(DateTime.MinValue);
+
+            var cols = Columns.Create<IDigitalFormInstance>(type)
+                .Add<IDigitalFormInstance>(c => c.ID)
+                .Add<IDigitalFormInstance>(c => c.Parent.ID)
+                .Add<IDigitalFormInstance>(c => c.Form.ID)
+                .Add<IDigitalFormInstance>(c => c.Number)
+                .Add<IDigitalFormInstance>(c => c.Form.Description)
+                .Add<IDigitalFormInstance>(c => c.FormCompleted)
+                .Add<IDigitalFormInstance>(c => c.FormCompletedBy.UserID)
+                .Add<IDigitalFormInstance>(c => c.FormProcessed);
+
+            var sorts = SortOrder.Create<IDigitalFormInstance>(type, x => x.FormCompleted, SortDirection.Descending);
+
+            queryDefs.Add(
+                type.ToString(),
+                new QueryDef(type)
+                {
+                    Filter = filter,
+                    Columns = cols,
+                    SortOrder = sorts
+                });
+        }
+
+        Client.QueryMultiple(
+            (results, e) =>
+            {
+                if(results is not null)
+                {
+                    var data = new CoreTable();
+                    data.LoadColumns(typeof(DigitalFormDockModel));
+
+                    foreach (var type in types)
+                        data.LoadFrom(
+                            results[type.ToString()],
+                            _mappings,
+                            (r) => r.Set<DigitalFormDockModel, Type>(x => x.FormType, type)
+                        );
+                    action.Invoke(data, null);
+                }
+                else if(e is not null)
+                {
+                    Logger.Send(LogType.Error, ClientFactory.UserID, CoreUtils.FormatException(e));
+                    MessageBox.Show($"Error: {e.Message}");
+                }
+            },
+            queryDefs);
+    }
+
+    protected override DigitalFormDockModel LoadItem(CoreRow row)
+    {
+        return row.ToObject<DigitalFormDockModel>();
+    }
+
+    public override void SaveItem(DigitalFormDockModel item)
+    {
+    }
+
+    protected override void DeleteItems(params CoreRow[] rows)
+    {
+    }
+
+    protected override void DoDoubleClick(object sender)
+    {
+        base.DoDoubleClick(sender);
+
+        var row = SelectedRows.FirstOrDefault();
+        if (row is null)
+            return;
+
+        var instanceID = row.Get<DigitalFormDockModel, Guid>(x => x.ID);
+        var formID = row.Get<DigitalFormDockModel, Guid>(x => x.FormID);
+        var formType = row.Get<DigitalFormDockModel, Type>(x => x.FormType);
+
+        var formInstance = Client.Create(formType)
+            .Query(
+                Filter.Create<IDigitalFormInstance>(formType, x => x.ID).IsEqualTo(instanceID),
+                DynamicFormEditWindow.FormColumns(formType))
+            .Rows.FirstOrDefault()
+            ?.ToObject(formType) as IDigitalFormInstance;
+
+        if (formInstance is null)
+        {
+            return;
+        }
+        if (formID == Guid.Empty)
+        {
+            var window = new DeletedFormWindow();
+            window.FormData = formInstance?.FormData ?? "";
+            window.ShowDialog();
+            return;
+        }
+        if (DynamicFormEditWindow.EditDigitalForm(formInstance, out var model))
+        {
+            model.Update(null);
+        }
+    }
+
+}

+ 43 - 0
prs.desktop/DockPanels/DigitalForms/DigitalFormDockModel.cs

@@ -0,0 +1,43 @@
+using InABox.Core;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PRSDesktop;
+
+public class DigitalFormDockModel : BaseObject
+{
+    [NullEditor]
+    public Guid ID { get; set; }
+
+    [NullEditor]
+    public Guid FormID { get; set; }
+
+    [CodeEditor(Visible = Visible.Default, Width = 80)]
+    [EditorSequence(1)]
+    public string Number { get; set; } = "";
+
+    [TextBoxEditor(Visible = Visible.Default)]
+    [EditorSequence(2)]
+    public string FormName { get; set; } = "";
+
+    [DateTimeEditor(Visible = Visible.Default, Width = 100, Format = "dd MMM yy HH:mm")]
+    [EditorSequence(3)]
+    public DateTime Completed { get; set; }
+
+    [TextBoxEditor(Visible = Visible.Default, Width = 80, Alignment = Alignment.MiddleCenter)]
+    [EditorSequence(4)]
+    public string CompletedBy { get; set; } = "";
+
+    [NullEditor]
+    public Type FormType { get; set; }
+
+    [NullEditor]
+    public DateTime Processed { get; set; }
+
+    [NullEditor]
+    public Guid ParentID { get; set; }
+}
+

+ 1 - 1
prs.desktop/DockPanels/DigitalFormsDock.xaml → prs.desktop/DockPanels/DigitalForms/DigitalFormsDock.xaml

@@ -10,7 +10,7 @@
         <Border DockPanel.Dock="Top" BorderBrush="Gray" BorderThickness="0.75" Background="WhiteSmoke" Padding="2">
             <DockPanel DockPanel.Dock="Top">
                 <Button x:Name="RefreshButton" DockPanel.Dock="Left" Width="25" BorderBrush="Gray" BorderThickness="0.75" Padding="2" Click="RefreshButton_OnClick">
-                    <Image Source="../Resources/refresh.png"/>
+                    <Image Source="pack://application:,,,/Resources/refresh.png"/>
                 </Button>
                 <StackPanel x:Name="TypeStack" DockPanel.Dock="Right" Orientation="Horizontal" />
                 <ComboBox x:Name="AgeCombo" DockPanel.Dock="Left" Margin="2,0,0,0" SelectionChanged="AgeCombo_OnSelectionChanged" SelectedIndex="0">

+ 79 - 0
prs.desktop/DockPanels/DigitalForms/DigitalFormsDock.xaml.cs

@@ -0,0 +1,79 @@
+using System;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+using InABox.Core;
+using InABox.WPF;
+
+namespace PRSDesktop;
+
+public partial class DigitalFormsDock : UserControl, IDockPanel
+{
+    private static readonly SolidColorBrush EnabledBrush = new SolidColorBrush(Colors.LightYellow);
+    private static readonly SolidColorBrush DisabledBrush = new SolidColorBrush(Colors.LightGray);
+
+    public DigitalFormsDock()
+    {
+        InitializeComponent();
+        //RefreshButton.Content = new Image() { Source = PRSDesktop.Resources.refresh.AsBitmapImage() };
+        foreach (var (type, image) in DigitalFormDockGrid.Images)
+        {
+            var button = new Button();
+            button.Background = EnabledBrush;
+            button.Content = new Image() { Source = image.AsBitmapImage() };
+            button.BorderBrush = new SolidColorBrush(Colors.Gray);
+            button.BorderThickness = new Thickness(0.75);
+            button.Margin = new Thickness(2, 0, 0, 0);
+            button.Width = 25D;
+            button.Padding = new Thickness(2);
+            button.ToolTip = type.EntityName().Split('.').Last().SplitCamelCase();
+            button.Click += TypeFilterClick;
+            button.Tag = type;
+            TypeStack.Children.Add(button);
+        }
+    }
+
+    private void TypeFilterClick(object sender, RoutedEventArgs e)
+    {
+        var button = (sender as Button)!;
+        var type = (button.Tag as Type)!;
+        if (Items.ExcludedTypes.Contains(type))
+            Items.ExcludedTypes.Remove(type);
+        else
+            Items.ExcludedTypes.Add(type);
+        button.Background = Items.ExcludedTypes.Contains(type)
+            ? DisabledBrush
+            : EnabledBrush;
+        Refresh();
+    }
+    
+    private void RefreshButton_OnClick(object sender, RoutedEventArgs e)
+    {
+        Refresh();
+    }
+    
+    public void Setup()
+    {
+        Items.Refresh(true, false);           
+    }
+    
+    public void Refresh()
+    {
+        Items.Refresh(false, true);
+    }
+
+    private void AgeCombo_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
+    {
+        var startdate = AgeCombo.SelectedIndex == 2
+            ? DateTime.Today.AddDays(-30)
+            : AgeCombo.SelectedIndex == 1
+                ? DateTime.Today.AddDays(-7)
+                : DateTime.Today;
+        if ((Items != null) && (startdate != Items.StartDate))
+        {
+            Items.StartDate = startdate;
+            Refresh();
+        }
+    }
+}

+ 0 - 710
prs.desktop/DockPanels/DigitalFormsDock.xaml.cs

@@ -1,710 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using System.Windows.Threading;
-using Comal.Classes;
-using InABox.Clients;
-using InABox.Core;
-using InABox.DynamicGrid;
-using InABox.Reports;
-using InABox.Core.Reports;
-using InABox.Scripting;
-using InABox.Wpf.Reports;
-using InABox.WPF;
-using PRSDesktop.Configuration;
-using System.Diagnostics;
-using System.Threading.Tasks;
-
-namespace PRSDesktop
-{
-    
-    public class DigitalFormDockModel : BaseObject
-    {
-        [NullEditor]
-        public Guid ID { get; set; }
-        
-        [NullEditor]
-        public Guid FormID { get; set; }
-        
-        [CodeEditor(Visible = Visible.Default, Width=80)]
-        [EditorSequence(1)]
-        public string Number { get; set; }
-
-        [TextBoxEditor(Visible = Visible.Default)]
-        [EditorSequence(2)]
-        public string FormName { get; set; }
-        
-        [DateTimeEditor(Visible=Visible.Default, Width=100, Format = "dd MMM yy HH:mm")]
-        [EditorSequence(3)]
-        public DateTime Completed { get; set; }
-        
-        [TextBoxEditor(Visible=Visible.Default, Width=80, Alignment = Alignment.MiddleCenter)]
-        [EditorSequence(4)]
-        public string CompletedBy { get; set; }
-
-        [NullEditor]
-        public Type FormType { get; set; }
-        
-        [NullEditor]
-        public DateTime Processed { get; set; }
-
-        [NullEditor]
-        public Guid ParentID { get; set; }
-    }
-    
-    public class DigitalFormDockGrid : DynamicGrid<DigitalFormDockModel>
-    {
-        private CoreFieldMap<IDigitalFormInstance, DigitalFormDockModel> _mappings;
-        private IEnumerable<Type> _types;
-        private MultiQuery _query;
-        private CoreTable _data;
-
-        public Dictionary<Type, System.Drawing.Bitmap> Images { get; private set; }
-        public List<Type> ExcludedTypes { get; private set; }
-        public DateTime StartDate { get; set; }
-        
-        public DigitalFormDockGrid() : base()
-        {
-
-            StartDate = DateTime.Today;
-            ExcludedTypes = new List<Type>();
-
-            Images = new Dictionary<Type, System.Drawing.Bitmap>()
-            {
-                { typeof(AssignmentForm), PRSDesktop.Resources.assignments },
-                { typeof(KanbanForm), PRSDesktop.Resources.kanban },
-                { typeof(JobForm), PRSDesktop.Resources.project },
-                { typeof(JobITPForm), PRSDesktop.Resources.checklist },
-                { typeof(EmployeeForm), PRSDesktop.Resources.employees },
-                { typeof(LeaveRequestForm), PRSDesktop.Resources.leave },
-                { typeof(ManufacturingPacketStage), PRSDesktop.Resources.factory },
-                { typeof(TimeSheetForm), PRSDesktop.Resources.time },
-                { typeof(PurchaseOrderItemForm), PRSDesktop.Resources.purchase }
-            };
-
-            ActionColumns.Add(new DynamicImageColumn(TypeImage)
-            {
-                Position = DynamicActionColumnPosition.Start,
-                Filters = Images.Keys.Select(x=>x.EntityName().Split('.').Last().SplitCamelCase()).ToArray(),
-                FilterRecord = TypeFilter
-            });
-
-            ActionColumns.Add(new DynamicMenuColumn(MenuBuild, MenuStatus) { Position = DynamicActionColumnPosition.End });
-
-            _mappings = new CoreFieldMap<IDigitalFormInstance, DigitalFormDockModel>()
-                .Add(x => x.ID, x => x.ID)
-                .Add(x => x.Form.ID, x => x.FormID)
-                .Add(x => x.Number, x => x.Number)
-                .Add(x => x.Form.Description, x => x.FormName)
-                .Add(x => x.FormCompleted, x => x.Completed)
-                .Add(x => x.FormCompletedBy.UserID, x => x.CompletedBy)
-                .Add(x => x.FormProcessed, x => x.Processed)
-                .Add(x => x.Parent.ID, x => x.ParentID);
-            
-            _query = new MultiQuery();
-            
-            _types = CoreUtils.TypeList(e =>
-                e.IsSubclassOf(typeof(Entity))
-                && e.GetInterfaces().Contains(typeof(IRemotable))
-                && e.GetInterfaces().Contains(typeof(IPersistent))
-                && e.GetInterfaces().Contains(typeof(IDigitalFormInstance))
-            );
-            
-            _data = new CoreTable();
-            _data.LoadColumns(typeof(DigitalFormDockModel));
-
-        }
-
-        protected override void Init()
-        {
-        }
-
-        protected override void DoReconfigure(FluentList<DynamicGridOption> options)
-        {
-            options.BeginUpdate();
-            options.Clear();
-            options.Add(DynamicGridOption.FilterRows);
-            options.EndUpdate();
-        }
-
-        protected override DynamicGridStyle GetRowStyle(CoreRow row, DynamicGridStyle style)
-        {
-            var result = base.GetRowStyle(row, style);
-            if (!row.Get<DigitalFormDockModel, DateTime>(x => x.Processed).IsEmpty())
-                result = new DynamicGridRowStyle(result)
-                {
-                    Background = new SolidColorBrush(Colors.LightGray),
-                };
-            return result;
-        }
-
-        private bool TypeFilter(CoreRow row, string[] filter)
-        {
-            string typename = row.Get<DigitalFormDockModel, Type>(x => x.FormType).EntityName().Split('.').Last().SplitCamelCase();
-            return filter.Contains(typename);
-        }
-
-        private BitmapImage? TypeImage(CoreRow? arg)
-        {
-            if (arg == null)
-                return null;
-            var type = arg.Get<DigitalFormDockModel, Type>(x => x.FormType);
-            return Images.GetValueOrDefault(type)?.AsBitmapImage();
-        }
-
-        private void MenuBuild(DynamicMenuColumn column, CoreRow? row)
-        {
-            if (row == null) return;
-
-            var instanceID = row.Get<DigitalFormDockModel, Guid>(x => x.ID);
-            var formID = row.Get<DigitalFormDockModel, Guid>(x => x.FormID);
-            var formType = row.Get<DigitalFormDockModel, Type>(x => x.FormType);
-            var filter = Filter.Create<IDigitalFormInstance>(formType, x => x.ID).IsEqualTo(instanceID);
-            var model = (Activator.CreateInstance(typeof(DigitalFormReportDataModel<>).MakeGenericType(formType), filter, formID) as DataModel)!;
-
-            var modules = new Client<CustomModule>().Query(
-                new Filter<CustomModule>(x => x.DataModel).IsEqualTo(model.Name)
-                    .And(x => x.Section).IsEqualTo(formID.ToString())
-                    .And(x => x.Visible).IsEqualTo(true)
-                ).Rows.Select(x => x.ToObject<CustomModule>());
-            foreach(var module in modules)
-            {
-                column.AddItem(
-                    module.Name, 
-                    PRSDesktop.Resources.edit, 
-                    row =>
-                    {
-                        try
-                        {
-                            if(ScriptDocument.RunCustomModule(model, new Dictionary<string, object[]>(), module.Script))
-                            {
-                                Refresh(false, true);
-                            }
-                        }
-                        catch(CompileException c)
-                        {
-                            MessageBox.Show(c.Message);
-                        }
-                        catch(Exception e)
-                        {
-                            MessageBox.Show(CoreUtils.FormatException(e));
-                        }
-                    },
-                    null,
-                    row.Get<DigitalFormDockModel, DateTime>(x => x.Processed).IsEmpty()
-                );
-            }
-
-            if (modules.Any())
-                column.AddSeparator();
-            
-            if (row.Get<DigitalFormDockModel, DateTime>(x => x.Processed).IsEmpty())
-            {
-                column.AddItem(
-                    "Mark As Processed",
-                    PRSDesktop.Resources.lock_sml,
-                    (row) =>
-                    {
-                        var form = (Activator.CreateInstance(formType) as IDigitalFormInstance)!;
-                        form.ID = row!.Get<DigitalFormDockModel, Guid>(x => x.ID);
-                        form.FormProcessed = row.Get<DigitalFormDockModel, DateTime>(x => x.Processed);
-                        form.CommitChanges();
-                        form.FormProcessed = DateTime.Now;
-                        using (new WaitCursor())
-                        {
-                            ClientFactory.CreateClient(formType).Save(form, "Marked As Processed");
-                            Refresh(false, true);
-                        }
-                    }
-                );
-            }
-            else
-            {
-                column.AddItem(
-                    "Clear Processed Flag",
-                    PRSDesktop.Resources.lock_sml,
-                    (row) =>
-                    {
-                        var form = (Activator.CreateInstance(formType) as IDigitalFormInstance)!;
-                        form.ID = row!.Get<DigitalFormDockModel, Guid>(x => x.ID);
-                        form.FormProcessed = row.Get<DigitalFormDockModel, DateTime>(x => x.Processed);
-                        form.CommitChanges();
-                        form.FormProcessed = DateTime.MinValue;
-                        using (new WaitCursor())
-                        {
-                            ClientFactory.CreateClient(formType).Save(form, "Processed Flag Cleared");
-                            Refresh(false, true);
-                        }
-                    }
-                );
-            }
-
-            var entityType = DFUtils.FormEntityType(formType);
-            if(entityType.HasInterface<IJobScopedItem>())
-            {
-                var entityID = row.Get<DigitalFormDockModel, Guid>(x => x.ParentID);
-                column.AddSeparator();
-                column.AddItem("Set Job", PRSDesktop.Resources.project, (row) =>
-                {
-                    var entity = (Client.Create(entityType)
-                        .Query(
-                            Filter.Create<Entity>(entityType, x => x.ID).IsEqualTo(entityID),
-                            LookupFactory.DefineFilterColumns(typeof(Job), entityType)
-                                .Add<Entity>(x => x.ID)
-                                .Add<IJobScopedItem>(x => x.JobLink.ID)
-                                .Add<IJobScopedItem>(x => x.JobLink.JobNumber))
-                        .ToObjects(entityType).First() as Entity)!;
-                    var item = (entity as IJobScopedItem)!;
-
-                    var arr = Array.CreateInstance(entityType, 1);
-                    arr.SetValue(entity, 0);
-                    var window = new MultiSelectDialog<Job>(
-                        LookupFactory.DefineFilter<Job>(entityType, arr),
-                        new Columns<Job>(x => x.DefaultScope.ID).Add(x => x.JobNumber),
-                        multiselect: false);
-                    if (!window.ShowDialog(nameof(Job.JobNumber), item.JobLink.JobNumber, Syncfusion.Data.FilterType.Equals))
-                    {
-                        return;
-                    }
-                    var job = window.Data().ToObjects<Job>().First();
-                    item.JobLink.ID = job.ID;
-                    item.JobLink.Synchronise(job);
-
-                    Client.Create(entityType).Save(entity, "Linked job set by user from Digital forms dock.");
-                    MessageBox.Show($"{entityType.Name} has been assigned to job {job.JobNumber}.");
-                });
-                column.AddItem("Set Job Scope", PRSDesktop.Resources.project, (row) =>
-                {
-                    var entity = (Client.Create(entityType)
-                        .Query(
-                            Filter.Create<Entity>(entityType, x => x.ID).IsEqualTo(entityID),
-                            LookupFactory.DefineFilterColumns(typeof(JobScope), entityType)
-                                .Add<Entity>(x => x.ID)
-                                .Add<IJobScopedItem>(x => x.JobLink.ID)
-                                .Add<IJobScopedItem>(x => x.JobScope.ID)
-                                .Add<IJobScopedItem>(x => x.JobScope.Number))
-                        .ToObjects(entityType).First() as Entity)!;
-                    var item = (entity as IJobScopedItem)!;
-                    if(item.JobLink.ID == Guid.Empty)
-                    {
-                        MessageBox.Show($"{entityType.Name} is not linked to a job. Please select a job first.");
-                        return;
-                    }
-
-                    var arr = Array.CreateInstance(entityType, 1);
-                    arr.SetValue(entity, 0);
-                    var window = new MultiSelectDialog<JobScope>(
-                        new Filters<JobScope>()
-                            .Add(LookupFactory.DefineFilter<JobScope>(entityType, arr))
-                            .Add(new Filter<JobScope>(x => x.Job.ID).IsEqualTo(item.JobLink.ID))
-                            .Combine(),
-                        new Columns<JobScope>(x => x.ID).Add(x => x.Number),
-                        multiselect: false);
-                    if (!window.ShowDialog(nameof(JobScope.Number), item.JobScope.Number, Syncfusion.Data.FilterType.Equals))
-                    {
-                        return;
-                    }
-                    var scope = window.Data().ToObjects<JobScope>().First();
-                    item.JobScope.ID = scope.ID;
-
-                    Client.Create(entityType).Save(entity, "Linked scope set by user from Digital forms dock.");
-                    MessageBox.Show($"{entityType.Name} has been assigned to scope {scope.Number}.");
-                });
-            }
-            else if(entityType == typeof(Job) && formType == typeof(JobForm))
-            {
-                column.AddSeparator();
-                column.AddItem("Set Job Scope", PRSDesktop.Resources.project, (row) =>
-                {
-                    var instance = Client.Query(
-                        new Filter<JobForm>(x => x.ID).IsEqualTo(instanceID),
-                        LookupFactory.DefineFilterColumns<JobForm, JobScope>()
-                            .Add(x => x.ID)
-                            .Add(x => x.Parent.ID))
-                        .ToObjects<JobForm>().First();
-                    var job = Client.Query(
-                        new Filter<Job>(x => x.ID).IsEqualTo(instance.Parent.ID),
-                        LookupFactory.DefineFilterColumns<Job, JobScope>()).ToObjects<Job>().First();
-
-                    var window = new MultiSelectDialog<JobScope>(
-                        new Filters<JobScope>()
-                            .Add(LookupFactory.DefineFilter<Job, JobScope>(new Job[] { job }))
-                            .Add(new Filter<JobScope>(x => x.Job.ID).IsEqualTo(instance.Parent.ID))
-                            .Combine(),
-                        new Columns<JobScope>(x => x.ID).Add(x => x.Number), multiselect: false);
-                    if (!window.ShowDialog())
-                    {
-                        return;
-                    }
-                    var scope = window.Data().ToObjects<JobScope>().First();
-                    instance.JobScope.ID = scope.ID;
-
-                    Client.Save(instance, "Linked scope set by user from Digital forms dock.");
-                    MessageBox.Show($"Form has been assigned to scope {scope.Number}.");
-                });
-            }
-
-
-            if (Security.IsAllowed<CanCustomiseModules>())
-            {
-                column.AddSeparator();
-                column.AddItem("Customise Modules", PRSDesktop.Resources.script, row =>
-                {
-                    var manager = new CustomModuleManager
-                    {
-                        Section = formID.ToString(),
-                        DataModel = model
-                    };
-                    manager.ShowDialog();
-                });
-            }
-
-            if (Security.IsAllowed<CanPrintReports>())
-            {
-                column.AddSeparator();
-                var printItem = column.AddItem("Print", PRSDesktop.Resources.printer, null);
-
-                ReportUtils.PopulateMenu(printItem, formID.ToString(), model, Security.IsAllowed<CanDesignReports>(), true);
-            }
-        }
-
-        /*private void AttachToScope(CoreRow row, Type formType, DataModel model)
-        {
-            var instance = Client.Create(formType)
-                .Query(
-                    Filter.Create<IDigitalFormInstance>(formType, x => x.ID).IsEqualTo(row.Get<DigitalFormDockModel, Guid>(x => x.ID)),
-                    Columns.Create<IDigitalFormInstance>(formType)
-                        .Add<IDigitalFormInstance>(x => x.ID)
-                        .Add<IDigitalFormInstance>(x => x.Number))
-                .Rows.FirstOrDefault()
-                ?.ToObject(formType) as IDigitalFormInstance;
-            if (instance is null)
-            {
-                MessageBox.Show("Form does not exist!");
-                return;
-            }
-            var formID = row.Get<DigitalFormDockModel, Guid>(x => x.FormID);
-
-            var entityColumns = JobScopeForms.GetEntityColumns(formType)?.Add<Entity>(x => x.ID);
-            var entityObj = Client.Create(instance.ParentType())
-                .Query(
-                    Filter.Create<Entity>(instance.ParentType(), x => x.ID).IsEqualTo(row.Get<DigitalFormDockModel, Guid>(x => x.ParentID)),
-                    entityColumns).ToObjects(instance.ParentType()).FirstOrDefault();
-            if (entityObj is not Entity entity)
-            {
-                MessageBox.Show($"Attached {instance.ParentType().Name} does not exist!");
-                return;
-            }
-
-            #region Selecting Scope
-
-            // JobScopes obviously can't have an empty ID, so it's fine that I turn null into Guid.Empty here.
-            var scopeID = JobScopeForms.GetJobScopeID(formType, instance, entity) ?? Guid.Empty;
-            if(scopeID == Guid.Empty)
-            {
-                // JobScopes can't have an empty job, so it's fine that I turn null into Guid.Empty here.
-                var jobID = JobScopeForms.GetJobID(formType, instance, entity) ?? Guid.Empty;
-                if(jobID == Guid.Empty)
-                {
-                    var jobs = new MultiSelectDialog<Job>(null, null, multiselect: false);
-                    if(!jobs.ShowDialog("Select a job"))
-                    {
-                        MessageBox.Show("No job selected; process cancelled.");
-                        return;
-                    }
-                    jobID = jobs.IDs().First();
-                }
-
-                var scopes = new MultiSelectDialog<JobScope>(new Filter<JobScope>(x => x.Job.ID).IsEqualTo(jobID), null, multiselect: false);
-                if (!scopes.ShowDialog("Select a scope"))
-                {
-                    MessageBox.Show("Process cancelled.");
-                    return;
-                }
-                scopeID = scopes.IDs().First();
-            }
-
-            var scopeTask = Task.Run(() => Client.QueryMultiple(
-                new KeyedQueryDef<JobScope>(
-                    new Filter<JobScope>(x => x.ID).IsEqualTo(scopeID),
-                    new Columns<JobScope>(x => x.ID).Add(x => x.Number)),
-                new KeyedQueryDef<Job>(
-                    new Filter<Job>(x => x.ID).InQuery(new Filter<JobScope>(x => x.ID).IsEqualTo(scopeID), x => x.Job.ID),
-                    new Columns<Job>(x => x.JobNumber))));
-
-            #endregion
-
-            #region Selecting Report
-
-            var reports = new List<ReportTemplate>();
-            Progress.ShowModal("Loading Document", (progress) =>
-            {
-                reports = ReportUtils.LoadReports(formID.ToString(), model).ToList();
-            });
-
-            ReportTemplate report;
-            if (reports.Count == 0)
-            {
-                ReportTemplate reportTemplate = null!;
-                Progress.ShowModal("Loading form layout", (progress) =>
-                {
-                    progress.Report("Loading form layout");
-                    var layouts = Client.Query(
-                        new Filter<DigitalFormLayout>(x => x.Form.ID).IsEqualTo(formID),
-                        new Columns<DigitalFormLayout>(x => x.Layout)
-                            .Add(x => x.Code)
-                            .Add(x => x.Type)
-                            .Add(x => x.Description))
-                        .ToObjects<DigitalFormLayout>().ToArray();
-                    var layout = layouts.FirstOrDefault(x => x.Type == DFLayoutType.Desktop) ?? layouts.FirstOrDefault();
-                    if (layout is null)
-                    {
-                        MessageBox.Show("This form has no report or layout, so it cannot be attached to a job scope!");
-                        return;
-                    }
-
-                    progress.Report("Generating report");
-
-                    report = new ReportTemplate
-                    {
-                        DataModel = model.Name,
-                        Name = layout.Description.NotWhiteSpaceOr(layout.Code),
-                        RDL = DigitalFormUtils.GenerateReport(layout, model)?.SaveToString()
-                    };
-                });
-                report = reportTemplate;
-            }
-            else if (reports.Count == 1)
-            {
-                report = reports[0];
-            }
-            else
-            {
-                var selectedReports = new MultiSelectDialog<ReportTemplate>(
-                    ReportUtils.GetReportFilter(formID.ToString(), model),
-                    null,
-                    multiselect: false);
-                if (!selectedReports.ShowDialog())
-                {
-                    return;
-                }
-                report = selectedReports.Items().First();
-            }
-
-            #endregion
-
-            Document document = null!;
-
-            Progress.ShowModal("Loading Document", (progress) =>
-            {
-                progress.Report("Generating Document");
-
-                var data = ReportUtils.ReportToPDF(report, model, true);
-
-                progress.Report("Saving Document");
-
-                document = new Document
-                {
-                    FileName = $"{instance.Number}: {report.Name}.pdf",
-                    Data = data,
-                    CRC = CoreUtils.CalculateCRC(data)
-                };
-                Client.Save(document, "");
-
-                progress.Report("Creating Job Scope Document");
-
-                var jobScopeDocument = new JobScopeDocument();
-                jobScopeDocument.DocumentLink.ID = document.ID;
-                jobScopeDocument.EntityLink.ID = scopeID;
-                Client.Save(jobScopeDocument, $"Generated from form: {instance.Number}");
-            });
-
-            scopeTask.Wait();
-            var scopeResults = scopeTask.Result;
-            var scope = scopeResults.GetObjects<JobScope>().First();
-            var job = scopeResults.GetObjects<Job>().First();
-
-            MessageBox.Show($"Attached as '{document.FileName}' to job scope {scope.Number} for job {job.JobNumber}");
-        }*/
-
-        private DynamicMenuStatus MenuStatus(CoreRow row)
-        {
-            if (row == null) return DynamicMenuStatus.Hidden;
-            return DynamicMenuStatus.Enabled;
-        }
-
-        protected override void Reload(Filters<DigitalFormDockModel> criteria, Columns<DigitalFormDockModel> columns, ref SortOrder<DigitalFormDockModel>? sort, Action<CoreTable, Exception?> action)
-        {
-            
-            _query.Clear();
-            foreach (var type in _types.Where(x => !ExcludedTypes.Contains(x)))
-            {
-
-                var filter = Filter.Create<IDigitalFormInstance>(type, x => x.FormCompleted).IsGreaterThanOrEqualTo(StartDate)
-                    .And<IDigitalFormInstance>(x=>x.FormCancelled).IsEqualTo(DateTime.MinValue);
-
-                var cols = Columns.Create<IDigitalFormInstance>(type)
-                    .Add<IDigitalFormInstance>(c => c.ID)
-                    .Add<IDigitalFormInstance>(c => c.Parent.ID)
-                    .Add<IDigitalFormInstance>(c => c.Form.ID)
-                    .Add<IDigitalFormInstance>(c => c.Number)
-                    .Add<IDigitalFormInstance>(c => c.Form.Description)
-                    .Add<IDigitalFormInstance>(c => c.FormCompleted)
-                    .Add<IDigitalFormInstance>(c => c.FormCompletedBy.UserID)
-                    .Add<IDigitalFormInstance>(c => c.FormProcessed);
-
-                var sorts = SortOrder.Create<IDigitalFormInstance>(type, x => x.FormCompleted, SortDirection.Descending);
-
-                _query.Add(
-                    new QueryDef(type)
-                    {
-                        Filter = filter,
-                        Columns = cols,
-                        SortOrder = sorts
-                    },
-                    type
-                );
-
-            }
-
-            _query.Query((q) =>
-            {
-                _data.Rows.Clear();
-                foreach (var type in _types.Where(x=>!ExcludedTypes.Contains(x)))
-                    _data.LoadFrom(
-                        _query.Get(type), 
-                        _mappings, 
-                        (r) => r.Set<DigitalFormDockModel,Type>(x=>x.FormType,type)
-                    );
-                action.Invoke(_data, null);
-            });
-        }
-
-        protected override DigitalFormDockModel LoadItem(CoreRow row)
-        {
-            return row.ToObject<DigitalFormDockModel>();
-        }
-
-        public override void SaveItem(DigitalFormDockModel item)
-        {
-            //throw new NotImplementedException();
-        }
-
-        protected override void DeleteItems(params CoreRow[] rows)
-        {
-            //throw new NotImplementedException();
-        }
-
-        protected override void DoDoubleClick(object sender)
-        {
-            base.DoDoubleClick(sender);
-
-            var row = SelectedRows.FirstOrDefault();
-            if (row is null)
-                return;
-
-            var instanceID = row.Get<DigitalFormDockModel, Guid>(x => x.ID);
-            var formID = row.Get<DigitalFormDockModel, Guid>(x => x.FormID);
-            var formType = row.Get<DigitalFormDockModel, Type>(x => x.FormType);
-            
-            var formInstance = Client.Create(formType)
-                .Query(
-                    Filter.Create<IDigitalFormInstance>(formType, x => x.ID).IsEqualTo(instanceID),
-                    DynamicFormEditWindow.FormColumns(formType))
-                .Rows.FirstOrDefault()
-                ?.ToObject(formType) as IDigitalFormInstance;
-
-            if(formInstance is null)
-            {
-                return;
-            }
-            if (formID == Guid.Empty)
-            {
-                var window = new DeletedFormWindow();
-                window.FormData = formInstance?.FormData ?? "";
-                window.ShowDialog();
-                return;
-            }
-            if (DynamicFormEditWindow.EditDigitalForm(formInstance, out var model))
-            {
-                model.Update(null);
-            }
-        }
-        
-    }
-    
-    public partial class DigitalFormsDock : UserControl, IDockPanel
-    {
-
-        private static SolidColorBrush EnabledBrush = new SolidColorBrush(Colors.LightYellow);
-        private static SolidColorBrush DisabledBrush = new SolidColorBrush(Colors.LightGray);
-
-        public DigitalFormsDock()
-        {
-            InitializeComponent();
-            RefreshButton.Content = new Image() { Source = PRSDesktop.Resources.refresh.AsBitmapImage() };
-            foreach (var type in Items.Images.Keys)
-            {
-                Button button = new Button();
-                button.Background = EnabledBrush;
-                button.Content = new Image() { Source = Items.Images[type].AsBitmapImage() };
-                button.BorderBrush = new SolidColorBrush(Colors.Gray);
-                button.BorderThickness = new Thickness(0.75);
-                button.Margin = new Thickness(2, 0, 0, 0);
-                button.Width = 25D;
-                button.Padding = new Thickness(2);
-                button.ToolTip = type.EntityName().Split('.').Last().SplitCamelCase();
-                button.Click += TypeFilterClick;
-                button.Tag = type;
-                TypeStack.Children.Add(button);
-            }
-        }
-
-        private void TypeFilterClick(object sender, RoutedEventArgs e)
-        {
-            var button = (sender as Button)!;
-            var type = (button.Tag as Type)!;
-            if (Items.ExcludedTypes.Contains(type))
-                Items.ExcludedTypes.Remove(type);
-            else
-                Items.ExcludedTypes.Add(type);
-            button.Background = Items.ExcludedTypes.Contains(type)
-                ? DisabledBrush
-                : EnabledBrush;
-            Refresh();
-        }
-        
-        private void RefreshButton_OnClick(object sender, RoutedEventArgs e)
-        {
-            Refresh();
-        }
-        
-        public void Setup()
-        {
-            Items.Refresh(true, false);           
-        }
-        
-        public void Refresh()
-        {
-            Items.Refresh(false, true);
-        }
-
-        private void AgeCombo_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
-        {
-            var startdate = AgeCombo.SelectedIndex == 2
-                ? DateTime.Today.AddDays(-30)
-                : AgeCombo.SelectedIndex == 1
-                    ? DateTime.Today.AddDays(-7)
-                    : DateTime.Today;
-            if ((Items != null) && (startdate != Items.StartDate))
-            {
-                Items.StartDate = startdate;
-                Refresh();
-            }
-        }
-    }
-}

+ 5 - 6
prs.desktop/DockPanels/IDockPanel.cs

@@ -1,8 +1,7 @@
-namespace PRSDesktop
+namespace PRSDesktop;
+
+public interface IDockPanel
 {
-    public interface IDockPanel
-    {
-        void Refresh();
-        void Setup();
-    }
+    void Setup();
+    void Refresh();
 }

+ 3 - 3
prs.desktop/MainWindow.xaml

@@ -1164,9 +1164,9 @@
                                 CanDockAsTabbedDocument="False"
                                 AutoHideWidth="500"
                                 FloatingWidth="500"
-                                AutoHideMinWidth="400"
-                                IsActiveChanged="DockPanel_OnIsActiveChanged">
-                                <Border BorderThickness="0.75" BorderBrush="Gray">
+                                AutoHideMinWidth="400">
+                                <Border BorderThickness="0.75" BorderBrush="Gray"
+                                        IsVisibleChanged="DockPanelBorder_IsVisibleChanged">
                                     <local:DigitalFormsDock x:Name="DigitalForms" />
                                 </Border>
                             </LayoutAnchorable>

+ 45 - 66
prs.desktop/MainWindow.xaml.cs

@@ -1984,6 +1984,31 @@ namespace PRSDesktop
                 r.Get<TimeSheet, DateTime>(c => c.Date).Date == DateTime.Today && r.Get<TimeSheet, TimeSpan>(c => c.Finish) == new TimeSpan());
         }
 
+        private void FinalizeAutoTimesheet()
+        {
+            if (Security.IsAllowed<AutoGenerateTimesheet>())
+            {
+                var check = new Client<TimeSheet>().Query(
+                    new Filter<TimeSheet>(x => x.Date).IsEqualTo(DateTime.Today).And(x => x.EmployeeLink.ID).IsEqualTo(App.EmployeeID).And(x => x.Finish)
+                        .IsEqualTo(TimeSpan.Zero)
+                );
+                if (check.Rows.Any())
+                {
+                    var ts = check.Rows.First().ToObject<TimeSheet>();
+                    if (DateTime.Now.TimeOfDay < ts.Start.Add(new TimeSpan(0, 2, 0)))
+                    {
+                        new Client<TimeSheet>().Delete(ts, "Deleting Auto TimeSheet because TimeBench Bypass is enabled", (o, ex) => { });
+                    }
+                    else
+                    {
+                        ts.Finish = DateTime.Now.TimeOfDay;
+                        new Client<TimeSheet>().Save(ts, "Clocking off Auto Timesheet because Timesheet Bypass is enabled", (o, ex) => { });
+                    }
+                }
+            }
+        }
+
+
         #endregion
 
         private string CurrentPanelSlug()
@@ -2119,6 +2144,8 @@ namespace PRSDesktop
             //}
         }
 
+        #region Visibility
+
         private void SetVisibility(FrameworkElement button, bool visible)
         {
             var vResult = true;
@@ -2162,6 +2189,8 @@ namespace PRSDesktop
             tab.Visibility = bVisible ? Visibility.Visible : Visibility.Collapsed;
         }
 
+        #endregion
+
         private Fluent.RibbonGroupBox? FindRibbonBar(Fluent.RibbonTabItem tab, Func<Fluent.RibbonGroupBox, bool> predicate)
         {
             foreach (var group in tab.Groups)
@@ -2174,27 +2203,6 @@ namespace PRSDesktop
             return null;
         }
 
-        //private Style tabselected = null;
-        //private Style tabunselected = null;
-
-        //private Style Getstyle(bool selected)
-        //{
-        //    if (tabunselected == null)
-        //    {
-        //        tabunselected = new Style(typeof(TabButton));
-        //        Setter st1 = new Setter(TabButton.BackgroundProperty, new SolidColorBrush(Colors.Transparent));
-        //        tabunselected.Setters.Add(st1);
-        //    }
-        //    if (tabselected == null)
-        //    {
-        //        tabselected = new Style(typeof(TabButton));
-        //        Setter st1 = new Setter(TabButton.BackgroundProperty, new SolidColorBrush(System.Windows.Media.Color.FromArgb(0xFF, 0x9E, 0xDA, 0x11)));
-        //        tabselected.Setters.Add(st1);
-        //    }
-
-        //    return selected ? tabselected : tabunselected;
-        //}
-
         #region Button Event Handlers
 
         #region Common
@@ -3205,31 +3213,6 @@ namespace PRSDesktop
 
         }
 
-        private void FinalizeAutoTimesheet()
-        {
-            if (Security.IsAllowed<AutoGenerateTimesheet>())
-            {
-                var check = new Client<TimeSheet>().Query(
-                    new Filter<TimeSheet>(x => x.Date).IsEqualTo(DateTime.Today).And(x => x.EmployeeLink.ID).IsEqualTo(App.EmployeeID).And(x => x.Finish)
-                        .IsEqualTo(TimeSpan.Zero)
-                );
-                if (check.Rows.Any())
-                {
-                    var ts = check.Rows.First().ToObject<TimeSheet>();
-                    if (DateTime.Now.TimeOfDay < ts.Start.Add(new TimeSpan(0, 2, 0)))
-                    {
-                        new Client<TimeSheet>().Delete(ts, "Deleting Auto TimeSheet because TimeBench Bypass is enabled", (o, ex) => { });
-                    }
-                    else
-                    {
-                        ts.Finish = DateTime.Now.TimeOfDay;
-                        new Client<TimeSheet>().Save(ts, "Clocking off Auto Timesheet because Timesheet Bypass is enabled", (o, ex) => { });
-                    }
-                }
-            }
-        }
-
-
 
         #region Notifications + Heartbeat
 
@@ -3866,23 +3849,6 @@ namespace PRSDesktop
         [DllImport("user32.dll")]
         private static extern IntPtr GetActiveWindow();
 
-
-        //private void ConvertProgressEvent(object sender, ConvertProgressEventArgs e)
-        //{
-        //    Dispatcher.Invoke(() =>
-        //    {
-        //        try
-        //        {
-        //            if (e.TotalDuration.TotalSeconds != 0.0F)
-        //                Progress.SetMessage(String.Format("Transcoding ({0:F2}% complete)", e.ProcessedDuration.TotalSeconds * 100.0F / e.TotalDuration.TotalSeconds));
-        //        }
-        //        catch (Exception err)
-        //        {
-
-        //        }
-        //    });
-        //}
-
         #region Check For Updates
 
         private string GetUpdateLocation()
@@ -4364,17 +4330,29 @@ namespace PRSDesktop
 
         #endregion
 
+        private void DockPanelBorder_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
+        {
+            if (sender is not Border border || !border.IsVisible)
+                return;
+
+            var dock = border.Child is IDockPanel panel ? panel : border.Child?.FindVisualChildren<IDockPanel>().FirstOrDefault();
+            if (dock is null)
+                return;
+
+            dock.Refresh();
+        }
+
         private void DockPanel_OnIsActiveChanged(object? sender, EventArgs e)
         {
-            var layout = sender as LayoutAnchorable;
-            if (layout == null)
+            if (sender is not LayoutAnchorable layout)
                 return;
 
             var content = layout.Content as DependencyObject;
 
             var dock = content is IDockPanel panel ? panel : content?.FindVisualChildren<IDockPanel>().FirstOrDefault();
-            if (dock == null)
+            if (dock is null)
                 return;
+
             if (layout.IsActive && layout.IsVisible)
                 dock.Refresh();
         }
@@ -4542,5 +4520,6 @@ namespace PRSDesktop
 
 
         #endregion
+
     }
 }

+ 2 - 2
prs.desktop/PRSDesktop.csproj

@@ -801,8 +801,8 @@
     <ItemGroup>
       <PackageReference Include="bblanchon.PDFium.Win32" Version="121.0.6150" />
       <PackageReference Include="DeviceId.Windows.Wmi" Version="6.2.0" />
-      <PackageReference Include="Dirkster.AvalonDock" Version="4.70.3" />
-      <PackageReference Include="Dirkster.AvalonDock.Themes.Metro" Version="4.70.3" />
+      <PackageReference Include="Dirkster.AvalonDock" Version="4.71.0" />
+      <PackageReference Include="Dirkster.AvalonDock.Themes.Metro" Version="4.71.0" />
       <PackageReference Include="Fluent.Ribbon" Version="9.0.4" />
       <PackageReference Include="Geocoding.Core" Version="4.0.1" />
       <PackageReference Include="Geocoding.Google" Version="4.0.1" />