diff --git a/ChronoFlow.Controller/ChronoFlow.Controller.csproj b/ChronoFlow.Controller/ChronoFlow.Controller.csproj
index fc966e6..f3d3af5 100644
--- a/ChronoFlow.Controller/ChronoFlow.Controller.csproj
+++ b/ChronoFlow.Controller/ChronoFlow.Controller.csproj
@@ -2,13 +2,14 @@
net9.0
+ Library
enable
enable
-
-
+
+
diff --git a/ChronoFlow.Model/User.cs b/ChronoFlow.Model/User.cs
index a2247f6..e42e874 100644
--- a/ChronoFlow.Model/User.cs
+++ b/ChronoFlow.Model/User.cs
@@ -1,16 +1,26 @@
namespace ChronoFlow.Model
{
-
- ///
- /// Repräsentiert einen Benutzer mit Benutzernamen, Passwort und Rolle.
- ///
-
public class User
{
public string Username { get; set; }
- public string Password { get; set; } //vorerst im Klartext, wird später geändert
- public string Role { get; set; } //"Admin" oder "Mitarbeiter"
- public string Mitarbeiternummer { get; set; } = "";
- public string Abteilung { get; set; } = "";
+ public string Password { get; set; }
+ public string Role { get; set; }
+ public string Mitarbeiternummer { get; set; }
+ public string Abteilung { get; set; }
+
+ public int Id { get; set; }
+ public string OriginalUsername { get; set; } // wichtig für Updates
+
+ public bool MussPasswortAendern { get; set; }
+
+ public User()
+ {
+ Username = "";
+ Password = "";
+ Role = "";
+ Mitarbeiternummer = "";
+ Abteilung = "";
+ OriginalUsername = "";
+ }
}
}
\ No newline at end of file
diff --git a/ChronoFlow.Model/Zeiteintrag.cs b/ChronoFlow.Model/Zeiteintrag.cs
index 891b441..f0cdbc6 100644
--- a/ChronoFlow.Model/Zeiteintrag.cs
+++ b/ChronoFlow.Model/Zeiteintrag.cs
@@ -1,26 +1,27 @@
-using System;
-
namespace ChronoFlow.Model
{
public class Zeiteintrag
{
+ public int Id { get; set; } // <<< NEU
+
public string Mitarbeiter { get; set; }
public DateTime Startzeit { get; set; }
public DateTime Endzeit { get; set; }
- public string? Projekt { get; set; }
- public string? Kommentar { get; set; }
-
- public TimeSpan Dauer => Endzeit - Startzeit;
-
- //Felder für Mitarbeiter-Rückmeldung
+ public string Projekt { get; set; }
+ public string Kommentar { get; set; }
public bool Erledigt { get; set; }
- public string? MitarbeiterKommentar { get; set; }
-
+ public string MitarbeiterKommentar { get; set; }
- public override string ToString()
+ public Zeiteintrag()
{
- return $"{Mitarbeiter} - {Startzeit:HH:mm} - {Endzeit:HH:mm} | {Projekt}";
+ Id = 0;
+ Mitarbeiter = "";
+ Startzeit = DateTime.Now;
+ Endzeit = DateTime.Now;
+ Projekt = "";
+ Kommentar = "";
+ Erledigt = false;
+ MitarbeiterKommentar = "";
}
-
}
-}
+}
\ No newline at end of file
diff --git a/ChronoFlow.Persistence/ChronoFlow.Persistence.csproj b/ChronoFlow.Persistence/ChronoFlow.Persistence.csproj
index 30dbf86..8e1dec1 100644
--- a/ChronoFlow.Persistence/ChronoFlow.Persistence.csproj
+++ b/ChronoFlow.Persistence/ChronoFlow.Persistence.csproj
@@ -7,11 +7,12 @@
-
+
-
+
+
diff --git a/ChronoFlow.Persistence/SecurityReferenceTest.cs b/ChronoFlow.Persistence/SecurityReferenceTest.cs
new file mode 100644
index 0000000..2578ef1
--- /dev/null
+++ b/ChronoFlow.Persistence/SecurityReferenceTest.cs
@@ -0,0 +1,19 @@
+using System;
+using ChronoFlow.Security;
+
+namespace ChronoFlow.Persistence
+{
+ public class SecurityReferenceTest
+ {
+ public static void TestSecurityReference()
+ {
+ string testPasswort = "Test123!";
+ string hashed = PasswordHasher.HashPassword(testPasswort);
+
+ Console.WriteLine($"✅ Hash erfolgreich erzeugt: {hashed}");
+
+ bool isValid = PasswordHasher.VerifyPassword(testPasswort, hashed);
+ Console.WriteLine($"✅ Überprüfung erfolgreich: {isValid}");
+ }
+ }
+}
\ No newline at end of file
diff --git a/ChronoFlow.Persistence/SqliteZeiterfassungsService.cs b/ChronoFlow.Persistence/SqliteZeiterfassungsService.cs
index c5147f4..8ba464e 100644
--- a/ChronoFlow.Persistence/SqliteZeiterfassungsService.cs
+++ b/ChronoFlow.Persistence/SqliteZeiterfassungsService.cs
@@ -1,21 +1,23 @@
using System;
using System.Collections.Generic;
using System.IO;
-using System.Security.Cryptography;
using Microsoft.Data.Sqlite;
using ChronoFlow.Model;
+using ChronoFlow.Security;
namespace ChronoFlow.Persistence
{
public class SqliteZeiterfassungsService
{
- private readonly string _dbPath = "chrono_data_v2.sb";
+ private readonly string _dbPath = "chrono_data.sb";
public SqliteZeiterfassungsService()
{
- // Prüfe, ob die DB existiert, sonst erstelle sie
if (!File.Exists(_dbPath))
ErstelleDatenbank();
+
+ // IMMER prüfen, auch nach neuem Erstellen
+ PrüfeUndErweitereDatenbank();
}
private void ErstelleDatenbank()
@@ -27,31 +29,210 @@ namespace ChronoFlow.Persistence
var cmd1 = connection.CreateCommand();
cmd1.CommandText = @"
- CREATE TABLE IF NOT EXISTS Zeiteintraege (
- Id INTEGER PRIMARY KEY AUTOINCREMENT,
- Mitarbeiter TEXT NOT NULL,
- Startzeit TEXT NOT NULL,
- Endzeit TEXT NOT NULL,
- Projekt TEXT,
- Kommentar TEXT,
- Erledigt INTEGER,
- MitarbeiterKommentar TEXT
- );";
+ CREATE TABLE IF NOT EXISTS Zeiteintraege (
+ Id INTEGER PRIMARY KEY AUTOINCREMENT,
+ Mitarbeiter TEXT NOT NULL,
+ Startzeit TEXT NOT NULL,
+ Endzeit TEXT NOT NULL,
+ Projekt TEXT,
+ Kommentar TEXT,
+ Erledigt INTEGER,
+ MitarbeiterKommentar TEXT
+ );";
cmd1.ExecuteNonQuery();
var cmd2 = connection.CreateCommand();
cmd2.CommandText = @"
- CREATE TABLE IF NOT EXISTS Benutzer (
- Id INTEGER PRIMARY KEY AUTOINCREMENT,
- Username TEXT NOT NULL,
- Password TEXT NOT NULL,
- Role TEXT NOT NULL,
- Mitarbeiternummer TEXT,
- Abteilung TEXT
- );";
+ CREATE TABLE IF NOT EXISTS Benutzer (
+ Id INTEGER PRIMARY KEY AUTOINCREMENT,
+ Username TEXT NOT NULL,
+ Password TEXT NOT NULL,
+ Role TEXT NOT NULL,
+ Mitarbeitennummer TEXT,
+ Abteilung TEXT,
+ MussPasswortAendern INTEGER DEFAULT 1
+ );";
cmd2.ExecuteNonQuery();
}
+
+ public void ErstelleNeuenBenutzer(User benutzer)
+ {
+ using var connection = new SqliteConnection($"Data Source={_dbPath}");
+ connection.Open();
+ var cmd = connection.CreateCommand();
+ cmd.CommandText = @"
+ INSERT INTO Benutzer (Username, Password, Role, Mitarbeitennummer, Abteilung, MussPasswortAendern)
+ VALUES ($Username, $Password, $Role, $Mitarbeiternummer, $Abteilung, $MussPasswortAendern);
+ ";
+
+ cmd.Parameters.AddWithValue("$Username", benutzer.Username);
+ cmd.Parameters.AddWithValue("$Password", benutzer.Password);
+ cmd.Parameters.AddWithValue("$Role", benutzer.Role);
+ cmd.Parameters.AddWithValue("$Mitarbeiternummer", benutzer.Mitarbeiternummer ?? "");
+ cmd.Parameters.AddWithValue("$Abteilung", benutzer.Abteilung ?? "");
+ cmd.Parameters.AddWithValue("$MussPasswortAendern", benutzer.MussPasswortAendern ? 1 : 0);
+
+ int rowsAffected = cmd.ExecuteNonQuery();
+ Console.WriteLine($"✅ Neuer Benutzer '{benutzer.Username}' wurde gespeichert (Rows affected: {rowsAffected}).");
+ }
+
+
+
+ private void PrüfeUndErweitereDatenbank()
+ {
+ using var connection = new SqliteConnection($"Data Source={_dbPath}");
+ connection.Open();
+
+ AddColumnIfMissing(connection, "Benutzer", "Mitarbeitennummer", "TEXT");
+ AddColumnIfMissing(connection, "Benutzer", "Abteilung", "TEXT");
+ AddColumnIfMissing(connection, "Benutzer", "MussPasswortAendern", "INTEGER DEFAULT 1");
+ }
+
+ private void AddColumnIfMissing(SqliteConnection connection, string tableName, string columnName, string columnType)
+ {
+ var checkCmd = connection.CreateCommand();
+ checkCmd.CommandText = $"PRAGMA table_info({tableName});";
+
+ using var reader = checkCmd.ExecuteReader();
+ bool columnExists = false;
+
+ while (reader.Read())
+ {
+ var existingColumnName = reader.GetString(1);
+ if (existingColumnName.Equals(columnName, StringComparison.OrdinalIgnoreCase))
+ {
+ columnExists = true;
+ break;
+ }
+ }
+
+ if (!columnExists)
+ {
+ var alterCmd = connection.CreateCommand();
+ alterCmd.CommandText = $"ALTER TABLE {tableName} ADD COLUMN {columnName} {columnType};";
+ alterCmd.ExecuteNonQuery();
+ Console.WriteLine($"✅ Spalte '{columnName}' in Tabelle '{tableName}' hinzugefügt.");
+ }
+ }
+
+ public void ErstelleStandardAdmin()
+ {
+ using var connection = new SqliteConnection($"Data Source={_dbPath}");
+ connection.Open();
+
+ var checkCmd = connection.CreateCommand();
+ checkCmd.CommandText = "SELECT COUNT(*) FROM Benutzer WHERE Username = 'admin';";
+ var count = Convert.ToInt32(checkCmd.ExecuteScalar());
+
+ if (count == 0)
+ {
+ var hashedPassword = PasswordHasher.HashPassword("admin");
+ var insertCmd = connection.CreateCommand();
+ insertCmd.CommandText = @"
+ INSERT INTO Benutzer (Username, Password, Role, Mitarbeitennummer, Abteilung, MussPasswortAendern)
+ VALUES ('admin', $Password, 'Admin', '0001', 'IT', 1);
+ ";
+ insertCmd.Parameters.AddWithValue("$Password", hashedPassword);
+ insertCmd.ExecuteNonQuery();
+
+ Console.WriteLine("✅ Standard-Admin erfolgreich eingefügt.");
+ }
+ else
+ {
+ Console.WriteLine("ℹ Standard-Admin existiert bereits – kein neuer Eintrag erstellt.");
+ }
+ }
+
+ public List LadeAlleBenutzer()
+ {
+ var benutzerListe = new List();
+
+ using var connection = new SqliteConnection($"Data Source={_dbPath}");
+ connection.Open();
+
+ var cmd = connection.CreateCommand();
+ cmd.CommandText = "SELECT Id, Username, Password, Role, Mitarbeitennummer, Abteilung, MussPasswortAendern FROM Benutzer;";
+
+ using var reader = cmd.ExecuteReader();
+ while (reader.Read())
+ {
+ benutzerListe.Add(new User
+ {
+ Id = reader.GetInt32(0),
+ Username = reader.GetString(1),
+ Password = reader.GetString(2),
+ Role = reader.GetString(3),
+ Mitarbeiternummer = reader.IsDBNull(4) ? "" : reader.GetString(4),
+ Abteilung = reader.IsDBNull(5) ? "" : reader.GetString(5),
+ MussPasswortAendern = reader.GetInt32(6) == 1,
+ OriginalUsername = reader.GetString(1)
+ });
+ }
+
+ return benutzerListe;
+ }
+
+ public void UpdateBenutzer(User benutzer)
+ {
+ using var connection = new SqliteConnection($"Data Source={_dbPath}");
+ connection.Open();
+
+ var cmd = connection.CreateCommand();
+ cmd.CommandText = @"
+ UPDATE Benutzer
+ SET Username = $NewUsername,
+ Password = $Password,
+ Role = $Role,
+ Mitarbeitennummer = $Mitarbeiternummer,
+ Abteilung = $Abteilung,
+ MussPasswortAendern = $MussPasswortAendern
+ WHERE Username = $OriginalUsername;
+ ";
+
+ cmd.Parameters.AddWithValue("$NewUsername", benutzer.Username);
+ cmd.Parameters.AddWithValue("$Password", benutzer.Password);
+ cmd.Parameters.AddWithValue("$Role", benutzer.Role);
+ cmd.Parameters.AddWithValue("$Mitarbeiternummer", benutzer.Mitarbeiternummer ?? "");
+ cmd.Parameters.AddWithValue("$Abteilung", benutzer.Abteilung ?? "");
+ cmd.Parameters.AddWithValue("$MussPasswortAendern", benutzer.MussPasswortAendern ? 1 : 0);
+ cmd.Parameters.AddWithValue("$OriginalUsername", benutzer.OriginalUsername);
+
+ int rowsAffected = cmd.ExecuteNonQuery();
+ Console.WriteLine($"✏ Benutzer aktualisiert: {benutzer.Username} (Rows affected: {rowsAffected})");
+ }
+
+ public void LoescheBenutzer(string username)
+ {
+ using var connection = new SqliteConnection($"Data Source={_dbPath}");
+ connection.Open();
+
+ var cmd = connection.CreateCommand();
+ cmd.CommandText = "DELETE FROM Benutzer WHERE Username = $Username;";
+ cmd.Parameters.AddWithValue("$Username", username);
+
+ int rowsAffected = cmd.ExecuteNonQuery();
+ Console.WriteLine($"❌ Benutzer gelöscht: {username} (Rows affected: {rowsAffected})");
+ }
+
+ public List LadeAlleMitarbeiterNamen()
+ {
+ var namen = new List();
+
+ using var connection = new SqliteConnection($"Data Source={_dbPath}");
+ connection.Open();
+
+ var cmd = connection.CreateCommand();
+ cmd.CommandText = "SELECT Username FROM Benutzer WHERE Role = 'Mitarbeiter';";
+
+ using var reader = cmd.ExecuteReader();
+ while (reader.Read())
+ {
+ namen.Add(reader.GetString(0));
+ }
+
+ return namen;
+ }
public void SpeichereEintrag(Zeiteintrag eintrag)
{
@@ -74,6 +255,8 @@ namespace ChronoFlow.Persistence
cmd.Parameters.AddWithValue("$MitarbeiterKommentar", eintrag.MitarbeiterKommentar ?? "");
cmd.ExecuteNonQuery();
+
+ Console.WriteLine($"✅ Zeiteintrag für {eintrag.Mitarbeiter} gespeichert.");
}
public List LadeAlleZeiteintraege()
@@ -91,6 +274,7 @@ namespace ChronoFlow.Persistence
{
eintraege.Add(new Zeiteintrag
{
+ Id = reader.GetInt32(0),
Mitarbeiter = reader.GetString(1),
Startzeit = DateTime.Parse(reader.GetString(2)),
Endzeit = DateTime.Parse(reader.GetString(3)),
@@ -100,85 +284,179 @@ namespace ChronoFlow.Persistence
MitarbeiterKommentar = reader.GetString(7)
});
}
-
+
return eintraege;
}
- public List LadeAlleMitarbeiterNamen()
- {
- var namen = new List();
-
- using var connection = new SqliteConnection($"Data Source={_dbPath}");
- connection.Open();
-
- var cmd = connection.CreateCommand();
- cmd.CommandText = "SELECT Username From Benutzer Where Role = 'Mitarbeiter';";
-
- using var reader = cmd.ExecuteReader();
- while (reader.Read())
- {
- namen.Add(reader.GetString(0));
- }
- return namen;
- }
- public List LadeAlleBenutzer()
- {
- var benutzerListe = new List();
- using var connection = new SqliteConnection($"Data Source={_dbPath}");
- connection.Open();
-
- var cmd = connection.CreateCommand();
- cmd.CommandText = "SELECT Username, Password, Role, Mitarbeiternummer, Abteilung FROM Benutzer;";
-
- using var reader = cmd.ExecuteReader();
- while (reader.Read())
- {
- benutzerListe.Add(new User
- {
- Username = reader.GetString(0),
- Password = reader.GetString(1),
- Role = reader.GetString(2),
- Mitarbeiternummer = reader.IsDBNull(3) ? "" : reader.GetString(3),
- Abteilung = reader.IsDBNull(4) ? "" : reader.GetString(4)
- });
- }
-
- return benutzerListe;
- }
-
- public void ErstelleStandardAdmin()
+ public void UpdateProjekt(Zeiteintrag projekt)
{
using var connection = new SqliteConnection($"Data Source={_dbPath}");
connection.Open();
var cmd = connection.CreateCommand();
cmd.CommandText = @"
- INSERT INTO Benutzer (Username, Password, Role, Mitarbeiternummer, Abteilung)
- VALUES ('admin', 'admin', 'Admin', '0001', 'IT');
- ";
+ UPDATE Zeiteintraege
+ SET Projekt = $Projekt,
+ Kommentar = $Kommentar,
+ Startzeit = $Startzeit,
+ Endzeit = $Endzeit,
+ Mitarbeiter = $Mitarbeiter,
+ Erledigt = $Erledigt
+ WHERE Id = $Id;
+ ";
- cmd.ExecuteNonQuery();
-
- Console.WriteLine("✅ Standard-Admin erfolgreich eingefügt.");
+ cmd.Parameters.AddWithValue("$Projekt", projekt.Projekt);
+ cmd.Parameters.AddWithValue("$Kommentar", projekt.Kommentar ?? "");
+ cmd.Parameters.AddWithValue("$Startzeit", projekt.Startzeit.ToString("o"));
+ cmd.Parameters.AddWithValue("$Endzeit", projekt.Endzeit.ToString("o"));
+ cmd.Parameters.AddWithValue("$Mitarbeiter", projekt.Mitarbeiter);
+ cmd.Parameters.AddWithValue("$Erledigt", projekt.Erledigt ? 1 : 0);
+ cmd.Parameters.AddWithValue("$Id", projekt.Id);
+
+ int rowsAffected = cmd.ExecuteNonQuery();
+ Console.WriteLine($"✏ Projekt aktualisiert (Id={projekt.Id}, Rows affected: {rowsAffected})");
}
-
- public List LadeAlleBenutzernamen()
- {
- var benutzernamen = new List();
+ public void LoescheProjekt(int id)
+ {
using var connection = new SqliteConnection($"Data Source={_dbPath}");
connection.Open();
-
- using var cmd = connection.CreateCommand();//<--- Wir erstellen cmd
- cmd.CommandText = "SELECT Username From Benutzer;";
-
+
+ var cmd = connection.CreateCommand();
+ cmd.CommandText = "DELETE FROM Zeiteintraege WHERE Id = $Id;";
+ cmd.Parameters.AddWithValue("$Id", id);
+
+ int rows = cmd.ExecuteNonQuery();
+ Console.WriteLine($"❌ Projekt gelöscht (Id: {id}, Rows: {rows})");
+ }
+
+ public void UpdateProjektStatus(int id, bool erledigt)
+ {
+ using var connection = new SqliteConnection($"Data Source={_dbPath}");
+ connection.Open();
+
+ var cmd = connection.CreateCommand();
+ cmd.CommandText = @"
+ UPDATE Zeiteintraege
+ SET Erledigt = $Erledigt
+ WHERE Id = $Id;
+ ";
+
+ cmd.Parameters.AddWithValue("$Erledigt", erledigt ? 1 : 0);
+ cmd.Parameters.AddWithValue("$Id", id);
+
+ int rowsAffected = cmd.ExecuteNonQuery();
+ Console.WriteLine($"✅ Projektstatus aktualisiert (Id={id}, Erledigt={erledigt}, Rows affected: {rowsAffected})");
+ }
+
+ public List LadeAbgeschlosseneProjekte()
+ {
+ var abgeschlossene = new List();
+ using var connection = new SqliteConnection($"Data Source={_dbPath}");
+ connection.Open();
+
+ var cmd = connection.CreateCommand();
+ cmd.CommandText = "SELECT * FROM Zeiteintraege WHERE Erledigt = 1;";
+
using var reader = cmd.ExecuteReader();
while (reader.Read())
{
- benutzernamen.Add(reader.GetString(0));
+ abgeschlossene.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 = Convert.ToInt32(reader["Erledigt"]) == 1,
+ MitarbeiterKommentar = reader.GetString(7)
+ });
}
- return benutzernamen;
+
+ return abgeschlossene;
+ }
+
+ public List LadeLetzteProjekte(int anzahl = 3)
+ {
+ var projekte = new List();
+ using var connection = new SqliteConnection($"Data Source={_dbPath}");
+ connection.Open();
+
+ var cmd = connection.CreateCommand();
+ cmd.CommandText = @"
+ SELECT * FROM Zeiteintraege
+ ORDER BY Id DESC
+ LIMIT $Anzahl;
+ ";
+ cmd.Parameters.AddWithValue("$Anzahl", anzahl);
+
+ using var reader = cmd.ExecuteReader();
+ while (reader.Read())
+ {
+ projekte.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 = Convert.ToInt32(reader["Erledigt"]) == 1,
+ MitarbeiterKommentar = reader.GetString(7)
+ });
+ }
+
+ return projekte;
+ }
+
+ public List LadeOffeneProjekte()
+ {
+ var offene = new List();
+ using var connection = new SqliteConnection($"Data Source={_dbPath}");
+ connection.Open();
+
+ var cmd = connection.CreateCommand();
+ cmd.CommandText = "SELECT * FROM Zeiteintraege WHERE Erledigt = 0;";
+
+ using var reader = cmd.ExecuteReader();
+ while (reader.Read())
+ {
+ offene.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 = Convert.ToInt32(reader["Erledigt"]) == 1,
+ MitarbeiterKommentar = reader.GetString(7)
+ });
+ }
+
+ return offene;
+ }
+
+ public void ResetBenutzerPasswort(string username)
+ {
+ using var connection = new SqliteConnection($"Data Source={_dbPath}");
+ connection.Open();
+
+ var hashedDefault = PasswordHasher.HashPassword("changeme");
+ var cmd = connection.CreateCommand();
+ cmd.CommandText = @"
+ UPDATE Benutzer
+ SET Password = $Password,
+ MussPasswortAendern = 1
+ WHERE Username = $Username;
+ ";
+
+ cmd.Parameters.AddWithValue("$Password", hashedDefault);
+ cmd.Parameters.AddWithValue("$Username", username);
+
+ int rowsAffected = cmd.ExecuteNonQuery();
+ Console.WriteLine($"🔒 Passwort für Benutzer '{username}' zurückgesetzt (Rows affected: {rowsAffected})");
}
-
}
}
diff --git a/ChronoFlow.Security/ChronoFlow.Security.csproj b/ChronoFlow.Security/ChronoFlow.Security.csproj
new file mode 100644
index 0000000..17b910f
--- /dev/null
+++ b/ChronoFlow.Security/ChronoFlow.Security.csproj
@@ -0,0 +1,9 @@
+
+
+
+ net9.0
+ enable
+ enable
+
+
+
diff --git a/ChronoFlow.Security/PasswordHasher.cs b/ChronoFlow.Security/PasswordHasher.cs
new file mode 100644
index 0000000..c95c757
--- /dev/null
+++ b/ChronoFlow.Security/PasswordHasher.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace ChronoFlow.Security
+{
+ ///
+ /// Diese Klasse bietet Funktionen zum sicheren Hashen und Überprüfen von Passwörtern.
+ ///
+ public static class PasswordHasher
+ {
+ ///
+ /// Erstellt einen sicheren Hash aus einem Passwort unter Verwendung von PBKDF2.
+ ///
+ /// Das Klartextpasswort
+ /// Ein kombinierter Hash-String (Salt + Hash)
+ public static string HashPassword(string password)
+ {
+ using var rng = RandomNumberGenerator.Create();
+ byte[] salt = new byte[16];
+ rng.GetBytes(salt);
+
+ var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 100_000, HashAlgorithmName.SHA256);
+ byte[] hash = pbkdf2.GetBytes(32);
+
+ // Kombiniere Salt + Hash in einen String (Base64-encodiert)
+ byte[] hashBytes = new byte[48];
+ Array.Copy(salt, 0, hashBytes, 0, 16);
+ Array.Copy(hash, 0, hashBytes, 16, 32);
+
+ return Convert.ToBase64String(hashBytes);
+ }
+
+ ///
+ /// Überprüft, ob ein Passwort zu einem gegebenen Hash passt.
+ ///
+ /// Das eingegebene Klartextpasswort
+ /// Der gespeicherte kombinierte Hash (Base64, Salt + Hash)
+ /// True, wenn das Passwort stimmt, sonst false
+ public static bool VerifyPassword(string password, string storedHash)
+ {
+ try
+ {
+ byte[] hashBytes = Convert.FromBase64String(storedHash);
+
+ byte[] salt = new byte[16];
+ Array.Copy(hashBytes, 0, salt, 0, 16);
+
+ var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 100_000, HashAlgorithmName.SHA256);
+ byte[] hash = pbkdf2.GetBytes(32);
+
+ for (int i = 0; i < 32; i++)
+ {
+ if (hashBytes[i + 16] != hash[i])
+ return false;
+ }
+
+ return true;
+ }
+ catch
+ {
+ // Falls der gespeicherte Hash beschädigt oder kein Base64 ist
+ return false;
+ }
+ }
+ }
+}
diff --git a/ChronoFlow.View/Admin/AbgeschlosseneProjekteView.axaml b/ChronoFlow.View/Admin/AbgeschlosseneProjekteView.axaml
new file mode 100644
index 0000000..3d9ea3e
--- /dev/null
+++ b/ChronoFlow.View/Admin/AbgeschlosseneProjekteView.axaml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ChronoFlow.View/Admin/AbgeschlosseneProjekteView.axaml.cs b/ChronoFlow.View/Admin/AbgeschlosseneProjekteView.axaml.cs
new file mode 100644
index 0000000..f896b7a
--- /dev/null
+++ b/ChronoFlow.View/Admin/AbgeschlosseneProjekteView.axaml.cs
@@ -0,0 +1,49 @@
+using System.Collections.ObjectModel;
+using System.Linq;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using ChronoFlow.Model;
+using ChronoFlow.Persistence;
+
+namespace ChronoFlow.View.Admin;
+
+public partial class AbgeschlosseneProjekteView : UserControl
+{
+ private readonly ViewManager _viewManager;
+ private readonly ObservableCollection _abgeschlosseneProjekte = new();
+ private readonly SqliteZeiterfassungsService _dbService = new();
+
+ public AbgeschlosseneProjekteView(ViewManager viewManager)
+ {
+ InitializeComponent();
+ _viewManager = viewManager;
+ LadeAbgeschlosseneProjekte();
+ }
+
+ private void LadeAbgeschlosseneProjekte()
+ {
+ _abgeschlosseneProjekte.Clear();
+
+ var ausDb = _dbService.LadeAbgeschlosseneProjekte();
+
+ foreach (var eintrag in ausDb)
+ _abgeschlosseneProjekte.Add(eintrag);
+
+ AbgeschlosseneListe.ItemsSource = _abgeschlosseneProjekte;
+ }
+
+ private void ZurueckButton_Click(object? sender, RoutedEventArgs e)
+ {
+ _viewManager.Show("AdminMain");
+ }
+ private void Suchfeld_KeyUp(object? sender, KeyEventArgs e)
+ {
+ var text = Suchfeld?.Text?.ToLower() ?? "";
+ AbgeschlosseneListe.ItemsSource = _abgeschlosseneProjekte
+ .Where(p => (p.Projekt?.ToLower().Contains(text) ?? false) ||
+ (p.Mitarbeiter?.ToLower().Contains(text) ?? false))
+ .ToList();
+ }
+
+}
\ No newline at end of file
diff --git a/ChronoFlow.View/Admin/AdminMainView.axaml b/ChronoFlow.View/Admin/AdminMainView.axaml
new file mode 100644
index 0000000..c58b125
--- /dev/null
+++ b/ChronoFlow.View/Admin/AdminMainView.axaml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ChronoFlow.View/Admin/AdminMainView.axaml.cs b/ChronoFlow.View/Admin/AdminMainView.axaml.cs
new file mode 100644
index 0000000..f0db60c
--- /dev/null
+++ b/ChronoFlow.View/Admin/AdminMainView.axaml.cs
@@ -0,0 +1,106 @@
+using System;
+using System.Collections.ObjectModel;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using ChronoFlow.Model;
+using ChronoFlow.Persistence;
+
+namespace ChronoFlow.View.Admin
+{
+ public partial class AdminMainView : UserControl
+ {
+ private readonly ViewManager _viewManager;
+ private readonly ObservableCollection _letzteProjekte = new();
+
+ public AdminMainView() : this(new ViewManager(new ContentControl()))
+ {
+ Console.WriteLine("⚠ Achtung: Parameterloser Konstruktor genutzt (nur Standard-ViewManager).");
+ }
+
+ public AdminMainView(ViewManager viewManager)
+ {
+ InitializeComponent();
+ _viewManager = viewManager;
+
+ Console.WriteLine("✅ AdminMainView wird initialisiert.");
+ LadeLetzteProjekte();
+ }
+
+ private void LadeLetzteProjekte()
+ {
+ Console.WriteLine("🔄 Lade letzte Projekte...");
+
+ try
+ {
+ var dbService = new SqliteZeiterfassungsService();
+ var letzteAusDb = dbService.LadeLetzteProjekte(3);
+
+ _letzteProjekte.Clear();
+ foreach (var eintrag in letzteAusDb)
+ {
+ Console.WriteLine($"✅ Projekt geladen: {eintrag.Projekt} ({eintrag.Id})");
+ _letzteProjekte.Add(eintrag);
+ }
+
+ if (LetzteProjekteListe != null)
+ {
+ LetzteProjekteListe.ItemsSource = _letzteProjekte;
+ Console.WriteLine("✅ LetzteProjekteListe erfolgreich gebunden.");
+ }
+ else
+ {
+ Console.WriteLine("⚠ Warnung: LetzteProjekteListe ist null. Prüfe XAML-Bindung!");
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[ERROR] Fehler beim Laden der letzten Projekte: {ex.Message}");
+ }
+ }
+
+ private void MitarbeiterHinzufuegen_Click(object sender, RoutedEventArgs e)
+ {
+ _viewManager.Show("MitarbeiterHinzufuegen");
+ }
+
+ private void ProjektErstellen_Click(object sender, RoutedEventArgs e)
+ {
+ _viewManager.Show("ProjektErstellen");
+ }
+
+ private void Dashboard_Click(object sender, RoutedEventArgs e)
+ {
+ _viewManager.Show("AdminMain");
+ }
+
+ private void AlleProjekte_Click(object sender, RoutedEventArgs e)
+ {
+ _viewManager.Show("AlleProjekte");
+ }
+
+ private void MitarbeiterListe_Click(object sender, RoutedEventArgs e)
+ {
+ _viewManager.Show("MitarbeiterListe");
+ }
+
+ private void Einstellungen_Click(object sender, RoutedEventArgs e)
+ {
+ _viewManager.Show("Einstellungen");
+ }
+
+ private void TogglePane_Click(object sender, RoutedEventArgs e)
+ {
+ AdminPane.IsPaneOpen = !AdminPane.IsPaneOpen;
+ }
+
+ public void AktualisiereLetzteProjekte()
+ {
+ LadeLetzteProjekte();
+ }
+
+ private void AbgeschlosseneProjekte_Click(object? sender, RoutedEventArgs e)
+ {
+ _viewManager.Show("AbgeschlosseneProjekte");
+ }
+ }
+}
diff --git a/ChronoFlow.View/Admin/AlleProjekteView.axaml b/ChronoFlow.View/Admin/AlleProjekteView.axaml
new file mode 100644
index 0000000..eb3f9db
--- /dev/null
+++ b/ChronoFlow.View/Admin/AlleProjekteView.axaml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ChronoFlow.View/Admin/AlleProjekteView.axaml.cs b/ChronoFlow.View/Admin/AlleProjekteView.axaml.cs
new file mode 100644
index 0000000..9b2b682
--- /dev/null
+++ b/ChronoFlow.View/Admin/AlleProjekteView.axaml.cs
@@ -0,0 +1,89 @@
+using System;
+using System.Collections.ObjectModel;
+using System.Linq;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using ChronoFlow.Model;
+using ChronoFlow.Persistence;
+using Avalonia.Input;
+
+namespace ChronoFlow.View.Admin;
+
+public partial class AlleProjekteView : UserControl
+{
+ private readonly ViewManager _viewManager;
+ private readonly ObservableCollection _alleProjekte = new();
+ private readonly SqliteZeiterfassungsService _dbService = new();
+
+ public AlleProjekteView(ViewManager viewManager)
+ {
+ InitializeComponent();
+ _viewManager = viewManager;
+ LadeAlleProjekte();
+ }
+
+ private void LadeAlleProjekte()
+ {
+ _alleProjekte.Clear();
+
+ var ausDb = _dbService.LadeAlleZeiteintraege()
+ .Where(p => !p.Erledigt) // Nur nicht erledigte Projekte!
+ .ToList();
+
+ foreach (var eintrag in ausDb)
+ _alleProjekte.Add(eintrag);
+
+ ProjekteListe.ItemsSource = _alleProjekte;
+ }
+ private void Suchfeld_KeyUp(object? sender, KeyEventArgs e)
+ {
+ var text = Suchfeld?.Text?.ToLower() ?? "";
+ ProjekteListe.ItemsSource = _alleProjekte
+ .Where(p => (p.Projekt?.ToLower().Contains(text) ?? false) ||
+ (p.Mitarbeiter?.ToLower().Contains(text) ?? false))
+ .ToList();
+ }
+
+
+
+ private async void Bearbeiten_Click(object? sender, RoutedEventArgs e)
+ {
+ if (sender is Button button && button.Tag is Zeiteintrag projekt)
+ {
+ var dialog = new ProjektBearbeitenDialog(projekt);
+ var updatedProjekt = await dialog.ShowDialog((Window)this.VisualRoot!);
+
+ if (updatedProjekt != null)
+ {
+ _dbService.UpdateProjekt(updatedProjekt);
+ LadeAlleProjekte();
+ }
+ }
+ }
+
+ private void Loeschen_Click(object? sender, RoutedEventArgs e)
+ {
+ if (sender is Button button && button.Tag is Zeiteintrag projekt)
+ {
+ _dbService.LoescheProjekt(projekt.Id);
+ LadeAlleProjekte();
+ }
+ }
+
+ private void Abschliessen_Click(object? sender, RoutedEventArgs e)
+ {
+ if (sender is Button button && button.Tag is Zeiteintrag projekt)
+ {
+ projekt.Erledigt = true;
+ _dbService.UpdateProjekt(projekt);
+
+ Console.WriteLine($"✅ Projekt abgeschlossen: {projekt.Projekt}");
+ LadeAlleProjekte();
+ }
+ }
+
+ private void ZurueckButton_Click(object? sender, RoutedEventArgs e)
+ {
+ _viewManager.Show("AdminMain");
+ }
+}
diff --git a/ChronoFlow.View/Admin/ConfirmDialog.axaml b/ChronoFlow.View/Admin/ConfirmDialog.axaml
new file mode 100644
index 0000000..9238ae5
--- /dev/null
+++ b/ChronoFlow.View/Admin/ConfirmDialog.axaml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ChronoFlow.View/Admin/ConfirmDialog.axaml.cs b/ChronoFlow.View/Admin/ConfirmDialog.axaml.cs
new file mode 100644
index 0000000..38de08c
--- /dev/null
+++ b/ChronoFlow.View/Admin/ConfirmDialog.axaml.cs
@@ -0,0 +1,27 @@
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+
+namespace ChronoFlow.View.Admin;
+
+public partial class ConfirmDialog : Window
+{
+ public bool Result { get; private set; } = false;
+
+ public ConfirmDialog(string frage)
+ {
+ InitializeComponent();
+ FrageText.Text = frage;
+ }
+
+ private void JaButton_Click(object? sender, RoutedEventArgs e)
+ {
+ Result = true;
+ Close(Result);
+ }
+
+ private void NeinButton_Click(object? sender, RoutedEventArgs e)
+ {
+ Result = false;
+ Close(Result);
+ }
+}
\ No newline at end of file
diff --git a/ChronoFlow.View/Admin/MitarbeiterBearbeitenDialog.axaml b/ChronoFlow.View/Admin/MitarbeiterBearbeitenDialog.axaml
new file mode 100644
index 0000000..e8a2383
--- /dev/null
+++ b/ChronoFlow.View/Admin/MitarbeiterBearbeitenDialog.axaml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ChronoFlow.View/Admin/MitarbeiterBearbeitenDialog.axaml.cs b/ChronoFlow.View/Admin/MitarbeiterBearbeitenDialog.axaml.cs
new file mode 100644
index 0000000..4edd1bc
--- /dev/null
+++ b/ChronoFlow.View/Admin/MitarbeiterBearbeitenDialog.axaml.cs
@@ -0,0 +1,41 @@
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using ChronoFlow.Model;
+
+namespace ChronoFlow.View.Admin;
+
+public partial class MitarbeiterBearbeitenDialog : Window
+{
+ public User UpdatedUser { get; private set; }
+
+ public MitarbeiterBearbeitenDialog(User user)
+ {
+ InitializeComponent();
+
+ UpdatedUser = new User
+ {
+ Username = user.Username,
+ OriginalUsername = user.Username, // Speichern des alten Namens
+ Abteilung = user.Abteilung,
+ Mitarbeiternummer = user.Mitarbeiternummer
+ };
+
+ UsernameBox.Text = user.Username;
+ AbteilungBox.Text = user.Abteilung;
+ MitarbeiternummerBox.Text = user.Mitarbeiternummer;
+ }
+
+ private void SpeichernButton_Click(object? sender, RoutedEventArgs e)
+ {
+ UpdatedUser.Username = UsernameBox.Text ?? UpdatedUser.Username;
+ UpdatedUser.Abteilung = AbteilungBox.Text ?? UpdatedUser.Abteilung;
+ UpdatedUser.Mitarbeiternummer = MitarbeiternummerBox.Text ?? UpdatedUser.Mitarbeiternummer;
+
+ this.Close(UpdatedUser);
+ }
+
+ private void AbbrechenButton_Click(object? sender, RoutedEventArgs e)
+ {
+ this.Close(null);
+ }
+}
\ No newline at end of file
diff --git a/ChronoFlow.View/Admin/MitarbeiterListeView.axaml b/ChronoFlow.View/Admin/MitarbeiterListeView.axaml
new file mode 100644
index 0000000..33c0e61
--- /dev/null
+++ b/ChronoFlow.View/Admin/MitarbeiterListeView.axaml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ChronoFlow.View/Admin/MitarbeiterListeView.axaml.cs b/ChronoFlow.View/Admin/MitarbeiterListeView.axaml.cs
new file mode 100644
index 0000000..f8aab25
--- /dev/null
+++ b/ChronoFlow.View/Admin/MitarbeiterListeView.axaml.cs
@@ -0,0 +1,143 @@
+using System;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Threading.Tasks;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using ChronoFlow.Model;
+using ChronoFlow.Persistence;
+using MessageBox.Avalonia;
+using MessageBox.Avalonia.DTO;
+using MessageBox.Avalonia.Enums;
+
+namespace ChronoFlow.View.Admin;
+
+public partial class MitarbeiterListeView : UserControl
+{
+ private readonly ViewManager _viewManager;
+ private readonly ObservableCollection _alleMitarbeiter = new();
+ private readonly SqliteZeiterfassungsService _dbService = new();
+
+ public MitarbeiterListeView(ViewManager viewManager)
+ {
+ InitializeComponent();
+ _viewManager = viewManager;
+ LadeMitarbeiter();
+ }
+
+ private void LadeMitarbeiter()
+ {
+ try
+ {
+ var mitarbeiter = _dbService.LadeAlleBenutzer()
+ .Where(m => m.Role == "Mitarbeiter")
+ .OrderBy(m => m.Username)
+ .ToList();
+
+ _alleMitarbeiter.Clear();
+ foreach (var user in mitarbeiter)
+ _alleMitarbeiter.Add(user);
+
+ MitarbeiterListe.ItemsSource = _alleMitarbeiter;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"❌ Fehler beim Laden der Mitarbeiter: {ex.Message}");
+ }
+ }
+
+ private void Suchfeld_KeyUp(object? sender, KeyEventArgs e)
+ {
+ var text = Suchfeld?.Text?.ToLower() ?? "";
+ MitarbeiterListe.ItemsSource = _alleMitarbeiter
+ .Where(m => m.Username.ToLower().Contains(text))
+ .ToList();
+ }
+
+ private async void Bearbeiten_Click(object? sender, RoutedEventArgs e)
+ {
+ if (sender is Button button && button.Tag is User benutzer)
+ {
+ var dialog = new MitarbeiterBearbeitenDialog(benutzer);
+ var updatedUser = await dialog.ShowDialog((Window)this.VisualRoot!);
+
+ if (updatedUser != null)
+ {
+ updatedUser.Password = benutzer.Password;
+ updatedUser.Role = benutzer.Role;
+ updatedUser.OriginalUsername = benutzer.Username; // ← WICHTIG!
+
+ _dbService.UpdateBenutzer(updatedUser);
+ LadeMitarbeiter();
+ }
+ }
+ }
+
+
+
+
+ private async void Loeschen_Click(object? sender, RoutedEventArgs e)
+ {
+ 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 result = await dialog.ShowDialog((Window)this.VisualRoot!);
+
+ if (result)
+ {
+ _dbService.LoescheBenutzer(benutzer.Username);
+ LadeMitarbeiter();
+ }
+ }
+ }
+
+
+ private void ZurueckButton_Click(object? sender, RoutedEventArgs e)
+ {
+ try
+ {
+ _viewManager.Show("AdminMain");
+ }
+ catch (Exception ex)
+ {
+ 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((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((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.");
+ }
+ }
+ }
+
+
+}
diff --git a/ChronoFlow.View/Admin/ProjektBearbeitenDialog.axaml b/ChronoFlow.View/Admin/ProjektBearbeitenDialog.axaml
new file mode 100644
index 0000000..2e912dd
--- /dev/null
+++ b/ChronoFlow.View/Admin/ProjektBearbeitenDialog.axaml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ChronoFlow.View/Admin/ProjektBearbeitenDialog.axaml.cs b/ChronoFlow.View/Admin/ProjektBearbeitenDialog.axaml.cs
new file mode 100644
index 0000000..5e7cfbe
--- /dev/null
+++ b/ChronoFlow.View/Admin/ProjektBearbeitenDialog.axaml.cs
@@ -0,0 +1,47 @@
+using System;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using ChronoFlow.Model;
+using ChronoFlow.Persistence;
+
+namespace ChronoFlow.View.Admin;
+
+public partial class ProjektBearbeitenDialog : Window
+{
+ public Zeiteintrag UpdatedProjekt { get; private set; }
+
+ public ProjektBearbeitenDialog(Zeiteintrag projekt)
+ {
+ InitializeComponent();
+
+ var dbService = new SqliteZeiterfassungsService();
+ var mitarbeiter = dbService.LadeAlleMitarbeiterNamen();
+ MitarbeiterDropdown.ItemsSource = mitarbeiter;
+ MitarbeiterDropdown.SelectedItem = projekt.Mitarbeiter;
+
+ // Vorbelegen
+ ProjektnameBox.Text = projekt.Projekt;
+ KommentarBox.Text = projekt.Kommentar;
+ StartzeitPicker.SelectedDate = new DateTimeOffset(projekt.Startzeit);
+ EndzeitPicker.SelectedDate = new DateTimeOffset(projekt.Endzeit);
+
+ UpdatedProjekt = projekt;
+ }
+
+ private void SpeichernButton_Click(object? sender, RoutedEventArgs e)
+ {
+ UpdatedProjekt.Projekt = ProjektnameBox.Text ?? "";
+ UpdatedProjekt.Kommentar = KommentarBox.Text ?? "";
+ UpdatedProjekt.Startzeit = (StartzeitPicker.SelectedDate ?? DateTimeOffset.Now).DateTime;
+ UpdatedProjekt.Endzeit = (EndzeitPicker.SelectedDate ?? DateTimeOffset.Now).DateTime;
+ UpdatedProjekt.Mitarbeiter = MitarbeiterDropdown.SelectedItem?.ToString() ?? "";
+
+ Close(UpdatedProjekt);
+ }
+
+ private void AbbrechenButton_Click(object? sender, RoutedEventArgs e)
+ {
+ Close(null);
+ }
+
+}
\ No newline at end of file
diff --git a/ChronoFlow.View/Admin/ProjektErstellenView.axaml b/ChronoFlow.View/Admin/ProjektErstellenView.axaml
new file mode 100644
index 0000000..07c870f
--- /dev/null
+++ b/ChronoFlow.View/Admin/ProjektErstellenView.axaml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ChronoFlow.View/Admin/ProjektErstellenView.axaml.cs b/ChronoFlow.View/Admin/ProjektErstellenView.axaml.cs
new file mode 100644
index 0000000..1e5a296
--- /dev/null
+++ b/ChronoFlow.View/Admin/ProjektErstellenView.axaml.cs
@@ -0,0 +1,102 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using Avalonia.Media;
+using ChronoFlow.Model;
+using ChronoFlow.Persistence;
+
+namespace ChronoFlow.View.Admin
+{
+ public partial class ProjektErstellenView : UserControl
+ {
+ private readonly ViewManager _viewManager;
+
+ public ProjektErstellenView(ViewManager viewManager)
+ {
+ InitializeComponent();
+ _viewManager = viewManager;
+
+ var dbService = new SqliteZeiterfassungsService();
+ List mitarbeiter = dbService.LadeAlleMitarbeiterNamen();
+
+// ✅ Nur ItemsSource verwenden, nicht Items
+ MitarbeiterDropdown.ItemsSource = mitarbeiter;
+ }
+
+ private void SpeichernButton_Click(object? sender, RoutedEventArgs e)
+ {
+ string projektname = ProjektnameBox.Text ?? "";
+ DateTime startdatum = StartdatumPicker.SelectedDate?.Date ?? DateTime.Today;
+ DateTime enddatum = EnddatumPicker.SelectedDate?.Date ?? DateTime.Today;
+ string startzeitText = StartzeitBox.Text ?? "00:00";
+ 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))
+ {
+ FeedbackText.Text = "⚠ Ungültige Startzeit (Format HH:mm)!";
+ FeedbackText.Foreground = Brushes.Red;
+ FeedbackText.IsVisible = true;
+ return;
+ }
+
+ if (!TimeSpan.TryParse(endzeitText, out var endzeit))
+ {
+ FeedbackText.Text = "⚠ Ungültige Endzeit (Format HH:mm)!";
+ FeedbackText.Foreground = Brushes.Red;
+ FeedbackText.IsVisible = true;
+ return;
+ }
+
+ DateTime startDateTime = startdatum + startzeit;
+ DateTime endDateTime = enddatum + endzeit;
+
+ var dbService = new SqliteZeiterfassungsService();
+
+ // 💡 Achtung: Aktuell speichern wir als Zeiteintrag (kein eigenes Projektmodell!)
+ dbService.SpeichereEintrag(new Zeiteintrag
+ {
+ Mitarbeiter = mitarbeiter,
+ Projekt = projektname,
+ Startzeit = startDateTime,
+ Endzeit = endDateTime,
+ Kommentar = kommentar,
+ 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("AdminMain", out var adminView))
+ {
+ adminView.AktualisiereLetzteProjekte();
+ }
+ }
+
+ private void ZurueckButton_Click(object? sender, RoutedEventArgs e)
+ {
+ _viewManager.Show("AdminMain");
+ }
+ }
+}
diff --git a/ChronoFlow.View/App.axaml b/ChronoFlow.View/App.axaml
index dafeae2..0988159 100644
--- a/ChronoFlow.View/App.axaml
+++ b/ChronoFlow.View/App.axaml
@@ -1,10 +1,9 @@
-
-
-
+
\ No newline at end of file
diff --git a/ChronoFlow.View/ChronoFlow.View.csproj b/ChronoFlow.View/ChronoFlow.View.csproj
index 1797e5c..d109a1d 100644
--- a/ChronoFlow.View/ChronoFlow.View.csproj
+++ b/ChronoFlow.View/ChronoFlow.View.csproj
@@ -1,37 +1,31 @@
+
- WinExe
net9.0
+ WinExe
+ enable
enable
- true
- app.manifest
- true
-
-
-
-
-
-
- None
- All
-
+
+
+
+
-
-
+
+
+
+
-
-
-
-
-
- ZeiterfassungView.axaml
+
+ MitarbeiterHinzufuegenView.axaml
+ Code
+
diff --git a/ChronoFlow.View/LoginWindow.axaml b/ChronoFlow.View/LoginWindow.axaml
index 15c7f6c..5a79cbe 100644
--- a/ChronoFlow.View/LoginWindow.axaml
+++ b/ChronoFlow.View/LoginWindow.axaml
@@ -1,18 +1,20 @@
-
+ Width="400" Height="300"
+ Title="ChronoFlow Login">
-
-
-
-
-
+
-
+
+
-
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ChronoFlow.View/LoginWindow.axaml.cs b/ChronoFlow.View/LoginWindow.axaml.cs
index 4520a55..93429c8 100644
--- a/ChronoFlow.View/LoginWindow.axaml.cs
+++ b/ChronoFlow.View/LoginWindow.axaml.cs
@@ -1,9 +1,11 @@
+using System;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Interactivity;
-using Avalonia.Metadata;
using ChronoFlow.Controller;
using ChronoFlow.Persistence;
+using ChronoFlow.Security;
+using ChronoFlow.View.Security;
namespace ChronoFlow.View
{
@@ -12,50 +14,111 @@ namespace ChronoFlow.View
///
public partial class LoginWindow : Window
{
- private LoginController _loginController;
+ private readonly LoginController _loginController;
public LoginWindow()
{
- InitializeComponent(); // Verbindet XAML mit diesem Code
- _loginController = new LoginController(); // Unsere "Logik-Klasse"
+ InitializeComponent();
+ _loginController = new LoginController();
+
var service = new SqliteZeiterfassungsService();
service.ErstelleStandardAdmin();
-
}
///
/// Wird ausgeführt, wenn der Benutzer auf "Anmelden" klickt.
///
- private void LoginButton_Click(object? sender, RoutedEventArgs e)
+ 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.Text = "Bitte Benutzername und Passwort eingeben.";
ErrorText.IsVisible = true;
return;
}
var service = new SqliteZeiterfassungsService();
var benutzerListe = service.LadeAlleBenutzer();
-
- var user = benutzerListe.FirstOrDefault(u => u.Username == username && u.Password == password);
- if (user != null)
+ var matchingUsers = benutzerListe.Where(u => u.Username == username).ToList();
+
+ if (matchingUsers.Count == 0)
{
- // Wenn erfolgreich: öffne das MainWindow
+ 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(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...");
var main = new MainWindow(user);
main.Show();
- this.Close(); // Schließe das Login-Fenster
+ Console.WriteLine("✅ MainWindow wurde geöffnet.");
+
+ this.Close();
+ Console.WriteLine("✅ LoginWindow wurde geschlossen.");
}
- else
+ catch (Exception ex)
{
- // Wenn fehlgeschlagen: Fehlermeldung anzeigen
- ErrorText.Text = "Login fehlgeschlagen. Bitte prüfen Sie Ihre Eingaben.";
+ Console.WriteLine($"[ERROR] MainWindow konnte nicht geöffnet werden: {ex.Message}");
+ ErrorText.Text = "Interner Fehler beim Starten des Hauptfensters.";
ErrorText.IsVisible = true;
}
}
}
-}
\ No newline at end of file
+}
diff --git a/ChronoFlow.View/MainWindow.axaml b/ChronoFlow.View/MainWindow.axaml
index 76ba89e..40b44ab 100644
--- a/ChronoFlow.View/MainWindow.axaml
+++ b/ChronoFlow.View/MainWindow.axaml
@@ -2,25 +2,12 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+ mc:Ignorable="d" d:DesignWidth="1000" d:DesignHeight="600"
x:Class="ChronoFlow.View.MainWindow"
- Title="ChronoFlow.View">
+ Title="ChronoFlow">
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
-
-
\ No newline at end of file
diff --git a/ChronoFlow.View/MainWindow.axaml.cs b/ChronoFlow.View/MainWindow.axaml.cs
index ea79175..89aed83 100644
--- a/ChronoFlow.View/MainWindow.axaml.cs
+++ b/ChronoFlow.View/MainWindow.axaml.cs
@@ -1,54 +1,63 @@
+using System;
using Avalonia.Controls;
using Avalonia.Interactivity;
using ChronoFlow.Model;
+using ChronoFlow.View.Admin;
-namespace ChronoFlow.View;
-
-public partial class MainWindow : Window
+namespace ChronoFlow.View
{
- private readonly ViewManager _viewManager;
- private readonly User _loggedInUser;
-
- public MainWindow(User user)
+ public partial class MainWindow : Window
{
- InitializeComponent();
+ private readonly ViewManager _viewManager;
+ private readonly User _loggedInUser;
- _loggedInUser = user;
-
- // ✅ Workaround: Lokale Kopie für Lambda-Nutzung im Register
- var currentUser = _loggedInUser;
-
- _viewManager = new ViewManager(ContentArea);
-
- // ✅ Register-Aufruf mit stabiler local variable
- _viewManager.Register("Zeiterfassung", () => new ZeiterfassungView(currentUser));
- _viewManager.Register("MitarbeiterHinzufuegen", () => new MitarbeiterHinzufuegenView());
-
- // Begrüßungsanzeige
- ContentArea.Content = new TextBlock
+ public MainWindow(User user)
{
- Text = $"Willkommen bei ChronoFlow, {currentUser.Username}!",
- FontSize = 24,
- HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center,
- VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
- };
+ InitializeComponent();
- // Fenstertitel dynamisch setzen
- this.Title = $"ChronoFlow - Willkommen {currentUser.Username} ({currentUser.Role})";
- }
+ _loggedInUser = user;
+ Console.WriteLine($"[DEBUG] MainWindow gestartet für Benutzer: {_loggedInUser.Username} ({_loggedInUser.Role})");
- private void PaneOpenClose_Click(object sender, RoutedEventArgs e)
- {
- PaneView.IsPaneOpen = !PaneView.IsPaneOpen;
- }
+ _viewManager = new ViewManager(ContentArea);
- private void Zeiterfassung_Click(object? sender, RoutedEventArgs e)
- {
- _viewManager.Show("Zeiterfassung");
- }
+ _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));
+ // ⏳ später: _viewManager.Register("MitarbeiterMain", () => new MitarbeiterMainView(_viewManager));
- private void MitarbeiterHinzufuegen_Click(object? sender, RoutedEventArgs e)
- {
- _viewManager.Show("MitarbeiterHinzufuegen");
+ 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})";
+ }
+
+ private void Zeiterfassung_Click(object? sender, RoutedEventArgs e)
+ {
+ _viewManager.Show("Zeiterfassung");
+ }
+
+ private void MitarbeiterHinzufuegen_Click(object? sender, RoutedEventArgs e)
+ {
+ _viewManager.Show("MitarbeiterHinzufuegen");
+ }
+
+ private void AdminDashboard_Click(object? sender, RoutedEventArgs e)
+ {
+ _viewManager.Show("AdminMain");
+ }
+
+ public void ShowAdminDashboard()
+ {
+ _viewManager.Show("AdminMain");
+ }
}
-}
\ No newline at end of file
+}
diff --git a/ChronoFlow.View/MitarbeiterHinzufuegenView.axaml b/ChronoFlow.View/MitarbeiterHinzufuegenView.axaml
index e50d99f..cfc866f 100644
--- a/ChronoFlow.View/MitarbeiterHinzufuegenView.axaml
+++ b/ChronoFlow.View/MitarbeiterHinzufuegenView.axaml
@@ -17,6 +17,7 @@
+
\ No newline at end of file
diff --git a/ChronoFlow.View/MitarbeiterHinzufuegenView.axaml.cs b/ChronoFlow.View/MitarbeiterHinzufuegenView.axaml.cs
index 799fdc1..f270e09 100644
--- a/ChronoFlow.View/MitarbeiterHinzufuegenView.axaml.cs
+++ b/ChronoFlow.View/MitarbeiterHinzufuegenView.axaml.cs
@@ -3,8 +3,8 @@ using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Media;
using ChronoFlow.Model;
-using Microsoft.Data.Sqlite;
using ChronoFlow.Persistence;
+using ChronoFlow.Security;
namespace ChronoFlow.View
{
@@ -21,11 +21,11 @@ namespace ChronoFlow.View
{
var service = new SqliteZeiterfassungsService();
- string username = UsernameBox.Text ?? "";
- string password = PasswordBox.Text ?? "";
+ string username = UsernameBox.Text?.Trim() ?? "";
+ string password = PasswordBox.Text?.Trim() ?? "";
string rolle = (RoleBox.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "";
- string mitarbeiternummer = MitarbeiternummerBox.Text ?? "";
- string abteilung = AbteilungBox.Text ?? "";
+ string mitarbeiternummer = MitarbeiternummerBox.Text?.Trim() ?? "";
+ string abteilung = AbteilungBox.Text?.Trim() ?? "";
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password) || string.IsNullOrWhiteSpace(rolle))
{
@@ -35,24 +35,23 @@ namespace ChronoFlow.View
return;
}
- using var connection = new SqliteConnection("Data Source=chrono_data.sb");
- connection.Open();
+ var neuerBenutzer = new User
+ {
+ Username = username,
+ Password = PasswordHasher.HashPassword(password),
+ Role = rolle,
+ Mitarbeiternummer = mitarbeiternummer,
+ Abteilung = abteilung,
+ MussPasswortAendern = true
+ };
- var cmd = connection.CreateCommand();
- cmd.CommandText = @"
- INSERT INTO Benutzer (Username, Password, Role, Mitarbeiternummer, Abteilung)
- VALUES ($Username, $Password, $Role, $Mitarbeiternummer, $Abteilung);";
-
- cmd.Parameters.AddWithValue("$Username", username);
- cmd.Parameters.AddWithValue("$Password", password);
- cmd.Parameters.AddWithValue("$Role", rolle);
- cmd.Parameters.AddWithValue("$Mitarbeiternummer", mitarbeiternummer);
- cmd.Parameters.AddWithValue("$Abteilung", abteilung);
- cmd.ExecuteNonQuery();
+ service.ErstelleNeuenBenutzer(neuerBenutzer);
FeedbackText.Text = "✅ Mitarbeiter erfolgreich gespeichert.";
FeedbackText.Foreground = Brushes.Green;
FeedbackText.IsVisible = true;
+
+ ClearFields();
}
catch (Exception ex)
{
@@ -65,5 +64,26 @@ namespace ChronoFlow.View
}
}
+ private void ClearFields()
+ {
+ UsernameBox.Text = "";
+ PasswordBox.Text = "";
+ MitarbeiternummerBox.Text = "";
+ AbteilungBox.Text = "";
+ RoleBox.SelectedIndex = -1;
+ }
+
+ private void ZurueckZumDashboard_Click(object? sender, RoutedEventArgs e)
+ {
+ var mainWindow = this.VisualRoot as MainWindow;
+ if (mainWindow != null)
+ {
+ mainWindow.ShowAdminDashboard();
+ }
+ else
+ {
+ Console.WriteLine("⚠️ MainWindow nicht gefunden!");
+ }
+ }
}
}
diff --git a/ChronoFlow.View/Program.cs b/ChronoFlow.View/Program.cs
index 6621b51..7504179 100644
--- a/ChronoFlow.View/Program.cs
+++ b/ChronoFlow.View/Program.cs
@@ -1,21 +1,23 @@
using Avalonia;
using System;
+using ChronoFlow.Persistence;
namespace ChronoFlow.View;
class Program
{
- // Initialization code. Don't use any Avalonia, third-party APIs or any
- // SynchronizationContext-reliant code before AppMain is called: things aren't initialized
- // yet and stuff might break.
[STAThread]
- public static void Main(string[] args) => BuildAvaloniaApp()
- .StartWithClassicDesktopLifetime(args);
+ public static void Main(string[] args)
+ {
+ // Aufruf des Test-Checkers INSIDE der Main-Methode!
+ SecurityReferenceTest.TestSecurityReference();
+
+ BuildAvaloniaApp()
+ .StartWithClassicDesktopLifetime(args);
+ }
- // Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure()
.UsePlatformDetect()
- .WithInterFont()
.LogToTrace();
}
\ No newline at end of file
diff --git a/ChronoFlow.View/Security/PasswortAendernDialog.axaml b/ChronoFlow.View/Security/PasswortAendernDialog.axaml
new file mode 100644
index 0000000..68c0bb1
--- /dev/null
+++ b/ChronoFlow.View/Security/PasswortAendernDialog.axaml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ChronoFlow.View/Security/PasswortAendernDialog.axaml.cs b/ChronoFlow.View/Security/PasswortAendernDialog.axaml.cs
new file mode 100644
index 0000000..1b66a37
--- /dev/null
+++ b/ChronoFlow.View/Security/PasswortAendernDialog.axaml.cs
@@ -0,0 +1,46 @@
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using ChronoFlow.Model;
+
+namespace ChronoFlow.View.Security;
+
+public partial class PasswortAendernDialog : Window
+{
+ public string NeuesPasswort { get; private set; } = "";
+ private readonly User _user;
+
+ // Konstruktor mit Benutzerobjekt
+ public PasswortAendernDialog(User user)
+ {
+ InitializeComponent();
+ _user = user;
+ }
+
+ private void SpeichernButton_Click(object? sender, RoutedEventArgs e)
+ {
+ var passwort = NeuesPasswortBox.Text?.Trim();
+ var bestaetigen = BestaetigenBox.Text?.Trim();
+
+ if (string.IsNullOrWhiteSpace(passwort) || string.IsNullOrWhiteSpace(bestaetigen))
+ {
+ FehlerText.Text = "Bitte alle Felder ausfüllen.";
+ FehlerText.IsVisible = true;
+ return;
+ }
+
+ if (passwort != bestaetigen)
+ {
+ FehlerText.Text = "Passwörter stimmen nicht überein.";
+ FehlerText.IsVisible = true;
+ return;
+ }
+
+ NeuesPasswort = passwort;
+ Close(NeuesPasswort);
+ }
+
+ private void AbbrechenButton_Click(object? sender, RoutedEventArgs e)
+ {
+ Close(null);
+ }
+}
\ No newline at end of file
diff --git a/ChronoFlow.View/ViewManager.cs b/ChronoFlow.View/ViewManager.cs
index 5508b30..ff3665c 100644
--- a/ChronoFlow.View/ViewManager.cs
+++ b/ChronoFlow.View/ViewManager.cs
@@ -1,49 +1,62 @@
using System;
using System.Collections.Generic;
using Avalonia.Controls;
-using Microsoft.Data.Sqlite;
namespace ChronoFlow.View
{
- ///
- /// Verwaltet alle Views der Anwendung und wechselt sie bei Bedarf.
- ///
-
public class ViewManager
{
- private readonly ContentControl _targetControl;
- private readonly Dictionary> _registieredViews = new();
+ private readonly ContentControl _contentControl;
- public ViewManager(ContentControl targetControl)
+ // Dictionary speichert die registrierten Views mit ihren Erstellungs-Methoden (Factories)
+ private readonly Dictionary> _views = new();
+
+ public ViewManager(ContentControl contentControl)
{
- _targetControl = targetControl;
+ _contentControl = contentControl;
}
-
- ///
- /// Registriert eine View mit einem Namen
+
+ ///
+ /// Registriert eine View mit einem eindeutigen Namen.
///
-
- public void Show(string name, Func viewFactory)
+ public void Register(string name, Func factory)
{
- _registieredViews[name] = viewFactory;
+ if (!_views.ContainsKey(name))
+ {
+ _views[name] = factory;
+ }
}
- ///
- /// Zeigt die View mit dem gegebenen Namen an.
+ ///
+ /// Zeigt eine registrierte View an.
///
public void Show(string name)
{
- if(_registieredViews.TryGetValue(name, out var factory))
- _targetControl.Content = factory();
+ if (_views.TryGetValue(name, out var factory))
+ {
+ var view = factory();
+ _contentControl.Content = view;
+ }
else
{
throw new InvalidOperationException($"View {name} is not registered");
}
}
- public void Register(string name, Func viewFactory)
+
+ ///
+ /// Holt eine bereits registrierte View als konkreten Typ (z. B. AdminMainView).
+ ///
+ public bool TryGetView(string name, out T? view) where T : class
{
- _registieredViews[name] = viewFactory;
+ if (_views.TryGetValue(name, out var factory))
+ {
+ var instance = factory();
+ view = instance as T;
+ return view != null;
+ }
+
+ view = null;
+ return false;
}
-
}
}
\ No newline at end of file
diff --git a/ChronoFlow.View/ZeiterfassungView.axaml b/ChronoFlow.View/ZeiterfassungView.axaml
deleted file mode 100644
index 9575ad7..0000000
--- a/ChronoFlow.View/ZeiterfassungView.axaml
+++ /dev/null
@@ -1,44 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/ChronoFlow.View/ZeiterfassungView.axaml.cs b/ChronoFlow.View/ZeiterfassungView.axaml.cs
deleted file mode 100644
index 878144d..0000000
--- a/ChronoFlow.View/ZeiterfassungView.axaml.cs
+++ /dev/null
@@ -1,134 +0,0 @@
-using System;
-using System.Collections.ObjectModel;
-using System.Linq; // Wichtig für .Where und .Select
-using Avalonia.Controls;
-using Avalonia.Interactivity;
-using Avalonia.Media;
-using ChronoFlow.Controller;
-using ChronoFlow.Model;
-using ChronoFlow.Persistence; // Wichtig für SqliteZeiterfassungsService
-
-namespace ChronoFlow.View
-{
- public partial class ZeiterfassungView : UserControl
- {
- private readonly ZeiterfassungsController _controller;
- private readonly ObservableCollection _anzeigeEinträge;
- private readonly User _user;
-
- public ZeiterfassungView(User user)
- {
- InitializeComponent();
- _user = user;
-
- _controller = new ZeiterfassungsController();
- _anzeigeEinträge = new ObservableCollection();
-
- // ✅ Benutzer aus Datenbank laden und Dropdown füllen
- var benutzer = new SqliteZeiterfassungsService().LadeAlleBenutzer();
- var nurMitarbeiter = benutzer
- .Where(b => b.Role == "Mitarbeiter")
- .Select(b => b.Username)
- .ToList();
-
- MitarbeiterBoxDropdown.ItemsSource = nurMitarbeiter;
-
- // Einträge aus SQLite laden
- var geladeneEintraege = _controller.LadeAlleEintraege();
- foreach (var eintrag in geladeneEintraege)
- {
- _anzeigeEinträge.Add(eintrag);
- }
-
- Eintragsliste.ItemsSource = _anzeigeEinträge;
-
- // Eingabeformular nur für Admin sichtbar
- if (_user.Role != "Admin" && EingabePanel != null)
- {
- EingabePanel.IsVisible = false;
- }
- }
-
- private void SpeichernButton_Click(object? sender, RoutedEventArgs e)
- {
- try
- {
- string mitarbeiter = MitarbeiterBoxDropdown.SelectedItem?.ToString() ?? "";
- DateTime datum = DatumPicker.SelectedDate?.Date ?? DateTime.Today;
- string startText = StartzeitBox.Text ?? "";
- string endText = EndzeitBox.Text ?? "";
-
- if (!TimeSpan.TryParse(startText, out TimeSpan startZeit))
- {
- FeedbackText.Text = "Ungültige Startzeit!";
- FeedbackText.Foreground = Brushes.Red;
- FeedbackText.IsVisible = true;
- return;
- }
-
- if (!TimeSpan.TryParse(endText, out TimeSpan endZeit))
- {
- FeedbackText.Text = "Ungültige Endzeit!";
- FeedbackText.Foreground = Brushes.Red;
- FeedbackText.IsVisible = true;
- return;
- }
-
- var eintrag = new Zeiteintrag
- {
- Mitarbeiter = mitarbeiter,
- Startzeit = datum.Date + startZeit,
- Endzeit = datum.Date + endZeit,
- Projekt = ProjektBox.Text,
- Kommentar = KommentarBox.Text,
- Erledigt = false
- };
-
- _controller.SpeichereEintrag(eintrag);
- _anzeigeEinträge.Add(eintrag);
-
- FeedbackText.Text = "Eintrag gespeichert.";
- FeedbackText.Foreground = Brushes.Green;
- FeedbackText.IsVisible = true;
- }
- catch (Exception ex)
- {
- FeedbackText.Text = $"Fehler: {ex.Message}";
- FeedbackText.Foreground = Brushes.Red;
- FeedbackText.IsVisible = true;
- }
- }
-
- private void MarkiereErledigt_Click(object? sender, RoutedEventArgs e)
- {
- if (sender is Button button && button.DataContext is Zeiteintrag eintrag)
- {
- eintrag.Erledigt = true;
- RefreshListe();
- }
- }
-
- private void MarkiereNichtErledigt_Click(object? sender, RoutedEventArgs e)
- {
- if (sender is Button button && button.DataContext is Zeiteintrag eintrag)
- {
- eintrag.Erledigt = false;
- RefreshListe();
- }
- }
-
- private void Kommentar_LostFocus(object? sender, RoutedEventArgs e)
- {
- if (sender is TextBox tb && tb.DataContext is Zeiteintrag eintrag)
- {
- eintrag.MitarbeiterKommentar = tb.Text;
- }
- }
-
- private void RefreshListe()
- {
- Eintragsliste.ItemsSource = null;
- Eintragsliste.ItemsSource = _anzeigeEinträge;
- }
- }
-}
diff --git a/ChronoFlow.sln b/ChronoFlow.sln
index 6e4f119..75a1524 100644
--- a/ChronoFlow.sln
+++ b/ChronoFlow.sln
@@ -8,6 +8,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChronoFlow.Persistence", "C
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChronoFlow.Controller", "ChronoFlow.Controller\ChronoFlow.Controller.csproj", "{BCCF491C-6A5D-45E4-B490-C553A550F559}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChronoFlow.Security", "ChronoFlow.Security\ChronoFlow.Security.csproj", "{51F4750C-938D-451F-8E32-B7FB7436D125}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -30,5 +32,9 @@ Global
{BCCF491C-6A5D-45E4-B490-C553A550F559}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BCCF491C-6A5D-45E4-B490-C553A550F559}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BCCF491C-6A5D-45E4-B490-C553A550F559}.Release|Any CPU.Build.0 = Release|Any CPU
+ {51F4750C-938D-451F-8E32-B7FB7436D125}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {51F4750C-938D-451F-8E32-B7FB7436D125}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {51F4750C-938D-451F-8E32-B7FB7436D125}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {51F4750C-938D-451F-8E32-B7FB7436D125}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal