Explorar el Código

avalonia: Created basic ProjectPlanner module

Kenric Nugteren hace 5 días
padre
commit
2beba0f7c5

+ 1 - 0
PRS.Avalonia/PRS.Avalonia/HomePage/HomePageViewModel.cs

@@ -61,6 +61,7 @@ public partial class HomePageViewModel : ViewModelBase
         _notifications = Modules.Add<NotificationsViewModel>("Notifications", "", Images.notification, isVisible: false);
         Modules.Add<ViewMobilePurchaseOrdersModule, PurchaseOrdersViewModel>("Orders", "", Images.shoppingcart, isVisible: true);
         Modules.Add<ViewMobileProductsModule, ProductsViewModel>("Products", "", Images.stock, isVisible: false);
+        Modules.Add<ViewMobileSiteModule, ProjectPlannerViewModel>("Project Planner", "", Images.schedule);
         Modules.Add("Scanner", "", Images.barcode, () =>
         {
             Navigation.Navigate<ScannerViewModel>(x =>

+ 54 - 0
PRS.Avalonia/PRS.Avalonia/Modules/ProjectPlanner/ProjectPlannerView.axaml

@@ -0,0 +1,54 @@
+<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"
+             xmlns:components="using:InABox.Avalonia.Components"
+             xmlns:local="using:PRS.Avalonia.Modules"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:Class="PRS.Avalonia.Modules.ProjectPlannerView"
+             x:DataType="local:ProjectPlannerViewModel">
+    <Grid RowDefinitions="Auto,*"
+          ColumnDefinitions="Auto,*,Auto">
+        <Button Classes="Standard"
+                Grid.Row="0" Grid.Column="0"
+                Command="{Binding PreviousDayCommand}">
+            <Image Classes="Small" Source="{SvgImage /Images/arrow_white_left.svg}"/>
+        </Button>
+        <Button Classes="Standard"
+                Grid.Row="0" Grid.Column="1"
+                Command="{Binding SelectDayCommand}">
+            <TextBlock Text="{Binding Date, StringFormat={}{0:MMMM yyyy}}"/>
+        </Button>
+        <Button Classes="Standard"
+                Grid.Row="0" Grid.Column="2"
+                Command="{Binding NextDayCommand}">
+            <Image Classes="Small" Source="{SvgImage /Images/arrow_white_right.svg}"/>
+        </Button>
+        <components:MonthView Classes="Standard"
+                              CurrentDate="{Binding Date}"
+                              Grid.Row="1"
+                              Grid.ColumnSpan="3"
+                              BlockClicked="Block_Clicked"
+                              BlockHeld="Block_Held"
+                              CellClicked="Cell_Clicked"
+                              CellHeld="Cell_Held"
+                              DateRangeChanged="MonthView_DateRangeChanged"
+                              ItemsSource="{Binding Model.Items}"
+                              StartDateMapping="{Binding StartDate}"
+                              EndDateMapping="{Binding EndDate}"
+                              ColourMapping="{Binding Background}">
+            <components:MonthView.ItemTemplate>
+                <DataTemplate>
+                    <TextBlock Foreground="{Binding Foreground}">
+                        <TextBlock.Text>
+                            <MultiBinding StringFormat="{}{0}: {1}">
+                                <Binding Path="JobNumber"/>
+                                <Binding Path="JobName"/>
+                            </MultiBinding>
+                        </TextBlock.Text>
+                    </TextBlock>
+                </DataTemplate>
+            </components:MonthView.ItemTemplate>
+        </components:MonthView>
+    </Grid>
+</UserControl>

+ 63 - 0
PRS.Avalonia/PRS.Avalonia/Modules/ProjectPlanner/ProjectPlannerView.axaml.cs

@@ -0,0 +1,63 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using Avalonia.Media;
+using InABox.Avalonia.Components;
+using InABox.Avalonia.Dialogs;
+using InABox.Core;
+
+namespace PRS.Avalonia.Modules;
+
+public partial class ProjectPlannerView : UserControl
+{
+    public ProjectPlannerView()
+    {
+        InitializeComponent();
+    }
+
+    private void Block_Clicked(object sender, MonthViewBlockEventArgs e)
+    {
+        if (DataContext is not ProjectPlannerViewModel model) return;
+        if(e.Value is JobStageShell jobStage)
+        {
+            model.BlockClicked(jobStage).ErrorIfFail();
+        }
+    }
+
+    private void Block_Held(object sender, MonthViewBlockEventArgs e)
+    {
+        if (DataContext is not ProjectPlannerViewModel model) return;
+        if(e.Value is JobStageShell jobStage)
+        {
+            var menu = new CoreMenu<IImage>();
+            model.BlockHeld(jobStage, menu);
+            if (menu.Items.Count == 0) return;
+            var contextMenu = menu.Build();
+            contextMenu.Open(sender as Control);
+        }
+    }
+
+    private void Cell_Clicked(object sender, MonthViewCellEventArgs e)
+    {
+        if (DataContext is not ProjectPlannerViewModel model) return;
+        model.EmptyBlockClicked(e.Date);
+    }
+
+    private void Cell_Held(object sender, MonthViewCellEventArgs e)
+    {
+        if (DataContext is not ProjectPlannerViewModel model) return;
+
+        var menu = new CoreMenu<IImage>();
+        model.EmptyBlockHeld(e.Date, menu);
+        if (menu.Items.Count == 0) return;
+        var contextMenu = menu.Build();
+        contextMenu.Open(sender as Control);
+    }
+
+    private void MonthView_DateRangeChanged(object sender, MonthViewDateRangeEventArgs e)
+    {
+        if (DataContext is not ProjectPlannerViewModel model) return;
+
+        model.DateRange = (e.StartDate, e.EndDate);
+    }
+}

+ 114 - 0
PRS.Avalonia/PRS.Avalonia/Modules/ProjectPlanner/ProjectPlannerViewModel.cs

@@ -0,0 +1,114 @@
+using Avalonia.Media;
+using Comal.Classes;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using InABox.Avalonia;
+using InABox.Avalonia.Components.DateSelector;
+using InABox.Avalonia.Dialogs;
+using InABox.Clients;
+using InABox.Core;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PRS.Avalonia.Modules;
+
+public partial class ProjectPlannerViewModel : ModuleViewModel
+{
+    public override string Title => "Project Planner";
+
+    [ObservableProperty]
+    private DateTime _date;
+
+    [ObservableProperty]
+    private JobStageModel _model;
+
+    [ObservableProperty]
+    private (DateTime Start, DateTime End) _dateRange;
+
+    public ProjectPlannerViewModel()
+    {
+        var currentDate = DateTime.Today;
+        Date = new(currentDate.Year, currentDate.Month, 1);
+
+        Model = new JobStageModel(DataAccess,
+            () => Filter<JobStage>.Where(x => x.StartDate).IsLessThanOrEqualTo(_dateRange.End)
+                .And(x => x.EndDate).IsGreaterThanOrEqualTo(_dateRange.Start));
+    }
+
+    partial void OnDateRangeChanged((DateTime Start, DateTime End) value)
+    {
+        Model.RefreshAsync(true).ErrorIfFail();
+    }
+
+    public async Task BlockClicked(JobStageShell jobStage)
+    {
+        await OpenJobStageClick(jobStage);
+    }
+
+    public void BlockHeld(JobStageShell jobStage, CoreMenu<IImage> menu)
+    {
+        menu.AddItem("View Job Stage", () => OpenJobStageClick(jobStage));
+        if (Security.CanDelete<JobStage>())
+        {
+            menu.AddItem("Delete Job Stage", () => DeleteJobStageClick(jobStage));
+        }
+    }
+
+    private async Task<bool> OpenJobStageClick(JobStageShell shell)
+    {
+        // Navigation.Navigate<AssignmentEditViewModel>(x =>
+        // {
+        //     x.Shell = shell;
+        // });
+        return true;
+    }
+
+    private async Task<bool> DeleteJobStageClick(JobStageShell shell)
+    {
+        if(!await MessageDialog.ShowYesCancel("Are you sure you wish to delete this job stage?"))
+        {
+            return false;
+        }
+        await Client.DeleteAsync(shell.Entity, "Deleted on Mobile Device");
+        Model.DeleteItem(shell);
+        return true;
+    }
+
+    public void EmptyBlockClicked(DateTime date)
+    {
+        // Do nothing for now.
+    }
+
+    public void EmptyBlockHeld(DateTime date, CoreMenu<IImage> menu)
+    {
+        // menu.AddItem("New Assignment", () => NewAssignment(start, end, employeeID));
+    }
+
+    [RelayCommand]
+    private void PreviousDay()
+    {
+        Date = Date.AddMonths(-1);
+    }
+
+    [RelayCommand]
+    private async Task SelectDay()
+    {
+        var date = await Navigation.Popup<DateSelectorViewModel, DateTime?>(x =>
+        {
+            x.Date = Date;
+        });
+        if (date.HasValue)
+        {
+            Date = date.Value == DateTime.MinValue ? DateTime.Today : date.Value;
+        }
+    }
+
+    [RelayCommand]
+    private void NextDay()
+    {
+        Date = Date.AddMonths(1);
+    }
+}

+ 17 - 0
PRS.Avalonia/PRS.Avalonia/Repositories/JobStage/JobStageModel.cs

@@ -0,0 +1,17 @@
+using Comal.Classes;
+using InABox.Avalonia;
+using InABox.Core;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PRS.Avalonia;
+
+public class JobStageModel : CoreRepository<JobStageModel, JobStageShell, JobStage>
+{
+    public JobStageModel(IModelHost host, Func<Filter<JobStage>> filter, Func<string>? filename = null) : base(host, filter, filename)
+    {
+    }
+}

+ 40 - 0
PRS.Avalonia/PRS.Avalonia/Repositories/JobStage/JobStageShell.cs

@@ -0,0 +1,40 @@
+using Avalonia.Media;
+using Comal.Classes;
+using InABox.Avalonia;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PRS.Avalonia;
+
+public class JobStageShell : Shell<JobStageModel, JobStage>
+{
+    public string JobNumber => Get<string>();
+
+    public string JobName => Get<string>();
+
+    public string TypeColour => Get<string>();
+
+    public DateTime StartDate => Get<DateTime>();
+
+    public DateTime EndDate => Get<DateTime>();
+
+    public Color BackgroundColor => !string.IsNullOrWhiteSpace(TypeColour) && Color.TryParse(TypeColour, out var colour)
+        ? colour : Colors.Silver;
+
+    public IBrush Background => new SolidColorBrush(BackgroundColor);
+
+    public IBrush Foreground => new SolidColorBrush(BackgroundColor.GetForegroundColor());
+
+    protected override void ConfigureColumns(ShellColumns<JobStageModel, JobStage> columns)
+    {
+        columns
+            .Map(nameof(JobNumber), x => x.Job.JobNumber)
+            .Map(nameof(JobName), x => x.Job.Name)
+            .Map(nameof(TypeColour), x => x.Type.Color)
+            .Map(nameof(StartDate), x => x.StartDate)
+            .Map(nameof(EndDate), x => x.EndDate);
+    }
+}

+ 24 - 16
PRS.Avalonia/PRS.Avalonia/TestView.axaml

@@ -7,8 +7,24 @@
              mc:Ignorable="d" d:DesignWidth="300" d:DesignHeight="400"
              x:Class="PRS.Avalonia.TestView"
              x:DataType="prs:TestViewModel">
-    <Grid RowDefinitions="*,100">
-        <components:CalendarView Name="Calendar"
+    <Grid RowDefinitions="*,0">
+        <components:MonthView Name="Month"
+                              ItemsSource="{Binding Items}"
+                              StartDateMapping="{Binding Start}"
+                              EndDateMapping="{Binding End}"
+                              ColourMapping="{Binding Colour}"
+                              FirstDayOfWeek="{Binding FirstDayOfWeek}"
+                              CurrentDate="{Binding CurrentDate}">
+            <components:MonthView.ItemTemplate>
+                <DataTemplate>
+                    <TextBlock Text="{Binding Name}"
+                               Classes="Small"
+                               VerticalAlignment="Center"
+                               Foreground="{Binding Colour, Converter={x:Static prs:TestViewModel.ForegroundConverter}}"/>
+                </DataTemplate>
+            </components:MonthView.ItemTemplate>
+        </components:MonthView>
+        <!--components:CalendarView Name="Calendar"
                                  ItemsSource="{Binding Items}"
                                  StartTimeMapping="{Binding Start}"
                                  EndTimeMapping="{Binding End}"
@@ -34,26 +50,18 @@
                     </Border>
                 </DataTemplate>
             </components:CalendarView.ItemTemplate>
-        </components:CalendarView>
+        </components:CalendarView-->
         <ScrollViewer Grid.Row="1">
             <UniformGrid Columns="2" ColumnSpacing="5">
                 <Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto">
-                    <TextBlock Classes="ExtraSmall" Text="Row Height: "
-                               VerticalAlignment="Center"/>
-                    <Slider Grid.Column="1"
-                            Value="{Binding RowHeight, ElementName=Calendar}"
-                            Minimum="20" Maximum="100"/>
-                </Grid>
-                <Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto">
-                    <TextBlock Classes="ExtraSmall" Text="Row Interval: "
+                    <TextBlock Classes="ExtraSmall" Text="Start Date"
                                VerticalAlignment="Center"/>
                     <Slider Grid.Column="1"
-                            Value="{Binding RowIntervalMinutes}"
-                            Minimum="10" Maximum="120" IsSnapToTickEnabled="True"
-                            TickFrequency="10"/>
+                            Value="{Binding FirstDayOfWeekNumber}"
+                            Minimum="0" Maximum="6"
+                            IsSnapToTickEnabled="True"
+                            TickFrequency="1"/>
                 </Grid>
-                <TextBlock Text="{Binding Block}"
-                           Classes="ExtraSmall"/>
             </UniformGrid>
         </ScrollViewer>
     </Grid>

+ 2 - 0
PRS.Avalonia/PRS.Avalonia/TestView.axaml.cs

@@ -1,8 +1,10 @@
 using Avalonia;
 using Avalonia.Controls;
+using Avalonia.Data.Converters;
 using Avalonia.Markup.Xaml;
 using Avalonia.Media.Imaging;
 using InABox.Avalonia.Components;
+using InABox.Avalonia.Converters;
 using System;
 
 namespace PRS.Avalonia;

+ 65 - 31
PRS.Avalonia/PRS.Avalonia/TestViewModel.cs

@@ -1,9 +1,14 @@
-using Avalonia.Media;
+using Avalonia.Data.Converters;
+using Avalonia.Media;
 using Avalonia.Media.Imaging;
 using CommunityToolkit.Mvvm.ComponentModel;
 using CommunityToolkit.Mvvm.Input;
+using DynamicData;
 using InABox.Avalonia;
+using InABox.Clients;
+using NetTopologySuite.Triangulate;
 using PRS.Avalonia.Modules;
+using Svg.Model.Drawables.Elements;
 using System;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
@@ -16,11 +21,9 @@ namespace PRS.Avalonia;
 public partial class A : ObservableObject
 {
     [ObservableProperty]
-    private TimeSpan _start;
+    private DateTime _start;
     [ObservableProperty]
-    private TimeSpan _end;
-    [ObservableProperty]
-    private DateTime _date;
+    private DateTime _end;
 
     [ObservableProperty]
     private string _name;
@@ -31,38 +34,66 @@ public partial class A : ObservableObject
 
 public partial class TestViewModel : ModuleViewModel
 {
+    public static readonly IValueConverter ForegroundConverter = new FuncValueConverter<ISolidColorBrush, IBrush?>(x =>
+    {
+        if (x is null) return null;
+
+        double ConvertC(byte c)
+        {
+            var frac = c / 255.0;
+            if(frac <= 0.04045)
+            {
+                return frac / 12.92;
+            }
+            else
+            {
+                return Math.Pow((frac + 0.055) / 1.055, 2.4);
+            }
+        }
+
+        var fill = x.Color;
+        var L = 0.2126 * ConvertC(fill.R)
+            + 0.7152 * ConvertC(fill.G)
+            + 0.0722 * ConvertC(fill.B);
+        if(L > 0.179)
+        {
+            return new SolidColorBrush(Colors.Black);
+        }
+        else
+        {
+            return new SolidColorBrush(Colors.White);
+        }
+    });
+
     public override string Title => "Test";
 
     [ObservableProperty]
     private ObservableCollection<A> _items = new();
 
     [ObservableProperty]
-    private double _rowIntervalMinutes = 60;
+    private string _block = "";
 
     [ObservableProperty]
-    private TimeSpan _rowInterval = TimeSpan.FromHours(1);
+    private DateTime _currentDate = DateTime.Today;
 
     [ObservableProperty]
-    private string _block = "";
+    private double _firstDayOfWeekNumber = 0;
 
     [ObservableProperty]
-    private DateTime[] _columns = [];
+    private DayOfWeek _firstDayOfWeek = DayOfWeek.Sunday;
 
     public TestViewModel()
     {
         PrimaryMenu.Items.Add(new(Images.refresh, Refresh));
         PrimaryMenu.Items.Add(new(Images.notification, () =>
         {
-            if(Items.Count > 0)
-            {
-                Items[0].Date = DateTime.Today.AddDays(Random.Shared.Next(0, 3));
-            }
+            Items = new(Items);
         }));
     }
 
-    partial void OnRowIntervalMinutesChanged(double oldValue, double newValue)
+    partial void OnFirstDayOfWeekNumberChanged(double value)
     {
-        RowInterval = TimeSpan.FromMinutes(RowIntervalMinutes);
+        FirstDayOfWeek = (DayOfWeek)(int)Math.Floor(value);
     }
 
     public void BlockHeld(object? value, object column, TimeSpan start, TimeSpan end)
@@ -90,27 +121,30 @@ public partial class TestViewModel : ModuleViewModel
     }
     
     private void Refresh()
+    {
+        RebuildRandom();
+    }
+
+    private void RebuildRandom()
     {
         Items.Clear();
-        for(int j = 0; j < 4; ++j)
+        var date = DateTime.Today;
+        var startDate = new DateTime(date.Year, date.Month, 1);
+        var endDate = startDate.AddMonths(2).AddDays(-1);
+        for(int j = 0; j < 30; ++j)
         {
-            var date = DateTime.Today.AddDays(j);
-            for(int i = 0; i < 5; ++i)
+            var start = Random.Shared.Next(0, (endDate - startDate).Days + 1);
+            var end = Random.Shared.Next(start, (endDate - startDate).Days + 1);
+            var colour = new byte[3];
+            Random.Shared.NextBytes(colour);
+            Items.Add(new A
             {
-                var start = RandomTime();
-                var colour = new byte[3];
-                Random.Shared.NextBytes(colour);
-                Items.Add(new A
-                {
-                    Date = date,
-                    Start = start,
-                    End = RandomTime(minimum: start),
-                    Name = RandomString(),
-                    Colour = new SolidColorBrush(new Color(255, colour[0], colour[1], colour[2]))
-                });
-            }
+                Start = startDate.AddDays(start),
+                End = startDate.AddDays(end),
+                Name = RandomString(),
+                Colour = new SolidColorBrush(new Color(255, colour[0], colour[1], colour[2]))
+            });
         }
-        Columns = Items.Select(x => x.Date).Distinct().Reverse().ToArray();
     }
 
     private static string RandomString()