فهرست منبع

Adding Clock On / Off functionality to Home Screen

Frank van den Bos 3 هفته پیش
والد
کامیت
1d353c8304

+ 0 - 2
PRS.Avalonia/PRS.Avalonia.iOS.sln

@@ -74,8 +74,6 @@ Global
 		{EFF425D8-1394-49B3-8B80-E311AFE71591}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{EFF425D8-1394-49B3-8B80-E311AFE71591}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{EFF425D8-1394-49B3-8B80-E311AFE71591}.Release|Any CPU.Build.0 = Release|Any CPU
-		{EFF425D8-1394-49B3-8B80-E311AFE71591}.Publish|Any CPU.ActiveCfg = Publish|Any CPU
-		{EFF425D8-1394-49B3-8B80-E311AFE71591}.Publish|Any CPU.Build.0 = Publish|Any CPU
 		{AF7B7FFA-B47F-4E58-813E-E58AA7C2EA7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{AF7B7FFA-B47F-4E58-813E-E58AA7C2EA7E}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{AF7B7FFA-B47F-4E58-813E-E58AA7C2EA7E}.Release|Any CPU.ActiveCfg = Release|Any CPU

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

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
     <PropertyGroup>
         <OutputType>Exe</OutputType>
-        <TargetFrameworks>net8.0-ios;net9.0-ios</TargetFrameworks>
+        <TargetFrameworks>net8.0-ios</TargetFrameworks>
         <SupportedOSPlatformVersion>13.0</SupportedOSPlatformVersion>
         <Nullable>enable</Nullable>
         <Platforms>AnyCPU</Platforms>

+ 96 - 56
PRS.Avalonia/PRS.Avalonia/HomePage/HomePageView.axaml

@@ -5,7 +5,6 @@
              xmlns:viewModels="clr-namespace:PRS.Avalonia.Modules"
              xmlns:moduleGrid="clr-namespace:InABox.Avalonia.Components.ModuleGrid;assembly=InABox.Avalonia"
              xmlns:converters="clr-namespace:InABox.Avalonia.Converters;assembly=InABox.Avalonia"
-             xmlns:platform="clr-namespace:InABox.Avalonia.Platform;assembly=InABox.Avalonia.Platform"
              mc:Ignorable="d" d:DesignWidth="450" d:DesignHeight="1000"
              x:Class="PRS.Avalonia.Modules.HomePageView"
              x:DataType="viewModels:HomePageViewModel">
@@ -15,55 +14,95 @@
         <converters:BooleanToStringConverter x:Key="BooleanToStringConverter" True="CLOCK OFF" False="CLOCK ON" />
         <converters:BooleanMatcher x:Key="BooleanMatcher" Comparison="EqualTo" Type="All" />
     </UserControl.Resources>
-    <Grid>
-
-        <Grid.RowDefinitions>
-            <RowDefinition Height="200" />
-            <RowDefinition Height="Auto" />
-            <RowDefinition Height="Auto" />
-            <RowDefinition Height="*" />
-        </Grid.RowDefinitions>
-
-        <Grid.ColumnDefinitions>
-            <ColumnDefinition Width="*" />
-            <ColumnDefinition Width="100" />
-        </Grid.ColumnDefinitions>
+    <Grid 
+        RowDefinitions="200 Auto Auto *" 
+        ColumnDefinitions="* 100">
 
         <Button Classes="Standard"
             Grid.Row="0"
             Grid.Column="0"
             Grid.ColumnSpan="2"
-            
+            Padding="0"
             IsVisible="{Binding ClockOnVisible}"
             Background="{Binding IsClockedOn, Converter={StaticResource BooleanToColorConverter}}"
-            Command="{Binding ClockOnCommand}">
+            Command="{Binding ClockOnCommand}"
+            HorizontalContentAlignment="Stretch"
+            VerticalContentAlignment="Stretch">
             <Button.IsEnabled>
-                <MultiBinding Converter="{StaticResource BooleanMatcher}">
+                <MultiBinding Converter="{x:Static BoolConverters.And}">
                     <Binding Path="DataReady" />
-                    <Binding Path="CurrentLocation" Converter="{x:Static ObjectConverters.IsNotNull}" />
+                    <Binding Path="ClockOnLocation" Converter="{x:Static ObjectConverters.IsNotNull}" />
                 </MultiBinding>
             </Button.IsEnabled>
-            <StackPanel Orientation="Vertical">
-                <Label 
-                    FontSize="{StaticResource PrsFontSizeExtraLarge}"
-                    FontWeight="DemiBold"
-                    Content="{Binding IsClockedOn, Converter={StaticResource BooleanToStringConverter}}"
-                    HorizontalAlignment="Center"/>
-                <Label 
-                    FontSize="{StaticResource PrsFontSizeLarge}"
-                    FontWeight="DemiBold"
-                    Content="Missing/Invalid GPS Signal"
-                    IsVisible="{Binding CurrentLocation, Converter={x:Static ObjectConverters.IsNull}}"
-                    HorizontalAlignment="Center"/>
-
-                <Label 
-                    FontSize="{StaticResource PrsFontSizeLarge}"
-                    FontWeight="DemiBold"
-                    Content="{Binding CurrentJob.DisplayName, FallbackValue='No Job Site Detected'}"
-                    IsVisible="{Binding CurrentLocation, Converter={x:Static ObjectConverters.IsNotNull}}"
-                    HorizontalAlignment="Center"/>
+            
+            <Grid 
+                ColumnDefinitions="* Auto" 
+                RowDefinitions="* Auto"
+                HorizontalAlignment="Stretch"
+                VerticalAlignment="Stretch">
+                
+                <StackPanel 
+                    Orientation="Vertical"
+                    HorizontalAlignment="Center"
+                    VerticalAlignment="Center"
+                    Grid.Row="0"
+                    Grid.Column="0"
+                    Grid.RowSpan="2"
+                    Grid.ColumnSpan="2">
+                    
+                    <Label 
+                        FontSize="{StaticResource PrsFontSizeExtraLarge}"
+                        FontWeight="Bold"
+                        Content="{Binding IsClockedOn, Converter={StaticResource BooleanToStringConverter}}"
+                        HorizontalAlignment="Center"/>
+                    
+                    <Label 
+                        FontSize="{StaticResource PrsFontSizeNormal}"
+                        FontWeight="DemiBold"
+                        Content="{Binding ClockOnLocation}"
+                        HorizontalAlignment="Center">
+                        <Label.IsVisible>
+                            <MultiBinding Converter = "{x:Static BoolConverters.And}">
+                                <Binding Path="IsClockedOn" Converter="{x:Static BoolConverters.Not}" />
+                                <Binding Path="ClockOnLocation" Converter="{x:Static StringConverters.IsNotNullOrEmpty}" />
+                            </MultiBinding>
+                        </Label.IsVisible>
+                    </Label>
+                    
+                    <Label 
+                        FontSize="{StaticResource PrsFontSizeNormal}"
+                        FontWeight="DemiBold"
+                        Content="No Job Site Found"
+                        HorizontalAlignment="Center">
+                        <Label.IsVisible>
+                            <MultiBinding Converter="{x:Static BoolConverters.And}">
+                                <Binding Path="IsClockedOn" Converter="{x:Static BoolConverters.Not}" />
+                                <Binding Path="ClockOnLocation" Converter="{x:Static ObjectConverters.IsNull}" />
+                                <Binding Path="CurrentLocation" Converter="{x:Static ObjectConverters.IsNotNull}" />
+                            </MultiBinding>
+                        </Label.IsVisible>
+                    </Label>
+                    
+                    <Label 
+                        FontSize="{StaticResource PrsFontSizeNormal}"
+                        FontWeight="DemiBold"
+                        Content="Missing/Invalid GPS Signal"
+                        IsVisible="{Binding CurrentLocation, Converter={x:Static ObjectConverters.IsNull}}"
+                        HorizontalAlignment="Center"/>
+                    
+                </StackPanel>
                 
-            </StackPanel>
+                <ProgressBar
+                    Grid.Row="0"
+                    Grid.Column="1"
+                    Foreground="Black"
+                    Background="Transparent"
+                    Opacity="0.2"
+                    Maximum="{Binding GpsFrequency}"
+                    Value="{Binding GpsRemaining}"
+                    Orientation="Vertical"/>
+                
+            </Grid>
         </Button>
 
         <Border Classes="Standard"
@@ -78,45 +117,46 @@
         <Button Classes="Standard"
             Grid.Row="1"
             Grid.Column="0"
-            Content="Select Job"
+            Content="{Binding Repositories.CurrentAssignment.JobDisplay, FallbackValue='Select Job'}"
             IsVisible="{Binding ClockOnVisible}"
-            Background="{Binding IsClockedOn, Converter={StaticResource BooleanToColorConverter}}">
+            Background="{Binding IsClockedOn, Converter={StaticResource BooleanToColorConverter}}"
+            Command="{Binding SelectJobCommand}">
             <Button.IsEnabled>
-                <MultiBinding Converter="{StaticResource BooleanMatcher}">
+                <MultiBinding Converter="{x:Static BoolConverters.And}">
                     <Binding Path="DataReady" />
                     <Binding Path="IsClockedOn" />
                     <Binding Path="CurrentLocation" Converter="{x:Static ObjectConverters.IsNotNull}" />
                 </MultiBinding>
             </Button.IsEnabled>
         </Button>
-
+        
         <Button Classes="Standard"
-            Grid.Row="1"
-            Grid.Column="1"
-            Content="Add Note"
+            Grid.Row="2"
+            Grid.Column="0"
+            Content="{Binding Repositories.CurrentAssignment.TaskDisplay, FallbackValue='Select Task'}"
             IsVisible="{Binding ClockOnVisible}"
-            Background="{Binding IsClockedOn, Converter={StaticResource BooleanToColorConverter}}">
+            Background="{Binding IsClockedOn, Converter={StaticResource BooleanToColorConverter}}"
+            Command="{Binding SelectTaskCommand}">
             <Button.IsEnabled>
-                <MultiBinding Converter="{StaticResource BooleanMatcher}">
+                <MultiBinding Converter="{x:Static BoolConverters.And}">
                     <Binding Path="DataReady" />
                     <Binding Path="IsClockedOn" />
-                    <Binding Path="CurrentLocation" Converter="{x:Static ObjectConverters.IsNotNull}" />
                 </MultiBinding>
             </Button.IsEnabled>
         </Button>
-
+        
         <Button Classes="Standard"
-            Grid.Row="2"
-            Grid.Column="0"
-            Grid.ColumnSpan="2"
-            Content="Select Task"
+            Grid.Row="1"
+            Grid.Column="1"
+            Grid.RowSpan="2"
+            Content="Add Note"
             IsVisible="{Binding ClockOnVisible}"
-            Background="{Binding IsClockedOn, Converter={StaticResource BooleanToColorConverter}}">
+            Background="{Binding IsClockedOn, Converter={StaticResource BooleanToColorConverter}}"
+            Command="{Binding TimeSheetNotesCommand}">
             <Button.IsEnabled>
-                <MultiBinding Converter="{StaticResource BooleanMatcher}">
+                <MultiBinding Converter="{x:Static BoolConverters.And}">
                     <Binding Path="DataReady" />
                     <Binding Path="IsClockedOn" />
-                    <Binding Path="CurrentLocation" Converter="{x:Static ObjectConverters.IsNotNull}" />
                 </MultiBinding>
             </Button.IsEnabled>
         </Button>

+ 170 - 26
PRS.Avalonia/PRS.Avalonia/HomePage/HomePageViewModel.cs

@@ -2,6 +2,7 @@
 using System.Drawing;
 using System.Linq;
 using System.Threading.Tasks;
+using Avalonia.Controls;
 using Avalonia.Media;
 // using Avalonia.Svg.Skia;
 using Comal.Classes;
@@ -12,7 +13,8 @@ using ExCSS;
 using InABox.Avalonia;
 using InABox.Avalonia.Components;
 using InABox.Avalonia.Platform;
-using InABox.Core; 
+using InABox.Core;
+using PRS.Avalonia.Components;
 using PRS.Avalonia.Login;
 using PRS.Avalonia.Modules.MyHR;
 using PRS.Avalonia.Settings;
@@ -30,14 +32,7 @@ public partial class HomePageViewModel : ViewModelBase
     [ObservableProperty] 
     private AvaloniaModuleCollection _modules;
     
-    [ObservableProperty] private TimeSheetModel _timeSheets = new TimeSheetModel(
-        DataAccess, 
-        () => new Filter<TimeSheet>(x=>x.Date).IsEqualTo(DateTime.Today)
-            .And(x=>x.Finish).IsEqualTo(TimeSpan.Zero)
-            .And(x=>x.EmployeeLink.ID).IsEqualTo(Repositories.Me.ID)
-    );
-
-    public bool IsClockedOn => TimeSheets.Any();
+    public bool IsClockedOn => Repositories.CurrentTimeSheet != null;
 
     [ObservableProperty] 
     private GeoPoint?_currentLocation;
@@ -131,6 +126,7 @@ public partial class HomePageViewModel : ViewModelBase
     {
         await PlatformTools.Permissions.IsPermitted(Permission.Geolocation);
         PlatformTools.Geolocation.LocationChanged += LocationChanged;
+        PlatformTools.Geolocation.TimerChanged += TimerChanged;
         PlatformTools.Geolocation.Scanning = true;
         await base.OnActivated();
     }
@@ -139,20 +135,61 @@ public partial class HomePageViewModel : ViewModelBase
     {
         PlatformTools.Geolocation.Scanning = false;
         PlatformTools.Geolocation.LocationChanged -= LocationChanged;
+        PlatformTools.Geolocation.TimerChanged -= TimerChanged;
         return base.OnDeactivated();
     }
 
-    private void LocationChanged(object? sender, EventArgs e)
+    private void TimerChanged(object sender, GeoLocationTimerArgs e)
+    {
+        GpsFrequency = e.Frequency.TotalMilliseconds;
+        GpsRemaining = e.Remaining.TotalMilliseconds;
+    }
+
+    [ObservableProperty] 
+    private double _gpsFrequency;
+
+    [ObservableProperty] 
+    private double _gpsRemaining;
+    
+    private void LocationChanged(object? sender, GeoLocationChangedArgs e)
+    {
+        DoLocationChanged();
+    }
+
+    private void DoLocationChanged()
     {
         CurrentLocation = PlatformTools.Geolocation.CurrentLocation;
-        if (CurrentLocation != null)
-        {
-            CurrentJob = Repositories.Jobs.Items.FirstOrDefault(x => x.Geofence?.Contains(CurrentLocation) == true);
-        }
+        CurrentJob = CurrentLocation != null
+            ? Repositories.Jobs.Items.FirstOrDefault(x => x.Geofence?.Contains(CurrentLocation) == true)
+            : null;
+        OnPropertyChanged(nameof(ClockOnLocation));
     }
 
     [ObservableProperty] private JobShell? _currentJob;
 
+    public string? ClockOnLocation
+    {
+        get
+        {
+            if (CurrentLocation == null)
+                return null;
+            
+            if (CurrentJob != null)
+                return CurrentJob.DisplayName;
+
+            var info = Repositories.CompanyInformation.Items.FirstOrDefault();
+            if (info?.Geofence?.Coordinates.Any() != true)
+                return null;
+            if (info.Geofence.Contains(CurrentLocation))
+                return info.CompanyName;
+            
+            if (Security.IsAllowed<CanBypassGPSClockIn>() || IsClockedOn)
+                return "";
+            
+            return null;
+        }
+    }
+
     protected override async Task<TimeSpan> OnRefresh()
     {
         if (_clockingOn)
@@ -172,10 +209,17 @@ public partial class HomePageViewModel : ViewModelBase
                     : "";
 
             }),
