Compare commits

...

3 Commits

Author SHA1 Message Date
d18df4600a Project leader funciton 2025-06-28 20:12:56 +02:00
88c5ed5484 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	ChronoFlow.View/Admin/MitarbeiterBearbeitenDialog.axaml
#	ChronoFlow.View/Admin/MitarbeiterBearbeitenDialog.axaml.cs
#	ChronoFlow.View/Security/PasswortAendernDialog.axaml.cs
2025-06-28 14:27:45 +02:00
b206a43e47 Updates 2025-06-28 14:27:18 +02:00
27 changed files with 1734 additions and 738 deletions

View File

@ -27,9 +27,13 @@ namespace ChronoFlow.Controller
} }
// Diese Methode lädt alle bisherigen Zeiteinträge aus der Datenbank // Diese Methode lädt alle bisherigen Zeiteinträge aus der Datenbank
public List<Zeiteintrag> LadeAlleEintraege() public async Task<List<Zeiteintrag>> LadeAlleEintraegeAsync()
{ {
// Holt die Liste der Einträge von der Datenbank und gibt sie zurück // Holt die Liste der Einträge von der Datenbank und gibt sie zurück
return await _sqliteService.LadeAlleEintraegeAsync();
}
public List<Zeiteintrag> LadeAlleEintraege()
{
return _sqliteService.LadeAlleZeiteintraege(); return _sqliteService.LadeAlleZeiteintraege();
} }
} }

View File

@ -1,28 +1,95 @@
namespace ChronoFlow.Model namespace ChronoFlow.Model
{ {
/// <summary>
/// Datenmodell für einen Zeiteintrag / ein Projekt.
/// Wird z.B. zur Speicherung in SQLite verwendet.
/// </summary>
public class Zeiteintrag public class Zeiteintrag
{ {
public int Id { get; set; } // <<< NEU /// <summary>
/// Eindeutige ID des Eintrags in der Datenbank.
/// </summary>
public int Id { get; set; }
/// <summary>
/// Name des zugewiesenen Mitarbeiters.
/// </summary>
public string Mitarbeiter { get; set; } public string Mitarbeiter { get; set; }
/// <summary>
/// Name des Projektleiters (neu hinzugefügt).
/// </summary>
public string Projektleiter { get; set; } = "";
/// <summary>
/// Zeitpunkt, wann das Projekt beginnt.
/// </summary>
public DateTime Startzeit { get; set; } public DateTime Startzeit { get; set; }
/// <summary>
/// Zeitpunkt, wann das Projekt endet.
/// </summary>
public DateTime Endzeit { get; set; } public DateTime Endzeit { get; set; }
/// <summary>
/// Name oder Titel des Projekts.
/// </summary>
public string Projekt { get; set; } public string Projekt { get; set; }
/// <summary>
/// Beschreibung oder Kommentar durch den Admin.
/// </summary>
public string Kommentar { get; set; } public string Kommentar { get; set; }
/// <summary>
/// Gibt an, ob das Projekt als erledigt markiert wurde.
/// </summary>
public bool Erledigt { get; set; } public bool Erledigt { get; set; }
/// <summary>
/// Kommentar vom Mitarbeiter (z.B. Rückmeldung zum Projekt).
/// </summary>
public string MitarbeiterKommentar { get; set; } public string MitarbeiterKommentar { get; set; }
/// <summary>
/// Zeitpunkt der letzten Änderung an diesem Eintrag.
/// </summary>
public DateTime LetzteBearbeitung { get; set; }
/// <summary>
/// Kennzeichnet, ob es sich um einen neu erzeugten Eintrag handelt (z.B. seit dem letzten Login).
/// </summary>
public bool IstNeu { get; set; }
/// <summary>
/// True, wenn der Eintrag seit dem letzten Login verändert wurde.
/// </summary>
public bool WurdeSeitLoginBearbeitet { get; set; }
/// <summary>
/// Konstruktor Initialisiert alle Felder mit Standardwerten.
/// </summary>
public Zeiteintrag() public Zeiteintrag()
{ {
Id = 0; Id = 0;
Mitarbeiter = ""; Mitarbeiter = "";
Projektleiter = ""; // <<< NEU
Startzeit = DateTime.Now; Startzeit = DateTime.Now;
Endzeit = DateTime.Now; Endzeit = DateTime.Now;
Projekt = ""; Projekt = "";
Kommentar = ""; Kommentar = "";
Erledigt = false; Erledigt = false;
MitarbeiterKommentar = ""; MitarbeiterKommentar = "";
Projektleiter = "";
LetzteBearbeitung = DateTime.Now;
IstNeu = false;
WurdeSeitLoginBearbeitet = false;
} }
/// <summary>
/// Gibt die Prioritätsfarbe basierend auf der verbleibenden Zeit bis zur Deadline zurück.
/// Rot = < 3 Tage, Orange = 37 Tage, Grün = > 7 Tage.
/// </summary>
public string PrioritaetsFarbe public string PrioritaetsFarbe
{ {
get get
@ -37,8 +104,5 @@ namespace ChronoFlow.Model
return "Green"; return "Green";
} }
} }
public DateTime LetzteBearbeitung { get; set; }
public bool IstNeu { get; set; } // wird im ViewModel gesetzt
public bool WurdeSeitLoginBearbeitet { get; set; }
} }
} }

View File

@ -5,14 +5,35 @@ using ChronoFlow.Model;
namespace ChronoFlow.Persistence namespace ChronoFlow.Persistence
{ {
/// <summary>
/// Repository-Interface für den Zugriff auf Zeiteinträge in der Datenbank.
/// Wird von der SqliteZeiterfassungsService-Klasse implementiert.
/// </summary>
public interface ITimeEntryRepository public interface ITimeEntryRepository
{ {
/// <summary>
/// Gibt alle Zeiteinträge für einen bestimmten Mitarbeiter zurück.
/// </summary>
Task<List<Zeiteintrag>> GetEntriesForUserAsync(string username); Task<List<Zeiteintrag>> GetEntriesForUserAsync(string username);
Task UpdateEntryStatusAndCommentAsync(int id, bool isCompleted, string? comment);
// (Optional) Weitere Methoden für Admin-Funktionen: /// <summary>
Task<List<Zeiteintrag>> GetAllEntriesAsync(); // Admin /// Gibt alle Zeiteinträge aus der Datenbank zurück (Admin-Ansicht).
Task AddEntryAsync(Zeiteintrag entry); // Admin /// </summary>
Task DeleteEntryAsync(int id); // Admin Task<List<Zeiteintrag>> LadeAlleEintraegeAsync();
/// <summary>
/// Fügt einen neuen Zeiteintrag in die Datenbank ein.
/// </summary>
Task AddEntryAsync(Zeiteintrag entry);
/// <summary>
/// Löscht einen Zeiteintrag anhand seiner ID.
/// </summary>
Task DeleteEntryAsync(int id);
/// <summary>
/// Aktualisiert den Status (erledigt) und den Kommentar eines Eintrags.
/// </summary>
Task UpdateEntryStatusAndCommentAsync(int id, bool isCompleted, string? comment);
} }
} }

View File

