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.
+
+
+1. Print all records
+
+This option give user another choises of how to print data
+
+
+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.
+
+
+2.New record
+
+This create new record. User write start and end time in 'dd.MM.yyyy H:m' format:
+
+
+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.