+            
+            Task.Run(async () =>
+            {
+                await Repositories.RefreshCurrentTimeSheet();
+            }),
+            
             Task.Run(async () =>
             {
-                await TimeSheets.RefreshAsync(true);
+                await Repositories.RefreshCurrentAssignmentAsync();
             }),
+            
             Task.Run(async () =>
             {
                 await Repositories.Jobs.RefreshAsync(true);
@@ -183,6 +227,8 @@ public partial class HomePageViewModel : ViewModelBase
         };
         Task.WaitAll(tasks);
         
+        DoLocationChanged();
+        
         var result =  await base.OnRefresh();
         
         OnPropertyChanged(nameof(IsClockedOn));
@@ -195,7 +241,7 @@ public partial class HomePageViewModel : ViewModelBase
     [RelayCommand]
     private async Task ClockOn()
     {
-        if (!DataReady)
+        if (!DataReady || _clockingOn)
             return;
 
         _clockingOn = true;
@@ -205,19 +251,29 @@ public partial class HomePageViewModel : ViewModelBase
 
         if (IsClockedOn)
         {
-            foreach (var ts in TimeSheets.Items)
-                ts.ActualFinish = DateTime.Now.TimeOfDay;
-            await TimeSheets.SaveAsync("Clocked off from Mobile App");
-            await TimeSheets.RefreshAsync(true);
+            await Repositories.CloseTimeSheetAsync();
+            await Repositories.CloseAssignmentAsync();
         }
         else
         {
-            var tsShell = TimeSheets.CreateItem();
-            tsShell.Date = DateTime.Today;
-            tsShell.ActualStart = DateTime.Now.TimeOfDay;
-            tsShell.EmployeeID = Repositories.Me.ID;
-            TimeSheets.CommitItem(tsShell);
-            await TimeSheets.SaveAsync("Clocked on from Mobile App");
+            await Repositories.CreateTimeSheetAsync(tsShell =>
+            {
+                tsShell.JobID = CurrentJob?.ID ?? Guid.Empty;
+                tsShell.JobNumber = CurrentJob?.JobNumber ?? "";
+                tsShell.JobName = CurrentJob?.Name ?? "";
+            });
+            
+            if (CurrentJob != null)
+            {
+                await Repositories.CreateAssignmentAsync(assShell =>
+                {
+            
+                    assShell.JobID = CurrentJob?.ID ?? Guid.Empty;
+                    assShell.JobNumber = CurrentJob?.JobNumber ?? "";
+                    assShell.JobName = CurrentJob?.Name ?? "";
+                });
+            
+            }
         }
 
         _clockingOn = false;
@@ -227,4 +283,92 @@ public partial class HomePageViewModel : ViewModelBase
         OnPropertyChanged(nameof(IsClockedOn));
 
     }
+
+    [RelayCommand]
+    private async Task SelectJob()
+    {
+        var job = (await SelectionViewModel.ExecutePopup<JobShell>(model =>
+        {
+            model.Columns.BeginUpdate()
+                .Add(new AvaloniaDataGridTextColumn<JobShell>
+                {
+                    Column = x => x.JobNumber,
+                    Caption = "Number",
+                    Width = GridLength.Auto
+                })
+                .Add(new AvaloniaDataGridTextColumn<JobShell>
+                {
+                    Column = x => x.Name,
+                    Caption = "Name",
+                    Width = GridLength.Star
+                })
+                .EndUpdate();
+            model.AddFilters(Repositories.Jobs.AvailableFilters.Select(x => x.Name).NotNull());
+        }, args =>
+        {
+            Repositories.Jobs.SelectFilter(args.Filter);
+            return Repositories.Jobs.Refresh(args.Force);
+        }))?.FirstOrDefault();
+        if (job is null) 
+            return;
+        await Repositories.CloseAssignmentAsync();
+        await Repositories.CreateAssignmentAsync(ass =>
+        {
+            ass.JobID = job.ID;
+            ass.JobNumber = job.JobNumber;
+            ass.JobName = job.Name;
+        });
+    }
+
+    [RelayCommand]
+    private async Task SelectTask()
+    {
+        var task = (await SelectionViewModel.ExecutePopup<KanbanShell>(model =>
+        {
+            model.Columns.BeginUpdate()
+                .Add(new AvaloniaDataGridTextColumn<KanbanShell>
+                {
+                    Column = x => x.JobNumber,
+                    Caption = "Job",
+                    Width = GridLength.Auto
+                })
+                .Add(new AvaloniaDataGridIntegerColumn<KanbanShell>
+                {
+                    Column = x => x.Number,
+                    Caption = "Number",
+                    Width = GridLength.Auto,
+                    Format = "F0"
+                })
+                .Add(new AvaloniaDataGridTextColumn<KanbanShell>
+                {
+                    Column = x => x.Title,
+                    Caption = "Title",
+                    Width = GridLength.Star
+                })
+                .EndUpdate();
+            model.AddFilters(Repositories.MyTasks.AvailableFilters.Select(x => x.Name).NotNull());
+        }, args =>
+        {
+            Repositories.MyTasks.SelectFilter(args.Filter);
+            return Repositories.MyTasks.Refresh(args.Force);
+        }))?.FirstOrDefault();
+        if (task is null) 
+            return;
+        await Repositories.CloseAssignmentAsync();
+        await Repositories.CreateAssignmentAsync(ass =>
+        {
+            ass.JobID = task.JobID;
+            ass.JobNumber = task.JobNumber;
+            ass.JobName = task.JobName;
+            ass.TaskID = task.ID;
+            ass.TaskNumber = task.Number;
+            ass.TaskName = task.Title;
+        });
+    }
+    
+    [RelayCommand]
+    private async Task TimeSheetNotes()
+    {
+        
+    }
 }

