浏览代码

avalonia: Finished FormsList

Kenric Nugteren 7 月之前
父节点
当前提交
bac799c850

+ 7 - 0
PRS.Avalonia/PRS.Avalonia/Components/FormsEditor/DigitalFormsHostView.axaml

@@ -0,0 +1,7 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:Class="PRS.Avalonia.DigitalForms.DigitalFormsHostView">
+</UserControl>

+ 13 - 0
PRS.Avalonia/PRS.Avalonia/Components/FormsEditor/DigitalFormsHostView.axaml.cs

@@ -0,0 +1,13 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace PRS.Avalonia.DigitalForms;
+
+public partial class DigitalFormsHostView : UserControl
+{
+    public DigitalFormsHostView()
+    {
+        InitializeComponent();
+    }
+}

+ 35 - 0
PRS.Avalonia/PRS.Avalonia/Components/FormsEditor/DigitalFormsHostViewModel.cs

@@ -0,0 +1,35 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using InABox.Core;
+using PRS.Avalonia.Modules;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PRS.Avalonia.DigitalForms;
+
+public partial class DigitalFormsHostViewModel : ModuleViewModel
+{
+    public override string Title => "WIP";
+
+    [ObservableProperty]
+    private Entity _parent;
+
+    [ObservableProperty]
+    private Guid _formID;
+
+    [ObservableProperty]
+    private Guid _instanceID;
+
+    public event Action? OnSaved;
+
+    public void Configure(Entity parent, Guid formID, Guid instanceID)
+    {
+        Parent = parent;
+        FormID = formID;
+        InstanceID = instanceID;
+
+        // TODO: LoadItems; show loading screen or progress ring while loading.
+    }
+}

+ 9 - 8
PRS.Avalonia/PRS.Avalonia/Components/FormsList/FormsList.axaml

@@ -14,7 +14,6 @@
 		<local:FormsListBackgroundColorConverter x:Key="BackgroundColorConverter"/>
 		<local:FormsListForegroundColorConverter x:Key="ForegroundColorConverter"/>
 		<local:ExistingFormStatusConverter x:Key="StatusConverter"/>
-		<converters:ShellSelectedConverter x:Key="ShellSelectedConverter"/>
 	</UserControl.Resources>
 	<Grid>
 		<Grid.RowDefinitions>
@@ -27,11 +26,12 @@
 		</Grid.ColumnDefinitions>
 
 		<listView:PrsListView Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
-							  Repository="{Binding $parent[local:FormsList].Model}">
+							  Repository="{Binding $parent[local:FormsList].Model}"
+							  FilterShell="FilterShell">
 			<listView:PrsListView.ItemTemplate>
 				<DataTemplate DataType="prs:IDigitalFormInstanceShell">
 					<Button Background="{Binding .,Converter={StaticResource BackgroundColorConverter}}"
-							Command="{Binding $parent[local:FormsList].FormClickedCommand}"
+							Command="{Binding $parent[local:FormsList].FormClicked}"
 							CommandParameter="{Binding .}"
 							Height="50"
 							Padding="0"
@@ -52,7 +52,7 @@
 							<CheckBox Grid.Row="0" Grid.Column="0"
 									  Height="15"
 									  Classes="small"
-									  IsChecked="{Binding .,Converter={StaticResource ShellSelectedConverter}}"
+									  IsChecked="{Binding IsSelected}"
 									  Command="{Binding $parent[local:FormsList].FormCheckedCommand}"
 									  CommandParameter="{Binding .}"/>
 							<Label Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="3"
@@ -81,14 +81,15 @@
 				</DataTemplate>
 			</listView:PrsListView.ItemTemplate>
 		</listView:PrsListView>
-		<TabStrip Grid.Row="1" Grid.Column="0"
-				  IsVisible="{Binding $parent[local:FormsList].SeparateHistory}">
+		<TabStrip Name="_tabList"
+				  Grid.Row="1" Grid.Column="0"
+				  IsVisible="{Binding $parent[local:FormsList].SeparateHistory}"
+				  SelectionChanged="TabStrip_SelectionChanged">
 			<TabStripItem Content="Open Forms"/>
 			<TabStripItem Content="History"/>
 		</TabStrip>
-		<Button Grid.Row="1" Grid.Column="1"
+		<Button Name="SelectionMenuButton" Grid.Row="1" Grid.Column="1"
 				Background="Silver"
