Final Changes

This commit is contained in:
Viperion 2025-06-29 17:50:02 +02:00
parent d18df4600a
commit 146aba1157
14 changed files with 444 additions and 214 deletions

View File

@ -1,18 +1,70 @@
using System;
namespace ChronoFlow.Model
{
/// <summary>
/// Repräsentiert einen Benutzer im System (Mitarbeiter oder Admin).
/// Wird für Authentifizierung, Rechteverwaltung und Zuordnung verwendet.
/// </summary>
public class User
{
/// <summary>
/// Der Benutzername, der zur Anmeldung verwendet wird.
/// </summary>
public string Username { get; set; }
/// <summary>
/// Das gehashte Passwort des Benutzers.
/// </summary>
public string Password { get; set; }
/// <summary>
/// Rolle des Benutzers, z.B. "Admin" oder "Mitarbeiter".
/// </summary>
public string Role { get; set; }
/// <summary>
/// Interne Mitarbeiternummer, optional.
/// </summary>
public string Mitarbeiternummer { get; set; }
/// <summary>
/// Abteilung des Mitarbeiters, z.B. „Produktion“, „IT“, etc.
/// </summary>
public string Abteilung { get; set; }
/// <summary>
/// Primärschlüssel in der Datenbank.
/// </summary>
public int Id { get; set; }
public string OriginalUsername { get; set; } // wichtig für Updates
/// <summary>
/// Der ursprüngliche Benutzername wird z.B. für Updates verwendet,
/// um Namensänderungen korrekt zu verarbeiten.
/// </summary>
public string OriginalUsername { get; set; }
/// <summary>
/// Gibt an, ob der Benutzer beim nächsten Login sein Passwort ändern muss.
/// Wird z.B. bei neu erstellten Konten gesetzt.
/// </summary>
public bool MussPasswortAendern { get; set; }
/// <summary>
/// Letzter erfolgreicher Login des Benutzers.
/// Dient z.B. zur Änderungsverfolgung von Projekten seit dem letzten Login.
/// </summary>
public DateTime LetzterLogin { get; set; } = DateTime.MinValue;
/// <summary>
/// Vorheriger Login-Zeitpunkt wird beim nächsten Login zu LetzterLogin verschoben.
/// Ermöglicht die Erkennung von Änderungen zwischen zwei Sessions.
/// </summary>
public DateTime VorletzterLogin { get; set; }
/// <summary>
/// Konstruktor initialisiert leere Zeichenketten zur Vermeidung von Nullwerten.
/// </summary>
public User()
{
Username = "";
@ -22,7 +74,5 @@ namespace ChronoFlow.Model
Abteilung = "";
OriginalUsername = "";
}
public DateTime LetzterLogin { get; set; } = DateTime.MinValue;
public DateTime VorletzterLogin { get; set; }
}
}

View File

@ -2,25 +2,53 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:model1="clr-namespace:ChronoFlow.Model;assembly=ChronoFlow.Model"
x:Class="ChronoFlow.View.Admin.AbgeschlosseneProjekteView">
<!-- Haupt-Layout mit vier Zeilen: Titel, Suchfeld, Liste, Zurück-Button -->
<Grid RowDefinitions="Auto,Auto,*,Auto" Margin="20">
<TextBlock Grid.Row="0" Text="Abgeschlossene Projekte" FontSize="20" FontWeight="Bold" HorizontalAlignment="Center" Margin="0,0,0,10"/>
<TextBox Grid.Row="1" x:Name="Suchfeld" Watermark="🔍 Nach Projekt oder Mitarbeiter suchen..." KeyUp="Suchfeld_KeyUp" Margin="0,0,0,10"/>
<!-- 🔷 Überschrift -->
<TextBlock Grid.Row="0"
Text="Abgeschlossene Projekte"
FontSize="20"
FontWeight="Bold"
HorizontalAlignment="Center"
Margin="0,0,0,10"/>
<!-- 🔍 Suchfeld zum Filtern der Projekte (nach Name oder Mitarbeiter) -->
<TextBox Grid.Row="1"
x:Name="Suchfeld"
Watermark="🔍 Nach Projekt oder Mitarbeiter suchen..."
KeyUp="Suchfeld_KeyUp"
Margin="0,0,0,10"/>
<!-- 📋 Scrollbare Liste abgeschlossener Projekte -->
<ScrollViewer Grid.Row="2">
<ListBox x:Name="AbgeschlosseneListe">
<ListBox.ItemTemplate>
<!-- Darstellung jedes abgeschlossenen Zeiteintrags -->
<DataTemplate DataType="{x:Type model1:Zeiteintrag}">
<StackPanel Orientation="Horizontal" Spacing="10" Margin="5">
<!-- 📌 Projektname -->
<TextBlock Text="{Binding Projekt}" Width="150"/>
<!-- 👤 Mitarbeitender -->
<TextBlock Text="{Binding Mitarbeiter}" Width="150"/>
<!-- ⏰ Endzeit -->
<TextBlock Text="{Binding Endzeit}" Width="150"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ScrollViewer>
<Button Grid.Row="3" Content="⬅ Zurück zum Dashboard" Click="ZurueckButton_Click" HorizontalAlignment="Center" Margin="0,10,0,0"/>
<!-- 🔙 Zurück-Button zum Admin-Dashboard -->
<Button Grid.Row="3"
Content="⬅ Zurück zum Dashboard"
Click="ZurueckButton_Click"
HorizontalAlignment="Center"
Margin="0,10,0,0"/>
</Grid>
</UserControl>