+ 13 - 0
PRS.Avalonia/PRS.Avalonia/Repositories/CompanyInformation/CompanyInformationModel.cs

@@ -0,0 +1,13 @@
+using System;
+using Comal.Classes;
+using InABox.Avalonia;
+using InABox.Core;
+
+namespace PRS.Avalonia;
+
+public class CompanyInformationModel : CoreRepository<CompanyInformationModel, CompanyInformationShell, CompanyInformation>
+{
+    public CompanyInformationModel(IModelHost host, Func<Filter<CompanyInformation>> filter, Func<string>? filename = null) : base(host, filter, filename)
+    {
+    }
+}

+ 35 - 0
PRS.Avalonia/PRS.Avalonia/Repositories/CompanyInformation/CompanyInformationShell.cs

@@ -0,0 +1,35 @@
+using System;
+using System.Linq;
+using InABox.Avalonia;
+using InABox.Core;
+
+namespace PRS.Avalonia;
+
+public class CompanyInformationShell : Shell<CompanyInformationModel, CompanyInformation>
+{
+
+    public string CompanyName
+    {
+        get => Get<String>();
+        set => Set(value);
+    }
+    
+    private GeoFenceDefinition? _geofence;
+    public GeoFenceDefinition? Geofence {
+        get
+        {
+            var json = Get<String>();
+            if (_geofence?.Coordinates.Any() != true && json.Length > "{\"Coordinates\":[]}".Length)
+                _geofence = Serialization.Deserialize<GeoFenceDefinition>(json) ?? new GeoFenceDefinition();
+            return _geofence;
+            
+        }
+    }
+
+    protected override void ConfigureColumns(ShellColumns<CompanyInformationModel, CompanyInformation> columns)
+    {
+        columns
+            .Map(nameof(Geofence), x => x.DeliveryAddress.Geofence)
+            .Map(nameof(CompanyName), x => x.CompanyName);
+    }
+}