@ -7,26 +7,39 @@ using ChronoFlow.Security;
namespace ChronoFlow.Persistence namespace ChronoFlow.Persistence
{ {
// Schnittstelle für die Zeiterfassungsfunktionen
public interface IZeiterfassungsRepository public interface IZeiterfassungsRepository
{ {
Task<List<Zeiteintrag>> GetEintraegeFuerMitarbeiterAsync(string mitarbeiterName); Task<List<Zeiteintrag>> GetEintraegeFuerMitarbeiterAsync(string mitarbeiterName);
Task UpdateStatusUndKommentarAsync(int id, bool erledigt, string mitarbeiterKommentar); Task UpdateStatusUndKommentarAsync(int id, bool erledigt, string mitarbeiterKommentar);
} }
/// <summary>
/// Implementierung des SQLite-Datenbankzugriffs für Benutzer und Zeiteinträge.
/// </summary>
public class SqliteZeiterfassungsService : IZeiterfassungsRepository public class SqliteZeiterfassungsService : IZeiterfassungsRepository
{ {
// Pfad zur SQLite-Datenbankdatei
private readonly string _dbPath = "chrono_data.sb"; private readonly string _dbPath = "chrono_data.sb";
/// <summary>
/// Konstruktor: Erstellt Datenbank falls nicht vorhanden und stellt die Struktur sicher.
/// </summary>
public SqliteZeiterfassungsService() public SqliteZeiterfassungsService()
{ {
if (!File.Exists(_dbPath)) if (!File.Exists(_dbPath))
ErstelleDatenbank(); ErstelleDatenbank();
// IMMER prüfen, auch nach neuem Erstellen // Auch bei bestehender Datei sicherstellen, dass alle Spalten vorhanden sind
PrüfeUndErweitereDatenbank(); PrüfeUndErweitereDatenbank();
_ = StelleDatenbankstrukturSicherAsync(); // Async-Aufruf ignorieren // Async-Aufruf wird absichtlich nicht awaitet
_ = StelleDatenbankstrukturSicherAsync();
} }
/// <summary>
/// Erstellt die Basis-Datenbankstruktur, falls Datei noch nicht existiert.
/// </summary>
private void ErstelleDatenbank() private void ErstelleDatenbank()
{ {
Console.WriteLine("🛠️ ErstelleDatenbank wurde aufgerufen!"); Console.WriteLine("🛠️ ErstelleDatenbank wurde aufgerufen!");
@ -61,9 +74,11 @@ namespace ChronoFlow.Persistence
);"; );";
cmd2.ExecuteNonQuery(); cmd2.ExecuteNonQuery();
} }
public void ErstelleNeuenBenutzer(User benutzer) public void ErstelleNeuenBenutzer(User benutzer)
{ {
// Erstellt eine neue Benutzerzeile in der Datenbank
using var connection = new SqliteConnection($"Data Source={_dbPath}"); using var connection = new SqliteConnection($"Data Source={_dbPath}");
connection.Open(); connection.Open();
@ -81,26 +96,28 @@ namespace ChronoFlow.Persistence
cmd.Parameters.AddWithValue("$MussPasswortAendern", benutzer.MussPasswortAendern ? 1 : 0); cmd.Parameters.AddWithValue("$MussPasswortAendern", benutzer.MussPasswortAendern ? 1 : 0);
int rowsAffected = cmd.ExecuteNonQuery(); int rowsAffected = cmd.ExecuteNonQuery();
Console.WriteLine($"✅ Neuer Benutzer '{benutzer.Username}' wurde gespeichert (Rows affected: {rowsAffected})."); Console.WriteLine(
$"✅ Neuer Benutzer '{benutzer.Username}' wurde gespeichert (Rows affected: {rowsAffected}).");
} }
private void PrüfeUndErweitereDatenbank() private void PrüfeUndErweitereDatenbank()
{ {
// Stellt sicher, dass die Datenbank alle benötigten Spalten enthält
using var connection = new SqliteConnection($"Data Source={_dbPath}"); using var connection = new SqliteConnection($"Data Source={_dbPath}");
connection.Open(); connection.Open();
AddColumnIfMissing(connection, "Benutzer", "Mitarbeitennummer", "TEXT"); AddColumnIfMissing(connection, "Benutzer", "Mitarbeitennummer", "TEXT");
AddColumnIfMissing(connection, "Benutzer", "Abteilung", "TEXT"); AddColumnIfMissing(connection, "Benutzer", "Abteilung", "TEXT");
AddColumnIfMissing(connection, "Benutzer", "MussPasswortAendern", "INTEGER DEFAULT 1"); AddColumnIfMissing(connection, "Benutzer", "MussPasswortAendern", "INTEGER DEFAULT 1");
AddColumnIfMissing(connection, "Benutzer", "LetzterLogin", "TEXT"); AddColumnIfMissing(connection, "Benutzer", "LetzterLogin", "TEXT");
AddColumnIfMissing(connection, "Benutzer", "VorletzterLogin", "TEXT"); AddColumnIfMissing(connection, "Benutzer", "VorletzterLogin", "TEXT");
AddColumnIfMissing(connection, "Zeiteintraege", "LetzteBearbeitung", "TEXT"); AddColumnIfMissing(connection, "Zeiteintraege", "LetzteBearbeitung", "TEXT");
} }
private void AddColumnIfMissing(SqliteConnection connection, string tableName, string columnName, string columnType) private void AddColumnIfMissing(SqliteConnection connection, string tableName, string columnName,
string columnType)
{ {
// Prüft, ob eine Spalte existiert und fügt sie bei Bedarf hinzu
var checkCmd = connection.CreateCommand(); var checkCmd = connection.CreateCommand();
checkCmd.CommandText = $"PRAGMA table_info({tableName});"; checkCmd.CommandText = $"PRAGMA table_info({tableName});";
@ -128,6 +145,7 @@ namespace ChronoFlow.Persistence
public void ErstelleStandardAdmin() public void ErstelleStandardAdmin()
{ {
// Erstellt einen Admin-Benutzer, wenn dieser noch nicht existiert
using var connection = new SqliteConnection($"Data Source={_dbPath}"); using var connection = new SqliteConnection($"Data Source={_dbPath}");
connection.Open(); connection.Open();
@ -140,9 +158,9 @@ namespace ChronoFlow.Persistence
var hashedPassword = PasswordHasher.HashPassword("admin"); var hashedPassword = PasswordHasher.HashPassword("admin");
var insertCmd = connection.CreateCommand(); var insertCmd = connection.CreateCommand();
insertCmd.CommandText = @" insertCmd.CommandText = @"
INSERT INTO Benutzer (Username, Password, Role, Mitarbeitennummer, Abteilung, MussPasswortAendern) INSERT INTO Benutzer (Username, Password, Role, Mitarbeitennummer, Abteilung, MussPasswortAendern)
VALUES ('admin', $Password, 'Admin', '0001', 'IT', 1); VALUES ('admin', $Password, 'Admin', '0001', 'IT', 1);
"; ";
insertCmd.Parameters.AddWithValue("$Password", hashedPassword); insertCmd.Parameters.AddWithValue("$Password", hashedPassword);
insertCmd.ExecuteNonQuery(); insertCmd.ExecuteNonQuery();
@ -158,47 +176,51 @@ namespace ChronoFlow.Persistence
{ {
var benutzerListe = new List<User>(); var benutzerListe = new List<User>();
using var connection = new SqliteConnection($"Data Source={_dbPath}"); try
connection.Open();
var cmd = connection.CreateCommand();
cmd.CommandText = @"
SELECT Id, Username, Password, Role, Mitarbeitennummer, Abteilung, MussPasswortAendern, LetzterLogin, VorletzterLogin
FROM Benutzer;
";
using var reader = cmd.ExecuteReader();
while (reader.Read())
{ {
// Absicherungen für mögliche NULL- oder ungültige Werte: using var connection = new SqliteConnection($"Data Source={_dbPath}");
var letzterLogin = DateTime.MinValue; connection.Open();
var vorletzterLogin = DateTime.MinValue;
if (!reader.IsDBNull(7)) var cmd = connection.CreateCommand();
DateTime.TryParse(reader.GetString(7), out letzterLogin); cmd.CommandText = @"
SELECT Id, Username, Password, Role, Mitarbeitennummer, Abteilung, MussPasswortAendern, LetzterLogin, VorletzterLogin
FROM Benutzer;
";
if (!reader.IsDBNull(8)) using var reader = cmd.ExecuteReader();
DateTime.TryParse(reader.GetString(8), out vorletzterLogin); while (reader.Read())
benutzerListe.Add(new User
{ {
Id = reader.GetInt32(0), // Parsing wie gehabt
Username = reader.GetString(1), benutzerListe.Add(new User
Password = reader.GetString(2), {
Role = reader.GetString(3), Id = reader.GetInt32(0),
Mitarbeiternummer = reader.IsDBNull(4) ? "" : reader.GetString(4), Username = reader.GetString(1),
Abteilung = reader.IsDBNull(5) ? "" : reader.GetString(5), Password = reader.GetString(2),
MussPasswortAendern = reader.GetInt32(6) == 1, Role = reader.GetString(3),
LetzterLogin = letzterLogin, Mitarbeiternummer = reader.IsDBNull(4) ? "" : reader.GetString(4),
VorletzterLogin = vorletzterLogin, Abteilung = reader.IsDBNull(5) ? "" : reader.GetString(5),
OriginalUsername = reader.GetString(1) MussPasswortAendern = reader.GetInt32(6) == 1,
}); LetzterLogin = reader.IsDBNull(7) ? DateTime.MinValue : DateTime.Parse(reader.GetString(7)),
VorletzterLogin = reader.IsDBNull(8) ? DateTime.MinValue : DateTime.Parse(reader.GetString(8)),
OriginalUsername = reader.GetString(1)
});
}
}
catch (SqliteException ex) when (ex.SqliteErrorCode == 5)
{
Console.WriteLine("❌ Datenbank ist gesperrt. Bitte schließen Sie andere Programme, die auf die Datenbank zugreifen.");
throw new InvalidOperationException("Die Datenbank ist derzeit gesperrt bitte schließen Sie andere Programme (z.B. DB Browser) und starten Sie die Anwendung neu.");
} }
return benutzerListe; return benutzerListe;
} }
/// <summary>
/// Aktualisiert einen vorhandenen Benutzer in der Datenbank.
/// </summary>
public void UpdateBenutzer(User benutzer) public void UpdateBenutzer(User benutzer)
{ {
using var connection = new SqliteConnection($"Data Source={_dbPath}"); using var connection = new SqliteConnection($"Data Source={_dbPath}");
@ -228,6 +250,9 @@ namespace ChronoFlow.Persistence
Console.WriteLine($"✏ Benutzer aktualisiert: {benutzer.Username} (Rows affected: {rowsAffected})"); Console.WriteLine($"✏ Benutzer aktualisiert: {benutzer.Username} (Rows affected: {rowsAffected})");
} }
/// <summary>
/// Löscht einen Benutzer anhand seines Benutzernamens.
/// </summary>
public void LoescheBenutzer(string username) public void LoescheBenutzer(string username)
{ {
using var connection = new SqliteConnection($"Data Source={_dbPath}"); using var connection = new SqliteConnection($"Data Source={_dbPath}");
@ -241,6 +266,9 @@ namespace ChronoFlow.Persistence
Console.WriteLine($"❌ Benutzer gelöscht: {username} (Rows affected: {rowsAffected})"); Console.WriteLine($"❌ Benutzer gelöscht: {username} (Rows affected: {rowsAffected})");
} }
/// <summary>
/// Gibt eine Liste aller Benutzernamen mit Rolle 'Mitarbeiter' zurück.
/// </summary>
public List<string> LadeAlleMitarbeiterNamen() public List<string> LadeAlleMitarbeiterNamen()
{ {
var namen = new List<string>(); var namen = new List<string>();
@ -260,33 +288,83 @@ namespace ChronoFlow.Persistence
return namen; return namen;
} }
/// <summary>
/// Speichert einen neuen Zeiteintrag in der Datenbank.
/// </summary>
/// <summary>
/// Speichert einen neuen Zeiteintrag in der Datenbank.
/// </summary>
/// <param name="eintrag">Das zu speichernde Zeiteintrag-Objekt.</param>
public void SpeichereEintrag(Zeiteintrag eintrag) public void SpeichereEintrag(Zeiteintrag eintrag)
{ {
using var connection = new SqliteConnection($"Data Source={_dbPath}"); using var connection = new SqliteConnection($"Data Source={_dbPath}");
connection.Open(); connection.Open();
// SQL-Befehl mit zusätzlicher Spalte „Projektleiter“
var cmd = connection.CreateCommand(); var cmd = connection.CreateCommand();
cmd.CommandText = @" cmd.CommandText = @"
INSERT INTO Zeiteintraege INSERT INTO Zeiteintraege
(Mitarbeiter, Startzeit, Endzeit, Projekt, Kommentar, Erledigt, MitarbeiterKommentar, LetzteBearbeitung) (Mitarbeiter, Startzeit, Endzeit, Projekt, Kommentar, Erledigt, MitarbeiterKommentar, LetzteBearbeitung, Projektleiter)
VALUES VALUES
($Mitarbeiter, $Startzeit, $Endzeit, $Projekt, $Kommentar, $Erledigt, $MitarbeiterKommentar, $LetzteBearbeitung);"; ($Mitarbeiter, $Startzeit, $Endzeit, $Projekt, $Kommentar, $Erledigt, $MitarbeiterKommentar, $LetzteBearbeitung, $Projektleiter);";
// Parameter an das SQL-Kommando binden
cmd.Parameters.AddWithValue("$Mitarbeiter", eintrag.Mitarbeiter); cmd.Parameters.AddWithValue("$Mitarbeiter", eintrag.Mitarbeiter);
cmd.Parameters.AddWithValue("$Startzeit", eintrag.Startzeit.ToString("o")); cmd.Parameters.AddWithValue("$Startzeit", eintrag.Startzeit.ToString("o")); // ISO-8601-Format
cmd.Parameters.AddWithValue("$Endzeit", eintrag.Endzeit.ToString("o")); cmd.Parameters.AddWithValue("$Endzeit", eintrag.Endzeit.ToString("o"));
cmd.Parameters.AddWithValue("$Projekt", eintrag.Projekt ?? ""); cmd.Parameters.AddWithValue("$Projekt", eintrag.Projekt ?? "");
cmd.Parameters.AddWithValue("$Kommentar", eintrag.Kommentar ?? ""); cmd.Parameters.AddWithValue("$Kommentar", eintrag.Kommentar ?? "");
cmd.Parameters.AddWithValue("$Erledigt", eintrag.Erledigt ? 1 : 0); cmd.Parameters.AddWithValue("$Erledigt", eintrag.Erledigt ? 1 : 0);
cmd.Parameters.AddWithValue("$MitarbeiterKommentar", eintrag.MitarbeiterKommentar ?? ""); cmd.Parameters.AddWithValue("$MitarbeiterKommentar", eintrag.MitarbeiterKommentar ?? "");
cmd.Parameters.AddWithValue("$LetzteBearbeitung", DateTime.Now.ToString("o")); cmd.Parameters.AddWithValue("$LetzteBearbeitung", DateTime.Now.ToString("o"));
cmd.Parameters.AddWithValue("$Projektleiter", eintrag.Projektleiter ?? "");
// Ausführung des SQL-Befehls
cmd.ExecuteNonQuery(); cmd.ExecuteNonQuery();
Console.WriteLine($"✅ Zeiteintrag für {eintrag.Mitarbeiter} gespeichert."); Console.WriteLine($"✅ Zeiteintrag für {eintrag.Mitarbeiter} gespeichert.");
} }
/// <summary>
/// Lädt alle Zeiteinträge aus der Datenbank.
/// </summary>
public async Task<List<Zeiteintrag>> LadeAlleEintraegeAsync()
{
var eintraege = new List<Zeiteintrag>();
using var connection = new SqliteConnection($"Data Source={_dbPath}");
await connection.OpenAsync();
var command = connection.CreateCommand();
command.CommandText = "SELECT * FROM Zeiteintraege";
using var reader = await command.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
eintraege.Add(new Zeiteintrag
{
Id = reader.GetInt32(0),
Mitarbeiter = reader.GetString(1),
Startzeit = DateTime.Parse(reader.GetString(2)),
Endzeit = DateTime.Parse(reader.GetString(3)),
Projekt = reader.GetString(4),
Kommentar = reader.GetString(5),
Erledigt = reader.GetInt32(6) == 1,
MitarbeiterKommentar = reader.GetString(7),
LetzteBearbeitung = DateTime.Parse(reader.GetString(8)),
Projektleiter = reader.IsDBNull(9) ? "" : reader.GetString(9)
});
}
return eintraege;
}
/// <summary>
/// Lädt alle vorhandenen Zeiteinträge aus der Datenbank.
/// Wird z.B. von Admins oder Projektleitern verwendet, um eine Gesamtübersicht
/// aller Projekte und Aufgaben zu erhalten.
/// </summary>
public List<Zeiteintrag> LadeAlleZeiteintraege() public List<Zeiteintrag> LadeAlleZeiteintraege()
{ {
var eintraege = new List<Zeiteintrag>(); var eintraege = new List<Zeiteintrag>();
@ -300,7 +378,7 @@ VALUES
using var reader = cmd.ExecuteReader(); using var reader = cmd.ExecuteReader();
while (reader.Read()) while (reader.Read())
{ {
eintraege.Add(new Zeiteintrag var eintrag = new Zeiteintrag
{ {
Id = reader.GetInt32(0), Id = reader.GetInt32(0),
Mitarbeiter = reader.GetString(1), Mitarbeiter = reader.GetString(1),
@ -308,14 +386,25 @@ VALUES
Endzeit = DateTime.Parse(reader.GetString(3)), Endzeit = DateTime.Parse(reader.GetString(3)),
Projekt = reader.GetString(4), Projekt = reader.GetString(4),
Kommentar = reader.GetString(5), Kommentar = reader.GetString(5),
Erledigt = Convert.ToInt32(reader["Erledigt"]) == 1, Erledigt = reader.GetBoolean(6),
MitarbeiterKommentar = reader.GetString(7) MitarbeiterKommentar = reader.GetString(7),
}); LetzteBearbeitung = reader.IsDBNull(8)
? DateTime.MinValue
: DateTime.Parse(reader.GetString(8)),
Projektleiter = reader.IsDBNull(9) ? "" : reader.GetString(9)
};
eintraege.Add(eintrag);
} }
return eintraege; return eintraege;
} }
/// <summary>
/// Aktualisiert ein bestehendes Projekt in der Datenbank anhand der ID.
/// </summary>
/// <param name="projekt">Das Projekt, das aktualisiert werden soll.</param>
public void UpdateProjekt(Zeiteintrag projekt) public void UpdateProjekt(Zeiteintrag projekt)
{ {
using var connection = new SqliteConnection($"Data Source={_dbPath}"); using var connection = new SqliteConnection($"Data Source={_dbPath}");
@ -325,12 +414,12 @@ VALUES
cmd.CommandText = @" cmd.CommandText = @"
UPDATE Zeiteintraege UPDATE Zeiteintraege
SET Projekt = $Projekt, SET Projekt = $Projekt,
Kommentar = $Kommentar, Kommentar = $Kommentar,
Startzeit = $Startzeit, Startzeit = $Startzeit,
Endzeit = $Endzeit, Endzeit = $Endzeit,
Mitarbeiter = $Mitarbeiter, Mitarbeiter = $Mitarbeiter,
Erledigt = $Erledigt, Erledigt = $Erledigt,
LetzteBearbeitung = $LetzteBearbeitung LetzteBearbeitung = $LetzteBearbeitung
WHERE Id = $Id;"; WHERE Id = $Id;";
cmd.Parameters.AddWithValue("$Projekt", projekt.Projekt); cmd.Parameters.AddWithValue("$Projekt", projekt.Projekt);
@ -346,6 +435,10 @@ VALUES
Console.WriteLine($"✏ Projekt aktualisiert (Id={projekt.Id}, Rows affected: {rowsAffected})"); Console.WriteLine($"✏ Projekt aktualisiert (Id={projekt.Id}, Rows affected: {rowsAffected})");
} }
/// <summary>
/// Löscht ein Projekt anhand seiner ID aus der Datenbank.
/// </summary>
/// <param name="id">Die ID des zu löschenden Projekts.</param>
public void LoescheProjekt(int id) public void LoescheProjekt(int id)
{ {
using var connection = new SqliteConnection($"Data Source={_dbPath}"); using var connection = new SqliteConnection($"Data Source={_dbPath}");
@ -359,6 +452,11 @@ VALUES
Console.WriteLine($"❌ Projekt gelöscht (Id: {id}, Rows: {rows})"); Console.WriteLine($"❌ Projekt gelöscht (Id: {id}, Rows: {rows})");
} }
/// <summary>
/// Aktualisiert nur den Status "Erledigt" eines Projekts.
/// </summary>
/// <param name="id">ID des Projekts.</param>
/// <param name="erledigt">Neuer Status (true/false).</param>
public void UpdateProjektStatus(int id, bool erledigt) public void UpdateProjektStatus(int id, bool erledigt)
{ {
using var connection = new SqliteConnection($"Data Source={_dbPath}"); using var connection = new SqliteConnection($"Data Source={_dbPath}");
@ -375,9 +473,14 @@ VALUES
cmd.Parameters.AddWithValue("$Id", id); cmd.Parameters.AddWithValue("$Id", id);
int rowsAffected = cmd.ExecuteNonQuery(); int rowsAffected = cmd.ExecuteNonQuery();
Console.WriteLine($"✅ Projektstatus aktualisiert (Id={id}, Erledigt={erledigt}, Rows affected: {rowsAffected})"); Console.WriteLine(
$"✅ Projektstatus aktualisiert (Id={id}, Erledigt={erledigt}, Rows affected: {rowsAffected})");
} }
/// <summary>
/// Lädt alle Projekte, die als erledigt markiert sind.
/// </summary>
/// <returns>Liste abgeschlossener Zeiteinträge.</returns>
public List<Zeiteintrag> LadeAbgeschlosseneProjekte() public List<Zeiteintrag> LadeAbgeschlosseneProjekte()
{ {
var abgeschlossene = new List<Zeiteintrag>(); var abgeschlossene = new List<Zeiteintrag>();
@ -406,6 +509,11 @@ VALUES
return abgeschlossene; return abgeschlossene;
} }
/// <summary>
/// Lädt die letzten Projekte basierend auf der ID (absteigend sortiert).
/// </summary>
/// <param name="anzahl">Anzahl der letzten Projekte.</param>
/// <returns>Liste der letzten Zeiteinträge.</returns>
public List<Zeiteintrag> LadeLetzteProjekte(int anzahl = 3) public List<Zeiteintrag> LadeLetzteProjekte(int anzahl = 3)
{ {
var projekte = new List<Zeiteintrag>(); var projekte = new List<Zeiteintrag>();
@ -439,6 +547,10 @@ VALUES
return projekte; return projekte;
} }
/// <summary>
/// Lädt alle noch nicht erledigten Projekte (Zeiteinträge) aus der Datenbank.
/// </summary>
/// <returns>Liste offener Zeiteinträge</returns>
public List<Zeiteintrag> LadeOffeneProjekte() public List<Zeiteintrag> LadeOffeneProjekte()
{ {
var offene = new List<Zeiteintrag>(); var offene = new List<Zeiteintrag>();
@ -460,26 +572,35 @@ VALUES
Projekt = reader.GetString(4), Projekt = reader.GetString(4),
Kommentar = reader.GetString(5), Kommentar = reader.GetString(5),
Erledigt = Convert.ToInt32(reader["Erledigt"]) == 1, Erledigt = Convert.ToInt32(reader["Erledigt"]) == 1,
MitarbeiterKommentar = reader.GetString(7) MitarbeiterKommentar = reader.GetString(7),
LetzteBearbeitung = reader.IsDBNull(8) ? DateTime.MinValue : DateTime.Parse(reader.GetString(8)),
Projektleiter = reader.IsDBNull(9) ? "" : reader.GetString(9) // << NEU
}); });
} }
return offene; return offene;
} }
/// <summary>
/// Setzt das Passwort eines Benutzers zurück (auf 'changeme').
/// Das Passwort wird automatisch gehasht.
/// </summary>
public void ResetBenutzerPasswort(string username) public void ResetBenutzerPasswort(string username)
{ {
using var connection = new SqliteConnection($"Data Source={_dbPath}"); using var connection = new SqliteConnection($"Data Source={_dbPath}");
connection.Open(); connection.Open();
// Neues Standardpasswort hashen
var hashedDefault = PasswordHasher.HashPassword("changeme"); var hashedDefault = PasswordHasher.HashPassword("changeme");
var cmd = connection.CreateCommand(); var cmd = connection.CreateCommand();
cmd.CommandText = @" cmd.CommandText = @"
UPDATE Benutzer UPDATE Benutzer
SET Password = $Password, SET Password = $Password,
MussPasswortAendern = 1 MussPasswortAendern = 1
WHERE Username = $Username; WHERE Username = $Username;
"; ";
cmd.Parameters.AddWithValue("$Password", hashedDefault); cmd.Parameters.AddWithValue("$Password", hashedDefault);
cmd.Parameters.AddWithValue("$Username", username); cmd.Parameters.AddWithValue("$Username", username);
@ -487,7 +608,134 @@ VALUES
int rowsAffected = cmd.ExecuteNonQuery(); int rowsAffected = cmd.ExecuteNonQuery();
Console.WriteLine($"🔒 Passwort für Benutzer '{username}' zurückgesetzt (Rows affected: {rowsAffected})"); Console.WriteLine($"🔒 Passwort für Benutzer '{username}' zurückgesetzt (Rows affected: {rowsAffected})");
} }
/// <summary>
/// Lädt alle Zeiteinträge eines bestimmten Mitarbeiters aus der Datenbank.
/// </summary>
/// <param name="name">Name des Mitarbeiters</param>
/// <returns>Liste der zugehörigen Zeiteinträge</returns>
public async Task<List<Zeiteintrag>> GetEintraegeFuerMitarbeiterAsync(string name)
{
var eintraege = new List<Zeiteintrag>();
using var connection = new SqliteConnection($"Data Source={_dbPath}");
await connection.OpenAsync();
var cmd = connection.CreateCommand();
cmd.CommandText = @"
SELECT Id, Mitarbeiter, Startzeit, Endzeit, Projekt, Kommentar, Erledigt, MitarbeiterKommentar, LetzteBearbeitung, Projektleiter
FROM Zeiteintraege
WHERE Mitarbeiter = $Name;";
cmd.Parameters.AddWithValue("$Name", name);
using var reader = await cmd.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
eintraege.Add(new Zeiteintrag
{
Id = reader.GetInt32(0),
Mitarbeiter = reader.GetString(1),
Startzeit = reader.GetDateTime(2),
Endzeit = reader.GetDateTime(3),
Projekt = reader.GetString(4),
Kommentar = reader.GetString(5),
Erledigt = reader.GetBoolean(6),
MitarbeiterKommentar = reader.GetString(7),
LetzteBearbeitung = reader.IsDBNull(8) ? DateTime.MinValue : reader.GetDateTime(8),
Projektleiter = reader.IsDBNull(9) ? "" : reader.GetString(9) // << NEU
});
}
return eintraege;
}
/// <summary>
/// Überprüft, ob die Spalte 'VorletzterLogin' in der Tabelle 'Benutzer' vorhanden ist.
/// Falls nicht, wird sie hinzugefügt.
/// </summary>
public async Task StelleDatenbankstrukturSicherAsync()
{
using var connection = new SqliteConnection($"Data Source={_dbPath}");
await connection.OpenAsync();
// ========= 🧪 Spalte 'VorletzterLogin' in 'Benutzer' prüfen =========
var checkBenutzerCmd = connection.CreateCommand();
checkBenutzerCmd.CommandText = "PRAGMA table_info(Benutzer);";
var readerBenutzer = await checkBenutzerCmd.ExecuteReaderAsync();
bool vorletzterLoginExistiert = false;
while (await readerBenutzer.ReadAsync())
{
if (readerBenutzer.GetString(1).Equals("VorletzterLogin", StringComparison.OrdinalIgnoreCase))
{
vorletzterLoginExistiert = true;
break;
}
}
await readerBenutzer.DisposeAsync();
if (!vorletzterLoginExistiert)
{
var alterBenutzerCmd = connection.CreateCommand();
alterBenutzerCmd.CommandText = "ALTER TABLE Benutzer ADD COLUMN VorletzterLogin TEXT;";
await alterBenutzerCmd.ExecuteNonQueryAsync();
Console.WriteLine("🛠 Spalte 'VorletzterLogin' wurde zur Tabelle 'Benutzer' hinzugefügt.");
}
else
{
Console.WriteLine("✅ Spalte 'VorletzterLogin' ist bereits vorhanden.");
}
// ========= 🧪 Spalte 'Projektleiter' in 'Zeiteintraege' prüfen =========
var checkProjektleiterCmd = connection.CreateCommand();
checkProjektleiterCmd.CommandText = "PRAGMA table_info(Zeiteintraege);";
var readerProjektleiter = await checkProjektleiterCmd.ExecuteReaderAsync();
bool projektleiterExistiert = false;
while (await readerProjektleiter.ReadAsync())
{
if (readerProjektleiter.GetString(1).Equals("Projektleiter", StringComparison.OrdinalIgnoreCase))
{
projektleiterExistiert = true;
break;
}
}
await readerProjektleiter.DisposeAsync();
if (!projektleiterExistiert)
{
var alterZeiteintraegeCmd = connection.CreateCommand();
alterZeiteintraegeCmd.CommandText = "ALTER TABLE Zeiteintraege ADD COLUMN Projektleiter TEXT;";
await alterZeiteintraegeCmd.ExecuteNonQueryAsync();
Console.WriteLine("🛠 Spalte 'Projektleiter' wurde zur Tabelle 'Zeiteintraege' hinzugefügt.");
}
else
{
Console.WriteLine("✅ Spalte 'Projektleiter' ist bereits vorhanden.");
}
// ========= 🛠 Neue Tabelle 'ProjektMitarbeiter' erstellen =========
var createProjektMitarbeiterCmd = connection.CreateCommand();
createProjektMitarbeiterCmd.CommandText = @"
CREATE TABLE IF NOT EXISTS ProjektMitarbeiter (
ProjektId INTEGER NOT NULL,
Mitarbeiter TEXT NOT NULL,
PRIMARY KEY (ProjektId, Mitarbeiter),
FOREIGN KEY (ProjektId) REFERENCES Zeiteintraege(Id)
);
";
await createProjektMitarbeiterCmd.ExecuteNonQueryAsync();
Console.WriteLine("✅ Tabelle 'ProjektMitarbeiter' wurde (falls nötig) erstellt.");
}
/// <summary>
/// Aktualisiert die Login-Zeitpunkte des Benutzers in der Datenbank.
/// </summary>
/// <param name="user">Der Benutzer, dessen Loginzeiten aktualisiert werden sollen</param>
public void UpdateLoginZeiten(User user) public void UpdateLoginZeiten(User user)
{ {
using var connection = new SqliteConnection($"Data Source={_dbPath}"); using var connection = new SqliteConnection($"Data Source={_dbPath}");
@ -501,86 +749,21 @@ VALUES
WHERE Username = $Username; WHERE Username = $Username;
"; ";
cmd.Parameters.AddWithValue("$LetzterLogin", user.LetzterLogin.ToString("o")); // Setzt die Parameter für das SQL-Statement
cmd.Parameters.AddWithValue("$VorletzterLogin", user.VorletzterLogin.ToString("o")); cmd.Parameters.AddWithValue("$LetzterLogin", user.LetzterLogin.ToString("o")); // ISO 8601-Format
cmd.Parameters.AddWithValue("$Username", user.Username); cmd.Parameters.AddWithValue("$VorletzterLogin", user.VorletzterLogin.ToString("o")); // ebenfalls ISO
cmd.Parameters.AddWithValue("$Username", user.Username); // Nutzername als Schlüssel
cmd.ExecuteNonQuery(); cmd.ExecuteNonQuery(); // Führt das Update aus
}
public async Task<List<Zeiteintrag>> GetEintraegeFuerMitarbeiterAsync(string name)
{
var eintraege = new List<Zeiteintrag>();
using var connection = new SqliteConnection($"Data Source={_dbPath}");
await connection.OpenAsync();
var cmd = connection.CreateCommand();
cmd.CommandText = @"
SELECT Id, Mitarbeiter, Startzeit, Endzeit, Projekt, Kommentar, Erledigt, MitarbeiterKommentar, LetzteBearbeitung
FROM Zeiteintraege
WHERE Mitarbeiter = $Name;
";
cmd.Parameters.AddWithValue("$Name", name);
using var reader = await cmd.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
eintraege.Add(new Zeiteintrag
{
Id = reader.GetInt32(0),
Mitarbeiter = reader.GetString(1),
Startzeit = reader.GetDateTime(2),
Endzeit = reader.GetDateTime(3),
Projekt = reader.GetString(4),
Kommentar = reader.GetString(5),
Erledigt = reader.GetBoolean(6),
MitarbeiterKommentar = reader.GetString(7),
LetzteBearbeitung = reader.IsDBNull(8) ? DateTime.MinValue : reader.GetDateTime(8)
});
}
return eintraege;
} }
public async Task StelleDatenbankstrukturSicherAsync()
{
using var connection = new SqliteConnection($"Data Source={_dbPath}");
await connection.OpenAsync();
//Überprüfung ob die Spalte "Voletzter Login" bereits besteht
var checkCmd = connection.CreateCommand();
checkCmd.CommandText = "PRAGMA table_info(Benutzer);";
var reader = await checkCmd.ExecuteReaderAsync();
bool spalteExistiert = false;
while (await reader.ReadAsync())
{
if (reader.GetString(1).Equals("Vorletzer Login", StringComparison.OrdinalIgnoreCase))
{
spalteExistiert = true;
break;
}
}
await reader.DisposeAsync();
if (!spalteExistiert)
{
var alterCmd = connection.CreateCommand();
alterCmd.CommandText = "ALTER TABLE Benutzer ADD COLUMN VorletzterLogin TEXT;";
await alterCmd.ExecuteNonQueryAsync();
Console.WriteLine("🛠 Spalte 'VorletzterLogin' wurde zur Tabelle 'Benutzer' hinzugefügt.");
}
else
{
Console.WriteLine("✅ Spalte 'VorletzterLogin' ist bereits vorhanden.");
}
}
/// <summary>
/// Aktualisiert den Erledigt-Status, Kommentar und das Bearbeitungsdatum eines Zeiteintrags.
/// </summary>
/// <param name="id">ID des Zeiteintrags</param>
/// <param name="erledigt">Neuer Status: erledigt oder nicht</param>
/// <param name="mitarbeiterKommentar">Neuer Kommentar des Mitarbeiters</param>
public async Task UpdateStatusUndKommentarAsync(int id, bool erledigt, string mitarbeiterKommentar) public async Task UpdateStatusUndKommentarAsync(int id, bool erledigt, string mitarbeiterKommentar)
{ {
using var connection = new SqliteConnection($"Data Source={_dbPath}"); using var connection = new SqliteConnection($"Data Source={_dbPath}");
@ -588,12 +771,12 @@ VALUES
var cmd = connection.CreateCommand(); var cmd = connection.CreateCommand();
cmd.CommandText = @" cmd.CommandText = @"
UPDATE Zeiteintraege UPDATE Zeiteintraege
SET Erledigt = $Erledigt, SET Erledigt = $Erledigt,
MitarbeiterKommentar = $Kommentar, MitarbeiterKommentar = $Kommentar,
LetzteBearbeitung = $Bearbeitet LetzteBearbeitung = $Bearbeitet
WHERE Id = $Id; WHERE Id = $Id;
"; ";
cmd.Parameters.AddWithValue("$Erledigt", erledigt ? 1 : 0); cmd.Parameters.AddWithValue("$Erledigt", erledigt ? 1 : 0);
cmd.Parameters.AddWithValue("$Kommentar", mitarbeiterKommentar ?? ""); cmd.Parameters.AddWithValue("$Kommentar", mitarbeiterKommentar ?? "");
@ -603,7 +786,5 @@ VALUES
int rowsAffected = await cmd.ExecuteNonQueryAsync(); int rowsAffected = await cmd.ExecuteNonQueryAsync();
Console.WriteLine($"✏ Zeiteintrag (Id={id}) aktualisiert: erledigt={erledigt}, LetzteBearbeitung gesetzt."); Console.WriteLine($"✏ Zeiteintrag (Id={id}) aktualisiert: erledigt={erledigt}, LetzteBearbeitung gesetzt.");
} }
} }
} }