-				IsVisible="{Binding SelectionMenuVisible}"
 				Command="{Binding $parent[local:FormsList].SelectionMenuCommand}"/>
 	</Grid>
 </UserControl>

+ 98 - 21
PRS.Avalonia/PRS.Avalonia/Components/FormsList/FormsList.axaml.cs

@@ -3,12 +3,18 @@ using Avalonia.Controls;
 using Avalonia.Data.Converters;
 using Avalonia.Markup.Xaml;
 using Avalonia.Media;
+using Avalonia.Threading;
+using CommunityToolkit.Mvvm.ComponentModel;
 using CommunityToolkit.Mvvm.Input;
 using InABox.Avalonia;
 using InABox.Avalonia.Components;
 using InABox.Avalonia.Converters;
+using InABox.Clients;
 using InABox.Core;
+using JetBrains.Annotations;
+using PRS.Avalonia.DigitalForms;
 using System;
+using System.ComponentModel;
 using System.Globalization;
 using System.Linq;
 using System.Threading.Tasks;
@@ -60,57 +66,82 @@ public class ExistingFormStatusConverter : AbstractConverter<IDigitalFormInstanc
     }
 }
 
+public class FormsListSearchEventArgs(IDigitalFormInstanceShell shell)
+{
+    public IDigitalFormInstanceShell Shell { get; set; } = shell;
+}
+public delegate bool FormsListSearchEvent(object sender, FormsListSearchEventArgs args);
+
+// TODO: RefreshRequested, and thus RefreshData; FilterShell
+// FormTapped
+
 public partial class FormsList : UserControl
 {
     public static readonly StyledProperty<bool> SeparateHistoryProperty =
         AvaloniaProperty.Register<FormsList, bool>(nameof(SeparateHistory), true);
-
-    public static readonly StyledProperty<bool> SelectionMenuVisibleProperty =
-        AvaloniaProperty.Register<FormsList, bool>(nameof(SelectionMenuVisible), true);
-
-    public string SearchText { get; set; }
+    public static readonly StyledProperty<string> AppliesToProperty =
+        AvaloniaProperty.Register<FormsList, string>(nameof(AppliesTo), "");
+    public static readonly StyledProperty<ICoreRepository?> ModelProperty =
+        AvaloniaProperty.Register<FormsList, ICoreRepository?>(nameof(Model));
+    public static readonly StyledProperty<ICommand?> FormClickedProperty =
+        AvaloniaProperty.Register<FormsList, ICommand?>(nameof(FormClicked));
 
     public bool SeparateHistory
     {
         get => GetValue(SeparateHistoryProperty);
         set => SetValue(SeparateHistoryProperty, value);
     }
+    public string AppliesTo
+    {
+        get => GetValue(AppliesToProperty);
+        set => SetValue(AppliesToProperty, value);
+    }
 
-    public bool SelectionMenuVisible
+    private bool SelectionMenuVisible
     {
-        get => GetValue(SelectionMenuVisibleProperty);
-        set => SetValue(SelectionMenuVisibleProperty, value);
+        set => SelectionMenuButton.IsVisible = value;
     }
 
-    public static readonly StyledProperty<ICoreRepository?> ModelProperty =
-        AvaloniaProperty.Register<FormsList, ICoreRepository?>(nameof(Model));
+    private bool ShowIncomplete { get; set; } = true;
+
+    public event FormsListSearchEvent? Search;
 
     public ICoreRepository? Model
     {
         get => GetValue(ModelProperty);
         set => SetValue(ModelProperty, value);
     }
+   
+    public ICommand? FormClicked
+    {
+        get => GetValue(FormClickedProperty);
+        set => SetValue(FormClickedProperty, value);
+    }
 
     public FormsList()
     {
         InitializeComponent();
     }
 
-    [RelayCommand]
-    private void Search()
+    private bool FilterShell(IShell shell)
     {
+        if (shell is not IDigitalFormInstanceShell formShell) return false;
 
+        return (!SeparateHistory || ShowIncomplete == (formShell.Completed == DateTime.MinValue))
+            && (Search is null || Search.Invoke(this, new(formShell)));
     }
 
-    [RelayCommand]
-    private void FormClicked(IDigitalFormInstanceShell form)
+    private void TabStrip_SelectionChanged(object sender, SelectionChangedEventArgs e)
     {
+        if (_tabList is null) return;
+        ShowIncomplete = _tabList.SelectedIndex == 0;
+        Model?.SelectNone();
+        SelectionMenuVisible = false;
     }
 
     [RelayCommand]
     private void FormChecked(IDigitalFormInstanceShell form)
     {
-        Model?.ToggleSelection(form);
         SelectionMenuVisible = Model?.SelectedItems.OfType<IShell>().Any() == true;
     }
 
@@ -119,31 +150,77 @@ public partial class FormsList : UserControl
     {
         var menu = new ContextMenu();
         AvaloniaMenuItem.LoadMenuItems([
-            new CoreMenuItem<IImage>("Complete Forms", null, CompleteForms),
-            new CoreMenuItem<IImage>("Re-open Forms", null, ReopenForms),
+            new CoreMenuItem<IImage>("Complete Forms", null, CompleteForms, () => ShowIncomplete),
+            new CoreMenuItem<IImage>("Re-open Forms", null, ReopenForms, () => !ShowIncomplete),
             new CoreMenuSeparator(),
             new CoreMenuItem<IImage>("Select All", null, SelectAll),
             new CoreMenuItem<IImage>("Select None", null, SelectNone),
             ], menu.Items);
+        menu.Open(SelectionMenuButton);
     }
 
-    private async Task<bool> SelectNone()
+    private Task<bool> SelectNone()
     {
-        return true;
+        Model?.SelectNone();
+        return Task.FromResult(true);
     }
 
-    private async Task<bool> SelectAll()
+    private Task<bool> SelectAll()
     {
-        return true;
+        Model?.SelectAll();
+        return Task.FromResult(true);
     }
 
     private async Task<bool> ReopenForms()
     {
+        if (Model is null) return true;
+
+        var shells = Model.SelectedItems.OfType<IDigitalFormInstanceShell>().ToArray();
+        foreach(var shell in shells)
+        {
+            shell.Completed = DateTime.MinValue;
+        }
+        await Model.SaveAsync("Re-opened on Mobile Device");
+        Model.SelectNone();
+        SelectionMenuVisible = false;
+        await Model.RefreshAsync(true);
         return true;
     }
 
     private async Task<bool> CompleteForms()
     {
+        if (Model is null) return true;
+
+        var shells = Model.SelectedItems.OfType<IDigitalFormInstanceShell>().ToArray();
+        foreach(var shell in shells)
+        {
+            shell.Completed = DateTime.Now;
+        }
+        await Model.SaveAsync("Completed on Mobile Device");
+        Model.SelectNone();
+        SelectionMenuVisible = false;
+        await Model.RefreshAsync(true);
         return true;
     }
+
+    public void EditForm<TParent, TParentLink, TForm>(IDigitalFormInstanceShell shell, TParent parent)
+        where TParent : Entity, IRemotable, IPersistent, new()
+        where TParentLink : EntityLink<TParent>, new()
+        where TForm : Entity, IRemotable, IPersistent, IDigitalFormInstance<TParentLink>, new()
+    {
+        Navigation.Navigate<DigitalFormsHostViewModel>(x =>
+        {
+            x.Configure(parent, shell.FormID, shell.ID);
+            x.OnSaved += () =>
+            {
+                Model?.RefreshAsync(true).ContinueWith(task =>
+                {
+                    if(task.Exception is not null)
+                    {
+                        MobileLogging.Log(task.Exception);
+                    }
+                });
+            };
+        });
+    }
 }