+ 3 - 1
PRS.Avalonia/PRS.Avalonia/Repositories/Job/JobShell.cs

@@ -1,7 +1,9 @@
 using System;
+using System.Linq;
 using Comal.Classes;
 using InABox.Avalonia;
 using InABox.Core;
+using ReactiveUI;
 
 namespace PRS.Avalonia;
 
@@ -29,7 +31,7 @@ public class JobShell : Shell<JobModel, Job>, ILookupShell
         get
         {
             var json = Get<String>();
-            if (_geofence == null && !string.IsNullOrWhiteSpace(json))
+            if (_geofence?.Coordinates.Any() != true && json.Length > "{\"Coordinates\":[]}".Length)
                 _geofence = Serialization.Deserialize<GeoFenceDefinition>(json) ?? new GeoFenceDefinition();
             return _geofence;
             

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

@@ -1,11 +1,14 @@
 using System;
 using System.Linq;
+using System.Threading.Tasks;
+using Avalonia.Threading;
 using Comal.Classes;
 using CommunityToolkit.Mvvm.ComponentModel;
 using InABox.Avalonia;
 using InABox.Clients;
 using InABox.Core;
 using PRS.Avalonia.Components.FormsEditor;
+using ReactiveUI;
 
 namespace PRS.Avalonia;
 
@@ -24,6 +27,9 @@ public partial class RepositoryLayer : ObservableObject
     public RepositoryLayer(IModelHost data)
     {
         Data = data;
+        
+        CompanyInformation = new CompanyInformationModel(Data,
+            () => new Filter<CompanyInformation>().All());
 
         MeModel = new EmployeeDetailModel(Data,
             () => new Filter<Employee>(x => x.UserLink.ID).IsEqualTo(ClientFactory.UserGuid));
@@ -50,10 +56,28 @@ public partial class RepositoryLayer : ObservableObject
             () => JobFilter(),
             () => DefaultCacheFileName<JobShell>()        
         );
+        
+        _currentTimeSheets = new TimeSheetModel(
+            Data, 
+            () => new Filter<TimeSheet>(x=>x.Date).IsEqualTo(DateTime.Today)
+                .And(x=>x.Finish).IsEqualTo(TimeSpan.Zero)
+                .And(x=>x.EmployeeLink.ID).IsEqualTo(Me.ID)
+        );
+        
+        _currentAssignments = new AssignmentModel(
+            Data, 
+            () => new Filter<Assignment>(x=>x.Date).IsEqualTo(DateTime.Today)
+                .And(x=>x.Actual.Start).IsNotEqualTo(TimeSpan.Zero)
+                .And(x=>x.Completed).IsEqualTo(DateTime.MinValue)
+                .And(x=>x.EmployeeLink.ID).IsEqualTo(Me.ID)
+        );
     }
 
