diff --git a/CodingTracker/CodingController.cs b/CodingTracker/CodingController.cs new file mode 100644 index 00000000..2c7dee28 --- /dev/null +++ b/CodingTracker/CodingController.cs @@ -0,0 +1,144 @@ +using Spectre.Console; +using System.Globalization; + +namespace CodingTracker +{ + public class CodingController + { + private readonly Database _db; + + public CodingController(Database db) + { + _db = db; + } + + public string CalculateDuration(string timeStart, string timeEnd) + { + DateTime parseTimeStart = DateTime.ParseExact(timeStart, "dd.MM.yyyy HH:mm", CultureInfo.InvariantCulture); + DateTime parseTimeEnd = DateTime.ParseExact(timeEnd, "dd.MM.yyyy HH:mm", CultureInfo.InvariantCulture); + + TimeSpan duration = parseTimeEnd - parseTimeStart; + + var durationString = $"{duration.TotalHours.ToString()} hours {duration.Minutes.ToString()} minutes"; + + return durationString; + } + + public void CreateSession() + { + var v = new Validation(); + var isGetCorrectDates = false; + var timeStart = ""; + var timeEnd = ""; + var isCorrectDates = true; + + while (!isGetCorrectDates) + { + timeStart = AnsiConsole.Prompt(new TextPrompt("Enter the date and time of the session start [grey](dd.MM.yyyy HH:mm)[/]:")); + timeEnd = AnsiConsole.Prompt(new TextPrompt("Enter the date and time of the session end [grey](dd.MM.yyyy HH:mm)[/]:")); + + isCorrectDates = v.ValidationDates(timeStart, timeEnd); + isGetCorrectDates = isCorrectDates; + } + + string durationSession = CalculateDuration(timeStart, timeEnd); + _db.CreateRecordSession(timeStart, timeEnd, durationSession); + } + + public void ReadAllSessions() + { + List listSessions = _db.ReadListSessions(); + if (listSessions.Count() == 0) + { + AnsiConsole.MarkupLine("[red]Oops! There are no saved sessions yet.[/]"); + return; + } + + var tableSessions = new Table(); + tableSessions.AddColumn("ID"); + tableSessions.AddColumn("Time of the session Start"); + tableSessions.AddColumn("Time of the session End"); + tableSessions.AddColumn("Duration of the session"); + + foreach (CodingSession session in listSessions) + { + tableSessions.AddRow(session.ID.ToString(), session.StartTime.ToString(), session.EndTime.ToString(), session.Duration.ToString()); + } + AnsiConsole.Write(tableSessions); + } + + public void UpdateSession() + { + var v = new Validation(); + List listSessions = _db.ReadListSessions(); + if (listSessions.Count() == 0) + { + AnsiConsole.MarkupLine("[red]Oops! There are no saved sessions yet.[/]"); + return; + } + + CodingSession choiceSession = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("Which session do you want to update?") + .AddChoices(listSessions) + ); + var choiceChange = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("What do you want to change?") + .AddChoices(new[] { + "Time of session start", "Time of session end" + })); + + var newTimeStart = choiceSession.StartTime; + var newTimeEnd = choiceSession.EndTime; + var newDuration = choiceSession.Duration; + var isGetCorrectDates = false; + + switch (choiceChange) + { + case "Time of session start": + while (!isGetCorrectDates) + { + newTimeStart = AnsiConsole.Prompt(new TextPrompt("Enter a new session start date and time [grey](dd.MM.yyyy HH:mm)[/]:")); + var isCorrectDate = v.ValidationDates(newTimeStart, newTimeEnd); + isGetCorrectDates = isCorrectDate; + } + + newDuration = CalculateDuration(newTimeStart, newTimeEnd); + + _db.UpdateRecordSession(choiceSession.ID, newTimeStart, newTimeEnd, newDuration); + break; + case "Time of session end": + while (!isGetCorrectDates) + { + newTimeEnd = AnsiConsole.Prompt(new TextPrompt("Enter a new session end date and time [grey](dd.MM.yyyy HH:mm)[/]:")); + var isCorrectDate = v.ValidationDates(newTimeStart, newTimeEnd); + isGetCorrectDates = isCorrectDate; + } + + newDuration = CalculateDuration(newTimeStart, newTimeEnd); + + _db.UpdateRecordSession(choiceSession.ID, newTimeStart, newTimeEnd, newDuration); + break; + } + } + + public void DeleteSession() + { + List listSessions = _db.ReadListSessions(); + if (listSessions.Count() == 0) + { + AnsiConsole.MarkupLine("[red]Oops! There are no saved sessions yet.[/]"); + return; + } + + CodingSession choiceSession = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("Which session do you want to delete?") + .AddChoices(listSessions) + ); + + _db.DeleteRecordSession(choiceSession.ID); + } + } +} diff --git a/CodingTracker/CodingSession.cs b/CodingTracker/CodingSession.cs new file mode 100644 index 00000000..7a9ed329 --- /dev/null +++ b/CodingTracker/CodingSession.cs @@ -0,0 +1,14 @@ +namespace CodingTracker +{ + public class CodingSession + { + public int ID { get; set; } + public string StartTime { get; set; } = ""; + public string EndTime { get; set; } = ""; + public string Duration { get; set; } = ""; + public override string ToString() + { + return $"{ID}. {StartTime: dd.MM.yyyy HH:mm} → {EndTime: dd.MM.yyyy HH:mm} ({Duration})"; + } + } +} diff --git a/CodingTracker/CodingTracker.csproj b/CodingTracker/CodingTracker.csproj new file mode 100644 index 00000000..95548f88 --- /dev/null +++ b/CodingTracker/CodingTracker.csproj @@ -0,0 +1,18 @@ + + + + Exe + net10.0 + enable + enable + + + + + + + + + + + diff --git a/CodingTracker/CodingTracker.slnx b/CodingTracker/CodingTracker.slnx new file mode 100644 index 00000000..ba103e54 --- /dev/null +++ b/CodingTracker/CodingTracker.slnx @@ -0,0 +1,3 @@ + + + diff --git a/CodingTracker/Database.cs b/CodingTracker/Database.cs new file mode 100644 index 00000000..d0d86049 --- /dev/null +++ b/CodingTracker/Database.cs @@ -0,0 +1,74 @@ +using Dapper; +using Microsoft.Extensions.Configuration; +using Spectre.Console; +using System.Data; +using Microsoft.Data.Sqlite; + +namespace CodingTracker +{ + public class Database + { + private string _connectionString = null!; + public void Initialize() + { + IConfigurationRoot configuration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .Build(); + + var connectionString = configuration.GetConnectionString("Default"); + + if (connectionString == null) + { + AnsiConsole.MarkupLine("[red]Oops! Connection string not found.[/]"); + return; + } + + _connectionString = connectionString; + + using IDbConnection connection = new SqliteConnection(_connectionString); + + var request = "CREATE TABLE IF NOT EXISTS CodingSessions (ID INTEGER PRIMARY KEY AUTOINCREMENT, StartTime TEXT NOT NULL, EndTime TEXT NOT NULL, Duration TEXT NOT NULL);"; + + connection.Execute(request); + } + + public List ReadListSessions() + { + using IDbConnection connection = new SqliteConnection(_connectionString); + + var request = "SELECT * FROM CodingSessions"; + + var sessions = connection.Query(request); + + return sessions.ToList(); + } + + public void CreateRecordSession(string timeStart, string timeEnd, string durationSession) + { + using IDbConnection connection = new SqliteConnection(_connectionString); + + var request = "INSERT INTO CodingSessions (StartTime, EndTime, Duration) VALUES (@TimeStart, @TimeEnd, @DurationSession)"; + + connection.Execute(request, new { TimeStart = timeStart, TimeEnd = timeEnd, DurationSession = durationSession }); + } + + public void UpdateRecordSession(int id, string timeStart, string timeEnd, string durationSession) + { + using IDbConnection connection = new SqliteConnection(_connectionString); + + var request = "UPDATE CodingSessions SET StartTime = @TimeStart, EndTime = @TimeEnd, Duration = @DurationSession WHERE ID = @ID"; + + connection.Execute(request, new { TimeStart = timeStart, TimeEnd = timeEnd, DurationSession = durationSession, ID = id }); + } + + public void DeleteRecordSession(int id) + { + using IDbConnection connection = new SqliteConnection(_connectionString); + + var request = "DELETE FROM CodingSessions WHERE ID = @id"; + + connection.Execute(request, new { id }); + } + } +} diff --git a/CodingTracker/Properties/launchSettings.json b/CodingTracker/Properties/launchSettings.json new file mode 100644 index 00000000..dc2cd39f --- /dev/null +++ b/CodingTracker/Properties/launchSettings.json @@ -0,0 +1,7 @@ +{ + "profiles": { + "CodingTracker": { + "commandName": "Project" + } + } +} \ No newline at end of file diff --git a/CodingTracker/README.md b/CodingTracker/README.md new file mode 100644 index 00000000..f588c6a2 --- /dev/null +++ b/CodingTracker/README.md @@ -0,0 +1,44 @@ +# Habit Tracker + +Habit Tracker is a console-based CRUD application for tracking coding sessions. +The project is built with **C#**, **Dapper**, and **Spectre.Console**, focusing on clean architecture, simplicity, and practical use of database-driven applications. + +The application allows users to manage coding sessions while automatically calculating the duration based on the provided start and end times. + +--- + +## Features + +The user can: + +- Create a new coding session +- View all existing coding sessions +- Update an existing coding session +- Delete a coding session + +### Automatic Duration Calculation + +- The user enters a start date and time +- The user enters an end date and time +- The application calculates the session duration automatically + +--- + +## Technologies Used + +- C# +- .NET +- Dapper +- Spectre.Console +- SQL Server / LocalDB + +--- + +## Getting Started + +### Clone the Repository + +```bash +git clone +cd HabitTracker +``` \ No newline at end of file diff --git a/CodingTracker/UserInput.cs b/CodingTracker/UserInput.cs new file mode 100644 index 00000000..d24a79bb --- /dev/null +++ b/CodingTracker/UserInput.cs @@ -0,0 +1,47 @@ +using Spectre.Console; + +namespace CodingTracker +{ + internal class UserInput + { + public static void Main() + { + var db = new Database(); + db.Initialize(); + var cc = new CodingController(db); + var isRunning = true; + while (isRunning) + { + AnsiConsole.Write(new Rule("[bold violet]MAIN MENU[/]").RuleStyle("violet").LeftJustified()); + + var choiceAction = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("[yellow]What do you want to do?[/]") + .PageSize(5) + .AddChoices(new[] { + "Exit", "Create a new session", "Read all existing sessions", + "Update the session", "Delete the session" + })); + + switch (choiceAction) { + case "Exit": + isRunning = false; + AnsiConsole.Clear(); + break; + case "Create a new session": + cc.CreateSession(); + break; + case "Read all existing sessions": + cc.ReadAllSessions(); + break; + case "Update the session": + cc.UpdateSession(); + break; + case "Delete the session": + cc.DeleteSession(); + break; + } + } + } + } +} \ No newline at end of file diff --git a/CodingTracker/Validation.cs b/CodingTracker/Validation.cs new file mode 100644 index 00000000..bc2e39ec --- /dev/null +++ b/CodingTracker/Validation.cs @@ -0,0 +1,28 @@ +using Spectre.Console; +using System.Globalization; + +namespace CodingTracker +{ + internal class Validation + { + public bool ValidationDates(string timeStart, string timeEnd) + { + if (!DateTime.TryParseExact(timeStart, "dd.MM.yyyy HH:mm", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime parsedTimeStart)) + { + AnsiConsole.MarkupLine("[red]Oops! Incorrect session start date format.[/]"); + return false; + } + if (!DateTime.TryParseExact(timeEnd, "dd.MM.yyyy HH:mm", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime parsedTimeEnd)) + { + AnsiConsole.MarkupLine("[red]Oops! Incorrect session end date format.[/]"); + return false; + } + if (parsedTimeStart >= parsedTimeEnd) + { + AnsiConsole.MarkupLine("[red]Oops! Session start date cannot be greater than or equal to the end date.[/]"); + return false; + } + return true; + } + } +} diff --git a/CodingTracker/appsettings.json b/CodingTracker/appsettings.json new file mode 100644 index 00000000..c42fbc19 --- /dev/null +++ b/CodingTracker/appsettings.json @@ -0,0 +1,8 @@ +{ + "DataBase": { + "FileName": "codingTracker.db" + }, + "ConnectionStrings": { + "Default": "Data Source=./codingTracker.db" + } +} \ No newline at end of file