View File

@ -8,12 +8,29 @@ using ChronoFlow.Persistence;
namespace ChronoFlow.View.Admin; namespace ChronoFlow.View.Admin;
/// <summary>
/// Zeigt eine Liste aller abgeschlossenen Projekte an.
/// Ermöglicht Suche und Navigation zurück zum Admin-Dashboard.
/// </summary>
public partial class AbgeschlosseneProjekteView : UserControl public partial class AbgeschlosseneProjekteView : UserControl
{ {
private readonly ViewManager _viewManager; private readonly ViewManager? _viewManager; // Nur gesetzt, wenn Konstruktor mit Parameter verwendet wird
private readonly ObservableCollection<Zeiteintrag> _abgeschlosseneProjekte = new(); private readonly ObservableCollection<Zeiteintrag> _abgeschlosseneProjekte = new();
private readonly SqliteZeiterfassungsService _dbService = new(); private readonly SqliteZeiterfassungsService _dbService = new();
/// <summary>
/// Öffentlicher, parameterloser Konstruktor für Avalonia (zur Laufzeit erforderlich).
/// Wird z.B. vom XAML-Loader genutzt, daher AVLN0005-Warnung ohne ihn.
/// </summary>
public AbgeschlosseneProjekteView()
{
InitializeComponent();
LadeAbgeschlosseneProjekte();
}
/// <summary>
/// Konstruktor mit ViewManager zur Navigation wird typischerweise aus Code aufgerufen.
/// </summary>
public AbgeschlosseneProjekteView(ViewManager viewManager) public AbgeschlosseneProjekteView(ViewManager viewManager)
{ {
InitializeComponent(); InitializeComponent();
@ -21,6 +38,10 @@ public partial class AbgeschlosseneProjekteView : UserControl
LadeAbgeschlosseneProjekte(); LadeAbgeschlosseneProjekte();
} }
/// <summary>
/// Lädt alle abgeschlossenen Projekte aus der Datenbank
/// und aktualisiert die Liste in der Benutzeroberfläche.
/// </summary>
private void LadeAbgeschlosseneProjekte() private void LadeAbgeschlosseneProjekte()
{ {
_abgeschlosseneProjekte.Clear(); _abgeschlosseneProjekte.Clear();
@ -33,10 +54,10 @@ public partial class AbgeschlosseneProjekteView : UserControl
AbgeschlosseneListe.ItemsSource = _abgeschlosseneProjekte; AbgeschlosseneListe.ItemsSource = _abgeschlosseneProjekte;
} }
private void ZurueckButton_Click(object? sender, RoutedEventArgs e) /// <summary>
{ /// Wird aufgerufen, wenn eine Taste im Suchfeld gedrückt wird.
_viewManager.Show("AdminMain"); /// Filtert die Liste der Projekte anhand von Projektname oder Mitarbeitername.
} /// </summary>
private void Suchfeld_KeyUp(object? sender, KeyEventArgs e) private void Suchfeld_KeyUp(object? sender, KeyEventArgs e)
{ {
var text = Suchfeld?.Text?.ToLower() ?? ""; var text = Suchfeld?.Text?.ToLower() ?? "";
@ -46,4 +67,11 @@ public partial class AbgeschlosseneProjekteView : UserControl
.ToList(); .ToList();
} }
} /// <summary>
/// Navigiert zurück zur Admin-Hauptansicht (z.B. Dashboard).
/// </summary>
private void ZurueckButton_Click(object? sender, RoutedEventArgs e)
{
_viewManager?.Show("AdminMain");
}
}

View File

@ -7,16 +7,26 @@ using ChronoFlow.Persistence;
namespace ChronoFlow.View.Admin namespace ChronoFlow.View.Admin
{ {
/// <summary>
/// Die Hauptansicht für Administratoren nach dem Login.
/// Zeigt die zuletzt bearbeiteten Projekte und Navigationsoptionen.
/// </summary>
public partial class AdminMainView : UserControl public partial class AdminMainView : UserControl
{ {
private readonly ViewManager _viewManager; private readonly ViewManager _viewManager;
private readonly ObservableCollection<Zeiteintrag> _letzteProjekte = new(); private readonly ObservableCollection<Zeiteintrag> _letzteProjekte = new();
/// <summary>
/// Parameterloser Konstruktor (wird nur intern verwendet).
/// </summary>
public AdminMainView() : this(new ViewManager(new ContentControl())) public AdminMainView() : this(new ViewManager(new ContentControl()))
{ {
Console.WriteLine("⚠ Achtung: Parameterloser Konstruktor genutzt (nur Standard-ViewManager)."); Console.WriteLine("⚠ Achtung: Parameterloser Konstruktor genutzt (nur Standard-ViewManager).");
} }
/// <summary>
/// Konstruktor mit Übergabe des ViewManagers für Navigation.
/// </summary>
public AdminMainView(ViewManager viewManager) public AdminMainView(ViewManager viewManager)
{ {
InitializeComponent(); InitializeComponent();
@ -26,6 +36,9 @@ namespace ChronoFlow.View.Admin
LadeLetzteProjekte(); LadeLetzteProjekte();
} }
/// <summary>
/// Holt die letzten 3 Projekte aus der Datenbank und zeigt sie an.
/// </summary>
private void LadeLetzteProjekte() private void LadeLetzteProjekte()
{ {
Console.WriteLine("🔄 Lade letzte Projekte..."); Console.WriteLine("🔄 Lade letzte Projekte...");
@ -58,6 +71,8 @@ namespace ChronoFlow.View.Admin
} }
} }
// 🔽 Navigation per Buttonklick
private void MitarbeiterHinzufuegen_Click(object sender, RoutedEventArgs e) private void MitarbeiterHinzufuegen_Click(object sender, RoutedEventArgs e)
{ {
_viewManager.Show("MitarbeiterHinzufuegen"); _viewManager.Show("MitarbeiterHinzufuegen");
@ -67,6 +82,7 @@ namespace ChronoFlow.View.Admin
{ {
_viewManager.Show("ProjektErstellen"); _viewManager.Show("ProjektErstellen");
} }
private void AlleProjekte_Click(object sender, RoutedEventArgs e) private void AlleProjekte_Click(object sender, RoutedEventArgs e)
{ {
_viewManager.Show("AlleProjekte"); _viewManager.Show("AlleProjekte");
@ -77,20 +93,17 @@ namespace ChronoFlow.View.Admin
_viewManager.Show("MitarbeiterListe"); _viewManager.Show("MitarbeiterListe");
} }
public void AktualisiereLetzteProjekte()
{
LadeLetzteProjekte();
}
private void AbgeschlosseneProjekte_Click(object? sender, RoutedEventArgs e) private void AbgeschlosseneProjekte_Click(object? sender, RoutedEventArgs e)
{ {
_viewManager.Show("AbgeschlosseneProjekte"); _viewManager.Show("AbgeschlosseneProjekte");
} }
/// <summary>
/// Wird z.B. vom ProjektErstellenView aufgerufen, um die Liste neu zu laden.
/// </summary>
public void AktualisiereLetzteProjekte()
{
LadeLetzteProjekte();
}
} }
} }

View File

@ -4,15 +4,15 @@
x:Class="ChronoFlow.View.Admin.AlleProjekteView"> x:Class="ChronoFlow.View.Admin.AlleProjekteView">
<Grid RowDefinitions="Auto,Auto,*,Auto" Margin="20"> <Grid RowDefinitions="Auto,Auto,*,Auto" Margin="20">
<!-- Überschrift --> <!-- 🔹 Überschrift -->
<TextBlock Grid.Row="0" Text="Alle Projekte" FontSize="20" FontWeight="Bold" <TextBlock Grid.Row="0" Text="Alle Projekte" FontSize="20" FontWeight="Bold"
HorizontalAlignment="Center" Margin="0,0,0,10"/> HorizontalAlignment="Center" Margin="0,0,0,10"/>
<!-- Suchfeld --> <!-- 🔍 Suchfeld zur Projekt- oder Mitarbeitersuche -->
<TextBox Grid.Row="1" x:Name="Suchfeld" Watermark="🔍 Nach Projekt oder Mitarbeiter suchen..." <TextBox Grid.Row="1" x:Name="Suchfeld" Watermark="🔍 Nach Projekt oder Mitarbeiter suchen..."
KeyUp="Suchfeld_KeyUp" Margin="0,0,0,10"/> KeyUp="Suchfeld_KeyUp" Margin="0,0,0,10"/>
<!-- Projektliste --> <!-- 📋 Projektliste -->
<ScrollViewer Grid.Row="2"> <ScrollViewer Grid.Row="2">
<ListBox x:Name="ProjekteListe"> <ListBox x:Name="ProjekteListe">
<ListBox.ItemTemplate> <ListBox.ItemTemplate>
@ -20,7 +20,7 @@
<Border BorderBrush="Gray" BorderThickness="1" CornerRadius="5" Padding="10" Margin="0,5"> <Border BorderBrush="Gray" BorderThickness="1" CornerRadius="5" Padding="10" Margin="0,5">
<StackPanel Spacing="5"> <StackPanel Spacing="5">
<!-- Projektübersicht --> <!-- 🔧 Projektinformationen -->
<StackPanel Orientation="Horizontal" Spacing="10"> <StackPanel Orientation="Horizontal" Spacing="10">
<TextBlock Text="{Binding Projekt}" Width="150"/> <TextBlock Text="{Binding Projekt}" Width="150"/>
<TextBlock Text="{Binding Mitarbeiter}" Width="150"/> <TextBlock Text="{Binding Mitarbeiter}" Width="150"/>
@ -28,11 +28,15 @@
<TextBlock Text="{Binding Endzeit}" Width="150"/> <TextBlock Text="{Binding Endzeit}" Width="150"/>
</StackPanel> </StackPanel>
<!-- Kommentar vom Admin --> <!-- 🧑‍💼 Projektleiter -->
<TextBlock Text="👨‍💼 Projektleiter:" FontSize="12" Foreground="LightGray"/>
<TextBlock Text="{Binding Projektleiter}" FontStyle="Italic" FontWeight="SemiBold"/>
<!-- 📝 Kommentar vom Admin -->
<TextBlock Text="📝 Kommentar (Admin):" FontSize="12" Foreground="LightGray"/> <TextBlock Text="📝 Kommentar (Admin):" FontSize="12" Foreground="LightGray"/>
<TextBlock Text="{Binding Kommentar}" FontWeight="SemiBold"/> <TextBlock Text="{Binding Kommentar}" FontWeight="SemiBold"/>
<!-- Kommentar vom Mitarbeiter --> <!-- 💬 Kommentar vom Mitarbeiter -->
<TextBlock Text="💬 Kommentar (Mitarbeiter):" FontSize="12" Foreground="LightGray"/> <TextBlock Text="💬 Kommentar (Mitarbeiter):" FontSize="12" Foreground="LightGray"/>
<TextBlock Text="{Binding MitarbeiterKommentar}" <TextBlock Text="{Binding MitarbeiterKommentar}"
FontStyle="Italic" FontStyle="Italic"
@ -41,7 +45,7 @@
TextWrapping="Wrap" TextWrapping="Wrap"
Margin="0,2,0,0"/> Margin="0,2,0,0"/>
<!-- Aktionsbuttons --> <!-- 🔘 Aktionsbuttons -->
<StackPanel Orientation="Horizontal" Spacing="8"> <StackPanel Orientation="Horizontal" Spacing="8">
<Button Content="🖋 Bearbeiten" Tag="{Binding}" Click="Bearbeiten_Click"/> <Button Content="🖋 Bearbeiten" Tag="{Binding}" Click="Bearbeiten_Click"/>
<Button Content="🗑 Löschen" Tag="{Binding}" Click="Loeschen_Click"/> <Button Content="🗑 Löschen" Tag="{Binding}" Click="Loeschen_Click"/>
@ -55,7 +59,7 @@
</ListBox> </ListBox>
</ScrollViewer> </ScrollViewer>
<!-- Zurück-Button --> <!-- 🔙 Zurück-Button -->
<Button Grid.Row="3" Content="⬅ Zurück zum Dashboard" <Button Grid.Row="3" Content="⬅ Zurück zum Dashboard"
Click="ZurueckButton_Click" HorizontalAlignment="Center" Margin="0,10,0,0"/> Click="ZurueckButton_Click" HorizontalAlignment="Center" Margin="0,10,0,0"/>
</Grid> </Grid>

