Przeglądaj źródła

Merge remote-tracking branch 'origin/kenric' into frank

frankvandenbos 2 miesięcy temu
rodzic
commit
4aae213ac7

+ 5 - 1
PRS.Avalonia/PRS.Avalonia.Desktop/PRS.Avalonia.Desktop.csproj

@@ -9,7 +9,7 @@
         <UseWinUI>true</UseWinUI>
         <WindowsPackaging>None</WindowsPackaging>
         <EnableMsixTooling>true</EnableMsixTooling>
-        <Configurations>Debug;Release</Configurations>
+        <Configurations>Debug;Release;DebugDev</Configurations>
     </PropertyGroup>
 
     <PropertyGroup>
@@ -20,6 +20,10 @@
       <OutputPath>bin\x64\Debug\</OutputPath>
     </PropertyGroup>
 
+    <PropertyGroup Condition=" '$(Configuration)' == 'DebugDev' ">
+      <OutputPath>bin\x64\DebugDev\</OutputPath>
+    </PropertyGroup>
+
     <ItemGroup>
         <PackageReference Include="Avalonia.Desktop" Version="11.2.2" />
         <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->

+ 17 - 3
PRS.Avalonia/PRS.Avalonia/App.axaml.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Linq;
+using System.Threading.Tasks;
 using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Controls.ApplicationLifetimes;
@@ -19,6 +20,9 @@ public class App : Application
 
     public override void Initialize()
     {
+        TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
+        MobileLogging.LogException += MobileLogging_LogException;
+
         CoreUtils.RegisterClasses();
         ComalUtils.RegisterClasses();
         CoreUtils.RegisterClasses(typeof(App).Assembly);
@@ -26,9 +30,19 @@ public class App : Application
         AvaloniaXamlLoader.Load(this);
     }
 
+    private void MobileLogging_LogException(Exception ex, string? tag)
+    {
+        HandleException(ex);
+    }
+
+    private void TaskScheduler_UnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
+    {
+        HandleException(e.Exception);
+    }
+
     public static void HandleException(Exception e)
     {
-        MobileLogging.Log(CoreUtils.FormatException(e));
+        MobileLogging.LogError(CoreUtils.FormatException(e));
     }
 
     public override void OnFrameworkInitializationCompleted()
@@ -41,8 +55,8 @@ public class App : Application
             desktop.MainWindow = new MainWindow
             {
                 DataContext = new MainViewModel(),
-                Width = 450,
-                Height = 1000,
+                Width = 400,
+                Height = 700,
                 WindowStartupLocation = WindowStartupLocation.CenterScreen
             };
         }

+ 45 - 29
PRS.Avalonia/PRS.Avalonia/Components/FormsEditor/DigitalFormsHostViewModel.cs

@@ -129,8 +129,21 @@ public partial class DigitalFormsHostViewModel<TModel, TShell, TParent, TParentL
         ProgressVisible = true;
     }
 
-    public void Configure(TParent parent, Guid formID, Entity instance)
+    private void BaseConfigure()
     {
+        Model = DigitalFormModel.CreateModel(DataAccess, Parent.GetType().Name);
+        Documents = new DigitalFormDocumentModel(DataAccess,
+            () => new Filter<DigitalFormDocument>(x => x.EntityLink.ID).IsEqualTo(FormID),
+            () => $"{nameof(DigitalFormDocument)}.{FormID}");
+    }
+
+    public void Configure(TParent parent, Guid formID, TForm instance)
+    {
+        Parent = parent;
+        FormID = formID;
+        InstanceID = instance.ID;
+        Form = instance;
+        BaseConfigure();
     }
 
     public void Configure(TParent parent, Guid formID, Guid instanceID)
@@ -139,10 +152,7 @@ public partial class DigitalFormsHostViewModel<TModel, TShell, TParent, TParentL
         FormID = formID;
         InstanceID = instanceID;
 
-        Model = DigitalFormModel.CreateModel(DataAccess, Parent.GetType().Name);
-        Documents = new DigitalFormDocumentModel(DataAccess,
-            () => new Filter<DigitalFormDocument>(x => x.EntityLink.ID).IsEqualTo(FormID),
-            () => $"{nameof(DigitalFormDocument)}.{FormID}");
+        BaseConfigure();
     }
 
     protected override async Task<TimeSpan> OnRefresh()
