瀏覽代碼

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