Compare commits
3 Commits
e358454b5e
...
e707deafdf
| Author | SHA1 | Date | |
|---|---|---|---|
| e707deafdf | |||
| 53a3c1b4da | |||
| 0ffe6da3c6 |
@ -2,13 +2,14 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<OutputType>Library</OutputType>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\ChronoFlow.Model\ChronoFlow.Model.csproj" />
|
<ProjectReference Include="..\ChronoFlow.Persistence\ChronoFlow.Persistence.csproj" />
|
||||||
<ProjectReference Include="..\ChronoFlow.Persistence\ChronoFlow.Persistence.csproj" />
|
<ProjectReference Include="..\ChronoFlow.Security\ChronoFlow.Security.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@ -1,16 +1,26 @@
|
|||||||
namespace ChronoFlow.Model
|
namespace ChronoFlow.Model
|
||||||
{
|
{
|
||||||
|
|
||||||
///<summary>
|
|
||||||
/// Repräsentiert einen Benutzer mit Benutzernamen, Passwort und Rolle.
|
|
||||||
/// </summary>
|
|
||||||
|
|
||||||
public class User
|
public class User
|
||||||
{
|
{
|
||||||
public string Username { get; set; }
|
public string Username { get; set; }
|
||||||
public string Password { get; set; } //vorerst im Klartext, wird später geändert
|
public string Password { get; set; }
|
||||||
public string Role { get; set; } //"Admin" oder "Mitarbeiter"
|
public string Role { get; set; }
|
||||||
public string Mitarbeiternummer { get; set; } = "";
|
public string Mitarbeiternummer { get; set; }
|
||||||
public string Abteilung { 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 = "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,26 +1,27 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace ChronoFlow.Model
|
namespace ChronoFlow.Model
|
||||||
{
|
{
|
||||||
public class Zeiteintrag
|
public class Zeiteintrag
|
||||||
{
|
{
|
||||||
|
public int Id { get; set; } // <<< NEU
|
||||||
|
|
||||||
public string Mitarbeiter { get; set; }
|
public string Mitarbeiter { get; set; }
|
||||||
public DateTime Startzeit { get; set; }
|
public DateTime Startzeit { get; set; }
|
||||||
public DateTime Endzeit { get; set; }
|
public DateTime Endzeit { get; set; }
|
||||||
public string? Projekt { get; set; }
|
public string Projekt { get; set; }
|
||||||
public string? Kommentar { get; set; }
|
public string Kommentar { get; set; }
|
||||||
|
|
||||||
public TimeSpan Dauer => Endzeit - Startzeit;
|
|
||||||
|
|
||||||
//Felder für Mitarbeiter-Rückmeldung
|
|
||||||
public bool Erledigt { get; set; }
|
public bool Erledigt { get; set; }
|
||||||
public string? MitarbeiterKommentar { get; set; }
|
public string MitarbeiterKommentar { get; set; }
|
||||||
|
|
||||||
|
public Zeiteintrag()
|
||||||
public override string ToString()
|
|
||||||
{
|
{
|
||||||
return $"{Mitarbeiter} - {Startzeit:HH:mm} - {Endzeit:HH:mm} | {Projekt}";
|
Id = 0;
|
||||||
|
Mitarbeiter = "";
|
||||||
|
Startzeit = DateTime.Now;
|
||||||
|
Endzeit = DateTime.Now;
|
||||||
|
Projekt = "";
|
||||||
|
Kommentar = "";
|
||||||
|
Erledigt = false;
|
||||||
|
MitarbeiterKommentar = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -7,11 +7,12 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\ChronoFlow.Model\ChronoFlow.Model.csproj" />
|
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.0-preview.3.25171.6" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.0-preview.3.25171.6" />
|
<ProjectReference Include="..\ChronoFlow.Model\ChronoFlow.Model.csproj" />
|
||||||
|
<ProjectReference Include="..\ChronoFlow.Security\ChronoFlow.Security.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
19
ChronoFlow.Persistence/SecurityReferenceTest.cs
Normal file
19
ChronoFlow.Persistence/SecurityReferenceTest.cs
Normal file
@ -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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,93 +1,237 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using Microsoft.Data.Sqlite;
|
using Microsoft.Data.Sqlite;
|
||||||
using ChronoFlow.Model;
|
using ChronoFlow.Model;
|
||||||
|
using ChronoFlow.Security;
|
||||||
|
|
||||||
namespace ChronoFlow.Persistence
|
namespace ChronoFlow.Persistence
|
||||||
{
|
{
|
||||||
public class SqliteZeiterfassungsService
|
public class SqliteZeiterfassungsService
|
||||||
{
|
{
|
||||||
private readonly string _dbPath;
|
private readonly string _dbPath = "chrono_data.sb";
|
||||||
private bool _dbInitialisiert ;
|
|
||||||
|
|
||||||
public SqliteZeiterfassungsService()
|
public SqliteZeiterfassungsService()
|
||||||
{
|
|
||||||
_dbPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "chrono_data.sb");
|
|
||||||
InitialisiereDatenbank();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitialisiereDatenbank()
|
|
||||||
{
|
{
|
||||||
if (!File.Exists(_dbPath))
|
if (!File.Exists(_dbPath))
|
||||||
{
|
|
||||||
Console.WriteLine("📂 Datenbank existiert nicht. Erstelle neue...");
|
|
||||||
ErstelleDatenbank();
|
ErstelleDatenbank();
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Console.WriteLine("✅ Datenbankdatei gefunden: " + _dbPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
_dbInitialisiert = true;
|
// IMMER prüfen, auch nach neuem Erstellen
|
||||||
ZeigeExistierendeTabellen();
|
PrüfeUndErweitereDatenbank();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ErstelleDatenbank()
|
private void ErstelleDatenbank()
|
||||||
{
|
{
|
||||||
|
Console.WriteLine("🛠️ ErstelleDatenbank wurde aufgerufen!");
|
||||||
|
|
||||||
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
||||||
connection.Open();
|
connection.Open();
|
||||||
|
|
||||||
using (var cmd = connection.CreateCommand())
|
var cmd1 = connection.CreateCommand();
|
||||||
{
|
cmd1.CommandText = @"
|
||||||
cmd.CommandText = @"
|
CREATE TABLE IF NOT EXISTS Zeiteintraege (
|
||||||
CREATE TABLE IF NOT EXISTS Zeiteintraege (
|
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
Mitarbeiter TEXT NOT NULL,
|
||||||
Mitarbeiter TEXT NOT NULL,
|
Startzeit TEXT NOT NULL,
|
||||||
Startzeit TEXT NOT NULL,
|
Endzeit TEXT NOT NULL,
|
||||||
Endzeit TEXT NOT NULL,
|
Projekt TEXT,
|
||||||
Projekt TEXT,
|
Kommentar TEXT,
|
||||||
Kommentar TEXT,
|
Erledigt INTEGER,
|
||||||
Erledigt INTEGER,
|
MitarbeiterKommentar TEXT
|
||||||
MitarbeiterKommentar TEXT
|
);";
|
||||||
);
|
cmd1.ExecuteNonQuery();
|
||||||
";
|
|
||||||
cmd.ExecuteNonQuery();
|
|
||||||
Console.WriteLine("🛠️ Tabelle Zeiteintraege wurde erstellt/überprüft.");
|
|
||||||
}
|
|
||||||
|
|
||||||
using (var cmd = connection.CreateCommand())
|
var cmd2 = connection.CreateCommand();
|
||||||
{
|
cmd2.CommandText = @"
|
||||||
cmd.CommandText = @"
|
CREATE TABLE IF NOT EXISTS Benutzer (
|
||||||
CREATE TABLE IF NOT EXISTS Benutzer (
|
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
Username TEXT NOT NULL,
|
||||||
Username TEXT NOT NULL,
|
Password TEXT NOT NULL,
|
||||||
Password TEXT NOT NULL,
|
Role TEXT NOT NULL,
|
||||||
Role TEXT NOT NULL,
|
Mitarbeitennummer TEXT,
|
||||||
Mitarbeiternummer TEXT,
|
Abteilung TEXT,
|
||||||
Abteilung TEXT
|
MussPasswortAendern INTEGER DEFAULT 1
|
||||||
);
|
);";
|
||||||
";
|
cmd2.ExecuteNonQuery();
|
||||||
cmd.ExecuteNonQuery();
|
|
||||||
Console.WriteLine("🛠️ Tabelle Benutzer wurde erstellt/überprüft.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ErstelleStandardAdmin()
|
public void ErstelleNeuenBenutzer(User benutzer)
|
||||||
{
|
{
|
||||||
if (!_dbInitialisiert)
|
|
||||||
{
|
|
||||||
throw new Exception("❗ Fehler: Datenbank wurde noch nicht initialisiert!");
|
|
||||||
}
|
|
||||||
|
|
||||||
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
||||||
connection.Open();
|
connection.Open();
|
||||||
|
|
||||||
var cmd = connection.CreateCommand();
|
var cmd = connection.CreateCommand();
|
||||||
cmd.CommandText = @"
|
cmd.CommandText = @"
|
||||||
INSERT INTO Benutzer (Username, Password, Role, Mitarbeiternummer, Abteilung)
|
INSERT INTO Benutzer (Username, Password, Role, Mitarbeitennummer, Abteilung, MussPasswortAendern)
|
||||||
VALUES ('admin', 'admin', 'Admin', '0001', 'IT');
|
VALUES ($Username, $Password, $Role, $Mitarbeiternummer, $Abteilung, $MussPasswortAendern);
|
||||||
";
|
";
|
||||||
cmd.ExecuteNonQuery();
|
|
||||||
|
|
||||||
Console.WriteLine("✅ Standard-Admin erfolgreich erstellt.");
|
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<User> LadeAlleBenutzer()
|
||||||
|
{
|
||||||
|
var benutzerListe = new List<User>();
|
||||||
|
|
||||||
|
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<string> LadeAlleMitarbeiterNamen()
|
||||||
|
{
|
||||||
|
var namen = new List<string>();
|
||||||
|
|
||||||
|
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)
|
public void SpeichereEintrag(Zeiteintrag eintrag)
|
||||||
@ -111,6 +255,8 @@ namespace ChronoFlow.Persistence
|
|||||||
cmd.Parameters.AddWithValue("$MitarbeiterKommentar", eintrag.MitarbeiterKommentar ?? "");
|
cmd.Parameters.AddWithValue("$MitarbeiterKommentar", eintrag.MitarbeiterKommentar ?? "");
|
||||||
|
|
||||||
cmd.ExecuteNonQuery();
|
cmd.ExecuteNonQuery();
|
||||||
|
|
||||||
|
Console.WriteLine($"✅ Zeiteintrag für {eintrag.Mitarbeiter} gespeichert.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Zeiteintrag> LadeAlleZeiteintraege()
|
public List<Zeiteintrag> LadeAlleZeiteintraege()
|
||||||
@ -128,6 +274,7 @@ namespace ChronoFlow.Persistence
|
|||||||
{
|
{
|
||||||
eintraege.Add(new Zeiteintrag
|
eintraege.Add(new Zeiteintrag
|
||||||
{
|
{
|
||||||
|
Id = reader.GetInt32(0),
|
||||||
Mitarbeiter = reader.GetString(1),
|
Mitarbeiter = reader.GetString(1),
|
||||||
Startzeit = DateTime.Parse(reader.GetString(2)),
|
Startzeit = DateTime.Parse(reader.GetString(2)),
|
||||||
Endzeit = DateTime.Parse(reader.GetString(3)),
|
Endzeit = DateTime.Parse(reader.GetString(3)),
|
||||||
@ -141,66 +288,175 @@ namespace ChronoFlow.Persistence
|
|||||||
return eintraege;
|
return eintraege;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<User> LadeAlleBenutzer()
|
public void UpdateProjekt(Zeiteintrag projekt)
|
||||||
{
|
{
|
||||||
var benutzerListe = new List<User>();
|
|
||||||
|
|
||||||
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
||||||
connection.Open();
|
connection.Open();
|
||||||
|
|
||||||
var cmd = connection.CreateCommand();
|
var cmd = connection.CreateCommand();
|
||||||
cmd.CommandText = "SELECT Username, Password, Role, Mitarbeiternummer, Abteilung FROM Benutzer;";
|
cmd.CommandText = @"
|
||||||
|
UPDATE Zeiteintraege
|
||||||
|
SET Projekt = $Projekt,
|
||||||
|
Kommentar = $Kommentar,
|
||||||
|
Startzeit = $Startzeit,
|
||||||
|
Endzeit = $Endzeit,
|
||||||
|
Mitarbeiter = $Mitarbeiter,
|
||||||
|
Erledigt = $Erledigt
|
||||||
|
WHERE Id = $Id;
|
||||||
|
";
|
||||||
|
|
||||||
|
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 void LoescheProjekt(int id)
|
||||||
|
{
|
||||||
|
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
||||||
|
connection.Open();
|
||||||
|
|
||||||
|
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<Zeiteintrag> LadeAbgeschlosseneProjekte()
|
||||||
|
{
|
||||||
|
var abgeschlossene = new List<Zeiteintrag>();
|
||||||
|
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();
|
using var reader = cmd.ExecuteReader();
|
||||||
while (reader.Read())
|
while (reader.Read())
|
||||||
{
|
{
|
||||||
benutzerListe.Add(new User
|
abgeschlossene.Add(new Zeiteintrag
|
||||||
{
|
{
|
||||||
Username = reader.GetString(0),
|
Id = reader.GetInt32(0),
|
||||||
Password = reader.GetString(1),
|
Mitarbeiter = reader.GetString(1),
|
||||||
Role = reader.GetString(2),
|
Startzeit = DateTime.Parse(reader.GetString(2)),
|
||||||
Mitarbeiternummer = reader.IsDBNull(3) ? "" : reader.GetString(3),
|
Endzeit = DateTime.Parse(reader.GetString(3)),
|
||||||
Abteilung = reader.IsDBNull(4) ? "" : reader.GetString(4)
|
Projekt = reader.GetString(4),
|
||||||
|
Kommentar = reader.GetString(5),
|
||||||
|
Erledigt = Convert.ToInt32(reader["Erledigt"]) == 1,
|
||||||
|
MitarbeiterKommentar = reader.GetString(7)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return benutzerListe;
|
return abgeschlossene;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ZeigeExistierendeTabellen()
|
public List<Zeiteintrag> LadeLetzteProjekte(int anzahl = 3)
|
||||||
{
|
{
|
||||||
|
var projekte = new List<Zeiteintrag>();
|
||||||
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
||||||
connection.Open();
|
connection.Open();
|
||||||
|
|
||||||
var cmd = connection.CreateCommand();
|
var cmd = connection.CreateCommand();
|
||||||
cmd.CommandText = "SELECT name FROM sqlite_master WHERE type='table';";
|
cmd.CommandText = @"
|
||||||
|
SELECT * FROM Zeiteintraege
|
||||||
|
ORDER BY Id DESC
|
||||||
|
LIMIT $Anzahl;
|
||||||
|
";
|
||||||
|
cmd.Parameters.AddWithValue("$Anzahl", anzahl);
|
||||||
|
|
||||||
using var reader = cmd.ExecuteReader();
|
using var reader = cmd.ExecuteReader();
|
||||||
Console.WriteLine("🗂️ Tabellen in der Datenbank:");
|
|
||||||
while (reader.Read())
|
while (reader.Read())
|
||||||
{
|
{
|
||||||
Console.WriteLine($" ➔ {reader.GetString(0)}");
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public List<Zeiteintrag> LadeOffeneProjekte()
|
||||||
/// Prüft, ob ein Benutzername bereits existiert.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="username">Benutzername, der überprüft werden soll</param>
|
|
||||||
/// <returns>True, wenn Name bereits existiert, sonst False</returns>
|
|
||||||
public bool BenutzernameExistiert(string username)
|
|
||||||
{
|
{
|
||||||
|
var offene = new List<Zeiteintrag>();
|
||||||
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
||||||
connection.Open();
|
connection.Open();
|
||||||
|
|
||||||
var cmd = connection.CreateCommand();
|
var cmd = connection.CreateCommand();
|
||||||
cmd.CommandText = "SELECT COUNT(*) FROM Benutzer WHERE Username = $username";
|
cmd.CommandText = "SELECT * FROM Zeiteintraege WHERE Erledigt = 0;";
|
||||||
cmd.Parameters.AddWithValue("$username", username);
|
|
||||||
|
|
||||||
var result = Convert.ToInt32(cmd.ExecuteScalar());
|
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 result > 0;
|
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})");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
9
ChronoFlow.Security/ChronoFlow.Security.csproj
Normal file
9
ChronoFlow.Security/ChronoFlow.Security.csproj
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
67
ChronoFlow.Security/PasswordHasher.cs
Normal file
67
ChronoFlow.Security/PasswordHasher.cs
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
using System;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace ChronoFlow.Security
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Diese Klasse bietet Funktionen zum sicheren Hashen und Überprüfen von Passwörtern.
|
||||||
|
/// </summary>
|
||||||
|
public static class PasswordHasher
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Erstellt einen sicheren Hash aus einem Passwort unter Verwendung von PBKDF2.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="password">Das Klartextpasswort</param>
|
||||||
|
/// <returns>Ein kombinierter Hash-String (Salt + Hash)</returns>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Überprüft, ob ein Passwort zu einem gegebenen Hash passt.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="password">Das eingegebene Klartextpasswort</param>
|
||||||
|
/// <param name="storedHash">Der gespeicherte kombinierte Hash (Base64, Salt + Hash)</param>
|
||||||
|
/// <returns>True, wenn das Passwort stimmt, sonst false</returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
26
ChronoFlow.View/Admin/AbgeschlosseneProjekteView.axaml
Normal file
26
ChronoFlow.View/Admin/AbgeschlosseneProjekteView.axaml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:model1="clr-namespace:ChronoFlow.Model;assembly=ChronoFlow.Model"
|
||||||
|
x:Class="ChronoFlow.View.Admin.AbgeschlosseneProjekteView">
|
||||||
|
<Grid RowDefinitions="Auto,Auto,*,Auto" Margin="20">
|
||||||
|
<TextBlock Grid.Row="0" Text="Abgeschlossene Projekte" FontSize="20" FontWeight="Bold" HorizontalAlignment="Center" Margin="0,0,0,10"/>
|
||||||
|
|
||||||
|
<TextBox Grid.Row="1" x:Name="Suchfeld" Watermark="🔍 Nach Projekt oder Mitarbeiter suchen..." KeyUp="Suchfeld_KeyUp" Margin="0,0,0,10"/>
|
||||||
|
|
||||||
|
<ScrollViewer Grid.Row="2">
|
||||||
|
<ListBox x:Name="AbgeschlosseneListe">
|
||||||
|
<ListBox.ItemTemplate>
|
||||||
|
<DataTemplate DataType="{x:Type model1:Zeiteintrag}">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="10" Margin="5">
|
||||||
|
<TextBlock Text="{Binding Projekt}" Width="150"/>
|
||||||
|
<TextBlock Text="{Binding Mitarbeiter}" Width="150"/>
|
||||||
|
<TextBlock Text="{Binding Endzeit}" Width="150"/>
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
</ListBox>
|
||||||
|
</ScrollViewer>
|
||||||
|
|
||||||
|
<Button Grid.Row="3" Content="⬅ Zurück zum Dashboard" Click="ZurueckButton_Click" HorizontalAlignment="Center" Margin="0,10,0,0"/>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
49
ChronoFlow.View/Admin/AbgeschlosseneProjekteView.axaml.cs
Normal file
49
ChronoFlow.View/Admin/AbgeschlosseneProjekteView.axaml.cs
Normal file
@ -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<Zeiteintrag> _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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
59
ChronoFlow.View/Admin/AdminMainView.axaml
Normal file
59
ChronoFlow.View/Admin/AdminMainView.axaml
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:model="clr-namespace:ChronoFlow.Model;assembly=ChronoFlow.Model"
|
||||||
|
x:Class="ChronoFlow.View.Admin.AdminMainView">
|
||||||
|
|
||||||
|
<SplitView x:Name="AdminPane" DisplayMode="CompactInline" IsPaneOpen="True" CompactPaneLength="37" OpenPaneLength="200">
|
||||||
|
|
||||||
|
<!-- Linke Sidebar (SplitView-Pane) -->
|
||||||
|
<SplitView.Pane>
|
||||||
|
<StackPanel>
|
||||||
|
<!-- Burger-Button -->
|
||||||
|
<Button Content="☰" Click="TogglePane_Click"/>
|
||||||
|
|
||||||
|
<!-- Navigation Buttons -->
|
||||||
|
<Button Content="🏠 Dashboard" Click="Dashboard_Click"/>
|
||||||
|
<Button Content="📋 Alle Projekte anzeigen" Click="AlleProjekte_Click"/>
|
||||||
|
<Button Content="👥 Mitarbeiter-Liste" Click="MitarbeiterListe_Click"/>
|
||||||
|
<Button Content="✅ Abgeschlossene Projekte" Click="AbgeschlosseneProjekte_Click" />
|
||||||
|
<Button Content="⚙ Einstellungen" Click="Einstellungen_Click"/>
|
||||||
|
</StackPanel>
|
||||||
|
</SplitView.Pane>
|
||||||
|
|
||||||
|
<!-- Hauptinhalt -->
|
||||||
|
<SplitView.Content>
|
||||||
|
<StackPanel Margin="20" Spacing="15">
|
||||||
|
|
||||||
|
<!-- Überschrift -->
|
||||||
|
<TextBlock Text="Admin-Dashboard" FontSize="24" FontWeight="Bold" HorizontalAlignment="Center"/>
|
||||||
|
|
||||||
|
<!-- Aktions-Buttons (oben) -->
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="10">
|
||||||
|
<Button Content="➕ Mitarbeiter hinzufügen" Width="200" Height="50" Click="MitarbeiterHinzufuegen_Click"/>
|
||||||
|
<Button Content="➕ Projekt erstellen" Width="200" Height="50" Click="ProjektErstellen_Click"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Anzeige der letzten Projekte -->
|
||||||
|
<TextBlock Text="Zuletzt hinzugefügte Projekte" FontSize="18" FontWeight="SemiBold" Margin="0,20,0,10"/>
|
||||||
|
|
||||||
|
<ItemsControl x:Name="LetzteProjekteListe">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate DataType="{x:Type model:Zeiteintrag}">
|
||||||
|
<Border BorderBrush="Gray" BorderThickness="1" Padding="10" Margin="0,5">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Text="{Binding Projekt}" FontWeight="Bold"/>
|
||||||
|
<TextBlock Text="{Binding Mitarbeiter}"/>
|
||||||
|
<TextBlock Text="{Binding Startzeit, StringFormat='Start: {0:G}'}"/>
|
||||||
|
<TextBlock Text="{Binding Endzeit, StringFormat='Ende: {0:G}'}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
|
||||||
|
</ItemsControl>
|
||||||
|
|
||||||
|
</StackPanel>
|
||||||
|
</SplitView.Content>
|
||||||
|
|
||||||
|
</SplitView>
|
||||||
|
</UserControl>
|
||||||
106
ChronoFlow.View/Admin/AdminMainView.axaml.cs
Normal file
106
ChronoFlow.View/Admin/AdminMainView.axaml.cs
Normal file
@ -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<Zeiteintrag> _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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
ChronoFlow.View/Admin/AlleProjekteView.axaml
Normal file
31
ChronoFlow.View/Admin/AlleProjekteView.axaml
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:model1="clr-namespace:ChronoFlow.Model;assembly=ChronoFlow.Model"
|
||||||
|
x:Class="ChronoFlow.View.Admin.AlleProjekteView">
|
||||||
|
<Grid RowDefinitions="Auto,Auto,*,Auto" Margin="20">
|
||||||
|
<TextBlock Grid.Row="0" Text="Alle Projekte" FontSize="20" FontWeight="Bold" HorizontalAlignment="Center" Margin="0,0,0,10"/>
|
||||||
|
|
||||||
|
<TextBox Grid.Row="1" x:Name="Suchfeld" Watermark="🔍 Nach Projekt oder Mitarbeiter suchen..." KeyUp="Suchfeld_KeyUp" Margin="0,0,0,10"/>
|
||||||
|
|
||||||
|
<ScrollViewer Grid.Row="2">
|
||||||
|
<ListBox x:Name="ProjekteListe">
|
||||||
|
<ListBox.ItemTemplate>
|
||||||
|
<DataTemplate DataType="{x:Type model1:Zeiteintrag}">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="10" Margin="5">
|
||||||
|
<TextBlock Text="{Binding Projekt}" Width="150"/>
|
||||||
|
<TextBlock Text="{Binding Mitarbeiter}" Width="150"/>
|
||||||
|
<TextBlock Text="{Binding Startzeit}" Width="150"/>
|
||||||
|
<TextBlock Text="{Binding Endzeit}" Width="150"/>
|
||||||
|
<TextBlock Text="{Binding Kommentar}" Width="200"/>
|
||||||
|
<Button Content="🖋 Bearbeiten" Tag="{Binding}" Click="Bearbeiten_Click"/>
|
||||||
|
<Button Content="🗑 Löschen" Tag="{Binding}" Click="Loeschen_Click"/>
|
||||||
|
<Button Content="✅ Abschließen" Tag="{Binding}" Click="Abschliessen_Click"/>
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
</ListBox>
|
||||||
|
</ScrollViewer>
|
||||||
|
|
||||||
|
<Button Grid.Row="3" Content="⬅ Zurück zum Dashboard" Click="ZurueckButton_Click" HorizontalAlignment="Center" Margin="0,10,0,0"/>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
89
ChronoFlow.View/Admin/AlleProjekteView.axaml.cs
Normal file
89
ChronoFlow.View/Admin/AlleProjekteView.axaml.cs
Normal file
@ -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<Zeiteintrag> _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<Zeiteintrag>((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");
|
||||||
|
}
|
||||||
|
}
|
||||||
15
ChronoFlow.View/Admin/ConfirmDialog.axaml
Normal file
15
ChronoFlow.View/Admin/ConfirmDialog.axaml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
x:Class="ChronoFlow.View.Admin.ConfirmDialog"
|
||||||
|
Width="400" Height="200"
|
||||||
|
Title="Bestätigung">
|
||||||
|
|
||||||
|
<StackPanel Margin="20" Spacing="10">
|
||||||
|
<TextBlock x:Name="FrageText" Text="Sind Sie sicher?" FontSize="16" FontWeight="Bold" TextWrapping="Wrap" />
|
||||||
|
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="10">
|
||||||
|
<Button Content="✅ Ja" Width="80" Click="JaButton_Click" />
|
||||||
|
<Button Content="❌ Nein" Width="80" Click="NeinButton_Click" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</Window>
|
||||||
27
ChronoFlow.View/Admin/ConfirmDialog.axaml.cs
Normal file
27
ChronoFlow.View/Admin/ConfirmDialog.axaml.cs
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
31
ChronoFlow.View/Admin/MitarbeiterBearbeitenDialog.axaml
Normal file
31
ChronoFlow.View/Admin/MitarbeiterBearbeitenDialog.axaml
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
x:Class="ChronoFlow.View.Admin.MitarbeiterBearbeitenDialog"
|
||||||
|
Width="450" Height="600"
|
||||||
|
Title="Mitarbeiter bearbeiten">
|
||||||
|
|
||||||
|
<StackPanel Margin="20" Spacing="10">
|
||||||
|
<TextBlock Text="Username:" />
|
||||||
|
<TextBox x:Name="UsernameBox" />
|
||||||
|
|
||||||
|
<TextBlock Text="Abteilung:" />
|
||||||
|
<TextBox x:Name="AbteilungBox" />
|
||||||
|
|
||||||
|
<TextBlock Text="Mitarbeiternummer:" />
|
||||||
|
<TextBox x:Name="MitarbeiternummerBox" />
|
||||||
|
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="10" Margin="0,10,0,0">
|
||||||
|
<Button Content="✅ Speichern" Width="120" Click="SpeichernButton_Click" />
|
||||||
|
<Button Content="❌ Abbrechen" Width="120" Click="AbbrechenButton_Click" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Erfolgs-Hinweis -->
|
||||||
|
<TextBlock x:Name="FeedbackText"
|
||||||
|
Text="Änderungen erfolgreich übernommen."
|
||||||
|
Foreground="Green"
|
||||||
|
FontWeight="Bold"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
IsVisible="False"
|
||||||
|
Margin="0,10,0,0" />
|
||||||
|
</StackPanel>
|
||||||
|
</Window>
|
||||||
41
ChronoFlow.View/Admin/MitarbeiterBearbeitenDialog.axaml.cs
Normal file
41
ChronoFlow.View/Admin/MitarbeiterBearbeitenDialog.axaml.cs
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
29
ChronoFlow.View/Admin/MitarbeiterListeView.axaml
Normal file
29
ChronoFlow.View/Admin/MitarbeiterListeView.axaml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:model1="clr-namespace:ChronoFlow.Model;assembly=ChronoFlow.Model"
|
||||||
|
x:Class="ChronoFlow.View.Admin.MitarbeiterListeView">
|
||||||
|
<Grid RowDefinitions="Auto,Auto,*,Auto" Margin="20">
|
||||||
|
<TextBlock Grid.Row="0" Text="Alle Mitarbeiter" FontSize="20" FontWeight="Bold" HorizontalAlignment="Center" Margin="0,0,0,10"/>
|
||||||
|
|
||||||
|
<TextBox Grid.Row="1" x:Name="Suchfeld" Watermark="🔍 Suchen..." KeyUp="Suchfeld_KeyUp" Margin="0,0,0,10"/>
|
||||||
|
|
||||||
|
<ScrollViewer Grid.Row="2">
|
||||||
|
<ListBox x:Name="MitarbeiterListe">
|
||||||
|
<ListBox.ItemTemplate>
|
||||||
|
<DataTemplate DataType="{x:Type model1:User}">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="10" Margin="5">
|
||||||
|
<TextBlock Text="{Binding Username}" Width="150"/>
|
||||||
|
<TextBlock Text="{Binding Abteilung}" Width="150"/>
|
||||||
|
<TextBlock Text="{Binding Mitarbeiternummer}" Width="150"/>
|
||||||
|
<Button Content="🖋 Bearbeiten" Tag="{Binding}" Click="Bearbeiten_Click"/>
|
||||||
|
<Button Content="🗑 Löschen" Tag="{Binding}" Click="Loeschen_Click"/>
|
||||||
|
<Button Content="🔑 Passwort zurücksetzen" Tag="{Binding}" Click="PasswortReset_Click" />
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
</ListBox>
|
||||||
|
</ScrollViewer>
|
||||||
|
|
||||||
|
<Button Grid.Row="3" Content="⬅ Zurück zum Dashboard" Click="ZurueckButton_Click" HorizontalAlignment="Center" Margin="0,10,0,0"/>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
143
ChronoFlow.View/Admin/MitarbeiterListeView.axaml.cs
Normal file
143
ChronoFlow.View/Admin/MitarbeiterListeView.axaml.cs
Normal file
@ -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<User> _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<User>((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<bool>((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<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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
28
ChronoFlow.View/Admin/ProjektBearbeitenDialog.axaml
Normal file
28
ChronoFlow.View/Admin/ProjektBearbeitenDialog.axaml
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
x:Class="ChronoFlow.View.Admin.ProjektBearbeitenDialog"
|
||||||
|
Width="500" Height="600"
|
||||||
|
Title="Projekt bearbeiten">
|
||||||
|
|
||||||
|
<StackPanel Margin="20" Spacing="12">
|
||||||
|
<TextBlock Text="Projektname:" />
|
||||||
|
<TextBox x:Name="ProjektnameBox" />
|
||||||
|
|
||||||
|
<TextBlock Text="Kommentar:" />
|
||||||
|
<TextBox x:Name="KommentarBox" AcceptsReturn="True" Height="80" />
|
||||||
|
|
||||||
|
<TextBlock Text="Mitarbeiter auswählen:" />
|
||||||
|
<ComboBox x:Name="MitarbeiterDropdown" />
|
||||||
|
|
||||||
|
<TextBlock Text="Startdatum:" />
|
||||||
|
<DatePicker x:Name="StartzeitPicker" />
|
||||||
|
|
||||||
|
<TextBlock Text="Enddatum:" />
|
||||||
|
<DatePicker x:Name="EndzeitPicker" />
|
||||||
|
|
||||||
|
<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>
|
||||||
|
</Window>
|
||||||
47
ChronoFlow.View/Admin/ProjektBearbeitenDialog.axaml.cs
Normal file
47
ChronoFlow.View/Admin/ProjektBearbeitenDialog.axaml.cs
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
39
ChronoFlow.View/Admin/ProjektErstellenView.axaml
Normal file
39
ChronoFlow.View/Admin/ProjektErstellenView.axaml
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
x:Class="ChronoFlow.View.Admin.ProjektErstellenView">
|
||||||
|
|
||||||
|
<StackPanel Margin="20" Spacing="10">
|
||||||
|
|
||||||
|
<TextBlock Text="Neues Projekt erstellen" FontSize="20" FontWeight="Bold" HorizontalAlignment="Center" />
|
||||||
|
|
||||||
|
<TextBlock Text="Projektname:" />
|
||||||
|
<TextBox x:Name="ProjektnameBox" />
|
||||||
|
|
||||||
|
<TextBlock Text="Startdatum:" />
|
||||||
|
<DatePicker x:Name="StartdatumPicker" />
|
||||||
|
|
||||||
|
<TextBlock Text="Startzeit (HH:mm):" />
|
||||||
|
<TextBox x:Name="StartzeitBox" Watermark="z.B. 09:00" />
|
||||||
|
|
||||||
|
<TextBlock Text="Enddatum:" />
|
||||||
|
<DatePicker x:Name="EnddatumPicker" />
|
||||||
|
|
||||||
|
<TextBlock Text="Endzeit (HH:mm):" />
|
||||||
|
<TextBox x:Name="EndzeitBox" Watermark="z.B. 17:00" />
|
||||||
|
|
||||||
|
<TextBlock Text="Mitarbeiter auswählen:" />
|
||||||
|
<ComboBox x:Name="MitarbeiterDropdown" />
|
||||||
|
|
||||||
|
<TextBlock Text="Kommentar:" />
|
||||||
|
<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" />
|
||||||
|
<Button Content="⬅ Zurück zum Dashboard" Click="ZurueckButton_Click" Width="150" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<TextBlock x:Name="FeedbackText" Foreground="Red" IsVisible="False" />
|
||||||
|
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
</UserControl>
|
||||||
102
ChronoFlow.View/Admin/ProjektErstellenView.axaml.cs
Normal file
102
ChronoFlow.View/Admin/ProjektErstellenView.axaml.cs
Normal file
@ -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<string> 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<AdminMainView>("AdminMain", out var adminView))
|
||||||
|
{
|
||||||
|
adminView.AktualisiereLetzteProjekte();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ZurueckButton_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_viewManager.Show("AdminMain");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,10 +1,9 @@
|
|||||||
<Application xmlns="https://github.com/avaloniaui"
|
<Application xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:themes="clr-namespace:Avalonia.Themes.Fluent;assembly=Avalonia.Themes.Fluent"
|
||||||
x:Class="ChronoFlow.App"
|
x:Class="ChronoFlow.App"
|
||||||
RequestedThemeVariant="Default">
|
RequestedThemeVariant="Default">
|
||||||
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
|
|
||||||
|
|
||||||
<Application.Styles>
|
<Application.Styles>
|
||||||
<FluentTheme />
|
<themes:FluentTheme />
|
||||||
</Application.Styles>
|
</Application.Styles>
|
||||||
</Application>
|
</Application>
|
||||||
@ -1,37 +1,31 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>WinExe</OutputType>
|
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
|
||||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
|
||||||
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Avalonia" Version="11.2.7"/>
|
<PackageReference Include="Avalonia" Version="11.0.6" />
|
||||||
<PackageReference Include="Avalonia.Desktop" Version="11.2.7"/>
|
<PackageReference Include="Avalonia.Desktop" Version="11.0.6" />
|
||||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.7"/>
|
<PackageReference Include="MessageBox.Avalonia" Version="0.10.4" />
|
||||||
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.2.7"/>
|
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.6" />
|
||||||
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
|
||||||
<PackageReference Include="Avalonia.Diagnostics" Version="11.2.7">
|
|
||||||
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
|
|
||||||
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
|
|
||||||
</PackageReference>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\ChronoFlow.Controller\ChronoFlow.Controller.csproj" />
|
<ProjectReference Include="..\ChronoFlow.Controller\ChronoFlow.Controller.csproj" />
|
||||||
<ProjectReference Include="..\ChronoFlow.Model\ChronoFlow.Model.csproj" />
|
<ProjectReference Include="..\ChronoFlow.Persistence\ChronoFlow.Persistence.csproj" />
|
||||||
|
<ProjectReference Include="..\ChronoFlow.Security\ChronoFlow.Security.csproj" />
|
||||||
|
<ProjectReference Include="..\ChronoFlow.Model\ChronoFlow.Model.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Assets\" />
|
<Compile Update="MitarbeiterHinzufuegenView.axaml.cs">
|
||||||
</ItemGroup>
|
<DependentUpon>MitarbeiterHinzufuegenView.axaml</DependentUpon>
|
||||||
|
<SubType>Code</SubType>
|
||||||
<ItemGroup>
|
|
||||||
<Compile Update="ZeiterfassungView.axaml.cs">
|
|
||||||
<DependentUpon>ZeiterfassungView.axaml</DependentUpon>
|
|
||||||
</Compile>
|
</Compile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@ -1,18 +1,20 @@
|
|||||||
<!-- Datei: View/LoginWindow.axaml -->
|
|
||||||
<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.LoginWindow"
|
x:Class="ChronoFlow.View.LoginWindow"
|
||||||
Title="ChronoFlow Login" Width="400" Height="250" WindowStartupLocation="CenterScreen">
|
Width="400" Height="300"
|
||||||
|
Title="ChronoFlow Login">
|
||||||
|
|
||||||
<StackPanel Margin="20" Spacing="10">
|
<StackPanel Margin="20" Spacing="10">
|
||||||
<TextBlock Text="ChronoFlow – Login" FontWeight="Bold" FontSize="18" HorizontalAlignment="Center"/>
|
<TextBlock Text="ChronoFlow Login" FontSize="24" FontWeight="Bold" HorizontalAlignment="Center" />
|
||||||
|
|
||||||
<TextBox x:Name="UsernameBox" Watermark="Benutzername"/>
|
<TextBlock Text="Benutzername" />
|
||||||
<TextBlock Text="PasswordBox" FontSize="18" TextAlignment="Center" Margin="0,0,0,10"/>
|
<TextBox x:Name="UsernameBox" />
|
||||||
<TextBox x:Name = "PasswordBox" PasswordChar="*" Text="Enabled"/>
|
|
||||||
|
|
||||||
<TextBlock x:Name="ErrorText" Foreground="Red" IsVisible="False"/>
|
<TextBlock Text="Passwort" />
|
||||||
|
<TextBox x:Name="PasswordBox" PasswordChar="●" />
|
||||||
|
|
||||||
<Button Content="Anmelden" Click="LoginButton_Click" HorizontalAlignment="Center"/>
|
<Button Content="Anmelden" Click="LoginButton_Click" HorizontalAlignment="Center" Width="120" Margin="0,10,0,0" />
|
||||||
|
|
||||||
|
<TextBlock x:Name="ErrorText" Foreground="Red" IsVisible="False" TextWrapping="Wrap" TextAlignment="Center" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Window>
|
</Window>
|
||||||
@ -1,8 +1,11 @@
|
|||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using ChronoFlow.Controller;
|
using ChronoFlow.Controller;
|
||||||
using ChronoFlow.Persistence;
|
using ChronoFlow.Persistence;
|
||||||
|
using ChronoFlow.Security;
|
||||||
|
using ChronoFlow.View.Security;
|
||||||
|
|
||||||
namespace ChronoFlow.View
|
namespace ChronoFlow.View
|
||||||
{
|
{
|
||||||
@ -11,28 +14,28 @@ namespace ChronoFlow.View
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class LoginWindow : Window
|
public partial class LoginWindow : Window
|
||||||
{
|
{
|
||||||
private LoginController _loginController;
|
private readonly LoginController _loginController;
|
||||||
|
|
||||||
public LoginWindow()
|
public LoginWindow()
|
||||||
{
|
{
|
||||||
InitializeComponent(); // Verbindet XAML mit diesem Code
|
InitializeComponent();
|
||||||
_loginController = new LoginController(); // Unsere "Logik-Klasse"
|
_loginController = new LoginController();
|
||||||
|
|
||||||
var service = new SqliteZeiterfassungsService();
|
var service = new SqliteZeiterfassungsService();
|
||||||
service.ErstelleStandardAdmin();
|
service.ErstelleStandardAdmin();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Wird ausgeführt, wenn der Benutzer auf "Anmelden" klickt.
|
/// Wird ausgeführt, wenn der Benutzer auf "Anmelden" klickt.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void LoginButton_Click(object? sender, RoutedEventArgs e)
|
private async void LoginButton_Click(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
var username = UsernameBox.Text?.Trim();
|
var username = UsernameBox.Text?.Trim();
|
||||||
var password = PasswordBox.Text?.Trim();
|
var password = PasswordBox.Text?.Trim();
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
|
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
|
||||||
{
|
{
|
||||||
ErrorText.Text = "Bitte Benutzername und Passwort eingeben";
|
ErrorText.Text = "Bitte Benutzername und Passwort eingeben.";
|
||||||
ErrorText.IsVisible = true;
|
ErrorText.IsVisible = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -40,19 +43,80 @@ namespace ChronoFlow.View
|
|||||||
var service = new SqliteZeiterfassungsService();
|
var service = new SqliteZeiterfassungsService();
|
||||||
var benutzerListe = service.LadeAlleBenutzer();
|
var benutzerListe = service.LadeAlleBenutzer();
|
||||||
|
|
||||||
var user = benutzerListe.FirstOrDefault(u => u.Username == username && u.Password == password);
|
var matchingUsers = benutzerListe.Where(u => u.Username == username).ToList();
|
||||||
|
|
||||||
if (user != null)
|
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<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...");
|
||||||
var main = new MainWindow(user);
|
var main = new MainWindow(user);
|
||||||
main.Show();
|
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
|
Console.WriteLine($"[ERROR] MainWindow konnte nicht geöffnet werden: {ex.Message}");
|
||||||
ErrorText.Text = "Login fehlgeschlagen. Bitte prüfen Sie Ihre Eingaben.";
|
ErrorText.Text = "Interner Fehler beim Starten des Hauptfensters.";
|
||||||
ErrorText.IsVisible = true;
|
ErrorText.IsVisible = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,25 +2,12 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
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"
|
x:Class="ChronoFlow.View.MainWindow"
|
||||||
Title="ChronoFlow.View">
|
Title="ChronoFlow">
|
||||||
|
|
||||||
<SplitView x:Name="PaneView" DisplayMode="CompactInline" IsPaneOpen="True" CompactPaneLength="38" OpenPaneLength="200">
|
<Grid>
|
||||||
|
<ContentControl x:Name="ContentArea" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
<SplitView.Pane>
|
|
||||||
<StackPanel>
|
|
||||||
<Button Content="☰" Click="PaneOpenClose_Click"/>
|
|
||||||
<Button Content="⏱ Zeiterfassung" Click="Zeiterfassung_Click"/>
|
|
||||||
<Button Content="📄 Auswertung"/>
|
|
||||||
<Button Content="👤 Mitarbeiter hinzufügen" Click="MitarbeiterHinzufuegen_Click"/>
|
|
||||||
<Button Content="⚙ Einstellungen"/>
|
|
||||||
</StackPanel>
|
|
||||||
</SplitView.Pane>
|
|
||||||
|
|
||||||
<SplitView.Content>
|
|
||||||
<ContentControl x:Name="ContentArea" />
|
|
||||||
</SplitView.Content>
|
|
||||||
|
|
||||||
</SplitView>
|
|
||||||
</Window>
|
</Window>
|
||||||
@ -1,54 +1,63 @@
|
|||||||
|
using System;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using ChronoFlow.Model;
|
using ChronoFlow.Model;
|
||||||
|
using ChronoFlow.View.Admin;
|
||||||
|
|
||||||
namespace ChronoFlow.View;
|
namespace ChronoFlow.View
|
||||||
|
|
||||||
public partial class MainWindow : Window
|
|
||||||
{
|
{
|
||||||
private readonly ViewManager _viewManager;
|
public partial class MainWindow : Window
|
||||||
private readonly User _loggedInUser;
|
|
||||||
|
|
||||||
public MainWindow(User user)
|
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
private readonly ViewManager _viewManager;
|
||||||
|
private readonly User _loggedInUser;
|
||||||
|
|
||||||
_loggedInUser = user;
|
public MainWindow(User 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
|
|
||||||
{
|
{
|
||||||
Text = $"Willkommen bei ChronoFlow, {currentUser.Username}!",
|
InitializeComponent();
|
||||||
FontSize = 24,
|
|
||||||
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center,
|
|
||||||
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fenstertitel dynamisch setzen
|
_loggedInUser = user;
|
||||||
this.Title = $"ChronoFlow - Willkommen {currentUser.Username} ({currentUser.Role})";
|
Console.WriteLine($"[DEBUG] MainWindow gestartet für Benutzer: {_loggedInUser.Username} ({_loggedInUser.Role})");
|
||||||
}
|
|
||||||
|
|
||||||
private void PaneOpenClose_Click(object sender, RoutedEventArgs e)
|
_viewManager = new ViewManager(ContentArea);
|
||||||
{
|
|
||||||
PaneView.IsPaneOpen = !PaneView.IsPaneOpen;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Zeiterfassung_Click(object? sender, RoutedEventArgs e)
|
_viewManager.Register("ProjektErstellen", () => new ProjektErstellenView(_viewManager));
|
||||||
{
|
_viewManager.Register("MitarbeiterHinzufuegen", () => new MitarbeiterHinzufuegenView());
|
||||||
_viewManager.Show("Zeiterfassung");
|
_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)
|
if (_loggedInUser.Role == "Admin")
|
||||||
{
|
{
|
||||||
_viewManager.Show("MitarbeiterHinzufuegen");
|
_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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -17,6 +17,7 @@
|
|||||||
<TextBox x:Name="AbteilungBox" Watermark="Abteilung"/>
|
<TextBox x:Name="AbteilungBox" Watermark="Abteilung"/>
|
||||||
|
|
||||||
<Button Content="💾 Speichern" Click="SpeichernButton_Click" HorizontalAlignment="Center"/>
|
<Button Content="💾 Speichern" Click="SpeichernButton_Click" HorizontalAlignment="Center"/>
|
||||||
|
<Button Content="⬅ Zurück zum Dashboard" Click="ZurueckZumDashboard_Click" HorizontalAlignment="Center" Margin="0,10,0,0"/>
|
||||||
<TextBlock x:Name="FeedbackText" Foreground="Green" IsVisible="False" TextAlignment="Center"/>
|
<TextBlock x:Name="FeedbackText" Foreground="Green" IsVisible="False" TextAlignment="Center"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@ -3,8 +3,8 @@ using Avalonia.Controls;
|
|||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
using ChronoFlow.Model;
|
using ChronoFlow.Model;
|
||||||
using Microsoft.Data.Sqlite;
|
|
||||||
using ChronoFlow.Persistence;
|
using ChronoFlow.Persistence;
|
||||||
|
using ChronoFlow.Security;
|
||||||
|
|
||||||
namespace ChronoFlow.View
|
namespace ChronoFlow.View
|
||||||
{
|
{
|
||||||
@ -16,63 +16,74 @@ namespace ChronoFlow.View
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void SpeichernButton_Click(object? sender, RoutedEventArgs e)
|
private void SpeichernButton_Click(object? sender, RoutedEventArgs e)
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var service = new SqliteZeiterfassungsService();
|
|
||||||
|
|
||||||
string username = UsernameBox.Text?.Trim() ?? "";
|
|
||||||
string password = PasswordBox.Text?.Trim() ?? "";
|
|
||||||
string rolle = (RoleBox.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "";
|
|
||||||
string mitarbeiternummer = MitarbeiternummerBox.Text?.Trim() ?? "";
|
|
||||||
string abteilung = AbteilungBox.Text?.Trim() ?? "";
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password) || string.IsNullOrWhiteSpace(rolle))
|
|
||||||
{
|
{
|
||||||
FeedbackText.Text = "⚠ Bitte alle Pflichtfelder ausfüllen!";
|
try
|
||||||
FeedbackText.Foreground = Brushes.Red;
|
{
|
||||||
FeedbackText.IsVisible = true;
|
var service = new SqliteZeiterfassungsService();
|
||||||
return;
|
|
||||||
|
string username = UsernameBox.Text?.Trim() ?? "";
|
||||||
|
string password = PasswordBox.Text?.Trim() ?? "";
|
||||||
|
string rolle = (RoleBox.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "";
|
||||||
|
string mitarbeiternummer = MitarbeiternummerBox.Text?.Trim() ?? "";
|
||||||
|
string abteilung = AbteilungBox.Text?.Trim() ?? "";
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password) || string.IsNullOrWhiteSpace(rolle))
|
||||||
|
{
|
||||||
|
FeedbackText.Text = "⚠ Bitte alle Pflichtfelder ausfüllen!";
|
||||||
|
FeedbackText.Foreground = Brushes.Red;
|
||||||
|
FeedbackText.IsVisible = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var neuerBenutzer = new User
|
||||||
|
{
|
||||||
|
Username = username,
|
||||||
|
Password = PasswordHasher.HashPassword(password),
|
||||||
|
Role = rolle,
|
||||||
|
Mitarbeiternummer = mitarbeiternummer,
|
||||||
|
Abteilung = abteilung,
|
||||||
|
MussPasswortAendern = true
|
||||||
|
};
|
||||||
|
|
||||||
|
service.ErstelleNeuenBenutzer(neuerBenutzer);
|
||||||
|
|
||||||
|
FeedbackText.Text = "✅ Mitarbeiter erfolgreich gespeichert.";
|
||||||
|
FeedbackText.Foreground = Brushes.Green;
|
||||||
|
FeedbackText.IsVisible = true;
|
||||||
|
|
||||||
|
ClearFields();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
FeedbackText.Text = $"❌ Fehler: {ex.Message}";
|
||||||
|
FeedbackText.Foreground = Brushes.Red;
|
||||||
|
FeedbackText.IsVisible = true;
|
||||||
|
|
||||||
|
Console.WriteLine("❌ Ausnahme beim Speichern:");
|
||||||
|
Console.WriteLine(ex.ToString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ❗ Hier neue Prüfung, ob Benutzername existiert:
|
private void ClearFields()
|
||||||
if (service.BenutzernameExistiert(username))
|
|
||||||
{
|
{
|
||||||
FeedbackText.Text = "⚠ Benutzername existiert bereits!";
|
UsernameBox.Text = "";
|
||||||
FeedbackText.Foreground = Brushes.Red;
|
PasswordBox.Text = "";
|
||||||
FeedbackText.IsVisible = true;
|
MitarbeiternummerBox.Text = "";
|
||||||
return;
|
AbteilungBox.Text = "";
|
||||||
|
RoleBox.SelectedIndex = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
using var connection = new SqliteConnection("Data Source=chrono_data.sb");
|
private void ZurueckZumDashboard_Click(object? sender, RoutedEventArgs e)
|
||||||
connection.Open();
|
{
|
||||||
|
var mainWindow = this.VisualRoot as MainWindow;
|
||||||
var cmd = connection.CreateCommand();
|
if (mainWindow != null)
|
||||||
cmd.CommandText = @"
|
{
|
||||||
INSERT INTO Benutzer (Username, Password, Role, Mitarbeiternummer, Abteilung)
|
mainWindow.ShowAdminDashboard();
|
||||||
VALUES ($Username, $Password, $Role, $Mitarbeiternummer, $Abteilung);";
|
}
|
||||||
|
else
|
||||||
cmd.Parameters.AddWithValue("$Username", username);
|
{
|
||||||
cmd.Parameters.AddWithValue("$Password", password);
|
Console.WriteLine("⚠️ MainWindow nicht gefunden!");
|
||||||
cmd.Parameters.AddWithValue("$Role", rolle);
|
}
|
||||||
cmd.Parameters.AddWithValue("$Mitarbeiternummer", mitarbeiternummer);
|
}
|
||||||
cmd.Parameters.AddWithValue("$Abteilung", abteilung);
|
|
||||||
cmd.ExecuteNonQuery();
|
|
||||||
|
|
||||||
FeedbackText.Text = "✅ Mitarbeiter erfolgreich gespeichert.";
|
|
||||||
FeedbackText.Foreground = Brushes.Green;
|
|
||||||
FeedbackText.IsVisible = true;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
FeedbackText.Text = $"❌ Fehler: {ex.Message}";
|
|
||||||
FeedbackText.Foreground = Brushes.Red;
|
|
||||||
FeedbackText.IsVisible = true;
|
|
||||||
|
|
||||||
Console.WriteLine("❌ Ausnahme beim Speichern:");
|
|
||||||
Console.WriteLine(ex.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,21 +1,23 @@
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using System;
|
using System;
|
||||||
|
using ChronoFlow.Persistence;
|
||||||
|
|
||||||
namespace ChronoFlow.View;
|
namespace ChronoFlow.View;
|
||||||
|
|
||||||
class Program
|
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]
|
[STAThread]
|
||||||
public static void Main(string[] args) => BuildAvaloniaApp()
|
public static void Main(string[] args)
|
||||||
.StartWithClassicDesktopLifetime(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()
|
public static AppBuilder BuildAvaloniaApp()
|
||||||
=> AppBuilder.Configure<App>()
|
=> AppBuilder.Configure<App>()
|
||||||
.UsePlatformDetect()
|
.UsePlatformDetect()
|
||||||
.WithInterFont()
|
|
||||||
.LogToTrace();
|
.LogToTrace();
|
||||||
}
|
}
|
||||||
23
ChronoFlow.View/Security/PasswortAendernDialog.axaml
Normal file
23
ChronoFlow.View/Security/PasswortAendernDialog.axaml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
x:Class="ChronoFlow.View.Security.PasswortAendernDialog"
|
||||||
|
Width="400" Height="250"
|
||||||
|
Title="Passwort ändern">
|
||||||
|
|
||||||
|
<StackPanel Margin="20" Spacing="10">
|
||||||
|
<TextBlock x:Name="UsernameTextBlock" Text="Benutzer: " FontSize="16" FontWeight="Bold" />
|
||||||
|
|
||||||
|
<TextBlock Text="Neues Passwort:" />
|
||||||
|
<TextBox x:Name="NeuesPasswortBox" />
|
||||||
|
|
||||||
|
<TextBlock Text="Passwort bestätigen:" />
|
||||||
|
<TextBox x:Name="BestaetigenBox" />
|
||||||
|
|
||||||
|
<TextBlock x:Name="FehlerText" Foreground="Red" IsVisible="False" />
|
||||||
|
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="10" Margin="0,10,0,0">
|
||||||
|
<Button Content="💾 Speichern" Click="SpeichernButton_Click" Width="100" />
|
||||||
|
<Button Content="❌ Abbrechen" Click="AbbrechenButton_Click" Width="100" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</Window>
|
||||||
46
ChronoFlow.View/Security/PasswortAendernDialog.axaml.cs
Normal file
46
ChronoFlow.View/Security/PasswortAendernDialog.axaml.cs
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,49 +1,62 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Microsoft.Data.Sqlite;
|
|
||||||
|
|
||||||
namespace ChronoFlow.View
|
namespace ChronoFlow.View
|
||||||
{
|
{
|
||||||
///<summary>
|
|
||||||
/// Verwaltet alle Views der Anwendung und wechselt sie bei Bedarf.
|
|
||||||
/// </summary>
|
|
||||||
|
|
||||||
public class ViewManager
|
public class ViewManager
|
||||||
{
|
{
|
||||||
private readonly ContentControl _targetControl;
|
private readonly ContentControl _contentControl;
|
||||||
private readonly Dictionary<string, Func<UserControl>> _registieredViews = new();
|
|
||||||
|
|
||||||
public ViewManager(ContentControl targetControl)
|
// Dictionary speichert die registrierten Views mit ihren Erstellungs-Methoden (Factories)
|
||||||
|
private readonly Dictionary<string, Func<UserControl>> _views = new();
|
||||||
|
|
||||||
|
public ViewManager(ContentControl contentControl)
|
||||||
{
|
{
|
||||||
_targetControl = targetControl;
|
_contentControl = contentControl;
|
||||||
}
|
}
|
||||||
|
|
||||||
///<summary>
|
/// <summary>
|
||||||
/// Registriert eine View mit einem Namen
|
/// Registriert eine View mit einem eindeutigen Namen.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
public void Register(string name, Func<UserControl> factory)
|
||||||
public void Show(string name, Func<UserControl> viewFactory)
|
|
||||||
{
|
{
|
||||||
_registieredViews[name] = viewFactory;
|
if (!_views.ContainsKey(name))
|
||||||
|
{
|
||||||
|
_views[name] = factory;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///<summary>
|
/// <summary>
|
||||||
/// Zeigt die View mit dem gegebenen Namen an.
|
/// Zeigt eine registrierte View an.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Show(string name)
|
public void Show(string name)
|
||||||
{
|
{
|
||||||
if(_registieredViews.TryGetValue(name, out var factory))
|
if (_views.TryGetValue(name, out var factory))
|
||||||
_targetControl.Content = factory();
|
{
|
||||||
|
var view = factory();
|
||||||
|
_contentControl.Content = view;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"View {name} is not registered");
|
throw new InvalidOperationException($"View {name} is not registered");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public void Register(string name, Func<UserControl> viewFactory)
|
|
||||||
{
|
|
||||||
_registieredViews[name] = viewFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Holt eine bereits registrierte View als konkreten Typ (z. B. AdminMainView).
|
||||||
|
/// </summary>
|
||||||
|
public bool TryGetView<T>(string name, out T? view) where T : class
|
||||||
|
{
|
||||||
|
if (_views.TryGetValue(name, out var factory))
|
||||||
|
{
|
||||||
|
var instance = factory();
|
||||||
|
view = instance as T;
|
||||||
|
return view != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
view = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,44 +0,0 @@
|
|||||||
<UserControl xmlns="https://github.com/avaloniaui"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
x:Class="ChronoFlow.View.ZeiterfassungView"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
xmlns:views="clr-namespace:ChronoFlow.View"
|
|
||||||
mc:Ignorable="d">
|
|
||||||
|
|
||||||
<StackPanel x:Name="EingabePanel" Spacing="10">
|
|
||||||
<TextBlock Text="Zeiterfassung" FontWeight="Bold" FontSize="20" HorizontalAlignment="Center"/>
|
|
||||||
<ComboBox x:Name="MitarbeiterBoxDropdown"
|
|
||||||
Width="250"
|
|
||||||
PlaceholderText="Mitarbeitername auswählen"
|
|
||||||
Margin="0,5"/>
|
|
||||||
<DatePicker x:Name="DatumPicker"/>
|
|
||||||
<TextBox x:Name="StartzeitBox" Watermark="Startzeit (z.B. 08:00)"/>
|
|
||||||
<TextBox x:Name="EndzeitBox" Watermark="Endzeit (z.B. 16:30)"/>
|
|
||||||
<TextBox x:Name="ProjektBox" Watermark="Projektname"/>
|
|
||||||
<TextBox x:Name="KommentarBox" Watermark="Kommentar (Optional)"/>
|
|
||||||
<Button Content="Eintrag speichern" Click="SpeichernButton_Click" HorizontalAlignment="Center"/>
|
|
||||||
<TextBlock x:Name="FeedbackText" Foreground="Green" IsVisible="False"/>
|
|
||||||
|
|
||||||
<TextBlock Text="Gespeicherte Einträge:" FontWeight="Bold" Margin="0,20,0,5"></TextBlock>
|
|
||||||
<ListBox x:Name="Eintragsliste" Margin="0,20,0,0">
|
|
||||||
<ListBox.ItemTemplate>
|
|
||||||
<DataTemplate>
|
|
||||||
<Border BorderBrush="Gray" BorderThickness="1" CornerRadius="4" Padding="8" Margin="4">
|
|
||||||
<StackPanel>
|
|
||||||
<!-- Anzeige -->
|
|
||||||
<TextBlock Text="{Binding}" FontWeight="Bold"></TextBlock>
|
|
||||||
|
|
||||||
<!-- Nur für Mitarbeiter sichtbar: Status-Buttons + Kommentar -->
|
|
||||||
<StackPanel Orientation="Horizontal" Spacing="8" Margin="0,5,0,0">
|
|
||||||
<Button Content="✅" Click="MarkiereErledigt_Click"/>
|
|
||||||
<Button Content="❌" Click="MarkiereNichtErledigt_Click"/>
|
|
||||||
</StackPanel>
|
|
||||||
<TextBox x:Name="KommentarEingabe" Watermark="Kommentar" LostFocus="Kommentar_LostFocus"/>
|
|
||||||
</StackPanel>
|
|
||||||
</Border>
|
|
||||||
</DataTemplate>
|
|
||||||
</ListBox.ItemTemplate>
|
|
||||||
</ListBox>
|
|
||||||
</StackPanel>
|
|
||||||
</UserControl>
|
|
||||||
@ -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<Zeiteintrag> _anzeigeEinträge;
|
|
||||||
private readonly User _user;
|
|
||||||
|
|
||||||
public ZeiterfassungView(User user)
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
_user = user;
|
|
||||||
|
|
||||||
_controller = new ZeiterfassungsController();
|
|
||||||
_anzeigeEinträge = new ObservableCollection<Zeiteintrag>();
|
|
||||||
|
|
||||||
// ✅ 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -8,6 +8,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChronoFlow.Persistence", "C
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChronoFlow.Controller", "ChronoFlow.Controller\ChronoFlow.Controller.csproj", "{BCCF491C-6A5D-45E4-B490-C553A550F559}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChronoFlow.Controller", "ChronoFlow.Controller\ChronoFlow.Controller.csproj", "{BCCF491C-6A5D-45E4-B490-C553A550F559}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChronoFlow.Security", "ChronoFlow.Security\ChronoFlow.Security.csproj", "{51F4750C-938D-451F-8E32-B7FB7436D125}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
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}.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.ActiveCfg = Release|Any CPU
|
||||||
{BCCF491C-6A5D-45E4-B490-C553A550F559}.Release|Any CPU.Build.0 = 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
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|||||||
5
ChronoFlow.sln.DotSettings.user
Normal file
5
ChronoFlow.sln.DotSettings.user
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASqliteCommand_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F7af32a60b614a4736554243e7b8aba5c9a167efe6e7254e6648651482183_003FSqliteCommand_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASqliteException_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F154220569126135ad5d7314bf2bc694d3cf7c95840d481d44f0336f4f1f8e9c_003FSqliteException_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=8201A15C_002D62F0_002D4397_002DA6E3_002DE8B34C171052_002Fd_003AAdmin_002Ff_003AAdminMainView_002Eaxaml_002Fz_003A2_002D0/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=8201A15C_002D62F0_002D4397_002DA6E3_002DE8B34C171052_002Fd_003AAdmin_002Ff_003AProjektErstellenView_002Eaxaml_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>
|
||||||
Loading…
Reference in New Issue
Block a user