View File

@ -3,18 +3,36 @@ using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Input;
using ChronoFlow.Model; using ChronoFlow.Model;
using ChronoFlow.Persistence; using ChronoFlow.Persistence;
using Avalonia.Input;
namespace ChronoFlow.View.Admin; namespace ChronoFlow.View.Admin;
/// <summary>
/// Diese View zeigt alle noch nicht abgeschlossenen Projekte.
/// Admins können Projekte bearbeiten, löschen oder abschließen.
/// </summary>
public partial class AlleProjekteView : UserControl public partial class AlleProjekteView : UserControl
{ {
private readonly ViewManager _viewManager; private readonly ViewManager? _viewManager;
private readonly ObservableCollection<Zeiteintrag> _alleProjekte = new(); private readonly ObservableCollection<Zeiteintrag> _alleProjekte = new();
private readonly SqliteZeiterfassungsService _dbService = new(); private readonly SqliteZeiterfassungsService _dbService = new();
/// <summary>
/// Öffentlicher, parameterloser Konstruktor notwendig für Avalonia XAML-Runtime (AVLN0005).
/// Wird nur verwendet, wenn kein ViewManager übergeben wird.
/// </summary>
public AlleProjekteView()
{
InitializeComponent();
Console.WriteLine("⚠ AlleProjekteView mit Standard-Konstruktor initialisiert.");
LadeAlleProjekte();
}
/// <summary>
/// Konstruktor mit ViewManager zur Navigation (z.B. zurück zum Admin-Dashboard).
/// </summary>
public AlleProjekteView(ViewManager viewManager) public AlleProjekteView(ViewManager viewManager)
{ {
InitializeComponent(); InitializeComponent();
@ -22,54 +40,96 @@ public partial class AlleProjekteView : UserControl
LadeAlleProjekte(); LadeAlleProjekte();
} }
/// <summary>
/// Lädt alle nicht erledigten Projekte aus der Datenbank und zeigt sie in der Liste an.
/// </summary>
private void LadeAlleProjekte() private void LadeAlleProjekte()
{ {
_alleProjekte.Clear();
try
{
_alleProjekte.Clear();
var alle = _dbService.LadeAlleZeiteintraege();
Console.WriteLine($"🧪 Aus DB geladen: {alle.Count} Einträge");
var ausDb = _dbService.LadeAlleZeiteintraege() foreach (var e in alle)
.Where(p => !p.Erledigt) // Nur nicht erledigte Projekte! Console.WriteLine($"➡️ {e.Projekt} | {e.Mitarbeiter} | {e.Projektleiter}");
.ToList();
foreach (var eintrag in ausDb) var ausDb = alle
_alleProjekte.Add(eintrag); //.Where(p => !p.Erledigt)
.OrderBy(p => p.Endzeit)
.ToList();
foreach (var eintrag in ausDb)
_alleProjekte.Add(eintrag);
ProjekteListe.ItemsSource = _alleProjekte; ProjekteListe.ItemsSource = _alleProjekte;
Console.WriteLine($"🔄 {ausDb.Count} offene Projekte geladen.");
}
catch (Exception ex)
{
Console.WriteLine($"❌ Fehler beim Laden der Projekte: {ex.Message}");
}
} }
/// <summary>
/// Filtert die Projektliste nach Projekt- oder Mitarbeitername bei Tasteneingabe im Suchfeld.
/// </summary>
private void Suchfeld_KeyUp(object? sender, KeyEventArgs e) private void Suchfeld_KeyUp(object? sender, KeyEventArgs e)
{ {
var text = Suchfeld?.Text?.ToLower() ?? ""; var text = Suchfeld?.Text?.ToLower() ?? "";
ProjekteListe.ItemsSource = _alleProjekte ProjekteListe.ItemsSource = _alleProjekte
.Where(p => (p.Projekt?.ToLower().Contains(text) ?? false) || .Where(p => (p.Projekt?.ToLower().Contains(text) ?? false) ||
(p.Mitarbeiter?.ToLower().Contains(text) ?? false)) (p.Mitarbeiter?.ToLower().Contains(text) ?? false))
.ToList(); .ToList();
} }
/// <summary>
/// Öffnet einen Dialog zum Bearbeiten des ausgewählten Projekts.
/// Änderungen werden gespeichert und die Liste neu geladen.
/// </summary>
private async void Bearbeiten_Click(object? sender, RoutedEventArgs e) private async void Bearbeiten_Click(object? sender, RoutedEventArgs e)
{ {
if (sender is Button button && button.Tag is Zeiteintrag projekt) if (sender is Button button && button.Tag is Zeiteintrag projekt)
{ {
var dialog = new ProjektBearbeitenDialog(projekt); var dialog = new ProjektBearbeitenDialog(projekt);
var updatedProjekt = await dialog.ShowDialog<Zeiteintrag>((Window)this.VisualRoot!);
if (updatedProjekt != null) if (this.VisualRoot is Window parentWindow)
{ {
_dbService.UpdateProjekt(updatedProjekt); var updatedProjekt = await dialog.ShowDialog<Zeiteintrag>(parentWindow);
LadeAlleProjekte();
if (updatedProjekt != null)
{
_dbService.UpdateProjekt(updatedProjekt);
LadeAlleProjekte();
Console.WriteLine($"✏️ Projekt aktualisiert: {updatedProjekt.Projekt}");
}
}
else
{
Console.WriteLine("❌ Bearbeiten_Click: VisualRoot ist kein Window.");
} }
} }
} }
/// <summary>
/// Löscht das ausgewählte Projekt dauerhaft aus der Datenbank.
/// </summary>
private void Loeschen_Click(object? sender, RoutedEventArgs e) private void Loeschen_Click(object? sender, RoutedEventArgs e)
{ {
if (sender is Button button && button.Tag is Zeiteintrag projekt) if (sender is Button button && button.Tag is Zeiteintrag projekt)
{ {
_dbService.LoescheProjekt(projekt.Id); _dbService.LoescheProjekt(projekt.Id);
LadeAlleProjekte(); LadeAlleProjekte();
Console.WriteLine($"🗑️ Projekt gelöscht: {projekt.Projekt}");
} }
} }
/// <summary>
/// Markiert ein Projekt als erledigt (abgeschlossen) und aktualisiert die Anzeige.
/// </summary>
private void Abschliessen_Click(object? sender, RoutedEventArgs e) private void Abschliessen_Click(object? sender, RoutedEventArgs e)
{ {
if (sender is Button button && button.Tag is Zeiteintrag projekt) if (sender is Button button && button.Tag is Zeiteintrag projekt)
@ -82,8 +142,11 @@ public partial class AlleProjekteView : UserControl
} }
} }
/// <summary>
/// Navigiert zurück zur Admin-Startansicht.
/// </summary>
private void ZurueckButton_Click(object? sender, RoutedEventArgs e) private void ZurueckButton_Click(object? sender, RoutedEventArgs e)
{ {
_viewManager.Show("AdminMain"); _viewManager?.Show("AdminMain");
} }
} }

View File

@ -3,22 +3,50 @@ using Avalonia.Interactivity;
namespace ChronoFlow.View.Admin; namespace ChronoFlow.View.Admin;
/// <summary>
/// Ein einfacher Bestätigungsdialog mit "Ja" und "Nein"-Buttons.
/// Gibt bei Abschluss ein bool-Wert zurück: true = bestätigt, false = abgelehnt.
/// </summary>
public partial class ConfirmDialog : Window public partial class ConfirmDialog : Window
{ {
/// <summary>
/// Ergebnis der Auswahl: true = Ja, false = Nein (Standard).
/// </summary>
public bool Result { get; private set; } = false; public bool Result { get; private set; } = false;
/// <summary>
/// Öffentlicher, parameterloser Konstruktor.
/// Wichtig für Avalonia zur Laufzeit, verhindert AVLN0005-Warnungen.
/// </summary>
public ConfirmDialog()
{
InitializeComponent();
}
/// <summary>
/// Konstruktor mit einer Frage, die im Dialog angezeigt wird.
/// </summary>
/// <param name="frage">Der anzuzeigende Fragetext im Dialog</param>
public ConfirmDialog(string frage) public ConfirmDialog(string frage)
{ {
InitializeComponent(); InitializeComponent();
FrageText.Text = frage; FrageText.Text = frage;
} }
/// <summary>
/// Wird aufgerufen, wenn der Benutzer "Ja" klickt.
/// Schließt das Fenster mit Result = true.
/// </summary>
private void JaButton_Click(object? sender, RoutedEventArgs e) private void JaButton_Click(object? sender, RoutedEventArgs e)
{ {
Result = true; Result = true;
Close(Result); Close(Result);
} }
/// <summary>
/// Wird aufgerufen, wenn der Benutzer "Nein" klickt.
/// Schließt das Fenster mit Result = false.
/// </summary>
private void NeinButton_Click(object? sender, RoutedEventArgs e) private void NeinButton_Click(object? sender, RoutedEventArgs e)
{ {
Result = false; Result = false;

View File

@ -1,36 +1,47 @@
<!--
Fenster zur Bearbeitung von Mitarbeiterdaten (Username, Abteilung, Mitarbeitennummer).
Zusätzlich wird ein Hinweis eingeblendet, falls das Passwort auf einen Standardwert zurückgesetzt wurde.
-->
<Window xmlns="https://github.com/avaloniaui" <Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ChronoFlow.View.Admin.MitarbeiterBearbeitenDialog" x:Class="ChronoFlow.View.Admin.MitarbeiterBearbeitenDialog"
Width="450" Height="600" Width="450" Height="600"
Title="Mitarbeiter bearbeiten"> Title="Mitarbeiter bearbeiten">
<!-- Ermöglicht vertikales Scrollen bei kleineren Fenstern -->
<ScrollViewer> <ScrollViewer>
<!-- Hauptcontainer mit Abstand und vertikaler Anordnung -->
<StackPanel Margin="20" Spacing="15"> <StackPanel Margin="20" Spacing="15">
<!-- Eingabe: Username --> <!-- Eingabe für den Benutzernamen -->
<TextBlock Text="Username:" /> <TextBlock Text="Username:" />
<TextBox x:Name="UsernameBox" /> <TextBox x:Name="UsernameBox" />
<!-- Eingabe: Abteilung --> <!-- Eingabe für die Abteilung -->
<TextBlock Text="Abteilung:" /> <TextBlock Text="Abteilung:" />
<TextBox x:Name="AbteilungBox" /> <TextBox x:Name="AbteilungBox" />
<!-- Eingabe: Mitarbeiternummer --> <!-- Eingabe für die Mitarbeitennummer -->
<TextBlock Text="Mitarbeiternummer:" /> <TextBlock Text="Mitarbeiternummer:" />
<TextBox x:Name="MitarbeiternummerBox" /> <TextBox x:Name="MitarbeiternummerBox" />
<!-- Checkbox für Passwort-Reset --> <!-- Schaltflächen zum Speichern oder Abbrechen -->
<TextBlock Text="Passwort zurücksetzen:" /> <StackPanel Orientation="Horizontal"
<CheckBox x:Name="ResetPasswordCheckbox" HorizontalAlignment="Center"
Content="Passwort auf 'newpassword' zurücksetzen und beim nächsten Login ändern lassen" /> Spacing="10"
Margin="0,10,0,0">
<!-- Buttons: Speichern & Abbrechen --> <!-- Speichern-Button -->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="10" Margin="0,10,0,0"> <Button Content="✅ Speichern"
<Button Content="✅ Speichern" Width="120" Click="SpeichernButton_Click" /> Width="120"
<Button Content="❌ Abbrechen" Width="120" Click="AbbrechenButton_Click" /> Click="SpeichernButton_Click" />
<!-- Abbrechen-Button -->
<Button Content="❌ Abbrechen"
Width="120"
Click="AbbrechenButton_Click" />
</StackPanel> </StackPanel>
<!-- Hinweistext bei Erfolg --> <!-- Text zur Rückmeldung bei erfolgreichem Speichern -->
<TextBlock x:Name="FeedbackText" <TextBlock x:Name="FeedbackText"
Text="Änderungen erfolgreich übernommen." Text="Änderungen erfolgreich übernommen."
Foreground="Green" Foreground="Green"
@ -38,6 +49,17 @@
HorizontalAlignment="Center" HorizontalAlignment="Center"
IsVisible="False" IsVisible="False"
Margin="0,10,0,0" /> Margin="0,10,0,0" />
<!-- Hinweis für Admins beim Zurücksetzen des Passworts -->
<TextBlock x:Name="ResetHinweisText"
Text="Das Passwort wurde auf 'changeme' zurückgesetzt bitte an den Mitarbeiter weitergeben."
Foreground="DarkRed"
FontWeight="Bold"
HorizontalAlignment="Center"
TextWrapping="Wrap"
IsVisible="False"
Margin="0,10,0,0" />
</StackPanel> </StackPanel>
</ScrollViewer> </ScrollViewer>
</Window> </Window>

View File

@ -1,68 +1,90 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using ChronoFlow.Model; using ChronoFlow.Model;
using ChronoFlow.Security;
namespace ChronoFlow.View.Admin; namespace ChronoFlow.View.Admin;
/// <summary> /// <summary>
/// Dialogfenster zur Bearbeitung eines Mitarbeiters durch den Admin. /// Dialogfenster zur Bearbeitung von Mitarbeiterdaten.
/// Enthält Eingabefelder für Name, Abteilung, Nummer und optional Passwort-Reset. /// Der übergebene Benutzer kann geändert und anschließend zurückgegeben werden.
/// </summary> /// </summary>
public partial class MitarbeiterBearbeitenDialog : Window public partial class MitarbeiterBearbeitenDialog : Window
{ {
/// <summary> /// <summary>
/// Benutzerobjekt mit den aktualisierten Werten, wird beim Schließen zurückgegeben. /// Rückgabe des aktualisierten Benutzers nach Speichern.
/// </summary> /// </summary>
public User UpdatedUser { get; private set; } public User UpdatedUser { get; private set; }
/// <summary> /// <summary>
/// Konstruktor befüllt die UI mit den bestehenden Benutzerwerten. /// Gibt an, ob das Passwort dieses Benutzers zurückgesetzt wurde.
/// Wird extern aufgerufen und steuert die Anzeige des Hinweistexts.
/// </summary> /// </summary>
/// <param name="user">Der zu bearbeitende Benutzer</param> public bool PasswortWurdeZurueckgesetzt
{
set
{
if (value && this.FindControl<TextBlock>("ResetHinweisText") is { } resetText)
{
resetText.IsVisible = true;
}
}
}
/// <summary>
/// Öffentlicher, parameterloser Konstruktor.
/// Wichtig für Avalonia zur Laufzeit, z.B. XAML-Loader (verhindert AVLN0005-Warnung).
/// </summary>
public MitarbeiterBearbeitenDialog()
{
InitializeComponent();
UpdatedUser = new User(); // Dummy-Initialisierung für Designer oder Avalonia-Runtime
}
/// <summary>
/// Konstruktor mit Benutzerobjekt lädt dessen Daten zur Bearbeitung.
/// </summary>
/// <param name="user">Benutzer, der bearbeitet werden soll</param>
public MitarbeiterBearbeitenDialog(User user) public MitarbeiterBearbeitenDialog(User user)
{ {
InitializeComponent(); InitializeComponent();
// Erzeuge eine Kopie mit übertragbaren Werten // Erstellen einer Kopie des Benutzers zur späteren Rückgabe
UpdatedUser = new User UpdatedUser = new User
{ {
Username = user.Username, Username = user.Username,
OriginalUsername = user.Username, // für Identifikation bei Änderungen OriginalUsername = user.Username, // Originalname speichern für Update-Vergleich
Abteilung = user.Abteilung, Abteilung = user.Abteilung,
Mitarbeiternummer = user.Mitarbeiternummer Mitarbeiternummer = user.Mitarbeiternummer
}; };
// Setze UI-Felder // Felder in der Oberfläche vorausfüllen
UsernameBox.Text = user.Username; UsernameBox.Text = user.Username;
AbteilungBox.Text = user.Abteilung; AbteilungBox.Text = user.Abteilung;
MitarbeiternummerBox.Text = user.Mitarbeiternummer; MitarbeiternummerBox.Text = user.Mitarbeiternummer;
// Hinweistext zum Passwortreset initial ausblenden (nur zur Sicherheit)
if (this.FindControl<TextBlock>("ResetHinweisText") is { } resetText)
{
resetText.IsVisible = false;
}
} }
/// <summary> /// <summary>
/// Wird ausgeführt, wenn der Admin auf „Speichern“ klickt. /// Wird ausgeführt, wenn auf „Speichern“ geklickt wird.
/// Änderungen werden übernommen und ggf. Passwort zurückgesetzt. /// Aktualisiert die Daten des Benutzers und schließt das Fenster mit Rückgabe.
/// </summary> /// </summary>
private void SpeichernButton_Click(object? sender, RoutedEventArgs e) private void SpeichernButton_Click(object? sender, RoutedEventArgs e)
{ {
// Aktualisiere Werte aus der Eingabe
UpdatedUser.Username = UsernameBox.Text ?? UpdatedUser.Username; UpdatedUser.Username = UsernameBox.Text ?? UpdatedUser.Username;
UpdatedUser.Abteilung = AbteilungBox.Text ?? UpdatedUser.Abteilung; UpdatedUser.Abteilung = AbteilungBox.Text ?? UpdatedUser.Abteilung;
UpdatedUser.Mitarbeiternummer = MitarbeiternummerBox.Text ?? UpdatedUser.Mitarbeiternummer; UpdatedUser.Mitarbeiternummer = MitarbeiternummerBox.Text ?? UpdatedUser.Mitarbeiternummer;
// Falls Passwort zurückgesetzt werden soll
if (ResetPasswordCheckbox.IsChecked == true)
{
UpdatedUser.Password = PasswordHasher.HashPassword("newpassword");
UpdatedUser.MussPasswortAendern = true; // Benutzer muss neues Passwort setzen
}
// Schließe Fenster und übergebe aktualisierten Benutzer
this.Close(UpdatedUser); this.Close(UpdatedUser);
} }
/// <summary> /// <summary>
/// Wird ausgeführt, wenn der Admin den Dialog ohne Änderungen verlässt. /// Wird ausgeführt, wenn auf „Abbrechen“ geklickt wird.
/// Fenster wird geschlossen, ohne Änderungen zurückzugeben.
/// </summary> /// </summary>
private void AbbrechenButton_Click(object? sender, RoutedEventArgs e) private void AbbrechenButton_Click(object? sender, RoutedEventArgs e)
{ {

View File

@ -1,24 +1,35 @@
using System; using System;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using ChronoFlow.Model; using ChronoFlow.Model;
using ChronoFlow.Persistence; using ChronoFlow.Persistence;
using MessageBox.Avalonia; using ChronoFlow.Security;
using MessageBox.Avalonia.DTO;
using MessageBox.Avalonia.Enums;
namespace ChronoFlow.View.Admin; namespace ChronoFlow.View.Admin;
/// <summary>
/// Zeigt eine Liste aller Benutzer mit der Rolle "Mitarbeiter" zur Bearbeitung oder Löschung.
/// </summary>
public partial class MitarbeiterListeView : UserControl public partial class MitarbeiterListeView : UserControl
{ {
private readonly ViewManager _viewManager; private readonly ViewManager _viewManager;
private readonly ObservableCollection<User> _alleMitarbeiter = new(); private readonly ObservableCollection<User> _alleMitarbeiter = new();
private readonly SqliteZeiterfassungsService _dbService = new(); private readonly SqliteZeiterfassungsService _dbService = new();
/// <summary>
/// Öffentlicher parameterloser Konstruktor für Avalonia (zur Vermeidung von AVLN0005-Warnungen).
/// </summary>
public MitarbeiterListeView() : this(new ViewManager(new ContentControl()))
{
Console.WriteLine("⚠ Parameterloser Konstruktor genutzt (Dummy-ViewManager).");
}
/// <summary>
/// Konstruktor mit ViewManager (für Navigation).
/// </summary>
public MitarbeiterListeView(ViewManager viewManager) public MitarbeiterListeView(ViewManager viewManager)
{ {
InitializeComponent(); InitializeComponent();
@ -26,6 +37,14 @@ public partial class MitarbeiterListeView : UserControl
LadeMitarbeiter(); LadeMitarbeiter();
} }
/// <summary>
/// Hilfsmethode zum Abrufen des übergeordneten Fensters.
/// </summary>
private Window? GetParentWindow() => this.VisualRoot as Window;
/// <summary>
/// Lädt alle Benutzer mit Rolle "Mitarbeiter" und zeigt sie in der Liste an.
/// </summary>
private void LadeMitarbeiter() private void LadeMitarbeiter()
{ {
try try
@ -47,52 +66,107 @@ public partial class MitarbeiterListeView : UserControl
} }
} }
/// <summary>
/// Filtert die Mitarbeiterliste beim Tippen im Suchfeld.
/// </summary>
private void Suchfeld_KeyUp(object? sender, KeyEventArgs e) private void Suchfeld_KeyUp(object? sender, KeyEventArgs e)
{ {
var text = Suchfeld?.Text?.ToLower() ?? ""; var text = Suchfeld?.Text?.ToLower() ?? "";
MitarbeiterListe.ItemsSource = _alleMitarbeiter MitarbeiterListe.ItemsSource = _alleMitarbeiter
.Where(m => m.Username.ToLower().Contains(text)) .Where(m => m.Username.ToLower().Contains(text))
.ToList(); .ToList();
} }
/// <summary>
/// Öffnet Dialog zur Bearbeitung eines Mitarbeiters.
/// </summary>
private async void Bearbeiten_Click(object? sender, RoutedEventArgs e) private async void Bearbeiten_Click(object? sender, RoutedEventArgs e)
{ {
if (sender is Button button && button.Tag is User benutzer) if (sender is Button button && button.Tag is User benutzer)
{ {
var dialog = new MitarbeiterBearbeitenDialog(benutzer); var dialog = new MitarbeiterBearbeitenDialog(benutzer);
var updatedUser = await dialog.ShowDialog<User>((Window)this.VisualRoot!); var parentWindow = GetParentWindow();
if (updatedUser != null) if (parentWindow != null)
{ {
updatedUser.Password = benutzer.Password; var updatedUser = await dialog.ShowDialog<User>(parentWindow);
updatedUser.Role = benutzer.Role;
updatedUser.OriginalUsername = benutzer.Username; // ← WICHTIG!
_dbService.UpdateBenutzer(updatedUser); if (updatedUser != null)
LadeMitarbeiter(); {
updatedUser.Password = benutzer.Password;
updatedUser.Role = benutzer.Role;
updatedUser.OriginalUsername = benutzer.Username;
_dbService.UpdateBenutzer(updatedUser);
LadeMitarbeiter();
}
}
else
{
Console.WriteLine("❌ Bearbeiten_Click: Konnte übergeordnetes Fenster nicht ermitteln.");
} }
} }
} }
/// <summary>
/// Löscht einen Benutzer nach Bestätigung.
/// </summary>
private async void Loeschen_Click(object? sender, RoutedEventArgs e) private async void Loeschen_Click(object? sender, RoutedEventArgs e)
{ {
if (sender is Button button && button.Tag is User benutzer) if (sender is Button button && button.Tag is User benutzer)
{ {
var dialog = new ConfirmDialog($"Möchten Sie den Benutzer '{benutzer.Username}' wirklich löschen?"); var dialog = new ConfirmDialog($"Möchten Sie den Benutzer '{benutzer.Username}' wirklich löschen?");
var result = await dialog.ShowDialog<bool>((Window)this.VisualRoot!); var parentWindow = GetParentWindow();
if (result) if (parentWindow != null)
{ {
_dbService.LoescheBenutzer(benutzer.Username); var result = await dialog.ShowDialog<bool>(parentWindow);
LadeMitarbeiter(); if (result)
{
_dbService.LoescheBenutzer(benutzer.Username);
LadeMitarbeiter();
}
}
else
{
Console.WriteLine("❌ Loeschen_Click: VisualRoot ist kein Window.");
} }
} }
} }
/// <summary>
/// Setzt das Passwort eines Benutzers zurück auf "newpassword" (gehasht).
/// </summary>
private async void PasswortReset_Click(object? sender, RoutedEventArgs e)
{
if (sender is Button button && button.Tag is User benutzer)
{
var parentWindow = GetParentWindow();
if (parentWindow != null)
{
var dialog = new ConfirmDialog($"Möchten Sie das Passwort für '{benutzer.Username}' wirklich zurücksetzen?");
var result = await dialog.ShowDialog<bool>(parentWindow);
if (result)
{
benutzer.Password = PasswordHasher.HashPassword("newpassword");
benutzer.MussPasswortAendern = true;
_dbService.UpdateBenutzer(benutzer);
Console.WriteLine($"✅ Passwort für {benutzer.Username} wurde zurückgesetzt.");
}
}
else
{
Console.WriteLine("❌ PasswortReset_Click: VisualRoot ist kein Window.");
}
}
}
/// <summary>
/// Navigiert zurück zur Admin-Hauptansicht.
/// </summary>
private void ZurueckButton_Click(object? sender, RoutedEventArgs e) private void ZurueckButton_Click(object? sender, RoutedEventArgs e)
{ {
try try
@ -104,40 +178,4 @@ public partial class MitarbeiterListeView : UserControl
Console.WriteLine($"❌ Fehler beim Zurückspringen: {ex.Message}"); Console.WriteLine($"❌ Fehler beim Zurückspringen: {ex.Message}");
} }
} }
private async void ResetPasswort_Click(object? sender, RoutedEventArgs e)
{
if (sender is Button button && button.Tag is User benutzer)
{
var dialog = new ConfirmDialog($"Soll das Passwort für '{benutzer.Username}' wirklich zurückgesetzt werden?");
var result = await dialog.ShowDialog<bool>((Window)this.VisualRoot!);
if (result)
{
_dbService.ResetBenutzerPasswort(benutzer.Username);
Console.WriteLine($"✅ Passwort für {benutzer.Username} zurückgesetzt.");
}
}
}
private async void PasswortReset_Click(object? sender, RoutedEventArgs e)
{
if (sender is Button button && button.Tag is User benutzer)
{
var dialog = new ConfirmDialog($"Möchten Sie das Passwort für '{benutzer.Username}' wirklich zurücksetzen?");
var result = await dialog.ShowDialog<bool>((Window)this.VisualRoot!);
if (result)
{
benutzer.Password = "newpassword"; // 💡 hier später besser: generiertes oder festgelegtes Initialpasswort
benutzer.MussPasswortAendern = true;
_dbService.UpdateBenutzer(benutzer);
Console.WriteLine($"✅ Passwort für {benutzer.Username} wurde zurückgesetzt.");
}
}
}
} }

View File

@ -1,30 +1,48 @@
<Window xmlns="https://github.com/avaloniaui" <Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ChronoFlow.View.Admin.ProjektBearbeitenDialog" x:Class="ChronoFlow.View.Admin.ProjektBearbeitenDialog"
Width="500" Height="600" Width="600" Height="700"
MinWidth="500" MinHeight="650"
Title="Projekt bearbeiten"> Title="Projekt bearbeiten">
<ScrollViewer> <ScrollViewer>
<StackPanel Margin="20" Spacing="15"> <StackPanel Margin="25" Spacing="15">
<TextBlock Text="Projektname:" />
<TextBox x:Name="ProjektnameBox" />
<TextBlock Text="Kommentar:" /> <!-- Eingabefelder -->
<TextBox x:Name="KommentarBox" AcceptsReturn="True" Height="80" /> <TextBlock Text="Projektname:" />
<TextBox x:Name="ProjektnameBox" />
<TextBlock Text="Mitarbeiter auswählen:" />
<ComboBox x:Name="MitarbeiterDropdown" />
<TextBlock Text="Startdatum:" /> <TextBlock Text="Kommentar:" />
<DatePicker x:Name="StartzeitPicker" /> <TextBox x:Name="KommentarBox" AcceptsReturn="True" Height="80" />
<TextBlock Text="Enddatum:" /> <TextBlock Text="Mitarbeiter auswählen:" />
<DatePicker x:Name="EndzeitPicker" /> <ComboBox x:Name="MitarbeiterDropdown" />
<TextBlock Text="Startdatum:" />
<DatePicker x:Name="StartzeitPicker" />
<TextBlock Text="Enddatum:" />
<DatePicker x:Name="EndzeitPicker" />
<!-- Hinweisbereich farblich und visuell getrennt -->
<Border Background="#111111" CornerRadius="5" Padding="10" Margin="0,10,0,0">
<StackPanel Spacing="5">
<StackPanel Orientation="Horizontal" Spacing="5">
<TextBlock Text="🛈" FontWeight="Bold" />
<TextBlock Text="Hinweise zur Deadline-Anzeige:" FontWeight="Bold" />
</StackPanel>
<TextBlock Text="• Grün: Noch mehr als 7 Tage bis zur Deadline" />
<TextBlock Text="• Orange: Weniger als 7 Tage bis zur Deadline" />
<TextBlock Text="• Rot: Weniger als 3 Tage bis zur Deadline oder bereits abgelaufen" />
</StackPanel>
</Border>
<!-- Buttons -->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="20" Margin="0,15,0,0">
<Button Content="✅ Speichern" Width="120" Click="SpeichernButton_Click" />
<Button Content="❌ Abbrechen" Width="130" Click="AbbrechenButton_Click" />
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="20" Margin="0,15,0,0">
<Button Content="✅ Speichern" Width="120" Click="SpeichernButton_Click" />
<Button Content="❌ Abbrechen" Width="130" Click="AbbrechenButton_Click" />
</StackPanel> </StackPanel>
</StackPanel> </ScrollViewer>
</ScrollViewer> </Window>
</Window>

View File

@ -6,28 +6,52 @@ using ChronoFlow.Persistence;
namespace ChronoFlow.View.Admin; namespace ChronoFlow.View.Admin;
/// <summary>
/// Dialogfenster zum Bearbeiten eines Projekts (Zeiteintrag).
/// </summary>
public partial class ProjektBearbeitenDialog : Window public partial class ProjektBearbeitenDialog : Window
{ {
public Zeiteintrag UpdatedProjekt { get; private set; } /// <summary>
/// Der bearbeitete Zeiteintrag, der beim Schließen zurückgegeben wird.
/// </summary>
public Zeiteintrag UpdatedProjekt { get; private set; } = new();
/// <summary>
/// Öffentlicher parameterloser Konstruktor (z.B. für Design-Time oder XAML-Loader).
/// </summary>
public ProjektBearbeitenDialog()
{
InitializeComponent();
Console.WriteLine("⚠ Parameterloser Konstruktor verwendet. Achtung: Kein Projekt übergeben.");
}
/// <summary>
/// Konstruktor initialisiert das Fenster mit bestehenden Projektdaten.
/// </summary>
/// <param name="projekt">Das Projekt (Zeiteintrag), das bearbeitet wird.</param>
public ProjektBearbeitenDialog(Zeiteintrag projekt) public ProjektBearbeitenDialog(Zeiteintrag projekt)
{ {
InitializeComponent(); InitializeComponent();
var dbService = new SqliteZeiterfassungsService(); var dbService = new SqliteZeiterfassungsService();
var mitarbeiter = dbService.LadeAlleMitarbeiterNamen(); var mitarbeiter = dbService.LadeAlleMitarbeiterNamen();
MitarbeiterDropdown.ItemsSource = mitarbeiter; MitarbeiterDropdown.ItemsSource = mitarbeiter;
MitarbeiterDropdown.SelectedItem = projekt.Mitarbeiter; MitarbeiterDropdown.SelectedItem = projekt.Mitarbeiter;
// Vorbelegen // Felder vorbelegen
ProjektnameBox.Text = projekt.Projekt; ProjektnameBox.Text = projekt.Projekt;
KommentarBox.Text = projekt.Kommentar; KommentarBox.Text = projekt.Kommentar;
StartzeitPicker.SelectedDate = new DateTimeOffset(projekt.Startzeit); StartzeitPicker.SelectedDate = new DateTimeOffset(projekt.Startzeit);
EndzeitPicker.SelectedDate = new DateTimeOffset(projekt.Endzeit); EndzeitPicker.SelectedDate = new DateTimeOffset(projekt.Endzeit);
// Referenz übernehmen
UpdatedProjekt = projekt; UpdatedProjekt = projekt;
} }
/// <summary>
/// Übernimmt die Änderungen und schließt das Dialogfenster.
/// </summary>
private void SpeichernButton_Click(object? sender, RoutedEventArgs e) private void SpeichernButton_Click(object? sender, RoutedEventArgs e)
{ {
UpdatedProjekt.Projekt = ProjektnameBox.Text ?? ""; UpdatedProjekt.Projekt = ProjektnameBox.Text ?? "";
@ -39,9 +63,11 @@ public partial class ProjektBearbeitenDialog : Window
Close(UpdatedProjekt); Close(UpdatedProjekt);
} }
/// <summary>
/// Schließt den Dialog ohne Änderungen.
/// </summary>
private void AbbrechenButton_Click(object? sender, RoutedEventArgs e) private void AbbrechenButton_Click(object? sender, RoutedEventArgs e)
{ {
Close(null); Close(null);
} }
}
}

View File

@ -2,40 +2,108 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ChronoFlow.View.Admin.ProjektErstellenView"> x:Class="ChronoFlow.View.Admin.ProjektErstellenView">
<!-- Scrollfähiger Bereich für kleinere Fenster -->
<ScrollViewer> <ScrollViewer>
<StackPanel Margin="20" Spacing="15"> <StackPanel Margin="25" Spacing="15">
<TextBlock Text="Neues Projekt erstellen" FontSize="20" FontWeight="Bold" HorizontalAlignment="Center" /> <!-- Titelüberschrift -->
<TextBlock Text="Neues Projekt erstellen"
FontSize="20"
FontWeight="Bold"
HorizontalAlignment="Center" />
<TextBlock Text="Projektname:" /> <!-- Eingabefeld: Projektname -->
<TextBox x:Name="ProjektnameBox" /> <TextBlock Text="Projektname:" />
<TextBox x:Name="ProjektnameBox" />
<TextBlock Text="Startdatum:" /> <!-- Startdatum auswählen -->
<DatePicker x:Name="StartdatumPicker" /> <TextBlock Text="Startdatum:" />
<DatePicker x:Name="StartdatumPicker" />
<TextBlock Text="Startzeit (HH:mm):" /> <!-- Startzeit im Format HH:mm -->
<TextBox x:Name="StartzeitBox" Watermark="z.B. 09:00" /> <TextBlock Text="Startzeit (HH:mm):" />
<TextBox x:Name="StartzeitBox" Watermark="z.B. 09:00" />
<TextBlock Text="Enddatum:" /> <!-- Enddatum auswählen -->
<DatePicker x:Name="EnddatumPicker" /> <TextBlock Text="Enddatum:" />
<DatePicker x:Name="EnddatumPicker" />
<TextBlock Text="Endzeit (HH:mm):" /> <!-- Endzeit im Format HH:mm -->
<TextBox x:Name="EndzeitBox" Watermark="z.B. 17:00" /> <TextBlock Text="Endzeit (HH:mm):" />
<TextBox x:Name="EndzeitBox" Watermark="z.B. 17:00" />
<!-- Projektleiter über Dropdown zuweisen -->
<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>
</StackPanel>
<!-- Kommentarbereich -->
<TextBlock Text="Kommentar:" />
<TextBox x:Name="KommentarBox" AcceptsReturn="True" Height="80" />
<TextBlock Text="Mitarbeiter auswählen:" /> <!-- Hinweise zur Deadline-Farbe -->
<ComboBox x:Name="MitarbeiterDropdown" /> <Border Background="#111111"
CornerRadius="5"
Padding="10"
Margin="0,10,0,0">
<StackPanel Spacing="5">
<StackPanel Orientation="Horizontal" Spacing="5">
<TextBlock Text="🛈" FontWeight="Bold" />
<TextBlock Text="Hinweise zur Deadline-Anzeige:" FontWeight="Bold" />
</StackPanel>
<TextBlock Text="• Grün: Noch mehr als 7 Tage bis zur Deadline" />
<TextBlock Text="• Orange: Weniger als 7 Tage bis zur Deadline" />
<TextBlock Text="• Rot: Weniger als 3 Tage bis zur Deadline oder bereits abgelaufen" />
</StackPanel>
</Border>
<TextBlock Text="Kommentar:" /> <!-- Aktions-Buttons -->
<TextBox x:Name="KommentarBox" AcceptsReturn="True" Height="60" /> <StackPanel Orientation="Horizontal"
Spacing="10"
HorizontalAlignment="Center"
Margin="0,10,0,0">
<Button Content="✅ Speichern"
Click="SpeichernButton_Click"
Width="115"
Height="36" />
<StackPanel Orientation="Horizontal" Spacing="10" HorizontalAlignment="Center" Margin="0,10,0,0"> <Button Content="🧪 3 Demo-Projekte (rot/gelb/grün)"
<Button Content="✅ Speichern" Click="SpeichernButton_Click" Width="115" /> Click="DemoProjekteButton_Click"
<Button Content="⬅ Zurück zum Dashboard" Click="ZurueckButton_Click" Width="180" /> Width="250"
Height="36" />
<Button Content="⬅ Zurück zum Dashboard"
Click="ZurueckButton_Click"
Width="180"
Height="36" />
</StackPanel>
<!-- Optionaler Fehler-/Hinweistext -->
<TextBlock x:Name="FeedbackText"
Foreground="Red"
IsVisible="False"
HorizontalAlignment="Center"
Margin="0,10,0,0" />
</StackPanel> </StackPanel>
</ScrollViewer>
<TextBlock x:Name="FeedbackText" Foreground="Red" IsVisible="False" /> </UserControl>
</StackPanel>
</ScrollViewer>
</UserControl>

View File

@ -1,102 +1,213 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Media; using Avalonia.Media;
using ChronoFlow.Model; using ChronoFlow.Model;
using ChronoFlow.Persistence; using ChronoFlow.Persistence;
namespace ChronoFlow.View.Admin namespace ChronoFlow.View.Admin;
/// <summary>
/// Ansicht für Admins zum Erstellen neuer Projekte.
/// Wird intern als Zeiteintrag gespeichert.
/// </summary>
public partial class ProjektErstellenView : UserControl
{ {
public partial class ProjektErstellenView : UserControl private readonly ViewManager _viewManager;
public ProjektErstellenView() : this(new ViewManager(new ContentControl()))
{ {
private readonly ViewManager _viewManager; Console.WriteLine("⚠ Parameterloser Konstruktor genutzt. Dummy-ViewManager initialisiert.");
}
public ProjektErstellenView(ViewManager viewManager) public ProjektErstellenView(ViewManager viewManager)
{
InitializeComponent();
_viewManager = viewManager;
try
{ {
InitializeComponent();
_viewManager = viewManager;
var dbService = new SqliteZeiterfassungsService(); var dbService = new SqliteZeiterfassungsService();
List<string> mitarbeiter = dbService.LadeAlleMitarbeiterNamen(); List<string> mitarbeiter = dbService.LadeAlleMitarbeiterNamen();
// ✅ Nur ItemsSource verwenden, nicht Items // 🔍 Konsolenausgabe zur Kontrolle
MitarbeiterDropdown.ItemsSource = mitarbeiter; if (mitarbeiter is { Count: > 0 })
{
Console.WriteLine("✅ Mitarbeitende erfolgreich geladen:");
foreach (var name in mitarbeiter)
{
Console.WriteLine($"- {name}");
}
}
else
{
Console.WriteLine("⚠️ Achtung: Mitarbeitendenliste ist leer.");
}
// Items in GUI setzen
MitarbeiterListBox!.ItemsSource = mitarbeiter;
ProjektleiterDropdown!.ItemsSource = mitarbeiter;
} }
catch (Exception ex)
private void SpeichernButton_Click(object? sender, RoutedEventArgs e)
{ {
string projektname = ProjektnameBox.Text ?? ""; Console.WriteLine($"❌ Fehler beim Laden der Mitarbeiterauswahl: {ex.Message}");
DateTime startdatum = StartdatumPicker.SelectedDate?.Date ?? DateTime.Today; FeedbackText!.Text = "⚠ Fehler beim Laden der Mitarbeiter.";
DateTime enddatum = EnddatumPicker.SelectedDate?.Date ?? DateTime.Today; FeedbackText.Foreground = Brushes.Red;
string startzeitText = StartzeitBox.Text ?? "00:00"; FeedbackText.IsVisible = true;
string endzeitText = EndzeitBox.Text ?? "00:00"; }
string mitarbeiter = MitarbeiterDropdown.SelectedItem?.ToString() ?? ""; }
string kommentar = KommentarBox.Text ?? "";
if (string.IsNullOrWhiteSpace(projektname) || string.IsNullOrWhiteSpace(mitarbeiter))
{
FeedbackText.Text = "⚠ Bitte Projektname und Mitarbeiter ausfüllen!";
FeedbackText.Foreground = Brushes.Red;
FeedbackText.IsVisible = true;
return;
}
if (!TimeSpan.TryParse(startzeitText, out var startzeit)) private void SpeichernButton_Click(object? sender, RoutedEventArgs e)
{ {
FeedbackText.Text = "⚠ Ungültige Startzeit (Format HH:mm)!"; FeedbackText.IsVisible = false;
FeedbackText.Foreground = Brushes.Red;
FeedbackText.IsVisible = true;
return;
}
if (!TimeSpan.TryParse(endzeitText, out var endzeit)) string projektname = ProjektnameBox.Text ?? "";
{ DateTime startdatum = StartdatumPicker.SelectedDate?.Date ?? DateTime.Today;
FeedbackText.Text = "⚠ Ungültige Endzeit (Format HH:mm)!"; DateTime enddatum = EnddatumPicker.SelectedDate?.Date ?? DateTime.Today;
FeedbackText.Foreground = Brushes.Red; string startzeitText = StartzeitBox.Text ?? "00:00";
FeedbackText.IsVisible = true; string endzeitText = EndzeitBox.Text ?? "00:00";
return; string kommentar = KommentarBox.Text ?? "";
} string projektleiter = ProjektleiterDropdown.SelectedItem?.ToString() ?? "";
DateTime startDateTime = startdatum + startzeit; var ausgewaehlteMitarbeiter = MitarbeiterListBox?.SelectedItems?.Cast<string>().ToList() ?? new List<string>();
DateTime endDateTime = enddatum + endzeit;
var dbService = new SqliteZeiterfassungsService(); if (string.IsNullOrWhiteSpace(projektname) || ausgewaehlteMitarbeiter.Count == 0 || string.IsNullOrWhiteSpace(projektleiter))
{
FeedbackText.Text = "⚠ Bitte alle Pflichtfelder ausfüllen (Projektname, mindestens ein Mitarbeiter, Projektleiter)!";
FeedbackText.Foreground = Brushes.Red;
FeedbackText.IsVisible = true;
return;
}
// 💡 Achtung: Aktuell speichern wir als Zeiteintrag (kein eigenes Projektmodell!) if (!TimeSpan.TryParse(startzeitText, out var startzeit) || !TimeSpan.TryParse(endzeitText, out var endzeit))
{
FeedbackText.Text = "⚠ Ungültige Zeitangaben (Format HH:mm)!";
FeedbackText.Foreground = Brushes.Red;
FeedbackText.IsVisible = true;
return;
}
DateTime startDateTime = startdatum + startzeit;
DateTime endDateTime = enddatum + endzeit;
try
{
var dbService = new SqliteZeiterfassungsService();
foreach (var einzelnerMitarbeiter in ausgewaehlteMitarbeiter)
{
dbService.SpeichereEintrag(new Zeiteintrag dbService.SpeichereEintrag(new Zeiteintrag
{ {
Mitarbeiter = mitarbeiter, Mitarbeiter = einzelnerMitarbeiter,
Projekt = projektname, Projekt = projektname,
Startzeit = startDateTime, Startzeit = startDateTime,
Endzeit = endDateTime, Endzeit = endDateTime,
Kommentar = kommentar, Kommentar = kommentar,
Projektleiter = projektleiter,
Erledigt = false Erledigt = false
}); });
FeedbackText.Text = "✅ Projekt erfolgreich gespeichert.";
FeedbackText.Foreground = Brushes.Green;
FeedbackText.IsVisible = true;
// Felder zurücksetzen
ProjektnameBox.Text = "";
KommentarBox.Text = "";
StartdatumPicker.SelectedDate = DateTime.Today;
EnddatumPicker.SelectedDate = DateTime.Today;
StartzeitBox.Text = "09:00";
EndzeitBox.Text = "17:00";
MitarbeiterDropdown.SelectedItem = null;
// 🔄 Dashboard aktualisieren, wenn zurück
if (_viewManager.TryGetView<AdminMainView>("AdminMain", out var adminView) && adminView != null)
{
adminView.AktualisiereLetzteProjekte();
}
} }
private void ZurueckButton_Click(object? sender, RoutedEventArgs e) FeedbackText.Text = "✅ Projekt erfolgreich gespeichert.";
{ FeedbackText.Foreground = Brushes.Green;
_viewManager.Show("AdminMain"); FeedbackText.IsVisible = true;
}
ProjektnameBox.Text = "";
KommentarBox.Text = "";
StartdatumPicker.SelectedDate = DateTime.Today;
EnddatumPicker.SelectedDate = DateTime.Today;
StartzeitBox.Text = "09:00";
EndzeitBox.Text = "17:00";
MitarbeiterListBox.SelectedItems?.Clear();
MitarbeiterListBox.SelectedIndex = -1;
ProjektleiterDropdown.SelectedItem = null;
if (_viewManager.TryGetView<AdminMainView>("AdminMain", out var adminView) && adminView != null)
adminView.AktualisiereLetzteProjekte();
}
catch (Exception ex)
{
FeedbackText.Text = $"❌ Fehler beim Speichern: {ex.Message}";
FeedbackText.Foreground = Brushes.Red;
FeedbackText.IsVisible = true;
Console.WriteLine("❌ Ausnahme beim Speichern:");
Console.WriteLine(ex.ToString());
}
}
private void DemoProjekteButton_Click(object? sender, RoutedEventArgs e)
{
if (ProjektleiterDropdown.SelectedItem is not string projektleiterName)
{
FeedbackText.Text = "❗ Bitte wähle auch einen Projektleiter für die Demo-Projekte.";
FeedbackText.Foreground = Brushes.Red;
FeedbackText.IsVisible = true;
return;
}
if (MitarbeiterListBox.SelectedItems?.Count == 0)
{
FeedbackText.Text = "❗ Bitte wähle mindestens einen Mitarbeiter für die Demo-Projekte.";
FeedbackText.Foreground = Brushes.Red;
FeedbackText.IsVisible = true;
return;
}
var service = new SqliteZeiterfassungsService();
var heute = DateTime.Today;
var mitarbeiter = MitarbeiterListBox?.SelectedItems?.Cast<string>().ToList() ?? new List<string>();
foreach (var name in mitarbeiter)
{
var projekte = new List<Zeiteintrag>
{
new()
{
Projekt = "Demo: Langfristig (grün)",
Kommentar = "✅ Deadline in mehr als 7 Tagen.",
Startzeit = heute.AddDays(-1).AddHours(9),
Endzeit = heute.AddDays(10).AddHours(17),
Mitarbeiter = name,
Projektleiter = projektleiterName,
Erledigt = false
},
new()
{
Projekt = "Demo: Mittel (Orange)",
Kommentar = "🟡 Deadline in 37 Tagen.",
Startzeit = heute.AddDays(-1).AddHours(9),
Endzeit = heute.AddDays(5).AddHours(17),
Mitarbeiter = name,
Projektleiter = projektleiterName,
Erledigt = false
},
new()
{
Projekt = "Demo: Kurzfristig (rot)",
Kommentar = "🔴 Deadline in weniger als 3 Tagen.",
Startzeit = heute.AddDays(-1).AddHours(9),
Endzeit = heute.AddDays(1).AddHours(17),
Mitarbeiter = name,
Projektleiter = projektleiterName,
Erledigt = false
}
};
foreach (var p in projekte)
service.SpeichereEintrag(p);
}
FeedbackText.Text = "✔️ 3 Demo-Projekte wurden erfolgreich erstellt.";
FeedbackText.Foreground = Brushes.Green;
FeedbackText.IsVisible = true;
}
private void ZurueckButton_Click(object? sender, RoutedEventArgs e)
{
_viewManager.Show("AdminMain");
} }
} }

View File

@ -2,19 +2,43 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ChronoFlow.View.LoginWindow" x:Class="ChronoFlow.View.LoginWindow"
Width="400" Height="300" Width="400" Height="300"
Title="ChronoFlow Login"> Title="ChronoFlow Login"
CanResize="False"
WindowStartupLocation="CenterScreen">
<StackPanel Margin="20" Spacing="10"> <StackPanel Margin="20" Spacing="10">
<TextBlock Text="ChronoFlow Login" FontSize="24" FontWeight="Bold" HorizontalAlignment="Center" />
<!-- 🔐 Überschrift -->
<TextBlock Text="ChronoFlow Login"
FontSize="24"
FontWeight="Bold"
HorizontalAlignment="Center" />
<!-- 👤 Benutzername -->
<TextBlock Text="Benutzername" /> <TextBlock Text="Benutzername" />
<TextBox x:Name="UsernameBox" /> <TextBox x:Name="UsernameBox"
Watermark="z.B. admin" />
<!-- 🔒 Passwort -->
<TextBlock Text="Passwort" /> <TextBlock Text="Passwort" />
<TextBox x:Name="PasswordBox" PasswordChar="●" /> <TextBox x:Name="PasswordBox"
PasswordChar="●"
Watermark="Passwort" />
<Button Content="Anmelden" Click="LoginButton_Click" HorizontalAlignment="Center" Width="120" Margin="0,10,0,0" /> <!-- 🟩 Login-Button -->
<Button Content="Anmelden"
Click="LoginButton_Click"
HorizontalAlignment="Center"
Width="120"
Margin="0,10,0,0" />
<!-- ❌ Fehleranzeige -->
<TextBlock x:Name="ErrorText"
Foreground="Red"
IsVisible="False"
TextWrapping="Wrap"
TextAlignment="Center"
FontWeight="SemiBold" />
<TextBlock x:Name="ErrorText" Foreground="Red" IsVisible="False" TextWrapping="Wrap" TextAlignment="Center" />
</StackPanel> </StackPanel>
</Window> </Window>

View File

@ -6,128 +6,142 @@ using ChronoFlow.Controller;
using ChronoFlow.Persistence; using ChronoFlow.Persistence;
using ChronoFlow.Security; using ChronoFlow.Security;
using ChronoFlow.View.Security; using ChronoFlow.View.Security;
using Microsoft.Data.Sqlite;
namespace ChronoFlow.View namespace ChronoFlow.View;
/// <summary>
/// Das Fenster für den Benutzer-Login.
/// </summary>
public partial class LoginWindow : Window
{ {
private readonly LoginController _loginController;
/// <summary> /// <summary>
/// Das Fenster für den Benutzer-Login. /// Konstruktor Initialisiert die Oberfläche und legt bei Bedarf einen Standard-Admin an.
/// </summary> /// </summary>
public partial class LoginWindow : Window public LoginWindow()
{ {
private readonly LoginController _loginController; InitializeComponent();
_loginController = new LoginController();
public LoginWindow() try
{ {
InitializeComponent();
_loginController = new LoginController();
var service = new SqliteZeiterfassungsService(); var service = new SqliteZeiterfassungsService();
service.ErstelleStandardAdmin(); service.ErstelleStandardAdmin();
} }
catch (SqliteException ex) when (ex.SqliteErrorCode == 5)
/// <summary>
/// Wird ausgeführt, wenn der Benutzer auf "Anmelden" klickt.
/// </summary>
private async void LoginButton_Click(object? sender, RoutedEventArgs e)
{ {
var username = UsernameBox.Text?.Trim(); ErrorText.Text = "⚠️ Die Datenbank ist gesperrt. Bitte schließen Sie andere Programme (z.B. DB Browser for SQLite) und starten Sie die App neu.";
var password = PasswordBox.Text?.Trim(); ErrorText.IsVisible = true;
}
catch (Exception ex)
{
ErrorText.Text = $"Fehler beim Initialisieren: {ex.Message}";
ErrorText.IsVisible = true;
}
}
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password)) /// <summary>
/// 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();
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
{
ErrorText.Text = "Bitte Benutzername und Passwort eingeben.";
ErrorText.IsVisible = true;
return;
}
SqliteZeiterfassungsService service;
try
{
service = new SqliteZeiterfassungsService();
}
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.IsVisible = true;
return;
}
var benutzerListe = service.LadeAlleBenutzer();
var matchingUsers = benutzerListe
.Where(u => u.Username.Equals(username, StringComparison.OrdinalIgnoreCase))
.ToList();
if (matchingUsers.Count == 0)
{
ErrorText.Text = "Benutzername nicht gefunden.";
ErrorText.IsVisible = true;
return;
}
if (matchingUsers.Count > 1)
{
ErrorText.Text = "Interner Fehler: Mehrere Benutzer mit gleichem Namen.";
ErrorText.IsVisible = true;
return;
}
var user = matchingUsers.First();
if (!PasswordHasher.VerifyPassword(password, user.Password))
{
ErrorText.Text = "Falsches Passwort. Bitte erneut versuchen.";
ErrorText.IsVisible = true;
return;
}
// Passwort muss geändert werden
if (user.MussPasswortAendern)
{
var dialog = new PasswortAendernDialog(user);
var neuesPasswort = await dialog.ShowDialog<string>(this);
if (!string.IsNullOrEmpty(neuesPasswort))
{ {
ErrorText.Text = "Bitte Benutzername und Passwort eingeben."; string neuerHash = PasswordHasher.HashPassword(neuesPasswort);
user.Password = neuerHash;
user.MussPasswortAendern = false;
service.UpdateBenutzer(user);
}
else
{
ErrorText.Text = "Sie müssen ein neues Passwort setzen!";
ErrorText.IsVisible = true; ErrorText.IsVisible = true;
return; return;
} }
var service = new SqliteZeiterfassungsService();
var benutzerListe = service.LadeAlleBenutzer();
var matchingUsers = benutzerListe.Where(u => u.Username == username).ToList();
if (matchingUsers.Count == 0)
{
ErrorText.Text = "Benutzername nicht gefunden.";
ErrorText.IsVisible = true;
return;
}
if (matchingUsers.Count > 1)
{
Console.WriteLine("[WARNUNG] Mehrere Benutzer mit gleichem Namen gefunden! Bitte Datenbank prüfen.");
ErrorText.Text = "Interner Fehler: Mehrere Benutzer mit gleichem Namen.";
ErrorText.IsVisible = true;
return;
}
var user = matchingUsers.First();
Console.WriteLine($"[DEBUG] Benutzer gefunden: {user.Username}");
Console.WriteLine($"[DEBUG] Gespeicherter Hash in DB: {user.Password}");
Console.WriteLine($"[DEBUG] Eingegebenes Passwort: {password}");
bool isMatch = PasswordHasher.VerifyPassword(password, user.Password);
Console.WriteLine($"[DEBUG] Passwortprüfung erfolgreich: {isMatch}");
if (!isMatch)
{
ErrorText.Text = "Falsches Passwort. Bitte erneut versuchen.";
ErrorText.IsVisible = true;
return;
}
// Wenn Passwortänderung erforderlich, Dialog anzeigen
if (user.MussPasswortAendern)
{
var dialog = new PasswortAendernDialog(user);
var neuesPasswort = await dialog.ShowDialog<string>(this);
if (!string.IsNullOrEmpty(neuesPasswort))
{
string neuerHash = PasswordHasher.HashPassword(neuesPasswort);
Console.WriteLine($"[DEBUG] Neues Passwort (klar): {neuesPasswort}");
Console.WriteLine($"[DEBUG] Neuer gespeicherter Hash: {neuerHash}");
user.Password = neuerHash;
user.MussPasswortAendern = false;
service.UpdateBenutzer(user);
Console.WriteLine("✅ Passwort wurde erfolgreich geändert.");
}
else
{
ErrorText.Text = "Sie müssen ein neues Passwort setzen!";
ErrorText.IsVisible = true;
return;
}
}
// 🚀 Login erfolgreich → MainWindow starten
try
{
Console.WriteLine("🚀 Öffne MainWindow...");
// ⏱️ bisherigen Login sichern und VorletzterLogin setzen
var bisherigerLogin = user.LetzterLogin;
user.LetzterLogin = DateTime.Now;
user.VorletzterLogin = bisherigerLogin;
// 💾 Neue Login-Zeitpunkte speichern
service.UpdateLoginZeiten(user);
// Fenster öffnen
var main = new MainWindow(user);
main.Show();
main.Activate(); // ✨ explizit in den Vordergrund bringen
Close(); // LoginWindow erst danach schließen
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] MainWindow konnte nicht geöffnet werden: {ex.Message}");
ErrorText.Text = "Interner Fehler beim Starten des Hauptfensters.";
ErrorText.IsVisible = true;
}
} }
try
{
// Login-Zeiten aktualisieren
var vorher = user.LetzterLogin;
user.LetzterLogin = DateTime.Now;
user.VorletzterLogin = vorher;
service.UpdateLoginZeiten(user);
// Hauptfenster öffnen
var main = new MainWindow(user);
main.Show();
main.Activate();
Close();
}
catch (SqliteException ex) when (ex.SqliteErrorCode == 5)
{
ErrorText.Text = "⚠️ Datenbank gesperrt. Bitte schließen Sie andere Programme und starten Sie die App neu.";
ErrorText.IsVisible = true;
}
catch (Exception ex)
{
ErrorText.Text = $"Interner Fehler beim Starten des Hauptfensters: {ex.Message}";
ErrorText.IsVisible = true;
}
} }
} }

