From 3f21875adb6f897159611c2e7a9206bd94800cae Mon Sep 17 00:00:00 2001
From: Myhos0 <160954026+Myhos0@users.noreply.github.com>
Date: Sun, 1 Mar 2026 23:08:39 -0500
Subject: [PATCH 1/4] Initialize README with project details
Add detailed project description, requirements, features, challenges, learnings, and resources used.
---
README.md | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 103 insertions(+)
create mode 100644 README.md
diff --git a/README.md b/README.md
new file mode 100644
index 000000000..12bf9393c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,103 @@
+# CodingTracker
+My fourth console project at C# Academy.
+A console application where we track code sessions by how long they lasted.
+# Requirements
+
+- [x] This application has the same requirements as the previous project, except that now you'll be logging your daily coding time.
+- [x] To show the data on the console, you should use the Spectre.Console library.
+- [x] You're required to have separate classes in different files (i.e. UserInput.cs, Validation.cs, CodingController.cs)
+- [x] You should tell the user the specific format you want the date and time to be logged and not allow any other format.
+- [x] You'll need to create a configuration file called appsettings.json, which will contain your database path and connection strings (and any other configs you might need).
+- [x] You'll need to create a CodingSession class in a separate file. It will contain the properties of your coding session: Id, StartTime, EndTime, Duration. When reading from the database, you can't use an anonymous object, you have to read your table into a List of CodingSession.
+- [x] The user shouldn't input the duration of the session. It should be calculated based on the Start and End times
+- [x] The user should be able to input the start and end times manually.
+- [x] You need to use Dapper ORM for the data access instead of ADO.NET. (This requirement was included in Feb/2024)
+- [x] Follow the DRY Principle, and avoid code repetition.
+- [x] Don't forget the ReadMe explaining your thought process.
+
+# Features
+
+- **SQLite database connection**
+ - The program uses SQLite to create a database to store and read information.
+ - When the program is started, the application generates the database and table necessary for its operation.
+ - When the application is launched, the program inserts 100 test values (2023–2026).
+
+- **Spectre Console UI**
+ - The application uses a Spectre Console UI where we can navigate using the keyboard.
+
+
+
+
+
+ - The first menu shows two options:
+ - Stopwatch
+ - ConsultMenu
+## Stopwatch
+
+- The Stopwatch menu allows us to time the duration of a coding session and stop with the key ENTER, the coding session is saved when the stopwatch stops or the aplication is closes.
+- The duration of the session must be greater than five minutes to save in both cases.
+- **Examples:**
+
+
+
+
+
+
+
+
+
+## ConsultMenu
+
+
+
+
+
+- The consult menu shows basic CRUD operations and a fifth option to perform filtered queries.
+
+### PeriodMenu
+
+
+
+
+
+- The period menu shows five options to perform different queries:
+ - Filter by day, week, month, and year
+ - Results are ordered by:
+ - Date (ascending)
+ - Duration (ascending)
+
+- **Example**
+
+
+
+
+
+# Chanllenges
+- One of the difficulties I encountered in completing this project was implementing the stopwatch, mainly due to two factors:
+ - Display the stopwatch correctly on the live screen, spectre console made this point easy.
+ - That the session will be saved correctly when the stopwatch is stopped or the application is closed abruptly.
+- Add filtering of sessions by date, mainly by week, as we need to learn how Sqlite stores dates in order to filter them.
+- Working with times and dates was complicated at first, but once I understood how TimeSpan and DateTime objects work, it became clearer how to implement them.
+
+# What I have learned
+- How TimeSpan and DateTime work in C# and their methods for performing different operations.
+- Add a repository to query the database.
+- Use Spectre Console to display live information that is constantly updated.
+- How to handle unexpected application closure without losing information.
+- Add more details to the application to make it look better.
+
+# Areas to improve
+- Improve the way classes are organised to make them easier to understand, use folders to improve structure.
+- Implement better methods, objects, or classes that help control exceptions if the application is forced to close.
+- Learn more about how to write SQL queries that are better suited to what I want to do.
+- How to correctly configure the database settings from a .json file and locate the database in a project folder of my choice.
+ - For example, having a folder where the database is stored and that folder is in the root of the project where the .cs classes are stored. (I don't know if this is good practice.)
+
+# Resourced Used
+- C# Separation of Concerns article: https://www.thecsharpacademy.com/article/30005/separation-of-concerns-csharp
+- Specter Console Documentation: https://spectreconsole.net/
+- Learn Dapper: https://www.learndapper.com/
+- SQlite Web: https://www.sqlite.org/
+- Microsoft documentation for DateTime: https://learn.microsoft.com/en-us/dotnet/api/system.datetime?view=net-10.0
+- Microsoft documentation for TimeSpan: https://learn.microsoft.com/es-es/dotnet/api/system.timespan?view=net-8.0
+- Microsoft documentation for IEnumerable https://learn.microsoft.com/es-es/dotnet/api/system.collections.ienumerable?view=net-8.0
From d3fa4e6be141dfb503008e81fd2eef00b4e7e13c Mon Sep 17 00:00:00 2001
From: Miguel Angel Ospina Paez
Date: Sun, 1 Mar 2026 23:30:14 -0500
Subject: [PATCH 2/4] Project Finish!!!
---
CodingTracker.Myhos0/CodingSession.cs | 11 +
.../CodingSessionRepository.cs | 77 ++++
CodingTracker.Myhos0/CodingSessionSeeder.cs | 48 +++
CodingTracker.Myhos0/CodingTracker.cs | 383 ++++++++++++++++++
CodingTracker.Myhos0/CodingTracker.csproj | 24 ++
CodingTracker.Myhos0/CodingTracker.sln | 25 ++
CodingTracker.Myhos0/DataBase.cs | 34 ++
CodingTracker.Myhos0/Enum.cs | 32 ++
CodingTracker.Myhos0/Program.cs | 36 ++
README.md => CodingTracker.Myhos0/README.md | 0
CodingTracker.Myhos0/Stopwatch.cs | 145 +++++++
CodingTracker.Myhos0/ValidateInput.cs | 172 ++++++++
CodingTracker.Myhos0/appsettings.json | 5 +
13 files changed, 992 insertions(+)
create mode 100644 CodingTracker.Myhos0/CodingSession.cs
create mode 100644 CodingTracker.Myhos0/CodingSessionRepository.cs
create mode 100644 CodingTracker.Myhos0/CodingSessionSeeder.cs
create mode 100644 CodingTracker.Myhos0/CodingTracker.cs
create mode 100644 CodingTracker.Myhos0/CodingTracker.csproj
create mode 100644 CodingTracker.Myhos0/CodingTracker.sln
create mode 100644 CodingTracker.Myhos0/DataBase.cs
create mode 100644 CodingTracker.Myhos0/Enum.cs
create mode 100644 CodingTracker.Myhos0/Program.cs
rename README.md => CodingTracker.Myhos0/README.md (100%)
create mode 100644 CodingTracker.Myhos0/Stopwatch.cs
create mode 100644 CodingTracker.Myhos0/ValidateInput.cs
create mode 100644 CodingTracker.Myhos0/appsettings.json
diff --git a/CodingTracker.Myhos0/CodingSession.cs b/CodingTracker.Myhos0/CodingSession.cs
new file mode 100644
index 000000000..5942771a9
--- /dev/null
+++ b/CodingTracker.Myhos0/CodingSession.cs
@@ -0,0 +1,11 @@
+namespace CodingTrackerProgram;
+
+internal class CodingSession
+{
+ public int Id { get; set; }
+ public DateTime Date { get; set; }
+ public string StartTime { get; set; }
+ public string EndTime { get; set; }
+ public string Duration { get; set; }
+}
+
diff --git a/CodingTracker.Myhos0/CodingSessionRepository.cs b/CodingTracker.Myhos0/CodingSessionRepository.cs
new file mode 100644
index 000000000..4971f99d5
--- /dev/null
+++ b/CodingTracker.Myhos0/CodingSessionRepository.cs
@@ -0,0 +1,77 @@
+using Dapper;
+
+namespace CodingTrackerProgram;
+
+internal class CodingSessionRepository
+{
+ private readonly DataBase _database;
+
+ public CodingSessionRepository(DataBase dataBase)
+ {
+ _database = dataBase;
+ }
+
+ public void Insert(CodingSession session)
+ {
+ const string SQL = @"INSERT INTO CodingSession(Date,StartTime,EndTime,Duration) Values(@Date,@StartTime,@EndTime,@Duration)";
+
+ using var connection = _database.GetConnection();
+ connection.Execute(SQL, session);
+ }
+
+ public IEnumerable GetAll()
+ {
+ const string SQL = @"SELECT Id,Date,StartTime,EndTime,Duration FROM CodingSession ORDER BY Date ASC";
+
+ using var connection = _database.GetConnection();
+ return connection.Query(SQL);
+ }
+
+ public void Delete(int id)
+ {
+ const string SQL = @"DElETE FROM CodingSession WHERE Id = @Id";
+
+ using var connection = _database.GetConnection();
+ int rows = connection.Execute(SQL, new { Id = id });
+
+ if (rows == 0)
+ {
+ throw new Exception("No records Found.");
+ }
+ }
+
+ public void Update(CodingSession session)
+ {
+ const string SQL = @"UPDATE CodingSession SET Date = @Date, StartTime = @StartTime, EndTime = @EndTime, Duration = @Duration WHERE Id = @Id";
+
+ using var connection = _database.GetConnection();
+ int rows = connection.Execute(SQL, session);
+
+ if (rows == 0)
+ throw new Exception("No record found to update.");
+ }
+
+ public bool SessionExist(int id)
+ {
+ const string SQL = "SELECT 1 FROM CodingSession WHERE Id = @Id LIMIT 1";
+
+ using var connection = _database.GetConnection();
+ return connection.ExecuteScalar(SQL, new { Id = id }) != null;
+ }
+
+ public IEnumerable GetSessionsByDateRange(string start, string end)
+ {
+ const string SQL = @"SELECT * FROM CodingSession WHERE Date BETWEEN @Start AND @End ORDER BY Date ASC, Duration ASC;";
+
+ using var connection = _database.GetConnection();
+ return connection.Query(SQL, new { Start = start, End = end });
+ }
+
+ public bool HasAnySessions()
+ {
+ const string SQL = "SELECT 1 FROM CodingSession LIMIT 1";
+
+ using var connection = _database.GetConnection();
+ return connection.ExecuteScalar(SQL) != null;
+ }
+}
\ No newline at end of file
diff --git a/CodingTracker.Myhos0/CodingSessionSeeder.cs b/CodingTracker.Myhos0/CodingSessionSeeder.cs
new file mode 100644
index 000000000..46b03370a
--- /dev/null
+++ b/CodingTracker.Myhos0/CodingSessionSeeder.cs
@@ -0,0 +1,48 @@
+using CodingTrackerProgram;
+using Spectre.Console;
+using System;
+
+namespace CodingTrackerProgram;
+
+internal class CodingSessionSeeder
+{
+ private readonly CodingSessionRepository repository;
+ private readonly Random random = new();
+
+ public CodingSessionSeeder(DataBase dataBase)
+ {
+ repository = new CodingSessionRepository(dataBase);
+ }
+
+ public void Seed(int numberOfSessions)
+ {
+ for (int i = 0; i < numberOfSessions; i++)
+ {
+ CodingSession session = GenerateRandomSession();
+ repository.Insert(session);
+ }
+ }
+
+ private CodingSession GenerateRandomSession()
+ {
+ DateTime startRange = DateTime.Today.AddYears(-3);
+ int range = (DateTime.Today - startRange).Days;
+
+ DateTime date = startRange.AddDays(random.Next(0, range));
+
+ TimeSpan start = TimeSpan.FromMinutes(random.Next(6 * 60, 22 * 60));
+
+ TimeSpan duration = TimeSpan.FromMinutes(random.Next(60,241));
+
+ TimeSpan end = start + duration;
+
+ return new CodingSession
+ {
+ Date = date,
+ StartTime = start.ToString(@"hh\:mm\:ss"),
+ EndTime = end.ToString(@"hh\:mm\:ss"),
+ Duration = duration.ToString(@"hh\:mm\:ss")
+ };
+ }
+}
+
diff --git a/CodingTracker.Myhos0/CodingTracker.cs b/CodingTracker.Myhos0/CodingTracker.cs
new file mode 100644
index 000000000..7231eef8f
--- /dev/null
+++ b/CodingTracker.Myhos0/CodingTracker.cs
@@ -0,0 +1,383 @@
+using Spectre.Console;
+using CodingTrackerProgram.Enums;
+using System.Globalization;
+
+namespace CodingTrackerProgram;
+
+internal class CodingTracker
+{
+ private readonly CodingSession codingSession = new();
+ private readonly CodingSessionRepository codingSessionRepository;
+ private readonly Stopwatch stopwatch;
+ private readonly ValidateInput validateInput = new();
+
+ public CodingTracker(DataBase dataBase)
+ {
+ codingSessionRepository = new CodingSessionRepository(dataBase);
+ stopwatch = new Stopwatch(dataBase);
+ }
+
+ public void MainMenu()
+ {
+ bool open = true;
+
+ while (open)
+ {
+ Console.Clear();
+
+ var menuOption = AnsiConsole.Prompt(
+ new SelectionPrompt