Преглед изворни кода

Moved digital forms handling into their own panel, removing the QADashboard and digital forms manager from where they were.
Also added digital forms role cross tab.

Kenric Nugteren пре 1 година
родитељ
комит
e6a1872a66

+ 18 - 5
prs.classes/SecurityDescriptors/Desktop_Access.cs

@@ -274,6 +274,24 @@ namespace Comal.Classes.SecurityDescriptors
     }
     #endregion
 
+    #region Digital Forms
+
+    /// <summary>
+    /// Entire Tab Visibility
+    /// </summary>
+
+    [Caption("View Desktop DigitalForms Tab")]
+    public class ViewDesktopDigitalFormsTab : EnabledSecurityDescriptor<DesktopAccessLicence>
+    {
+    }
+
+    [Caption("View Desktop Completed Forms Screen")]
+    public class ViewDesktopDigitalFormsDashboard : EnabledSecurityDescriptor<DesktopAccessLicence>
+    {
+    }
+
+    #endregion
+
     #region Dashboards
     /// <summary>
     /// Entire Tab Visibility
@@ -308,11 +326,6 @@ namespace Comal.Classes.SecurityDescriptors
     { 
     }
 
-    [Caption("View Desktop Digital Forms Dashboard")]
-    public class ViewDesktopDigitalFormsDashboard : EnabledSecurityDescriptor<DesktopAccessLicence>
-    { 
-    }
-
     [Caption("View Desktop Quick Status Dashboard")]
     public class ViewDesktopQuickStatusDashboard : EnabledSecurityDescriptor<DesktopAccessLicence>
     { 

+ 37 - 11
prs.desktop/MainWindow.xaml

@@ -151,14 +151,6 @@
                             HorizontalAlignment="Stretch"
                             Foreground="{Binding Path=(themes:ThemeManager.BackstageForegroundBrush)}" />
 
-                        <fluent:Button
-                            x:Name="QAFormSetupButton"
-                            Header="Digital Forms"
-                            Click="QAFormSetupButton_OnClick"
-                            Size="Middle"
-                            HorizontalAlignment="Stretch"
-                            Foreground="{Binding Path=(themes:ThemeManager.BackstageForegroundBrush)}" />
-
                         <fluent:Button
                             x:Name="TaskTypesButton"
                             Header="Task Types"
@@ -820,6 +812,43 @@
 
             </fluent:RibbonTabItem>
 
+            <fluent:RibbonTabItem x:Name="DigitalFormsTab" Header="Digital Forms" IsSelected="False" Visibility="Collapsed">
+                <fluent:RibbonGroupBox x:Name="DigitalFormsActions" Width="Auto" Header="Actions"
+                                       LauncherClick="ManageModulesClick" IsLauncherVisible="False">
+
+                    <fluent:Button Header="Refresh"
+                                   LargeIcon="pack://application:,,,/Resources/refresh.png"
+                                   Click="RefreshMenu_Click" MinWidth="60" />
+                    <syncfusion:RibbonSeparator />
+                    <fluent:Button x:Name="DigitalFormsDashboardButton" Header="Dashboards"
+                                   LargeIcon="pack://application:,,,/Resources/kpi.png"
+                                   Click="Dashboards_Checked" MinWidth="60" />
+                    <fluent:Button x:Name="DigitalFormsMessagesButton" Size="Large"
+                                   Header="Notification Centre"
+                                   LargeIcon="pack://application:,,,/Resources/email.png"
+                                   Click="Messages_Checked" MinWidth="60" />
+                    <fluent:Button x:Name="DigitalFormsTaskButton" Header="Task List"
+                                   LargeIcon="pack://application:,,,/Resources/kanban.png"
+                                   Click="Tasks_Checked" MinWidth="60" />
+                    <fluent:Button x:Name="DigitalFormsAttendanceButton" Header="In/Out Board"
+                                   LargeIcon="pack://application:,,,/Resources/attendance.png"
+                                   Click="Attendance_Checked" MinWidth="60" />
+                    <fluent:Button x:Name="DigitalFormsMapButton" Header="Live Maps"
+                                   LargeIcon="pack://application:,,,/Resources/map.png" Click="Maps_Checked"
+                                   MinWidth="60" />
+                    <fluent:Button x:Name="DigitalFormsDailyReportButton" Header="Daily Report"
+                                   LargeIcon="pack://application:,,,/Resources/report.png"
+                                   Click="DailyReport_Checked" MinWidth="60" />
+                    <syncfusion:RibbonSeparator x:Name="DigitalFormsTaskSeparator" />
+                    <fluent:Button x:Name="DigitalFormsFormsLibraryButton" Header="Forms Library"
+                                   LargeIcon="pack://application:,,,/Resources/checklist.png"
+                                   Click="DigitalFormsFormsLibraryButton_Click" MinWidth="60" />
+                    <fluent:Button x:Name="DigitalFormsCompletedFormsButton" Header="Completed Forms"
+                                   LargeIcon="pack://application:,,,/Resources/checklist.png"
+                                   Click="DigitalFormsCompletedFormsButton_Click" MinWidth="60" />
+                </fluent:RibbonGroupBox>
+            </fluent:RibbonTabItem>
+
             <fluent:RibbonTabItem x:Name="DashboardsTab" Header="Dashboards" IsSelected="False" Visibility="Collapsed">
 
                 <fluent:RibbonGroupBox x:Name="DashboardsActions" Header="Actions" LauncherClick="ManageModulesClick"
@@ -864,9 +893,6 @@
                     <fluent:Button x:Name="UserActivityButton" Header="User Activity"
                                    LargeIcon="pack://application:,,,/Resources/kpi.png"
                                    Click="UserActivityButton_Click" MinWidth="60" />
-                    <fluent:Button x:Name="DigitalFormsButton" Header="Digital Forms"
-                                   LargeIcon="pack://application:,,,/Resources/kpi.png"
-                                   Click="DigitalFormsButton_Click" MinWidth="60" />
                     <fluent:Button x:Name="QuickStatusButton" Header="Quick Status"
                                    LargeIcon="pack://application:,,,/Resources/kpi.png"
                                    Click="QuickStatus_Click" MinWidth="60" />

+ 135 - 61
prs.desktop/MainWindow.xaml.cs

@@ -699,6 +699,7 @@ namespace PRSDesktop
                 new ProgressSection("Configuring Human Resources", () => SetupHumanResourcesTab(bMaps)),
                 new ProgressSection("Configuring Accounts", () => SetupAccountsTab(bMaps)),
                 new ProgressSection("Configuring Equipment", () => SetupEquipmentTab(bMaps)),
+                new ProgressSection("Configuring DigitalForms", () => SetupDigitalFormsTab(bMaps)),
                 new ProgressSection("Configuring Dashboards", () => SetupDashboardsTab(bMaps)),
                 new ProgressSection("Configuring System Modules", SetupSystemModules)
             );
