Finished capture page
This commit is contained in:
@@ -3,6 +3,7 @@ using MauiIcons.Material;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using WorkTime.Database;
|
||||
using WorkTime.Mobile.Pages;
|
||||
using WorkTime.Mobile.Pages.Components;
|
||||
|
||||
namespace WorkTime.Mobile;
|
||||
|
||||
@@ -22,6 +23,7 @@ public static class MauiProgram {
|
||||
|
||||
builder.Services.AddTransient<CapturePageModel>();
|
||||
builder.Services.AddTransient<SettingsPageModel>();
|
||||
builder.Services.AddTransient<AddEntryModel>();
|
||||
|
||||
#if DEBUG
|
||||
builder.Logging.AddDebug();
|
||||
|
||||
@@ -15,23 +15,28 @@
|
||||
Command="{Binding LoadDateCommand}" />
|
||||
|
||||
<ScrollView Grid.Row="1">
|
||||
<CollectionView ItemsSource="{Binding Entries}">
|
||||
<CollectionView ItemsSource="{Binding Entries}"
|
||||
SelectionMode="Single"
|
||||
SelectedItem="{Binding SelectedEntry, Mode=TwoWay}"
|
||||
SelectionChangedCommand="{Binding DeleteEntryCommand}"
|
||||
SelectionChangedCommandParameter="{Binding SelectedEntry}">
|
||||
<CollectionView.ItemsLayout>
|
||||
<LinearItemsLayout Orientation="Vertical" ItemSpacing="20" />
|
||||
</CollectionView.ItemsLayout>
|
||||
<CollectionView.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:TimeEntry">
|
||||
<HorizontalStackLayout Spacing="5">
|
||||
<Label Text="{Binding Timestamp}" />
|
||||
<Label Text="{Binding Type}" />
|
||||
</HorizontalStackLayout>
|
||||
<components:CaptureEntry />
|
||||
</DataTemplate>
|
||||
</CollectionView.ItemTemplate>
|
||||
</CollectionView>
|
||||
</ScrollView>
|
||||
|
||||
<HorizontalStackLayout Grid.Row="2" HorizontalOptions="Center" Spacing="20">
|
||||
<HorizontalStackLayout Grid.Row="2" HorizontalOptions="Center" Spacing="20" IsVisible="{Binding IsToday}">
|
||||
<Button Text="{Binding CurrentTypeName}"
|
||||
Command="{Binding RegisterEntryCommand}" />
|
||||
|
||||
<Button mi:MauiIcon.Value="{mi:Material Icon=Add}" />
|
||||
<Button mi:MauiIcon.Value="{mi:Material Icon=Add}"
|
||||
Command="{Binding OpenPopupCommand}" />
|
||||
</HorizontalStackLayout>
|
||||
</Grid>
|
||||
</ContentPage.Content>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using WorkTime.Mobile.Pages.Components;
|
||||
using WorkTime.Models;
|
||||
using WorkTime.Models.Repositories;
|
||||
|
||||
@@ -17,21 +18,32 @@ public partial class CapturePage : ContentPage {
|
||||
|
||||
protected override void OnAppearing() {
|
||||
base.OnAppearing();
|
||||
_model.Navigation = Navigation;
|
||||
_model.Window = Window!;
|
||||
|
||||
if (_model.AppearingCommand.CanExecute(null))
|
||||
_model.AppearingCommand.Execute(null);
|
||||
}
|
||||
}
|
||||
|
||||
public partial class CapturePageModel(ITimeEntryRepository entryRepository) : ObservableObject {
|
||||
public partial class CapturePageModel(ITimeEntryRepository entryRepository, IServiceProvider provider) : ObservableObject {
|
||||
|
||||
private DateOnly _currentDate = DateOnly.FromDateTime(DateTime.Now);
|
||||
|
||||
public INavigation Navigation = null!;
|
||||
public Window Window = null!;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<TimeEntry> Entries { get; set; } = new();
|
||||
|
||||
[ObservableProperty]
|
||||
public partial EntryType CurrentType { get; set; } = EntryType.Login;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsToday { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial TimeEntry? SelectedEntry { get; set; }
|
||||
|
||||
public string CurrentTypeName => CurrentType switch {
|
||||
EntryType.Login => "Einstempeln",
|
||||
@@ -48,8 +60,9 @@ public partial class CapturePageModel(ITimeEntryRepository entryRepository) : Ob
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task LoadDate(DateOnly date) {
|
||||
private async Task LoadDate(DateOnly date) {
|
||||
_currentDate = date;
|
||||
IsToday = date == DateOnly.FromDateTime(DateTime.Today);
|
||||
Entries.Clear();
|
||||
|
||||
var result = await entryRepository.GetTimeEntries(date);
|
||||
@@ -61,7 +74,8 @@ public partial class CapturePageModel(ITimeEntryRepository entryRepository) : Ob
|
||||
}
|
||||
|
||||
private void UpdateCurrentType() {
|
||||
var last = Entries.LastOrDefault();
|
||||
var last = Entries
|
||||
.LastOrDefault(e => e.Timestamp <= DateTime.Now);
|
||||
|
||||
if (last is null) {
|
||||
CurrentType = EntryType.Login;
|
||||
@@ -82,12 +96,12 @@ public partial class CapturePageModel(ITimeEntryRepository entryRepository) : Ob
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task OnAppearing() {
|
||||
private async Task OnAppearing() {
|
||||
await LoadDate(_currentDate);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task RegisterEntry(TimeEntry? entry = null) {
|
||||
private async Task RegisterEntry(TimeEntry? entry = null) {
|
||||
entry ??= new TimeEntry {
|
||||
Timestamp = DateTime.Now,
|
||||
Type = CurrentType
|
||||
@@ -98,9 +112,32 @@ public partial class CapturePageModel(ITimeEntryRepository entryRepository) : Ob
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task DeleteEntry(Guid entryId) {
|
||||
await entryRepository.DeleteTimeEntry(entryId);
|
||||
private async Task DeleteEntry(TimeEntry entry) {
|
||||
SelectedEntry = null;
|
||||
if (!IsToday) return;
|
||||
|
||||
var confirmed = await Window.Page!.DisplayAlertAsync(
|
||||
"Achtung!",
|
||||
"Möchten sie diesen Eintrag wirklich löschen?",
|
||||
"Löschen",
|
||||
"Abbrechen");
|
||||
|
||||
if (!confirmed) return;
|
||||
|
||||
await entryRepository.DeleteTimeEntry(entry.Id);
|
||||
await LoadDate(_currentDate);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task OpenPopup() {
|
||||
var modal = new AddEntryModal(provider.GetRequiredService<AddEntryModel>());
|
||||
await Navigation.PushModalAsync(modal);
|
||||
|
||||
var result = await modal.Result.Task;
|
||||
await Navigation.PopModalAsync();
|
||||
|
||||
if (result is not null)
|
||||
await RegisterEntry(result);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
48
src/WorkTime.Mobile/Pages/Components/AddEntryModal.xaml
Normal file
48
src/WorkTime.Mobile/Pages/Components/AddEntryModal.xaml
Normal file
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=System.Runtime"
|
||||
xmlns:components="clr-namespace:WorkTime.Mobile.Pages.Components"
|
||||
x:Class="WorkTime.Mobile.Pages.Components.AddEntryModal"
|
||||
x:DataType="components:AddEntryModel">
|
||||
<ContentPage.Content>
|
||||
<Grid Padding="20" RowSpacing="15" RowDefinitions="Auto,Auto,*,Auto">
|
||||
<Label Grid.Row="0" Text="Neuen Eintrag hinzufügen" FontAttributes="Bold" />
|
||||
|
||||
<VerticalStackLayout Grid.Row="1">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<Label Grid.Column="0" Text="Uhrzeit" VerticalOptions="Center" />
|
||||
|
||||
<TimePicker Grid.Column="1" VerticalOptions="Center" Time="{Binding Time, Mode=TwoWay}" />
|
||||
</Grid>
|
||||
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<Label Grid.Column="0" Text="Stempeltyp" VerticalOptions="Center" />
|
||||
|
||||
<Picker Grid.Column="1" VerticalOptions="Center" SelectedIndex="{Binding SelectedType, Mode=TwoWay}">
|
||||
<Picker.ItemsSource>
|
||||
<x:Array Type="{x:Type system:String}">
|
||||
<system:String>Einstempeln</system:String>
|
||||
<system:String>Ausstempeln</system:String>
|
||||
<system:String>Dienstreise starten</system:String>
|
||||
<system:String>Dienstreise beenden</system:String>
|
||||
</x:Array>
|
||||
</Picker.ItemsSource>
|
||||
</Picker>
|
||||
</Grid>
|
||||
|
||||
<Grid ColumnDefinitions="*,Auto" IsVisible="{Binding MobaEnabled}">
|
||||
<Label Grid.Column="0" Text="Mobiles Arbeiten" VerticalOptions="Center" />
|
||||
|
||||
<CheckBox Grid.Column="1" VerticalOptions="Center" HorizontalOptions="End" IsChecked="{Binding IsMoba, Mode=TwoWay}" />
|
||||
</Grid>
|
||||
</VerticalStackLayout>
|
||||
|
||||
<HorizontalStackLayout Grid.Row="3" Spacing="10" HorizontalOptions="Center" Margin="0, 0, 0, 30">
|
||||
<Button Text="Abbrechen" Command="{Binding CancelCommand}" />
|
||||
<Button Text="Speichern" Command="{Binding SubmitCommand}" />
|
||||
</HorizontalStackLayout>
|
||||
</Grid>
|
||||
</ContentPage.Content>
|
||||
</ContentPage>
|
||||
105
src/WorkTime.Mobile/Pages/Components/AddEntryModal.xaml.cs
Normal file
105
src/WorkTime.Mobile/Pages/Components/AddEntryModal.xaml.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using WorkTime.Models;
|
||||
using WorkTime.Models.Repositories;
|
||||
|
||||
namespace WorkTime.Mobile.Pages.Components;
|
||||
|
||||
public partial class AddEntryModal : ContentPage {
|
||||
public TaskCompletionSource<TimeEntry?> Result = new();
|
||||
private readonly AddEntryModel _model;
|
||||
|
||||
public AddEntryModal(AddEntryModel model) {
|
||||
InitializeComponent();
|
||||
BindingContext = model;
|
||||
model.ResultSource = Result;
|
||||
_model = model;
|
||||
}
|
||||
|
||||
protected override void OnAppearing() {
|
||||
base.OnAppearing();
|
||||
|
||||
if (_model.AppearingCommand.CanExecute(null))
|
||||
_model.AppearingCommand.Execute(null);
|
||||
}
|
||||
}
|
||||
|
||||
public partial class AddEntryModel(ITimeEntryRepository entryRepository) : ObservableObject {
|
||||
public TaskCompletionSource<TimeEntry?> ResultSource { get; set; } = null!;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial int SelectedType { get; set; } = 0;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsMoba { get; set; } = false;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool MobaEnabled { get; set; } = true;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial TimeSpan Time { get; set; } = DateTime.Now.TimeOfDay;
|
||||
|
||||
[RelayCommand]
|
||||
private async Task OnAppearing() {
|
||||
var entries = await entryRepository.GetTimeEntries(DateOnly.FromDateTime(DateTime.Now));
|
||||
|
||||
var last = entries
|
||||
.OrderBy(e => e.Timestamp)
|
||||
.LastOrDefault();
|
||||
|
||||
if (last is not null) {
|
||||
SelectedType = last.Type switch {
|
||||
EntryType.Login or EntryType.LoginHome => 1,
|
||||
EntryType.Logout or EntryType.LogoutHome => 0,
|
||||
EntryType.LoginTrip => 3,
|
||||
EntryType.LogoutTrip => 0,
|
||||
_ => 0
|
||||
};
|
||||
|
||||
IsMoba = last.Type is EntryType.LoginHome or EntryType.LogoutHome;
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnSelectedTypeChanged(int value) {
|
||||
MobaEnabled = value < 2;
|
||||
|
||||
if (!MobaEnabled)
|
||||
IsMoba = false;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void Submit() {
|
||||
var type = SelectedType switch {
|
||||
0 => EntryType.Login,
|
||||
1 => EntryType.Logout,
|
||||
2 => EntryType.LoginTrip,
|
||||
3 => EntryType.LogoutTrip,
|
||||
_ => EntryType.Login
|
||||
};
|
||||
|
||||
if (IsMoba) {
|
||||
type = type switch {
|
||||
EntryType.Login => EntryType.LoginHome,
|
||||
EntryType.Logout => EntryType.LogoutHome,
|
||||
_ => type
|
||||
};
|
||||
}
|
||||
|
||||
var entry = new TimeEntry {
|
||||
Timestamp = DateTime.Today + Time,
|
||||
Type = type
|
||||
};
|
||||
|
||||
ResultSource.SetResult(entry);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void Cancel() {
|
||||
ResultSource.SetResult(null);
|
||||
}
|
||||
}
|
||||
33
src/WorkTime.Mobile/Pages/Components/CaptureEntry.xaml
Normal file
33
src/WorkTime.Mobile/Pages/Components/CaptureEntry.xaml
Normal file
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:models="clr-namespace:WorkTime.Models;assembly=WorkTime.Models"
|
||||
xmlns:icons="http://www.aathifmahir.com/dotnet/2022/maui/icons"
|
||||
x:Class="WorkTime.Mobile.Pages.Components.CaptureEntry"
|
||||
x:DataType="models:TimeEntry">
|
||||
<Border StrokeShape="RoundRectangle 15" StrokeThickness="0">
|
||||
<Grid ColumnDefinitions="Auto,Auto,Auto,*,Auto,Auto" ColumnSpacing="15"
|
||||
BackgroundColor="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}"
|
||||
Padding="10">
|
||||
<Border
|
||||
Grid.Column="0"
|
||||
BackgroundColor="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}"
|
||||
WidthRequest="15" HeightRequest="15"
|
||||
VerticalOptions="Center"
|
||||
StrokeShape="RoundRectangle 7.5"/>
|
||||
|
||||
<icons:MauiIcon Grid.Column="1" Icon="{icons:Material Icon=Home}" VerticalOptions="Center" IsVisible="{Binding IsHomeEntry}" />
|
||||
|
||||
<Label Grid.Column="2" Text="{Binding DisplayName}" VerticalOptions="Center" />
|
||||
|
||||
<HorizontalStackLayout Grid.Column="4" VerticalOptions="Center">
|
||||
<Label Text="{Binding Timestamp.Hour}" />
|
||||
<Label Text=":" />
|
||||
<Label Text="{Binding Timestamp.Minute}" />
|
||||
</HorizontalStackLayout>
|
||||
|
||||
<icons:MauiIcon Grid.Column="5" Icon="{icons:Material Icon=Delete}" VerticalOptions="Center" IsVisible="{Binding IsToday}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</ContentView>
|
||||
14
src/WorkTime.Mobile/Pages/Components/CaptureEntry.xaml.cs
Normal file
14
src/WorkTime.Mobile/Pages/Components/CaptureEntry.xaml.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using WorkTime.Models;
|
||||
|
||||
namespace WorkTime.Mobile.Pages.Components;
|
||||
|
||||
public partial class CaptureEntry : ContentView {
|
||||
public CaptureEntry() {
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 23 KiB |
Reference in New Issue
Block a user