View File

@ -6,68 +6,106 @@ using ChronoFlow.Persistence;
using ChronoFlow.View.Admin; using ChronoFlow.View.Admin;
using ChronoFlow.View.Mitarbeiter; using ChronoFlow.View.Mitarbeiter;
namespace ChronoFlow.View namespace ChronoFlow.View;
/// <summary>
/// Hauptfenster der Anwendung je nach Benutzerrolle wird die passende Startansicht geladen.
/// </summary>
public partial class MainWindow : Window
{ {
public partial class MainWindow : Window private readonly ViewManager _viewManager;
private readonly User _loggedInUser;
/// <summary>
/// Parameterloser Konstruktor erforderlich für Avalonia Runtime Loader.
/// Startet mit einem Dummy-Nutzer (Demo-Zwecke oder Designer-Unterstützung).
/// </summary>
public MainWindow() : this(new User
{ {
private readonly ViewManager _viewManager; Username = "DemoUser",
private readonly User _loggedInUser; Role = "Mitarbeiter"
})
{
Console.WriteLine("⚠ Achtung: MainWindow ohne echten Benutzer gestartet (Demo-Modus).");
}
public MainWindow(User user) /// <summary>
/// Konstruktor bekommt den eingeloggten Benutzer übergeben.
/// Registriert alle möglichen Views und zeigt die Startansicht je nach Rolle.
/// </summary>
public MainWindow(User user)
{
InitializeComponent();
_loggedInUser = user;
Console.WriteLine($"[DEBUG] MainWindow gestartet für Benutzer: {_loggedInUser.Username} ({_loggedInUser.Role})");
// Initialisierung des ViewManagers mit der ContentArea (wird in XAML gesetzt)
_viewManager = new ViewManager(ContentArea);
// 🔄 Registrierung aller Views, die im Projekt vorkommen
_viewManager.Register("ProjektErstellen", () => new ProjektErstellenView(_viewManager));
_viewManager.Register("MitarbeiterHinzufuegen", () => new MitarbeiterHinzufuegenView());
_viewManager.Register("AdminMain", () => new AdminMainView(_viewManager));
_viewManager.Register("AlleProjekte", () => new AlleProjekteView(_viewManager));
_viewManager.Register("MitarbeiterListe", () => new MitarbeiterListeView(_viewManager));
_viewManager.Register("AbgeschlosseneProjekte", () => new AbgeschlosseneProjekteView(_viewManager));
_viewManager.Register("Zeiterfassung", () => new EmployeeTasksView(_loggedInUser, new SqliteZeiterfassungsService()));
// 🧭 Starte mit der passenden Ansicht je nach Rolle
if (_loggedInUser.Role == "Admin")
{ {
InitializeComponent(); _viewManager.Show("AdminMain");
_loggedInUser = user;
Console.WriteLine($"[DEBUG] MainWindow gestartet für Benutzer: {_loggedInUser.Username} ({_loggedInUser.Role})");
_viewManager = new ViewManager(ContentArea);
_viewManager.Register("ProjektErstellen", () => new ProjektErstellenView(_viewManager));
_viewManager.Register("MitarbeiterHinzufuegen", () => new MitarbeiterHinzufuegenView());
_viewManager.Register("AdminMain", () => new AdminMainView(_viewManager));
_viewManager.Register("AlleProjekte", () => new AlleProjekteView(_viewManager));
_viewManager.Register("MitarbeiterListe", () => new MitarbeiterListeView(_viewManager));
_viewManager.Register("AbgeschlosseneProjekte", () => new AbgeschlosseneProjekteView(_viewManager));
_viewManager.Register("Zeiterfassung", () =>
new EmployeeTasksView(_loggedInUser, new SqliteZeiterfassungsService()));
if (_loggedInUser.Role == "Admin")
{
_viewManager.Show("AdminMain");
}
else if (_loggedInUser.Role == "Mitarbeiter")
{
_viewManager.Show("Zeiterfassung"); // ⏳ später: MitarbeiterMain
}
this.Title = $"ChronoFlow - Willkommen {_loggedInUser.Username} ({_loggedInUser.Role})";
} }
else if (_loggedInUser.Role == "Mitarbeiter")
private void Zeiterfassung_Click(object? sender, RoutedEventArgs e)
{ {
_viewManager.Show("Zeiterfassung"); _viewManager.Show("Zeiterfassung");
} }
private void MitarbeiterHinzufuegen_Click(object? sender, RoutedEventArgs e) // 🖼️ Fenster-Titel setzen (optional für Nutzerfreundlichkeit)
{ this.Title = $"ChronoFlow - Willkommen {_loggedInUser.Username} ({_loggedInUser.Role})";
_viewManager.Show("MitarbeiterHinzufuegen"); }
}
private void AdminDashboard_Click(object? sender, RoutedEventArgs e) /// <summary>
{ /// Öffnet die Mitarbeiter-Zeiterfassung (nur für Mitarbeiter-Sidebar-Button).
_viewManager.Show("AdminMain"); /// </summary>
} private void Zeiterfassung_Click(object? sender, RoutedEventArgs e)
{
_viewManager.Show("Zeiterfassung");
}
public void ShowAdminDashboard() /// <summary>
{ /// Öffnet das Formular zum Anlegen eines neuen Mitarbeiters.
_viewManager.Show("AdminMain"); /// </summary>
} private void MitarbeiterHinzufuegen_Click(object? sender, RoutedEventArgs e)
private void Logout_Click(object? sender, RoutedEventArgs e) {
{ _viewManager.Show("MitarbeiterHinzufuegen");
var loginWindow = new LoginWindow(); }
loginWindow.Show();
this.Close(); // Aktuelles Fenster schließen /// <summary>
} /// Öffnet das Admin-Dashboard (Übersicht).
/// </summary>
private void AdminDashboard_Click(object? sender, RoutedEventArgs e)
{
_viewManager.Show("AdminMain");
}
/// <summary>
/// Extern aufrufbar für "Zurück zum Dashboard"-Funktion (z.B. in anderen Views).
/// </summary>
public void ShowAdminDashboard()
{
_viewManager.Show("AdminMain");
}
/// <summary>
/// Führt einen Logout durch öffnet das Loginfenster neu und schließt sich selbst.
/// </summary>
private void Logout_Click(object? sender, RoutedEventArgs e)
{
var loginWindow = new LoginWindow();
loginWindow.Show();
this.Close(); // Schließt das aktuelle Fenster
} }
} }