@@ -760,9 +761,7 @@ namespace PRSDesktop
 
             SetVisibility(DocumentTypeList, ClientFactory.IsSupported<DocumentType>() && Security.IsAllowed<CanViewDocumentTypes>());
             SetVisibility(DocumentList, ClientFactory.IsSupported<Document>() && Security.IsAllowed<CanViewDocumentList>());
-            SetVisibility(QAFormSetupButton,
-                ClientFactory.IsSupported<DigitalForm>() && Security.IsAllowed<CanAdministerDigitalFormsLibrary>());
-            SetVisibleIfAny(BackstageSeparator2, DocumentTypeList, DocumentList, QAFormSetupButton);
+            SetVisibleIfAny(BackstageSeparator2, DocumentTypeList, DocumentList);
 
             SetVisibility(VideoRecordingButton, Security.IsAllowed<CanRecordScreen>());
 
@@ -819,7 +818,6 @@ namespace PRSDesktop
                 && Security.IsAllowed<CanViewUserActivity>()
                 && Security.IsAllowed<ViewDesktopUserActivityDashboard>());
 
-            SetVisibility(DigitalFormsButton, Security.IsAllowed<CanViewDigitalFormsDashbaord>() && Security.IsAllowed<ViewDesktopDigitalFormsDashboard>());
             SetVisibility(QuickStatusButton, Security.IsAllowed<CanViewQuickStatus>() && Security.IsAllowed<ViewDesktopQuickStatusDashboard>());
 
             SetVisibleIfEither(DashboardsTaskSeparator,
@@ -831,12 +829,11 @@ namespace PRSDesktop
                 },
                 new FrameworkElement[]
                 {
-                                FactoryProductivityButton, TemplateAnalysisButton, FactoryAnalysisButton, DatabaseActivityButton, UserActivityButton,
-                                DigitalFormsButton, QuickStatusButton
+                                FactoryProductivityButton, TemplateAnalysisButton, FactoryAnalysisButton, DatabaseActivityButton, UserActivityButton, QuickStatusButton
                 });
             SetVisibleIfAny(DashboardsActions, DashboardsDashboardButton, DashboardMessagesButton, DashboardsTaskButton,
                 DashboardsAttendanceButton, DashboardsDailyReportButton, FactoryProductivityButton, TemplateAnalysisButton,
-                FactoryAnalysisButton, DatabaseActivityButton, UserActivityButton, DigitalFormsButton, QuickStatusButton);
+                FactoryAnalysisButton, DatabaseActivityButton, UserActivityButton, QuickStatusButton);
 
             //DashboardsActions.IsLauncherButtonVisible = Security.IsAllowed<CanCustomiseModules>();
             //DashboardsReports.IsLauncherButtonVisible = Security.IsAllowed<CanDesignReports>();