+ 28 - 5
PRS.Avalonia/PRS.Avalonia/Components/ListView/PrsListView.axaml.cs

@@ -17,11 +17,24 @@ namespace PRS.Avalonia.Components.ListView;
 
 public partial class PrsListView : UserControl
 {
+    public delegate bool FilterShellHandler(IShell shell);
+
+    public event FilterShellHandler? FilterShell;
+
     public PrsListView()
     {
         InitializeComponent();
     }
-    
+    static PrsListView()
+    {
+        RepositoryProperty.Changed.AddClassHandler<PrsListView>(RepositoryChanged);
+    }
+
+    private static void RepositoryChanged(PrsListView view, AvaloniaPropertyChangedEventArgs args)
+    {
+        view.Repository?.Search(view.SearchPredicate);
+    }
+
     public static readonly StyledProperty<bool> SearchVisibleProperty =
         AvaloniaProperty.Register<PrsListView, bool>(nameof(SearchVisible), true);
     
@@ -97,12 +110,22 @@ public partial class PrsListView : UserControl
         }
     });
 
+    private bool DoFilterShell(IShell shell)
+    {
+        if (!shell.Match(SearchText)) return false;
+        if (FilterShell is not null && !FilterShell.Invoke(shell)) return false;
+
+        return true;
+    }
+
+    private bool SearchPredicate(object o)
+    {
+        return o is IShell shell && DoFilterShell(shell);
+    }
+
     [RelayCommand]
     private void Search()
     {
-        if (Repository != null)
-            Repository.Search(o => o is IShell shell 
-                ? shell.Match(SearchText) 
-                : false);
+        Repository?.Search();
     }
 }

