Started working on main page

This commit is contained in:
2025-11-12 20:12:03 +01:00
parent e0bf95060a
commit 969219137a
148 changed files with 1053 additions and 93 deletions

View File

@@ -0,0 +1,9 @@
<?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"
x:Class="WorkTime.Mobile.Pages.AnalysisPage">
<ContentPage.Content>
</ContentPage.Content>
</ContentPage>

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WorkTime.Mobile.Pages;
public partial class AnalysisPage : ContentPage {
public AnalysisPage() {
InitializeComponent();
}
}

View File

@@ -0,0 +1,38 @@
<?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:components="clr-namespace:WorkTime.Mobile.Pages.Components"
xmlns:pages="clr-namespace:WorkTime.Mobile.Pages"
xmlns:mi="http://www.aathifmahir.com/dotnet/2022/maui/icons"
xmlns:models="clr-namespace:WorkTime.Models;assembly=WorkTime.Models"
x:Class="WorkTime.Mobile.Pages.CapturePage"
x:DataType="pages:CapturePageModel">
<ContentPage.Content>
<Grid Margin="10" RowSpacing="10" RowDefinitions="Auto,*,Auto">
<components:DateSelector Grid.Row="0"
Command="{Binding LoadDateCommand}" />
<ScrollView Grid.Row="1">
<CollectionView ItemsSource="{Binding Entries}">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:TimeEntry">
<HorizontalStackLayout Spacing="5">
<Label Text="{Binding Timestamp}" />
<Label Text="{Binding Type}" />
</HorizontalStackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</ScrollView>
<HorizontalStackLayout Grid.Row="2" HorizontalOptions="Center" Spacing="20">
<Button Text="{Binding CurrentTypeName}"
Command="{Binding RegisterEntryCommand}" />
<Button mi:MauiIcon.Value="{mi:Material Icon=Add}" />
</HorizontalStackLayout>
</Grid>
</ContentPage.Content>
</ContentPage>

View File

@@ -0,0 +1,106 @@
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using WorkTime.Models;
using WorkTime.Models.Repositories;
namespace WorkTime.Mobile.Pages;
public partial class CapturePage : ContentPage {
private readonly CapturePageModel _model;
public CapturePage(CapturePageModel model) {
InitializeComponent();
BindingContext = model;
_model = model;
}
protected override void OnAppearing() {
base.OnAppearing();
if (_model.AppearingCommand.CanExecute(null))
_model.AppearingCommand.Execute(null);
}
}
public partial class CapturePageModel(ITimeEntryRepository entryRepository) : ObservableObject {
private DateOnly _currentDate = DateOnly.FromDateTime(DateTime.Now);
[ObservableProperty]
public partial ObservableCollection<TimeEntry> Entries { get; set; } = new();
[ObservableProperty]
public partial EntryType CurrentType { get; set; } = EntryType.Login;
public string CurrentTypeName => CurrentType switch {
EntryType.Login => "Einstempeln",
EntryType.LoginHome => "Einstempeln (mobA)",
EntryType.LoginTrip => "Dienstreise starten",
EntryType.Logout => "Ausstempeln",
EntryType.LogoutHome => "Ausstempeln (mobA)",
EntryType.LogoutTrip => "Dienstreise beenden",
_ => "UNKNOWN"
};
partial void OnCurrentTypeChanged(EntryType value) {
OnPropertyChanged(nameof(CurrentTypeName));
}
[RelayCommand]
public async Task LoadDate(DateOnly date) {
_currentDate = date;
Entries.Clear();
var result = await entryRepository.GetTimeEntries(date);
foreach (var entry in result) {
Entries.Add(entry);
}
UpdateCurrentType();
}
private void UpdateCurrentType() {
var last = Entries.LastOrDefault();
if (last is null) {
CurrentType = EntryType.Login;
return;
}
CurrentType = last.Type switch {
EntryType.Login => EntryType.Logout,
EntryType.LoginHome => EntryType.LogoutHome,
EntryType.LoginTrip => EntryType.LogoutTrip,
EntryType.Logout => EntryType.Login,
EntryType.LogoutHome => EntryType.LoginHome,
EntryType.LogoutTrip => EntryType.LoginTrip,
_ => EntryType.Login
};
}
[RelayCommand]
public async Task OnAppearing() {
await LoadDate(_currentDate);
}
[RelayCommand]
public async Task RegisterEntry(TimeEntry? entry = null) {
entry ??= new TimeEntry {
Timestamp = DateTime.Now,
Type = CurrentType
};
await entryRepository.AddTimeEntry(entry);
await LoadDate(_currentDate);
}
[RelayCommand]
public async Task DeleteEntry(Guid entryId) {
await entryRepository.DeleteTimeEntry(entryId);
await LoadDate(_currentDate);
}
}