@@ -845,8 +842,44 @@ namespace PRSDesktop
 
             SetVisibleIfAny(DashboardsTab, FactoryProductivityButton, TemplateAnalysisButton, FactoryAnalysisButton,
                 DatabaseActivityButton,
-                UserActivityButton, DigitalFormsButton, QuickStatusButton);
+                UserActivityButton, QuickStatusButton);
         }
+        private void SetupDigitalFormsTab(bool bMaps)
+        {
+            if (!Security.IsAllowed<ViewDesktopDigitalFormsTab>())
+                return;
+
+            SetVisibility(DigitalFormsDashboardButton, Security.IsAllowed<CanViewUserDefinedDashboards>());
+
+            SetVisibility(DigitalFormsMessagesButton, Security.CanView<Notification>());
+            SetVisibility(DigitalFormsTaskButton, ClientFactory.IsSupported<Kanban>() && Security.IsAllowed<CanViewTasks>());
+            SetVisibility(DigitalFormsAttendanceButton, ClientFactory.IsSupported<TimeSheet>() && Security.IsAllowed<CanViewInOutBoard>());
+            SetVisibility(DigitalFormsMapButton, bMaps);
+            SetVisibility(DigitalFormsDailyReportButton,
+                ClientFactory.IsSupported<TimeSheet, Assignment>() && Security.IsAllowed<CanViewDailyReports>());
+
+            SetVisibility(DigitalFormsFormsLibraryButton, ClientFactory.IsSupported<DigitalForm>()
+                && Security.CanView<DigitalForm>()
+                && Security.IsAllowed<CanAdministerDigitalFormsLibrary>());
+
+            SetVisibility(DigitalFormsCompletedFormsButton, Security.IsAllowed<CanViewDigitalFormsDashbaord>() && Security.IsAllowed<ViewDesktopDigitalFormsDashboard>());
+
+            SetVisibleIfEither(DigitalFormsTaskSeparator,
+                new FrameworkElement[]
+                {
+                    DigitalFormsDashboardButton, DigitalFormsMessagesButton, DigitalFormsTaskButton, DigitalFormsAttendanceButton, DigitalFormsMapButton,
+                    DigitalFormsDailyReportButton
+                }, new FrameworkElement[] { DigitalFormsFormsLibraryButton, DigitalFormsCompletedFormsButton });
+
+            SetVisibleIfAny(DigitalFormsActions, DigitalFormsDashboardButton, DigitalFormsMessagesButton, DigitalFormsTaskButton,
+                DigitalFormsAttendanceButton, DigitalFormsDailyReportButton, DigitalFormsFormsLibraryButton, DigitalFormsCompletedFormsButton);
+
+            AddSetupModulesAndReports(DigitalFormsTab);
+
+            SetTabVisibleIfAny(DigitalFormsTab, DigitalFormsFormsLibraryButton, DigitalFormsCompletedFormsButton);
+
+        }
+
 
         private void SetupEquipmentTab(bool bMaps)
         {
@@ -2162,27 +2195,38 @@ namespace PRSDesktop
 
         #region Button Event Handlers
 
+        #region Common
+
+
+        private void Tasks_Checked(object sender, RoutedEventArgs e)
+        {
+            //LoadWindow<KanbanPanel>((RibbonButton)sender);
+            LoadWindow<TaskPanel>((Fluent.Button)sender);
+        }
+
         private void DataEntry_Click(object sender, RoutedEventArgs e)
         {
             LoadWindow<DataEntryPanel>((Fluent.Button)sender);
         }
 
-        private void Console_Click(object sender, RoutedEventArgs a)
+        private void Attendance_Checked(object sender, RoutedEventArgs e)
         {
-            if (_console is null)
-            {
-                _console = new Console();
-                _console.Closing += (o, args) => _console = null;
-            }
-
-            _console.Show();
+            LoadWindow<AttendancePanel>((Fluent.Button)sender);
         }
 
+        #endregion
+
+        #region Quotes
+
         private void Quotes_Checked(object sender, RoutedEventArgs e)
         {
             LoadWindow<QuotePanel>((Fluent.Button)sender);
         }
 
+        #endregion
+
+        #region Projects
+
         private void Jobs_Checked(object sender, RoutedEventArgs e)
         {
             LoadWindow<JobPanel>((Fluent.Button)sender);
@@ -2204,40 +2248,44 @@ namespace PRSDesktop
             //LoadWindow<JobPanel>((RibbonButton)sender);
         }
 
-        private void Tasks_Checked(object sender, RoutedEventArgs e)
-        {
-            //LoadWindow<KanbanPanel>((RibbonButton)sender);
-            LoadWindow<TaskPanel>((Fluent.Button)sender);
-        }
+        #endregion
+
+        #region Manufacturing
 
         private void ManufacturingMenu_Checked(object sender, RoutedEventArgs e)
         {
             LoadWindow<ManufacturingPanel>((Fluent.Button)sender);
         }
 
-        // private void Schedule_Checked(object sender, RoutedEventArgs e)
-        // {
-        //     LoadWindow<SchedulePanel>((RibbonButton)sender);
-        // }
-
         private void FactoryFloorButton_Click(object sender, RoutedEventArgs e)
         {
             LoadWindow<FactoryPanel>((Fluent.Button)sender);
         }
 
-        private void DispatchMenu_Checked(object sender, RoutedEventArgs e)
+        private void FactorySetup_Click()
         {
-            LoadWindow<DispatchPanel>((Fluent.Button)sender);
+            var list = new MasterList(typeof(ManufacturingFactory));
+            list.ShowDialog();
         }
 
+        private void TemplateSetup_Click()
+        {
+            var list = new MasterList(typeof(ManufacturingTemplate), "Factory.Name", null, true);
+            list.ShowDialog();
+        }
+
+        #endregion
+
+        #region Logistics
+
         private void ReadyToGoMenu_Checked(object sender, RoutedEventArgs e)
         {
             LoadWindow<ReadyToGoPanel>((Fluent.Button)sender);
         }
 
-        private void Equipment_Checked(object sender, RoutedEventArgs e)
+        private void DispatchMenu_Checked(object sender, RoutedEventArgs e)
         {
-            LoadWindow<EquipmentPanel>((Fluent.Button)sender);
+            LoadWindow<DispatchPanel>((Fluent.Button)sender);
         }
 
         private void Requisitions_Checked(object sender, RoutedEventArgs e)
@@ -2245,36 +2293,57 @@ namespace PRSDesktop
             LoadWindow<RequisitionPanel>((Fluent.Button)sender);
         }
 
