diff --git a/ChronoFlow.Controller/ZeiterfassungsController.cs b/ChronoFlow.Controller/ZeiterfassungsController.cs index c273543..d728dd5 100644 --- a/ChronoFlow.Controller/ZeiterfassungsController.cs +++ b/ChronoFlow.Controller/ZeiterfassungsController.cs @@ -27,9 +27,13 @@ namespace ChronoFlow.Controller } // Diese Methode lädt alle bisherigen Zeiteinträge aus der Datenbank - public List LadeAlleEintraege() + public async Task> LadeAlleEintraegeAsync() { // Holt die Liste der Einträge von der Datenbank und gibt sie zurück + return await _sqliteService.LadeAlleEintraegeAsync(); + } + public List LadeAlleEintraege() + { return _sqliteService.LadeAlleZeiteintraege(); } } diff --git a/ChronoFlow.Model/Zeiteintrag.cs b/ChronoFlow.Model/Zeiteintrag.cs index 410080d..1b8fec2 100644 --- a/ChronoFlow.Model/Zeiteintrag.cs +++ b/ChronoFlow.Model/Zeiteintrag.cs @@ -1,28 +1,95 @@ namespace ChronoFlow.Model { + /// + /// Datenmodell für einen Zeiteintrag / ein Projekt. + /// Wird z.B. zur Speicherung in SQLite verwendet. + /// public class Zeiteintrag { - public int Id { get; set; } // <<< NEU + /// + /// Eindeutige ID des Eintrags in der Datenbank. + /// + public int Id { get; set; } + /// + /// Name des zugewiesenen Mitarbeiters. + /// public string Mitarbeiter { get; set; } + + /// + /// Name des Projektleiters (neu hinzugefügt). + /// + public string Projektleiter { get; set; } = ""; + + /// + /// Zeitpunkt, wann das Projekt beginnt. + /// public DateTime Startzeit { get; set; } + + /// + /// Zeitpunkt, wann das Projekt endet. + /// public DateTime Endzeit { get; set; } + + /// + /// Name oder Titel des Projekts. + /// public string Projekt { get; set; } + + /// + /// Beschreibung oder Kommentar durch den Admin. + /// public string Kommentar { get; set; } + + /// + /// Gibt an, ob das Projekt als erledigt markiert wurde. + /// public bool Erledigt { get; set; } + + /// + /// Kommentar vom Mitarbeiter (z.B. Rückmeldung zum Projekt). + /// public string MitarbeiterKommentar { get; set; } + /// + /// Zeitpunkt der letzten Änderung an diesem Eintrag. + /// + public DateTime LetzteBearbeitung { get; set; } + + /// + /// Kennzeichnet, ob es sich um einen neu erzeugten Eintrag handelt (z.B. seit dem letzten Login). + /// + public bool IstNeu { get; set; } + + /// + /// True, wenn der Eintrag seit dem letzten Login verändert wurde. + /// + public bool WurdeSeitLoginBearbeitet { get; set; } + + /// + /// Konstruktor – Initialisiert alle Felder mit Standardwerten. + /// public Zeiteintrag() { Id = 0; Mitarbeiter = ""; + Projektleiter = ""; // <<< NEU Startzeit = DateTime.Now; Endzeit = DateTime.Now; Projekt = ""; Kommentar = ""; Erledigt = false; MitarbeiterKommentar = ""; + Projektleiter = ""; + LetzteBearbeitung = DateTime.Now; + IstNeu = false; + WurdeSeitLoginBearbeitet = false; } + + /// + /// 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. + /// public string PrioritaetsFarbe { get @@ -37,8 +104,5 @@ namespace ChronoFlow.Model return "Green"; } } - public DateTime LetzteBearbeitung { get; set; } - public bool IstNeu { get; set; } // wird im ViewModel gesetzt - public bool WurdeSeitLoginBearbeitet { get; set; } } -} \ No newline at end of file +} diff --git a/ChronoFlow.Persistence/ITimeEntryRepository.cs b/ChronoFlow.Persistence/ITimeEntryRepository.cs index e02c5d4..5fa042e 100644 --- a/ChronoFlow.Persistence/ITimeEntryRepository.cs +++ b/ChronoFlow.Persistence/ITimeEntryRepository.cs @@ -5,14 +5,35 @@ using ChronoFlow.Model; namespace ChronoFlow.Persistence { + /// + /// Repository-Interface für den Zugriff auf Zeiteinträge in der Datenbank. + /// Wird von der SqliteZeiterfassungsService-Klasse implementiert. + /// public interface ITimeEntryRepository { + /// + /// Gibt alle Zeiteinträge für einen bestimmten Mitarbeiter zurück. + /// Task> GetEntriesForUserAsync(string username); - Task UpdateEntryStatusAndCommentAsync(int id, bool isCompleted, string? comment); - // (Optional) Weitere Methoden für Admin-Funktionen: - Task> GetAllEntriesAsync(); // Admin - Task AddEntryAsync(Zeiteintrag entry); // Admin - Task DeleteEntryAsync(int id); // Admin + /// + /// Gibt alle Zeiteinträge aus der Datenbank zurück (Admin-Ansicht). + /// + Task> LadeAlleEintraegeAsync(); + + /// + /// Fügt einen neuen Zeiteintrag in die Datenbank ein. + /// + Task AddEntryAsync(Zeiteintrag entry); + + /// + /// Löscht einen Zeiteintrag anhand seiner ID. + /// + Task DeleteEntryAsync(int id); + + /// + /// Aktualisiert den Status (erledigt) und den Kommentar eines Eintrags. + /// + Task UpdateEntryStatusAndCommentAsync(int id, bool isCompleted, string? comment); } } \ No newline at end of file diff --git a/ChronoFlow.Persistence/SqliteZeiterfassungsService.cs b/ChronoFlow.Persistence/SqliteZeiterfassungsService.cs index 7eaac05..904259c 100644 --- a/ChronoFlow.Persistence/SqliteZeiterfassungsService.cs +++ b/ChronoFlow.Persistence/SqliteZeiterfassungsService.cs @@ -7,26 +7,39 @@ using ChronoFlow.Security; namespace ChronoFlow.Persistence { + // Schnittstelle für die Zeiterfassungsfunktionen public interface IZeiterfassungsRepository { Task> GetEintraegeFuerMitarbeiterAsync(string mitarbeiterName); Task UpdateStatusUndKommentarAsync(int id, bool erledigt, string mitarbeiterKommentar); } + + /// + /// Implementierung des SQLite-Datenbankzugriffs für Benutzer und Zeiteinträge. + /// public class SqliteZeiterfassungsService : IZeiterfassungsRepository { + // Pfad zur SQLite-Datenbankdatei private readonly string _dbPath = "chrono_data.sb"; + /// + /// Konstruktor: Erstellt Datenbank falls nicht vorhanden und stellt die Struktur sicher. + /// public SqliteZeiterfassungsService() { if (!File.Exists(_dbPath)) ErstelleDatenbank(); - // IMMER prüfen, auch nach neuem Erstellen + // Auch bei bestehender Datei sicherstellen, dass alle Spalten vorhanden sind PrüfeUndErweitereDatenbank(); - - _ = StelleDatenbankstrukturSicherAsync(); // Async-Aufruf ignorieren + + // Async-Aufruf wird absichtlich nicht awaitet + _ = StelleDatenbankstrukturSicherAsync(); } + /// + /// Erstellt die Basis-Datenbankstruktur, falls Datei noch nicht existiert. + /// private void ErstelleDatenbank() { Console.WriteLine("🛠️ ErstelleDatenbank wurde aufgerufen!"); @@ -61,9 +74,11 @@ namespace ChronoFlow.Persistence );"; cmd2.ExecuteNonQuery(); } - + + public void ErstelleNeuenBenutzer(User benutzer) { + // Erstellt eine neue Benutzerzeile in der Datenbank using var connection = new SqliteConnection($"Data Source={_dbPath}"); connection.Open(); @@ -81,26 +96,28 @@ namespace ChronoFlow.Persistence cmd.Parameters.AddWithValue("$MussPasswortAendern", benutzer.MussPasswortAendern ? 1 : 0); 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() { + // Stellt sicher, dass die Datenbank alle benötigten Spalten enthält using var connection = new SqliteConnection($"Data Source={_dbPath}"); connection.Open(); AddColumnIfMissing(connection, "Benutzer", "Mitarbeitennummer", "TEXT"); AddColumnIfMissing(connection, "Benutzer", "Abteilung", "TEXT"); AddColumnIfMissing(connection, "Benutzer", "MussPasswortAendern", "INTEGER DEFAULT 1"); - AddColumnIfMissing(connection, "Benutzer", "LetzterLogin", "TEXT"); - AddColumnIfMissing(connection, "Benutzer", "VorletzterLogin", "TEXT"); + AddColumnIfMissing(connection, "Benutzer", "LetzterLogin", "TEXT"); + AddColumnIfMissing(connection, "Benutzer", "VorletzterLogin", "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(); checkCmd.CommandText = $"PRAGMA table_info({tableName});"; @@ -128,6 +145,7 @@ namespace ChronoFlow.Persistence public void ErstelleStandardAdmin() { + // Erstellt einen Admin-Benutzer, wenn dieser noch nicht existiert using var connection = new SqliteConnection($"Data Source={_dbPath}"); connection.Open(); @@ -140,9 +158,9 @@ namespace ChronoFlow.Persistence var hashedPassword = PasswordHasher.HashPassword("admin"); var insertCmd = connection.CreateCommand(); insertCmd.CommandText = @" - INSERT INTO Benutzer (Username, Password, Role, Mitarbeitennummer, Abteilung, MussPasswortAendern) - VALUES ('admin', $Password, 'Admin', '0001', 'IT', 1); - "; + INSERT INTO Benutzer (Username, Password, Role, Mitarbeitennummer, Abteilung, MussPasswortAendern) + VALUES ('admin', $Password, 'Admin', '0001', 'IT', 1); + "; insertCmd.Parameters.AddWithValue("$Password", hashedPassword); insertCmd.ExecuteNonQuery(); @@ -158,47 +176,51 @@ namespace ChronoFlow.Persistence { var benutzerListe = new List(); - using var connection = new SqliteConnection($"Data Source={_dbPath}"); - connection.Open(); - - var cmd = connection.CreateCommand(); - cmd.CommandText = @" - SELECT Id, Username, Password, Role, Mitarbeitennummer, Abteilung, MussPasswortAendern, LetzterLogin, VorletzterLogin - FROM Benutzer; - "; - - using var reader = cmd.ExecuteReader(); - while (reader.Read()) + try { - // Absicherungen für mögliche NULL- oder ungültige Werte: - var letzterLogin = DateTime.MinValue; - var vorletzterLogin = DateTime.MinValue; + using var connection = new SqliteConnection($"Data Source={_dbPath}"); + connection.Open(); - if (!reader.IsDBNull(7)) - DateTime.TryParse(reader.GetString(7), out letzterLogin); + var cmd = connection.CreateCommand(); + cmd.CommandText = @" + SELECT Id, Username, Password, Role, Mitarbeitennummer, Abteilung, MussPasswortAendern, LetzterLogin, VorletzterLogin + FROM Benutzer; + "; - if (!reader.IsDBNull(8)) - DateTime.TryParse(reader.GetString(8), out vorletzterLogin); - - benutzerListe.Add(new User + using var reader = cmd.ExecuteReader(); + while (reader.Read()) { - Id = reader.GetInt32(0), - Username = reader.GetString(1), - Password = reader.GetString(2), - Role = reader.GetString(3), - Mitarbeiternummer = reader.IsDBNull(4) ? "" : reader.GetString(4), - Abteilung = reader.IsDBNull(5) ? "" : reader.GetString(5), - MussPasswortAendern = reader.GetInt32(6) == 1, - LetzterLogin = letzterLogin, - VorletzterLogin = vorletzterLogin, - OriginalUsername = reader.GetString(1) - }); + // Parsing wie gehabt + benutzerListe.Add(new User + { + Id = reader.GetInt32(0), + Username = reader.GetString(1), + Password = reader.GetString(2), + Role = reader.GetString(3), + Mitarbeiternummer = reader.IsDBNull(4) ? "" : reader.GetString(4), + Abteilung = reader.IsDBNull(5) ? "" : reader.GetString(5), + MussPasswortAendern = reader.GetInt32(6) == 1, + LetzterLogin = reader.IsDBNull(7) ? DateTime.MinValue : DateTime.Parse(reader.GetString(7)), + VorletzterLogin = reader.IsDBNull(8) ? DateTime.MinValue : DateTime.Parse(reader.GetString(8)), + OriginalUsername = reader.GetString(1) + }); + } + } + catch (SqliteException ex) when (ex.SqliteErrorCode == 5) + { + Console.WriteLine("❌ Datenbank ist gesperrt. Bitte schließen Sie andere Programme, die auf die Datenbank zugreifen."); + throw new InvalidOperationException("Die Datenbank ist derzeit gesperrt – bitte schließen Sie andere Programme (z.B. DB Browser) und starten Sie die Anwendung neu."); } return benutzerListe; } + + + /// + /// Aktualisiert einen vorhandenen Benutzer in der Datenbank. + /// public void UpdateBenutzer(User benutzer) { using var connection = new SqliteConnection($"Data Source={_dbPath}"); @@ -228,6 +250,9 @@ namespace ChronoFlow.Persistence Console.WriteLine($"✏ Benutzer aktualisiert: {benutzer.Username} (Rows affected: {rowsAffected})"); } + /// + /// Löscht einen Benutzer anhand seines Benutzernamens. + /// public void LoescheBenutzer(string username) { using var connection = new SqliteConnection($"Data Source={_dbPath}"); @@ -241,6 +266,9 @@ namespace ChronoFlow.Persistence Console.WriteLine($"❌ Benutzer gelöscht: {username} (Rows affected: {rowsAffected})"); } + /// + /// Gibt eine Liste aller Benutzernamen mit Rolle 'Mitarbeiter' zurück. + /// public List LadeAlleMitarbeiterNamen() { var namen = new List(); @@ -260,33 +288,83 @@ namespace ChronoFlow.Persistence return namen; } + /// + /// Speichert einen neuen Zeiteintrag in der Datenbank. + /// + /// + /// Speichert einen neuen Zeiteintrag in der Datenbank. + /// + /// Das zu speichernde Zeiteintrag-Objekt. public void SpeichereEintrag(Zeiteintrag eintrag) { using var connection = new SqliteConnection($"Data Source={_dbPath}"); connection.Open(); + // SQL-Befehl mit zusätzlicher Spalte „Projektleiter“ var cmd = connection.CreateCommand(); cmd.CommandText = @" - INSERT INTO Zeiteintraege -(Mitarbeiter, Startzeit, Endzeit, Projekt, Kommentar, Erledigt, MitarbeiterKommentar, LetzteBearbeitung) -VALUES -($Mitarbeiter, $Startzeit, $Endzeit, $Projekt, $Kommentar, $Erledigt, $MitarbeiterKommentar, $LetzteBearbeitung);"; - + INSERT INTO Zeiteintraege + (Mitarbeiter, Startzeit, Endzeit, Projekt, Kommentar, Erledigt, MitarbeiterKommentar, LetzteBearbeitung, Projektleiter) + VALUES + ($Mitarbeiter, $Startzeit, $Endzeit, $Projekt, $Kommentar, $Erledigt, $MitarbeiterKommentar, $LetzteBearbeitung, $Projektleiter);"; + // Parameter an das SQL-Kommando binden 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("$Projekt", eintrag.Projekt ?? ""); cmd.Parameters.AddWithValue("$Kommentar", eintrag.Kommentar ?? ""); cmd.Parameters.AddWithValue("$Erledigt", eintrag.Erledigt ? 1 : 0); cmd.Parameters.AddWithValue("$MitarbeiterKommentar", eintrag.MitarbeiterKommentar ?? ""); cmd.Parameters.AddWithValue("$LetzteBearbeitung", DateTime.Now.ToString("o")); + cmd.Parameters.AddWithValue("$Projektleiter", eintrag.Projektleiter ?? ""); + // Ausführung des SQL-Befehls cmd.ExecuteNonQuery(); Console.WriteLine($"✅ Zeiteintrag für {eintrag.Mitarbeiter} gespeichert."); } + + /// + /// Lädt alle Zeiteinträge aus der Datenbank. + /// + public async Task> LadeAlleEintraegeAsync() + { + var eintraege = new List(); + + 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; + } + + /// + /// 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. + /// public List LadeAlleZeiteintraege() { var eintraege = new List(); @@ -300,7 +378,7 @@ VALUES using var reader = cmd.ExecuteReader(); while (reader.Read()) { - eintraege.Add(new Zeiteintrag + var eintrag = new Zeiteintrag { Id = reader.GetInt32(0), Mitarbeiter = reader.GetString(1), @@ -308,14 +386,25 @@ VALUES Endzeit = DateTime.Parse(reader.GetString(3)), Projekt = reader.GetString(4), Kommentar = reader.GetString(5), - Erledigt = Convert.ToInt32(reader["Erledigt"]) == 1, - MitarbeiterKommentar = reader.GetString(7) - }); + Erledigt = reader.GetBoolean(6), + 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; } + + /// + /// Aktualisiert ein bestehendes Projekt in der Datenbank anhand der ID. + /// + /// Das Projekt, das aktualisiert werden soll. public void UpdateProjekt(Zeiteintrag projekt) { using var connection = new SqliteConnection($"Data Source={_dbPath}"); @@ -325,12 +414,12 @@ VALUES cmd.CommandText = @" UPDATE Zeiteintraege SET Projekt = $Projekt, - Kommentar = $Kommentar, - Startzeit = $Startzeit, - Endzeit = $Endzeit, - Mitarbeiter = $Mitarbeiter, - Erledigt = $Erledigt, - LetzteBearbeitung = $LetzteBearbeitung + Kommentar = $Kommentar, + Startzeit = $Startzeit, + Endzeit = $Endzeit, + Mitarbeiter = $Mitarbeiter, + Erledigt = $Erledigt, + LetzteBearbeitung = $LetzteBearbeitung WHERE Id = $Id;"; cmd.Parameters.AddWithValue("$Projekt", projekt.Projekt); @@ -346,6 +435,10 @@ VALUES Console.WriteLine($"✏ Projekt aktualisiert (Id={projekt.Id}, Rows affected: {rowsAffected})"); } + /// + /// Löscht ein Projekt anhand seiner ID aus der Datenbank. + /// + /// Die ID des zu löschenden Projekts. public void LoescheProjekt(int id) { using var connection = new SqliteConnection($"Data Source={_dbPath}"); @@ -359,6 +452,11 @@ VALUES Console.WriteLine($"❌ Projekt gelöscht (Id: {id}, Rows: {rows})"); } + /// + /// Aktualisiert nur den Status "Erledigt" eines Projekts. + /// + /// ID des Projekts. + /// Neuer Status (true/false). public void UpdateProjektStatus(int id, bool erledigt) { using var connection = new SqliteConnection($"Data Source={_dbPath}"); @@ -375,9 +473,14 @@ VALUES cmd.Parameters.AddWithValue("$Id", id); 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})"); } + /// + /// Lädt alle Projekte, die als erledigt markiert sind. + /// + /// Liste abgeschlossener Zeiteinträge. public List LadeAbgeschlosseneProjekte() { var abgeschlossene = new List(); @@ -406,6 +509,11 @@ VALUES return abgeschlossene; } + /// + /// Lädt die letzten Projekte basierend auf der ID (absteigend sortiert). + /// + /// Anzahl der letzten Projekte. + /// Liste der letzten Zeiteinträge. public List LadeLetzteProjekte(int anzahl = 3) { var projekte = new List(); @@ -439,6 +547,10 @@ VALUES return projekte; } + /// + /// Lädt alle noch nicht erledigten Projekte (Zeiteinträge) aus der Datenbank. + /// + /// Liste offener Zeiteinträge public List LadeOffeneProjekte() { var offene = new List(); @@ -460,26 +572,35 @@ VALUES Projekt = reader.GetString(4), Kommentar = reader.GetString(5), 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; } + + /// + /// Setzt das Passwort eines Benutzers zurück (auf 'changeme'). + /// Das Passwort wird automatisch gehasht. + /// public void ResetBenutzerPasswort(string username) { using var connection = new SqliteConnection($"Data Source={_dbPath}"); connection.Open(); + // Neues Standardpasswort hashen var hashedDefault = PasswordHasher.HashPassword("changeme"); + var cmd = connection.CreateCommand(); cmd.CommandText = @" - UPDATE Benutzer - SET Password = $Password, - MussPasswortAendern = 1 - WHERE Username = $Username; - "; + UPDATE Benutzer + SET Password = $Password, + MussPasswortAendern = 1 + WHERE Username = $Username; + "; cmd.Parameters.AddWithValue("$Password", hashedDefault); cmd.Parameters.AddWithValue("$Username", username); @@ -487,7 +608,134 @@ VALUES int rowsAffected = cmd.ExecuteNonQuery(); Console.WriteLine($"🔒 Passwort für Benutzer '{username}' zurückgesetzt (Rows affected: {rowsAffected})"); } - + + + /// + /// Lädt alle Zeiteinträge eines bestimmten Mitarbeiters aus der Datenbank. + /// + /// Name des Mitarbeiters + /// Liste der zugehörigen Zeiteinträge + public async Task> GetEintraegeFuerMitarbeiterAsync(string name) + { + var eintraege = new List(); + + 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; + } + + + /// + /// Überprüft, ob die Spalte 'VorletzterLogin' in der Tabelle 'Benutzer' vorhanden ist. + /// Falls nicht, wird sie hinzugefügt. + /// +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."); +} + + + + /// + /// Aktualisiert die Login-Zeitpunkte des Benutzers in der Datenbank. + /// + /// Der Benutzer, dessen Loginzeiten aktualisiert werden sollen public void UpdateLoginZeiten(User user) { using var connection = new SqliteConnection($"Data Source={_dbPath}"); @@ -501,86 +749,21 @@ VALUES WHERE Username = $Username; "; - cmd.Parameters.AddWithValue("$LetzterLogin", user.LetzterLogin.ToString("o")); - cmd.Parameters.AddWithValue("$VorletzterLogin", user.VorletzterLogin.ToString("o")); - cmd.Parameters.AddWithValue("$Username", user.Username); + // Setzt die Parameter für das SQL-Statement + cmd.Parameters.AddWithValue("$LetzterLogin", user.LetzterLogin.ToString("o")); // ISO 8601-Format + cmd.Parameters.AddWithValue("$VorletzterLogin", user.VorletzterLogin.ToString("o")); // ebenfalls ISO + cmd.Parameters.AddWithValue("$Username", user.Username); // Nutzername als Schlüssel - cmd.ExecuteNonQuery(); - } - - public async Task> GetEintraegeFuerMitarbeiterAsync(string name) - { - var eintraege = new List(); - - 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; + cmd.ExecuteNonQuery(); // Führt das Update aus } - 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."); - } - } + /// + /// Aktualisiert den Erledigt-Status, Kommentar und das Bearbeitungsdatum eines Zeiteintrags. + /// + /// ID des Zeiteintrags + /// Neuer Status: erledigt oder nicht + /// Neuer Kommentar des Mitarbeiters public async Task UpdateStatusUndKommentarAsync(int id, bool erledigt, string mitarbeiterKommentar) { using var connection = new SqliteConnection($"Data Source={_dbPath}"); @@ -588,12 +771,12 @@ VALUES var cmd = connection.CreateCommand(); cmd.CommandText = @" - UPDATE Zeiteintraege - SET Erledigt = $Erledigt, - MitarbeiterKommentar = $Kommentar, - LetzteBearbeitung = $Bearbeitet - WHERE Id = $Id; -"; + UPDATE Zeiteintraege + SET Erledigt = $Erledigt, + MitarbeiterKommentar = $Kommentar, + LetzteBearbeitung = $Bearbeitet + WHERE Id = $Id; + "; cmd.Parameters.AddWithValue("$Erledigt", erledigt ? 1 : 0); cmd.Parameters.AddWithValue("$Kommentar", mitarbeiterKommentar ?? ""); @@ -603,7 +786,5 @@ VALUES int rowsAffected = await cmd.ExecuteNonQueryAsync(); Console.WriteLine($"✏ Zeiteintrag (Id={id}) aktualisiert: erledigt={erledigt}, LetzteBearbeitung gesetzt."); } - - } } \ No newline at end of file diff --git a/ChronoFlow.View/Admin/AbgeschlosseneProjekteView.axaml.cs b/ChronoFlow.View/Admin/AbgeschlosseneProjekteView.axaml.cs index f896b7a..39a5893 100644 --- a/ChronoFlow.View/Admin/AbgeschlosseneProjekteView.axaml.cs +++ b/ChronoFlow.View/Admin/AbgeschlosseneProjekteView.axaml.cs @@ -8,12 +8,29 @@ using ChronoFlow.Persistence; namespace ChronoFlow.View.Admin; +/// +/// Zeigt eine Liste aller abgeschlossenen Projekte an. +/// Ermöglicht Suche und Navigation zurück zum Admin-Dashboard. +/// public partial class AbgeschlosseneProjekteView : UserControl { - private readonly ViewManager _viewManager; + private readonly ViewManager? _viewManager; // Nur gesetzt, wenn Konstruktor mit Parameter verwendet wird private readonly ObservableCollection _abgeschlosseneProjekte = new(); private readonly SqliteZeiterfassungsService _dbService = new(); + /// + /// Öffentlicher, parameterloser Konstruktor für Avalonia (zur Laufzeit erforderlich). + /// Wird z. B. vom XAML-Loader genutzt, daher AVLN0005-Warnung ohne ihn. + /// + public AbgeschlosseneProjekteView() + { + InitializeComponent(); + LadeAbgeschlosseneProjekte(); + } + + /// + /// Konstruktor mit ViewManager zur Navigation – wird typischerweise aus Code aufgerufen. + /// public AbgeschlosseneProjekteView(ViewManager viewManager) { InitializeComponent(); @@ -21,6 +38,10 @@ public partial class AbgeschlosseneProjekteView : UserControl LadeAbgeschlosseneProjekte(); } + /// + /// Lädt alle abgeschlossenen Projekte aus der Datenbank + /// und aktualisiert die Liste in der Benutzeroberfläche. + /// private void LadeAbgeschlosseneProjekte() { _abgeschlosseneProjekte.Clear(); @@ -33,10 +54,10 @@ public partial class AbgeschlosseneProjekteView : UserControl AbgeschlosseneListe.ItemsSource = _abgeschlosseneProjekte; } - private void ZurueckButton_Click(object? sender, RoutedEventArgs e) - { - _viewManager.Show("AdminMain"); - } + /// + /// Wird aufgerufen, wenn eine Taste im Suchfeld gedrückt wird. + /// Filtert die Liste der Projekte anhand von Projektname oder Mitarbeitername. + /// private void Suchfeld_KeyUp(object? sender, KeyEventArgs e) { var text = Suchfeld?.Text?.ToLower() ?? ""; @@ -46,4 +67,11 @@ public partial class AbgeschlosseneProjekteView : UserControl .ToList(); } -} \ No newline at end of file + /// + /// Navigiert zurück zur Admin-Hauptansicht (z. B. Dashboard). + /// + private void ZurueckButton_Click(object? sender, RoutedEventArgs e) + { + _viewManager?.Show("AdminMain"); + } +} diff --git a/ChronoFlow.View/Admin/AdminMainView.axaml.cs b/ChronoFlow.View/Admin/AdminMainView.axaml.cs index 5d4af99..7ef619a 100644 --- a/ChronoFlow.View/Admin/AdminMainView.axaml.cs +++ b/ChronoFlow.View/Admin/AdminMainView.axaml.cs @@ -7,16 +7,26 @@ using ChronoFlow.Persistence; namespace ChronoFlow.View.Admin { + /// + /// Die Hauptansicht für Administratoren nach dem Login. + /// Zeigt die zuletzt bearbeiteten Projekte und Navigationsoptionen. + /// public partial class AdminMainView : UserControl { private readonly ViewManager _viewManager; private readonly ObservableCollection _letzteProjekte = new(); + /// + /// Parameterloser Konstruktor (wird nur intern verwendet). + /// public AdminMainView() : this(new ViewManager(new ContentControl())) { Console.WriteLine("⚠ Achtung: Parameterloser Konstruktor genutzt (nur Standard-ViewManager)."); } + /// + /// Konstruktor mit Übergabe des ViewManagers für Navigation. + /// public AdminMainView(ViewManager viewManager) { InitializeComponent(); @@ -26,6 +36,9 @@ namespace ChronoFlow.View.Admin LadeLetzteProjekte(); } + /// + /// Holt die letzten 3 Projekte aus der Datenbank und zeigt sie an. + /// private void LadeLetzteProjekte() { Console.WriteLine("🔄 Lade letzte Projekte..."); @@ -58,6 +71,8 @@ namespace ChronoFlow.View.Admin } } + // 🔽 Navigation per Buttonklick + private void MitarbeiterHinzufuegen_Click(object sender, RoutedEventArgs e) { _viewManager.Show("MitarbeiterHinzufuegen"); @@ -67,6 +82,7 @@ namespace ChronoFlow.View.Admin { _viewManager.Show("ProjektErstellen"); } + private void AlleProjekte_Click(object sender, RoutedEventArgs e) { _viewManager.Show("AlleProjekte"); @@ -77,20 +93,17 @@ namespace ChronoFlow.View.Admin _viewManager.Show("MitarbeiterListe"); } - - - - - public void AktualisiereLetzteProjekte() - { - LadeLetzteProjekte(); - } - private void AbgeschlosseneProjekte_Click(object? sender, RoutedEventArgs e) { _viewManager.Show("AbgeschlosseneProjekte"); } - - + + /// + /// Wird z. B. vom ProjektErstellenView aufgerufen, um die Liste neu zu laden. + /// + public void AktualisiereLetzteProjekte() + { + LadeLetzteProjekte(); + } } } diff --git a/ChronoFlow.View/Admin/AlleProjekteView.axaml b/ChronoFlow.View/Admin/AlleProjekteView.axaml index 1f13717..dd09bd7 100644 --- a/ChronoFlow.View/Admin/AlleProjekteView.axaml +++ b/ChronoFlow.View/Admin/AlleProjekteView.axaml @@ -4,15 +4,15 @@ x:Class="ChronoFlow.View.Admin.AlleProjekteView"> - + - + - + @@ -20,7 +20,7 @@ - + @@ -28,11 +28,15 @@ - + + + + + - + - +