View File

@@ -0,0 +1,19 @@
<?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:components="clr-namespace:WorkTime.Mobile.Pages.Components"
x:Class="WorkTime.Mobile.Pages.Components.DateSelector"
x:DataType="components:DateSelector"
x:Name="Component">
<Grid ColumnDefinitions="*,Auto">
<Label Grid.Column="0"
Text="Tag"
VerticalOptions="Center" />
<DatePicker Grid.Column="1"
Date="{Binding CurrentDate, Source={x:Reference Component}}"
MaximumDate="{Binding MaxDate, Source={x:Reference Component}}"
DateSelected="OnDateSelected"/>
</Grid>
</ContentView>

View File

@@ -0,0 +1,48 @@
using CommunityToolkit.Mvvm.Input;
namespace WorkTime.Mobile.Pages.Components;
public partial class DateSelector : ContentView {
public static readonly BindableProperty CurrentDateProperty = BindableProperty.Create(
nameof(CurrentDate),
typeof(DateTime),
typeof(DateSelector),
DateTime.Now);
public static readonly BindableProperty MaxDateProperty = BindableProperty.Create(
nameof(MaxDate),
typeof(DateTime),
typeof(DateSelector),
DateTime.Now);
public static readonly BindableProperty CommandProperty = BindableProperty.Create(
nameof(Command),
typeof(IRelayCommand<DateOnly>),
typeof(DateSelector));
public DateTime CurrentDate {
get => (DateTime)GetValue(CurrentDateProperty);
set => SetValue(CurrentDateProperty, value);
}
public DateTime MaxDate {
get => (DateTime)GetValue(MaxDateProperty);
set => SetValue(MaxDateProperty, value);
}
public IRelayCommand<DateOnly>? Command {
get => (IRelayCommand<DateOnly>)GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}
public DateSelector() {
InitializeComponent();
}
private void OnDateSelected(object? sender, DateChangedEventArgs e) {
var date = DateOnly.FromDateTime(CurrentDate);
Command?.Execute(date);
}
}

View File

@@ -0,0 +1,24 @@
<?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:components="clr-namespace:WorkTime.Mobile.Pages.Components"
x:Class="WorkTime.Mobile.Pages.Components.SettingComponent"
x:DataType="components:SettingComponent"
x:Name="Component">
<Grid ColumnDefinitions="*,Auto,80" ColumnSpacing="5">
<Label Text="{Binding Title, Source={x:Reference Component}}"
VerticalTextAlignment="Center"
Grid.Column="0"/>
<Entry Placeholder="{Binding Placeholder, Source={x:Reference Component}}"
Keyboard="Numeric"
Grid.Column="1"
TextChanged="OnValueChanged"
x:Name="EntryField"/>
<Label Text="{Binding Unit, Source={x:Reference Component}}"
VerticalTextAlignment="Center"
Grid.Column="2"/>
</Grid>
</ContentView>

View File