-        //private void Purchases_Checked(object sender, RoutedEventArgs e)
-        //{
-        //    LoadWindow(PurchasesMenu, new PurchasesPanel());
-        //}
+        #endregion
+
+        #region Products
+
+        #endregion
+
+        #region Human Resources
 
         private void Timesheets_Checked(object sender, RoutedEventArgs e)
         {
             LoadWindow<TimesheetPanel>((Fluent.Button)sender);
         }
 
-        private void Attendance_Checked(object sender, RoutedEventArgs e)
-        {
-            LoadWindow<AttendancePanel>((Fluent.Button)sender);
-        }
+        #endregion
 
-        private void Maps_Checked(object sender, RoutedEventArgs e)
+        #region Accounts
+
+        #endregion
+
+        #region Equipment
+
+        private void Equipment_Checked(object sender, RoutedEventArgs e)
         {
-            LoadWindow<MapsPanel>((Fluent.Button)sender);
+            LoadWindow<EquipmentPanel>((Fluent.Button)sender);
         }
 
-        private void FactorySetup_Click()
+        #endregion
+
+        #region Dashboards
+
+        #endregion
+
+        private void Console_Click(object sender, RoutedEventArgs a)
         {
-            var list = new MasterList(typeof(ManufacturingFactory));
-            list.ShowDialog();
+            if (_console is null)
+            {
+                _console = new Console();
+                _console.Closing += (o, args) => _console = null;
+            }
+
+            _console.Show();
         }
 
