diff --git a/CodingTracker.Solomonlol/CodingTracker.Solomonlol.csproj b/CodingTracker.Solomonlol/CodingTracker.Solomonlol.csproj new file mode 100644 index 000000000..5e419a358 --- /dev/null +++ b/CodingTracker.Solomonlol/CodingTracker.Solomonlol.csproj @@ -0,0 +1,24 @@ + + + + Exe + net10.0 + enable + enable + + + + + + + + + + + + + PreserveNewest + + + + diff --git a/CodingTracker.Solomonlol/CodingTracker.Solomonlol.slnx b/CodingTracker.Solomonlol/CodingTracker.Solomonlol.slnx new file mode 100644 index 000000000..6e762a04d --- /dev/null +++ b/CodingTracker.Solomonlol/CodingTracker.Solomonlol.slnx @@ -0,0 +1,3 @@ + + + diff --git a/CodingTracker.Solomonlol/Controllers/CodingController.cs b/CodingTracker.Solomonlol/Controllers/CodingController.cs new file mode 100644 index 000000000..450d00f81 --- /dev/null +++ b/CodingTracker.Solomonlol/Controllers/CodingController.cs @@ -0,0 +1,216 @@ +using CodingTracker.Solomonlol.Model; +using Dapper; +using Microsoft.Data.Sqlite; +using Microsoft.Extensions.Configuration; +using Spectre.Console; +using System.Data; +using System.Diagnostics; +using System.Globalization; +using static CodingTracker.Solomonlol.Model.MenuValues; + +namespace CodingTracker.Solomonlol.Controllers +{ + internal class CodingController : ICodingController + { + public void CreateTableIfNotExists() + { + using IDbConnection db = new SqliteConnection(GetConString()); + db.Open(); + var sqlQuery = "CREATE TABLE IF NOT EXISTS CodingSessions" + + "(Id INTEGER PRIMARY KEY AUTOINCREMENT," + + "Date TEXT NOT NULL," + + "StartTime TEXT NOT NULL," + + "EndTime TEXT NOT NULL," + + "Duration TEXT NOT NULL)"; + db.Execute(sqlQuery); + db.Close(); + } + + public List GetData(string? s = null) + { + if (s == null) s = "SELECT * FROM CodingSessions"; + using IDbConnection db = new SqliteConnection(GetConString()); + return [.. db.Query(s)]; + } + + public void CreateData(CodingSession? codingSession=null) + { + PrintData(); + CodingSession session = new(); + if (codingSession == null) + { + session = NewRecord(); + } + else session = codingSession; + + using IDbConnection db = new SqliteConnection(GetConString()); + var sqlQuery = "INSERT INTO CodingSessions (Date, StartTime, EndTime, Duration)" + + "VALUES (@Date, @StartTime, @EndTime, @Duration)"; + db.Execute(sqlQuery, session); + PrintData(); + } + + public void DeleteData() + { + PrintData(); + NumberInput("Write record Id to delete:", out int id); + using IDbConnection db = new SqliteConnection(GetConString()); + var sqlQuery = "DELETE FROM CodingSessions WHERE Id=@id"; + var check = db.Execute(sqlQuery, new { id }); + if (check == 0) + { + AnsiConsole.MarkupLine($"[red]Record whith Id={id} does not exists.[/]"); + + } + else + { + PrintData(); + AnsiConsole.MarkupLine($"[green]Record whith Id={id} was deleted.[/]"); + } + + } + + public void UpdateData() + { + PrintData(); + NumberInput("Write record Id to update:", out int id); + string sql = "SELECT COUNT(1) FROM CodingSessions WHERE Id = @Id"; + + + using IDbConnection db = new SqliteConnection(GetConString()); + var checkIfExist = db.ExecuteScalar(sql, new { Id = id }); + if (checkIfExist) + { + var session = NewRecord(id); + + var sqlQuery = "UPDATE CodingSessions SET Date=@Date," + + "StartTime=@StartTime," + + "EndTime=@EndTime," + + "Duration=@Duration " + + "WHERE Id=@Id"; + db.Execute(sqlQuery, session); + PrintData(); + AnsiConsole.MarkupLine($"[green]Record whith Id={id} was upadted.[/]"); + } + else + { + PrintData(); + AnsiConsole.MarkupLine($"[red]Record whith Id={id} does not exists.[/]"); + } + + } + + public void PrintData(string? s = null) + { + AnsiConsole.Clear(); + List toPrint = new List(); + if (s != null) + { + toPrint = GetData(s); + } + else toPrint = GetData(); + if (toPrint.Count>0) + { + var table = new Table(); + table.AddColumns("ID", "Date", "Start time", "End time", "Duration"); + + foreach (var c in toPrint) + { + table.AddRow($"{c.Id}", + $"{c.Date}", + $"{c.StartTime}", + $"{c.EndTime}", + $"{c.Duration}"); + } + AnsiConsole.Write(table); + } + else AnsiConsole.MarkupLine("[red]Database is empty.[/]"); + } + + private static string GetConString() + { + IConfiguration config = new ConfigurationBuilder() + .AddJsonFile("appsetings.json") + .Build(); + + var connectionString = config.GetConnectionString("SQLiteConnection"); + return connectionString; + } + + private CodingSession NewRecord(int? id = null) + { + DateTime startTime, endTime; + do + { + PrintData(); + DateInput("Write start time in 'dd.MM.yyyy H:m' format:", out startTime); + DateInput("Write end time in 'dd.MM.yyyy H:m' format:", out endTime); + if (!(endTime > startTime)) + { + AnsiConsole.MarkupLine("[red]The end time cannot be earlier than or the same as the start time.[/]" + + "\nPress any key to continue"); + AnsiConsole.Console.Input.ReadKey(true); + } + } while (!(endTime > startTime)); + CodingSession session = new(startTime, endTime, id); + return session; + + } + private static void DateInput(string s, out DateTime date) + { + Console.WriteLine(s); + while (!DateTime.TryParseExact(Console.ReadLine(), "dd.MM.yyyy H:m", CultureInfo.InvariantCulture, DateTimeStyles.None, out date)) + { + AnsiConsole.MarkupLine("[red]Wrong data format. Write it like 'dd.MM.yyyy H:m' and try again.[/]"); + } + } + private static void NumberInput(string s, out int id) + { + Console.WriteLine(s); + while (!int.TryParse(Console.ReadLine(), out id) || id <= 0) + { + AnsiConsole.MarkupLine("[red]Wrong data format. The string must contain only digits above zero. Try again.[/]"); + } + } + public void StartTimer() + { + Stopwatch stopwatch = new Stopwatch(); + DateTime start = DateTime.Now; + DateTime end = new(); + stopwatch.Start(); + while(true) + { + if(Console.KeyAvailable) + { + var key = Console.ReadKey(true).Key; + if(key==ConsoleKey.Enter) + { + stopwatch.Stop(); + end = start+stopwatch.Elapsed; + CreateData(new CodingSession(start, end)); + break; + } + } + + Console.Clear(); + AnsiConsole.MarkupLine("[green]IT'S CODING TIME![/]\nPress Enter key to stop this session..."); + AnsiConsole.MarkupLine($"Current session: {stopwatch.Elapsed.ToString(@"mm\:ss")}"); + Thread.Sleep(1000); + } + } + public void PrintOrderby() + { + var choice = AnsiConsole.Prompt(new SelectionPrompt() + .Title("Select an [green]option[/]:") + .AddChoices(printValues.Keys)); + + printValues[choice](""); + + } + + public void Exit() + { + Environment.Exit(0); + } + } +} diff --git a/CodingTracker.Solomonlol/Controllers/ICodingController.cs b/CodingTracker.Solomonlol/Controllers/ICodingController.cs new file mode 100644 index 000000000..4cb742704 --- /dev/null +++ b/CodingTracker.Solomonlol/Controllers/ICodingController.cs @@ -0,0 +1,17 @@ +using CodingTracker.Solomonlol.Model; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CodingTracker.Solomonlol.Controllers +{ + internal interface ICodingController + { + public void CreateTableIfNotExists(); + public void UpdateData(); + public void DeleteData(); + public void PrintData(string? s=null); + public void CreateData(CodingSession? codingSession=null); + public void Exit(); + } +} diff --git a/CodingTracker.Solomonlol/Menu.cs b/CodingTracker.Solomonlol/Menu.cs new file mode 100644 index 000000000..48c1300a6 --- /dev/null +++ b/CodingTracker.Solomonlol/Menu.cs @@ -0,0 +1,29 @@ +using CodingTracker.Solomonlol.Controllers; +using CodingTracker.Solomonlol.Model; +using Dapper; +using Spectre.Console; +using System; +using System.Collections.Generic; +using System.Text; +using static CodingTracker.Solomonlol.Model.MenuValues; + + +namespace CodingTracker.Solomonlol +{ + internal class Menu + { + + public static void MainMenu() + { + cod.CreateTableIfNotExists(); + while (true) + { + var choice = AnsiConsole.Prompt(new SelectionPrompt() + .Title("Select an [green]option[/]:") + .AddChoices(menuValues.Keys)); + + menuValues[choice](); + } + } + } +} diff --git a/CodingTracker.Solomonlol/Model/CodingSession.cs b/CodingTracker.Solomonlol/Model/CodingSession.cs new file mode 100644 index 000000000..a08b8edcd --- /dev/null +++ b/CodingTracker.Solomonlol/Model/CodingSession.cs @@ -0,0 +1,28 @@ +using Spectre.Console; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CodingTracker.Solomonlol.Model +{ + internal class CodingSession + { + public int? Id { get; set; } = null; + public string Date { get; set; } + public string StartTime { get; set; } + public string EndTime { get; set; } + public string Duration { get; set; } + + public CodingSession() + { } + + public CodingSession(DateTime startTime, DateTime endTime, int? id = null) + { + Id = id; + Date = DateOnly.FromDateTime(startTime).ToString(); + StartTime = TimeOnly.FromDateTime(startTime).ToString(); + EndTime = TimeOnly.FromDateTime(endTime).ToString(); + Duration = ((int)endTime.Subtract(startTime).TotalMinutes) + " minutes"; + } + } +} diff --git a/CodingTracker.Solomonlol/Model/MenuValues.cs b/CodingTracker.Solomonlol/Model/MenuValues.cs new file mode 100644 index 000000000..6cf45d0cc --- /dev/null +++ b/CodingTracker.Solomonlol/Model/MenuValues.cs @@ -0,0 +1,29 @@ +using CodingTracker.Solomonlol.Controllers; + + +namespace CodingTracker.Solomonlol.Model +{ + public static class MenuValues + { + internal static CodingController cod = new(); + public static Dictionary menuValues = new() + { + { "Print all records", () => cod.PrintOrderby() }, + { "New record", () => cod.CreateData() }, + { "Update record", () => cod.UpdateData() }, + { "Delete record", () => cod.DeleteData() }, + { "Timer", ()=>cod.StartTimer() }, + { "Exit", () => cod.Exit() } + }; + + public static Dictionary> printValues = new() + { + { "Print", (s) => cod.PrintData() }, + { "Order by Date Ascending", (s) => cod.PrintData("SELECT * FROM CodingSessions ORDER BY Date ASC") }, + { "Order by Date Descending", (s) => cod.PrintData("SELECT * FROM CodingSessions ORDER BY Date DESC") }, + { "Order by Duration Ascending", (s) => cod.PrintData("SELECT * FROM CodingSessions ORDER BY Duration ASC") }, + { "Order by Duration Descending", (s) => cod.PrintData("SELECT * FROM CodingSessions ORDER BY Duration DESC") }, + }; + + } +} diff --git a/CodingTracker.Solomonlol/Program.cs b/CodingTracker.Solomonlol/Program.cs new file mode 100644 index 000000000..3564f7ef4 --- /dev/null +++ b/CodingTracker.Solomonlol/Program.cs @@ -0,0 +1,8 @@ +using CodingTracker.Solomonlol; +using Microsoft.Extensions.Configuration; +using Spectre.Console; + + + +Menu menu = new(); +Menu.MainMenu(); \ No newline at end of file diff --git a/CodingTracker.Solomonlol/appsetings.json b/CodingTracker.Solomonlol/appsetings.json new file mode 100644 index 000000000..718be077c --- /dev/null +++ b/CodingTracker.Solomonlol/appsetings.json @@ -0,0 +1,15 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "DatabaseSettings": { + "SQLiteDbPath": "ApplicationDatabase.db" + }, + "ConnectionStrings": { + "SQLiteConnection": "Data Source=ApplicationDatabase.db;" + } +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 000000000..e1d60d4b3 --- /dev/null +++ b/README.md @@ -0,0 +1,57 @@ +#ABOUT CODING TRACKER + +A .NET 10-based console application that allows users to create, view, update, and delete records of time spent coding. +It supports both manual data entry and automatic time tracking via a timer. +Users can view all database records in their original order or sort them in ascending or descending order. + +#FEATURES + +The program creates a database upon startup. +The user can select a menu item; navigation is performed using the arrow keys. +image + +1. Print all records + +This option give user another choises of how to print data +image + +Print - Output data without sorting +The other menu items speak for themselves. +After selecting the menu item, a table appears containing data read from the database. +image + +2.New record + +This create new record. User write start and end time in 'dd.MM.yyyy H:m' format: +image + +Data entered by the user must be correct. Otherwise, the program will indicate the incorrectly entered data. +3.Update record + +This option allows the user to make changes to an existing record. + +4.Delete record + +This option allows the user to delete an existing record. + +5.Timer + +This menu item starts a timer and displays the current session time on the console. +Once the user stops the timer, a new record containing the current session's timing data is created in the database. + +6.Exit + +This option close the app + +#Lessons Learned + +Learned how to use Stopwatch. +Tried creating a menu using a Dictionary instead of a switch statement. +Learned how to use Dapper—it is simpler than ADO.NET. +Gained experience in using a separate JSON file to store the database connection string. + + +#My thoughts + +I still have a lot to improve, but progress is evident, as this project was completed faster +than the previous one despite the use of new technologies.