Bessere Trennung manueller / automatischer Login
Umstellung auf Sekunden wegen aktualisierter Hauptanwendung
Umstellung auf Toasts bei Informationsmeldungen
Abstände und Sichtbarkeiten vereinheitlicht
Upgrade auf .NET9
This commit is contained in:
2025-02-15 22:59:06 +01:00
parent 65d5dc94df
commit 4449b4ad0e
18 changed files with 268 additions and 121 deletions

View File

@@ -10,7 +10,7 @@
<ShellContent <ShellContent
Title="Stunden" Title="Stunden"
ContentTemplate="{DataTemplate views:StundenPage}" ContentTemplate="{DataTemplate views:StundenPage}"
Icon="{OnPlatform 'icon_watch.png', iOS='icon_watch_ios.png', MacCatalyst='icon_watch_ios.png'}" /> Icon="{OnPlatform 'icon_watch.png', iOS='icon_watch_ios.png', MacCatalyst='icon_watch_ios.png'}" Route="StundenPage" />
<ShellContent <ShellContent
Title="Notizen" Title="Notizen"

View File

@@ -13,7 +13,6 @@ public partial class AppShell : Shell {
Routing.RegisterRoute(nameof(Views.NotePage), typeof(Views.NotePage)); Routing.RegisterRoute(nameof(Views.NotePage), typeof(Views.NotePage));
Routing.RegisterRoute(nameof(Views.StundePage), typeof(Views.StundePage)); Routing.RegisterRoute(nameof(Views.StundePage), typeof(Views.StundePage));
} }

View File

@@ -0,0 +1,29 @@
using System.Globalization;
namespace Jugenddienst_Stunden.Converter;
internal class SecondsTimeConverter : IValueConverter {
private int seconds;
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) {
if (value is null)
return "0:0";
if (value is int) {
seconds = (int)value;
} else {
int.TryParse((string?)value, out seconds);
}
TimeSpan time = TimeSpan.FromSeconds(seconds);
return (int)time.TotalHours + ":" + Math.Abs(time.Minutes);
//return time.ToString(@"hh\:mm");
//return time.ToString(@"hh\:mm\:ss");
//return "00:00";
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}

View File

@@ -244,14 +244,16 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CommunityToolkit.Maui" Version="11.1.0" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" /> <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="Microsoft.Maui.Controls" Version="9.0.30"> <PackageReference Include="Microsoft.Maui.Controls" Version="9.0.40">
<TreatAsUsed>true</TreatAsUsed> <TreatAsUsed>true</TreatAsUsed>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="9.0.30" /> <PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="9.0.40" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="9.0.1" /> <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="9.0.2" />
<PackageReference Include="Microsoft.Maui.Graphics" Version="9.0.30" /> <PackageReference Include="Microsoft.Maui.Graphics" Version="9.0.40" />
<PackageReference Include="Microsoft.NET.Runtime.MonoAOTCompiler.Task" Version="9.0.1" /> <PackageReference Include="Microsoft.NET.Runtime.MonoAOTCompiler.Task" Version="9.0.2" />
<PackageReference Include="Microsoft.NET.Runtime.WebAssembly.Wasi.Sdk" Version="9.0.2" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="ZXing.Net.Maui.Controls" Version="0.4.0" /> <PackageReference Include="ZXing.Net.Maui.Controls" Version="0.4.0" />
</ItemGroup> </ItemGroup>

View File

