diff --git a/CodingTracker.DzemalKurtic/CodingTracker.DzemalKurtic.slnx b/CodingTracker.DzemalKurtic/CodingTracker.DzemalKurtic.slnx
new file mode 100644
index 00000000..bbdb9534
--- /dev/null
+++ b/CodingTracker.DzemalKurtic/CodingTracker.DzemalKurtic.slnx
@@ -0,0 +1,3 @@
+
+
+
diff --git a/CodingTracker.DzemalKurtic/CodingTracker.DzemalKurtic/CodingTracker.DzemalKurtic.csproj b/CodingTracker.DzemalKurtic/CodingTracker.DzemalKurtic/CodingTracker.DzemalKurtic.csproj
new file mode 100644
index 00000000..2aaf3bf6
--- /dev/null
+++ b/CodingTracker.DzemalKurtic/CodingTracker.DzemalKurtic/CodingTracker.DzemalKurtic.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ net10.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
diff --git a/CodingTracker.DzemalKurtic/CodingTracker.DzemalKurtic/Controllers/CodingSessionController.cs b/CodingTracker.DzemalKurtic/CodingTracker.DzemalKurtic/Controllers/CodingSessionController.cs
new file mode 100644
index 00000000..d9cc117e
--- /dev/null
+++ b/CodingTracker.DzemalKurtic/CodingTracker.DzemalKurtic/Controllers/CodingSessionController.cs
@@ -0,0 +1,73 @@
+using CodingTracker.DzemalKurtic.Models;
+using Dapper;
+using Microsoft.Data.Sqlite;
+
+namespace CodingTracker.DzemalKurtic.Controllers;
+
+internal class CodingSessionController
+{
+ public string ConnectionString { get; set; }
+ public CodingSessionController(string connectionString)
+ {
+ ConnectionString = connectionString;
+ }
+
+ public void AddItem(DateTime start, DateTime end)
+ {
+ using var connection = new SqliteConnection(ConnectionString);
+ connection.Open();
+
+ var sql =
+ """
+ INSERT INTO coding_sessions
+ (StartTime, EndTime)
+ VALUES (@StartTime, @EndTime)
+ """;
+
+ var session = new CodingSession { StartTime = start, EndTime = end };
+
+ connection.Execute(sql, session);
+ }
+
+ public int DeleteItem(int id)
+ {
+ using var connection = new SqliteConnection(ConnectionString);
+ connection.Open();
+
+ var sql =
+ """
+ DELETE FROM coding_sessions
+ WHERE Id = @Id;
+ """;
+
+ var rowCount = connection.Execute(sql, new { Id = id });
+ return rowCount;
+ }
+
+ public int UpdateItem(int id, DateTime start, DateTime end)
+ {
+ using var connection = new SqliteConnection(ConnectionString);
+ connection.Open();
+
+ var sql =
+ """
+ UPDATE coding_sessions SET StartTime = @StartTime, EndTime = @EndTime
+ WHERE Id = @Id
+ """;
+ var session = new CodingSession { Id = id, StartTime = start, EndTime = end };
+
+ var rowCount = connection.Execute(sql, session);
+ return rowCount;
+ }
+
+ public List ViewItems()
+ {
+ using var connection = new SqliteConnection(ConnectionString);
+ connection.Open();
+
+ var sql = "SELECT * FROM coding_sessions";
+ var sessions = connection.Query(sql).ToList();
+
+ return sessions;
+ }
+}
diff --git a/CodingTracker.DzemalKurtic/CodingTracker.DzemalKurtic/Data/Database.cs b/CodingTracker.DzemalKurtic/CodingTracker.DzemalKurtic/Data/Database.cs
new file mode 100644
index 00000000..146c9a43
--- /dev/null
+++ b/CodingTracker.DzemalKurtic/CodingTracker.DzemalKurtic/Data/Database.cs
@@ -0,0 +1,24 @@
+using Dapper;
+using Microsoft.Data.Sqlite;
+
+namespace CodingTracker.DzemalKurtic.Data;
+
+internal static class Database
+{
+ public static void Initialize(string connectionString)
+ {
+ using var connection = new SqliteConnection(connectionString);
+ connection.Open();
+
+ var sql =
+ """
+ CREATE TABLE IF NOT EXISTS coding_sessions (
+ Id INTEGER PRIMARY KEY AUTOINCREMENT,
+ StartTime TEXT NOT NULL,
+ EndTime TEXT NOT NULL
+ );
+ """;
+
+ connection.Execute(sql);
+ }
+}
diff --git a/CodingTracker.DzemalKurtic/CodingTracker.DzemalKurtic/MenuAction.cs b/CodingTracker.DzemalKurtic/CodingTracker.DzemalKurtic/MenuAction.cs
new file mode 100644
index 00000000..a72d6f87
--- /dev/null
+++ b/CodingTracker.DzemalKurtic/CodingTracker.DzemalKurtic/MenuAction.cs
@@ -0,0 +1,9 @@
+namespace CodingTracker.DzemalKurtic;
+
+internal enum MenuAction
+{
+ ViewSessions,
+ AddSession,
+ UpdateSession,
+ DeleteSession
+}
diff --git a/CodingTracker.DzemalKurtic/CodingTracker.DzemalKurtic/Models/CodingSession.cs b/CodingTracker.DzemalKurtic/CodingTracker.DzemalKurtic/Models/CodingSession.cs
new file mode 100644
index 00000000..7d74c070
--- /dev/null
+++ b/CodingTracker.DzemalKurtic/CodingTracker.DzemalKurtic/Models/CodingSession.cs
@@ -0,0 +1,28 @@
+namespace CodingTracker.DzemalKurtic.Models;
+
+public class CodingSession
+{
+ public int Id { get; set; }
+ public DateTime StartTime { get; set; }
+ public DateTime EndTime { get; set; }
+
+ public TimeSpan Duration => EndTime - StartTime;
+
+ public CodingSession(int id, DateTime startTime, DateTime endTime)
+ {
+ Id = id;
+ StartTime = startTime;
+ EndTime = endTime;
+ }
+
+ public CodingSession(DateTime startTime, DateTime endTime)
+ {
+ StartTime = startTime;
+ EndTime = endTime;
+ }
+
+ public CodingSession()
+ {
+
+ }
+}
diff --git a/CodingTracker.DzemalKurtic/CodingTracker.DzemalKurtic/Program.cs b/CodingTracker.DzemalKurtic/CodingTracker.DzemalKurtic/Program.cs
new file mode 100644
index 00000000..1003bcf3
--- /dev/null
+++ b/CodingTracker.DzemalKurtic/CodingTracker.DzemalKurtic/Program.cs
@@ -0,0 +1,15 @@
+using CodingTracker.DzemalKurtic.Controllers;
+using CodingTracker.DzemalKurtic.Data;
+using CodingTracker.DzemalKurtic.Views;
+using Microsoft.Extensions.Configuration;
+
+IConfiguration config = new ConfigurationBuilder()
+ .AddJsonFile("appsettings.json")
+ .Build();
+
+var connectionString = config.GetConnectionString("DefaultConnection");
+
+Database.Initialize(connectionString);
+var controller = new CodingSessionController(connectionString);
+var ui = new UserInterface(controller);
+ui.MainMenu();
\ No newline at end of file
diff --git a/CodingTracker.DzemalKurtic/CodingTracker.DzemalKurtic/Properties/launchSettings.json b/CodingTracker.DzemalKurtic/CodingTracker.DzemalKurtic/Properties/launchSettings.json
new file mode 100644
index 00000000..f7bc628d
--- /dev/null
+++ b/CodingTracker.DzemalKurtic/CodingTracker.DzemalKurtic/Properties/launchSettings.json
@@ -0,0 +1,7 @@
+{
+ "profiles": {
+ "CodingTracker.DzemalKurtic": {
+ "commandName": "Project"
+ }
+ }
+}
\ No newline at end of file
diff --git a/CodingTracker.DzemalKurtic/CodingTracker.DzemalKurtic/README.md b/CodingTracker.DzemalKurtic/CodingTracker.DzemalKurtic/README.md
new file mode 100644
index 00000000..9589cc56
--- /dev/null
+++ b/CodingTracker.DzemalKurtic/CodingTracker.DzemalKurtic/README.md
@@ -0,0 +1,69 @@
+**Project requirements:**
+
+
+
+* Logging occurrence of a coding session
+* Users need to be bale to input the start date of the coding session
+* Users need to be bale to input the end date of the coding session
+* Duration of the sessions should be calculated from these values
+* App should use a real database
+* Users should be able to insert, delete, update and view their coding sessions
+* All input errors should be handled
+* Only Dapper should be used
+
+
+
+**How the App works?**
+
+
+
+When a user starts the app, he sees a menu with options:
+
+
+
+\- View all Sessions
+
+\- Add a Session
+
+\- Update a Session
+
+\- Delete a Session
+
+
+
+
+
+Choosing "View all Sessions" will show him a table with all the sessions that are recorded in the database.
+
+
+
+Choosing "Add a Session" will open another screen where he will be able to enter a start time in dd-mm-yy hh:mm format.
+
+After entering it he will be able to enter a end time in dd-mm-yy hh:mm format for the session.
+
+
+
+Choosing "Update a Session" will bring up a new screen where a user will be able to update an existing record.
+
+You must use Id of the record that exists in the database.
+
+After choosing an Id user will be able to enter new start time and time for the session.
+
+
+
+Choosing "Delete a Session" bring up a new screen where a use will be able to delete the sessions that are already recorded.
+
+You must use Id of the record that exists in the database.
+
+
+
+
+
+**Thoughts on the project:**
+
+
+
+This project made me use appsettings.json where I put the connection string to the Sqlite database. Dapper is easier to use than ADO.NET. For user interface I used spectre console, which has quite a few neat features. I really like the table feature. Also it's easier to get a list of options and a correct answer.
+
+Hardest part was figuring out how to convert between a model and a database because Sqlite doesn't have a date data type. I was using strings in the model because I couldn't get it to work with DateTime type. But then I was wondering how to calculate the duration of the session.
+
diff --git a/CodingTracker.DzemalKurtic/CodingTracker.DzemalKurtic/Validation.cs b/CodingTracker.DzemalKurtic/CodingTracker.DzemalKurtic/Validation.cs
new file mode 100644
index 00000000..e1068778
--- /dev/null
+++ b/CodingTracker.DzemalKurtic/CodingTracker.DzemalKurtic/Validation.cs
@@ -0,0 +1,40 @@
+using Spectre.Console;
+using System.Globalization;
+
+namespace CodingTracker.DzemalKurtic;
+
+internal static class Validation
+{
+ internal static DateTime ValidateDate(string dateInput, string dateName)
+ {
+ string date = dateInput;
+
+ while (!DateTime.TryParseExact(date, "dd-MM-yy HH:mm", CultureInfo.InvariantCulture, DateTimeStyles.None, out _))
+ {
+ AnsiConsole.WriteLine("Invalid date. (Format: dd-mm-yy hh:mm). Try again:");
+ date = AnsiConsole.Ask($"Enter the {dateName} date of the Coding Session: (Format: dd-mm-yy hh:mm)");
+ }
+
+ return DateTime.ParseExact(date, "dd-MM-yy HH:mm", CultureInfo.InvariantCulture);
+ }
+
+
+ internal static int ValidateId(int idInput)
+ {
+ int id = idInput;
+ while (Convert.ToInt32(id) < 0)
+ {
+ AnsiConsole.WriteLine("Number can't be negative. Try again.");
+ id = AnsiConsole.Ask("Please typt the Id od the item you want to update");
+ }
+
+ return Convert.ToInt32(id);
+ }
+
+ internal static bool ValidateTimespan(DateTime start, DateTime end)
+ {
+ bool isBigger = end <= start;
+ if (isBigger) AnsiConsole.WriteLine("End date can not be before start date");
+ return isBigger;
+ }
+}
diff --git a/CodingTracker.DzemalKurtic/CodingTracker.DzemalKurtic/Views/UserInterface.cs b/CodingTracker.DzemalKurtic/CodingTracker.DzemalKurtic/Views/UserInterface.cs
new file mode 100644
index 00000000..cf249e67
--- /dev/null
+++ b/CodingTracker.DzemalKurtic/CodingTracker.DzemalKurtic/Views/UserInterface.cs
@@ -0,0 +1,166 @@
+using CodingTracker.DzemalKurtic.Controllers;
+using Spectre.Console;
+
+namespace CodingTracker.DzemalKurtic.Views;
+
+internal class UserInterface
+{
+ private readonly CodingSessionController _codingSessionController;
+
+ internal UserInterface(CodingSessionController controller)
+ {
+ _codingSessionController = controller;
+ }
+
+ internal void MainMenu()
+ {
+ bool appRunning = true;
+ while (appRunning)
+ {
+ Console.Clear();
+
+ var choice = AnsiConsole.Prompt(
+ new SelectionPrompt()
+ .Title("What do you want to do?")
+ .AddChoices(Enum.GetValues())
+ .UseConverter(action => action switch
+ {
+ MenuAction.ViewSessions => "View all Sessions",
+ MenuAction.AddSession => "Add a Session",
+ MenuAction.UpdateSession => "Update a Session",
+ MenuAction.DeleteSession => "Delete a Session",
+ }));
+
+ switch (choice)
+ {
+ case MenuAction.ViewSessions:
+ ShowItems();
+ break;
+ case MenuAction.AddSession:
+ AddItem();
+ break;
+ case MenuAction.UpdateSession:
+ UpdateItem();
+ break;
+ case MenuAction.DeleteSession:
+ DeleteItem();
+ break;
+ }
+ }
+ }
+
+ internal void ShowItems()
+ {
+ var table = new Table();
+ table.Border(TableBorder.Rounded);
+
+ table.AddColumn("ID");
+ table.AddColumn("Start Time");
+ table.AddColumn("End Time");
+ table.AddColumn("Duration");
+
+ var sessions = _codingSessionController.ViewItems();
+
+ foreach (var session in sessions)
+ {
+ table.AddRow(
+ session.Id.ToString(),
+ $"[cyan]{session.StartTime:dd-MM-yyyy HH:mm}[/]",
+ $"[yellow]{session.EndTime:dd-MM-yyyy HH:mm}[/]",
+ $"[green]{session.Duration.Hours} hours {session.Duration.Minutes} minutes[/]"
+ );
+ }
+
+ AnsiConsole.Write(table);
+ AnsiConsole.MarkupLine("Press Any Key to Continue.");
+ Console.ReadKey();
+ }
+
+ internal void AddItem()
+ {
+ var start = getDate("start");
+ var startDate = Validation.ValidateDate(start, "start");
+
+ var end = getDate("end");
+ var endDate = Validation.ValidateDate(end, "end");
+
+ var isBigger = Validation.ValidateTimespan(startDate, endDate);
+
+ if (!isBigger)
+ {
+ _codingSessionController.AddItem(startDate, endDate);
+ AnsiConsole.MarkupLine("Press Any Key to Continue.");
+ Console.ReadKey();
+ }
+ else
+ {
+ AnsiConsole.MarkupLine("Press Any Key to Continue.");
+ Console.ReadKey();
+
+ Console.Clear();
+ AddItem();
+ }
+
+ }
+
+ internal void UpdateItem()
+ {
+ ShowItems();
+
+ var itemId = getId("update");
+ var id = Validation.ValidateId(itemId);
+
+ var start = getDate("start");
+ var startDate = Validation.ValidateDate(start, "start");
+
+ var end = getDate("end");
+ var endDate = Validation.ValidateDate(end, "end");
+
+ var rowCount = _codingSessionController.UpdateItem(itemId, startDate, endDate);
+ if (rowCount == 0)
+ {
+ AnsiConsole.MarkupLine($"Session with id {id} doesn't exist.");
+ AnsiConsole.MarkupLine("Press Any Key to Continue.");
+ Console.ReadKey();
+ Console.Clear();
+ MainMenu();
+ }else
+ {
+ AnsiConsole.MarkupLine("Press Any Key to Continue.");
+ Console.ReadKey();
+ }
+ }
+
+ internal void DeleteItem()
+ {
+ ShowItems();
+
+ var itemId = getId("delete");
+ var id = Validation.ValidateId(itemId);
+
+ var rowCount = _codingSessionController.DeleteItem(id);
+ if (rowCount == 0)
+ {
+ AnsiConsole.MarkupLine($"Session with id {id} doesn't exist.\n");
+ AnsiConsole.MarkupLine("Press Any Key to Continue.\n");
+ Console.ReadKey();
+ Console.Clear();
+ DeleteItem();
+ }
+ else
+ {
+ AnsiConsole.MarkupLine("Press Any Key to Continue.");
+ Console.ReadKey();
+ }
+ }
+
+ private string getDate(string time)
+ {
+ return AnsiConsole.Ask($"Enter the {time} time of the Coding Session: (Format: dd-mm-yy HH:mm)");
+ }
+
+ private int getId(string action)
+ {
+ return AnsiConsole.Ask($"Please typt the Id od the item you want to {action}");
+ }
+}
diff --git a/CodingTracker.DzemalKurtic/CodingTracker.DzemalKurtic/appsettings.json b/CodingTracker.DzemalKurtic/CodingTracker.DzemalKurtic/appsettings.json
new file mode 100644
index 00000000..04d1f510
--- /dev/null
+++ b/CodingTracker.DzemalKurtic/CodingTracker.DzemalKurtic/appsettings.json
@@ -0,0 +1,5 @@
+{
+ "ConnectionStrings": {
+ "DefaultConnection" : "Data Source=coding-Tracker.db"
+ }
+}
\ No newline at end of file