View File

@ -25,6 +25,12 @@
<!-- Projekttitel --> <!-- Projekttitel -->
<TextBlock Text="{Binding Projekt}" FontWeight="Bold"/> <TextBlock Text="{Binding Projekt}" FontWeight="Bold"/>
<!-- Projektleiter anzeigen -->
<TextBlock Text="{Binding Projektleiter, StringFormat='👤 Projektleiter: {0}'}"
FontSize="12"
Foreground="Gray"
FontStyle="Italic"/>
<!-- Admin-Kommentar --> <!-- Admin-Kommentar -->
<TextBlock Text="📝 Kommentar (Admin):" FontSize="12" Foreground="LightGray"/> <TextBlock Text="📝 Kommentar (Admin):" FontSize="12" Foreground="LightGray"/>
<TextBlock Text="{Binding Kommentar}" FontWeight="SemiBold"/> <TextBlock Text="{Binding Kommentar}" FontWeight="SemiBold"/>

View File

@ -1,16 +1,34 @@
using Avalonia.Controls; using Avalonia.Controls;
using ChronoFlow.View.Mitarbeiter;
using ChronoFlow.Persistence;
using ChronoFlow.Model; using ChronoFlow.Model;
using ChronoFlow.Persistence;
namespace ChronoFlow.View.Mitarbeiter namespace ChronoFlow.View.Mitarbeiter;
/// <summary>
/// View für Mitarbeiter zur Anzeige und Bearbeitung ihrer Aufgaben.
/// </summary>
public partial class EmployeeTasksView : UserControl
{ {
public partial class EmployeeTasksView : UserControl /// <summary>
/// Öffentlicher parameterloser Konstruktor notwendig für Avalonia-Runtime (AVLN0005).
/// Wird nur für Design-Time oder XAML-Lader verwendet.
/// </summary>
public EmployeeTasksView()
: this(new User { Username = "Demo", Role = "Mitarbeiter" }, new SqliteZeiterfassungsService())
{ {
public EmployeeTasksView(User user, IZeiterfassungsRepository repository) // Nur für Design-Time oder XAML-Vorschau gedacht.
{ }
InitializeComponent();
DataContext = new EmployeeTasksViewModel(user, repository); /// <summary>
} /// Konstruktor Setzt das DataContext auf das zugehörige ViewModel.
/// </summary>
/// <param name="user">Der aktuell eingeloggte Benutzer</param>
/// <param name="repository">Das Datenzugriffsobjekt</param>
public EmployeeTasksView(User user, IZeiterfassungsRepository repository)
{
InitializeComponent();
// Setzt das ViewModel als DataContext
DataContext = new EmployeeTasksViewModel(user);
} }
} }

View File

@ -1,4 +1,3 @@
// TestKommentar
using System; using System;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
@ -7,111 +6,86 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using ChronoFlow.Model; using ChronoFlow.Model;
using ChronoFlow.Persistence; using ChronoFlow.Persistence;
using ChronoFlow.View.Mitarbeiter; using ChronoFlow.Controller;
namespace ChronoFlow.View.Mitarbeiter 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.
/// </summary>
public partial class EmployeeTasksViewModel : ObservableObject
{ {
/// <summary> private readonly User _benutzer;
/// ViewModel für die Mitarbeiter-Aufgabenansicht.
/// Enthält alle Aufgaben für den eingeloggten Benutzer und erlaubt das Speichern von Änderungen. private readonly ZeiterfassungsController controller = new();
/// </summary>
public partial class EmployeeTasksViewModel : ObservableObject private readonly string aktuellerBenutzername;
[ObservableProperty]
private ObservableCollection<Zeiteintrag> eintraege = new();
[ObservableProperty]
private string? statusText;
public bool HatKeineEintraege => Eintraege.Count == 0;
partial void OnEintraegeChanged(ObservableCollection<Zeiteintrag>? oldValue, ObservableCollection<Zeiteintrag> newValue)
{ {
// 🧑‍💼 Der aktuell eingeloggte Benutzer (Mitarbeiter) OnPropertyChanged(nameof(HatKeineEintraege));
private readonly User _benutzer; }
// 💾 Zugriff auf die Datenbank-Funktionen (SQL-Repository) public EmployeeTasksViewModel(User benutzer)
private readonly IZeiterfassungsRepository _repository; {
_benutzer = benutzer;
aktuellerBenutzername = benutzer.Username;
// 📛 Einfacher Zugriff auf den Benutzernamen (spart Schreibarbeit) controller = new ZeiterfassungsController(); // falls du das oben nicht hast
private readonly string aktuellerBenutzername; _ = LadeEintraegeAsync();
}
// 📋 Alle Zeiteinträge, die dem Benutzer angezeigt werden
[ObservableProperty]
private ObservableCollection<Zeiteintrag> eintraege = new();
// 💬 Statusmeldung unten im Fenster (z.B. bei Erfolg) [RelayCommand]
[ObservableProperty] public async Task LadeEintraegeAsync()
private string? statusText; {
// 🔄 Einträge synchron laden, aber async verpacken
var alleEintraege = await Task.Run(() => controller.LadeAlleEintraege());
// ✅ Hilfs-Property, die sagt: "Gibt es keine Einträge?" var relevanteEintraege = alleEintraege
public bool HatKeineEintraege => Eintraege.Count == 0; .Where(e => !e.Erledigt &&
(e.Mitarbeiter == aktuellerBenutzername || e.Projektleiter == aktuellerBenutzername))
.OrderBy(e => e.Endzeit)
.ToList();
/// <summary> foreach (var eintrag in relevanteEintraege)
/// Diese Methode wird automatisch aufgerufen, wenn sich die Einträge ändern.
/// Damit wird die UI über die Änderung von 'HatKeineEintraege' informiert.
/// </summary>
partial void OnEintraegeChanged(ObservableCollection<Zeiteintrag>? oldValue, ObservableCollection<Zeiteintrag> newValue)
{ {
OnPropertyChanged(nameof(HatKeineEintraege)); eintrag.WurdeSeitLoginBearbeitet = eintrag.LetzteBearbeitung > _benutzer.VorletzterLogin;
} }
/// <summary> if (relevanteEintraege.Any(e => e.WurdeSeitLoginBearbeitet))
/// Konstruktor: Bekommt den eingeloggten Benutzer und das Repository. StatusText = "📢 Es wurden Aufgaben seit Ihrem letzten Login geändert.";
/// Lädt automatisch alle offenen Einträge für diesen Benutzer.
/// </summary> Eintraege = new ObservableCollection<Zeiteintrag>(relevanteEintraege);
public EmployeeTasksViewModel(User benutzer, IZeiterfassungsRepository repository) }
[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
foreach (var eintrag in Eintraege)
{ {
_benutzer = benutzer; await Task.Run(() =>
_repository = repository;
aktuellerBenutzername = benutzer.Username;
Console.WriteLine($"[DEBUG] ViewModel erstellt für Benutzer: {aktuellerBenutzername}");
// Daten automatisch beim Öffnen laden
_ = LadeEintraegeAsync();
}
/// <summary>
/// Lädt alle offenen Einträge für den aktuell eingeloggten Mitarbeiter.
/// Sortiert nach Fälligkeitsdatum (Endzeit).
/// Erkennt auch, ob seit dem letzten Login Änderungen gemacht wurden.
/// </summary>
[RelayCommand]
public async Task LadeEintraegeAsync()
{
var eintraegeAusDb = await _repository.GetEintraegeFuerMitarbeiterAsync(aktuellerBenutzername);
var offene = eintraegeAusDb
.Where(e => !e.Erledigt)
.OrderBy(e => e.Endzeit)
.ToList();
// 🔔 Markiere geänderte Einträge seit dem letzten Login
foreach (var eintrag in offene)
{ {
eintrag.WurdeSeitLoginBearbeitet = eintrag.LetzteBearbeitung > _benutzer.VorletzterLogin; var sqlite = new SqliteZeiterfassungsService();
} sqlite.UpdateStatusUndKommentarAsync(eintrag.Id, eintrag.Erledigt, eintrag.MitarbeiterKommentar).Wait();
});
// 🔔 Zeige Hinweis, wenn neue Änderungen vorhanden sind
if (offene.Any(e => e.WurdeSeitLoginBearbeitet))
StatusText = "📢 Es wurden Aufgaben seit Ihrem letzten Login geändert.";
// 🔄 Aktualisiere ObservableCollection für UI
Eintraege = new ObservableCollection<Zeiteintrag>(offene);
} }
/// <summary> StatusText = "✅ Änderungen gespeichert.";
/// Speichert alle Änderungen an den sichtbaren Einträgen await LadeEintraegeAsync();
/// (Status 'erledigt' + Mitarbeiter-Kommentar).
/// Danach wird die Liste automatisch neu geladen.
/// </summary>
[RelayCommand]
public async Task SpeichereEintraegeAsync()
{
foreach (var eintrag in Eintraege)
{
await _repository.UpdateStatusUndKommentarAsync(
eintrag.Id,
eintrag.Erledigt,
eintrag.MitarbeiterKommentar
);
}
StatusText = "✅ Änderungen gespeichert.";
// 🔄 Nach dem Speichern direkt neu laden
await LadeEintraegeAsync();
}
} }
} }