@@ -1,4 +1,5 @@
using Jugenddienst_Stunden.Models; using CommunityToolkit.Maui;
using Jugenddienst_Stunden.Models;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using ZXing.Net.Maui.Controls; using ZXing.Net.Maui.Controls;
@@ -13,6 +14,8 @@ public static class MauiProgram {
var builder = MauiApp.CreateBuilder(); var builder = MauiApp.CreateBuilder();
builder builder
.UseMauiApp<App>() .UseMauiApp<App>()
// Initialize the .NET MAUI Community Toolkit by adding the below line of code
.UseMauiCommunityToolkit()
.ConfigureFonts(fonts => { .ConfigureFonts(fonts => {
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold"); fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
@@ -22,19 +25,11 @@ public static class MauiProgram {
#if DEBUG #if DEBUG
if (GlobalVar.ApiKey == null) { if (GlobalVar.ApiKey == null) {
//Preferences.Default.Set("apiKey
//Preferences.Default.Set("apiKey", "M3xneWlWNG85TmNIcmo1NnpxWkxVYS9JMDBFRlV8aHR0cDovL2hvdXJzLmRhdW5pLm1pbmUubnU6ODEvYXBwYXBp");
//Preferences.Default.Set("name", "Testserver: Lea");
//Preferences.Default.Set("surname", "Mair");
//Preferences.Default.Set("EmployeeId", 3);
//Preferences.Default.Set("apiUrl", "http://hours.dauni.mine.nu:81/appapi");
//HoursBase HoursBase = new HoursBase();
//HoursBase.tokendata = new TokenData("MTQxfHNkdFptQkNZTXlPT3ZyMHNBZDl0UnVxNExMRXxodHRwOi8vaG91cnMuZGF1bmkubWluZS5udTo4MS9hcHBhcGk=");
GlobalVar.ApiKey = Preferences.Default.Get("apiKey", "MTQxfHNkdFptQkNZTXlPT3ZyMHNBZDl0UnVxNExMRXxodHRwOi8vaG91cnMuZGF1bmkubWluZS5udTo4MS9hcHBhcGk="); GlobalVar.ApiKey = Preferences.Default.Get("apiKey", "MTQxfHNkdFptQkNZTXlPT3ZyMHNBZDl0UnVxNExMRXxodHRwOi8vaG91cnMuZGF1bmkubWluZS5udTo4MS9hcHBhcGk=");
GlobalVar.Name = Preferences.Default.Get("name", "Testserver: Isabell"); GlobalVar.Name = Preferences.Default.Get("name", "Testserver: Isabell");
GlobalVar.Surname = Preferences.Default.Get("surname", "Biasi"); GlobalVar.Surname = Preferences.Default.Get("surname", "Biasi");
GlobalVar.EmployeeId = Preferences.Default.Get("EmployeeId", 141); GlobalVar.EmployeeId = Preferences.Default.Get("EmployeeId", 141);
GlobalVar.ApiUrl = Preferences.Default.Get("apiUrl", "http://hours.dauni.mine.nu:81/appapi"); GlobalVar.ApiUrl = Preferences.Default.Get("apiUrl", "https://hours.dauni.mine.nu/appapi");
} }
builder.Logging.AddDebug(); builder.Logging.AddDebug();
#endif #endif

View File

@@ -29,9 +29,9 @@ internal static class BaseFunc {
using (HttpResponseMessage HttpResponseMessage = await client.GetAsync(url).ConfigureAwait(false)) { using (HttpResponseMessage HttpResponseMessage = await client.GetAsync(url).ConfigureAwait(false)) {
var byteArray = await HttpResponseMessage.Content.ReadAsByteArrayAsync(); var byteArray = await HttpResponseMessage.Content.ReadAsByteArrayAsync();
string responseData = Encoding.UTF8.GetString(byteArray); string responseData = Encoding.UTF8.GetString(byteArray);
using (HttpContent HttpContent = HttpResponseMessage.Content) { //using (HttpContent HttpContent = HttpResponseMessage.Content) {
//responseData = await HttpContent.ReadAsStringAsync(); // //responseData = await HttpContent.ReadAsStringAsync();
} //}
if (HttpResponseMessage.StatusCode == System.Net.HttpStatusCode.OK) { if (HttpResponseMessage.StatusCode == System.Net.HttpStatusCode.OK) {
return responseData; return responseData;
} else { } else {
@@ -98,6 +98,9 @@ internal static class BaseFunc {
return null; return null;
} }
/// <summary>
/// Notiz laden
/// </summary>
internal static Note Load(string filename) { internal static Note Load(string filename) {
filename = System.IO.Path.Combine(FileSystem.AppDataDirectory, filename); filename = System.IO.Path.Combine(FileSystem.AppDataDirectory, filename);
@@ -169,6 +172,9 @@ internal static class BaseFunc {
} }
/// <summary>
/// Stundeneintrag löschen
/// </summary>
internal static async Task DeleteItemAsync(string url, string token) { internal static async Task DeleteItemAsync(string url, string token) {
using (HttpClient client = new HttpClient() { Timeout = TimeSpan.FromSeconds(15) }) { using (HttpClient client = new HttpClient() { Timeout = TimeSpan.FromSeconds(15) }) {

View File

@@ -6,7 +6,7 @@ namespace Jugenddienst_Stunden.Models;
internal static class HoursBase { internal static class HoursBase {
/// <summary> /// <summary>
/// what can be: "settings", "hours", date="YYYY-MM-DD", id=<int/> /// Laden ... what can be: "settings", "hours", date="YYYY-MM-DD", id=<int/>
/// </summary> /// </summary>
/// <returns>Entire response</returns> /// <returns>Entire response</returns>
internal static async Task<BaseResponse> LoadBase(string what) { internal static async Task<BaseResponse> LoadBase(string what) {
@@ -16,7 +16,7 @@ internal static class HoursBase {
} }
/// <summary> /// <summary>
/// Einstellungen /// Einstellungen laden
/// </summary> /// </summary>
/// <returns>Settings only</returns> /// <returns>Settings only</returns>
internal static async Task<Settings> LoadSettings() { internal static async Task<Settings> LoadSettings() {

View File

@@ -5,8 +5,8 @@ using System.Collections.ObjectModel;
namespace Jugenddienst_Stunden.Types; namespace Jugenddienst_Stunden.Types;
internal class Hours : ObservableObject { internal class Hours : ObservableObject {
public string? Zeit; public int? Zeit;
public string? Nominal; public int? Nominal;
//public Dictionary<DateOnly,NominalDay> nominal_day_api; //public Dictionary<DateOnly,NominalDay> nominal_day_api;
public List<NominalDay>? Nominal_day_api; public List<NominalDay>? Nominal_day_api;
//public Dictionary<int,NominalWeek> nominal_week_api; //public Dictionary<int,NominalWeek> nominal_week_api;
@@ -21,16 +21,16 @@ internal class Hours : ObservableObject {
public List<TimeDay> zeit_total_daily_api; public List<TimeDay> zeit_total_daily_api;
public List<DayTime>? daytime; public List<DayTime>? daytime;
//public List<string> wochensumme; //public List<string> wochensumme;
public string overtime_month; public int overtime_month;
public string overtime; public int overtime;
//public List<string> overtime_day; //public List<string> overtime_day;
public string zeitausgleich; public int zeitausgleich;
public string zeitausgleich_month; public int zeitausgleich_month;
public string holiday; public int holiday;
public string krankheit; public int krankheit;
public string weiterbildung; public int weiterbildung;
public string bereitschaft; public int bereitschaft;
public string bereitschaft_month; public int bereitschaft_month;
//public Operator operator_api; //public Operator operator_api;
public DateTime Today; public DateTime Today;
public DateTime Date; public DateTime Date;

View File

@@ -2,6 +2,6 @@
internal class NominalDay { internal class NominalDay {
public int day_number; public int day_number;
public int month_number; public int month_number;
public decimal hours; public int hours;
public DateOnly date; public DateOnly date;
} }

View File

@@ -2,5 +2,5 @@
internal class NominalWeek { internal class NominalWeek {
public int Week_number; public int Week_number;
public decimal Hours; public int Hours;
} }

View File

@@ -22,6 +22,8 @@ public partial class StundeViewModel : ObservableObject, IQueryAttributable {
public event EventHandler<string> AlertEvent; public event EventHandler<string> AlertEvent;
public event EventHandler<string> InfoEvent; public event EventHandler<string> InfoEvent;
public event Func<string, string, Task<bool>> ConfirmEvent; public event Func<string, string, Task<bool>> ConfirmEvent;
//public event Func<string, string, string?, string?, Task<bool>> ConfirmEvent;
//public event EventHandler<ConfirmEventArgs> ConfirmEvent;
/// <summary> /// <summary>
/// Gemeinden für die Auswahlliste /// Gemeinden für die Auswahlliste
@@ -73,6 +75,7 @@ public partial class StundeViewModel : ObservableObject, IQueryAttributable {
public ICommand DeleteConfirmCommand { get; private set; } public ICommand DeleteConfirmCommand { get; private set; }
//public ICommand LoadDataCommand { get; private set; } //public ICommand LoadDataCommand { get; private set; }
public StundeViewModel() { public StundeViewModel() {
SaveCommand = new AsyncRelayCommand(Save); SaveCommand = new AsyncRelayCommand(Save);
//DeleteCommand = new AsyncRelayCommand(Delete); //DeleteCommand = new AsyncRelayCommand(Delete);
@@ -95,7 +98,7 @@ public partial class StundeViewModel : ObservableObject, IQueryAttributable {
GemeindeAktivSet = Settings.GemeindeAktivSet; GemeindeAktivSet = Settings.GemeindeAktivSet;
ProjektAktivSet = Settings.ProjektAktivSet; ProjektAktivSet = Settings.ProjektAktivSet;
} catch (Exception e) { } catch (Exception e) {
AlertEvent?.Invoke(this, e.Message); AlertEvent?.Invoke(this, e.Message);
} }
@@ -103,26 +106,40 @@ public partial class StundeViewModel : ObservableObject, IQueryAttributable {
async Task Save() { async Task Save() {
bool exceptionOccurred = false; bool exceptionOccurred = false;
try { bool proceed = true;
await HoursBase.SaveEntry(DayTime); if (DayTime.TimeSpanVon == DayTime.TimeSpanBis && DayTime.FreistellungAktiv.Name == null) {
} catch (Exception e) { proceed = false;
AlertEvent?.Invoke(this, e.Message); AlertEvent?.Invoke(this, "Uhrzeiten sollten unterschiedlich sein");
exceptionOccurred = true;
} }
if (!exceptionOccurred) {
if (DayTime.Id != null) { if (proceed) {
await Shell.Current.GoToAsync($"..?saved={DayTime.Id}"); try {
} else { await HoursBase.SaveEntry(DayTime);
await Shell.Current.GoToAsync($"..?date={DayTime.Day.ToString("yyyy-MM-dd")}"); } catch (Exception e) {
AlertEvent?.Invoke(this, e.Message);
exceptionOccurred = true;
}
if (!exceptionOccurred) {
if (DayTime.Id != null) {
await Shell.Current.GoToAsync($"..?saved={DayTime.Id}");
} else {
await Shell.Current.GoToAsync($"..?date={DayTime.Day.ToString("yyyy-MM-dd")}");
}
} }
} }
} }
/// <summary>
/// Löschen ohne Bestätigung
/// </summary>
private async Task Delete() { private async Task Delete() {
await HoursBase.DeleteEntry(DayTime); await HoursBase.DeleteEntry(DayTime);
await Shell.Current.GoToAsync($"..?date={DayTime.Day.ToString("yyyy-MM-dd")}"); await Shell.Current.GoToAsync($"..?date={DayTime.Day.ToString("yyyy-MM-dd")}");
} }
/// <summary>
/// Löschen mit Bestätigung
/// </summary>
private async Task DeleteConfirm() { private async Task DeleteConfirm() {
if (ConfirmEvent != null) { if (ConfirmEvent != null) {
bool answer = await ConfirmEvent.Invoke("Achtung", "Löschen kann nicht ungeschehen gemacht werden. Fortfahren?"); bool answer = await ConfirmEvent.Invoke("Achtung", "Löschen kann nicht ungeschehen gemacht werden. Fortfahren?");
@@ -140,7 +157,8 @@ public partial class StundeViewModel : ObservableObject, IQueryAttributable {
/// Anwenden der Query-Parameter /// Anwenden der Query-Parameter
/// </summary> /// </summary>
async void IQueryAttributable.ApplyQueryAttributes(IDictionary<string, object> query) { async void IQueryAttributable.ApplyQueryAttributes(IDictionary<string, object> query) {
//load beinhaltet die ID: Eintrag bearbeiten
//date beinhaltet einen Tag: Neuen Eintrag erstellen
if (query.ContainsKey("load")) { if (query.ContainsKey("load")) {
//DateTime heute = DateTime.Now; //DateTime heute = DateTime.Now;
@@ -154,7 +172,7 @@ public partial class StundeViewModel : ObservableObject, IQueryAttributable {
DayTime = dat.daytime; DayTime = dat.daytime;
DayTime.TimeSpanVon = dat.daytime.Begin.ToTimeSpan(); DayTime.TimeSpanVon = dat.daytime.Begin.ToTimeSpan();
DayTime.TimeSpanBis = dat.daytime.End.ToTimeSpan(); DayTime.TimeSpanBis = dat.daytime.End.ToTimeSpan();
OptionsGemeinde = dat.settings.Gemeinden ?? new List<Gemeinde>(); OptionsGemeinde = dat.settings.Gemeinden ?? new List<Gemeinde>();
OptionsProjekt = dat.settings.Projekte ?? new List<Projekt>(); OptionsProjekt = dat.settings.Projekte ?? new List<Projekt>();
OptionsFreistellung = dat.settings.Freistellungen ?? new List<Freistellung>(); OptionsFreistellung = dat.settings.Freistellungen ?? new List<Freistellung>();
@@ -162,7 +180,12 @@ public partial class StundeViewModel : ObservableObject, IQueryAttributable {
DayTime.GemeindeAktiv = OptionsGemeinde.FirstOrDefault(Gemeinde => Gemeinde.Id == DayTime.Gemeinde) ?? new Gemeinde(); DayTime.GemeindeAktiv = OptionsGemeinde.FirstOrDefault(Gemeinde => Gemeinde.Id == DayTime.Gemeinde) ?? new Gemeinde();
DayTime.ProjektAktiv = OptionsProjekt.FirstOrDefault(Projekt => Projekt.Id == DayTime.Projekt) ?? new Projekt(); DayTime.ProjektAktiv = OptionsProjekt.FirstOrDefault(Projekt => Projekt.Id == DayTime.Projekt) ?? new Projekt();
DayTime.FreistellungAktiv = OptionsFreistellung.FirstOrDefault(Freistellung => Freistellung.Id == DayTime.Free) ?? new Freistellung(); DayTime.FreistellungAktiv = OptionsFreistellung.FirstOrDefault(Freistellung => Freistellung.Id == DayTime.Free) ?? new Freistellung();
//OnPropertyChanged(nameof(DayTime));
//Evtl. noch die anderen Zeiten des gleichen Tages holen
BaseResponse dat1 = await HoursBase.LoadBase("date=" + DayTime.Day.ToString("yyyy-MM-dd"));
DayTimes = dat1.daytimes;
OnPropertyChanged(nameof(DayTime));
OnPropertyChanged(nameof(DayTimes));
} catch (Exception e) { } catch (Exception e) {
AlertEvent?.Invoke(this, e.Message); AlertEvent?.Invoke(this, e.Message);
@@ -171,7 +194,7 @@ public partial class StundeViewModel : ObservableObject, IQueryAttributable {
} }
if (System.String.IsNullOrEmpty(DayTime.Description)) { if (System.String.IsNullOrEmpty(DayTime.Description)) {
AlertEvent?.Invoke(this, "Eintrag hat keinen Beschreibungstext"); InfoEvent?.Invoke(this, "Eintrag hat keinen Beschreibungstext");
} }
SubTitle = DayTime.Day.ToString("dddd, d. MMMM yyyy"); SubTitle = DayTime.Day.ToString("dddd, d. MMMM yyyy");
OnPropertyChanged(nameof(SubTitle)); OnPropertyChanged(nameof(SubTitle));
@@ -197,7 +220,7 @@ public partial class StundeViewModel : ObservableObject, IQueryAttributable {
GemeindeAktivSet = dat.settings.GemeindeAktivSet; GemeindeAktivSet = dat.settings.GemeindeAktivSet;
ProjektAktivSet = dat.settings.ProjektAktivSet; ProjektAktivSet = dat.settings.ProjektAktivSet;
} catch (Exception) { } catch (Exception) {
//Ein Tag ohne Einträge gibt eine Fehlermeldung, //Ein Tag ohne Einträge gibt eine Fehlermeldung,
//die soll aber ignoriert werden, weil beim Neueintrag ist das ja Wurscht //die soll aber ignoriert werden, weil beim Neueintrag ist das ja Wurscht
@@ -213,7 +236,7 @@ public partial class StundeViewModel : ObservableObject, IQueryAttributable {
SubTitle = _date.ToString("dddd, d. MMMM yyyy"); SubTitle = _date.ToString("dddd, d. MMMM yyyy");
FreistellungEnabled = true; FreistellungEnabled = true;
OnPropertyChanged(nameof(SubTitle)); OnPropertyChanged(nameof(SubTitle));
//OnPropertyChanged(nameof(DayTime)); //OnPropertyChanged(nameof(DayTime));
} }

View File

@@ -5,6 +5,8 @@ using Jugenddienst_Stunden.Types;
using System.ComponentModel; using System.ComponentModel;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Windows.Input; using System.Windows.Input;
using CommunityToolkit.Maui.Alerts;
using CommunityToolkit.Maui.Core;
namespace Jugenddienst_Stunden.ViewModels; namespace Jugenddienst_Stunden.ViewModels;
@@ -13,8 +15,6 @@ namespace Jugenddienst_Stunden.ViewModels;
/// </summary> /// </summary>
internal partial class StundenViewModel : ObservableObject, IQueryAttributable, INotifyPropertyChanged { internal partial class StundenViewModel : ObservableObject, IQueryAttributable, INotifyPropertyChanged {
public string LoadOverview => "Lade Summen für " + DateTime.Today.ToString("MMMM");
public ICommand NewEntryCommand { get; } public ICommand NewEntryCommand { get; }
public ICommand SelectEntryCommand { get; } public ICommand SelectEntryCommand { get; }
public ICommand LoadDataCommand { get; private set; } public ICommand LoadDataCommand { get; private set; }
@@ -25,6 +25,13 @@ internal partial class StundenViewModel : ObservableObject, IQueryAttributable,
public event EventHandler<string> AlertEvent; public event EventHandler<string> AlertEvent;
public event EventHandler<string> InfoEvent; public event EventHandler<string> InfoEvent;
/// <summary>
/// Beschriftung Button Monatsübersicht
/// </summary>
[ObservableProperty]
private string loadOverview;
//private HoursBase HoursBase = new HoursBase(); //private HoursBase HoursBase = new HoursBase();
internal Settings Settings = new Settings(); internal Settings Settings = new Settings();
@@ -45,7 +52,7 @@ internal partial class StundenViewModel : ObservableObject, IQueryAttributable,
/// </summary> /// </summary>
[ObservableProperty] [ObservableProperty]
private List<DayTime> dayTimes = new List<DayTime>(); private List<DayTime> dayTimes = new List<DayTime>();
public string Title { get; set; } = GlobalVar.Name + " " + GlobalVar.Surname; public string Title { get; set; } = GlobalVar.Name + " " + GlobalVar.Surname;
private Hours _hour; private Hours _hour;
@@ -76,7 +83,7 @@ internal partial class StundenViewModel : ObservableObject, IQueryAttributable,
set { set {
if (dateToday != value) { if (dateToday != value) {
dateToday = value; dateToday = value;
//GetDay = value; LoadOverview = "Lade Summen für " + dateToday.ToString("MMMM");
//OnPropertyChanged(); //OnPropertyChanged();
Task.Run(() => LoadDay(value)); Task.Run(() => LoadDay(value));
} }
@@ -93,28 +100,28 @@ internal partial class StundenViewModel : ObservableObject, IQueryAttributable,
/// <summary> /// <summary>
/// Monatsübersicht: Sollstunden /// Monatsübersicht: Sollstunden
/// </summary> /// </summary>
public string? Nominal { public int? Nominal {
get => Hours.Nominal; get => Hours.Nominal;
} }
/// <summary> /// <summary>
/// Monatsübersicht: Differenz zwischen Soll und geleisteten Stunden /// Monatsübersicht: Differenz zwischen Soll und geleisteten Stunden
/// </summary> /// </summary>
public string? Overtime { public int? Overtime {
get => Hours.overtime; get => Hours.overtime;
} }
/// <summary> /// <summary>
/// Monatsübersicht: Restüberstunden insgesamt /// Monatsübersicht: Restüberstunden insgesamt
/// </summary> /// </summary>
public string OvertimeMonth { public int OvertimeMonth {
get => Hours.overtime_month; get => Hours.overtime_month;
} }
/// <summary> /// <summary>
/// Monatsübersicht: Resturlaub /// Monatsübersicht: Resturlaub
/// </summary> /// </summary>
public string Holiday { public int Holiday {
get => Hours.holiday; get => Hours.holiday;
} }
@@ -138,12 +145,15 @@ internal partial class StundenViewModel : ObservableObject, IQueryAttributable,
private bool doContinue = true; private bool doContinue = true;
/// <summary> /// <summary>
/// CTOR /// CTOR
/// </summary> /// </summary>
public StundenViewModel() { public StundenViewModel() {
_hour = new Hours(); _hour = new Hours();
LoadOverview = "Lade Summen für " + DateToday.ToString("MMMM");
LoadDataCommand = new AsyncRelayCommand(LoadData); LoadDataCommand = new AsyncRelayCommand(LoadData);
NewEntryCommand = new AsyncRelayCommand(NewEntryAsync); NewEntryCommand = new AsyncRelayCommand(NewEntryAsync);
SelectEntryCommand = new AsyncRelayCommand<DayTime>(SelectEntryAsync); SelectEntryCommand = new AsyncRelayCommand<DayTime>(SelectEntryAsync);
@@ -183,7 +193,7 @@ internal partial class StundenViewModel : ObservableObject, IQueryAttributable,
/// </summary> /// </summary>
private async Task LoadData() { private async Task LoadData() {
try { try {
BaseResponse dat = await HoursBase.LoadBase("hours"); BaseResponse dat = await HoursBase.LoadBase("hours&month=" + DateToday.ToString("MM"));
_hour = dat.hour; _hour = dat.hour;
//_hour = await HoursBase.LoadData(); //_hour = await HoursBase.LoadData();
RefreshProperties(); RefreshProperties();
@@ -198,6 +208,7 @@ internal partial class StundenViewModel : ObservableObject, IQueryAttributable,
/// </summary> /// </summary>
public async Task LoadDay(DateTime date) { public async Task LoadDay(DateTime date) {
DayTotal = new TimeOnly(0); DayTotal = new TimeOnly(0);
Sollstunden = new TimeOnly(0);
try { try {
//_dayTimes = await HoursBase.LoadDay(date); //_dayTimes = await HoursBase.LoadDay(date);
BaseResponse dat = await HoursBase.LoadBase("date=" + date.ToString("yyyy-MM-dd")); BaseResponse dat = await HoursBase.LoadBase("date=" + date.ToString("yyyy-MM-dd"));
@@ -206,6 +217,7 @@ internal partial class StundenViewModel : ObservableObject, IQueryAttributable,
Settings = dat.settings; Settings = dat.settings;
GemeindeAktivSet = Settings.GemeindeAktivSet; GemeindeAktivSet = Settings.GemeindeAktivSet;
ProjektAktivSet = Settings.ProjektAktivSet; ProjektAktivSet = Settings.ProjektAktivSet;
OnPropertyChanged(nameof(GemeindeAktivSet)); OnPropertyChanged(nameof(GemeindeAktivSet));
OnPropertyChanged(nameof(ProjektAktivSet)); OnPropertyChanged(nameof(ProjektAktivSet));
@@ -241,23 +253,25 @@ internal partial class StundenViewModel : ObservableObject, IQueryAttributable,
} finally { } finally {
OnPropertyChanged(nameof(DayTotal)); OnPropertyChanged(nameof(DayTotal));
OnPropertyChanged(nameof(Sollstunden)); OnPropertyChanged(nameof(Sollstunden));
OnPropertyChanged(nameof(DateToday));
OnPropertyChanged(nameof(LoadOverview));
//OnPropertyChanged(nameof(DayTimes)); //OnPropertyChanged(nameof(DayTimes));
} }
} }
async void IQueryAttributable.ApplyQueryAttributes(IDictionary<string, object> query) { async void IQueryAttributable.ApplyQueryAttributes(IDictionary<string, object> query) {
if (query.ContainsKey("date")) { if (query.ContainsKey("date")) {
await LoadDay(Convert.ToDateTime(query["date"])); await LoadDay(Convert.ToDateTime(query["date"]));
} }
} }
/// <summary>
/// Seite aktualisieren
/// </summary>
private async Task RefreshItemsAsync() { private async Task RefreshItemsAsync() {
IsRefreshing = true; IsRefreshing = true;
// Fügen Sie hier die Logik zum Aktualisieren der Daten hinzu
//await Task.Delay(2000); // Simuliert eine Datenaktualisierung //await Task.Delay(2000); // Simuliert eine Datenaktualisierung
await LoadDay(DateToday); await LoadDay(DateToday);
@@ -277,6 +291,7 @@ internal partial class StundenViewModel : ObservableObject, IQueryAttributable,
OnPropertyChanged(nameof(Title)); OnPropertyChanged(nameof(Title));
OnPropertyChanged(nameof(MinimumDate)); OnPropertyChanged(nameof(MinimumDate));
OnPropertyChanged(nameof(MaximumDate)); OnPropertyChanged(nameof(MaximumDate));
OnPropertyChanged(nameof(LoadOverview));
} }
protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { protected void OnPropertyChanged([CallerMemberName] string propertyName = null) {

View File

@@ -52,13 +52,9 @@
<VerticalStackLayout x:Name="LoginManual" Spacing="15"> <VerticalStackLayout x:Name="LoginManual" Spacing="15">
<Label Text="Manueller Login" FontSize="32" HorizontalOptions="Start" /> <Label Text="Manueller Login" FontSize="32" HorizontalOptions="Start" />
<Entry x:Name="UsernameEntry" Placeholder="Benutzername" Keyboard="Email" /> <Entry x:Name="UsernameEntry" Placeholder="Benutzername" Keyboard="Email" />
<Entry x:Name="PasswordEntry" Placeholder="Passwort" IsPassword="True" /> <Entry x:Name="PasswordEntry" Placeholder="Passwort" IsPassword="True" />
<Entry x:Name="ServerEntry" Placeholder="Server" Keyboard="Url" /> <Entry x:Name="ServerEntry" Placeholder="Server" Keyboard="Url" />
<Button Text="Login" Clicked="OnLoginButtonClicked" /> <Button Text="Login" Clicked="OnLoginButtonClicked" />
</VerticalStackLayout> </VerticalStackLayout>
</VerticalStackLayout> </VerticalStackLayout>

View File

@@ -79,8 +79,13 @@ public partial class LoginPage : ContentPage {
await DisplayAlert("Login erfolgreich", user.Name + " " + user.Surname, "OK"); await DisplayAlert("Login erfolgreich", user.Name + " " + user.Surname, "OK");
if (Navigation.NavigationStack.Count > 1) if (Navigation.NavigationStack.Count > 1) {
//Beim ersten Start ohne Login, wird man automatisch auf die Loginseite geleitet. Danach in der History zur<75>ck
await Navigation.PopAsync(); await Navigation.PopAsync();
} else {
//Beim manuellen Wechsel auf die Loginseite leiten wir nach erfolgreichem Login auf die Stunden<65>bersicht
await Shell.Current.GoToAsync($"//StundenPage");
}
} catch (Exception e) { } catch (Exception e) {
await DisplayAlert("Fehler", e.Message, "OK"); await DisplayAlert("Fehler", e.Message, "OK");
@@ -126,30 +131,50 @@ public partial class LoginPage : ContentPage {
var password = PasswordEntry.Text; var password = PasswordEntry.Text;
var server = ServerEntry.Text; var server = ServerEntry.Text;
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password) || string.IsNullOrEmpty(server)) { if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password) || string.IsNullOrEmpty(server)) {
await DisplayAlert("Fehler", "Bitte alle Felder ausf<73>llen", "OK"); await DisplayAlert("Fehler", "Bitte alle Felder ausf<73>llen", "OK");
return; return;
} }
try { try {
Types.User response = await BaseFunc.AuthUserPass(username, password, server); Uri uri = new Uri(InputUrlWithSchema(server));
Types.User response = await BaseFunc.AuthUserPass(username, password, uri.Scheme + "://" + uri.Authority + "/appapi");
GlobalVar.ApiKey = response.Token; GlobalVar.ApiKey = response.Token;
GlobalVar.Name = response.Name; GlobalVar.Name = response.Name;
GlobalVar.Surname = response.Surname; GlobalVar.Surname = response.Surname;
GlobalVar.EmployeeId = response.Id; GlobalVar.EmployeeId = response.Id;
GlobalVar.ApiUrl = server; GlobalVar.ApiUrl = uri.Scheme + "://" + uri.Authority + "/appapi";
Title = response.Name + " " + response.Surname; Title = response.Name + " " + response.Surname;
ServerLabel.Text = "Server: " + server.Replace("/appapi", "").Replace("https://", "").Replace("http://", ""); //ServerLabel.Text = "Server: " + server.Replace("/appapi", "").Replace("https://", "").Replace("http://", "");
ServerLabel.Text = "Server: " + uri.Authority;
await DisplayAlert("Login erfolgreich", response.Name + " " + response.Surname, "OK"); await DisplayAlert("Login erfolgreich", response.Name + " " + response.Surname, "OK");
if (Navigation.NavigationStack.Count > 1) if (Navigation.NavigationStack.Count > 1)
await Navigation.PopAsync(); await Navigation.PopAsync();
else {
await Shell.Current.GoToAsync($"//StundenPage");
}
} catch (Exception ex) { } catch (Exception ex) {
await DisplayAlert("Fehler", ex.Message, "OK"); await DisplayAlert("Fehler", ex.Message, "OK");
} }
} }
/// <summary>
/// Aus einer URL ohne Schema eine URL mit Schema machen
/// </summary>
private static string InputUrlWithSchema(string url) {
if (!url.StartsWith("http://") && !url.StartsWith("https://")) {
url = "https://" + url;
}
if (url.StartsWith("http://")) {
url = url.Replace("http://", "https://");
}
return url;
}
//Zwischen manuellem und automatischem Login (mit QR-Code) umschalten und die Schalterstellung merken //Zwischen manuellem und automatischem Login (mit QR-Code) umschalten und die Schalterstellung merken
private void Switch_Toggled(object sender, ToggledEventArgs e) { private void Switch_Toggled(object sender, ToggledEventArgs e) {
var switcher = (Switch)sender; var switcher = (Switch)sender;

View File

@@ -19,36 +19,53 @@
<VerticalStackLayout Spacing="10" Margin="10"> <VerticalStackLayout Spacing="10" Margin="10">
<Label Text="{Binding SubTitle}" FontSize="Medium" FontAttributes="Bold" Margin="4,0,0,0" />
<Label Text="{Binding SubTitle}" FontSize="Medium" Margin="4,0,0,0" /> <Border>
<Border.Padding>
<OnPlatform x:TypeArguments="Thickness" Default="0,15,10,0">
<On Platform="Android" Value="0,4,10,8"/>
<On Platform="WPF" Value="0,15,10,0"/>
</OnPlatform>
</Border.Padding>
<Frame Padding="5,2,5,10">
<FlexLayout Direction="Row" AlignItems="Start" Wrap="Wrap" JustifyContent="SpaceBetween"> <FlexLayout Direction="Row" AlignItems="Start" Wrap="Wrap" JustifyContent="SpaceBetween">
<HorizontalStackLayout Spacing="10">
<HorizontalStackLayout> <Label Text="Beginn" VerticalTextAlignment="Center" HorizontalTextAlignment="End" MinimumWidthRequest="60"></Label>
<Label Text="Beginn" VerticalTextAlignment="Center" HorizontalTextAlignment="End" Padding="0,0,10,0" Margin="5,0,0,0" MinimumWidthRequest="60"></Label> <TimePicker x:Name="TimeBegin" HorizontalOptions="Center" Format="HH:mm" MinimumWidthRequest="80" Time="{Binding DayTime.TimeSpanVon}" />
<TimePicker x:Name="TimeBegin" HorizontalOptions="Center" Format="HH:mm" MinimumWidthRequest="80" Time="{Binding DayTime.TimeSpanVon}" Margin="0,0,0,-5" />
</HorizontalStackLayout> </HorizontalStackLayout>
<HorizontalStackLayout> <HorizontalStackLayout Spacing="10">
<Label Text="Ende" VerticalTextAlignment="Center" HorizontalTextAlignment="End" Padding="0,0,10,0" Margin="5,0,0,0" MinimumWidthRequest="60"></Label> <Label Text="Ende" VerticalTextAlignment="Center" HorizontalTextAlignment="End" MinimumWidthRequest="60"></Label>
<TimePicker x:Name="TimeEnd" Format="HH:mm" MinimumWidthRequest="80" Time="{Binding DayTime.TimeSpanBis}" Margin="0,0,0,-5" /> <TimePicker x:Name="TimeEnd" Format="HH:mm" MinimumWidthRequest="80" Time="{Binding DayTime.TimeSpanBis}" />
</HorizontalStackLayout> </HorizontalStackLayout>
</FlexLayout> </FlexLayout>
</Frame> </Border>
<Frame Padding="5,4,5,8"> <Border>
<Grid ColumnDefinitions="*,*,*"> <Border.Padding>
<OnPlatform x:TypeArguments="Thickness" Default="5">
<On Platform="Android" Value="5,4,5,8"/>
<On Platform="WPF" Value="5"/>
</OnPlatform>
</Border.Padding>
<!--<Grid ColumnDefinitions="*,*,*">
<Picker x:Name="pick_gemeinde" Title="Gemeinde" ItemsSource="{Binding OptionsGemeinde}" SelectedItem="{Binding DayTime.GemeindeAktiv, Mode=TwoWay}" ItemDisplayBinding="{Binding Name}" Grid.Column="0" IsVisible="{Binding GemeindeAktivSet}"> <Picker x:Name="pick_gemeinde" Title="Gemeinde" ItemsSource="{Binding OptionsGemeinde}" SelectedItem="{Binding DayTime.GemeindeAktiv, Mode=TwoWay}" ItemDisplayBinding="{Binding Name}" Grid.Column="0" IsVisible="{Binding GemeindeAktivSet}">
</Picker> </Picker>
<Picker x:Name="pick_projekt" Title="Projekt" ItemsSource="{Binding OptionsProjekt}" SelectedItem="{Binding DayTime.ProjektAktiv, Mode=TwoWay}" ItemDisplayBinding="{Binding Name}" Grid.Column="1" IsVisible="{Binding ProjektAktivSet}"> <Picker x:Name="pick_projekt" Title="Projekt" ItemsSource="{Binding OptionsProjekt}" SelectedItem="{Binding DayTime.ProjektAktiv, Mode=TwoWay}" ItemDisplayBinding="{Binding Name}" Grid.Column="1" IsVisible="{Binding ProjektAktivSet}">
</Picker> </Picker>
<Picker x:Name="pick_freistellung" Title="Freistellung" ItemsSource="{Binding OptionsFreistellung}" SelectedItem="{Binding DayTime.FreistellungAktiv, Mode=TwoWay}" ItemDisplayBinding="{Binding Name}" Grid.Column="2" IsEnabled="{Binding FreistellungEnabled}"> <Picker x:Name="pick_freistellung" Title="Freistellung" ItemsSource="{Binding OptionsFreistellung}" SelectedItem="{Binding DayTime.FreistellungAktiv, Mode=TwoWay}" ItemDisplayBinding="{Binding Name}" Grid.Column="2" IsEnabled="{Binding FreistellungEnabled}">
</Picker> </Picker>
</Grid> </Grid>-->
</Frame> <HorizontalStackLayout>
<Picker x:Name="pick_gemeinde" Title="Gemeinde" ItemsSource="{Binding OptionsGemeinde}" SelectedItem="{Binding DayTime.GemeindeAktiv, Mode=TwoWay}" ItemDisplayBinding="{Binding Name}" IsVisible="{Binding GemeindeAktivSet}">
</Picker>
<Picker x:Name="pick_projekt" Title="Projekt" ItemsSource="{Binding OptionsProjekt}" SelectedItem="{Binding DayTime.ProjektAktiv, Mode=TwoWay}" ItemDisplayBinding="{Binding Name}" IsVisible="{Binding ProjektAktivSet}">
</Picker>
<Picker x:Name="pick_freistellung" Title="Freistellung" ItemsSource="{Binding OptionsFreistellung}" SelectedItem="{Binding DayTime.FreistellungAktiv, Mode=TwoWay}" ItemDisplayBinding="{Binding Name}" IsEnabled="{Binding FreistellungEnabled}">
</Picker>
</HorizontalStackLayout>
</Border>
<Editor Placeholder="Beschreibung" Text="{Binding DayTime.Description}" MinimumHeightRequest="40" AutoSize="TextChanges" FontSize="18" /> <Editor Placeholder="Beschreibung" Text="{Binding DayTime.Description}" MinimumHeightRequest="40" AutoSize="TextChanges" FontSize="18" />
@@ -66,9 +83,8 @@
<Label> <Label>
<Label.FormattedText> <Label.FormattedText>
<FormattedString> <FormattedString>
<Span Text="Am " /> <Span Text="Vorhandene Einträge: " />
<Span Text="{Binding SubTitle}" /> <Span Text="{Binding SubTitle}" />
<Span Text=" vorhandene Einträge:"/>
</FormattedString> </FormattedString>
</Label.FormattedText> </Label.FormattedText>
</Label> </Label>

View File

@@ -1,32 +1,54 @@
using CommunityToolkit.Maui.Alerts;
using CommunityToolkit.Maui.Core;
using Jugenddienst_Stunden.Models;
using Jugenddienst_Stunden.ViewModels; using Jugenddienst_Stunden.ViewModels;
using System.Windows.Input; using System.Windows.Input;
namespace Jugenddienst_Stunden.Views; namespace Jugenddienst_Stunden.Views;
/// <summary>
/// Einzelner Stundeneintrag
/// </summary>
public partial class StundePage : ContentPage { public partial class StundePage : ContentPage {
/// <summary> /// <summary>
/// CTOR /// CTOR
/// </summary> /// </summary>
public StundePage() { public StundePage() {
InitializeComponent(); InitializeComponent();
if (BindingContext is StundeViewModel vm) {
vm.AlertEvent += Vm_AlertEvent;
//vm.InfoEvent += Vm_InfoEvent;
vm.ConfirmEvent += ShowConfirm;
}
}
if (BindingContext is StundeViewModel vm) {
vm.AlertEvent += Vm_AlertEvent;
vm.InfoEvent += Vm_InfoEvent;
vm.ConfirmEvent += ShowConfirm;
}
}
private void Vm_AlertEvent(object? sender, string e) { private void Vm_AlertEvent(object? sender, string e) {
DisplayAlert("Fehler:", e, "OK"); DisplayAlert("Fehler:", e, "OK");
} }
private async Task<bool> ShowConfirm(string title, string message) {
return await DisplayAlert(title, message, "Passt!", "Na, nor decht nit.");
}
private async Task<bool> ShowConfirm(string title, string message) { private void Vm_InfoEvent(object? sender, string e) {
return await DisplayAlert(title, message, "Passt!", "Na, nor decht nit."); MainThread.BeginInvokeOnMainThread(async () => {
} CancellationTokenSource cts = new CancellationTokenSource();
ToastDuration duration = ToastDuration.Short;
double fontSize = 20;
var toast = Toast.Make(e, duration, fontSize);
await toast.Show(cts.Token);
});
}
//private async Task<bool> ShowConfirm(string title, string message, string ok, string not_ok) {
// return await DisplayAlert(title, message, ok, not_ok);
//}
//private async void ShowConfirm(object? sender, ConfirmEventArgs e) {
// bool result = await DisplayAlert(e.Title, e.Message, e.Ok, e.NotOk);
// e.Result = result;
//}
} }

View File

@@ -1,8 +1,9 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:models="clr-namespace:Jugenddienst_Stunden.ViewModels"
xmlns:mmodels="clr-namespace:Jugenddienst_Stunden.Models" xmlns:mmodels="clr-namespace:Jugenddienst_Stunden.Models"
xmlns:models="clr-namespace:Jugenddienst_Stunden.ViewModels"
xmlns:conv="clr-namespace:Jugenddienst_Stunden.Converter"
x:Class="Jugenddienst_Stunden.Views.StundenPage" x:Class="Jugenddienst_Stunden.Views.StundenPage"
Title="{Binding Title}"> Title="{Binding Title}">
@@ -11,12 +12,16 @@
</ContentPage.BindingContext> </ContentPage.BindingContext>
<ContentPage.Resources> <ContentPage.Resources>
<FontImageSource x:Key="ToolbarIcon" <ResourceDictionary>
<conv:SecondsTimeConverter x:Key="secToTime" />
<FontImageSource x:Key="ToolbarIcon"
Glyph="+" Glyph="+"
Size="22" Size="22"
Color="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}"/> Color="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}"/>
</ResourceDictionary>
</ContentPage.Resources> </ContentPage.Resources>
<ContentPage.ToolbarItems> <ContentPage.ToolbarItems>
<!--<ToolbarItem Text="Lade Liste" Command="{Binding RefreshListCommand}"/>--> <!--<ToolbarItem Text="Lade Liste" Command="{Binding RefreshListCommand}"/>-->
<ToolbarItem Text="Neuer Eintrag" IconImageSource="{StaticResource ToolbarIcon}" Command="{Binding NewEntryCommand}" /> <ToolbarItem Text="Neuer Eintrag" IconImageSource="{StaticResource ToolbarIcon}" Command="{Binding NewEntryCommand}" />
@@ -93,9 +98,9 @@
<Setter Property="Padding" Value="4"/> <Setter Property="Padding" Value="4"/>
</DataTrigger> </DataTrigger>
</HorizontalStackLayout.Triggers> </HorizontalStackLayout.Triggers>
<Label Text="{Binding GemeindeAktiv.Name}" IsVisible="{Binding Source={RelativeSource AncestorType={x:Type ContentPage}}, Path=BindingContext.GemeindeAktivSet}" /> <Label Text="{Binding GemeindeAktiv.Name}" Margin="0,0,10,0" IsVisible="{Binding Source={RelativeSource AncestorType={x:Type ContentPage}}, Path=BindingContext.GemeindeAktivSet}" />
<Label Text="{Binding ProjektAktiv.Name}" Margin="10,0,0,0" IsVisible="{Binding Source={RelativeSource AncestorType={x:Type ContentPage}}, Path=BindingContext.ProjektAktivSet}" /> <Label Text="{Binding ProjektAktiv.Name}" Margin="0,0,10,0" IsVisible="{Binding Source={RelativeSource AncestorType={x:Type ContentPage}}, Path=BindingContext.ProjektAktivSet}" />
<Label Text="{Binding FreistellungAktiv.Name}" Margin="10,0,0,0" /> <Label Text="{Binding FreistellungAktiv.Name}" IsVisible="{Binding Approved}" />
</HorizontalStackLayout> </HorizontalStackLayout>
<Label Text="{Binding Description}" Padding="0,0,0,15"/> <Label Text="{Binding Description}" Padding="0,0,0,15"/>
@@ -108,7 +113,7 @@
<BoxView HeightRequest="1" /> <BoxView HeightRequest="1" />
<Button Text="{Binding LoadOverview}" Command="{Binding LoadDataCommand}" /> <Button Text="{Binding LoadOverview}" Command="{Binding LoadDataCommand}" />
<Frame Padding="2" HeightRequest="125"> <Border Padding="2" HeightRequest="125">
<Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto,*" ColumnDefinitions="Auto,*" Margin="10,2"> <Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto,*" ColumnDefinitions="Auto,*" Margin="10,2">
<Label Grid.Row="0" Text="Soll:" /> <Label Grid.Row="0" Text="Soll:" />
<Label Grid.Row="1" Text="Summe:" /> <Label Grid.Row="1" Text="Summe:" />
@@ -117,13 +122,13 @@
<Label Grid.Row="4" Text="Restüberstunden:" /> <Label Grid.Row="4" Text="Restüberstunden:" />
<Label Grid.Row="5" Text="Resturlaub:" /> <Label Grid.Row="5" Text="Resturlaub:" />
<Label Grid.Row="0" Grid.Column="1" HorizontalTextAlignment="End" Padding="0,0,5,0" Text="{Binding Nominal}" ToolTipProperties.Text="Sollstunden" /> <Label Grid.Row="0" Grid.Column="1" HorizontalTextAlignment="End" Padding="0,0,5,0" Text="{Binding Nominal, Converter={StaticResource secToTime}}" ToolTipProperties.Text="Sollstunden" />
<Label Grid.Row="1" Grid.Column="1" HorizontalTextAlignment="End" Padding="0,0,5,0" Text="{Binding ZeitCalculated}" ToolTipProperties.Text="Geleistete Stunden" /> <Label Grid.Row="1" Grid.Column="1" HorizontalTextAlignment="End" Padding="0,0,5,0" Text="{Binding ZeitCalculated, Converter={StaticResource secToTime}}" ToolTipProperties.Text="Geleistete Stunden" />
<Label Grid.Row="2" Grid.Column="1" HorizontalTextAlignment="End" Padding="0,0,5,0" Text="{Binding OvertimeMonth}" /> <Label Grid.Row="2" Grid.Column="1" HorizontalTextAlignment="End" Padding="0,0,5,0" Text="{Binding OvertimeMonth, Converter={StaticResource secToTime}}" />
<Label Grid.Row="4" Grid.Column="1" HorizontalTextAlignment="End" Padding="0,0,5,0" Text="{Binding Overtime}" /> <Label Grid.Row="4" Grid.Column="1" HorizontalTextAlignment="End" Padding="0,0,5,0" Text="{Binding Overtime, Converter={StaticResource secToTime}}" />
<Label Grid.Row="5" Grid.Column="1" HorizontalTextAlignment="End" Padding="0,0,5,0" Text="{Binding Holiday}" /> <Label Grid.Row="5" Grid.Column="1" HorizontalTextAlignment="End" Padding="0,0,5,0" Text="{Binding Holiday, Converter={StaticResource secToTime}}" />
</Grid> </Grid>
</Frame> </Border>
</VerticalStackLayout> </VerticalStackLayout>

View File

@@ -1,5 +1,7 @@
using Jugenddienst_Stunden.ViewModels; using Jugenddienst_Stunden.ViewModels;
using Jugenddienst_Stunden.Models; using Jugenddienst_Stunden.Models;
using CommunityToolkit.Maui.Core;
using CommunityToolkit.Maui.Alerts;
namespace Jugenddienst_Stunden.Views; namespace Jugenddienst_Stunden.Views;
@@ -41,9 +43,21 @@ public partial class StundenPage : ContentPage {
await DisplayAlert("Fehler:", e, "OK"); await DisplayAlert("Fehler:", e, "OK");
}); });
} }
//private void Vm_InfoEvent(object? sender, string e) {
// DisplayAlert("Information:", e, "OK");
//}
//private void Vm_InfoEvent(object? sender, string e) {
// MainThread.BeginInvokeOnMainThread(async () => {
// await DisplayAlert("Information:", e, "OK");
// });
//}
private void Vm_InfoEvent(object? sender, string e) { private void Vm_InfoEvent(object? sender, string e) {
MainThread.BeginInvokeOnMainThread(async () => { MainThread.BeginInvokeOnMainThread(async () => {
await DisplayAlert("Information:", e, "OK"); CancellationTokenSource cts = new CancellationTokenSource();
ToastDuration duration = ToastDuration.Short;
double fontSize = 20;
var toast = Toast.Make(e, duration, fontSize);
await toast.Show(cts.Token);
}); });
} }