-    public EmployeeDetailModel MeModel { get; private set; }
 
+    public CompanyInformationModel CompanyInformation { get; private set; }
+    
+    public EmployeeDetailModel MeModel { get; private set; }
+    
     public EmployeeDetailShell? MaybeMe
     {
         get
@@ -92,4 +116,95 @@ public partial class RepositoryLayer : ObservableObject
                 .And(x => x.Active).IsEqualTo(true)
                 .And(x => x.ID).InQuery(new Filter<DigitalFormLayout>(x => x.Active).IsEqualTo(true), x => x.Form.ID),
             () => DefaultCacheFileName<DigitalFormShell>());
+
+    #region Current TimeSheet
+
+    private readonly TimeSheetModel _currentTimeSheets;
+
+    public TimeSheetShell? CurrentTimeSheet => _currentTimeSheets.FirstOrDefault();
+    
+    public async Task RefreshCurrentTimeSheet()
+    {
+        await _currentTimeSheets.RefreshAsync(true);
+        OnPropertyChanged(nameof(CurrentTimeSheet));
+    }
+
+    public async Task CreateTimeSheetAsync(Action<TimeSheetShell> populate)
+    {
+        var tsShell = _currentTimeSheets.CreateItem();
+        tsShell.Date = DateTime.Today;
+        tsShell.ActualStart = TimeSpan.FromMinutes(Math.Floor(DateTime.Now.TimeOfDay.TotalMinutes));
+        tsShell.EmployeeID = Me.ID;
+        populate(tsShell);
+        _currentTimeSheets.CommitItem(tsShell);
+        await _currentTimeSheets.SaveAsync("Clocked on from Mobile App");
+        OnPropertyChanged(nameof(CurrentTimeSheet));
+    }
+
+    public async Task CloseTimeSheetAsync()
+    {
+        if (CurrentTimeSheet == null)
+            return;
+        CurrentTimeSheet.ActualFinish = TimeSpan.FromMinutes(Math.Floor(DateTime.Now.TimeOfDay.TotalMinutes));
+        await _currentTimeSheets.SaveAsync("Clocked off from Mobile App");
+        _currentTimeSheets.Reset();
+        OnPropertyChanged(nameof(CurrentTimeSheet));
+    }
+    
+    #endregion
+    
+    #region Current Assignment
+
+    private readonly AssignmentModel _currentAssignments;
+
+    public AssignmentShell? CurrentAssignment =>
+        _currentAssignments.FirstOrDefault();
+    
+    public async Task RefreshCurrentAssignmentAsync()
+    {
+        if (CurrentAssignment != null)
+        {
+            var newminutes = (int)Math.Floor(DateTime.Now.TimeOfDay.TotalMinutes);
+            var oldminutes = (int)Math.Floor(CurrentAssignment.ActualFinish.TotalMinutes);
+            if (newminutes > oldminutes)
+            {
+                CurrentAssignment.ActualFinish = TimeSpan.FromMinutes(newminutes);
+                await _currentAssignments.SaveAsync("");
+            }
+        }
+        await _currentAssignments.RefreshAsync(true);
+        OnPropertyChanged(nameof(CurrentAssignment));
+    }
+
+    public async Task CreateAssignmentAsync(Action<AssignmentShell> populate)
+    {
+        var assShell = _currentAssignments.CreateItem();
+        
+        assShell.Date = DateTime.Today;
+        assShell.ActualStart = TimeSpan.FromMinutes(Math.Floor(DateTime.Now.TimeOfDay.TotalMinutes));
+        assShell.ActualFinish = assShell.ActualStart;
+        assShell.EmployeeID = Me.ID;
+        
+        populate(assShell);       
+        
+        _currentAssignments.CommitItem(assShell);
+        await _currentAssignments.SaveAsync("Clocked on from Mobile App");
+        OnPropertyChanged(nameof(CurrentAssignment));
+    }
+    
+    
+    public async Task CloseAssignmentAsync()
+    {
+        if (CurrentAssignment == null)
+            return;
+        
+        CurrentAssignment.ActualFinish = TimeSpan.FromMinutes(Math.Floor(DateTime.Now.TimeOfDay.TotalMinutes));
+        CurrentAssignment.Completed = DateTime.Now;
+        
+        await _currentAssignments.SaveAsync("Clocked off from Mobile App");
+        _currentAssignments.Reset();
+        OnPropertyChanged(nameof(CurrentAssignment));
+    }
+    
+    #endregion
 }

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

@@ -102,6 +102,7 @@ public abstract partial class ViewModelBase : ObservableValidator, IViewModelBas
     private static void Validated()
     {
         Repositories.MeModel.Refresh(true);
+        Repositories.CompanyInformation.Refresh(true);
     }
 
     private static void Disconnected()