-        private void TemplateSetup_Click()
+        //private void Purchases_Checked(object sender, RoutedEventArgs e)
+        //{
+        //    LoadWindow(PurchasesMenu, new PurchasesPanel());
+        //}
+
+        private void Maps_Checked(object sender, RoutedEventArgs e)
         {
-            var list = new MasterList(typeof(ManufacturingTemplate), "Factory.Name", null, true);
-            list.ShowDialog();
+            LoadWindow<MapsPanel>((Fluent.Button)sender);
         }
 
         private void RefreshMenu_Click(object sender, RoutedEventArgs e)
@@ -2738,11 +2807,6 @@ namespace PRSDesktop
             LoadWindow<UserActivity>((Fluent.Button)sender);
         }
 
-        private void DigitalFormsButton_Click(object sender, RoutedEventArgs e)
-        {
-            LoadWindow<QADashboard>((Fluent.Button)sender);
-        }
-
         private void QuickStatus_Click(object sender, RoutedEventArgs e)
         {
             LoadWindow<WidgetDashboard>((Fluent.Button)sender);
@@ -2837,6 +2901,25 @@ namespace PRSDesktop
             list.ShowDialog();
         }
 
+        #region Digital Forms
+
+        private void DigitalFormsFormsLibraryButton_Click(object sender, RoutedEventArgs e)
+        {
+            LoadWindow<DigitalFormsLibrary>((Fluent.Button)sender);
+        }
+
+        private void DigitalFormsRoleButton_Click(object sender, RoutedEventArgs e)
+        {
+
+        }
+
+        private void DigitalFormsCompletedFormsButton_Click(object sender, RoutedEventArgs e)
+        {
+            LoadWindow<QADashboard>((Fluent.Button)sender);
+        }
+
+        #endregion
+
         private void Dashboards_Checked(object sender, RoutedEventArgs e)
         {
             LoadWindow<UtilityDashboard>((Fluent.Button)sender);
@@ -4396,14 +4479,6 @@ namespace PRSDesktop
             list.ShowDialog();
         }
 
-        private void QAFormSetupButton_OnClick(object sender, RoutedEventArgs e)
-        {
-            var list = new MasterList(typeof(DigitalForm), "AppliesTo", null, true);
-            list.ShowDialog();
-        }
-
-
-
         private void EditDetailsButton_OnClick(object sender, RoutedEventArgs e)
         {
             var employee = new Client<Employee>().Query(
@@ -4470,8 +4545,7 @@ namespace PRSDesktop
         }
 
 
-        #endregion
-
 
+        #endregion
     }
 }