@@ -155,29 +165,35 @@ public partial class DigitalFormsHostViewModel<TModel, TShell, TParent, TParentL
 
         if(DataAccess.Status == ConnectionStatus.Connected)
         {
-            Parent = Client.Query(
-                new Filter<TParent>(x => x.ID).IsEqualTo(Parent.ID),
-                DFUtils.EntityColumns<TParent>(Variables))
-                .ToObjects<TParent>()
-                .First();
-            Form = Client.Query(
-                new Filter<TForm>(x => x.ID).IsEqualTo(InstanceID),
-                Columns.None<TForm>()
-                    .Add(
-                        x => x.ID,
-                        x => x.Number,
-                        x => x.FormData,
-                        x => x.Form.ID,
-                        x => x.Form.Description,
-                        x => x.Form.DescriptionExpression,
-                        x => x.FormCompleted,
-                        x => x.FormCompletedBy.ID,
-                        x => x.Created,
-                        x => x.FormStarted,
-                        x => x.FormOpen,
-                        x => x.BlobData))
-                .ToObjects<TForm>()
-                .First();
+            if(Parent.ID != Guid.Empty)
+            {
+                Parent = Client.Query(
+                    new Filter<TParent>(x => x.ID).IsEqualTo(Parent.ID),
+                    DFUtils.EntityColumns<TParent>(Variables))
+                    .ToObjects<TParent>()
+                    .First();
+            }
+            if(InstanceID != Guid.Empty)
+            {
+                Form = Client.Query(
+                    new Filter<TForm>(x => x.ID).IsEqualTo(InstanceID),
+                    Columns.None<TForm>()
+                        .Add(
+                            x => x.ID,
+                            x => x.Number,
+                            x => x.FormData,
+                            x => x.Form.ID,
+                            x => x.Form.Description,
+                            x => x.Form.DescriptionExpression,
+                            x => x.FormCompleted,
+                            x => x.FormCompletedBy.ID,
+                            x => x.Created,
+                            x => x.FormStarted,
+                            x => x.FormOpen,
+                            x => x.BlobData))
+                    .ToObjects<TForm>()
+                    .First();
+            }
             CacheManager.SaveBinary<DigitalFormCacheModel<TParent, TParentLink, TForm>>(CacheFileName, new()
             {
                 Parent = Parent,
@@ -297,7 +313,7 @@ public partial class DigitalFormsHostViewModel<TModel, TShell, TParent, TParentL
     {
         Navigation.Navigate<DigitalFormsHostViewModel<TModel, TShell, TParent, TParentLink, TForm>>(x =>
         {
-            x.Configure(parent, shell.FormID, shell.ID);
+            x.Configure(parent, shell.FormID, shell.Entity);
             x.OnSaved += () =>
             {
                 model.RefreshAsync(true).ContinueWith(task =>

+ 13 - 0
PRS.Avalonia/PRS.Avalonia/Components/SelectionView/SelectionViewModel.cs

@@ -74,6 +74,19 @@ public partial class SelectionViewModel : PopupViewModel<object?[]>, IModuleView
         PrimaryMenu.Add(new(Images.tick, Tick_Click));
     }
 
+    private void DoAddFilters(IEnumerable<string> filters)
+    {
+        foreach(var filter in filters)
+        {
+            FilterButtons.Add(new()
+            {
+                Text = filter
+            });
+        }
+    }
+    public void AddFilters(IEnumerable<string> filters) => DoAddFilters(filters);
+    public void AddFilters(params string[] filters) => DoAddFilters(filters);
+
     private Task<bool> Tick_Click()
     {
         Selected?.Invoke(_selectedItems);

+ 1 - 1
PRS.Avalonia/PRS.Avalonia/MainViewModel.cs

@@ -32,7 +32,7 @@ public partial class MainViewModel : ViewModelBase
             Content = viewModel;
             Title = viewModel is IModuleViewModel module
                 ? module.Title
-                : Repositories.Me?.Name ?? "PRS Avalonia";
+                : Repositories.MaybeMe?.Name ?? "PRS Avalonia";
             PrimaryMenu = viewModel.PrimaryMenu;
             SecondaryMenu = viewModel.SecondaryMenu;
             BackButtonVisible = viewModel.BackButtonVisible;

+ 5 - 0
PRS.Avalonia/PRS.Avalonia/MainWindow.axaml.cs

@@ -1,3 +1,4 @@
+using Avalonia;
 using Avalonia.Controls;
 
 namespace PRS.Avalonia;
@@ -7,5 +8,9 @@ public partial class MainWindow : Window
     public MainWindow()
     {
         InitializeComponent();
+
+#if DEBUGDEV
+        this.AttachDevTools();
+#endif
     }
 }

+ 6 - 2
PRS.Avalonia/PRS.Avalonia/Modules/DigitalForms/FormsView.axaml

@@ -2,7 +2,11 @@
              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"
+             xmlns:avComponents="using:PRS.Avalonia.Components"
+			 xmlns:local="using:PRS.Avalonia.Modules"
              mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
-             x:Class="PRS.Avalonia.Modules.FormsView">
-    Welcome to Avalonia!
+             x:Class="PRS.Avalonia.Modules.FormsView"
+			 x:DataType="local:FormsViewModel">
+	<avComponents:FormsList Model="{Binding Forms}"
+							FormClicked="{Binding FormClickedCommand}"/>
 </UserControl>

+ 118 - 1
PRS.Avalonia/PRS.Avalonia/Modules/DigitalForms/FormsViewModel.cs

@@ -1,6 +1,123 @@
+using Avalonia.Controls;
+using Comal.Classes;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using DynamicData;
+using InABox.Avalonia;
+using InABox.Avalonia.Components;
+using InABox.Clients;
+using InABox.Core;
+using PRS.Avalonia.Components;
+using PRS.Avalonia.DigitalForms;
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
 namespace PRS.Avalonia.Modules;
 
-public class FormsViewModel : ModuleViewModel
+public partial class FormsViewModel : ModuleViewModel
 {
     public override string Title => "Forms";
+
+    [ObservableProperty]
+    private KanbanModel _kanbans;
+
+    [ObservableProperty]
+    private KanbanFormModel _forms;
+
+    [ObservableProperty]
+    private DigitalFormModel _digitalForms;
+
+    public FormsViewModel()
+    {
+        Kanbans = new KanbanModel(DataAccess,
+            () => new Filter<Kanban>(x => x.ID).InQuery(new Filter<KanbanSubscriber>(x => x.Employee.ID).IsEqualTo(Repositories.Me.ID), x => x.Kanban.ID)
+                .And(new Filter<Kanban>(x => x.Status).IsNotEqualTo(KanbanStatus.Complete)
+                    .Or(x => x.Completed).IsGreaterThanOrEqualTo(DateTime.Today.AddDays(-7))),
+            () => DefaultCacheFileName<KanbanShell>());
+
+        Forms = new KanbanFormModel(DataAccess,
+            () => new Filter<KanbanForm>(x => x.Parent.EmployeeLink.ID).IsEqualTo(Repositories.Me.ID)
+                .And(new Filter<KanbanForm>(x => x.FormCompleted).IsEqualTo(DateTime.MinValue)
+                    .Or(x => x.FormCompleted).IsGreaterThanOrEqualTo(DateTime.Today.AddDays(-7))),
+            () => DefaultCacheFileName<KanbanFormShell>());
+
+        DigitalForms = new DigitalFormModel(DataAccess,
+            () => new Filter<DigitalForm>(x => x.ID).InQuery(new Filter<EmployeeDigitalForm>(x => x.Employee.ID).IsEqualTo(Repositories.Me.ID), x => x.Form.ID)
+                .And(x => x.Active).IsEqualTo(true),
+            () => DefaultCacheFileName<DigitalFormShell>());
+
+        PrimaryMenu.Add(new AvaloniaMenuItem(Images.plus, AddForm));
+    }
+
+    private async Task<bool> AddForm()
+    {
+        var form = (await SelectionViewModel.ExecutePopup<DigitalFormShell>(model =>
+        {
+            model.AddFilters(DigitalForms.AvailableFilters.Select(x => x.Name).NotNull());
+            model.Columns.BeginUpdate()
+                .Add(new AvaloniaDataGridTextColumn<DigitalFormShell>
+                {
+                    Column = x => x.Code,
+                    Width = GridLength.Auto
+                })
+                .Add(new AvaloniaDataGridTextColumn<DigitalFormShell>
+                {
+                    Column = x => x.Description,
+                    Width = GridLength.Star
+                })
+                .EndUpdate();
+        }, args =>
+        {
+            DigitalForms.SelectFilter(args.Filter);
+            DigitalForms.Search(x => x.AppliesTo == nameof(Kanban));
+            return DigitalForms.Refresh(args.Force);
+        }))?.FirstOrDefault();
+        if (form is null) return true;
+        
+        var kanban = Kanbans.CreateItem();
+        kanban.EmployeeID = Repositories.Me.ID;
+        kanban.ManagerID = Repositories.Me.ID;
+        kanban.Title = form.Description;
+
+        var kanbanForm = Forms.CreateItem();
+        kanbanForm.FormID = form.ID;
+        kanbanForm.FormCode = form.Code;
+        kanbanForm.FormDescription = form.Description;
+
+        DigitalFormsHostViewModel<KanbanFormModel, KanbanFormShell, Kanban, KanbanLink, KanbanForm>.EditForm(Forms, kanbanForm, kanban.Entity);
+
+        return true;
+    }
+
+    protected override async Task<TimeSpan> OnRefresh()
+    {
+        await Forms.RefreshAsync(false);
+        return TimeSpan.Zero;
+    }
+
+    [RelayCommand]
+    private async Task FormClicked(KanbanFormShell shell)
+    {
+        var kanban = Kanbans.FirstOrDefault(x => x.ID == shell.ParentID)?.Entity;
+        if(kanban is null)
+        {
+            // Need to go to the database, because this task isn't in our list.
+            ProgressVisible = true;
+
+            kanban = (await Client.QueryAsync(new Filter<Kanban>(x => x.ID).IsEqualTo(shell.ParentID), new KanbanShell().Columns.Columns))
+                .ToObjects<Kanban>().FirstOrDefault();
+
+            ProgressVisible = false;
+
+            if(kanban is null)
+            {
+                MobileLogging.Log($"ERROR: Could not find kanban with ID {shell.ParentID} for editing form.");
+                await MessageDialog.ShowMessage("Sorry, but we could not load this form.");
+                return;
+            }
+        }
+
+        DigitalFormsHostViewModel<KanbanFormModel, KanbanFormShell, Kanban, KanbanLink, KanbanForm>.EditForm(Forms, shell, kanban);
+    }
 }

+ 6 - 1
PRS.Avalonia/PRS.Avalonia/PRS.Avalonia.csproj

@@ -4,8 +4,12 @@
         <Nullable>enable</Nullable>
         <LangVersion>latest</LangVersion>
         <AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
-        <Configurations>Debug;Release</Configurations>
+        <Configurations>Debug;Release;DebugDev</Configurations>
     </PropertyGroup>
+
+	<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugDev|AnyCPU'">
+		<DefineConstants>$(DefineConstants);DEBUGDEV</DefineConstants>
+	</PropertyGroup>
     
     <ItemGroup>
         <AvaloniaResource Include="Assets\**" />
@@ -310,6 +314,7 @@
         <PackageReference Include="Microsoft.Maui.Essentials" Version="9.0.70" />
         
         <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
+		<PackageReference Condition="'$(Configuration)' == 'DebugDev'" Include="Avalonia.Diagnostics" Version="11.2.2"/>
     
     </ItemGroup>
 

+ 0 - 2
PRS.Avalonia/PRS.Avalonia/Repositories/DigitalForm/DigitalFormModel.cs

@@ -81,6 +81,4 @@ public class DigitalFormModel : CoreRepository<DigitalFormModel, DigitalFormShel
     {
         return CreateModel(host, typeof(TParent).Name);
     }
-
-
 }

+ 1 - 1
PRS.Avalonia/PRS.Avalonia/Repositories/Kanban/BaseKanbanModel.cs

@@ -11,7 +11,7 @@ public abstract class BaseKanbanModel<TModel, TShell> : CoreRepository<TModel, T
     where TShell : Shell<TModel, Kanban>, IKanbanShell, new()
 
 {
-    protected BaseKanbanModel(IModelHost host, Func<Filter<Kanban>> filter) : base(host, filter)
+    protected BaseKanbanModel(IModelHost host, Func<Filter<Kanban>> filter, Func<string>? filename = null) : base(host, filter, filename)
     {
     }
 

+ 1 - 1
PRS.Avalonia/PRS.Avalonia/Repositories/Kanban/KanbanModel.cs

@@ -7,7 +7,7 @@ namespace PRS.Avalonia;
 
 public class KanbanModel : BaseKanbanModel<KanbanModel, KanbanShell>
 {
-    public KanbanModel(IModelHost host, Func<Filter<Kanban>> filter) : base(host, filter)
+    public KanbanModel(IModelHost host, Func<Filter<Kanban>> filter, Func<string>? filename = null) : base(host, filter, filename)
     {
     }
 }

+ 1 - 1
PRS.Avalonia/PRS.Avalonia/Repositories/KanbanForm/KanbanFormModel.cs

@@ -8,7 +8,7 @@ namespace PRS.Avalonia;
 
 public class KanbanFormModel : DigitalFormInstanceModel<KanbanFormModel, KanbanFormShell, KanbanForm>
 {
-    public KanbanFormModel(IModelHost host, Func<Filter<KanbanForm>> filter) : base(host, filter)
+    public KanbanFormModel(IModelHost host, Func<Filter<KanbanForm>> filter, Func<string>? filename = null) : base(host, filter, filename)
     {
     }
 

+ 3 - 1
PRS.Avalonia/PRS.Avalonia/RepositoryLayer.cs

@@ -45,7 +45,7 @@ public partial class RepositoryLayer : ObservableObject
 
     public EmployeeModel MeModel { get; private set; }
 
-    public EmployeeShell? Me
+    public EmployeeShell? MaybeMe
     {
         get
         {
@@ -54,6 +54,8 @@ public partial class RepositoryLayer : ObservableObject
         }
     }
 
+    public EmployeeShell Me => MaybeMe ?? throw new Exception("Invalid employee");
+
     public EmployeeAlertModel MyAlerts { get; private set; }
 
     public NotificationModel MyNotifications { get; private set; }

+ 2 - 1
PRS.Avalonia/PRS.Avalonia/ViewModelBase.cs

@@ -52,6 +52,7 @@ public abstract partial class ViewModelBase : ObservableObject, IViewModelBase
                 _dataAccessLayer = new DataAccessLayer();
                 _dataAccessLayer.Connected += (s, e) => Connected();
                 _dataAccessLayer.Disconnected += (s, e) => Disconnected();
+                _dataAccessLayer.Validated += (s, e) => Validated();
             }
 
             _dataAccessLayer ??= new DataAccessLayer();
@@ -103,7 +104,7 @@ public abstract partial class ViewModelBase : ObservableObject, IViewModelBase
     {
     }
 
-    private void Validated()
+    private static void Validated()
     {
         Repositories.MeModel.Refresh(true);
     }

+ 5 - 4
prs.desktop/Dashboards/Common/DigitalFormsDashboard.xaml.cs

@@ -1425,13 +1425,14 @@ public partial class DigitalFormsDashboard : UserControl,
             {
                 var code = variable.Code.Replace("/", " ");
                 Grid.QuestionCodes[code] = Thread.CurrentThread.CurrentCulture.TextInfo.ToTitleCase(code.ToLower());
-                try
+
+                if (cData.HasColumn(code))
                 {
-                    cData.AddColumn<string>(code);
+                    MessageWindow.ShowError($"Error: duplicate variable code {code}", $"Error: duplicate variable code {code}", title: "Duplicate code");
                 }
-                catch (DuplicateNameException e)
+                else
                 {
-                    MessageWindow.ShowError($"Error: duplicate variable code {code}", e, title: "Duplicate code");
+                    cData.AddColumn<string>(code);
                 }
             }
         }