+ 2 - 1
PRS.Avalonia/PRS.Avalonia/Modules/Site/SiteItps/SiteITPFormsView.axaml

@@ -7,5 +7,6 @@
              mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
 			 x:Class="PRS.Avalonia.Modules.SiteITPFormsView"
 			 x:DataType="local:SiteITPFormsViewModel">
-	<avComponents:FormsList Model="{Binding Forms}"/>
+	<avComponents:FormsList Model="{Binding Forms}"
+							FormClicked="{Binding FormClickedCommand}"/>
 </UserControl>

+ 7 - 0
PRS.Avalonia/PRS.Avalonia/Modules/Site/SiteItps/SiteITPFormsViewModel.cs

@@ -1,4 +1,5 @@
 using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
 using System;
 using System.Collections.Generic;
 using System.Linq;
@@ -28,4 +29,10 @@ public partial class SiteITPFormsViewModel : ModuleViewModel
             ]);
         return TimeSpan.Zero;
     }
+
+    [RelayCommand]
+    private void FormClicked(JobITPFormShell shell)
+    {
+
+    }
 }

+ 3 - 0
PRS.Avalonia/PRS.Avalonia/PRS.Avalonia.csproj

@@ -334,6 +334,9 @@
     </ItemGroup>
 
     <ItemGroup>
+        <Compile Update="Components\FormsEditor\DigitalFormsHostView.axaml.cs">
+          <DependentUpon>DigitalFormsHostView.axaml</DependentUpon>
+        </Compile>
         <Compile Update="Components\FormsList\FormsList.axaml.cs">
           <DependentUpon>FormsList.axaml</DependentUpon>
         </Compile>

+ 5 - 0
PRS.Avalonia/PRS.Avalonia/Repositories/DigitalFormInstance/DigitalFormInstanceShell.cs

@@ -82,4 +82,9 @@ public abstract class DigitalFormInstanceShell<TModel, TParent, TParentLink, TFo
             .Map(nameof(Created), x => x.Created)
             .Map(nameof(Cancelled), x => x.FormCancelled);
     }
+
+    public override string[] TextSearchValues()
+    {
+        return [FormCode, FormDescription];
+    }
 }

+ 2 - 0
prs.mobile.new/PRS.Mobile/Modules/Assignments/Edit/Views/AssignmentEditFormsView.xaml.cs

@@ -38,6 +38,8 @@ namespace PRS.Mobile
             {
                 using (await MaterialDialog.Instance.LoadingDialogAsync(message: "Loading"))
                 {
+					// Pretty sure this can be replaced with _forms.EditFormAsync<>(instance, ViewModel.Item.Entity)
+					// Only difference is that calls RefreshData(true, true)
 
                     var form = new Client<AssignmentForm>()
                         .Query(

+ 3 - 0
prs.mobile.new/PRS.Mobile/Modules/Site/SiteITPForms/SiteITPForms.xaml.cs

@@ -82,6 +82,9 @@ namespace PRS.Mobile
         {
             if (args.Shell is JobITPFormShell shell)
             {
+				// Pretty sure this can be replaced with _forms.EditFormAsync<>(shell, _itp.Entity)
+				// Only difference is that calls RefreshData(true, true)
+				
                 var model = new DigitalFormHostModel<JobITP, JobITPLink, JobITPForm>();
                 model.LoadItems(_itp.Entity, shell.FormID, shell.ID, null);
                 var host = new DigitalFormHost(model);