@@ -0,0 +1,54 @@
namespace WorkTime.Mobile.Pages.Components;
public partial class SettingComponent : ContentView {
public static readonly BindableProperty TitleProperty =
BindableProperty.Create(nameof(Title), typeof(string), typeof(SettingComponent), string.Empty);
public static readonly BindableProperty UnitProperty =
BindableProperty.Create(nameof(Unit), typeof(string), typeof(SettingComponent), string.Empty);
public static readonly BindableProperty PlaceholderProperty =
BindableProperty.Create(nameof(Placeholder), typeof(string), typeof(SettingComponent), string.Empty);
public static readonly BindableProperty ValueProperty =
BindableProperty.Create(nameof(Value), typeof(TimeSpan), typeof(SettingComponent), TimeSpan.Zero,
BindingMode.TwoWay);
public string Title {
get => (string)GetValue(TitleProperty);
set => SetValue(TitleProperty, value);
}
public string Unit {
get => (string)GetValue(UnitProperty);
set => SetValue(UnitProperty, value);
}
public string Placeholder {
get => (string)GetValue(PlaceholderProperty);
set => SetValue(PlaceholderProperty, value);
}
public TimeSpan Value {
get => (TimeSpan)GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
public SettingComponent() {
InitializeComponent();
}
private void OnValueChanged(object? sender, TextChangedEventArgs e) {
if (!int.TryParse(e.NewTextValue, out var number)) return;
Value = Unit switch {
"Stunden" or "Uhr" => TimeSpan.FromHours(number),
"Minuten" => TimeSpan.FromMinutes(number),
_ => Value
};
}
public void ClearInput() {
Content.FindByName<Entry>(nameof(EntryField)).Text = string.Empty;
}
}

View File

@@ -0,0 +1,97 @@
<?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:components="clr-namespace:WorkTime.Mobile.Pages.Components"
xmlns:pages="clr-namespace:WorkTime.Mobile.Pages"
xmlns:icons="http://www.aathifmahir.com/dotnet/2022/maui/icons"
x:Class="WorkTime.Mobile.Pages.SettingsPage"
x:DataType="pages:SettingsPageModel">
<ContentPage.Content>
<Grid Margin="20,10" RowDefinitions="*,Auto">
<VerticalStackLayout Grid.Row="0" x:Name="SettingsStack">
<HorizontalStackLayout Margin="0, 0, 0, 5" Spacing="15">
<icons:MauiIcon Icon="{icons:Material Work}"
IconSize="30"
IconColor="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
<Label Text="Zeiten"
FontSize="20"
HorizontalTextAlignment="Center"/>
</HorizontalStackLayout>
<components:SettingComponent
Title="Arbeitszeit"
Unit="Stunden"
Placeholder="{Binding Settings.WorkTime.TotalHours}"
Value="{Binding Settings.WorkTime, Mode=OneWayToSource}"/>
<HorizontalStackLayout Margin="0, 10, 0, 5" Spacing="15">
<icons:MauiIcon Icon="{icons:Material LocalPizza}"
IconSize="30"
IconColor="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
<Label Text="Pause"
FontSize="20"
HorizontalTextAlignment="Center"/>
</HorizontalStackLayout>
<components:SettingComponent
Title="Nach 0 Stunden"
Unit="Minuten"
Placeholder="{Binding Settings.BreakAfter0.TotalMinutes}"
Value="{Binding Settings.BreakAfter0, Mode=OneWayToSource}"/>
<components:SettingComponent
Title="Nach 6 Stunden"
Unit="Minuten"
Placeholder="{Binding Settings.BreakAfter6.TotalMinutes}"
Value="{Binding Settings.BreakAfter6, Mode=OneWayToSource}"/>
<components:SettingComponent
Title="Nach 9 Stunden"
Unit="Minuten"
Placeholder="{Binding Settings.BreakAfter9.TotalMinutes}"
Value="{Binding Settings.BreakAfter9, Mode=OneWayToSource}"/>
<components:SettingComponent
Title="Nicht tracken nach"
Unit="Uhr"
Placeholder="{Binding Settings.DontTrackBreakAfter.TotalHours}"
Value="{Binding Settings.DontTrackBreakAfter, Mode=OneWayToSource}"/>
<HorizontalStackLayout Margin="0, 10, 0, 5" Spacing="15">
<icons:MauiIcon Icon="{icons:Material CreditCard}"
IconSize="30"
IconColor="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
<Label Text="Überstunden"
FontSize="20"
HorizontalTextAlignment="Center"/>
</HorizontalStackLayout>
<components:SettingComponent
Title="Optimal"
Unit="Minuten"
Placeholder="{Binding Settings.IdealOvertime.TotalMinutes}"
Value="{Binding Settings.IdealOvertime, Mode=OneWayToSource}"/>
<components:SettingComponent
Title="Maximal"
Unit="Minuten"
Placeholder="{Binding Settings.MaxOvertime.TotalMinutes}"
Value="{Binding Settings.MaxOvertime, Mode=OneWayToSource}"/>
</VerticalStackLayout>
<Button Grid.Row="1"
Text="Speichern"
HorizontalOptions="Center"
Command="{Binding SaveSettingsCommand}"/>
</Grid>
</ContentPage.Content>
</ContentPage>

View File

@@ -0,0 +1,49 @@
using CommunityToolkit.Maui.Alerts;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using WorkTime.Mobile.Pages.Components;
using WorkTime.Models;
using WorkTime.Models.Repositories;
namespace WorkTime.Mobile.Pages;
public partial class SettingsPage : ContentPage {
private readonly SettingsPageModel _model;
public SettingsPage(SettingsPageModel model) {
InitializeComponent();
BindingContext = model;
_model = model;
}
protected override void OnAppearing() {
base.OnAppearing();
if (_model.LoadSettingsCommand.CanExecute(null))
_model.LoadSettingsCommand.Execute(null);
foreach (var child in Content.FindByName<VerticalStackLayout>(nameof(SettingsStack)).Children) {
if (child is SettingComponent component)
component.ClearInput();
}
}
}
public partial class SettingsPageModel(ISettingsRepository settingsRepository) : ObservableObject {
[ObservableProperty]
public partial Settings Settings { get; set; }
[RelayCommand]
public async Task LoadSettings() {
Settings = await settingsRepository.LoadSettings();
}
[RelayCommand]
public async Task SaveSettings() {
await settingsRepository.SaveSettings(Settings);
await Toast.Make("Einstellungen gespeichert!").Show();
}
}