View File

@ -99,7 +99,7 @@ namespace ChronoFlow.View.Admin
}
/// <summary>
/// Wird z.B. vom ProjektErstellenView aufgerufen, um die Liste neu zu laden.
/// Wird z.B. vom ProjektErstellenView aufgerufen, um die Liste neu zu laden.
/// </summary>
public void AktualisiereLetzteProjekte()
{

View File

@ -4,12 +4,30 @@
Width="400" Height="200"
Title="Bestätigung">
<!-- 📦 Haupt-Layout: vertikale Anordnung mit Abstand -->
<StackPanel Margin="20" Spacing="10">
<TextBlock x:Name="FrageText" Text="Sind Sie sicher?" FontSize="16" FontWeight="Bold" TextWrapping="Wrap" />
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="10">
<Button Content="✅ Ja" Width="80" Click="JaButton_Click" />
<Button Content="❌ Nein" Width="80" Click="NeinButton_Click" />
<!-- ❓ Frage-Text (wird im Code gesetzt, z.B. "Möchten Sie das wirklich löschen?") -->
<TextBlock x:Name="FrageText"
Text="Sind Sie sicher?"
FontSize="16"
FontWeight="Bold"
TextWrapping="Wrap" />
<!-- 🔘 Buttons für Ja / Nein -->
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center"
Spacing="10">
<!-- ✅ Bestätigen -->
<Button Content="✅ Ja"
Width="80"
Click="JaButton_Click" />
<!-- ❌ Abbrechen -->
<Button Content="❌ Nein"
Width="80"
Click="NeinButton_Click" />
</StackPanel>
</StackPanel>
</Window>

View File

@ -2,28 +2,63 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:model1="clr-namespace:ChronoFlow.Model;assembly=ChronoFlow.Model"
x:Class="ChronoFlow.View.Admin.MitarbeiterListeView">
<!-- 🧱 Hauptlayout mit 4 Zeilen: Titel, Suchfeld, Liste, Zurück-Button -->
<Grid RowDefinitions="Auto,Auto,*,Auto" Margin="20">
<TextBlock Grid.Row="0" Text="Alle Mitarbeiter" FontSize="20" FontWeight="Bold" HorizontalAlignment="Center" Margin="0,0,0,10"/>
<TextBox Grid.Row="1" x:Name="Suchfeld" Watermark="🔍 Suchen..." KeyUp="Suchfeld_KeyUp" Margin="0,0,0,10"/>
<!-- 🧑‍🤝‍🧑 Titel der Ansicht -->
<TextBlock Grid.Row="0"
Text="Alle Mitarbeiter"
FontSize="20"
FontWeight="Bold"
HorizontalAlignment="Center"
Margin="0,0,0,10"/>
<!-- 🔍 Suchfeld zur Live-Suche nach Mitarbeitern (KeyUp-Event im CodeBehind) -->
<TextBox Grid.Row="1"
x:Name="Suchfeld"
Watermark="🔍 Suchen..."
KeyUp="Suchfeld_KeyUp"
Margin="0,0,0,10"/>
<!-- 📜 Scrollbarer Bereich für Mitarbeitereinträge -->
<ScrollViewer Grid.Row="2">
<ListBox x:Name="MitarbeiterListe">
<ListBox.ItemTemplate>
<!-- 🎨 Vorlage für jeden einzelnen Mitarbeiter (aus Model.User) -->
<DataTemplate DataType="{x:Type model1:User}">
<StackPanel Orientation="Horizontal" Spacing="10" Margin="5">
<StackPanel Orientation="Horizontal"
Spacing="10"
Margin="5">
<!-- 🧾 Benutzerinformationen -->
<TextBlock Text="{Binding Username}" Width="150"/>
<TextBlock Text="{Binding Abteilung}" Width="150"/>
<TextBlock Text="{Binding Mitarbeiternummer}" Width="150"/>
<Button Content="🖋 Bearbeiten" Tag="{Binding}" Click="Bearbeiten_Click"/>
<Button Content="🗑 Löschen" Tag="{Binding}" Click="Loeschen_Click"/>
<Button Content="🔑 Passwort zurücksetzen" Tag="{Binding}" Click="PasswortReset_Click" />
<!-- ✏ Aktionen für jeden Mitarbeiter -->
<Button Content="🖋 Bearbeiten"
Tag="{Binding}"
Click="Bearbeiten_Click"/>
<Button Content="🗑 Löschen"
Tag="{Binding}"
Click="Loeschen_Click"/>
<Button Content="🔑 Passwort zurücksetzen"
Tag="{Binding}"
Click="PasswortReset_Click"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ScrollViewer>
<Button Grid.Row="3" Content="⬅ Zurück zum Dashboard" Click="ZurueckButton_Click" HorizontalAlignment="Center" Margin="0,10,0,0"/>
<!-- 🔙 Zurück zum Admin-Dashboard -->
<Button Grid.Row="3"
Content="⬅ Zurück zum Dashboard"
Click="ZurueckButton_Click"
HorizontalAlignment="Center"
Margin="0,10,0,0"/>
</Grid>
</UserControl>

View File

@ -2,70 +2,64 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ChronoFlow.View.Admin.ProjektErstellenView">
<!-- Scrollfähiger Bereich für kleinere Fenster -->
<ScrollViewer>
<!-- Gesamter Inhalt ist scrollbar -->
<ScrollViewer Padding="10">
<StackPanel Margin="25" Spacing="15">
<!-- Titelüberschrift -->
<!-- Titel -->
<TextBlock Text="Neues Projekt erstellen"
FontSize="20"
FontWeight="Bold"
HorizontalAlignment="Center" />
<!-- Eingabefeld: Projektname -->
<!-- Projektname -->
<TextBlock Text="Projektname:" />
<TextBox x:Name="ProjektnameBox" />
<!-- Startdatum auswählen -->
<!-- Startzeit -->
<TextBlock Text="Startdatum:" />
<DatePicker x:Name="StartdatumPicker" />
<!-- Startzeit im Format HH:mm -->
<TextBlock Text="Startzeit (HH:mm):" />
<TextBox x:Name="StartzeitBox" Watermark="z.B. 09:00" />
<!-- Enddatum auswählen -->
<!-- Endzeit -->
<TextBlock Text="Enddatum:" />
<DatePicker x:Name="EnddatumPicker" />
<!-- Endzeit im Format HH:mm -->
<TextBlock Text="Endzeit (HH:mm):" />
<TextBox x:Name="EndzeitBox" Watermark="z.B. 17:00" />
<!-- Projektleiter über Dropdown zuweisen -->
<!-- Projektleiter -->
<TextBlock Text="Projektleiter auswählen:" />
<ComboBox x:Name="ProjektleiterDropdown" />
<StackPanel>
<TextBlock Text="Mitarbeiter auswählen:" Margin="0,10,0,5" />
<Expander Header=" Mitarbeitende auswählen" Background="#222" Foreground="White">
<ListBox x:Name="MitarbeiterListBox"
SelectionMode="Multiple"
Height="250"
MinWidth="300"
BorderBrush="Gray"
BorderThickness="1"
Background="#333"
Foreground="White"
ScrollViewer.VerticalScrollBarVisibility="Auto">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" Padding="4" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Expander>
<!-- Mitarbeiterauswahl -->
<TextBlock Text="Mitarbeitende auswählen:" Margin="0,10,0,5" />
<StackPanel Orientation="Horizontal" Spacing="10" Margin="0,0,0,5">
<Button Content="✅ Alle auswählen" Click="AlleMitarbeiterAuswaehlen_Click" />
<Button Content="❌ Keine auswählen" Click="KeineMitarbeiterAuswaehlen_Click" />
</StackPanel>
<!-- Kommentarbereich -->
<!-- Scrollbarer Kartenbereich mit Checkboxen -->
<ScrollViewer Height="240"
VerticalScrollBarVisibility="Auto"
Background="#2a2a2a"
Margin="0,0,0,10"
Padding="10,10,10,-17"
CornerRadius="8">
<ListBox x:Name="MitarbeiterCheckList"
BorderThickness="0"
SelectionMode="Multiple" />
</ScrollViewer>
<!-- Kommentar -->
<TextBlock Text="Kommentar:" />
<TextBox x:Name="KommentarBox" AcceptsReturn="True" Height="80" />
<!-- Hinweise zur Deadline-Farbe -->
<Border Background="#111111"
CornerRadius="5"
Padding="10"
Margin="0,10,0,0">
<!-- Farblegende für Deadlines -->
<Border Background="#111111" CornerRadius="5" Padding="10" Margin="0,10,0,0">
<StackPanel Spacing="5">
<StackPanel Orientation="Horizontal" Spacing="5">
<TextBlock Text="🛈" FontWeight="Bold" />
@ -77,28 +71,14 @@
</StackPanel>
</Border>
<!-- Aktions-Buttons -->
<StackPanel Orientation="Horizontal"
Spacing="10"
HorizontalAlignment="Center"
Margin="0,10,0,0">
<Button Content="✅ Speichern"
Click="SpeichernButton_Click"
Width="115"
Height="36" />
<Button Content="🧪 3 Demo-Projekte (rot/gelb/grün)"
Click="DemoProjekteButton_Click"
Width="250"
Height="36" />
<Button Content="⬅ Zurück zum Dashboard"
Click="ZurueckButton_Click"
Width="180"
Height="36" />
<!-- Aktionsbuttons -->
<StackPanel Orientation="Horizontal" Spacing="10" HorizontalAlignment="Center" Margin="0,10,0,0">
<Button Content="✅ Speichern" Click="SpeichernButton_Click" Width="115" Height="36" />
<Button Content="🧪 3 Demo-Projekte (rot/gelb/grün)" Click="DemoProjekteButton_Click" Width="250" Height="36" />
<Button Content="⬅ Zurück zum Dashboard" Click="ZurueckButton_Click" Width="180" Height="36" />
</StackPanel>
<!-- Optionaler Fehler-/Hinweistext -->
<!-- Feedbackbereich -->
<TextBlock x:Name="FeedbackText"
Foreground="Red"
IsVisible="False"

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Media;
@ -16,6 +17,7 @@ namespace ChronoFlow.View.Admin;
public partial class ProjektErstellenView : UserControl
{
private readonly ViewManager _viewManager;
private readonly Dictionary<string, CheckBox> _mitarbeiterCheckBoxMap = new();
public ProjektErstellenView() : this(new ViewManager(new ContentControl()))
{
@ -30,35 +32,74 @@ public partial class ProjektErstellenView : UserControl
try
{
var dbService = new SqliteZeiterfassungsService();
List<string> mitarbeiter = dbService.LadeAlleMitarbeiterNamen();
var mitarbeiter = dbService.LadeAlleMitarbeiterNamen();
// 🔍 Konsolenausgabe zur Kontrolle
if (mitarbeiter is { Count: > 0 })
{
Console.WriteLine("✅ Mitarbeitende erfolgreich geladen:");
foreach (var name in mitarbeiter)
Console.WriteLine("- " + name);
ProjektleiterDropdown.ItemsSource = mitarbeiter;
// Liste manuell aufbauen und Checkboxen erzeugen
_mitarbeiterCheckBoxMap.Clear();
var checkBoxen = new List<CheckBox>();
foreach (var name in mitarbeiter)
{
Console.WriteLine($"- {name}");
var cb = new CheckBox
{
Content = name,
Foreground = Brushes.White,
FontSize = 14,
Margin = new Thickness(4)
};
_mitarbeiterCheckBoxMap[name] = cb;
checkBoxen.Add(cb);
}
// ItemsSource der ListBox mit den Checkboxen befüllen
MitarbeiterCheckList.ItemsSource = checkBoxen;
}
else
{
Console.WriteLine("⚠️ Achtung: Mitarbeitendenliste ist leer.");
}
// Items in GUI setzen
MitarbeiterListBox!.ItemsSource = mitarbeiter;
ProjektleiterDropdown!.ItemsSource = mitarbeiter;
}
catch (Exception ex)
{
Console.WriteLine($"❌ Fehler beim Laden der Mitarbeiterauswahl: {ex.Message}");
FeedbackText!.Text = "⚠ Fehler beim Laden der Mitarbeiter.";
FeedbackText.Text = "⚠ Fehler beim Laden der Mitarbeiter.";
FeedbackText.Foreground = Brushes.Red;
FeedbackText.IsVisible = true;
}
}
private List<string> ErmittleAusgewaehlteMitarbeiter()
{
return _mitarbeiterCheckBoxMap
.Where(kvp => kvp.Value.IsChecked == true)
.Select(kvp => kvp.Key)
.ToList();
}
private void AlleMitarbeiterAuswaehlen_Click(object? sender, RoutedEventArgs e)
{
foreach (var cb in _mitarbeiterCheckBoxMap.Values)
{
cb.IsChecked = true;
}
}
private void KeineMitarbeiterAuswaehlen_Click(object? sender, RoutedEventArgs e)
{
foreach (var cb in _mitarbeiterCheckBoxMap.Values)
{
cb.IsChecked = false;
}
}
private void SpeichernButton_Click(object? sender, RoutedEventArgs e)
{
@ -72,7 +113,7 @@ public partial class ProjektErstellenView : UserControl
string kommentar = KommentarBox.Text ?? "";
string projektleiter = ProjektleiterDropdown.SelectedItem?.ToString() ?? "";
var ausgewaehlteMitarbeiter = MitarbeiterListBox?.SelectedItems?.Cast<string>().ToList() ?? new List<string>();
var ausgewaehlteMitarbeiter = ErmittleAusgewaehlteMitarbeiter();
if (string.IsNullOrWhiteSpace(projektname) || ausgewaehlteMitarbeiter.Count == 0 || string.IsNullOrWhiteSpace(projektleiter))
{
@ -97,11 +138,11 @@ public partial class ProjektErstellenView : UserControl
{
var dbService = new SqliteZeiterfassungsService();
foreach (var einzelnerMitarbeiter in ausgewaehlteMitarbeiter)
foreach (var name in ausgewaehlteMitarbeiter)
{
dbService.SpeichereEintrag(new Zeiteintrag
{
Mitarbeiter = einzelnerMitarbeiter,
Mitarbeiter = name,
Projekt = projektname,
Startzeit = startDateTime,
Endzeit = endDateTime,
@ -121,10 +162,11 @@ public partial class ProjektErstellenView : UserControl
EnddatumPicker.SelectedDate = DateTime.Today;
StartzeitBox.Text = "09:00";
EndzeitBox.Text = "17:00";
MitarbeiterListBox.SelectedItems?.Clear();
MitarbeiterListBox.SelectedIndex = -1;
ProjektleiterDropdown.SelectedItem = null;
foreach (var cb in _mitarbeiterCheckBoxMap.Values)
cb.IsChecked = false;
if (_viewManager.TryGetView<AdminMainView>("AdminMain", out var adminView) && adminView != null)
adminView.AktualisiereLetzteProjekte();
}
@ -134,11 +176,10 @@ public partial class ProjektErstellenView : UserControl
FeedbackText.Foreground = Brushes.Red;
FeedbackText.IsVisible = true;
Console.WriteLine("❌ Ausnahme beim Speichern:");
Console.WriteLine(ex.ToString());
Console.WriteLine(ex);
}
}
private void DemoProjekteButton_Click(object? sender, RoutedEventArgs e)
{
if (ProjektleiterDropdown.SelectedItem is not string projektleiterName)
@ -149,7 +190,9 @@ public partial class ProjektErstellenView : UserControl
return;
}
if (MitarbeiterListBox.SelectedItems?.Count == 0)
var ausgewaehlteMitarbeiter = ErmittleAusgewaehlteMitarbeiter();
if (ausgewaehlteMitarbeiter.Count == 0)
{
FeedbackText.Text = "❗ Bitte wähle mindestens einen Mitarbeiter für die Demo-Projekte.";
FeedbackText.Foreground = Brushes.Red;
@ -160,8 +203,7 @@ public partial class ProjektErstellenView : UserControl
var service = new SqliteZeiterfassungsService();
var heute = DateTime.Today;
var mitarbeiter = MitarbeiterListBox?.SelectedItems?.Cast<string>().ToList() ?? new List<string>();
foreach (var name in mitarbeiter)
foreach (var name in ausgewaehlteMitarbeiter)
{
var projekte = new List<Zeiteintrag>
{

View File

@ -5,19 +5,44 @@ using Avalonia.Media;
namespace ChronoFlow.View.Converter
{
/// <summary>
/// Konvertiert einen booleschen Wert in eine Farbe (Brush).
/// True => Blau, False => Grau.
/// Wird z.B. für farbliche Statusanzeigen in der Oberfläche verwendet.
/// </summary>
public class BoolToBrushConverter : IValueConverter
{
/// <summary>
/// Die Farbe, die bei "true" verwendet wird.
/// Kann in XAML überschrieben werden.
/// </summary>
public IBrush TrueBrush { get; set; } = Brushes.Blue;
/// <summary>
/// Die Farbe, die bei "false" verwendet wird.
/// Kann in XAML überschrieben werden.
/// </summary>
public IBrush FalseBrush { get; set; } = Brushes.Gray;
/// <summary>
/// Führt die Umwandlung des bool-Wertes in eine Brush durch.
/// </summary>
/// <param name="value">Der Wert, der umgewandelt werden soll (sollte bool sein).</param>
/// <param name="targetType">Zieltyp der Bindung (erwartet IBrush).</param>
/// <param name="parameter">Optionaler Parameter (nicht verwendet).</param>
/// <param name="culture">Kultureinstellungen (nicht verwendet).</param>
/// <returns>TrueBrush oder FalseBrush abhängig vom bool-Wert.</returns>
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is bool boolValue && boolValue)
return Brushes.Blue;
return TrueBrush;
return Brushes.Gray;
return FalseBrush;
}
/// <summary>
/// Umkehrkonvertierung wird hier nicht benötigt.
/// </summary>
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException(); // wird i.d.R. nicht benötigt

View File

@ -11,14 +11,15 @@ using Microsoft.Data.Sqlite;
namespace ChronoFlow.View;
/// <summary>
/// Das Fenster für den Benutzer-Login.
/// Das Login-Fenster für Benutzer der Anwendung.
/// Es prüft die Anmeldedaten, erzwingt ggf. eine Passwortänderung und startet das Hauptfenster.
/// </summary>
public partial class LoginWindow : Window
{
private readonly LoginController _loginController;
/// <summary>
/// Konstruktor Initialisiert die Oberfläche und legt bei Bedarf einen Standard-Admin an.
/// Konstruktor Initialisiert UI und legt bei Bedarf den Standard-Admin an.
/// </summary>
public LoginWindow()
{
@ -28,7 +29,7 @@ public partial class LoginWindow : Window
try
{
var service = new SqliteZeiterfassungsService();
service.ErstelleStandardAdmin();
service.ErstelleStandardAdmin(); // Falls noch kein Admin vorhanden, wird ein Default-Admin erstellt.
}
catch (SqliteException ex) when (ex.SqliteErrorCode == 5)
{
@ -43,13 +44,14 @@ public partial class LoginWindow : Window
}
/// <summary>
/// Wird aufgerufen, wenn der Benutzer auf "Anmelden" klickt.
/// Wird aufgerufen, wenn der Benutzer auf „Anmelden“ klickt.
/// </summary>
private async void LoginButton_Click(object? sender, RoutedEventArgs e)
{
var username = UsernameBox.Text?.Trim();
var password = PasswordBox.Text?.Trim();
// Validierung: Felder dürfen nicht leer sein
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
{
ErrorText.Text = "Bitte Benutzername und Passwort eingeben.";
@ -64,13 +66,14 @@ public partial class LoginWindow : Window
}
catch (SqliteException ex) when (ex.SqliteErrorCode == 5)
{
ErrorText.Text = "⚠️ Die Datenbank ist gesperrt. Bitte schließen Sie andere Programme (z.B. DB Browser) und versuchen Sie es erneut.";
ErrorText.Text = "⚠️ Die Datenbank ist gesperrt. Bitte schließen Sie andere Programme und versuchen Sie es erneut.";
ErrorText.IsVisible = true;
return;
}
var benutzerListe = service.LadeAlleBenutzer();
// Benutzer mit eingegebenem Namen (case-insensitive) finden
var matchingUsers = benutzerListe
.Where(u => u.Username.Equals(username, StringComparison.OrdinalIgnoreCase))
.ToList();
@ -91,6 +94,7 @@ public partial class LoginWindow : Window
var user = matchingUsers.First();
// Passwort-Überprüfung mit Hashing
if (!PasswordHasher.VerifyPassword(password, user.Password))
{
ErrorText.Text = "Falsches Passwort. Bitte erneut versuchen.";
@ -98,7 +102,7 @@ public partial class LoginWindow : Window
return;
}
// Passwort muss geändert werden
// Benutzer muss Passwort ändern? → Dialog anzeigen
if (user.MussPasswortAendern)
{
var dialog = new PasswortAendernDialog(user);
@ -121,13 +125,13 @@ public partial class LoginWindow : Window
try
{
// Login-Zeiten aktualisieren
// Login-Zeit aktualisieren (für spätere Änderungsbenachrichtigungen)
var vorher = user.LetzterLogin;
user.LetzterLogin = DateTime.Now;
user.VorletzterLogin = vorher;
service.UpdateLoginZeiten(user);
// Hauptfenster öffnen
// Hauptfenster öffnen und Login-Fenster schließen
var main = new MainWindow(user);
main.Show();
main.Activate();

View File

@ -91,7 +91,7 @@ public partial class MainWindow : Window
}
/// <summary>
/// Extern aufrufbar für "Zurück zum Dashboard"-Funktion (z.B. in anderen Views).
/// Extern aufrufbar für "Zurück zum Dashboard"-Funktion (z.B. in anderen Views).
/// </summary>
public void ShowAdminDashboard()
{

View File

@ -11,71 +11,86 @@ using ChronoFlow.Controller;
namespace ChronoFlow.View.Mitarbeiter;
/// <summary>
/// ViewModel für die Mitarbeiter-Aufgabenansicht.
/// Enthält alle Aufgaben für den eingeloggten Benutzer und erlaubt das Speichern von Änderungen.
/// ViewModel für die Aufgabenansicht eines Mitarbeiters.
/// Lädt relevante Aufgaben (nicht erledigt) und ermöglicht das Speichern von Kommentaren und Bearbeitungsstatus.
/// </summary>
public partial class EmployeeTasksViewModel : ObservableObject
{
private readonly User _benutzer;
private readonly ZeiterfassungsController controller = new();
private readonly string aktuellerBenutzername;
private readonly User _benutzer; // Der aktuell eingeloggte Benutzer
private readonly ZeiterfassungsController controller = new(); // Controller für Datenzugriff
private readonly string aktuellerBenutzername; // Benutzername (String, z.B. für Vergleich)
// Liste aller offenen, relevanten Aufgaben für diesen Benutzer
[ObservableProperty]
private ObservableCollection<Zeiteintrag> eintraege = new();
// Status-Text für Hinweise oder Rückmeldungen an den Benutzer
[ObservableProperty]
private string? statusText;
// Gibt an, ob überhaupt Aufgaben vorhanden sind (z.B. für "Keine Aufgaben"-Hinweis in der View)
public bool HatKeineEintraege => Eintraege.Count == 0;
// Wenn sich die Einträge ändern, wird auch das Property `HatKeineEintraege` neu berechnet
partial void OnEintraegeChanged(ObservableCollection<Zeiteintrag>? oldValue, ObservableCollection<Zeiteintrag> newValue)
{
OnPropertyChanged(nameof(HatKeineEintraege));
}
/// <summary>
/// Konstruktor übernimmt eingeloggten Benutzer, speichert Name für spätere Vergleiche
/// und lädt initial die relevanten Einträge.
/// </summary>
public EmployeeTasksViewModel(User benutzer)
{
_benutzer = benutzer;
aktuellerBenutzername = benutzer.Username;
controller = new ZeiterfassungsController(); // falls du das oben nicht hast
_ = LadeEintraegeAsync();
controller = new ZeiterfassungsController();
_ = LadeEintraegeAsync(); // asynchrone Initialladung
}
/// <summary>
/// Lädt alle offenen Aufgaben für diesen Benutzer (oder wenn er Projektleiter ist).
/// Sortiert die Aufgaben nach Enddatum.
/// Markiert Aufgaben als „geändert“, wenn sie seit dem letzten Login bearbeitet wurden.
/// </summary>
[RelayCommand]
public async Task LadeEintraegeAsync()
{
// 🔄 Einträge synchron laden, aber async verpacken
// Alle Aufgaben laden (aus Datenbank)
var alleEintraege = await Task.Run(() => controller.LadeAlleEintraege());
// Relevante Aufgaben filtern: noch nicht erledigt und betreffen den Benutzer
var relevanteEintraege = alleEintraege
.Where(e => !e.Erledigt &&
(e.Mitarbeiter == aktuellerBenutzername || e.Projektleiter == aktuellerBenutzername))
.OrderBy(e => e.Endzeit)
.ToList();
// Prüfung, ob Aufgabe nach letztem Login geändert wurde
foreach (var eintrag in relevanteEintraege)
{
eintrag.WurdeSeitLoginBearbeitet = eintrag.LetzteBearbeitung > _benutzer.VorletzterLogin;
}
// Hinweis anzeigen, wenn Aufgaben seit letztem Login verändert wurden
if (relevanteEintraege.Any(e => e.WurdeSeitLoginBearbeitet))
StatusText = "📢 Es wurden Aufgaben seit Ihrem letzten Login geändert.";
// Die View wird über `Eintraege` gebunden, daher muss hier neu zugewiesen werden
Eintraege = new ObservableCollection<Zeiteintrag>(relevanteEintraege);
}
/// <summary>
/// Speichert alle Änderungen (Kommentar + Erledigt-Status) der aktuellen Aufgaben.
/// Danach wird die Liste neu geladen.
/// </summary>
[RelayCommand]
public async Task SpeichereEintraegeAsync()
{
// Achtung: Hier verwendest du noch das Repository, obwohl du oben mit dem Controller arbeitest.
// Du kannst entweder:
// A) Das Repository auch im ViewModel übergeben
// B) Die Methode UpdateStatusUndKommentarAsync auch in den Controller packen
// Achtung: Diese Methode ruft direkt den Service auf.
// Dies könnte in Zukunft auch über den Controller geschehen.
foreach (var eintrag in Eintraege)
{
await Task.Run(() =>
@ -85,6 +100,7 @@ public partial class EmployeeTasksViewModel : ObservableObject
});
}
// Rückmeldung anzeigen und Liste aktualisieren
StatusText = "✅ Änderungen gespeichert.";
await LadeEintraegeAsync();
}

View File

@ -17,7 +17,7 @@ public partial class MitarbeiterMainView : UserControl
private readonly ObservableCollection<string> _notifications = new();
/// <summary>
/// Öffentlicher, parameterloser Konstruktor erforderlich für Avalonia Runtime Loader (z.B. Designer).
/// Öffentlicher, parameterloser Konstruktor erforderlich für Avalonia Runtime Loader (z.B. Designer).
/// Startet mit Dummy-Werten.
/// </summary>
public MitarbeiterMainView()

View File

@ -109,24 +109,26 @@ namespace ChronoFlow.View
/// </summary>
private void DemoBenutzerErstellen_Click(object? sender, RoutedEventArgs e)
{
var namen = new[] { "Max Mustermann", "Lena Schmidt", "Tobias Becker", "Julia Klein", "Nico Weber" };
var zufallsname = namen[new Random().Next(namen.Length)];
string[] vornamen = { "Max", "Lena", "Tobias", "Julia", "Nico", "Emma", "Finn", "Sophie", "Noah", "Laura" };
string[] nachnamen = { "Mustermann", "Schmidt", "Becker", "Klein", "Weber", "Meier", "Schulz", "Huber", "Lang", "Richter" };
var service = new SqliteZeiterfassungsService();
var existierendeUsernames = service.LadeAlleBenutzer().Select(u => u.Username).ToHashSet(StringComparer.OrdinalIgnoreCase);
// Prüfen, ob Name bereits vorhanden ist
if (service.LadeAlleBenutzer().Any(u => u.Username.Equals(zufallsname, StringComparison.OrdinalIgnoreCase)))
var rnd = new Random();
int versuche = 0;
while (versuche < 100)
{
FeedbackText.Text = "⚠ Demo-Mitarbeiter existiert bereits.";
FeedbackText.Foreground = Brushes.OrangeRed;
FeedbackText.IsVisible = true;
return;
}
string vorname = vornamen[rnd.Next(vornamen.Length)];
string nachname = nachnamen[rnd.Next(nachnamen.Length)];
string name = $"{vorname} {nachname}";
// Demo-User erstellen
if (!existierendeUsernames.Contains(name))
{
var demoUser = new User
{
Username = zufallsname,
Username = name,
Password = PasswordHasher.HashPassword("newpassword"),
Role = "Mitarbeiter",
MussPasswortAendern = true
@ -134,9 +136,18 @@ namespace ChronoFlow.View
service.ErstelleNeuenBenutzer(demoUser);
FeedbackText.Text = $"✅ Demo-Mitarbeiter '{zufallsname}' erstellt (Passwort: newpassword)";
FeedbackText.Text = $"✅ Demo-Mitarbeiter '{name}' erstellt (Passwort: newpassword)";
FeedbackText.Foreground = Brushes.Green;
FeedbackText.IsVisible = true;
return;
}
versuche++;
}
FeedbackText.Text = "❌ Fehler: Konnte keinen eindeutigen Demo-Mitarbeiter generieren.";
FeedbackText.Foreground = Brushes.Red;
FeedbackText.IsVisible = true;
}
}
}

View File

@ -4,20 +4,41 @@
Width="400" Height="250"
Title="Passwort ändern">
<!-- Haupt-Layout: vertikaler StackPanel für die Anordnung aller Elemente -->
<StackPanel Margin="20" Spacing="10">
<TextBlock x:Name="UsernameTextBlock" Text="Benutzer: " FontSize="16" FontWeight="Bold" />
<!-- Überschrift: Benutzername (wird zur Laufzeit ergänzt) -->
<TextBlock x:Name="UsernameTextBlock"
Text="Benutzer: "
FontSize="16"
FontWeight="Bold" />
<!-- Eingabe neues Passwort -->
<TextBlock Text="Neues Passwort:" />
<TextBox x:Name="NeuesPasswortBox" PasswordChar="●" />
<TextBox x:Name="NeuesPasswortBox"
PasswordChar="●" /> <!-- Passwort wird maskiert -->
<!-- Eingabe Bestätigung des Passworts -->
<TextBlock Text="Passwort bestätigen:" />
<TextBox x:Name="BestaetigenBox" PasswordChar="●" />
<TextBox x:Name="BestaetigenBox"
PasswordChar="●" /> <!-- Wiederholung zur Sicherheit -->
<TextBlock x:Name="FehlerText" Foreground="Red" IsVisible="False" />
<!-- Fehlermeldung (standardmäßig ausgeblendet) -->
<TextBlock x:Name="FehlerText"
Foreground="Red"
IsVisible="False" />
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="10" Margin="0,10,0,0">
<Button Content="💾 Speichern" Click="SpeichernButton_Click" Width="100" />
<Button Content="❌ Abbrechen" Click="AbbrechenButton_Click" Width="100" />
<!-- Aktionsbuttons (zentriert nebeneinander) -->
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center"
Spacing="10"
Margin="0,10,0,0">
<Button Content="💾 Speichern"
Click="SpeichernButton_Click"
Width="100" />
<Button Content="❌ Abbrechen"
Click="AbbrechenButton_Click"
Width="100" />
</StackPanel>
</StackPanel>
</Window>