View File

@ -4,47 +4,74 @@ using Avalonia.Controls;
using ChronoFlow.Model; using ChronoFlow.Model;
using ChronoFlow.Persistence; using ChronoFlow.Persistence;
namespace ChronoFlow.View.Mitarbeiter namespace ChronoFlow.View.Mitarbeiter;
/// <summary>
/// Hauptansicht für eingeloggte Mitarbeiter zeigt zuletzt bearbeitete Projekte und Benachrichtigungen.
/// </summary>
public partial class MitarbeiterMainView : UserControl
{ {
public partial class MitarbeiterMainView : UserControl private readonly ViewManager _viewManager;
private readonly User _currentUser;
private readonly ObservableCollection<Zeiteintrag> _letzteProjekte = new();
private readonly ObservableCollection<string> _notifications = new();
/// <summary>
/// Öffentlicher, parameterloser Konstruktor erforderlich für Avalonia Runtime Loader (z.B. Designer).
/// Startet mit Dummy-Werten.
/// </summary>
public MitarbeiterMainView()
: this(new ViewManager(new ContentControl()), new User { Username = "Demo", Role = "Mitarbeiter" })
{ {
private readonly ViewManager _viewManager; Console.WriteLine("⚠️ MitarbeiterMainView im Demo-Modus geladen.");
private readonly User _currentUser; }
private readonly ObservableCollection<Zeiteintrag> _letzteProjekte = new();
private readonly ObservableCollection<string> _notifications = new();
public MitarbeiterMainView(ViewManager viewManager, User user) /// <summary>
/// Konstruktor mit ViewManager und eingeloggtem Benutzer.
/// </summary>
/// <param name="viewManager">Instanz zur Navigation</param>
/// <param name="user">Eingeloggter Benutzer (Mitarbeiter)</param>
public MitarbeiterMainView(ViewManager viewManager, User user)
{
InitializeComponent();
_viewManager = viewManager;
_currentUser = user;
// Datenquellen binden
LetzteProjekteListe.ItemsSource = _letzteProjekte;
NotificationList.ItemsSource = _notifications;
// Daten laden
LadeLetzteProjekte();
LadeBenachrichtigungen();
}
/// <summary>
/// Lädt die letzten 3 Projekte, die vom aktuellen Benutzer bearbeitet wurden.
/// </summary>
private void LadeLetzteProjekte()
{
var dbService = new SqliteZeiterfassungsService();
var projekte = dbService.LadeLetzteProjekte(3);
_letzteProjekte.Clear();
foreach (var p in projekte)
{ {
InitializeComponent(); if (p.Mitarbeiter == _currentUser.Username)
_viewManager = viewManager; _letzteProjekte.Add(p);
_currentUser = user;
LetzteProjekteListe.ItemsSource = _letzteProjekte;
NotificationList.ItemsSource = _notifications;
LadeLetzteProjekte();
LadeBenachrichtigungen();
}
private void LadeLetzteProjekte()
{
var dbService = new SqliteZeiterfassungsService();
var projekte = dbService.LadeLetzteProjekte(3);
_letzteProjekte.Clear();
foreach (var p in projekte)
{
if (p.Mitarbeiter == _currentUser.Username)
_letzteProjekte.Add(p);
}
}
private void LadeBenachrichtigungen()
{
// 🛈 Platzhalter → hier später echte DB-Infos laden!
_notifications.Clear();
_notifications.Add("Projekt Alpha wurde aktualisiert.");
_notifications.Add("Deadline für Projekt Beta wurde verschoben.");
} }
} }
}
/// <summary>
/// Lädt Benachrichtigungen für den Benutzer (derzeit Platzhalter).
/// </summary>
private void LadeBenachrichtigungen()
{
_notifications.Clear();
// 📌 Hier später echte Änderungsinfos anzeigen
_notifications.Add("Projekt Alpha wurde aktualisiert.");
_notifications.Add("Deadline für Projekt Beta wurde verschoben.");
}
}

View File

@ -0,0 +1,75 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using ChronoFlow.Model;
using ChronoFlow.Persistence;
using ChronoFlow.Controller;
namespace ChronoFlow.View.Projektleiter;
public partial class ProjektleiterTasksViewModel : ObservableObject
{
private readonly User _benutzer;
private readonly ZeiterfassungsController controller = new();
private readonly string aktuellerBenutzername;
[ObservableProperty]
private ObservableCollection<Zeiteintrag> eintraege = new();
[ObservableProperty]
private string? statusText;
public bool HatKeineEintraege => Eintraege.Count == 0;
partial void OnEintraegeChanged(ObservableCollection<Zeiteintrag>? oldValue, ObservableCollection<Zeiteintrag> newValue)
{
OnPropertyChanged(nameof(HatKeineEintraege));
}
public ProjektleiterTasksViewModel(User benutzer)
{
_benutzer = benutzer;
aktuellerBenutzername = benutzer.Username;
_ = LadeEintraegeAsync();
}
[RelayCommand]
public async Task LadeEintraegeAsync()
{
var alleEintraege = await Task.Run(() => controller.LadeAlleEintraege());
var relevanteEintraege = alleEintraege
.Where(e => !e.Erledigt && e.Projektleiter == aktuellerBenutzername)
.OrderBy(e => e.Endzeit)
.ToList();
foreach (var eintrag in relevanteEintraege)
{
eintrag.WurdeSeitLoginBearbeitet = eintrag.LetzteBearbeitung > _benutzer.VorletzterLogin;
}
if (relevanteEintraege.Any(e => e.WurdeSeitLoginBearbeitet))
StatusText = "\ud83d\udce2 Es wurden Aufgaben seit Ihrem letzten Login geändert.";
Eintraege = new ObservableCollection<Zeiteintrag>(relevanteEintraege);
}
[RelayCommand]
public async Task SpeichereEintraegeAsync()
{
foreach (var eintrag in Eintraege)
{
await Task.Run(() =>
{
var sqlite = new SqliteZeiterfassungsService();
sqlite.UpdateStatusUndKommentarAsync(eintrag.Id, eintrag.Erledigt, eintrag.MitarbeiterKommentar).Wait();
});
}
StatusText = "Änderungen gespeichert.";
await LadeEintraegeAsync();
}
}

View File

@ -2,24 +2,54 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ChronoFlow.View.MitarbeiterHinzufuegenView"> x:Class="ChronoFlow.View.MitarbeiterHinzufuegenView">
<!-- Scrollbarer Bereich für kleinere Fenstergrößen -->
<ScrollViewer> <ScrollViewer>
<StackPanel Margin="20" Spacing="15"> <StackPanel Margin="20" Spacing="15">
<TextBlock Text=" Mitarbeiter hinzufügen" FontWeight="Bold" FontSize="20" HorizontalAlignment="Center"/> <!-- Überschrift -->
<TextBlock Text=" Mitarbeiter hinzufügen"
FontWeight="Bold"
FontSize="20"
HorizontalAlignment="Center" />
<TextBox x:Name="UsernameBox" Watermark="Benutzername"/> <!-- Eingabefeld: Benutzername -->
<TextBox x:Name="PasswordBox" Watermark="Passwort"/> <TextBox x:Name="UsernameBox" Watermark="Benutzername" />
<ComboBox x:Name="RoleBox" PlaceholderText="Rolle auswählen">
<ComboBoxItem Content="Mitarbeiter"/>
<ComboBoxItem Content="Admin"/>
</ComboBox>
<TextBox x:Name="MitarbeiternummerBox" Watermark="Mitarbeiternummer"/> <!-- Eingabefeld: Passwort -->
<TextBox x:Name="AbteilungBox" Watermark="Abteilung"/> <TextBox x:Name="PasswordBox" Watermark="Passwort" />
<Button Content="💾 Speichern" Click="SpeichernButton_Click" HorizontalAlignment="Center"/> <!-- Auswahl der Benutzerrolle -->
<Button Content="⬅ Zurück zum Dashboard" Click="ZurueckZumDashboard_Click" HorizontalAlignment="Center" Margin="0,10,0,0"/> <ComboBox x:Name="RoleBox" PlaceholderText="Rolle auswählen">
<TextBlock x:Name="FeedbackText" Foreground="Green" IsVisible="False" TextAlignment="Center"/> <ComboBoxItem Content="Mitarbeiter"/>
</StackPanel> <ComboBoxItem Content="Admin"/>
</ScrollViewer> </ComboBox>
</UserControl>
<!-- Eingabefeld: Mitarbeiternummer -->
<TextBox x:Name="MitarbeiternummerBox" Watermark="Mitarbeiternummer" />
<!-- Eingabefeld: Abteilung -->
<TextBox x:Name="AbteilungBox" Watermark="Abteilung" />
<!-- Speicher-Button -->
<Button Content="💾 Speichern"
Click="SpeichernButton_Click"
HorizontalAlignment="Center" />
<!-- Demo-Mitarbeiter erstellen (für schnelles Testen durch Dozent) -->
<Button Content="🧪 Demo-Mitarbeiter erstellen"
Click="DemoBenutzerErstellen_Click"
HorizontalAlignment="Center" />
<!-- Zurück-Button -->
<Button Content="⬅ Zurück zum Dashboard"
Click="ZurueckZumDashboard_Click"
HorizontalAlignment="Center"
Margin="0,10,0,0" />
<!-- Text für Statusmeldungen (Erfolg oder Fehler) -->
<TextBlock x:Name="FeedbackText"
Foreground="Green"
IsVisible="False"
TextAlignment="Center" />
</StackPanel>
</ScrollViewer>
</UserControl>

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Linq;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Media; using Avalonia.Media;
@ -8,6 +9,9 @@ using ChronoFlow.Security;
namespace ChronoFlow.View namespace ChronoFlow.View
{ {
/// <summary>
/// UI-Logik zum Hinzufügen neuer Benutzer durch einen Admin.
/// </summary>
public partial class MitarbeiterHinzufuegenView : UserControl public partial class MitarbeiterHinzufuegenView : UserControl
{ {
public MitarbeiterHinzufuegenView() public MitarbeiterHinzufuegenView()
@ -15,6 +19,9 @@ namespace ChronoFlow.View
InitializeComponent(); InitializeComponent();
} }
/// <summary>
/// Speichert den eingegebenen Benutzer, wenn alle Pflichtfelder ausgefüllt und der Benutzername noch nicht vergeben ist.
/// </summary>
private void SpeichernButton_Click(object? sender, RoutedEventArgs e) private void SpeichernButton_Click(object? sender, RoutedEventArgs e)
{ {
try try
@ -27,6 +34,7 @@ namespace ChronoFlow.View
string mitarbeiternummer = MitarbeiternummerBox.Text?.Trim() ?? ""; string mitarbeiternummer = MitarbeiternummerBox.Text?.Trim() ?? "";
string abteilung = AbteilungBox.Text?.Trim() ?? ""; string abteilung = AbteilungBox.Text?.Trim() ?? "";
// Validierung: Pflichtfelder
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password) || string.IsNullOrWhiteSpace(rolle)) if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password) || string.IsNullOrWhiteSpace(rolle))
{ {
FeedbackText.Text = "⚠ Bitte alle Pflichtfelder ausfüllen!"; FeedbackText.Text = "⚠ Bitte alle Pflichtfelder ausfüllen!";
@ -35,6 +43,16 @@ namespace ChronoFlow.View
return; return;
} }
// Verhindern von doppelten Benutzernamen (Case-Insensitive)
if (service.LadeAlleBenutzer().Any(u => u.Username.Equals(username, StringComparison.OrdinalIgnoreCase)))
{
FeedbackText.Text = "❌ Dieser Benutzername ist bereits vergeben.";
FeedbackText.Foreground = Brushes.Red;
FeedbackText.IsVisible = true;
return;
}
// Neuen Benutzer erstellen
var neuerBenutzer = new User var neuerBenutzer = new User
{ {
Username = username, Username = username,
@ -45,6 +63,7 @@ namespace ChronoFlow.View
MussPasswortAendern = true MussPasswortAendern = true
}; };
// Speichern in Datenbank
service.ErstelleNeuenBenutzer(neuerBenutzer); service.ErstelleNeuenBenutzer(neuerBenutzer);
FeedbackText.Text = "✅ Mitarbeiter erfolgreich gespeichert."; FeedbackText.Text = "✅ Mitarbeiter erfolgreich gespeichert.";
@ -58,12 +77,13 @@ namespace ChronoFlow.View
FeedbackText.Text = $"❌ Fehler: {ex.Message}"; FeedbackText.Text = $"❌ Fehler: {ex.Message}";
FeedbackText.Foreground = Brushes.Red; FeedbackText.Foreground = Brushes.Red;
FeedbackText.IsVisible = true; FeedbackText.IsVisible = true;
Console.WriteLine("❌ Ausnahme beim Speichern:\n" + ex);
Console.WriteLine("❌ Ausnahme beim Speichern:");
Console.WriteLine(ex.ToString());
} }
} }
/// <summary>
/// Setzt alle Eingabefelder zurück.
/// </summary>
private void ClearFields() private void ClearFields()
{ {
UsernameBox.Text = ""; UsernameBox.Text = "";
@ -73,17 +93,50 @@ namespace ChronoFlow.View
RoleBox.SelectedIndex = -1; RoleBox.SelectedIndex = -1;
} }
/// <summary>
/// Wechselt zurück zum Admin-Hauptfenster.
/// </summary>
private void ZurueckZumDashboard_Click(object? sender, RoutedEventArgs e) private void ZurueckZumDashboard_Click(object? sender, RoutedEventArgs e)
{ {
var mainWindow = this.VisualRoot as MainWindow; if (this.VisualRoot is MainWindow mainWindow)
if (mainWindow != null)
{
mainWindow.ShowAdminDashboard(); mainWindow.ShowAdminDashboard();
}
else else
{
Console.WriteLine("⚠️ MainWindow nicht gefunden!"); Console.WriteLine("⚠️ MainWindow nicht gefunden!");
}
/// <summary>
/// Erstellt einen zufälligen Test-Mitarbeiter mit dem Passwort "newpassword" (gehasht).
/// </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)];
var service = new SqliteZeiterfassungsService();
// Prüfen, ob Name bereits vorhanden ist
if (service.LadeAlleBenutzer().Any(u => u.Username.Equals(zufallsname, StringComparison.OrdinalIgnoreCase)))
{
FeedbackText.Text = "⚠ Demo-Mitarbeiter existiert bereits.";
FeedbackText.Foreground = Brushes.OrangeRed;
FeedbackText.IsVisible = true;
return;
} }
// Demo-User erstellen
var demoUser = new User
{
Username = zufallsname,
Password = PasswordHasher.HashPassword("newpassword"),
Role = "Mitarbeiter",
MussPasswortAendern = true
};
service.ErstelleNeuenBenutzer(demoUser);
FeedbackText.Text = $"✅ Demo-Mitarbeiter '{zufallsname}' erstellt (Passwort: newpassword)";
FeedbackText.Foreground = Brushes.Green;
FeedbackText.IsVisible = true;
} }
} }
} }

View File

@ -4,18 +4,41 @@ using ChronoFlow.Model;
namespace ChronoFlow.View.Security; namespace ChronoFlow.View.Security;
/// <summary>
/// Dialog zum Ändern des Passworts eines Benutzers.
/// </summary>
public partial class PasswortAendernDialog : Window public partial class PasswortAendernDialog : Window
{ {
/// <summary>
/// Das neue Passwort, das im Dialog eingegeben wurde.
/// </summary>
public string NeuesPasswort { get; private set; } = ""; public string NeuesPasswort { get; private set; } = "";
private readonly User _user; private readonly User _user;
/// <summary>
/// Parameterloser Konstruktor für Avalonia (z.B. für Preview, AVLN:0005-Fix).
/// ⚠ Wird nur für Entwicklungszwecke oder Design-Time genutzt.
/// </summary>
public PasswortAendernDialog() : this(new User { Username = "DemoUser", Role = "Mitarbeiter" })
{
System.Console.WriteLine("⚠ Parameterloser Konstruktor für PasswortAendernDialog verwendet.");
}
/// <summary>
/// Konstruktor mit Benutzerobjekt (wird im echten Ablauf verwendet).
/// </summary>
/// <param name="user">Der aktuell eingeloggte Benutzer.</param>
public PasswortAendernDialog(User user) public PasswortAendernDialog(User user)
{ {
InitializeComponent(); InitializeComponent();
_user = user; _user = user;
UsernameTextBlock.Text = $"Benutzer: {_user.Username}";
} }
/// <summary>
/// Wird aufgerufen, wenn der Benutzer auf "Speichern" klickt.
/// Prüft Eingaben und schließt den Dialog mit dem neuen Passwort.
/// </summary>
private void SpeichernButton_Click(object? sender, RoutedEventArgs e) private void SpeichernButton_Click(object? sender, RoutedEventArgs e)
{ {
var passwort = NeuesPasswortBox.Text?.Trim(); var passwort = NeuesPasswortBox.Text?.Trim();
@ -39,8 +62,11 @@ public partial class PasswortAendernDialog : Window
Close(NeuesPasswort); Close(NeuesPasswort);
} }
/// <summary>
/// Bricht den Vorgang ab und schließt den Dialog ohne Ergebnis.
/// </summary>
private void AbbrechenButton_Click(object? sender, RoutedEventArgs e) private void AbbrechenButton_Click(object? sender, RoutedEventArgs e)
{ {
Close(null); Close(null);
} }
} }