Updates
This commit is contained in:
parent
549ab3e53b
commit
b206a43e47
@ -27,9 +27,13 @@ namespace ChronoFlow.Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Diese Methode lädt alle bisherigen Zeiteinträge aus der Datenbank
|
// Diese Methode lädt alle bisherigen Zeiteinträge aus der Datenbank
|
||||||
public List<Zeiteintrag> LadeAlleEintraege()
|
public async Task<List<Zeiteintrag>> LadeAlleEintraegeAsync()
|
||||||
{
|
{
|
||||||
// Holt die Liste der Einträge von der Datenbank und gibt sie zurück
|
// Holt die Liste der Einträge von der Datenbank und gibt sie zurück
|
||||||
|
return await _sqliteService.LadeAlleEintraegeAsync();
|
||||||
|
}
|
||||||
|
public List<Zeiteintrag> LadeAlleEintraege()
|
||||||
|
{
|
||||||
return _sqliteService.LadeAlleZeiteintraege();
|
return _sqliteService.LadeAlleZeiteintraege();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,28 +1,95 @@
|
|||||||
namespace ChronoFlow.Model
|
namespace ChronoFlow.Model
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Datenmodell für einen Zeiteintrag / ein Projekt.
|
||||||
|
/// Wird z.B. zur Speicherung in SQLite verwendet.
|
||||||
|
/// </summary>
|
||||||
public class Zeiteintrag
|
public class Zeiteintrag
|
||||||
{
|
{
|
||||||
public int Id { get; set; } // <<< NEU
|
/// <summary>
|
||||||
|
/// Eindeutige ID des Eintrags in der Datenbank.
|
||||||
|
/// </summary>
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Name des zugewiesenen Mitarbeiters.
|
||||||
|
/// </summary>
|
||||||
public string Mitarbeiter { get; set; }
|
public string Mitarbeiter { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Name des Projektleiters (neu hinzugefügt).
|
||||||
|
/// </summary>
|
||||||
|
public string Projektleiter { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Zeitpunkt, wann das Projekt beginnt.
|
||||||
|
/// </summary>
|
||||||
public DateTime Startzeit { get; set; }
|
public DateTime Startzeit { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Zeitpunkt, wann das Projekt endet.
|
||||||
|
/// </summary>
|
||||||
public DateTime Endzeit { get; set; }
|
public DateTime Endzeit { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Name oder Titel des Projekts.
|
||||||
|
/// </summary>
|
||||||
public string Projekt { get; set; }
|
public string Projekt { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Beschreibung oder Kommentar durch den Admin.
|
||||||
|
/// </summary>
|
||||||
public string Kommentar { get; set; }
|
public string Kommentar { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gibt an, ob das Projekt als erledigt markiert wurde.
|
||||||
|
/// </summary>
|
||||||
public bool Erledigt { get; set; }
|
public bool Erledigt { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Kommentar vom Mitarbeiter (z.B. Rückmeldung zum Projekt).
|
||||||
|
/// </summary>
|
||||||
public string MitarbeiterKommentar { get; set; }
|
public string MitarbeiterKommentar { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Zeitpunkt der letzten Änderung an diesem Eintrag.
|
||||||
|
/// </summary>
|
||||||
|
public DateTime LetzteBearbeitung { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Kennzeichnet, ob es sich um einen neu erzeugten Eintrag handelt (z.B. seit dem letzten Login).
|
||||||
|
/// </summary>
|
||||||
|
public bool IstNeu { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// True, wenn der Eintrag seit dem letzten Login verändert wurde.
|
||||||
|
/// </summary>
|
||||||
|
public bool WurdeSeitLoginBearbeitet { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Konstruktor – Initialisiert alle Felder mit Standardwerten.
|
||||||
|
/// </summary>
|
||||||
public Zeiteintrag()
|
public Zeiteintrag()
|
||||||
{
|
{
|
||||||
Id = 0;
|
Id = 0;
|
||||||
Mitarbeiter = "";
|
Mitarbeiter = "";
|
||||||
|
Projektleiter = ""; // <<< NEU
|
||||||
Startzeit = DateTime.Now;
|
Startzeit = DateTime.Now;
|
||||||
Endzeit = DateTime.Now;
|
Endzeit = DateTime.Now;
|
||||||
Projekt = "";
|
Projekt = "";
|
||||||
Kommentar = "";
|
Kommentar = "";
|
||||||
Erledigt = false;
|
Erledigt = false;
|
||||||
MitarbeiterKommentar = "";
|
MitarbeiterKommentar = "";
|
||||||
|
Projektleiter = "";
|
||||||
|
LetzteBearbeitung = DateTime.Now;
|
||||||
|
IstNeu = false;
|
||||||
|
WurdeSeitLoginBearbeitet = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gibt die Prioritätsfarbe basierend auf der verbleibenden Zeit bis zur Deadline zurück.
|
||||||
|
/// Rot = < 3 Tage, Orange = 3–7 Tage, Grün = > 7 Tage.
|
||||||
|
/// </summary>
|
||||||
public string PrioritaetsFarbe
|
public string PrioritaetsFarbe
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@ -37,8 +104,5 @@ namespace ChronoFlow.Model
|
|||||||
return "Green";
|
return "Green";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public DateTime LetzteBearbeitung { get; set; }
|
|
||||||
public bool IstNeu { get; set; } // wird im ViewModel gesetzt
|
|
||||||
public bool WurdeSeitLoginBearbeitet { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -5,14 +5,35 @@ using ChronoFlow.Model;
|
|||||||
|
|
||||||
namespace ChronoFlow.Persistence
|
namespace ChronoFlow.Persistence
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Repository-Interface für den Zugriff auf Zeiteinträge in der Datenbank.
|
||||||
|
/// Wird von der SqliteZeiterfassungsService-Klasse implementiert.
|
||||||
|
/// </summary>
|
||||||
public interface ITimeEntryRepository
|
public interface ITimeEntryRepository
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gibt alle Zeiteinträge für einen bestimmten Mitarbeiter zurück.
|
||||||
|
/// </summary>
|
||||||
Task<List<Zeiteintrag>> GetEntriesForUserAsync(string username);
|
Task<List<Zeiteintrag>> GetEntriesForUserAsync(string username);
|
||||||
Task UpdateEntryStatusAndCommentAsync(int id, bool isCompleted, string? comment);
|
|
||||||
|
|
||||||
// (Optional) Weitere Methoden für Admin-Funktionen:
|
/// <summary>
|
||||||
Task<List<Zeiteintrag>> GetAllEntriesAsync(); // Admin
|
/// Gibt alle Zeiteinträge aus der Datenbank zurück (Admin-Ansicht).
|
||||||
Task AddEntryAsync(Zeiteintrag entry); // Admin
|
/// </summary>
|
||||||
Task DeleteEntryAsync(int id); // Admin
|
Task<List<Zeiteintrag>> LadeAlleEintraegeAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fügt einen neuen Zeiteintrag in die Datenbank ein.
|
||||||
|
/// </summary>
|
||||||
|
Task AddEntryAsync(Zeiteintrag entry);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Löscht einen Zeiteintrag anhand seiner ID.
|
||||||
|
/// </summary>
|
||||||
|
Task DeleteEntryAsync(int id);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Aktualisiert den Status (erledigt) und den Kommentar eines Eintrags.
|
||||||
|
/// </summary>
|
||||||
|
Task UpdateEntryStatusAndCommentAsync(int id, bool isCompleted, string? comment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -7,26 +7,39 @@ using ChronoFlow.Security;
|
|||||||
|
|
||||||
namespace ChronoFlow.Persistence
|
namespace ChronoFlow.Persistence
|
||||||
{
|
{
|
||||||
|
// Schnittstelle für die Zeiterfassungsfunktionen
|
||||||
public interface IZeiterfassungsRepository
|
public interface IZeiterfassungsRepository
|
||||||
{
|
{
|
||||||
Task<List<Zeiteintrag>> GetEintraegeFuerMitarbeiterAsync(string mitarbeiterName);
|
Task<List<Zeiteintrag>> GetEintraegeFuerMitarbeiterAsync(string mitarbeiterName);
|
||||||
Task UpdateStatusUndKommentarAsync(int id, bool erledigt, string mitarbeiterKommentar);
|
Task UpdateStatusUndKommentarAsync(int id, bool erledigt, string mitarbeiterKommentar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implementierung des SQLite-Datenbankzugriffs für Benutzer und Zeiteinträge.
|
||||||
|
/// </summary>
|
||||||
public class SqliteZeiterfassungsService : IZeiterfassungsRepository
|
public class SqliteZeiterfassungsService : IZeiterfassungsRepository
|
||||||
{
|
{
|
||||||
|
// Pfad zur SQLite-Datenbankdatei
|
||||||
private readonly string _dbPath = "chrono_data.sb";
|
private readonly string _dbPath = "chrono_data.sb";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Konstruktor: Erstellt Datenbank falls nicht vorhanden und stellt die Struktur sicher.
|
||||||
|
/// </summary>
|
||||||
public SqliteZeiterfassungsService()
|
public SqliteZeiterfassungsService()
|
||||||
{
|
{
|
||||||
if (!File.Exists(_dbPath))
|
if (!File.Exists(_dbPath))
|
||||||
ErstelleDatenbank();
|
ErstelleDatenbank();
|
||||||
|
|
||||||
// IMMER prüfen, auch nach neuem Erstellen
|
// Auch bei bestehender Datei sicherstellen, dass alle Spalten vorhanden sind
|
||||||
PrüfeUndErweitereDatenbank();
|
PrüfeUndErweitereDatenbank();
|
||||||
|
|
||||||
_ = StelleDatenbankstrukturSicherAsync(); // Async-Aufruf ignorieren
|
// Async-Aufruf wird absichtlich nicht awaitet
|
||||||
|
_ = StelleDatenbankstrukturSicherAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Erstellt die Basis-Datenbankstruktur, falls Datei noch nicht existiert.
|
||||||
|
/// </summary>
|
||||||
private void ErstelleDatenbank()
|
private void ErstelleDatenbank()
|
||||||
{
|
{
|
||||||
Console.WriteLine("🛠️ ErstelleDatenbank wurde aufgerufen!");
|
Console.WriteLine("🛠️ ErstelleDatenbank wurde aufgerufen!");
|
||||||
@ -62,8 +75,10 @@ namespace ChronoFlow.Persistence
|
|||||||
cmd2.ExecuteNonQuery();
|
cmd2.ExecuteNonQuery();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void ErstelleNeuenBenutzer(User benutzer)
|
public void ErstelleNeuenBenutzer(User benutzer)
|
||||||
{
|
{
|
||||||
|
// Erstellt eine neue Benutzerzeile in der Datenbank
|
||||||
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
||||||
connection.Open();
|
connection.Open();
|
||||||
|
|
||||||
@ -81,13 +96,13 @@ namespace ChronoFlow.Persistence
|
|||||||
cmd.Parameters.AddWithValue("$MussPasswortAendern", benutzer.MussPasswortAendern ? 1 : 0);
|
cmd.Parameters.AddWithValue("$MussPasswortAendern", benutzer.MussPasswortAendern ? 1 : 0);
|
||||||
|
|
||||||
int rowsAffected = cmd.ExecuteNonQuery();
|
int rowsAffected = cmd.ExecuteNonQuery();
|
||||||
Console.WriteLine($"✅ Neuer Benutzer '{benutzer.Username}' wurde gespeichert (Rows affected: {rowsAffected}).");
|
Console.WriteLine(
|
||||||
|
$"✅ Neuer Benutzer '{benutzer.Username}' wurde gespeichert (Rows affected: {rowsAffected}).");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void PrüfeUndErweitereDatenbank()
|
private void PrüfeUndErweitereDatenbank()
|
||||||
{
|
{
|
||||||
|
// Stellt sicher, dass die Datenbank alle benötigten Spalten enthält
|
||||||
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
||||||
connection.Open();
|
connection.Open();
|
||||||
|
|
||||||
@ -99,8 +114,10 @@ namespace ChronoFlow.Persistence
|
|||||||
AddColumnIfMissing(connection, "Zeiteintraege", "LetzteBearbeitung", "TEXT");
|
AddColumnIfMissing(connection, "Zeiteintraege", "LetzteBearbeitung", "TEXT");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddColumnIfMissing(SqliteConnection connection, string tableName, string columnName, string columnType)
|
private void AddColumnIfMissing(SqliteConnection connection, string tableName, string columnName,
|
||||||
|
string columnType)
|
||||||
{
|
{
|
||||||
|
// Prüft, ob eine Spalte existiert und fügt sie bei Bedarf hinzu
|
||||||
var checkCmd = connection.CreateCommand();
|
var checkCmd = connection.CreateCommand();
|
||||||
checkCmd.CommandText = $"PRAGMA table_info({tableName});";
|
checkCmd.CommandText = $"PRAGMA table_info({tableName});";
|
||||||
|
|
||||||
@ -128,6 +145,7 @@ namespace ChronoFlow.Persistence
|
|||||||
|
|
||||||
public void ErstelleStandardAdmin()
|
public void ErstelleStandardAdmin()
|
||||||
{
|
{
|
||||||
|
// Erstellt einen Admin-Benutzer, wenn dieser noch nicht existiert
|
||||||
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
||||||
connection.Open();
|
connection.Open();
|
||||||
|
|
||||||
@ -158,6 +176,8 @@ namespace ChronoFlow.Persistence
|
|||||||
{
|
{
|
||||||
var benutzerListe = new List<User>();
|
var benutzerListe = new List<User>();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
||||||
connection.Open();
|
connection.Open();
|
||||||
|
|
||||||
@ -170,16 +190,7 @@ namespace ChronoFlow.Persistence
|
|||||||
using var reader = cmd.ExecuteReader();
|
using var reader = cmd.ExecuteReader();
|
||||||
while (reader.Read())
|
while (reader.Read())
|
||||||
{
|
{
|
||||||
// Absicherungen für mögliche NULL- oder ungültige Werte:
|
// Parsing wie gehabt
|
||||||
var letzterLogin = DateTime.MinValue;
|
|
||||||
var vorletzterLogin = DateTime.MinValue;
|
|
||||||
|
|
||||||
if (!reader.IsDBNull(7))
|
|
||||||
DateTime.TryParse(reader.GetString(7), out letzterLogin);
|
|
||||||
|
|
||||||
if (!reader.IsDBNull(8))
|
|
||||||
DateTime.TryParse(reader.GetString(8), out vorletzterLogin);
|
|
||||||
|
|
||||||
benutzerListe.Add(new User
|
benutzerListe.Add(new User
|
||||||
{
|
{
|
||||||
Id = reader.GetInt32(0),
|
Id = reader.GetInt32(0),
|
||||||
@ -189,16 +200,27 @@ namespace ChronoFlow.Persistence
|
|||||||
Mitarbeiternummer = reader.IsDBNull(4) ? "" : reader.GetString(4),
|
Mitarbeiternummer = reader.IsDBNull(4) ? "" : reader.GetString(4),
|
||||||
Abteilung = reader.IsDBNull(5) ? "" : reader.GetString(5),
|
Abteilung = reader.IsDBNull(5) ? "" : reader.GetString(5),
|
||||||
MussPasswortAendern = reader.GetInt32(6) == 1,
|
MussPasswortAendern = reader.GetInt32(6) == 1,
|
||||||
LetzterLogin = letzterLogin,
|
LetzterLogin = reader.IsDBNull(7) ? DateTime.MinValue : DateTime.Parse(reader.GetString(7)),
|
||||||
VorletzterLogin = vorletzterLogin,
|
VorletzterLogin = reader.IsDBNull(8) ? DateTime.MinValue : DateTime.Parse(reader.GetString(8)),
|
||||||
OriginalUsername = reader.GetString(1)
|
OriginalUsername = reader.GetString(1)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
catch (SqliteException ex) when (ex.SqliteErrorCode == 5)
|
||||||
|
{
|
||||||
|
Console.WriteLine("❌ Datenbank ist gesperrt. Bitte schließen Sie andere Programme, die auf die Datenbank zugreifen.");
|
||||||
|
throw new InvalidOperationException("Die Datenbank ist derzeit gesperrt – bitte schließen Sie andere Programme (z.B. DB Browser) und starten Sie die Anwendung neu.");
|
||||||
|
}
|
||||||
|
|
||||||
return benutzerListe;
|
return benutzerListe;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Aktualisiert einen vorhandenen Benutzer in der Datenbank.
|
||||||
|
/// </summary>
|
||||||
public void UpdateBenutzer(User benutzer)
|
public void UpdateBenutzer(User benutzer)
|
||||||
{
|
{
|
||||||
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
||||||
@ -228,6 +250,9 @@ namespace ChronoFlow.Persistence
|
|||||||
Console.WriteLine($"✏ Benutzer aktualisiert: {benutzer.Username} (Rows affected: {rowsAffected})");
|
Console.WriteLine($"✏ Benutzer aktualisiert: {benutzer.Username} (Rows affected: {rowsAffected})");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Löscht einen Benutzer anhand seines Benutzernamens.
|
||||||
|
/// </summary>
|
||||||
public void LoescheBenutzer(string username)
|
public void LoescheBenutzer(string username)
|
||||||
{
|
{
|
||||||
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
||||||
@ -241,6 +266,9 @@ namespace ChronoFlow.Persistence
|
|||||||
Console.WriteLine($"❌ Benutzer gelöscht: {username} (Rows affected: {rowsAffected})");
|
Console.WriteLine($"❌ Benutzer gelöscht: {username} (Rows affected: {rowsAffected})");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gibt eine Liste aller Benutzernamen mit Rolle 'Mitarbeiter' zurück.
|
||||||
|
/// </summary>
|
||||||
public List<string> LadeAlleMitarbeiterNamen()
|
public List<string> LadeAlleMitarbeiterNamen()
|
||||||
{
|
{
|
||||||
var namen = new List<string>();
|
var namen = new List<string>();
|
||||||
@ -260,33 +288,83 @@ namespace ChronoFlow.Persistence
|
|||||||
return namen;
|
return namen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Speichert einen neuen Zeiteintrag in der Datenbank.
|
||||||
|
/// </summary>
|
||||||
|
/// <summary>
|
||||||
|
/// Speichert einen neuen Zeiteintrag in der Datenbank.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="eintrag">Das zu speichernde Zeiteintrag-Objekt.</param>
|
||||||
public void SpeichereEintrag(Zeiteintrag eintrag)
|
public void SpeichereEintrag(Zeiteintrag eintrag)
|
||||||
{
|
{
|
||||||
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
||||||
connection.Open();
|
connection.Open();
|
||||||
|
|
||||||
|
// SQL-Befehl mit zusätzlicher Spalte „Projektleiter“
|
||||||
var cmd = connection.CreateCommand();
|
var cmd = connection.CreateCommand();
|
||||||
cmd.CommandText = @"
|
cmd.CommandText = @"
|
||||||
INSERT INTO Zeiteintraege
|
INSERT INTO Zeiteintraege
|
||||||
(Mitarbeiter, Startzeit, Endzeit, Projekt, Kommentar, Erledigt, MitarbeiterKommentar, LetzteBearbeitung)
|
(Mitarbeiter, Startzeit, Endzeit, Projekt, Kommentar, Erledigt, MitarbeiterKommentar, LetzteBearbeitung, Projektleiter)
|
||||||
VALUES
|
VALUES
|
||||||
($Mitarbeiter, $Startzeit, $Endzeit, $Projekt, $Kommentar, $Erledigt, $MitarbeiterKommentar, $LetzteBearbeitung);";
|
($Mitarbeiter, $Startzeit, $Endzeit, $Projekt, $Kommentar, $Erledigt, $MitarbeiterKommentar, $LetzteBearbeitung, $Projektleiter);";
|
||||||
|
|
||||||
|
|
||||||
|
// Parameter an das SQL-Kommando binden
|
||||||
cmd.Parameters.AddWithValue("$Mitarbeiter", eintrag.Mitarbeiter);
|
cmd.Parameters.AddWithValue("$Mitarbeiter", eintrag.Mitarbeiter);
|
||||||
cmd.Parameters.AddWithValue("$Startzeit", eintrag.Startzeit.ToString("o"));
|
cmd.Parameters.AddWithValue("$Startzeit", eintrag.Startzeit.ToString("o")); // ISO-8601-Format
|
||||||
cmd.Parameters.AddWithValue("$Endzeit", eintrag.Endzeit.ToString("o"));
|
cmd.Parameters.AddWithValue("$Endzeit", eintrag.Endzeit.ToString("o"));
|
||||||
cmd.Parameters.AddWithValue("$Projekt", eintrag.Projekt ?? "");
|
cmd.Parameters.AddWithValue("$Projekt", eintrag.Projekt ?? "");
|
||||||
cmd.Parameters.AddWithValue("$Kommentar", eintrag.Kommentar ?? "");
|
cmd.Parameters.AddWithValue("$Kommentar", eintrag.Kommentar ?? "");
|
||||||
cmd.Parameters.AddWithValue("$Erledigt", eintrag.Erledigt ? 1 : 0);
|
cmd.Parameters.AddWithValue("$Erledigt", eintrag.Erledigt ? 1 : 0);
|
||||||
cmd.Parameters.AddWithValue("$MitarbeiterKommentar", eintrag.MitarbeiterKommentar ?? "");
|
cmd.Parameters.AddWithValue("$MitarbeiterKommentar", eintrag.MitarbeiterKommentar ?? "");
|
||||||
cmd.Parameters.AddWithValue("$LetzteBearbeitung", DateTime.Now.ToString("o"));
|
cmd.Parameters.AddWithValue("$LetzteBearbeitung", DateTime.Now.ToString("o"));
|
||||||
|
cmd.Parameters.AddWithValue("$Projektleiter", eintrag.Projektleiter ?? "");
|
||||||
|
|
||||||
|
// Ausführung des SQL-Befehls
|
||||||
cmd.ExecuteNonQuery();
|
cmd.ExecuteNonQuery();
|
||||||
|
|
||||||
Console.WriteLine($"✅ Zeiteintrag für {eintrag.Mitarbeiter} gespeichert.");
|
Console.WriteLine($"✅ Zeiteintrag für {eintrag.Mitarbeiter} gespeichert.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lädt alle Zeiteinträge aus der Datenbank.
|
||||||
|
/// </summary>
|
||||||
|
public async Task<List<Zeiteintrag>> LadeAlleEintraegeAsync()
|
||||||
|
{
|
||||||
|
var eintraege = new List<Zeiteintrag>();
|
||||||
|
|
||||||
|
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
||||||
|
await connection.OpenAsync();
|
||||||
|
|
||||||
|
var command = connection.CreateCommand();
|
||||||
|
command.CommandText = "SELECT * FROM Zeiteintraege";
|
||||||
|
|
||||||
|
using var reader = await command.ExecuteReaderAsync();
|
||||||
|
while (await reader.ReadAsync())
|
||||||
|
{
|
||||||
|
eintraege.Add(new Zeiteintrag
|
||||||
|
{
|
||||||
|
Id = reader.GetInt32(0),
|
||||||
|
Mitarbeiter = reader.GetString(1),
|
||||||
|
Startzeit = DateTime.Parse(reader.GetString(2)),
|
||||||
|
Endzeit = DateTime.Parse(reader.GetString(3)),
|
||||||
|
Projekt = reader.GetString(4),
|
||||||
|
Kommentar = reader.GetString(5),
|
||||||
|
Erledigt = reader.GetInt32(6) == 1,
|
||||||
|
MitarbeiterKommentar = reader.GetString(7),
|
||||||
|
LetzteBearbeitung = DateTime.Parse(reader.GetString(8)),
|
||||||
|
Projektleiter = reader.IsDBNull(9) ? "" : reader.GetString(9)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return eintraege;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lädt alle vorhandenen Zeiteinträge aus der Datenbank.
|
||||||
|
/// Wird z. B. von Admins oder Projektleitern verwendet, um eine Gesamtübersicht
|
||||||
|
/// aller Projekte und Aufgaben zu erhalten.
|
||||||
|
/// </summary>
|
||||||
public List<Zeiteintrag> LadeAlleZeiteintraege()
|
public List<Zeiteintrag> LadeAlleZeiteintraege()
|
||||||
{
|
{
|
||||||
var eintraege = new List<Zeiteintrag>();
|
var eintraege = new List<Zeiteintrag>();
|
||||||
@ -300,7 +378,7 @@ VALUES
|
|||||||
using var reader = cmd.ExecuteReader();
|
using var reader = cmd.ExecuteReader();
|
||||||
while (reader.Read())
|
while (reader.Read())
|
||||||
{
|
{
|
||||||
eintraege.Add(new Zeiteintrag
|
var eintrag = new Zeiteintrag
|
||||||
{
|
{
|
||||||
Id = reader.GetInt32(0),
|
Id = reader.GetInt32(0),
|
||||||
Mitarbeiter = reader.GetString(1),
|
Mitarbeiter = reader.GetString(1),
|
||||||
@ -308,14 +386,25 @@ VALUES
|
|||||||
Endzeit = DateTime.Parse(reader.GetString(3)),
|
Endzeit = DateTime.Parse(reader.GetString(3)),
|
||||||
Projekt = reader.GetString(4),
|
Projekt = reader.GetString(4),
|
||||||
Kommentar = reader.GetString(5),
|
Kommentar = reader.GetString(5),
|
||||||
Erledigt = Convert.ToInt32(reader["Erledigt"]) == 1,
|
Erledigt = reader.GetBoolean(6),
|
||||||
MitarbeiterKommentar = reader.GetString(7)
|
MitarbeiterKommentar = reader.GetString(7),
|
||||||
});
|
LetzteBearbeitung = reader.IsDBNull(8)
|
||||||
|
? DateTime.MinValue
|
||||||
|
: DateTime.Parse(reader.GetString(8)),
|
||||||
|
Projektleiter = reader.IsDBNull(9) ? "" : reader.GetString(9)
|
||||||
|
};
|
||||||
|
|
||||||
|
eintraege.Add(eintrag);
|
||||||
}
|
}
|
||||||
|
|
||||||
return eintraege;
|
return eintraege;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Aktualisiert ein bestehendes Projekt in der Datenbank anhand der ID.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="projekt">Das Projekt, das aktualisiert werden soll.</param>
|
||||||
public void UpdateProjekt(Zeiteintrag projekt)
|
public void UpdateProjekt(Zeiteintrag projekt)
|
||||||
{
|
{
|
||||||
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
||||||
@ -346,6 +435,10 @@ VALUES
|
|||||||
Console.WriteLine($"✏ Projekt aktualisiert (Id={projekt.Id}, Rows affected: {rowsAffected})");
|
Console.WriteLine($"✏ Projekt aktualisiert (Id={projekt.Id}, Rows affected: {rowsAffected})");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Löscht ein Projekt anhand seiner ID aus der Datenbank.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">Die ID des zu löschenden Projekts.</param>
|
||||||
public void LoescheProjekt(int id)
|
public void LoescheProjekt(int id)
|
||||||
{
|
{
|
||||||
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
||||||
@ -359,6 +452,11 @@ VALUES
|
|||||||
Console.WriteLine($"❌ Projekt gelöscht (Id: {id}, Rows: {rows})");
|
Console.WriteLine($"❌ Projekt gelöscht (Id: {id}, Rows: {rows})");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Aktualisiert nur den Status "Erledigt" eines Projekts.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">ID des Projekts.</param>
|
||||||
|
/// <param name="erledigt">Neuer Status (true/false).</param>
|
||||||
public void UpdateProjektStatus(int id, bool erledigt)
|
public void UpdateProjektStatus(int id, bool erledigt)
|
||||||
{
|
{
|
||||||
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
||||||
@ -375,9 +473,14 @@ VALUES
|
|||||||
cmd.Parameters.AddWithValue("$Id", id);
|
cmd.Parameters.AddWithValue("$Id", id);
|
||||||
|
|
||||||
int rowsAffected = cmd.ExecuteNonQuery();
|
int rowsAffected = cmd.ExecuteNonQuery();
|
||||||
Console.WriteLine($"✅ Projektstatus aktualisiert (Id={id}, Erledigt={erledigt}, Rows affected: {rowsAffected})");
|
Console.WriteLine(
|
||||||
|
$"✅ Projektstatus aktualisiert (Id={id}, Erledigt={erledigt}, Rows affected: {rowsAffected})");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lädt alle Projekte, die als erledigt markiert sind.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Liste abgeschlossener Zeiteinträge.</returns>
|
||||||
public List<Zeiteintrag> LadeAbgeschlosseneProjekte()
|
public List<Zeiteintrag> LadeAbgeschlosseneProjekte()
|
||||||
{
|
{
|
||||||
var abgeschlossene = new List<Zeiteintrag>();
|
var abgeschlossene = new List<Zeiteintrag>();
|
||||||
@ -406,6 +509,11 @@ VALUES
|
|||||||
return abgeschlossene;
|
return abgeschlossene;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lädt die letzten Projekte basierend auf der ID (absteigend sortiert).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="anzahl">Anzahl der letzten Projekte.</param>
|
||||||
|
/// <returns>Liste der letzten Zeiteinträge.</returns>
|
||||||
public List<Zeiteintrag> LadeLetzteProjekte(int anzahl = 3)
|
public List<Zeiteintrag> LadeLetzteProjekte(int anzahl = 3)
|
||||||
{
|
{
|
||||||
var projekte = new List<Zeiteintrag>();
|
var projekte = new List<Zeiteintrag>();
|
||||||
@ -439,6 +547,10 @@ VALUES
|
|||||||
return projekte;
|
return projekte;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lädt alle noch nicht erledigten Projekte (Zeiteinträge) aus der Datenbank.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Liste offener Zeiteinträge</returns>
|
||||||
public List<Zeiteintrag> LadeOffeneProjekte()
|
public List<Zeiteintrag> LadeOffeneProjekte()
|
||||||
{
|
{
|
||||||
var offene = new List<Zeiteintrag>();
|
var offene = new List<Zeiteintrag>();
|
||||||
@ -460,19 +572,28 @@ VALUES
|
|||||||
Projekt = reader.GetString(4),
|
Projekt = reader.GetString(4),
|
||||||
Kommentar = reader.GetString(5),
|
Kommentar = reader.GetString(5),
|
||||||
Erledigt = Convert.ToInt32(reader["Erledigt"]) == 1,
|
Erledigt = Convert.ToInt32(reader["Erledigt"]) == 1,
|
||||||
MitarbeiterKommentar = reader.GetString(7)
|
MitarbeiterKommentar = reader.GetString(7),
|
||||||
|
LetzteBearbeitung = reader.IsDBNull(8) ? DateTime.MinValue : DateTime.Parse(reader.GetString(8)),
|
||||||
|
Projektleiter = reader.IsDBNull(9) ? "" : reader.GetString(9) // << NEU
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return offene;
|
return offene;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Setzt das Passwort eines Benutzers zurück (auf 'changeme').
|
||||||
|
/// Das Passwort wird automatisch gehasht.
|
||||||
|
/// </summary>
|
||||||
public void ResetBenutzerPasswort(string username)
|
public void ResetBenutzerPasswort(string username)
|
||||||
{
|
{
|
||||||
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
||||||
connection.Open();
|
connection.Open();
|
||||||
|
|
||||||
|
// Neues Standardpasswort hashen
|
||||||
var hashedDefault = PasswordHasher.HashPassword("changeme");
|
var hashedDefault = PasswordHasher.HashPassword("changeme");
|
||||||
|
|
||||||
var cmd = connection.CreateCommand();
|
var cmd = connection.CreateCommand();
|
||||||
cmd.CommandText = @"
|
cmd.CommandText = @"
|
||||||
UPDATE Benutzer
|
UPDATE Benutzer
|
||||||
@ -488,6 +609,133 @@ VALUES
|
|||||||
Console.WriteLine($"🔒 Passwort für Benutzer '{username}' zurückgesetzt (Rows affected: {rowsAffected})");
|
Console.WriteLine($"🔒 Passwort für Benutzer '{username}' zurückgesetzt (Rows affected: {rowsAffected})");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lädt alle Zeiteinträge eines bestimmten Mitarbeiters aus der Datenbank.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">Name des Mitarbeiters</param>
|
||||||
|
/// <returns>Liste der zugehörigen Zeiteinträge</returns>
|
||||||
|
public async Task<List<Zeiteintrag>> GetEintraegeFuerMitarbeiterAsync(string name)
|
||||||
|
{
|
||||||
|
var eintraege = new List<Zeiteintrag>();
|
||||||
|
|
||||||
|
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
||||||
|
await connection.OpenAsync();
|
||||||
|
|
||||||
|
var cmd = connection.CreateCommand();
|
||||||
|
cmd.CommandText = @"
|
||||||
|
SELECT Id, Mitarbeiter, Startzeit, Endzeit, Projekt, Kommentar, Erledigt, MitarbeiterKommentar, LetzteBearbeitung, Projektleiter
|
||||||
|
FROM Zeiteintraege
|
||||||
|
WHERE Mitarbeiter = $Name;";
|
||||||
|
cmd.Parameters.AddWithValue("$Name", name);
|
||||||
|
|
||||||
|
using var reader = await cmd.ExecuteReaderAsync();
|
||||||
|
while (await reader.ReadAsync())
|
||||||
|
{
|
||||||
|
eintraege.Add(new Zeiteintrag
|
||||||
|
{
|
||||||
|
Id = reader.GetInt32(0),
|
||||||
|
Mitarbeiter = reader.GetString(1),
|
||||||
|
Startzeit = reader.GetDateTime(2),
|
||||||
|
Endzeit = reader.GetDateTime(3),
|
||||||
|
Projekt = reader.GetString(4),
|
||||||
|
Kommentar = reader.GetString(5),
|
||||||
|
Erledigt = reader.GetBoolean(6),
|
||||||
|
MitarbeiterKommentar = reader.GetString(7),
|
||||||
|
LetzteBearbeitung = reader.IsDBNull(8) ? DateTime.MinValue : reader.GetDateTime(8),
|
||||||
|
Projektleiter = reader.IsDBNull(9) ? "" : reader.GetString(9) // << NEU
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return eintraege;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Überprüft, ob die Spalte 'VorletzterLogin' in der Tabelle 'Benutzer' vorhanden ist.
|
||||||
|
/// Falls nicht, wird sie hinzugefügt.
|
||||||
|
/// </summary>
|
||||||
|
public async Task StelleDatenbankstrukturSicherAsync()
|
||||||
|
{
|
||||||
|
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
||||||
|
await connection.OpenAsync();
|
||||||
|
|
||||||
|
// ========= 🧪 Spalte 'VorletzterLogin' in 'Benutzer' prüfen =========
|
||||||
|
var checkBenutzerCmd = connection.CreateCommand();
|
||||||
|
checkBenutzerCmd.CommandText = "PRAGMA table_info(Benutzer);";
|
||||||
|
var readerBenutzer = await checkBenutzerCmd.ExecuteReaderAsync();
|
||||||
|
bool vorletzterLoginExistiert = false;
|
||||||
|
|
||||||
|
while (await readerBenutzer.ReadAsync())
|
||||||
|
{
|
||||||
|
if (readerBenutzer.GetString(1).Equals("VorletzterLogin", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
vorletzterLoginExistiert = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await readerBenutzer.DisposeAsync();
|
||||||
|
|
||||||
|
if (!vorletzterLoginExistiert)
|
||||||
|
{
|
||||||
|
var alterBenutzerCmd = connection.CreateCommand();
|
||||||
|
alterBenutzerCmd.CommandText = "ALTER TABLE Benutzer ADD COLUMN VorletzterLogin TEXT;";
|
||||||
|
await alterBenutzerCmd.ExecuteNonQueryAsync();
|
||||||
|
Console.WriteLine("🛠 Spalte 'VorletzterLogin' wurde zur Tabelle 'Benutzer' hinzugefügt.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine("✅ Spalte 'VorletzterLogin' ist bereits vorhanden.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========= 🧪 Spalte 'Projektleiter' in 'Zeiteintraege' prüfen =========
|
||||||
|
var checkProjektleiterCmd = connection.CreateCommand();
|
||||||
|
checkProjektleiterCmd.CommandText = "PRAGMA table_info(Zeiteintraege);";
|
||||||
|
var readerProjektleiter = await checkProjektleiterCmd.ExecuteReaderAsync();
|
||||||
|
bool projektleiterExistiert = false;
|
||||||
|
|
||||||
|
while (await readerProjektleiter.ReadAsync())
|
||||||
|
{
|
||||||
|
if (readerProjektleiter.GetString(1).Equals("Projektleiter", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
projektleiterExistiert = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await readerProjektleiter.DisposeAsync();
|
||||||
|
|
||||||
|
if (!projektleiterExistiert)
|
||||||
|
{
|
||||||
|
var alterZeiteintraegeCmd = connection.CreateCommand();
|
||||||
|
alterZeiteintraegeCmd.CommandText = "ALTER TABLE Zeiteintraege ADD COLUMN Projektleiter TEXT;";
|
||||||
|
await alterZeiteintraegeCmd.ExecuteNonQueryAsync();
|
||||||
|
Console.WriteLine("🛠 Spalte 'Projektleiter' wurde zur Tabelle 'Zeiteintraege' hinzugefügt.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine("✅ Spalte 'Projektleiter' ist bereits vorhanden.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========= 🛠 Neue Tabelle 'ProjektMitarbeiter' erstellen =========
|
||||||
|
var createProjektMitarbeiterCmd = connection.CreateCommand();
|
||||||
|
createProjektMitarbeiterCmd.CommandText = @"
|
||||||
|
CREATE TABLE IF NOT EXISTS ProjektMitarbeiter (
|
||||||
|
ProjektId INTEGER NOT NULL,
|
||||||
|
Mitarbeiter TEXT NOT NULL,
|
||||||
|
PRIMARY KEY (ProjektId, Mitarbeiter),
|
||||||
|
FOREIGN KEY (ProjektId) REFERENCES Zeiteintraege(Id)
|
||||||
|
);
|
||||||
|
";
|
||||||
|
await createProjektMitarbeiterCmd.ExecuteNonQueryAsync();
|
||||||
|
Console.WriteLine("✅ Tabelle 'ProjektMitarbeiter' wurde (falls nötig) erstellt.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Aktualisiert die Login-Zeitpunkte des Benutzers in der Datenbank.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user">Der Benutzer, dessen Loginzeiten aktualisiert werden sollen</param>
|
||||||
public void UpdateLoginZeiten(User user)
|
public void UpdateLoginZeiten(User user)
|
||||||
{
|
{
|
||||||
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
||||||
@ -501,86 +749,21 @@ VALUES
|
|||||||
WHERE Username = $Username;
|
WHERE Username = $Username;
|
||||||
";
|
";
|
||||||
|
|
||||||
cmd.Parameters.AddWithValue("$LetzterLogin", user.LetzterLogin.ToString("o"));
|
// Setzt die Parameter für das SQL-Statement
|
||||||
cmd.Parameters.AddWithValue("$VorletzterLogin", user.VorletzterLogin.ToString("o"));
|
cmd.Parameters.AddWithValue("$LetzterLogin", user.LetzterLogin.ToString("o")); // ISO 8601-Format
|
||||||
cmd.Parameters.AddWithValue("$Username", user.Username);
|
cmd.Parameters.AddWithValue("$VorletzterLogin", user.VorletzterLogin.ToString("o")); // ebenfalls ISO
|
||||||
|
cmd.Parameters.AddWithValue("$Username", user.Username); // Nutzername als Schlüssel
|
||||||
|
|
||||||
cmd.ExecuteNonQuery();
|
cmd.ExecuteNonQuery(); // Führt das Update aus
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<Zeiteintrag>> GetEintraegeFuerMitarbeiterAsync(string name)
|
|
||||||
{
|
|
||||||
var eintraege = new List<Zeiteintrag>();
|
|
||||||
|
|
||||||
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
|
||||||
await connection.OpenAsync();
|
|
||||||
|
|
||||||
var cmd = connection.CreateCommand();
|
|
||||||
cmd.CommandText = @"
|
|
||||||
SELECT Id, Mitarbeiter, Startzeit, Endzeit, Projekt, Kommentar, Erledigt, MitarbeiterKommentar, LetzteBearbeitung
|
|
||||||
FROM Zeiteintraege
|
|
||||||
WHERE Mitarbeiter = $Name;
|
|
||||||
";
|
|
||||||
cmd.Parameters.AddWithValue("$Name", name);
|
|
||||||
|
|
||||||
using var reader = await cmd.ExecuteReaderAsync();
|
|
||||||
|
|
||||||
while (await reader.ReadAsync())
|
|
||||||
{
|
|
||||||
eintraege.Add(new Zeiteintrag
|
|
||||||
{
|
|
||||||
Id = reader.GetInt32(0),
|
|
||||||
Mitarbeiter = reader.GetString(1),
|
|
||||||
Startzeit = reader.GetDateTime(2),
|
|
||||||
Endzeit = reader.GetDateTime(3),
|
|
||||||
Projekt = reader.GetString(4),
|
|
||||||
Kommentar = reader.GetString(5),
|
|
||||||
Erledigt = reader.GetBoolean(6),
|
|
||||||
MitarbeiterKommentar = reader.GetString(7),
|
|
||||||
LetzteBearbeitung = reader.IsDBNull(8) ? DateTime.MinValue : reader.GetDateTime(8)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return eintraege;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task StelleDatenbankstrukturSicherAsync()
|
|
||||||
{
|
|
||||||
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
|
||||||
await connection.OpenAsync();
|
|
||||||
|
|
||||||
//Überprüfung ob die Spalte "Voletzter Login" bereits besteht
|
|
||||||
var checkCmd = connection.CreateCommand();
|
|
||||||
checkCmd.CommandText = "PRAGMA table_info(Benutzer);";
|
|
||||||
|
|
||||||
var reader = await checkCmd.ExecuteReaderAsync();
|
|
||||||
bool spalteExistiert = false;
|
|
||||||
|
|
||||||
while (await reader.ReadAsync())
|
|
||||||
{
|
|
||||||
if (reader.GetString(1).Equals("Vorletzer Login", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
spalteExistiert = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await reader.DisposeAsync();
|
|
||||||
|
|
||||||
if (!spalteExistiert)
|
|
||||||
{
|
|
||||||
var alterCmd = connection.CreateCommand();
|
|
||||||
alterCmd.CommandText = "ALTER TABLE Benutzer ADD COLUMN VorletzterLogin TEXT;";
|
|
||||||
await alterCmd.ExecuteNonQueryAsync();
|
|
||||||
|
|
||||||
Console.WriteLine("🛠 Spalte 'VorletzterLogin' wurde zur Tabelle 'Benutzer' hinzugefügt.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Console.WriteLine("✅ Spalte 'VorletzterLogin' ist bereits vorhanden.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Aktualisiert den Erledigt-Status, Kommentar und das Bearbeitungsdatum eines Zeiteintrags.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">ID des Zeiteintrags</param>
|
||||||
|
/// <param name="erledigt">Neuer Status: erledigt oder nicht</param>
|
||||||
|
/// <param name="mitarbeiterKommentar">Neuer Kommentar des Mitarbeiters</param>
|
||||||
public async Task UpdateStatusUndKommentarAsync(int id, bool erledigt, string mitarbeiterKommentar)
|
public async Task UpdateStatusUndKommentarAsync(int id, bool erledigt, string mitarbeiterKommentar)
|
||||||
{
|
{
|
||||||
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
using var connection = new SqliteConnection($"Data Source={_dbPath}");
|
||||||
@ -603,7 +786,5 @@ VALUES
|
|||||||
int rowsAffected = await cmd.ExecuteNonQueryAsync();
|
int rowsAffected = await cmd.ExecuteNonQueryAsync();
|
||||||
Console.WriteLine($"✏ Zeiteintrag (Id={id}) aktualisiert: erledigt={erledigt}, LetzteBearbeitung gesetzt.");
|
Console.WriteLine($"✏ Zeiteintrag (Id={id}) aktualisiert: erledigt={erledigt}, LetzteBearbeitung gesetzt.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -8,12 +8,29 @@ using ChronoFlow.Persistence;
|
|||||||
|
|
||||||
namespace ChronoFlow.View.Admin;
|
namespace ChronoFlow.View.Admin;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Zeigt eine Liste aller abgeschlossenen Projekte an.
|
||||||
|
/// Ermöglicht Suche und Navigation zurück zum Admin-Dashboard.
|
||||||
|
/// </summary>
|
||||||
public partial class AbgeschlosseneProjekteView : UserControl
|
public partial class AbgeschlosseneProjekteView : UserControl
|
||||||
{
|
{
|
||||||
private readonly ViewManager _viewManager;
|
private readonly ViewManager? _viewManager; // Nur gesetzt, wenn Konstruktor mit Parameter verwendet wird
|
||||||
private readonly ObservableCollection<Zeiteintrag> _abgeschlosseneProjekte = new();
|
private readonly ObservableCollection<Zeiteintrag> _abgeschlosseneProjekte = new();
|
||||||
private readonly SqliteZeiterfassungsService _dbService = new();
|
private readonly SqliteZeiterfassungsService _dbService = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Öffentlicher, parameterloser Konstruktor für Avalonia (zur Laufzeit erforderlich).
|
||||||
|
/// Wird z. B. vom XAML-Loader genutzt, daher AVLN0005-Warnung ohne ihn.
|
||||||
|
/// </summary>
|
||||||
|
public AbgeschlosseneProjekteView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
LadeAbgeschlosseneProjekte();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Konstruktor mit ViewManager zur Navigation – wird typischerweise aus Code aufgerufen.
|
||||||
|
/// </summary>
|
||||||
public AbgeschlosseneProjekteView(ViewManager viewManager)
|
public AbgeschlosseneProjekteView(ViewManager viewManager)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@ -21,6 +38,10 @@ public partial class AbgeschlosseneProjekteView : UserControl
|
|||||||
LadeAbgeschlosseneProjekte();
|
LadeAbgeschlosseneProjekte();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lädt alle abgeschlossenen Projekte aus der Datenbank
|
||||||
|
/// und aktualisiert die Liste in der Benutzeroberfläche.
|
||||||
|
/// </summary>
|
||||||
private void LadeAbgeschlosseneProjekte()
|
private void LadeAbgeschlosseneProjekte()
|
||||||
{
|
{
|
||||||
_abgeschlosseneProjekte.Clear();
|
_abgeschlosseneProjekte.Clear();
|
||||||
@ -33,10 +54,10 @@ public partial class AbgeschlosseneProjekteView : UserControl
|
|||||||
AbgeschlosseneListe.ItemsSource = _abgeschlosseneProjekte;
|
AbgeschlosseneListe.ItemsSource = _abgeschlosseneProjekte;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ZurueckButton_Click(object? sender, RoutedEventArgs e)
|
/// <summary>
|
||||||
{
|
/// Wird aufgerufen, wenn eine Taste im Suchfeld gedrückt wird.
|
||||||
_viewManager.Show("AdminMain");
|
/// Filtert die Liste der Projekte anhand von Projektname oder Mitarbeitername.
|
||||||
}
|
/// </summary>
|
||||||
private void Suchfeld_KeyUp(object? sender, KeyEventArgs e)
|
private void Suchfeld_KeyUp(object? sender, KeyEventArgs e)
|
||||||
{
|
{
|
||||||
var text = Suchfeld?.Text?.ToLower() ?? "";
|
var text = Suchfeld?.Text?.ToLower() ?? "";
|
||||||
@ -46,4 +67,11 @@ public partial class AbgeschlosseneProjekteView : UserControl
|
|||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Navigiert zurück zur Admin-Hauptansicht (z. B. Dashboard).
|
||||||
|
/// </summary>
|
||||||
|
private void ZurueckButton_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
_viewManager?.Show("AdminMain");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -7,16 +7,26 @@ using ChronoFlow.Persistence;
|
|||||||
|
|
||||||
namespace ChronoFlow.View.Admin
|
namespace ChronoFlow.View.Admin
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Die Hauptansicht für Administratoren nach dem Login.
|
||||||
|
/// Zeigt die zuletzt bearbeiteten Projekte und Navigationsoptionen.
|
||||||
|
/// </summary>
|
||||||
public partial class AdminMainView : UserControl
|
public partial class AdminMainView : UserControl
|
||||||
{
|
{
|
||||||
private readonly ViewManager _viewManager;
|
private readonly ViewManager _viewManager;
|
||||||
private readonly ObservableCollection<Zeiteintrag> _letzteProjekte = new();
|
private readonly ObservableCollection<Zeiteintrag> _letzteProjekte = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parameterloser Konstruktor (wird nur intern verwendet).
|
||||||
|
/// </summary>
|
||||||
public AdminMainView() : this(new ViewManager(new ContentControl()))
|
public AdminMainView() : this(new ViewManager(new ContentControl()))
|
||||||
{
|
{
|
||||||
Console.WriteLine("⚠ Achtung: Parameterloser Konstruktor genutzt (nur Standard-ViewManager).");
|
Console.WriteLine("⚠ Achtung: Parameterloser Konstruktor genutzt (nur Standard-ViewManager).");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Konstruktor mit Übergabe des ViewManagers für Navigation.
|
||||||
|
/// </summary>
|
||||||
public AdminMainView(ViewManager viewManager)
|
public AdminMainView(ViewManager viewManager)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@ -26,6 +36,9 @@ namespace ChronoFlow.View.Admin
|
|||||||
LadeLetzteProjekte();
|
LadeLetzteProjekte();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Holt die letzten 3 Projekte aus der Datenbank und zeigt sie an.
|
||||||
|
/// </summary>
|
||||||
private void LadeLetzteProjekte()
|
private void LadeLetzteProjekte()
|
||||||
{
|
{
|
||||||
Console.WriteLine("🔄 Lade letzte Projekte...");
|
Console.WriteLine("🔄 Lade letzte Projekte...");
|
||||||
@ -58,6 +71,8 @@ namespace ChronoFlow.View.Admin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🔽 Navigation per Buttonklick
|
||||||
|
|
||||||
private void MitarbeiterHinzufuegen_Click(object sender, RoutedEventArgs e)
|
private void MitarbeiterHinzufuegen_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
_viewManager.Show("MitarbeiterHinzufuegen");
|
_viewManager.Show("MitarbeiterHinzufuegen");
|
||||||
@ -67,6 +82,7 @@ namespace ChronoFlow.View.Admin
|
|||||||
{
|
{
|
||||||
_viewManager.Show("ProjektErstellen");
|
_viewManager.Show("ProjektErstellen");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AlleProjekte_Click(object sender, RoutedEventArgs e)
|
private void AlleProjekte_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
_viewManager.Show("AlleProjekte");
|
_viewManager.Show("AlleProjekte");
|
||||||
@ -77,20 +93,17 @@ namespace ChronoFlow.View.Admin
|
|||||||
_viewManager.Show("MitarbeiterListe");
|
_viewManager.Show("MitarbeiterListe");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public void AktualisiereLetzteProjekte()
|
|
||||||
{
|
|
||||||
LadeLetzteProjekte();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AbgeschlosseneProjekte_Click(object? sender, RoutedEventArgs e)
|
private void AbgeschlosseneProjekte_Click(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
_viewManager.Show("AbgeschlosseneProjekte");
|
_viewManager.Show("AbgeschlosseneProjekte");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wird z. B. vom ProjektErstellenView aufgerufen, um die Liste neu zu laden.
|
||||||
|
/// </summary>
|
||||||
|
public void AktualisiereLetzteProjekte()
|
||||||
|
{
|
||||||
|
LadeLetzteProjekte();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,15 +4,15 @@
|
|||||||
x:Class="ChronoFlow.View.Admin.AlleProjekteView">
|
x:Class="ChronoFlow.View.Admin.AlleProjekteView">
|
||||||
<Grid RowDefinitions="Auto,Auto,*,Auto" Margin="20">
|
<Grid RowDefinitions="Auto,Auto,*,Auto" Margin="20">
|
||||||
|
|
||||||
<!-- Überschrift -->
|
<!-- 🔹 Überschrift -->
|
||||||
<TextBlock Grid.Row="0" Text="Alle Projekte" FontSize="20" FontWeight="Bold"
|
<TextBlock Grid.Row="0" Text="Alle Projekte" FontSize="20" FontWeight="Bold"
|
||||||
HorizontalAlignment="Center" Margin="0,0,0,10"/>
|
HorizontalAlignment="Center" Margin="0,0,0,10"/>
|
||||||
|
|
||||||
<!-- Suchfeld -->
|
<!-- 🔍 Suchfeld zur Projekt- oder Mitarbeitersuche -->
|
||||||
<TextBox Grid.Row="1" x:Name="Suchfeld" Watermark="🔍 Nach Projekt oder Mitarbeiter suchen..."
|
<TextBox Grid.Row="1" x:Name="Suchfeld" Watermark="🔍 Nach Projekt oder Mitarbeiter suchen..."
|
||||||
KeyUp="Suchfeld_KeyUp" Margin="0,0,0,10"/>
|
KeyUp="Suchfeld_KeyUp" Margin="0,0,0,10"/>
|
||||||
|
|
||||||
<!-- Projektliste -->
|
<!-- 📋 Projektliste -->
|
||||||
<ScrollViewer Grid.Row="2">
|
<ScrollViewer Grid.Row="2">
|
||||||
<ListBox x:Name="ProjekteListe">
|
<ListBox x:Name="ProjekteListe">
|
||||||
<ListBox.ItemTemplate>
|
<ListBox.ItemTemplate>
|
||||||
@ -20,7 +20,7 @@
|
|||||||
<Border BorderBrush="Gray" BorderThickness="1" CornerRadius="5" Padding="10" Margin="0,5">
|
<Border BorderBrush="Gray" BorderThickness="1" CornerRadius="5" Padding="10" Margin="0,5">
|
||||||
<StackPanel Spacing="5">
|
<StackPanel Spacing="5">
|
||||||
|
|
||||||
<!-- Projektübersicht -->
|
<!-- 🔧 Projektinformationen -->
|
||||||
<StackPanel Orientation="Horizontal" Spacing="10">
|
<StackPanel Orientation="Horizontal" Spacing="10">
|
||||||
<TextBlock Text="{Binding Projekt}" Width="150"/>
|
<TextBlock Text="{Binding Projekt}" Width="150"/>
|
||||||
<TextBlock Text="{Binding Mitarbeiter}" Width="150"/>
|
<TextBlock Text="{Binding Mitarbeiter}" Width="150"/>
|
||||||
@ -28,11 +28,15 @@
|
|||||||
<TextBlock Text="{Binding Endzeit}" Width="150"/>
|
<TextBlock Text="{Binding Endzeit}" Width="150"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<!-- Kommentar vom Admin -->
|
<!-- 🧑💼 Projektleiter -->
|
||||||
|
<TextBlock Text="👨💼 Projektleiter:" FontSize="12" Foreground="LightGray"/>
|
||||||
|
<TextBlock Text="{Binding Projektleiter}" FontStyle="Italic" FontWeight="SemiBold"/>
|
||||||
|
|
||||||
|
<!-- 📝 Kommentar vom Admin -->
|
||||||
<TextBlock Text="📝 Kommentar (Admin):" FontSize="12" Foreground="LightGray"/>
|
<TextBlock Text="📝 Kommentar (Admin):" FontSize="12" Foreground="LightGray"/>
|
||||||
<TextBlock Text="{Binding Kommentar}" FontWeight="SemiBold"/>
|
<TextBlock Text="{Binding Kommentar}" FontWeight="SemiBold"/>
|
||||||
|
|
||||||
<!-- Kommentar vom Mitarbeiter -->
|
<!-- 💬 Kommentar vom Mitarbeiter -->
|
||||||
<TextBlock Text="💬 Kommentar (Mitarbeiter):" FontSize="12" Foreground="LightGray"/>
|
<TextBlock Text="💬 Kommentar (Mitarbeiter):" FontSize="12" Foreground="LightGray"/>
|
||||||
<TextBlock Text="{Binding MitarbeiterKommentar}"
|
<TextBlock Text="{Binding MitarbeiterKommentar}"
|
||||||
FontStyle="Italic"
|
FontStyle="Italic"
|
||||||
@ -41,7 +45,7 @@
|
|||||||
TextWrapping="Wrap"
|
TextWrapping="Wrap"
|
||||||
Margin="0,2,0,0"/>
|
Margin="0,2,0,0"/>
|
||||||
|
|
||||||
<!-- Aktionsbuttons -->
|
<!-- 🔘 Aktionsbuttons -->
|
||||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
<Button Content="🖋 Bearbeiten" Tag="{Binding}" Click="Bearbeiten_Click"/>
|
<Button Content="🖋 Bearbeiten" Tag="{Binding}" Click="Bearbeiten_Click"/>
|
||||||
<Button Content="🗑 Löschen" Tag="{Binding}" Click="Loeschen_Click"/>
|
<Button Content="🗑 Löschen" Tag="{Binding}" Click="Loeschen_Click"/>
|
||||||
@ -55,7 +59,7 @@
|
|||||||
</ListBox>
|
</ListBox>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
|
|
||||||
<!-- Zurück-Button -->
|
<!-- 🔙 Zurück-Button -->
|
||||||
<Button Grid.Row="3" Content="⬅ Zurück zum Dashboard"
|
<Button Grid.Row="3" Content="⬅ Zurück zum Dashboard"
|
||||||
Click="ZurueckButton_Click" HorizontalAlignment="Center" Margin="0,10,0,0"/>
|
Click="ZurueckButton_Click" HorizontalAlignment="Center" Margin="0,10,0,0"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@ -3,18 +3,36 @@ using System.Collections.ObjectModel;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
|
using Avalonia.Input;
|
||||||
using ChronoFlow.Model;
|
using ChronoFlow.Model;
|
||||||
using ChronoFlow.Persistence;
|
using ChronoFlow.Persistence;
|
||||||
using Avalonia.Input;
|
|
||||||
|
|
||||||
namespace ChronoFlow.View.Admin;
|
namespace ChronoFlow.View.Admin;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Diese View zeigt alle noch nicht abgeschlossenen Projekte.
|
||||||
|
/// Admins können Projekte bearbeiten, löschen oder abschließen.
|
||||||
|
/// </summary>
|
||||||
public partial class AlleProjekteView : UserControl
|
public partial class AlleProjekteView : UserControl
|
||||||
{
|
{
|
||||||
private readonly ViewManager _viewManager;
|
private readonly ViewManager? _viewManager;
|
||||||
private readonly ObservableCollection<Zeiteintrag> _alleProjekte = new();
|
private readonly ObservableCollection<Zeiteintrag> _alleProjekte = new();
|
||||||
private readonly SqliteZeiterfassungsService _dbService = new();
|
private readonly SqliteZeiterfassungsService _dbService = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Öffentlicher, parameterloser Konstruktor – notwendig für Avalonia XAML-Runtime (AVLN0005).
|
||||||
|
/// Wird nur verwendet, wenn kein ViewManager übergeben wird.
|
||||||
|
/// </summary>
|
||||||
|
public AlleProjekteView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
Console.WriteLine("⚠ AlleProjekteView mit Standard-Konstruktor initialisiert.");
|
||||||
|
LadeAlleProjekte();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Konstruktor mit ViewManager zur Navigation (z.B. zurück zum Admin-Dashboard).
|
||||||
|
/// </summary>
|
||||||
public AlleProjekteView(ViewManager viewManager)
|
public AlleProjekteView(ViewManager viewManager)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@ -22,54 +40,96 @@ public partial class AlleProjekteView : UserControl
|
|||||||
LadeAlleProjekte();
|
LadeAlleProjekte();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lädt alle nicht erledigten Projekte aus der Datenbank und zeigt sie in der Liste an.
|
||||||
|
/// </summary>
|
||||||
private void LadeAlleProjekte()
|
private void LadeAlleProjekte()
|
||||||
{
|
{
|
||||||
_alleProjekte.Clear();
|
|
||||||
|
|
||||||
var ausDb = _dbService.LadeAlleZeiteintraege()
|
try
|
||||||
.Where(p => !p.Erledigt) // Nur nicht erledigte Projekte!
|
{
|
||||||
|
_alleProjekte.Clear();
|
||||||
|
var alle = _dbService.LadeAlleZeiteintraege();
|
||||||
|
Console.WriteLine($"🧪 Aus DB geladen: {alle.Count} Einträge");
|
||||||
|
|
||||||
|
foreach (var e in alle)
|
||||||
|
Console.WriteLine($"➡️ {e.Projekt} | {e.Mitarbeiter} | {e.Projektleiter}");
|
||||||
|
|
||||||
|
var ausDb = alle
|
||||||
|
//.Where(p => !p.Erledigt)
|
||||||
|
.OrderBy(p => p.Endzeit)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
foreach (var eintrag in ausDb)
|
foreach (var eintrag in ausDb)
|
||||||
_alleProjekte.Add(eintrag);
|
_alleProjekte.Add(eintrag);
|
||||||
|
|
||||||
ProjekteListe.ItemsSource = _alleProjekte;
|
ProjekteListe.ItemsSource = _alleProjekte;
|
||||||
|
|
||||||
|
Console.WriteLine($"🔄 {ausDb.Count} offene Projekte geladen.");
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"❌ Fehler beim Laden der Projekte: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Filtert die Projektliste nach Projekt- oder Mitarbeitername bei Tasteneingabe im Suchfeld.
|
||||||
|
/// </summary>
|
||||||
private void Suchfeld_KeyUp(object? sender, KeyEventArgs e)
|
private void Suchfeld_KeyUp(object? sender, KeyEventArgs e)
|
||||||
{
|
{
|
||||||
var text = Suchfeld?.Text?.ToLower() ?? "";
|
var text = Suchfeld?.Text?.ToLower() ?? "";
|
||||||
|
|
||||||
ProjekteListe.ItemsSource = _alleProjekte
|
ProjekteListe.ItemsSource = _alleProjekte
|
||||||
.Where(p => (p.Projekt?.ToLower().Contains(text) ?? false) ||
|
.Where(p => (p.Projekt?.ToLower().Contains(text) ?? false) ||
|
||||||
(p.Mitarbeiter?.ToLower().Contains(text) ?? false))
|
(p.Mitarbeiter?.ToLower().Contains(text) ?? false))
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Öffnet einen Dialog zum Bearbeiten des ausgewählten Projekts.
|
||||||
|
/// Änderungen werden gespeichert und die Liste neu geladen.
|
||||||
|
/// </summary>
|
||||||
private async void Bearbeiten_Click(object? sender, RoutedEventArgs e)
|
private async void Bearbeiten_Click(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is Button button && button.Tag is Zeiteintrag projekt)
|
if (sender is Button button && button.Tag is Zeiteintrag projekt)
|
||||||
{
|
{
|
||||||
var dialog = new ProjektBearbeitenDialog(projekt);
|
var dialog = new ProjektBearbeitenDialog(projekt);
|
||||||
var updatedProjekt = await dialog.ShowDialog<Zeiteintrag>((Window)this.VisualRoot!);
|
|
||||||
|
if (this.VisualRoot is Window parentWindow)
|
||||||
|
{
|
||||||
|
var updatedProjekt = await dialog.ShowDialog<Zeiteintrag>(parentWindow);
|
||||||
|
|
||||||
if (updatedProjekt != null)
|
if (updatedProjekt != null)
|
||||||
{
|
{
|
||||||
_dbService.UpdateProjekt(updatedProjekt);
|
_dbService.UpdateProjekt(updatedProjekt);
|
||||||
LadeAlleProjekte();
|
LadeAlleProjekte();
|
||||||
|
Console.WriteLine($"✏️ Projekt aktualisiert: {updatedProjekt.Projekt}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine("❌ Bearbeiten_Click: VisualRoot ist kein Window.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Löscht das ausgewählte Projekt dauerhaft aus der Datenbank.
|
||||||
|
/// </summary>
|
||||||
private void Loeschen_Click(object? sender, RoutedEventArgs e)
|
private void Loeschen_Click(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is Button button && button.Tag is Zeiteintrag projekt)
|
if (sender is Button button && button.Tag is Zeiteintrag projekt)
|
||||||
{
|
{
|
||||||
_dbService.LoescheProjekt(projekt.Id);
|
_dbService.LoescheProjekt(projekt.Id);
|
||||||
LadeAlleProjekte();
|
LadeAlleProjekte();
|
||||||
|
Console.WriteLine($"🗑️ Projekt gelöscht: {projekt.Projekt}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Markiert ein Projekt als erledigt (abgeschlossen) und aktualisiert die Anzeige.
|
||||||
|
/// </summary>
|
||||||
private void Abschliessen_Click(object? sender, RoutedEventArgs e)
|
private void Abschliessen_Click(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is Button button && button.Tag is Zeiteintrag projekt)
|
if (sender is Button button && button.Tag is Zeiteintrag projekt)
|
||||||
@ -82,8 +142,11 @@ public partial class AlleProjekteView : UserControl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Navigiert zurück zur Admin-Startansicht.
|
||||||
|
/// </summary>
|
||||||
private void ZurueckButton_Click(object? sender, RoutedEventArgs e)
|
private void ZurueckButton_Click(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
_viewManager.Show("AdminMain");
|
_viewManager?.Show("AdminMain");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,22 +3,50 @@ using Avalonia.Interactivity;
|
|||||||
|
|
||||||
namespace ChronoFlow.View.Admin;
|
namespace ChronoFlow.View.Admin;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ein einfacher Bestätigungsdialog mit "Ja" und "Nein"-Buttons.
|
||||||
|
/// Gibt bei Abschluss ein bool-Wert zurück: true = bestätigt, false = abgelehnt.
|
||||||
|
/// </summary>
|
||||||
public partial class ConfirmDialog : Window
|
public partial class ConfirmDialog : Window
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Ergebnis der Auswahl: true = Ja, false = Nein (Standard).
|
||||||
|
/// </summary>
|
||||||
public bool Result { get; private set; } = false;
|
public bool Result { get; private set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Öffentlicher, parameterloser Konstruktor.
|
||||||
|
/// Wichtig für Avalonia zur Laufzeit, verhindert AVLN0005-Warnungen.
|
||||||
|
/// </summary>
|
||||||
|
public ConfirmDialog()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Konstruktor mit einer Frage, die im Dialog angezeigt wird.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="frage">Der anzuzeigende Fragetext im Dialog</param>
|
||||||
public ConfirmDialog(string frage)
|
public ConfirmDialog(string frage)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
FrageText.Text = frage;
|
FrageText.Text = frage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wird aufgerufen, wenn der Benutzer "Ja" klickt.
|
||||||
|
/// Schließt das Fenster mit Result = true.
|
||||||
|
/// </summary>
|
||||||
private void JaButton_Click(object? sender, RoutedEventArgs e)
|
private void JaButton_Click(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
Result = true;
|
Result = true;
|
||||||
Close(Result);
|
Close(Result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wird aufgerufen, wenn der Benutzer "Nein" klickt.
|
||||||
|
/// Schließt das Fenster mit Result = false.
|
||||||
|
/// </summary>
|
||||||
private void NeinButton_Click(object? sender, RoutedEventArgs e)
|
private void NeinButton_Click(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
Result = false;
|
Result = false;
|
||||||
|
|||||||
@ -1,25 +1,47 @@
|
|||||||
|
<!--
|
||||||
|
Fenster zur Bearbeitung von Mitarbeiterdaten (Username, Abteilung, Mitarbeitennummer).
|
||||||
|
Zusätzlich wird ein Hinweis eingeblendet, falls das Passwort auf einen Standardwert zurückgesetzt wurde.
|
||||||
|
-->
|
||||||
<Window xmlns="https://github.com/avaloniaui"
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
x:Class="ChronoFlow.View.Admin.MitarbeiterBearbeitenDialog"
|
x:Class="ChronoFlow.View.Admin.MitarbeiterBearbeitenDialog"
|
||||||
Width="450" Height="600"
|
Width="450" Height="600"
|
||||||
Title="Mitarbeiter bearbeiten">
|
Title="Mitarbeiter bearbeiten">
|
||||||
|
|
||||||
|
<!-- Ermöglicht vertikales Scrollen bei kleineren Fenstern -->
|
||||||
<ScrollViewer>
|
<ScrollViewer>
|
||||||
|
|
||||||
|
<!-- Hauptcontainer mit Abstand und vertikaler Anordnung -->
|
||||||
<StackPanel Margin="20" Spacing="15">
|
<StackPanel Margin="20" Spacing="15">
|
||||||
|
|
||||||
|
<!-- Eingabe für den Benutzernamen -->
|
||||||
<TextBlock Text="Username:" />
|
<TextBlock Text="Username:" />
|
||||||
<TextBox x:Name="UsernameBox" />
|
<TextBox x:Name="UsernameBox" />
|
||||||
|
|
||||||
|
<!-- Eingabe für die Abteilung -->
|
||||||
<TextBlock Text="Abteilung:" />
|
<TextBlock Text="Abteilung:" />
|
||||||
<TextBox x:Name="AbteilungBox" />
|
<TextBox x:Name="AbteilungBox" />
|
||||||
|
|
||||||
|
<!-- Eingabe für die Mitarbeitennummer -->
|
||||||
<TextBlock Text="Mitarbeiternummer:" />
|
<TextBlock Text="Mitarbeiternummer:" />
|
||||||
<TextBox x:Name="MitarbeiternummerBox" />
|
<TextBox x:Name="MitarbeiternummerBox" />
|
||||||
|
|
||||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="10" Margin="0,10,0,0">
|
<!-- Schaltflächen zum Speichern oder Abbrechen -->
|
||||||
<Button Content="✅ Speichern" Width="120" Click="SpeichernButton_Click" />
|
<StackPanel Orientation="Horizontal"
|
||||||
<Button Content="❌ Abbrechen" Width="120" Click="AbbrechenButton_Click" />
|
HorizontalAlignment="Center"
|
||||||
|
Spacing="10"
|
||||||
|
Margin="0,10,0,0">
|
||||||
|
<!-- Speichern-Button -->
|
||||||
|
<Button Content="✅ Speichern"
|
||||||
|
Width="120"
|
||||||
|
Click="SpeichernButton_Click" />
|
||||||
|
<!-- Abbrechen-Button -->
|
||||||
|
<Button Content="❌ Abbrechen"
|
||||||
|
Width="120"
|
||||||
|
Click="AbbrechenButton_Click" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<!-- Erfolgs-Hinweis -->
|
<!-- Text zur Rückmeldung bei erfolgreichem Speichern -->
|
||||||
<TextBlock x:Name="FeedbackText"
|
<TextBlock x:Name="FeedbackText"
|
||||||
Text="Änderungen erfolgreich übernommen."
|
Text="Änderungen erfolgreich übernommen."
|
||||||
Foreground="Green"
|
Foreground="Green"
|
||||||
@ -27,6 +49,17 @@
|
|||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
IsVisible="False"
|
IsVisible="False"
|
||||||
Margin="0,10,0,0" />
|
Margin="0,10,0,0" />
|
||||||
|
|
||||||
|
<!-- Hinweis für Admins beim Zurücksetzen des Passworts -->
|
||||||
|
<TextBlock x:Name="ResetHinweisText"
|
||||||
|
Text="Das Passwort wurde auf 'changeme' zurückgesetzt – bitte an den Mitarbeiter weitergeben."
|
||||||
|
Foreground="DarkRed"
|
||||||
|
FontWeight="Bold"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
IsVisible="False"
|
||||||
|
Margin="0,10,0,0" />
|
||||||
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</Window>
|
</Window>
|
||||||
@ -4,27 +4,75 @@ using ChronoFlow.Model;
|
|||||||
|
|
||||||
namespace ChronoFlow.View.Admin;
|
namespace ChronoFlow.View.Admin;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dialogfenster zur Bearbeitung von Mitarbeiterdaten.
|
||||||
|
/// Der übergebene Benutzer kann geändert und anschließend zurückgegeben werden.
|
||||||
|
/// </summary>
|
||||||
public partial class MitarbeiterBearbeitenDialog : Window
|
public partial class MitarbeiterBearbeitenDialog : Window
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Rückgabe des aktualisierten Benutzers nach Speichern.
|
||||||
|
/// </summary>
|
||||||
public User UpdatedUser { get; private set; }
|
public User UpdatedUser { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gibt an, ob das Passwort dieses Benutzers zurückgesetzt wurde.
|
||||||
|
/// Wird extern aufgerufen und steuert die Anzeige des Hinweistexts.
|
||||||
|
/// </summary>
|
||||||
|
public bool PasswortWurdeZurueckgesetzt
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value && this.FindControl<TextBlock>("ResetHinweisText") is { } resetText)
|
||||||
|
{
|
||||||
|
resetText.IsVisible = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Öffentlicher, parameterloser Konstruktor.
|
||||||
|
/// Wichtig für Avalonia zur Laufzeit, z.B. XAML-Loader (verhindert AVLN0005-Warnung).
|
||||||
|
/// </summary>
|
||||||
|
public MitarbeiterBearbeitenDialog()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
UpdatedUser = new User(); // Dummy-Initialisierung für Designer oder Avalonia-Runtime
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Konstruktor mit Benutzerobjekt – lädt dessen Daten zur Bearbeitung.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user">Benutzer, der bearbeitet werden soll</param>
|
||||||
public MitarbeiterBearbeitenDialog(User user)
|
public MitarbeiterBearbeitenDialog(User user)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
|
// Erstellen einer Kopie des Benutzers zur späteren Rückgabe
|
||||||
UpdatedUser = new User
|
UpdatedUser = new User
|
||||||
{
|
{
|
||||||
Username = user.Username,
|
Username = user.Username,
|
||||||
OriginalUsername = user.Username, // Speichern des alten Namens
|
OriginalUsername = user.Username, // Originalname speichern für Update-Vergleich
|
||||||
Abteilung = user.Abteilung,
|
Abteilung = user.Abteilung,
|
||||||
Mitarbeiternummer = user.Mitarbeiternummer
|
Mitarbeiternummer = user.Mitarbeiternummer
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Felder in der Oberfläche vorausfüllen
|
||||||
UsernameBox.Text = user.Username;
|
UsernameBox.Text = user.Username;
|
||||||
AbteilungBox.Text = user.Abteilung;
|
AbteilungBox.Text = user.Abteilung;
|
||||||
MitarbeiternummerBox.Text = user.Mitarbeiternummer;
|
MitarbeiternummerBox.Text = user.Mitarbeiternummer;
|
||||||
|
|
||||||
|
// Hinweistext zum Passwortreset initial ausblenden (nur zur Sicherheit)
|
||||||
|
if (this.FindControl<TextBlock>("ResetHinweisText") is { } resetText)
|
||||||
|
{
|
||||||
|
resetText.IsVisible = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wird ausgeführt, wenn auf „Speichern“ geklickt wird.
|
||||||
|
/// Aktualisiert die Daten des Benutzers und schließt das Fenster mit Rückgabe.
|
||||||
|
/// </summary>
|
||||||
private void SpeichernButton_Click(object? sender, RoutedEventArgs e)
|
private void SpeichernButton_Click(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
UpdatedUser.Username = UsernameBox.Text ?? UpdatedUser.Username;
|
UpdatedUser.Username = UsernameBox.Text ?? UpdatedUser.Username;
|
||||||
@ -34,6 +82,10 @@ public partial class MitarbeiterBearbeitenDialog : Window
|
|||||||
this.Close(UpdatedUser);
|
this.Close(UpdatedUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wird ausgeführt, wenn auf „Abbrechen“ geklickt wird.
|
||||||
|
/// Fenster wird geschlossen, ohne Änderungen zurückzugeben.
|
||||||
|
/// </summary>
|
||||||
private void AbbrechenButton_Click(object? sender, RoutedEventArgs e)
|
private void AbbrechenButton_Click(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
this.Close(null);
|
this.Close(null);
|
||||||
|
|||||||
@ -1,24 +1,35 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using ChronoFlow.Model;
|
using ChronoFlow.Model;
|
||||||
using ChronoFlow.Persistence;
|
using ChronoFlow.Persistence;
|
||||||
using MessageBox.Avalonia;
|
using ChronoFlow.Security;
|
||||||
using MessageBox.Avalonia.DTO;
|
|
||||||
using MessageBox.Avalonia.Enums;
|
|
||||||
|
|
||||||
namespace ChronoFlow.View.Admin;
|
namespace ChronoFlow.View.Admin;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Zeigt eine Liste aller Benutzer mit der Rolle "Mitarbeiter" zur Bearbeitung oder Löschung.
|
||||||
|
/// </summary>
|
||||||
public partial class MitarbeiterListeView : UserControl
|
public partial class MitarbeiterListeView : UserControl
|
||||||
{
|
{
|
||||||
private readonly ViewManager _viewManager;
|
private readonly ViewManager _viewManager;
|
||||||
private readonly ObservableCollection<User> _alleMitarbeiter = new();
|
private readonly ObservableCollection<User> _alleMitarbeiter = new();
|
||||||
private readonly SqliteZeiterfassungsService _dbService = new();
|
private readonly SqliteZeiterfassungsService _dbService = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Öffentlicher parameterloser Konstruktor für Avalonia (zur Vermeidung von AVLN0005-Warnungen).
|
||||||
|
/// </summary>
|
||||||
|
public MitarbeiterListeView() : this(new ViewManager(new ContentControl()))
|
||||||
|
{
|
||||||
|
Console.WriteLine("⚠ Parameterloser Konstruktor genutzt (Dummy-ViewManager).");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Konstruktor mit ViewManager (für Navigation).
|
||||||
|
/// </summary>
|
||||||
public MitarbeiterListeView(ViewManager viewManager)
|
public MitarbeiterListeView(ViewManager viewManager)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@ -26,6 +37,14 @@ public partial class MitarbeiterListeView : UserControl
|
|||||||
LadeMitarbeiter();
|
LadeMitarbeiter();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hilfsmethode zum Abrufen des übergeordneten Fensters.
|
||||||
|
/// </summary>
|
||||||
|
private Window? GetParentWindow() => this.VisualRoot as Window;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lädt alle Benutzer mit Rolle "Mitarbeiter" und zeigt sie in der Liste an.
|
||||||
|
/// </summary>
|
||||||
private void LadeMitarbeiter()
|
private void LadeMitarbeiter()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -47,52 +66,107 @@ public partial class MitarbeiterListeView : UserControl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Filtert die Mitarbeiterliste beim Tippen im Suchfeld.
|
||||||
|
/// </summary>
|
||||||
private void Suchfeld_KeyUp(object? sender, KeyEventArgs e)
|
private void Suchfeld_KeyUp(object? sender, KeyEventArgs e)
|
||||||
{
|
{
|
||||||
var text = Suchfeld?.Text?.ToLower() ?? "";
|
var text = Suchfeld?.Text?.ToLower() ?? "";
|
||||||
|
|
||||||
MitarbeiterListe.ItemsSource = _alleMitarbeiter
|
MitarbeiterListe.ItemsSource = _alleMitarbeiter
|
||||||
.Where(m => m.Username.ToLower().Contains(text))
|
.Where(m => m.Username.ToLower().Contains(text))
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Öffnet Dialog zur Bearbeitung eines Mitarbeiters.
|
||||||
|
/// </summary>
|
||||||
private async void Bearbeiten_Click(object? sender, RoutedEventArgs e)
|
private async void Bearbeiten_Click(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is Button button && button.Tag is User benutzer)
|
if (sender is Button button && button.Tag is User benutzer)
|
||||||
{
|
{
|
||||||
var dialog = new MitarbeiterBearbeitenDialog(benutzer);
|
var dialog = new MitarbeiterBearbeitenDialog(benutzer);
|
||||||
var updatedUser = await dialog.ShowDialog<User>((Window)this.VisualRoot!);
|
var parentWindow = GetParentWindow();
|
||||||
|
|
||||||
|
if (parentWindow != null)
|
||||||
|
{
|
||||||
|
var updatedUser = await dialog.ShowDialog<User>(parentWindow);
|
||||||
|
|
||||||
if (updatedUser != null)
|
if (updatedUser != null)
|
||||||
{
|
{
|
||||||
updatedUser.Password = benutzer.Password;
|
updatedUser.Password = benutzer.Password;
|
||||||
updatedUser.Role = benutzer.Role;
|
updatedUser.Role = benutzer.Role;
|
||||||
updatedUser.OriginalUsername = benutzer.Username; // ← WICHTIG!
|
updatedUser.OriginalUsername = benutzer.Username;
|
||||||
|
|
||||||
_dbService.UpdateBenutzer(updatedUser);
|
_dbService.UpdateBenutzer(updatedUser);
|
||||||
LadeMitarbeiter();
|
LadeMitarbeiter();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine("❌ Bearbeiten_Click: Konnte übergeordnetes Fenster nicht ermitteln.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Löscht einen Benutzer nach Bestätigung.
|
||||||
|
/// </summary>
|
||||||
private async void Loeschen_Click(object? sender, RoutedEventArgs e)
|
private async void Loeschen_Click(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is Button button && button.Tag is User benutzer)
|
if (sender is Button button && button.Tag is User benutzer)
|
||||||
{
|
{
|
||||||
var dialog = new ConfirmDialog($"Möchten Sie den Benutzer '{benutzer.Username}' wirklich löschen?");
|
var dialog = new ConfirmDialog($"Möchten Sie den Benutzer '{benutzer.Username}' wirklich löschen?");
|
||||||
var result = await dialog.ShowDialog<bool>((Window)this.VisualRoot!);
|
var parentWindow = GetParentWindow();
|
||||||
|
|
||||||
|
if (parentWindow != null)
|
||||||
|
{
|
||||||
|
var result = await dialog.ShowDialog<bool>(parentWindow);
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
_dbService.LoescheBenutzer(benutzer.Username);
|
_dbService.LoescheBenutzer(benutzer.Username);
|
||||||
LadeMitarbeiter();
|
LadeMitarbeiter();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine("❌ Loeschen_Click: VisualRoot ist kein Window.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Setzt das Passwort eines Benutzers zurück auf "newpassword" (gehasht).
|
||||||
|
/// </summary>
|
||||||
|
private async void PasswortReset_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is Button button && button.Tag is User benutzer)
|
||||||
|
{
|
||||||
|
var parentWindow = GetParentWindow();
|
||||||
|
if (parentWindow != null)
|
||||||
|
{
|
||||||
|
var dialog = new ConfirmDialog($"Möchten Sie das Passwort für '{benutzer.Username}' wirklich zurücksetzen?");
|
||||||
|
var result = await dialog.ShowDialog<bool>(parentWindow);
|
||||||
|
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
benutzer.Password = PasswordHasher.HashPassword("newpassword");
|
||||||
|
benutzer.MussPasswortAendern = true;
|
||||||
|
|
||||||
|
_dbService.UpdateBenutzer(benutzer);
|
||||||
|
Console.WriteLine($"✅ Passwort für {benutzer.Username} wurde zurückgesetzt.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine("❌ PasswortReset_Click: VisualRoot ist kein Window.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Navigiert zurück zur Admin-Hauptansicht.
|
||||||
|
/// </summary>
|
||||||
private void ZurueckButton_Click(object? sender, RoutedEventArgs e)
|
private void ZurueckButton_Click(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -104,40 +178,4 @@ public partial class MitarbeiterListeView : UserControl
|
|||||||
Console.WriteLine($"❌ Fehler beim Zurückspringen: {ex.Message}");
|
Console.WriteLine($"❌ Fehler beim Zurückspringen: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void ResetPasswort_Click(object? sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
if (sender is Button button && button.Tag is User benutzer)
|
|
||||||
{
|
|
||||||
var dialog = new ConfirmDialog($"Soll das Passwort für '{benutzer.Username}' wirklich zurückgesetzt werden?");
|
|
||||||
var result = await dialog.ShowDialog<bool>((Window)this.VisualRoot!);
|
|
||||||
|
|
||||||
if (result)
|
|
||||||
{
|
|
||||||
_dbService.ResetBenutzerPasswort(benutzer.Username);
|
|
||||||
Console.WriteLine($"✅ Passwort für {benutzer.Username} zurückgesetzt.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void PasswortReset_Click(object? sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
if (sender is Button button && button.Tag is User benutzer)
|
|
||||||
{
|
|
||||||
var dialog = new ConfirmDialog($"Möchten Sie das Passwort für '{benutzer.Username}' wirklich zurücksetzen?");
|
|
||||||
var result = await dialog.ShowDialog<bool>((Window)this.VisualRoot!);
|
|
||||||
|
|
||||||
if (result)
|
|
||||||
{
|
|
||||||
benutzer.Password = "newpassword"; // 💡 hier später besser: generiertes oder festgelegtes Initialpasswort
|
|
||||||
benutzer.MussPasswortAendern = true;
|
|
||||||
|
|
||||||
_dbService.UpdateBenutzer(benutzer);
|
|
||||||
|
|
||||||
Console.WriteLine($"✅ Passwort für {benutzer.Username} wurde zurückgesetzt.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,14 @@
|
|||||||
<Window xmlns="https://github.com/avaloniaui"
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
x:Class="ChronoFlow.View.Admin.ProjektBearbeitenDialog"
|
x:Class="ChronoFlow.View.Admin.ProjektBearbeitenDialog"
|
||||||
Width="500" Height="600"
|
Width="600" Height="700"
|
||||||
|
MinWidth="500" MinHeight="650"
|
||||||
Title="Projekt bearbeiten">
|
Title="Projekt bearbeiten">
|
||||||
|
|
||||||
<ScrollViewer>
|
<ScrollViewer>
|
||||||
<StackPanel Margin="20" Spacing="15">
|
<StackPanel Margin="25" Spacing="15">
|
||||||
|
|
||||||
|
<!-- Eingabefelder -->
|
||||||
<TextBlock Text="Projektname:" />
|
<TextBlock Text="Projektname:" />
|
||||||
<TextBox x:Name="ProjektnameBox" />
|
<TextBox x:Name="ProjektnameBox" />
|
||||||
|
|
||||||
@ -21,10 +24,25 @@
|
|||||||
<TextBlock Text="Enddatum:" />
|
<TextBlock Text="Enddatum:" />
|
||||||
<DatePicker x:Name="EndzeitPicker" />
|
<DatePicker x:Name="EndzeitPicker" />
|
||||||
|
|
||||||
|
<!-- Hinweisbereich farblich und visuell getrennt -->
|
||||||
|
<Border Background="#111111" CornerRadius="5" Padding="10" Margin="0,10,0,0">
|
||||||
|
<StackPanel Spacing="5">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="5">
|
||||||
|
<TextBlock Text="🛈" FontWeight="Bold" />
|
||||||
|
<TextBlock Text="Hinweise zur Deadline-Anzeige:" FontWeight="Bold" />
|
||||||
|
</StackPanel>
|
||||||
|
<TextBlock Text="• Grün: Noch mehr als 7 Tage bis zur Deadline" />
|
||||||
|
<TextBlock Text="• Orange: Weniger als 7 Tage bis zur Deadline" />
|
||||||
|
<TextBlock Text="• Rot: Weniger als 3 Tage bis zur Deadline oder bereits abgelaufen" />
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- Buttons -->
|
||||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="20" Margin="0,15,0,0">
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="20" Margin="0,15,0,0">
|
||||||
<Button Content="✅ Speichern" Width="120" Click="SpeichernButton_Click" />
|
<Button Content="✅ Speichern" Width="120" Click="SpeichernButton_Click" />
|
||||||
<Button Content="❌ Abbrechen" Width="130" Click="AbbrechenButton_Click" />
|
<Button Content="❌ Abbrechen" Width="130" Click="AbbrechenButton_Click" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</Window>
|
</Window>
|
||||||
@ -6,28 +6,52 @@ using ChronoFlow.Persistence;
|
|||||||
|
|
||||||
namespace ChronoFlow.View.Admin;
|
namespace ChronoFlow.View.Admin;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dialogfenster zum Bearbeiten eines Projekts (Zeiteintrag).
|
||||||
|
/// </summary>
|
||||||
public partial class ProjektBearbeitenDialog : Window
|
public partial class ProjektBearbeitenDialog : Window
|
||||||
{
|
{
|
||||||
public Zeiteintrag UpdatedProjekt { get; private set; }
|
/// <summary>
|
||||||
|
/// Der bearbeitete Zeiteintrag, der beim Schließen zurückgegeben wird.
|
||||||
|
/// </summary>
|
||||||
|
public Zeiteintrag UpdatedProjekt { get; private set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Öffentlicher parameterloser Konstruktor (z. B. für Design-Time oder XAML-Loader).
|
||||||
|
/// </summary>
|
||||||
|
public ProjektBearbeitenDialog()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
Console.WriteLine("⚠ Parameterloser Konstruktor verwendet. Achtung: Kein Projekt übergeben.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Konstruktor – initialisiert das Fenster mit bestehenden Projektdaten.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="projekt">Das Projekt (Zeiteintrag), das bearbeitet wird.</param>
|
||||||
public ProjektBearbeitenDialog(Zeiteintrag projekt)
|
public ProjektBearbeitenDialog(Zeiteintrag projekt)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
var dbService = new SqliteZeiterfassungsService();
|
var dbService = new SqliteZeiterfassungsService();
|
||||||
var mitarbeiter = dbService.LadeAlleMitarbeiterNamen();
|
var mitarbeiter = dbService.LadeAlleMitarbeiterNamen();
|
||||||
|
|
||||||
MitarbeiterDropdown.ItemsSource = mitarbeiter;
|
MitarbeiterDropdown.ItemsSource = mitarbeiter;
|
||||||
MitarbeiterDropdown.SelectedItem = projekt.Mitarbeiter;
|
MitarbeiterDropdown.SelectedItem = projekt.Mitarbeiter;
|
||||||
|
|
||||||
// Vorbelegen
|
// Felder vorbelegen
|
||||||
ProjektnameBox.Text = projekt.Projekt;
|
ProjektnameBox.Text = projekt.Projekt;
|
||||||
KommentarBox.Text = projekt.Kommentar;
|
KommentarBox.Text = projekt.Kommentar;
|
||||||
StartzeitPicker.SelectedDate = new DateTimeOffset(projekt.Startzeit);
|
StartzeitPicker.SelectedDate = new DateTimeOffset(projekt.Startzeit);
|
||||||
EndzeitPicker.SelectedDate = new DateTimeOffset(projekt.Endzeit);
|
EndzeitPicker.SelectedDate = new DateTimeOffset(projekt.Endzeit);
|
||||||
|
|
||||||
|
// Referenz übernehmen
|
||||||
UpdatedProjekt = projekt;
|
UpdatedProjekt = projekt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Übernimmt die Änderungen und schließt das Dialogfenster.
|
||||||
|
/// </summary>
|
||||||
private void SpeichernButton_Click(object? sender, RoutedEventArgs e)
|
private void SpeichernButton_Click(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
UpdatedProjekt.Projekt = ProjektnameBox.Text ?? "";
|
UpdatedProjekt.Projekt = ProjektnameBox.Text ?? "";
|
||||||
@ -39,9 +63,11 @@ public partial class ProjektBearbeitenDialog : Window
|
|||||||
Close(UpdatedProjekt);
|
Close(UpdatedProjekt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Schließt den Dialog ohne Änderungen.
|
||||||
|
/// </summary>
|
||||||
private void AbbrechenButton_Click(object? sender, RoutedEventArgs e)
|
private void AbbrechenButton_Click(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
Close(null);
|
Close(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -2,40 +2,93 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
x:Class="ChronoFlow.View.Admin.ProjektErstellenView">
|
x:Class="ChronoFlow.View.Admin.ProjektErstellenView">
|
||||||
|
|
||||||
|
<!-- Scrollfähiger Bereich für kleinere Fenster -->
|
||||||
<ScrollViewer>
|
<ScrollViewer>
|
||||||
<StackPanel Margin="20" Spacing="15">
|
<StackPanel Margin="25" Spacing="15">
|
||||||
|
|
||||||
<TextBlock Text="Neues Projekt erstellen" FontSize="20" FontWeight="Bold" HorizontalAlignment="Center" />
|
<!-- Titelüberschrift -->
|
||||||
|
<TextBlock Text="Neues Projekt erstellen"
|
||||||
|
FontSize="20"
|
||||||
|
FontWeight="Bold"
|
||||||
|
HorizontalAlignment="Center" />
|
||||||
|
|
||||||
|
<!-- Eingabefeld: Projektname -->
|
||||||
<TextBlock Text="Projektname:" />
|
<TextBlock Text="Projektname:" />
|
||||||
<TextBox x:Name="ProjektnameBox" />
|
<TextBox x:Name="ProjektnameBox" />
|
||||||
|
|
||||||
|
<!-- Startdatum auswählen -->
|
||||||
<TextBlock Text="Startdatum:" />
|
<TextBlock Text="Startdatum:" />
|
||||||
<DatePicker x:Name="StartdatumPicker" />
|
<DatePicker x:Name="StartdatumPicker" />
|
||||||
|
|
||||||
|
<!-- Startzeit im Format HH:mm -->
|
||||||
<TextBlock Text="Startzeit (HH:mm):" />
|
<TextBlock Text="Startzeit (HH:mm):" />
|
||||||
<TextBox x:Name="StartzeitBox" Watermark="z.B. 09:00" />
|
<TextBox x:Name="StartzeitBox" Watermark="z.B. 09:00" />
|
||||||
|
|
||||||
|
<!-- Enddatum auswählen -->
|
||||||
<TextBlock Text="Enddatum:" />
|
<TextBlock Text="Enddatum:" />
|
||||||
<DatePicker x:Name="EnddatumPicker" />
|
<DatePicker x:Name="EnddatumPicker" />
|
||||||
|
|
||||||
|
<!-- Endzeit im Format HH:mm -->
|
||||||
<TextBlock Text="Endzeit (HH:mm):" />
|
<TextBlock Text="Endzeit (HH:mm):" />
|
||||||
<TextBox x:Name="EndzeitBox" Watermark="z.B. 17:00" />
|
<TextBox x:Name="EndzeitBox" Watermark="z.B. 17:00" />
|
||||||
|
|
||||||
|
<!-- Projektleiter über Dropdown zuweisen -->
|
||||||
|
<TextBlock Text="Projektleiter auswählen:" />
|
||||||
|
<ComboBox x:Name="ProjektleiterDropdown" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Mitarbeiter über Dropdown zuweisen -->
|
||||||
<TextBlock Text="Mitarbeiter auswählen:" />
|
<TextBlock Text="Mitarbeiter auswählen:" />
|
||||||
<ComboBox x:Name="MitarbeiterDropdown" />
|
<ComboBox x:Name="MitarbeiterDropdown" />
|
||||||
|
|
||||||
|
<!-- Kommentarbereich -->
|
||||||
<TextBlock Text="Kommentar:" />
|
<TextBlock Text="Kommentar:" />
|
||||||
<TextBox x:Name="KommentarBox" AcceptsReturn="True" Height="60" />
|
<TextBox x:Name="KommentarBox" AcceptsReturn="True" Height="80" />
|
||||||
|
|
||||||
<StackPanel Orientation="Horizontal" Spacing="10" HorizontalAlignment="Center" Margin="0,10,0,0">
|
<!-- Hinweise zur Deadline-Farbe -->
|
||||||
<Button Content="✅ Speichern" Click="SpeichernButton_Click" Width="115" />
|
<Border Background="#111111"
|
||||||
<Button Content="⬅ Zurück zum Dashboard" Click="ZurueckButton_Click" Width="180" />
|
CornerRadius="5"
|
||||||
|
Padding="10"
|
||||||
|
Margin="0,10,0,0">
|
||||||
|
<StackPanel Spacing="5">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="5">
|
||||||
|
<TextBlock Text="🛈" FontWeight="Bold" />
|
||||||
|
<TextBlock Text="Hinweise zur Deadline-Anzeige:" FontWeight="Bold" />
|
||||||
|
</StackPanel>
|
||||||
|
<TextBlock Text="• Grün: Noch mehr als 7 Tage bis zur Deadline" />
|
||||||
|
<TextBlock Text="• Orange: Weniger als 7 Tage bis zur Deadline" />
|
||||||
|
<TextBlock Text="• Rot: Weniger als 3 Tage bis zur Deadline oder bereits abgelaufen" />
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- Aktions-Buttons -->
|
||||||
|
<StackPanel Orientation="Horizontal"
|
||||||
|
Spacing="10"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Margin="0,10,0,0">
|
||||||
|
<Button Content="✅ Speichern"
|
||||||
|
Click="SpeichernButton_Click"
|
||||||
|
Width="115"
|
||||||
|
Height="36" />
|
||||||
|
|
||||||
|
<Button Content="🧪 3 Demo-Projekte (rot/gelb/grün)"
|
||||||
|
Click="DemoProjekteButton_Click"
|
||||||
|
Width="250"
|
||||||
|
Height="36" />
|
||||||
|
|
||||||
|
<Button Content="⬅ Zurück zum Dashboard"
|
||||||
|
Click="ZurueckButton_Click"
|
||||||
|
Width="180"
|
||||||
|
Height="36" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<TextBlock x:Name="FeedbackText" Foreground="Red" IsVisible="False" />
|
<!-- Optionaler Fehler-/Hinweistext -->
|
||||||
|
<TextBlock x:Name="FeedbackText"
|
||||||
|
Foreground="Red"
|
||||||
|
IsVisible="False"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Margin="0,10,0,0" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
|
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@ -6,22 +6,41 @@ using Avalonia.Media;
|
|||||||
using ChronoFlow.Model;
|
using ChronoFlow.Model;
|
||||||
using ChronoFlow.Persistence;
|
using ChronoFlow.Persistence;
|
||||||
|
|
||||||
namespace ChronoFlow.View.Admin
|
namespace ChronoFlow.View.Admin;
|
||||||
{
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ansicht für Admins zum Erstellen neuer Projekte.
|
||||||
|
/// Wird intern als Zeiteintrag gespeichert.
|
||||||
|
/// </summary>
|
||||||
public partial class ProjektErstellenView : UserControl
|
public partial class ProjektErstellenView : UserControl
|
||||||
{
|
{
|
||||||
private readonly ViewManager _viewManager;
|
private readonly ViewManager _viewManager;
|
||||||
|
|
||||||
|
public ProjektErstellenView() : this(new ViewManager(new ContentControl()))
|
||||||
|
{
|
||||||
|
Console.WriteLine("⚠ Parameterloser Konstruktor genutzt. Dummy-ViewManager initialisiert.");
|
||||||
|
}
|
||||||
|
|
||||||
public ProjektErstellenView(ViewManager viewManager)
|
public ProjektErstellenView(ViewManager viewManager)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
_viewManager = viewManager;
|
_viewManager = viewManager;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
var dbService = new SqliteZeiterfassungsService();
|
var dbService = new SqliteZeiterfassungsService();
|
||||||
List<string> mitarbeiter = dbService.LadeAlleMitarbeiterNamen();
|
List<string> mitarbeiter = dbService.LadeAlleMitarbeiterNamen();
|
||||||
|
|
||||||
// ✅ Nur ItemsSource verwenden, nicht Items
|
|
||||||
MitarbeiterDropdown.ItemsSource = mitarbeiter;
|
MitarbeiterDropdown.ItemsSource = mitarbeiter;
|
||||||
|
ProjektleiterDropdown.ItemsSource = mitarbeiter;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"❌ Fehler beim Laden der Mitarbeiterauswahl: {ex.Message}");
|
||||||
|
FeedbackText.Text = "⚠ Fehler beim Laden der Mitarbeiter.";
|
||||||
|
FeedbackText.Foreground = Brushes.Red;
|
||||||
|
FeedbackText.IsVisible = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SpeichernButton_Click(object? sender, RoutedEventArgs e)
|
private void SpeichernButton_Click(object? sender, RoutedEventArgs e)
|
||||||
@ -33,10 +52,12 @@ namespace ChronoFlow.View.Admin
|
|||||||
string endzeitText = EndzeitBox.Text ?? "00:00";
|
string endzeitText = EndzeitBox.Text ?? "00:00";
|
||||||
string mitarbeiter = MitarbeiterDropdown.SelectedItem?.ToString() ?? "";
|
string mitarbeiter = MitarbeiterDropdown.SelectedItem?.ToString() ?? "";
|
||||||
string kommentar = KommentarBox.Text ?? "";
|
string kommentar = KommentarBox.Text ?? "";
|
||||||
|
string projektleiter = ProjektleiterDropdown.SelectedItem?.ToString() ?? "";
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(projektname) || string.IsNullOrWhiteSpace(mitarbeiter))
|
// 🛑 Pflichtfeldprüfung
|
||||||
|
if (string.IsNullOrWhiteSpace(projektname) || string.IsNullOrWhiteSpace(mitarbeiter) || string.IsNullOrWhiteSpace(projektleiter))
|
||||||
{
|
{
|
||||||
FeedbackText.Text = "⚠ Bitte Projektname und Mitarbeiter ausfüllen!";
|
FeedbackText.Text = "⚠ Bitte alle Pflichtfelder ausfüllen (Projektname, Mitarbeiter, Projektleiter)!";
|
||||||
FeedbackText.Foreground = Brushes.Red;
|
FeedbackText.Foreground = Brushes.Red;
|
||||||
FeedbackText.IsVisible = true;
|
FeedbackText.IsVisible = true;
|
||||||
return;
|
return;
|
||||||
@ -61,9 +82,9 @@ namespace ChronoFlow.View.Admin
|
|||||||
DateTime startDateTime = startdatum + startzeit;
|
DateTime startDateTime = startdatum + startzeit;
|
||||||
DateTime endDateTime = enddatum + endzeit;
|
DateTime endDateTime = enddatum + endzeit;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
var dbService = new SqliteZeiterfassungsService();
|
var dbService = new SqliteZeiterfassungsService();
|
||||||
|
|
||||||
// 💡 Achtung: Aktuell speichern wir als Zeiteintrag (kein eigenes Projektmodell!)
|
|
||||||
dbService.SpeichereEintrag(new Zeiteintrag
|
dbService.SpeichereEintrag(new Zeiteintrag
|
||||||
{
|
{
|
||||||
Mitarbeiter = mitarbeiter,
|
Mitarbeiter = mitarbeiter,
|
||||||
@ -71,6 +92,7 @@ namespace ChronoFlow.View.Admin
|
|||||||
Startzeit = startDateTime,
|
Startzeit = startDateTime,
|
||||||
Endzeit = endDateTime,
|
Endzeit = endDateTime,
|
||||||
Kommentar = kommentar,
|
Kommentar = kommentar,
|
||||||
|
Projektleiter = projektleiter,
|
||||||
Erledigt = false
|
Erledigt = false
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -78,7 +100,7 @@ namespace ChronoFlow.View.Admin
|
|||||||
FeedbackText.Foreground = Brushes.Green;
|
FeedbackText.Foreground = Brushes.Green;
|
||||||
FeedbackText.IsVisible = true;
|
FeedbackText.IsVisible = true;
|
||||||
|
|
||||||
// Felder zurücksetzen
|
// Felder leeren
|
||||||
ProjektnameBox.Text = "";
|
ProjektnameBox.Text = "";
|
||||||
KommentarBox.Text = "";
|
KommentarBox.Text = "";
|
||||||
StartdatumPicker.SelectedDate = DateTime.Today;
|
StartdatumPicker.SelectedDate = DateTime.Today;
|
||||||
@ -86,17 +108,89 @@ namespace ChronoFlow.View.Admin
|
|||||||
StartzeitBox.Text = "09:00";
|
StartzeitBox.Text = "09:00";
|
||||||
EndzeitBox.Text = "17:00";
|
EndzeitBox.Text = "17:00";
|
||||||
MitarbeiterDropdown.SelectedItem = null;
|
MitarbeiterDropdown.SelectedItem = null;
|
||||||
|
ProjektleiterDropdown.SelectedItem = null;
|
||||||
|
|
||||||
// 🔄 Dashboard aktualisieren, wenn zurück
|
// Ansicht aktualisieren
|
||||||
if (_viewManager.TryGetView<AdminMainView>("AdminMain", out var adminView) && adminView != null)
|
if (_viewManager.TryGetView<AdminMainView>("AdminMain", out var adminView) && adminView != null)
|
||||||
{
|
{
|
||||||
adminView.AktualisiereLetzteProjekte();
|
adminView.AktualisiereLetzteProjekte();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
FeedbackText.Text = $"❌ Fehler beim Speichern: {ex.Message}";
|
||||||
|
FeedbackText.Foreground = Brushes.Red;
|
||||||
|
FeedbackText.IsVisible = true;
|
||||||
|
Console.WriteLine("❌ Ausnahme beim Speichern:");
|
||||||
|
Console.WriteLine(ex.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DemoProjekteButton_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (MitarbeiterDropdown.SelectedItem is not string mitarbeiterName)
|
||||||
|
{
|
||||||
|
FeedbackText.Text = "❗ Bitte zuerst einen Mitarbeiter auswählen.";
|
||||||
|
FeedbackText.Foreground = Brushes.Red;
|
||||||
|
FeedbackText.IsVisible = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ProjektleiterDropdown.SelectedItem is not string projektleiterName)
|
||||||
|
{
|
||||||
|
FeedbackText.Text = "❗ Bitte wähle auch einen Projektleiter für die Demo-Projekte.";
|
||||||
|
FeedbackText.Foreground = Brushes.Red;
|
||||||
|
FeedbackText.IsVisible = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var service = new SqliteZeiterfassungsService();
|
||||||
|
var heute = DateTime.Today;
|
||||||
|
|
||||||
|
var projekte = new List<Zeiteintrag>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Projekt = "Demo: Langfristig (grün)",
|
||||||
|
Kommentar = "✅ Deadline in mehr als 7 Tagen.",
|
||||||
|
Startzeit = heute.AddDays(-1).AddHours(9),
|
||||||
|
Endzeit = heute.AddDays(10).AddHours(17),
|
||||||
|
Mitarbeiter = mitarbeiterName,
|
||||||
|
Projektleiter = projektleiterName,
|
||||||
|
Erledigt = false
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Projekt = "Demo: Mittel (Orange)",
|
||||||
|
Kommentar = "🟡 Deadline in 3–7 Tagen.",
|
||||||
|
Startzeit = heute.AddDays(-1).AddHours(9),
|
||||||
|
Endzeit = heute.AddDays(5).AddHours(17),
|
||||||
|
Mitarbeiter = mitarbeiterName,
|
||||||
|
Projektleiter = projektleiterName,
|
||||||
|
Erledigt = false
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Projekt = "Demo: Kurzfristig (rot)",
|
||||||
|
Kommentar = "🔴 Deadline in weniger als 3 Tagen.",
|
||||||
|
Startzeit = heute.AddDays(-1).AddHours(9),
|
||||||
|
Endzeit = heute.AddDays(1).AddHours(17),
|
||||||
|
Mitarbeiter = mitarbeiterName,
|
||||||
|
Projektleiter = projektleiterName,
|
||||||
|
Erledigt = false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var p in projekte)
|
||||||
|
service.SpeichereEintrag(p);
|
||||||
|
|
||||||
|
FeedbackText.Text = "✔️ 3 Demo-Projekte wurden erfolgreich erstellt.";
|
||||||
|
FeedbackText.Foreground = Brushes.Green;
|
||||||
|
FeedbackText.IsVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
private void ZurueckButton_Click(object? sender, RoutedEventArgs e)
|
private void ZurueckButton_Click(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
_viewManager.Show("AdminMain");
|
_viewManager.Show("AdminMain");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
@ -2,19 +2,43 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
x:Class="ChronoFlow.View.LoginWindow"
|
x:Class="ChronoFlow.View.LoginWindow"
|
||||||
Width="400" Height="300"
|
Width="400" Height="300"
|
||||||
Title="ChronoFlow Login">
|
Title="ChronoFlow Login"
|
||||||
|
CanResize="False"
|
||||||
|
WindowStartupLocation="CenterScreen">
|
||||||
|
|
||||||
<StackPanel Margin="20" Spacing="10">
|
<StackPanel Margin="20" Spacing="10">
|
||||||
<TextBlock Text="ChronoFlow Login" FontSize="24" FontWeight="Bold" HorizontalAlignment="Center" />
|
|
||||||
|
|
||||||
|
<!-- 🔐 Überschrift -->
|
||||||
|
<TextBlock Text="ChronoFlow Login"
|
||||||
|
FontSize="24"
|
||||||
|
FontWeight="Bold"
|
||||||
|
HorizontalAlignment="Center" />
|
||||||
|
|
||||||
|
<!-- 👤 Benutzername -->
|
||||||
<TextBlock Text="Benutzername" />
|
<TextBlock Text="Benutzername" />
|
||||||
<TextBox x:Name="UsernameBox" />
|
<TextBox x:Name="UsernameBox"
|
||||||
|
Watermark="z. B. admin" />
|
||||||
|
|
||||||
|
<!-- 🔒 Passwort -->
|
||||||
<TextBlock Text="Passwort" />
|
<TextBlock Text="Passwort" />
|
||||||
<TextBox x:Name="PasswordBox" PasswordChar="●" />
|
<TextBox x:Name="PasswordBox"
|
||||||
|
PasswordChar="●"
|
||||||
|
Watermark="Passwort" />
|
||||||
|
|
||||||
<Button Content="Anmelden" Click="LoginButton_Click" HorizontalAlignment="Center" Width="120" Margin="0,10,0,0" />
|
<!-- 🟩 Login-Button -->
|
||||||
|
<Button Content="Anmelden"
|
||||||
|
Click="LoginButton_Click"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Width="120"
|
||||||
|
Margin="0,10,0,0" />
|
||||||
|
|
||||||
|
<!-- ❌ Fehleranzeige -->
|
||||||
|
<TextBlock x:Name="ErrorText"
|
||||||
|
Foreground="Red"
|
||||||
|
IsVisible="False"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
TextAlignment="Center"
|
||||||
|
FontWeight="SemiBold" />
|
||||||
|
|
||||||
<TextBlock x:Name="ErrorText" Foreground="Red" IsVisible="False" TextWrapping="Wrap" TextAlignment="Center" />
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Window>
|
</Window>
|
||||||
@ -6,9 +6,10 @@ using ChronoFlow.Controller;
|
|||||||
using ChronoFlow.Persistence;
|
using ChronoFlow.Persistence;
|
||||||
using ChronoFlow.Security;
|
using ChronoFlow.Security;
|
||||||
using ChronoFlow.View.Security;
|
using ChronoFlow.View.Security;
|
||||||
|
using Microsoft.Data.Sqlite;
|
||||||
|
|
||||||
|
namespace ChronoFlow.View;
|
||||||
|
|
||||||
namespace ChronoFlow.View
|
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Das Fenster für den Benutzer-Login.
|
/// Das Fenster für den Benutzer-Login.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -16,17 +17,33 @@ namespace ChronoFlow.View
|
|||||||
{
|
{
|
||||||
private readonly LoginController _loginController;
|
private readonly LoginController _loginController;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Konstruktor – Initialisiert die Oberfläche und legt bei Bedarf einen Standard-Admin an.
|
||||||
|
/// </summary>
|
||||||
public LoginWindow()
|
public LoginWindow()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
_loginController = new LoginController();
|
_loginController = new LoginController();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
var service = new SqliteZeiterfassungsService();
|
var service = new SqliteZeiterfassungsService();
|
||||||
service.ErstelleStandardAdmin();
|
service.ErstelleStandardAdmin();
|
||||||
}
|
}
|
||||||
|
catch (SqliteException ex) when (ex.SqliteErrorCode == 5)
|
||||||
|
{
|
||||||
|
ErrorText.Text = "⚠️ Die Datenbank ist gesperrt. Bitte schließen Sie andere Programme (z. B. DB Browser for SQLite) und starten Sie die App neu.";
|
||||||
|
ErrorText.IsVisible = true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ErrorText.Text = $"Fehler beim Initialisieren: {ex.Message}";
|
||||||
|
ErrorText.IsVisible = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Wird ausgeführt, wenn der Benutzer auf "Anmelden" klickt.
|
/// Wird aufgerufen, wenn der Benutzer auf "Anmelden" klickt.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async void LoginButton_Click(object? sender, RoutedEventArgs e)
|
private async void LoginButton_Click(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
@ -40,10 +57,23 @@ namespace ChronoFlow.View
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var service = new SqliteZeiterfassungsService();
|
SqliteZeiterfassungsService service;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
service = new SqliteZeiterfassungsService();
|
||||||
|
}
|
||||||
|
catch (SqliteException ex) when (ex.SqliteErrorCode == 5)
|
||||||
|
{
|
||||||
|
ErrorText.Text = "⚠️ Die Datenbank ist gesperrt. Bitte schließen Sie andere Programme (z. B. DB Browser) und versuchen Sie es erneut.";
|
||||||
|
ErrorText.IsVisible = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var benutzerListe = service.LadeAlleBenutzer();
|
var benutzerListe = service.LadeAlleBenutzer();
|
||||||
|
|
||||||
var matchingUsers = benutzerListe.Where(u => u.Username == username).ToList();
|
var matchingUsers = benutzerListe
|
||||||
|
.Where(u => u.Username.Equals(username, StringComparison.OrdinalIgnoreCase))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
if (matchingUsers.Count == 0)
|
if (matchingUsers.Count == 0)
|
||||||
{
|
{
|
||||||
@ -54,7 +84,6 @@ namespace ChronoFlow.View
|
|||||||
|
|
||||||
if (matchingUsers.Count > 1)
|
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.Text = "Interner Fehler: Mehrere Benutzer mit gleichem Namen.";
|
||||||
ErrorText.IsVisible = true;
|
ErrorText.IsVisible = true;
|
||||||
return;
|
return;
|
||||||
@ -62,21 +91,14 @@ namespace ChronoFlow.View
|
|||||||
|
|
||||||
var user = matchingUsers.First();
|
var user = matchingUsers.First();
|
||||||
|
|
||||||
Console.WriteLine($"[DEBUG] Benutzer gefunden: {user.Username}");
|
if (!PasswordHasher.VerifyPassword(password, user.Password))
|
||||||
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.Text = "Falsches Passwort. Bitte erneut versuchen.";
|
||||||
ErrorText.IsVisible = true;
|
ErrorText.IsVisible = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wenn Passwortänderung erforderlich, Dialog anzeigen
|
// Passwort muss geändert werden
|
||||||
if (user.MussPasswortAendern)
|
if (user.MussPasswortAendern)
|
||||||
{
|
{
|
||||||
var dialog = new PasswortAendernDialog(user);
|
var dialog = new PasswortAendernDialog(user);
|
||||||
@ -85,14 +107,9 @@ namespace ChronoFlow.View
|
|||||||
if (!string.IsNullOrEmpty(neuesPasswort))
|
if (!string.IsNullOrEmpty(neuesPasswort))
|
||||||
{
|
{
|
||||||
string neuerHash = PasswordHasher.HashPassword(neuesPasswort);
|
string neuerHash = PasswordHasher.HashPassword(neuesPasswort);
|
||||||
Console.WriteLine($"[DEBUG] Neues Passwort (klar): {neuesPasswort}");
|
|
||||||
Console.WriteLine($"[DEBUG] Neuer gespeicherter Hash: {neuerHash}");
|
|
||||||
|
|
||||||
user.Password = neuerHash;
|
user.Password = neuerHash;
|
||||||
user.MussPasswortAendern = false;
|
user.MussPasswortAendern = false;
|
||||||
|
|
||||||
service.UpdateBenutzer(user);
|
service.UpdateBenutzer(user);
|
||||||
Console.WriteLine("✅ Passwort wurde erfolgreich geändert.");
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -102,32 +119,29 @@ namespace ChronoFlow.View
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🚀 Login erfolgreich → MainWindow starten
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Console.WriteLine("🚀 Öffne MainWindow...");
|
// Login-Zeiten aktualisieren
|
||||||
|
var vorher = user.LetzterLogin;
|
||||||
// ⏱️ bisherigen Login sichern und VorletzterLogin setzen
|
|
||||||
var bisherigerLogin = user.LetzterLogin;
|
|
||||||
user.LetzterLogin = DateTime.Now;
|
user.LetzterLogin = DateTime.Now;
|
||||||
user.VorletzterLogin = bisherigerLogin;
|
user.VorletzterLogin = vorher;
|
||||||
|
|
||||||
// 💾 Neue Login-Zeitpunkte speichern
|
|
||||||
service.UpdateLoginZeiten(user);
|
service.UpdateLoginZeiten(user);
|
||||||
|
|
||||||
// Fenster öffnen
|
// Hauptfenster öffnen
|
||||||
var main = new MainWindow(user);
|
var main = new MainWindow(user);
|
||||||
main.Show();
|
main.Show();
|
||||||
main.Activate(); // ✨ explizit in den Vordergrund bringen
|
main.Activate();
|
||||||
Close(); // LoginWindow erst danach schließen
|
Close();
|
||||||
|
}
|
||||||
|
catch (SqliteException ex) when (ex.SqliteErrorCode == 5)
|
||||||
|
{
|
||||||
|
ErrorText.Text = "⚠️ Datenbank gesperrt. Bitte schließen Sie andere Programme und starten Sie die App neu.";
|
||||||
|
ErrorText.IsVisible = true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"[ERROR] MainWindow konnte nicht geöffnet werden: {ex.Message}");
|
ErrorText.Text = $"Interner Fehler beim Starten des Hauptfensters: {ex.Message}";
|
||||||
ErrorText.Text = "Interner Fehler beim Starten des Hauptfensters.";
|
|
||||||
ErrorText.IsVisible = true;
|
ErrorText.IsVisible = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,13 +6,33 @@ using ChronoFlow.Persistence;
|
|||||||
using ChronoFlow.View.Admin;
|
using ChronoFlow.View.Admin;
|
||||||
using ChronoFlow.View.Mitarbeiter;
|
using ChronoFlow.View.Mitarbeiter;
|
||||||
|
|
||||||
namespace ChronoFlow.View
|
namespace ChronoFlow.View;
|
||||||
{
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hauptfenster der Anwendung – je nach Benutzerrolle wird die passende Startansicht geladen.
|
||||||
|
/// </summary>
|
||||||
public partial class MainWindow : Window
|
public partial class MainWindow : Window
|
||||||
{
|
{
|
||||||
private readonly ViewManager _viewManager;
|
private readonly ViewManager _viewManager;
|
||||||
private readonly User _loggedInUser;
|
private readonly User _loggedInUser;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parameterloser Konstruktor – erforderlich für Avalonia Runtime Loader.
|
||||||
|
/// Startet mit einem Dummy-Nutzer (Demo-Zwecke oder Designer-Unterstützung).
|
||||||
|
/// </summary>
|
||||||
|
public MainWindow() : this(new User
|
||||||
|
{
|
||||||
|
Username = "DemoUser",
|
||||||
|
Role = "Mitarbeiter"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
Console.WriteLine("⚠ Achtung: MainWindow ohne echten Benutzer gestartet (Demo-Modus).");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Konstruktor – bekommt den eingeloggten Benutzer übergeben.
|
||||||
|
/// Registriert alle möglichen Views und zeigt die Startansicht je nach Rolle.
|
||||||
|
/// </summary>
|
||||||
public MainWindow(User user)
|
public MainWindow(User user)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@ -20,54 +40,72 @@ namespace ChronoFlow.View
|
|||||||
_loggedInUser = user;
|
_loggedInUser = user;
|
||||||
Console.WriteLine($"[DEBUG] MainWindow gestartet für Benutzer: {_loggedInUser.Username} ({_loggedInUser.Role})");
|
Console.WriteLine($"[DEBUG] MainWindow gestartet für Benutzer: {_loggedInUser.Username} ({_loggedInUser.Role})");
|
||||||
|
|
||||||
|
// Initialisierung des ViewManagers mit der ContentArea (wird in XAML gesetzt)
|
||||||
_viewManager = new ViewManager(ContentArea);
|
_viewManager = new ViewManager(ContentArea);
|
||||||
|
|
||||||
|
// 🔄 Registrierung aller Views, die im Projekt vorkommen
|
||||||
_viewManager.Register("ProjektErstellen", () => new ProjektErstellenView(_viewManager));
|
_viewManager.Register("ProjektErstellen", () => new ProjektErstellenView(_viewManager));
|
||||||
_viewManager.Register("MitarbeiterHinzufuegen", () => new MitarbeiterHinzufuegenView());
|
_viewManager.Register("MitarbeiterHinzufuegen", () => new MitarbeiterHinzufuegenView());
|
||||||
_viewManager.Register("AdminMain", () => new AdminMainView(_viewManager));
|
_viewManager.Register("AdminMain", () => new AdminMainView(_viewManager));
|
||||||
_viewManager.Register("AlleProjekte", () => new AlleProjekteView(_viewManager));
|
_viewManager.Register("AlleProjekte", () => new AlleProjekteView(_viewManager));
|
||||||
_viewManager.Register("MitarbeiterListe", () => new MitarbeiterListeView(_viewManager));
|
_viewManager.Register("MitarbeiterListe", () => new MitarbeiterListeView(_viewManager));
|
||||||
_viewManager.Register("AbgeschlosseneProjekte", () => new AbgeschlosseneProjekteView(_viewManager));
|
_viewManager.Register("AbgeschlosseneProjekte", () => new AbgeschlosseneProjekteView(_viewManager));
|
||||||
_viewManager.Register("Zeiterfassung", () =>
|
_viewManager.Register("Zeiterfassung", () => new EmployeeTasksView(_loggedInUser, new SqliteZeiterfassungsService()));
|
||||||
new EmployeeTasksView(_loggedInUser, new SqliteZeiterfassungsService()));
|
|
||||||
|
|
||||||
|
// 🧭 Starte mit der passenden Ansicht je nach Rolle
|
||||||
if (_loggedInUser.Role == "Admin")
|
if (_loggedInUser.Role == "Admin")
|
||||||
{
|
{
|
||||||
_viewManager.Show("AdminMain");
|
_viewManager.Show("AdminMain");
|
||||||
}
|
}
|
||||||
else if (_loggedInUser.Role == "Mitarbeiter")
|
else if (_loggedInUser.Role == "Mitarbeiter")
|
||||||
{
|
{
|
||||||
_viewManager.Show("Zeiterfassung"); // ⏳ später: MitarbeiterMain
|
_viewManager.Show("Zeiterfassung");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🖼️ Fenster-Titel setzen (optional für Nutzerfreundlichkeit)
|
||||||
this.Title = $"ChronoFlow - Willkommen {_loggedInUser.Username} ({_loggedInUser.Role})";
|
this.Title = $"ChronoFlow - Willkommen {_loggedInUser.Username} ({_loggedInUser.Role})";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Öffnet die Mitarbeiter-Zeiterfassung (nur für Mitarbeiter-Sidebar-Button).
|
||||||
|
/// </summary>
|
||||||
private void Zeiterfassung_Click(object? sender, RoutedEventArgs e)
|
private void Zeiterfassung_Click(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
_viewManager.Show("Zeiterfassung");
|
_viewManager.Show("Zeiterfassung");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Öffnet das Formular zum Anlegen eines neuen Mitarbeiters.
|
||||||
|
/// </summary>
|
||||||
private void MitarbeiterHinzufuegen_Click(object? sender, RoutedEventArgs e)
|
private void MitarbeiterHinzufuegen_Click(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
_viewManager.Show("MitarbeiterHinzufuegen");
|
_viewManager.Show("MitarbeiterHinzufuegen");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Öffnet das Admin-Dashboard (Übersicht).
|
||||||
|
/// </summary>
|
||||||
private void AdminDashboard_Click(object? sender, RoutedEventArgs e)
|
private void AdminDashboard_Click(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
_viewManager.Show("AdminMain");
|
_viewManager.Show("AdminMain");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extern aufrufbar für "Zurück zum Dashboard"-Funktion (z. B. in anderen Views).
|
||||||
|
/// </summary>
|
||||||
public void ShowAdminDashboard()
|
public void ShowAdminDashboard()
|
||||||
{
|
{
|
||||||
_viewManager.Show("AdminMain");
|
_viewManager.Show("AdminMain");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Führt einen Logout durch – öffnet das Loginfenster neu und schließt sich selbst.
|
||||||
|
/// </summary>
|
||||||
private void Logout_Click(object? sender, RoutedEventArgs e)
|
private void Logout_Click(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
var loginWindow = new LoginWindow();
|
var loginWindow = new LoginWindow();
|
||||||
loginWindow.Show();
|
loginWindow.Show();
|
||||||
|
|
||||||
this.Close(); // Aktuelles Fenster schließen
|
this.Close(); // Schließt das aktuelle Fenster
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,6 +25,12 @@
|
|||||||
<!-- Projekttitel -->
|
<!-- Projekttitel -->
|
||||||
<TextBlock Text="{Binding Projekt}" FontWeight="Bold"/>
|
<TextBlock Text="{Binding Projekt}" FontWeight="Bold"/>
|
||||||
|
|
||||||
|
<!-- Projektleiter anzeigen -->
|
||||||
|
<TextBlock Text="{Binding Projektleiter, StringFormat='👤 Projektleiter: {0}'}"
|
||||||
|
FontSize="12"
|
||||||
|
Foreground="Gray"
|
||||||
|
FontStyle="Italic"/>
|
||||||
|
|
||||||
<!-- Admin-Kommentar -->
|
<!-- Admin-Kommentar -->
|
||||||
<TextBlock Text="📝 Kommentar (Admin):" FontSize="12" Foreground="LightGray"/>
|
<TextBlock Text="📝 Kommentar (Admin):" FontSize="12" Foreground="LightGray"/>
|
||||||
<TextBlock Text="{Binding Kommentar}" FontWeight="SemiBold"/>
|
<TextBlock Text="{Binding Kommentar}" FontWeight="SemiBold"/>
|
||||||
|
|||||||
@ -1,16 +1,34 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using ChronoFlow.View.Mitarbeiter;
|
|
||||||
using ChronoFlow.Persistence;
|
|
||||||
using ChronoFlow.Model;
|
using ChronoFlow.Model;
|
||||||
|
using ChronoFlow.Persistence;
|
||||||
|
|
||||||
namespace ChronoFlow.View.Mitarbeiter
|
namespace ChronoFlow.View.Mitarbeiter;
|
||||||
{
|
|
||||||
|
/// <summary>
|
||||||
|
/// View für Mitarbeiter zur Anzeige und Bearbeitung ihrer Aufgaben.
|
||||||
|
/// </summary>
|
||||||
public partial class EmployeeTasksView : UserControl
|
public partial class EmployeeTasksView : UserControl
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Öffentlicher parameterloser Konstruktor – notwendig für Avalonia-Runtime (AVLN0005).
|
||||||
|
/// Wird nur für Design-Time oder XAML-Lader verwendet.
|
||||||
|
/// </summary>
|
||||||
|
public EmployeeTasksView()
|
||||||
|
: this(new User { Username = "Demo", Role = "Mitarbeiter" }, new SqliteZeiterfassungsService())
|
||||||
|
{
|
||||||
|
// Nur für Design-Time oder XAML-Vorschau gedacht.
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Konstruktor – Setzt das DataContext auf das zugehörige ViewModel.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user">Der aktuell eingeloggte Benutzer</param>
|
||||||
|
/// <param name="repository">Das Datenzugriffsobjekt</param>
|
||||||
public EmployeeTasksView(User user, IZeiterfassungsRepository repository)
|
public EmployeeTasksView(User user, IZeiterfassungsRepository repository)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
DataContext = new EmployeeTasksViewModel(user, repository);
|
|
||||||
}
|
// Setzt das ViewModel als DataContext
|
||||||
|
DataContext = new EmployeeTasksViewModel(user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,4 +1,3 @@
|
|||||||
// TestKommentar
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -7,111 +6,86 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
|||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using ChronoFlow.Model;
|
using ChronoFlow.Model;
|
||||||
using ChronoFlow.Persistence;
|
using ChronoFlow.Persistence;
|
||||||
using ChronoFlow.View.Mitarbeiter;
|
using ChronoFlow.Controller;
|
||||||
|
|
||||||
|
namespace ChronoFlow.View.Mitarbeiter;
|
||||||
|
|
||||||
namespace ChronoFlow.View.Mitarbeiter
|
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ViewModel für die Mitarbeiter-Aufgabenansicht.
|
/// ViewModel für die Mitarbeiter-Aufgabenansicht.
|
||||||
/// Enthält alle Aufgaben für den eingeloggten Benutzer und erlaubt das Speichern von Änderungen.
|
/// Enthält alle Aufgaben für den eingeloggten Benutzer und erlaubt das Speichern von Änderungen.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class EmployeeTasksViewModel : ObservableObject
|
public partial class EmployeeTasksViewModel : ObservableObject
|
||||||
{
|
{
|
||||||
// 🧑💼 Der aktuell eingeloggte Benutzer (Mitarbeiter)
|
|
||||||
private readonly User _benutzer;
|
private readonly User _benutzer;
|
||||||
|
|
||||||
// 💾 Zugriff auf die Datenbank-Funktionen (SQL-Repository)
|
private readonly ZeiterfassungsController controller = new();
|
||||||
private readonly IZeiterfassungsRepository _repository;
|
|
||||||
|
|
||||||
// 📛 Einfacher Zugriff auf den Benutzernamen (spart Schreibarbeit)
|
|
||||||
private readonly string aktuellerBenutzername;
|
private readonly string aktuellerBenutzername;
|
||||||
|
|
||||||
// 📋 Alle Zeiteinträge, die dem Benutzer angezeigt werden
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private ObservableCollection<Zeiteintrag> eintraege = new();
|
private ObservableCollection<Zeiteintrag> eintraege = new();
|
||||||
|
|
||||||
// 💬 Statusmeldung unten im Fenster (z. B. bei Erfolg)
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private string? statusText;
|
private string? statusText;
|
||||||
|
|
||||||
// ✅ Hilfs-Property, die sagt: "Gibt es keine Einträge?"
|
|
||||||
public bool HatKeineEintraege => Eintraege.Count == 0;
|
public bool HatKeineEintraege => Eintraege.Count == 0;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Diese Methode wird automatisch aufgerufen, wenn sich die Einträge ändern.
|
|
||||||
/// Damit wird die UI über die Änderung von 'HatKeineEintraege' informiert.
|
|
||||||
/// </summary>
|
|
||||||
partial void OnEintraegeChanged(ObservableCollection<Zeiteintrag>? oldValue, ObservableCollection<Zeiteintrag> newValue)
|
partial void OnEintraegeChanged(ObservableCollection<Zeiteintrag>? oldValue, ObservableCollection<Zeiteintrag> newValue)
|
||||||
{
|
{
|
||||||
OnPropertyChanged(nameof(HatKeineEintraege));
|
OnPropertyChanged(nameof(HatKeineEintraege));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public EmployeeTasksViewModel(User benutzer)
|
||||||
/// Konstruktor: Bekommt den eingeloggten Benutzer und das Repository.
|
|
||||||
/// Lädt automatisch alle offenen Einträge für diesen Benutzer.
|
|
||||||
/// </summary>
|
|
||||||
public EmployeeTasksViewModel(User benutzer, IZeiterfassungsRepository repository)
|
|
||||||
{
|
{
|
||||||
_benutzer = benutzer;
|
_benutzer = benutzer;
|
||||||
_repository = repository;
|
|
||||||
aktuellerBenutzername = benutzer.Username;
|
aktuellerBenutzername = benutzer.Username;
|
||||||
|
|
||||||
Console.WriteLine($"[DEBUG] ViewModel erstellt für Benutzer: {aktuellerBenutzername}");
|
controller = new ZeiterfassungsController(); // falls du das oben nicht hast
|
||||||
|
|
||||||
// Daten automatisch beim Öffnen laden
|
|
||||||
_ = LadeEintraegeAsync();
|
_ = LadeEintraegeAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Lädt alle offenen Einträge für den aktuell eingeloggten Mitarbeiter.
|
|
||||||
/// Sortiert nach Fälligkeitsdatum (Endzeit).
|
|
||||||
/// Erkennt auch, ob seit dem letzten Login Änderungen gemacht wurden.
|
|
||||||
/// </summary>
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
public async Task LadeEintraegeAsync()
|
public async Task LadeEintraegeAsync()
|
||||||
{
|
{
|
||||||
var eintraegeAusDb = await _repository.GetEintraegeFuerMitarbeiterAsync(aktuellerBenutzername);
|
// 🔄 Einträge synchron laden, aber async verpacken
|
||||||
|
var alleEintraege = await Task.Run(() => controller.LadeAlleEintraege());
|
||||||
|
|
||||||
var offene = eintraegeAusDb
|
var relevanteEintraege = alleEintraege
|
||||||
.Where(e => !e.Erledigt)
|
.Where(e => !e.Erledigt &&
|
||||||
|
(e.Mitarbeiter == aktuellerBenutzername || e.Projektleiter == aktuellerBenutzername))
|
||||||
.OrderBy(e => e.Endzeit)
|
.OrderBy(e => e.Endzeit)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
// 🔔 Markiere geänderte Einträge seit dem letzten Login
|
foreach (var eintrag in relevanteEintraege)
|
||||||
foreach (var eintrag in offene)
|
|
||||||
{
|
{
|
||||||
eintrag.WurdeSeitLoginBearbeitet = eintrag.LetzteBearbeitung > _benutzer.VorletzterLogin;
|
eintrag.WurdeSeitLoginBearbeitet = eintrag.LetzteBearbeitung > _benutzer.VorletzterLogin;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔔 Zeige Hinweis, wenn neue Änderungen vorhanden sind
|
if (relevanteEintraege.Any(e => e.WurdeSeitLoginBearbeitet))
|
||||||
if (offene.Any(e => e.WurdeSeitLoginBearbeitet))
|
|
||||||
StatusText = "📢 Es wurden Aufgaben seit Ihrem letzten Login geändert.";
|
StatusText = "📢 Es wurden Aufgaben seit Ihrem letzten Login geändert.";
|
||||||
|
|
||||||
// 🔄 Aktualisiere ObservableCollection für UI
|
Eintraege = new ObservableCollection<Zeiteintrag>(relevanteEintraege);
|
||||||
Eintraege = new ObservableCollection<Zeiteintrag>(offene);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Speichert alle Änderungen an den sichtbaren Einträgen
|
|
||||||
/// (Status 'erledigt' + Mitarbeiter-Kommentar).
|
|
||||||
/// Danach wird die Liste automatisch neu geladen.
|
|
||||||
/// </summary>
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
public async Task SpeichereEintraegeAsync()
|
public async Task SpeichereEintraegeAsync()
|
||||||
{
|
{
|
||||||
|
// Achtung: Hier verwendest du noch das Repository, obwohl du oben mit dem Controller arbeitest.
|
||||||
|
// Du kannst entweder:
|
||||||
|
// A) Das Repository auch im ViewModel übergeben
|
||||||
|
// B) Die Methode UpdateStatusUndKommentarAsync auch in den Controller packen
|
||||||
|
|
||||||
foreach (var eintrag in Eintraege)
|
foreach (var eintrag in Eintraege)
|
||||||
{
|
{
|
||||||
await _repository.UpdateStatusUndKommentarAsync(
|
await Task.Run(() =>
|
||||||
eintrag.Id,
|
{
|
||||||
eintrag.Erledigt,
|
var sqlite = new SqliteZeiterfassungsService();
|
||||||
eintrag.MitarbeiterKommentar
|
sqlite.UpdateStatusUndKommentarAsync(eintrag.Id, eintrag.Erledigt, eintrag.MitarbeiterKommentar).Wait();
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
StatusText = "✅ Änderungen gespeichert.";
|
StatusText = "✅ Änderungen gespeichert.";
|
||||||
|
|
||||||
// 🔄 Nach dem Speichern direkt neu laden
|
|
||||||
await LadeEintraegeAsync();
|
await LadeEintraegeAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
@ -4,8 +4,11 @@ using Avalonia.Controls;
|
|||||||
using ChronoFlow.Model;
|
using ChronoFlow.Model;
|
||||||
using ChronoFlow.Persistence;
|
using ChronoFlow.Persistence;
|
||||||
|
|
||||||
namespace ChronoFlow.View.Mitarbeiter
|
namespace ChronoFlow.View.Mitarbeiter;
|
||||||
{
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hauptansicht für eingeloggte Mitarbeiter – zeigt zuletzt bearbeitete Projekte und Benachrichtigungen.
|
||||||
|
/// </summary>
|
||||||
public partial class MitarbeiterMainView : UserControl
|
public partial class MitarbeiterMainView : UserControl
|
||||||
{
|
{
|
||||||
private readonly ViewManager _viewManager;
|
private readonly ViewManager _viewManager;
|
||||||
@ -13,25 +16,46 @@ namespace ChronoFlow.View.Mitarbeiter
|
|||||||
private readonly ObservableCollection<Zeiteintrag> _letzteProjekte = new();
|
private readonly ObservableCollection<Zeiteintrag> _letzteProjekte = new();
|
||||||
private readonly ObservableCollection<string> _notifications = new();
|
private readonly ObservableCollection<string> _notifications = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Öffentlicher, parameterloser Konstruktor – erforderlich für Avalonia Runtime Loader (z. B. Designer).
|
||||||
|
/// Startet mit Dummy-Werten.
|
||||||
|
/// </summary>
|
||||||
|
public MitarbeiterMainView()
|
||||||
|
: this(new ViewManager(new ContentControl()), new User { Username = "Demo", Role = "Mitarbeiter" })
|
||||||
|
{
|
||||||
|
Console.WriteLine("⚠️ MitarbeiterMainView im Demo-Modus geladen.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Konstruktor mit ViewManager und eingeloggtem Benutzer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="viewManager">Instanz zur Navigation</param>
|
||||||
|
/// <param name="user">Eingeloggter Benutzer (Mitarbeiter)</param>
|
||||||
public MitarbeiterMainView(ViewManager viewManager, User user)
|
public MitarbeiterMainView(ViewManager viewManager, User user)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
_viewManager = viewManager;
|
_viewManager = viewManager;
|
||||||
_currentUser = user;
|
_currentUser = user;
|
||||||
|
|
||||||
|
// Datenquellen binden
|
||||||
LetzteProjekteListe.ItemsSource = _letzteProjekte;
|
LetzteProjekteListe.ItemsSource = _letzteProjekte;
|
||||||
NotificationList.ItemsSource = _notifications;
|
NotificationList.ItemsSource = _notifications;
|
||||||
|
|
||||||
|
// Daten laden
|
||||||
LadeLetzteProjekte();
|
LadeLetzteProjekte();
|
||||||
LadeBenachrichtigungen();
|
LadeBenachrichtigungen();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lädt die letzten 3 Projekte, die vom aktuellen Benutzer bearbeitet wurden.
|
||||||
|
/// </summary>
|
||||||
private void LadeLetzteProjekte()
|
private void LadeLetzteProjekte()
|
||||||
{
|
{
|
||||||
var dbService = new SqliteZeiterfassungsService();
|
var dbService = new SqliteZeiterfassungsService();
|
||||||
var projekte = dbService.LadeLetzteProjekte(3);
|
var projekte = dbService.LadeLetzteProjekte(3);
|
||||||
_letzteProjekte.Clear();
|
|
||||||
|
|
||||||
|
_letzteProjekte.Clear();
|
||||||
foreach (var p in projekte)
|
foreach (var p in projekte)
|
||||||
{
|
{
|
||||||
if (p.Mitarbeiter == _currentUser.Username)
|
if (p.Mitarbeiter == _currentUser.Username)
|
||||||
@ -39,12 +63,15 @@ namespace ChronoFlow.View.Mitarbeiter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lädt Benachrichtigungen für den Benutzer (derzeit Platzhalter).
|
||||||
|
/// </summary>
|
||||||
private void LadeBenachrichtigungen()
|
private void LadeBenachrichtigungen()
|
||||||
{
|
{
|
||||||
// 🛈 Platzhalter → hier später echte DB-Infos laden!
|
|
||||||
_notifications.Clear();
|
_notifications.Clear();
|
||||||
|
|
||||||
|
// 📌 Hier später echte Änderungsinfos anzeigen
|
||||||
_notifications.Add("Projekt Alpha wurde aktualisiert.");
|
_notifications.Add("Projekt Alpha wurde aktualisiert.");
|
||||||
_notifications.Add("Deadline für Projekt Beta wurde verschoben.");
|
_notifications.Add("Deadline für Projekt Beta wurde verschoben.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
75
ChronoFlow.View/Mitarbeiter/ProjektleiterTasksViewModel.cs
Normal file
75
ChronoFlow.View/Mitarbeiter/ProjektleiterTasksViewModel.cs
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using ChronoFlow.Model;
|
||||||
|
using ChronoFlow.Persistence;
|
||||||
|
using ChronoFlow.Controller;
|
||||||
|
|
||||||
|
namespace ChronoFlow.View.Projektleiter;
|
||||||
|
|
||||||
|
public partial class ProjektleiterTasksViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
private readonly User _benutzer;
|
||||||
|
private readonly ZeiterfassungsController controller = new();
|
||||||
|
private readonly string aktuellerBenutzername;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private ObservableCollection<Zeiteintrag> eintraege = new();
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string? statusText;
|
||||||
|
|
||||||
|
public bool HatKeineEintraege => Eintraege.Count == 0;
|
||||||
|
|
||||||
|
partial void OnEintraegeChanged(ObservableCollection<Zeiteintrag>? oldValue, ObservableCollection<Zeiteintrag> newValue)
|
||||||
|
{
|
||||||
|
OnPropertyChanged(nameof(HatKeineEintraege));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProjektleiterTasksViewModel(User benutzer)
|
||||||
|
{
|
||||||
|
_benutzer = benutzer;
|
||||||
|
aktuellerBenutzername = benutzer.Username;
|
||||||
|
_ = LadeEintraegeAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public async Task LadeEintraegeAsync()
|
||||||
|
{
|
||||||
|
var alleEintraege = await Task.Run(() => controller.LadeAlleEintraege());
|
||||||
|
|
||||||
|
var relevanteEintraege = alleEintraege
|
||||||
|
.Where(e => !e.Erledigt && e.Projektleiter == aktuellerBenutzername)
|
||||||
|
.OrderBy(e => e.Endzeit)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var eintrag in relevanteEintraege)
|
||||||
|
{
|
||||||
|
eintrag.WurdeSeitLoginBearbeitet = eintrag.LetzteBearbeitung > _benutzer.VorletzterLogin;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relevanteEintraege.Any(e => e.WurdeSeitLoginBearbeitet))
|
||||||
|
StatusText = "\ud83d\udce2 Es wurden Aufgaben seit Ihrem letzten Login geändert.";
|
||||||
|
|
||||||
|
Eintraege = new ObservableCollection<Zeiteintrag>(relevanteEintraege);
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public async Task SpeichereEintraegeAsync()
|
||||||
|
{
|
||||||
|
foreach (var eintrag in Eintraege)
|
||||||
|
{
|
||||||
|
await Task.Run(() =>
|
||||||
|
{
|
||||||
|
var sqlite = new SqliteZeiterfassungsService();
|
||||||
|
sqlite.UpdateStatusUndKommentarAsync(eintrag.Id, eintrag.Erledigt, eintrag.MitarbeiterKommentar).Wait();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
StatusText = "Änderungen gespeichert.";
|
||||||
|
await LadeEintraegeAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,24 +2,54 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
x:Class="ChronoFlow.View.MitarbeiterHinzufuegenView">
|
x:Class="ChronoFlow.View.MitarbeiterHinzufuegenView">
|
||||||
|
|
||||||
|
<!-- Scrollbarer Bereich für kleinere Fenstergrößen -->
|
||||||
<ScrollViewer>
|
<ScrollViewer>
|
||||||
<StackPanel Margin="20" Spacing="15">
|
<StackPanel Margin="20" Spacing="15">
|
||||||
<TextBlock Text="➕ Mitarbeiter hinzufügen" FontWeight="Bold" FontSize="20" HorizontalAlignment="Center"/>
|
<!-- Überschrift -->
|
||||||
|
<TextBlock Text="➕ Mitarbeiter hinzufügen"
|
||||||
|
FontWeight="Bold"
|
||||||
|
FontSize="20"
|
||||||
|
HorizontalAlignment="Center" />
|
||||||
|
|
||||||
|
<!-- Eingabefeld: Benutzername -->
|
||||||
<TextBox x:Name="UsernameBox" Watermark="Benutzername" />
|
<TextBox x:Name="UsernameBox" Watermark="Benutzername" />
|
||||||
|
|
||||||
|
<!-- Eingabefeld: Passwort -->
|
||||||
<TextBox x:Name="PasswordBox" Watermark="Passwort" />
|
<TextBox x:Name="PasswordBox" Watermark="Passwort" />
|
||||||
|
|
||||||
|
<!-- Auswahl der Benutzerrolle -->
|
||||||
<ComboBox x:Name="RoleBox" PlaceholderText="Rolle auswählen">
|
<ComboBox x:Name="RoleBox" PlaceholderText="Rolle auswählen">
|
||||||
<ComboBoxItem Content="Mitarbeiter"/>
|
<ComboBoxItem Content="Mitarbeiter"/>
|
||||||
<ComboBoxItem Content="Admin"/>
|
<ComboBoxItem Content="Admin"/>
|
||||||
</ComboBox>
|
</ComboBox>
|
||||||
|
|
||||||
|
<!-- Eingabefeld: Mitarbeiternummer -->
|
||||||
<TextBox x:Name="MitarbeiternummerBox" Watermark="Mitarbeiternummer" />
|
<TextBox x:Name="MitarbeiternummerBox" Watermark="Mitarbeiternummer" />
|
||||||
|
|
||||||
|
<!-- Eingabefeld: Abteilung -->
|
||||||
<TextBox x:Name="AbteilungBox" Watermark="Abteilung" />
|
<TextBox x:Name="AbteilungBox" Watermark="Abteilung" />
|
||||||
|
|
||||||
<Button Content="💾 Speichern" Click="SpeichernButton_Click" HorizontalAlignment="Center"/>
|
<!-- Speicher-Button -->
|
||||||
<Button Content="⬅ Zurück zum Dashboard" Click="ZurueckZumDashboard_Click" HorizontalAlignment="Center" Margin="0,10,0,0"/>
|
<Button Content="💾 Speichern"
|
||||||
<TextBlock x:Name="FeedbackText" Foreground="Green" IsVisible="False" TextAlignment="Center"/>
|
Click="SpeichernButton_Click"
|
||||||
|
HorizontalAlignment="Center" />
|
||||||
|
|
||||||
|
<!-- Demo-Mitarbeiter erstellen (für schnelles Testen durch Dozent) -->
|
||||||
|
<Button Content="🧪 Demo-Mitarbeiter erstellen"
|
||||||
|
Click="DemoBenutzerErstellen_Click"
|
||||||
|
HorizontalAlignment="Center" />
|
||||||
|
|
||||||
|
<!-- Zurück-Button -->
|
||||||
|
<Button Content="⬅ Zurück zum Dashboard"
|
||||||
|
Click="ZurueckZumDashboard_Click"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Margin="0,10,0,0" />
|
||||||
|
|
||||||
|
<!-- Text für Statusmeldungen (Erfolg oder Fehler) -->
|
||||||
|
<TextBlock x:Name="FeedbackText"
|
||||||
|
Foreground="Green"
|
||||||
|
IsVisible="False"
|
||||||
|
TextAlignment="Center" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
@ -8,6 +9,9 @@ using ChronoFlow.Security;
|
|||||||
|
|
||||||
namespace ChronoFlow.View
|
namespace ChronoFlow.View
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// UI-Logik zum Hinzufügen neuer Benutzer durch einen Admin.
|
||||||
|
/// </summary>
|
||||||
public partial class MitarbeiterHinzufuegenView : UserControl
|
public partial class MitarbeiterHinzufuegenView : UserControl
|
||||||
{
|
{
|
||||||
public MitarbeiterHinzufuegenView()
|
public MitarbeiterHinzufuegenView()
|
||||||
@ -15,6 +19,9 @@ namespace ChronoFlow.View
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Speichert den eingegebenen Benutzer, wenn alle Pflichtfelder ausgefüllt und der Benutzername noch nicht vergeben ist.
|
||||||
|
/// </summary>
|
||||||
private void SpeichernButton_Click(object? sender, RoutedEventArgs e)
|
private void SpeichernButton_Click(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -27,6 +34,7 @@ namespace ChronoFlow.View
|
|||||||
string mitarbeiternummer = MitarbeiternummerBox.Text?.Trim() ?? "";
|
string mitarbeiternummer = MitarbeiternummerBox.Text?.Trim() ?? "";
|
||||||
string abteilung = AbteilungBox.Text?.Trim() ?? "";
|
string abteilung = AbteilungBox.Text?.Trim() ?? "";
|
||||||
|
|
||||||
|
// Validierung: Pflichtfelder
|
||||||
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password) || string.IsNullOrWhiteSpace(rolle))
|
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password) || string.IsNullOrWhiteSpace(rolle))
|
||||||
{
|
{
|
||||||
FeedbackText.Text = "⚠ Bitte alle Pflichtfelder ausfüllen!";
|
FeedbackText.Text = "⚠ Bitte alle Pflichtfelder ausfüllen!";
|
||||||
@ -35,6 +43,16 @@ namespace ChronoFlow.View
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verhindern von doppelten Benutzernamen (Case-Insensitive)
|
||||||
|
if (service.LadeAlleBenutzer().Any(u => u.Username.Equals(username, StringComparison.OrdinalIgnoreCase)))
|
||||||
|
{
|
||||||
|
FeedbackText.Text = "❌ Dieser Benutzername ist bereits vergeben.";
|
||||||
|
FeedbackText.Foreground = Brushes.Red;
|
||||||
|
FeedbackText.IsVisible = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neuen Benutzer erstellen
|
||||||
var neuerBenutzer = new User
|
var neuerBenutzer = new User
|
||||||
{
|
{
|
||||||
Username = username,
|
Username = username,
|
||||||
@ -45,6 +63,7 @@ namespace ChronoFlow.View
|
|||||||
MussPasswortAendern = true
|
MussPasswortAendern = true
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Speichern in Datenbank
|
||||||
service.ErstelleNeuenBenutzer(neuerBenutzer);
|
service.ErstelleNeuenBenutzer(neuerBenutzer);
|
||||||
|
|
||||||
FeedbackText.Text = "✅ Mitarbeiter erfolgreich gespeichert.";
|
FeedbackText.Text = "✅ Mitarbeiter erfolgreich gespeichert.";
|
||||||
@ -58,12 +77,13 @@ namespace ChronoFlow.View
|
|||||||
FeedbackText.Text = $"❌ Fehler: {ex.Message}";
|
FeedbackText.Text = $"❌ Fehler: {ex.Message}";
|
||||||
FeedbackText.Foreground = Brushes.Red;
|
FeedbackText.Foreground = Brushes.Red;
|
||||||
FeedbackText.IsVisible = true;
|
FeedbackText.IsVisible = true;
|
||||||
|
Console.WriteLine("❌ Ausnahme beim Speichern:\n" + ex);
|
||||||
Console.WriteLine("❌ Ausnahme beim Speichern:");
|
|
||||||
Console.WriteLine(ex.ToString());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Setzt alle Eingabefelder zurück.
|
||||||
|
/// </summary>
|
||||||
private void ClearFields()
|
private void ClearFields()
|
||||||
{
|
{
|
||||||
UsernameBox.Text = "";
|
UsernameBox.Text = "";
|
||||||
@ -73,17 +93,50 @@ namespace ChronoFlow.View
|
|||||||
RoleBox.SelectedIndex = -1;
|
RoleBox.SelectedIndex = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wechselt zurück zum Admin-Hauptfenster.
|
||||||
|
/// </summary>
|
||||||
private void ZurueckZumDashboard_Click(object? sender, RoutedEventArgs e)
|
private void ZurueckZumDashboard_Click(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
var mainWindow = this.VisualRoot as MainWindow;
|
if (this.VisualRoot is MainWindow mainWindow)
|
||||||
if (mainWindow != null)
|
|
||||||
{
|
|
||||||
mainWindow.ShowAdminDashboard();
|
mainWindow.ShowAdminDashboard();
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
Console.WriteLine("⚠️ MainWindow nicht gefunden!");
|
Console.WriteLine("⚠️ MainWindow nicht gefunden!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Erstellt einen zufälligen Test-Mitarbeiter mit dem Passwort "newpassword" (gehasht).
|
||||||
|
/// </summary>
|
||||||
|
private void DemoBenutzerErstellen_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var namen = new[] { "Max Mustermann", "Lena Schmidt", "Tobias Becker", "Julia Klein", "Nico Weber" };
|
||||||
|
var zufallsname = namen[new Random().Next(namen.Length)];
|
||||||
|
|
||||||
|
var service = new SqliteZeiterfassungsService();
|
||||||
|
|
||||||
|
// Prüfen, ob Name bereits vorhanden ist
|
||||||
|
if (service.LadeAlleBenutzer().Any(u => u.Username.Equals(zufallsname, StringComparison.OrdinalIgnoreCase)))
|
||||||
|
{
|
||||||
|
FeedbackText.Text = "⚠ Demo-Mitarbeiter existiert bereits.";
|
||||||
|
FeedbackText.Foreground = Brushes.OrangeRed;
|
||||||
|
FeedbackText.IsVisible = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Demo-User erstellen
|
||||||
|
var demoUser = new User
|
||||||
|
{
|
||||||
|
Username = zufallsname,
|
||||||
|
Password = PasswordHasher.HashPassword("newpassword"),
|
||||||
|
Role = "Mitarbeiter",
|
||||||
|
MussPasswortAendern = true
|
||||||
|
};
|
||||||
|
|
||||||
|
service.ErstelleNeuenBenutzer(demoUser);
|
||||||
|
|
||||||
|
FeedbackText.Text = $"✅ Demo-Mitarbeiter '{zufallsname}' erstellt (Passwort: newpassword)";
|
||||||
|
FeedbackText.Foreground = Brushes.Green;
|
||||||
|
FeedbackText.IsVisible = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,18 +4,41 @@ using ChronoFlow.Model;
|
|||||||
|
|
||||||
namespace ChronoFlow.View.Security;
|
namespace ChronoFlow.View.Security;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dialog zum Ändern des Passworts eines Benutzers.
|
||||||
|
/// </summary>
|
||||||
public partial class PasswortAendernDialog : Window
|
public partial class PasswortAendernDialog : Window
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Das neue Passwort, das im Dialog eingegeben wurde.
|
||||||
|
/// </summary>
|
||||||
public string NeuesPasswort { get; private set; } = "";
|
public string NeuesPasswort { get; private set; } = "";
|
||||||
|
|
||||||
private readonly User _user;
|
private readonly User _user;
|
||||||
|
|
||||||
// Konstruktor mit Benutzerobjekt
|
/// <summary>
|
||||||
|
/// Parameterloser Konstruktor für Avalonia (z. B. für Preview, AVLN:0005-Fix).
|
||||||
|
/// ⚠ Wird nur für Entwicklungszwecke oder Design-Time genutzt.
|
||||||
|
/// </summary>
|
||||||
|
public PasswortAendernDialog() : this(new User { Username = "DemoUser", Role = "Mitarbeiter" })
|
||||||
|
{
|
||||||
|
System.Console.WriteLine("⚠ Parameterloser Konstruktor für PasswortAendernDialog verwendet.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Konstruktor mit Benutzerobjekt (wird im echten Ablauf verwendet).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user">Der aktuell eingeloggte Benutzer.</param>
|
||||||
public PasswortAendernDialog(User user)
|
public PasswortAendernDialog(User user)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
_user = user;
|
_user = user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wird aufgerufen, wenn der Benutzer auf "Speichern" klickt.
|
||||||
|
/// Prüft Eingaben und schließt den Dialog mit dem neuen Passwort.
|
||||||
|
/// </summary>
|
||||||
private void SpeichernButton_Click(object? sender, RoutedEventArgs e)
|
private void SpeichernButton_Click(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
var passwort = NeuesPasswortBox.Text?.Trim();
|
var passwort = NeuesPasswortBox.Text?.Trim();
|
||||||
@ -39,6 +62,9 @@ public partial class PasswortAendernDialog : Window
|
|||||||
Close(NeuesPasswort);
|
Close(NeuesPasswort);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bricht den Vorgang ab und schließt den Dialog ohne Ergebnis.
|
||||||
|
/// </summary>
|
||||||
private void AbbrechenButton_Click(object? sender, RoutedEventArgs e)
|
private void AbbrechenButton_Click(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
Close(null);
|
Close(null);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user