+ 60 - 0
prs.desktop/Panels/DigitalForms/DigitalFormRoleCrossTab.cs

@@ -0,0 +1,60 @@
+using Comal.Classes;
+using InABox.Configuration;
+using InABox.Core;
+using InABox.DynamicGrid;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PRSDesktop;
+
+public class DigitalFormRoleCrossTab : DynamicManyToManyCrossTab<RoleForm, DigitalForm, Role>
+{
+    public DynamicGridFilterButtonComponent<DigitalForm> DigitalFormFilter { get; init; }
+
+    public DynamicGridFilterButtonComponent<Role> RoleFilter { get; init; }
+
+    public DigitalFormRoleCrossTab()
+    {
+        DigitalFormFilter = new(this, new GlobalConfiguration<CoreFilterDefinitions>(nameof(DigitalForm))) { ButtonText = "Digital Forms" };
+        RoleFilter = new(this, new GlobalConfiguration<CoreFilterDefinitions>(nameof(Role))) { ButtonText = "Roles" };
+
+        DigitalFormFilter.OnFilterRefresh += () => Refresh(false, true);
+        RoleFilter.OnFilterRefresh += () => Refresh(true, true);
+    }
+
+    protected override Filter<DigitalForm>? RowFilter()
+    {
+        return DigitalFormFilter.GetFilter();
+    }
+
+    protected override Filter<Role>? ColumnFilter()
+    {
+        return RoleFilter.GetFilter();
+    }
+
+    protected override Columns<Role>? LoadColumnColumns()
+    {
+        return new Columns<Role>(x => x.Code);
+    }
+
+    protected override string FormatColumnHeader(CoreRow row)
+    {
+        return row.Get<Role, string>(x => x.Code);
+    }
+
+    protected override SortOrder<Role>? LoadColumnSort()
+    {
+        return null;
+    }
+
+    protected override DynamicGridColumns LoadRowColumns()
+    {
+        var columns = new DynamicGridColumns();
+        columns.Add<DigitalForm, string>(x => x.Code, 100, "Code", "", Alignment.MiddleLeft);
+        columns.Add<DigitalForm, string>(x => x.Description, 100, "Description", "", Alignment.MiddleLeft);
+        return columns;
+    }
+}

+ 60 - 0
prs.desktop/Panels/DigitalForms/DigitalFormsLibrary.xaml

@@ -0,0 +1,60 @@
+<UserControl x:Class="PRSDesktop.DigitalFormsLibrary"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:PRSDesktop"
+             xmlns:dynamic_grid="clr-namespace:InABox.DynamicGrid;assembly=InABox.Wpf"
+             mc:Ignorable="d" 
+             d:DesignHeight="450" d:DesignWidth="800">
+    <dynamic_grid:DynamicTabControl x:Name="Tab"
+                                    SelectionChanged="Tab_SelectionChanged"
+                                    TabStripPlacement="Bottom">
+        <dynamic_grid:DynamicTabItem x:Name="FormsTab" Header="Digital Forms">
+            <Grid>
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="Auto" />
+                    <ColumnDefinition Width="*" />
+                </Grid.ColumnDefinitions>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="*"/>
+                </Grid.RowDefinitions>
+
+                <ListView x:Name="Groups"
+                          Grid.Column="0" Grid.Row="0"
+                          SelectionChanged="Groups_SelectionChanged"
+                          Width="Auto"
+                          Margin="0,0,5,0" BorderBrush="Gray" BorderThickness="0.75" Visibility="Collapsed">
+                    <ListView.ItemTemplate>
+                        <DataTemplate>
+                            <Grid Width="Auto" Margin="0,0,5,0">
+                                <Grid.ColumnDefinitions>
+                                    <ColumnDefinition Width="Auto" />
+                                    <ColumnDefinition Width="*" />
+                                </Grid.ColumnDefinitions>
+                                <Border Width="32" Height="32" CornerRadius="8" Grid.Column="0" Margin="0,5,5,5"
+                                        BorderBrush="Gray" BorderThickness="0.75" HorizontalAlignment="Center">
+                                    <Border.Background>
+                                        <ImageBrush ImageSource="{Binding Path=Item3}" Stretch="UniformToFill" />
+                                    </Border.Background>
+                                </Border>
+                                <TextBlock x:Name="PCName" Grid.Column="1" TextWrapping="WrapWithOverflow" Width="Auto"
+                                           MaxWidth="200" TextAlignment="Left" VerticalAlignment="Center"
+                                           Text="{Binding Item1}" />
+                            </Grid>
+                        </DataTemplate>
+                    </ListView.ItemTemplate>
+                </ListView>
+
+                <dynamic_grid:DigitalFormGrid x:Name="Forms"
+                                              Grid.Row="0" Grid.Column="1"
+                                              AfterRefresh="Forms_AfterRefresh"
+                                              OnCreateItem="Forms_OnCreateItem"
+                                              OnFilterRecord="Forms_OnFilterRecord"/>
+            </Grid>
+        </dynamic_grid:DynamicTabItem>
+        <dynamic_grid:DynamicTabItem x:Name="RolesTab" Header="Roles">
+            <local:DigitalFormRoleCrossTab x:Name="DigitalFormRoles"/>
+        </dynamic_grid:DynamicTabItem>
+    </dynamic_grid:DynamicTabControl>
+</UserControl>

+ 209 - 0
prs.desktop/Panels/DigitalForms/DigitalFormsLibrary.xaml.cs

@@ -0,0 +1,209 @@
+using Comal.Classes;
+using InABox.Core;
+using InABox.DynamicGrid;
+using InABox.WPF;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+
+namespace PRSDesktop;
+
+/// <summary>
+/// Interaction logic for DigitalFormsLibrary.xaml
+/// </summary>
+public partial class DigitalFormsLibrary : UserControl, IPanel<DigitalForm>
+{
+    private string? CurrentGroup = null;
+
+    private bool AllItems = true;
+
+    public List<Tuple<string, string?, BitmapImage>> GroupList { get; private set; }
+
+    private bool _refreshing;
+
+    public DigitalFormsLibrary()
+    {
+        InitializeComponent();
+    }
+
+    #region Panel Stuff
+
+    public bool IsReady { get; set; }
+
+    public string SectionName => "Digital Forms Library";
+
+    public event DataModelUpdateEvent? OnUpdateDataModel;
+
+    public Dictionary<string, object[]> Selected()
+    {
+        return new Dictionary<string, object[]> {
+            { typeof(DigitalForm).EntityName(), Forms.SelectedRows }
+        };
+    }
+
+    public DataModel DataModel(Selection selection)
+    {
+        var ids = Forms.ExtractValues(x => x.ID, selection).ToArray();
+        return new AutoDataModel<DigitalForm>(new Filter<DigitalForm>(x => x.ID).InList(ids));
+    }
+
+    public void CreateToolbarButtons(IPanelHost host)
+    {
+    }
+
+    public void Setup()
+    {
+        Forms.Refresh(true, false);
+
+        RolesTab.Visibility = Security.CanView<DigitalForm>()
+            && Security.CanView<Role>()
+            && Security.CanView<RoleForm>() ? Visibility.Visible : Visibility.Collapsed;
+        var visibleTabItems = Tab.Items.OfType<DynamicTabItem>().Where(x => x.Visibility == Visibility.Visible).ToList();
+        if (visibleTabItems.Count <= 1)
+        {
+            foreach (var tab in visibleTabItems)
+            {
+                tab.Visibility = Visibility.Collapsed;
+                Tab.SelectedItem = tab;
+            }
+        }
+    }
+
+    private void Tab_SelectionChanged(object sender, SelectionChangedEventArgs e)
+    {
+        if (e.OriginalSource != Tab) return;
+        RefreshCurrentTab();
+    }
+
+    private void RefreshCurrentTab()
+    {
+        if (Tab.SelectedTab == FormsTab)
+        {
+            RefreshFormsTab();
+        }
+        else if (Tab.SelectedTab == RolesTab)
+        {
+            RefreshRoleTab();
+        }
+    }
+
+    private void RefreshFormsTab()
+    {
+        Forms.Refresh(false, true);
+    }
+
+    private void RefreshRoleTab()
+    {
+        DigitalFormRoles.Refresh(true, true);
+    }
+
+    public void Refresh()
+    {
+        RefreshCurrentTab();
+    }
+
+    public void Heartbeat(TimeSpan time)
+    {
+    }
+
+    public void Shutdown(CancelEventArgs? cancel)
+    {
+    }
+
+    #endregion
+
+    private void Groups_SelectionChanged(object sender, SelectionChangedEventArgs e)
+    {
+        if (_refreshing || e.OriginalSource != Groups)
+            return;
+
+        string? newCurrentGroup;
+        if (e.AddedItems.Count == 0 || Groups.SelectedIndex == 0)
+        {
+            AllItems = true;
+            newCurrentGroup = GroupList.Any() ? GroupList.First().Item2 : null;
+        }
+        else
+        {
+            AllItems = false;
+            var selected = (Tuple<string, string?, BitmapImage>)e.AddedItems[0];
+            newCurrentGroup = selected.Item2;
+        }
+
+        if (!Equals(newCurrentGroup, CurrentGroup))
+        {
+            CurrentGroup = newCurrentGroup;
+            Forms.Refresh(false, false);
+        }
+    }
+
+    private void Forms_AfterRefresh(object sender, InABox.DynamicGrid.AfterRefreshEventArgs args)
+    {
+        if (Forms.MasterData is null)
+            return;
+
+        _refreshing = true;
+        GroupList = new List<Tuple<string, string?, BitmapImage>>
+        {
+            new Tuple<string, string?, BitmapImage>("All Items", null, PRSDesktop.Resources.doc_misc.AsBitmapImage())
+        };
+
+        foreach (var row in Forms.MasterData!.Rows)
+        {
+            var appliesTo = row.Get<DigitalForm, string>(x => x.AppliesTo);
+            var display = appliesTo.NotWhiteSpaceOr("Unassigned");
+
+            if (!GroupList.Any(x => x.Item1.Equals(display)))
+                GroupList.Add(new Tuple<string, string?, BitmapImage>(display, appliesTo, PRSDesktop.Resources.doc_misc.AsBitmapImage()));
+        }
+
+        GroupList = GroupList.OrderBy(x => (x.Item1 == "All Items" ? "0" : x.Item1 == "Unassigned" ? "1" : "2") + x.Item1).ToList();
+        Groups.ItemsSource = GroupList;
+
+        Groups.Visibility = Visibility.Visible;
+
+        var index = GroupList.FindIndex(x => Equals(x.Item2, CurrentGroup));
+        if(index == -1)
+        {
+            index = 0;
+            CurrentGroup = null;
+            Groups.SelectedIndex = 0;
+            AllItems = true;
+
+            _refreshing = false;
+
+            Forms.Refresh(false, false);
+        }
+        else
+        {
+            Groups.SelectedIndex = index;
+            AllItems = index == 0;
+
+            _refreshing = false;
+        }
+    }
+
+    private void Forms_OnCreateItem(object sender, object item)
+    {
+        if (item is not DigitalForm form) return;
+
+        form.AppliesTo = CurrentGroup ?? "";
+    }
+
+    private bool Forms_OnFilterRecord(CoreRow row)
+    {
+        return AllItems || Equals(row.Get<DigitalForm, string>(x => x.AppliesTo), CurrentGroup);
+    }
+}

+ 0 - 0
prs.desktop/Dashboards/Common/QADashboard.xaml → prs.desktop/Panels/DigitalForms/QADashboard.xaml


+ 0 - 0
prs.desktop/Dashboards/Common/QADashboard.xaml.cs → prs.desktop/Panels/DigitalForms/QADashboard.xaml.cs