From 624678f86651c462d9c13d84ce581210961786f5 Mon Sep 17 00:00:00 2001 From: jzhartman Date: Wed, 20 Aug 2025 21:17:54 -0400 Subject: [PATCH 01/57] Commit of folder structure Attempte at following MVC patter with Repositories and Connection Factories --- .../CodingSessionController.cs | 28 +++++++++++ .../CodingTracker.Controller.csproj | 17 +++++++ .../CodingTracker.Data.csproj | 18 ++++++++ .../CodingTracker.Data/DatabaseInitializer.cs | 46 +++++++++++++++++++ .../Interfaces/ICodingSessionRepository.cs | 9 ++++ .../Interfaces/IDatabaseInitializer.cs | 7 +++ .../Interfaces/ISqliteConnectionFactory.cs | 9 ++++ .../Repositories/CodingSessionRepository.cs | 30 ++++++++++++ .../SqliteConnectionFactory.cs | 28 +++++++++++ .../CodingTracker.Models.csproj | 14 ++++++ .../Entities/CodingSession.cs | 30 ++++++++++++ .../CodingTracker.View.csproj | 9 ++++ .../CodingTracker/App.config | 6 +++ .../CodingTracker.Console.csproj | 30 ++++++++++++ .../CodingTracker/Program.cs | 26 +++++++++++ .../CodingTracker/Startup.cs | 25 ++++++++++ .../codingTracker.jzhartman.sln | 43 +++++++++++++++++ 17 files changed, 375 insertions(+) create mode 100644 codingTracker.jzhartman/CodingTracker.Controller/CodingSessionController.cs create mode 100644 codingTracker.jzhartman/CodingTracker.Controller/CodingTracker.Controller.csproj create mode 100644 codingTracker.jzhartman/CodingTracker.Data/CodingTracker.Data.csproj create mode 100644 codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs create mode 100644 codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs create mode 100644 codingTracker.jzhartman/CodingTracker.Data/Interfaces/IDatabaseInitializer.cs create mode 100644 codingTracker.jzhartman/CodingTracker.Data/Interfaces/ISqliteConnectionFactory.cs create mode 100644 codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs create mode 100644 codingTracker.jzhartman/CodingTracker.Data/SqliteConnectionFactory.cs create mode 100644 codingTracker.jzhartman/CodingTracker.Models/CodingTracker.Models.csproj create mode 100644 codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSession.cs create mode 100644 codingTracker.jzhartman/CodingTracker.View/CodingTracker.View.csproj create mode 100644 codingTracker.jzhartman/CodingTracker/App.config create mode 100644 codingTracker.jzhartman/CodingTracker/CodingTracker.Console.csproj create mode 100644 codingTracker.jzhartman/CodingTracker/Program.cs create mode 100644 codingTracker.jzhartman/CodingTracker/Startup.cs create mode 100644 codingTracker.jzhartman/codingTracker.jzhartman.sln diff --git a/codingTracker.jzhartman/CodingTracker.Controller/CodingSessionController.cs b/codingTracker.jzhartman/CodingTracker.Controller/CodingSessionController.cs new file mode 100644 index 000000000..59bdaa87b --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Controller/CodingSessionController.cs @@ -0,0 +1,28 @@ +using CodingTracker.Data.Repositories; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CodingTracker.Controller +{ + internal class CodingSessionController + { + private readonly CodingSessionRepository _repository; + + public CodingSessionController(CodingSessionRepository repository) + { + _repository = repository; + } + + //Method to run repo.GetAll then pass data up to View + + + + // Methods for calling repo methods + // Used to handle the data and create lists + // Can contain the Console.WriteLine statements -- these will be called in the View later often + + } +} diff --git a/codingTracker.jzhartman/CodingTracker.Controller/CodingTracker.Controller.csproj b/codingTracker.jzhartman/CodingTracker.Controller/CodingTracker.Controller.csproj new file mode 100644 index 000000000..b4af0158c --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Controller/CodingTracker.Controller.csproj @@ -0,0 +1,17 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + diff --git a/codingTracker.jzhartman/CodingTracker.Data/CodingTracker.Data.csproj b/codingTracker.jzhartman/CodingTracker.Data/CodingTracker.Data.csproj new file mode 100644 index 000000000..7b45ce86a --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Data/CodingTracker.Data.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + diff --git a/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs b/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs new file mode 100644 index 000000000..139dfd098 --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Data.Sqlite; +using Dapper; +using CodingTracker.Data.Interfaces; + +namespace CodingTracker.Data +{ + public class DatabaseInitializer : IDatabaseInitializer + { + private readonly SqliteConnectionFactory _connectionFactory; + + public DatabaseInitializer(SqliteConnectionFactory connectionfactory) + { + _connectionFactory = connectionfactory; + } + + public void Initialize() + { + CreateTable(); + SeedData(); + } + + private void CreateTable() + { + using var connection = _connectionFactory.CreateConnection(); + + var command = connection.CreateCommand(); + command.CommandText = " SQL HERE"; + command.ExecuteNonQuery(); + } + + private void SeedData() + { + using var connection = _connectionFactory.CreateConnection(); + + var command = connection.CreateCommand(); + command.CommandText = " SQL HERE"; + command.ExecuteNonQuery(); + } + + } +} diff --git a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs new file mode 100644 index 000000000..9bb0fb9eb --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs @@ -0,0 +1,9 @@ +using CodingTracker.Core.Models; + +namespace CodingTracker.Data.Interfaces +{ + public interface ICodingSessionRepository + { + List GetAll(); + } +} \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/IDatabaseInitializer.cs b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/IDatabaseInitializer.cs new file mode 100644 index 000000000..9f7c57943 --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/IDatabaseInitializer.cs @@ -0,0 +1,7 @@ +namespace CodingTracker.Data.Interfaces +{ + public interface IDatabaseInitializer + { + void Initialize(); + } +} \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ISqliteConnectionFactory.cs b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ISqliteConnectionFactory.cs new file mode 100644 index 000000000..8e21fe809 --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ISqliteConnectionFactory.cs @@ -0,0 +1,9 @@ +using System.Data; + +namespace CodingTracker.Data.Interfaces +{ + public interface ISqliteConnectionFactory + { + IDbConnection CreateConnection(); + } +} \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs new file mode 100644 index 000000000..3d6c2b179 --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs @@ -0,0 +1,30 @@ +using CodingTracker.Core.Models; +using CodingTracker.Data.Interfaces; +using Dapper; + +namespace CodingTracker.Data.Repositories +{ + public class CodingSessionRepository : ICodingSessionRepository + //: ICodingSessionRepository + { + private readonly SqliteConnectionFactory _connectionFactory; + + public CodingSessionRepository(SqliteConnectionFactory connectionFactory) + { + _connectionFactory = connectionFactory; + } + + // Sample method + public List GetAll() + { + using var connection = _connectionFactory.CreateConnection(); + List sessions = connection.Query("Select * from CodingSessions order by StartDate").ToList(); + return sessions; + } + + // Method here using the following snippet + // using var connection = connectionFactory.CreateConnection(); + // connection.Open(); + // insert Dapper query here + } +} diff --git a/codingTracker.jzhartman/CodingTracker.Data/SqliteConnectionFactory.cs b/codingTracker.jzhartman/CodingTracker.Data/SqliteConnectionFactory.cs new file mode 100644 index 000000000..ed8c50ac8 --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Data/SqliteConnectionFactory.cs @@ -0,0 +1,28 @@ +using CodingTracker.Data.Interfaces; +using Microsoft.Data.Sqlite; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CodingTracker.Data +{ + public class SqliteConnectionFactory : ISqliteConnectionFactory + { + private readonly string _connectionString; + + public SqliteConnectionFactory(string connectionString) + { + _connectionString = connectionString; + } + + public IDbConnection CreateConnection() + { + var connection = new SqliteConnection(_connectionString); + connection.Open(); + return connection; + } + } +} diff --git a/codingTracker.jzhartman/CodingTracker.Models/CodingTracker.Models.csproj b/codingTracker.jzhartman/CodingTracker.Models/CodingTracker.Models.csproj new file mode 100644 index 000000000..787cc1f71 --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Models/CodingTracker.Models.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + enable + enable + + + + + + + + diff --git a/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSession.cs b/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSession.cs new file mode 100644 index 000000000..dad9df0b5 --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSession.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CodingTracker.Core.Models +{ + public class CodingSession + { + public int Id { get; set; } + public string StartTime { get; set; } + public string EndTime { get; set; } + public string Duration { get; set; } + + public CodingSession(int id, string startTime, string endTime, string duration) + { + Id = id; + StartTime = startTime; + EndTime = endTime; + GetDuration(); + } + + private void GetDuration() + { + var duration = DateTime.Parse(EndTime).Subtract(DateTime.Parse(StartTime)); + Duration = duration.ToString("yyyy-MM-dd HH:mm:ss"); + } + } +} diff --git a/codingTracker.jzhartman/CodingTracker.View/CodingTracker.View.csproj b/codingTracker.jzhartman/CodingTracker.View/CodingTracker.View.csproj new file mode 100644 index 000000000..fa71b7ae6 --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.View/CodingTracker.View.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/codingTracker.jzhartman/CodingTracker/App.config b/codingTracker.jzhartman/CodingTracker/App.config new file mode 100644 index 000000000..44a976751 --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/codingTracker.jzhartman/CodingTracker/CodingTracker.Console.csproj b/codingTracker.jzhartman/CodingTracker/CodingTracker.Console.csproj new file mode 100644 index 000000000..05d42e6dd --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker/CodingTracker.Console.csproj @@ -0,0 +1,30 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + + + + + PreserveNewest + + + + + + + + diff --git a/codingTracker.jzhartman/CodingTracker/Program.cs b/codingTracker.jzhartman/CodingTracker/Program.cs new file mode 100644 index 000000000..bac36c0b1 --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker/Program.cs @@ -0,0 +1,26 @@ +using CodingTracker.Console; +using CodingTracker.Data; +using CodingTracker.Data.Interfaces; +using Microsoft.Extensions.DependencyInjection; +using System.Collections.Specialized; +using System.Configuration; + +namespace CodingTracker; +internal class Program +{ + static void Main(string[] args) + { + //var connectionString = ConfigurationManager.AppSettings.Get("connectionString"); + //var connectionFactory = new SqliteConnectionFactory(connectionString); + + // Create a view and pass in the connection factory? + // var view = new CodingSessionView(connectionString); + // view.Run(); + + var serviceProvider = Startup.ConfigureServices(); + var dbInitializer = serviceProvider.GetRequiredService(); + dbInitializer.Initialize(); + + + } +} diff --git a/codingTracker.jzhartman/CodingTracker/Startup.cs b/codingTracker.jzhartman/CodingTracker/Startup.cs new file mode 100644 index 000000000..5ae46edbc --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker/Startup.cs @@ -0,0 +1,25 @@ +using CodingTracker.Data; +using CodingTracker.Data.Interfaces; +using CodingTracker.Data.Repositories; +using Microsoft.Extensions.DependencyInjection; +using System.Configuration; + +namespace CodingTracker.Console +{ + internal static class Startup + { + public static IServiceProvider ConfigureServices() + { + var services = new ServiceCollection(); + var connectionString = ConfigurationManager.AppSettings.Get("connectionString"); + + services.AddSingleton(provider => new SqliteConnectionFactory(connectionString)); + services.AddSingleton(); + + services.AddTransient(); + + return services.BuildServiceProvider(); + } + + } +} diff --git a/codingTracker.jzhartman/codingTracker.jzhartman.sln b/codingTracker.jzhartman/codingTracker.jzhartman.sln new file mode 100644 index 000000000..6aef9f532 --- /dev/null +++ b/codingTracker.jzhartman/codingTracker.jzhartman.sln @@ -0,0 +1,43 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36310.24 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodingTracker.Console", "CodingTracker\CodingTracker.Console.csproj", "{E718A170-B8DA-40F4-965C-5C112E649C99}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodingTracker.Controller", "CodingTracker.Controller\CodingTracker.Controller.csproj", "{350388E8-9796-437A-84E2-01BC1F133CF8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodingTracker.Data", "CodingTracker.Data\CodingTracker.Data.csproj", "{BCC76984-3A99-4DD8-97A3-216FCB166F02}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodingTracker.Models", "CodingTracker.Models\CodingTracker.Models.csproj", "{AE0E7C34-E681-47F8-B016-4BBD48955078}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E718A170-B8DA-40F4-965C-5C112E649C99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E718A170-B8DA-40F4-965C-5C112E649C99}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E718A170-B8DA-40F4-965C-5C112E649C99}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E718A170-B8DA-40F4-965C-5C112E649C99}.Release|Any CPU.Build.0 = Release|Any CPU + {350388E8-9796-437A-84E2-01BC1F133CF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {350388E8-9796-437A-84E2-01BC1F133CF8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {350388E8-9796-437A-84E2-01BC1F133CF8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {350388E8-9796-437A-84E2-01BC1F133CF8}.Release|Any CPU.Build.0 = Release|Any CPU + {BCC76984-3A99-4DD8-97A3-216FCB166F02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BCC76984-3A99-4DD8-97A3-216FCB166F02}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BCC76984-3A99-4DD8-97A3-216FCB166F02}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BCC76984-3A99-4DD8-97A3-216FCB166F02}.Release|Any CPU.Build.0 = Release|Any CPU + {AE0E7C34-E681-47F8-B016-4BBD48955078}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AE0E7C34-E681-47F8-B016-4BBD48955078}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AE0E7C34-E681-47F8-B016-4BBD48955078}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AE0E7C34-E681-47F8-B016-4BBD48955078}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {CF0F147B-48DF-4950-AB1E-F0DB27899A6B} + EndGlobalSection +EndGlobal From 170518a062c1b7607e04bbe2e0b04d1bd805c7df Mon Sep 17 00:00:00 2001 From: jzhartman Date: Thu, 21 Aug 2025 20:50:53 -0400 Subject: [PATCH 02/57] Builds. DI working. Created DB with table and seeds some starter data --- .../CodingTracker.Data/DatabaseInitializer.cs | 19 +++++++++++++++---- .../Repositories/CodingSessionRepository.cs | 4 ++-- .../CodingTracker/App.config | 2 +- .../CodingTracker.Console.csproj | 1 + .../CodingTracker/Program.cs | 13 +++---------- 5 files changed, 22 insertions(+), 17 deletions(-) diff --git a/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs b/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs index 139dfd098..f377b492d 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs @@ -11,9 +11,9 @@ namespace CodingTracker.Data { public class DatabaseInitializer : IDatabaseInitializer { - private readonly SqliteConnectionFactory _connectionFactory; + private readonly ISqliteConnectionFactory _connectionFactory; - public DatabaseInitializer(SqliteConnectionFactory connectionfactory) + public DatabaseInitializer(ISqliteConnectionFactory connectionfactory) { _connectionFactory = connectionfactory; } @@ -29,7 +29,12 @@ private void CreateTable() using var connection = _connectionFactory.CreateConnection(); var command = connection.CreateCommand(); - command.CommandText = " SQL HERE"; + command.CommandText = @"create table if not exists CodingSessions( + Id integer primary key not null, + StartTime text not null, + EndTime text not null, + Duration real not null + )"; command.ExecuteNonQuery(); } @@ -38,7 +43,13 @@ private void SeedData() using var connection = _connectionFactory.CreateConnection(); var command = connection.CreateCommand(); - command.CommandText = " SQL HERE"; + command.CommandText = @"insert into CodingSessions(StartTime, EndTime, Duration) + Values ('2025-08-01 21:00:00', '2025-08-01 23:30:00', 150), + ('2025-08-02 21:15:00', '2025-08-02 23:30:00', 135), + ('2025-08-03 22:00:00', '2025-08-03 23:45:00', 105), + ('2025-08-04 21:00:00', '2025-08-04 23:30:00', 150), + ('2025-08-05 21:00:00', '2025-08-05 23:30:00', 150); + "; command.ExecuteNonQuery(); } diff --git a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs index 3d6c2b179..7f468fc5f 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs @@ -7,9 +7,9 @@ namespace CodingTracker.Data.Repositories public class CodingSessionRepository : ICodingSessionRepository //: ICodingSessionRepository { - private readonly SqliteConnectionFactory _connectionFactory; + private readonly ISqliteConnectionFactory _connectionFactory; - public CodingSessionRepository(SqliteConnectionFactory connectionFactory) + public CodingSessionRepository(ISqliteConnectionFactory connectionFactory) { _connectionFactory = connectionFactory; } diff --git a/codingTracker.jzhartman/CodingTracker/App.config b/codingTracker.jzhartman/CodingTracker/App.config index 44a976751..807eb4cdb 100644 --- a/codingTracker.jzhartman/CodingTracker/App.config +++ b/codingTracker.jzhartman/CodingTracker/App.config @@ -1,6 +1,6 @@  - + diff --git a/codingTracker.jzhartman/CodingTracker/CodingTracker.Console.csproj b/codingTracker.jzhartman/CodingTracker/CodingTracker.Console.csproj index 05d42e6dd..d05e7e018 100644 --- a/codingTracker.jzhartman/CodingTracker/CodingTracker.Console.csproj +++ b/codingTracker.jzhartman/CodingTracker/CodingTracker.Console.csproj @@ -9,6 +9,7 @@ + diff --git a/codingTracker.jzhartman/CodingTracker/Program.cs b/codingTracker.jzhartman/CodingTracker/Program.cs index bac36c0b1..2537ea067 100644 --- a/codingTracker.jzhartman/CodingTracker/Program.cs +++ b/codingTracker.jzhartman/CodingTracker/Program.cs @@ -1,4 +1,5 @@ using CodingTracker.Console; +using SQLitePCL; using CodingTracker.Data; using CodingTracker.Data.Interfaces; using Microsoft.Extensions.DependencyInjection; @@ -10,17 +11,9 @@ internal class Program { static void Main(string[] args) { - //var connectionString = ConfigurationManager.AppSettings.Get("connectionString"); - //var connectionFactory = new SqliteConnectionFactory(connectionString); - - // Create a view and pass in the connection factory? - // var view = new CodingSessionView(connectionString); - // view.Run(); - + Batteries.Init(); var serviceProvider = Startup.ConfigureServices(); - var dbInitializer = serviceProvider.GetRequiredService(); - dbInitializer.Initialize(); - + serviceProvider.GetRequiredService().Initialize(); } } From dd8083e41915fdc832f4ffe265f495fe0b047a08 Mon Sep 17 00:00:00 2001 From: jzhartman Date: Thu, 21 Aug 2025 23:00:47 -0400 Subject: [PATCH 03/57] Reorganized project dependencies. DI works. Can query and print all records from db. --- .../CodingSessionController.cs | 28 --------------- .../CodingTracker.Controller.csproj | 7 ++-- .../Interfaces/IMenuController.cs | 7 ++++ .../MenuController.cs | 27 ++++++++++++++ .../CodingTracker.Data/DatabaseInitializer.cs | 12 +++---- .../Interfaces/ICodingSessionRepository.cs | 2 +- .../Repositories/CodingSessionRepository.cs | 6 ++-- .../CodingTracker.Models.csproj | 1 - .../Entities/CodingSession.cs | 9 ++--- .../CodingSessionService.cs | 35 +++++++++++++++++++ .../CodingTracker.Services.csproj | 14 ++++++++ .../Interfaces/ICodingSessionService.cs | 9 +++++ .../CodingTracker.Views/CodingSessionView.cs | 29 +++++++++++++++ .../CodingTracker.Views.csproj | 13 +++++++ .../CodingTracker/App.config | 1 + ...csproj => CodingTracker.ConsoleApp.csproj} | 6 ++-- .../CodingTracker/Program.cs | 9 +++-- .../CodingTracker/Startup.cs | 15 ++++++-- .../codingTracker.jzhartman.sln | 14 +++++++- 19 files changed, 187 insertions(+), 57 deletions(-) delete mode 100644 codingTracker.jzhartman/CodingTracker.Controller/CodingSessionController.cs create mode 100644 codingTracker.jzhartman/CodingTracker.Controller/Interfaces/IMenuController.cs create mode 100644 codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs create mode 100644 codingTracker.jzhartman/CodingTracker.Services/CodingSessionService.cs create mode 100644 codingTracker.jzhartman/CodingTracker.Services/CodingTracker.Services.csproj create mode 100644 codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionService.cs create mode 100644 codingTracker.jzhartman/CodingTracker.Views/CodingSessionView.cs create mode 100644 codingTracker.jzhartman/CodingTracker.Views/CodingTracker.Views.csproj rename codingTracker.jzhartman/CodingTracker/{CodingTracker.Console.csproj => CodingTracker.ConsoleApp.csproj} (83%) diff --git a/codingTracker.jzhartman/CodingTracker.Controller/CodingSessionController.cs b/codingTracker.jzhartman/CodingTracker.Controller/CodingSessionController.cs deleted file mode 100644 index 59bdaa87b..000000000 --- a/codingTracker.jzhartman/CodingTracker.Controller/CodingSessionController.cs +++ /dev/null @@ -1,28 +0,0 @@ -using CodingTracker.Data.Repositories; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace CodingTracker.Controller -{ - internal class CodingSessionController - { - private readonly CodingSessionRepository _repository; - - public CodingSessionController(CodingSessionRepository repository) - { - _repository = repository; - } - - //Method to run repo.GetAll then pass data up to View - - - - // Methods for calling repo methods - // Used to handle the data and create lists - // Can contain the Console.WriteLine statements -- these will be called in the View later often - - } -} diff --git a/codingTracker.jzhartman/CodingTracker.Controller/CodingTracker.Controller.csproj b/codingTracker.jzhartman/CodingTracker.Controller/CodingTracker.Controller.csproj index b4af0158c..791819140 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/CodingTracker.Controller.csproj +++ b/codingTracker.jzhartman/CodingTracker.Controller/CodingTracker.Controller.csproj @@ -7,11 +7,8 @@ - - - - - + + diff --git a/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/IMenuController.cs b/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/IMenuController.cs new file mode 100644 index 000000000..ab750cd5e --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/IMenuController.cs @@ -0,0 +1,7 @@ +namespace CodingTracker.Controller.Interfaces +{ + public interface IMenuController + { + void Run(); + } +} \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs b/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs new file mode 100644 index 000000000..42bb29f8f --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs @@ -0,0 +1,27 @@ +using CodingTracker.Controller.Interfaces; +using CodingTracker.Views; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CodingTracker.Models.Services +{ + public class MenuController : IMenuController + { + private readonly CodingSessionView _codingSessionView; + + public MenuController(CodingSessionView codingSessionView) + { + _codingSessionView = codingSessionView; + } + + public void Run() + { + _codingSessionView.RenderAllCodingSessions(); + } + + + } +} diff --git a/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs b/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs index f377b492d..e5adf323d 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs @@ -33,7 +33,7 @@ private void CreateTable() Id integer primary key not null, StartTime text not null, EndTime text not null, - Duration real not null + Duration text not null )"; command.ExecuteNonQuery(); } @@ -44,11 +44,11 @@ private void SeedData() var command = connection.CreateCommand(); command.CommandText = @"insert into CodingSessions(StartTime, EndTime, Duration) - Values ('2025-08-01 21:00:00', '2025-08-01 23:30:00', 150), - ('2025-08-02 21:15:00', '2025-08-02 23:30:00', 135), - ('2025-08-03 22:00:00', '2025-08-03 23:45:00', 105), - ('2025-08-04 21:00:00', '2025-08-04 23:30:00', 150), - ('2025-08-05 21:00:00', '2025-08-05 23:30:00', 150); + Values ('2025-08-01 21:00:00', '2025-08-01 23:30:00', '150'), + ('2025-08-02 21:15:00', '2025-08-02 23:30:00', '135'), + ('2025-08-03 22:00:00', '2025-08-03 23:45:00', '105'), + ('2025-08-04 21:00:00', '2025-08-04 23:30:00', '150'), + ('2025-08-05 21:00:00', '2025-08-05 23:30:00', '150'); "; command.ExecuteNonQuery(); } diff --git a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs index 9bb0fb9eb..00d06b6f0 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs @@ -1,4 +1,4 @@ -using CodingTracker.Core.Models; +using CodingTracker.Models.Entities; namespace CodingTracker.Data.Interfaces { diff --git a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs index 7f468fc5f..691bdc443 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs @@ -1,5 +1,5 @@ -using CodingTracker.Core.Models; -using CodingTracker.Data.Interfaces; +using CodingTracker.Data.Interfaces; +using CodingTracker.Models.Entities; using Dapper; namespace CodingTracker.Data.Repositories @@ -18,7 +18,7 @@ public CodingSessionRepository(ISqliteConnectionFactory connectionFactory) public List GetAll() { using var connection = _connectionFactory.CreateConnection(); - List sessions = connection.Query("Select * from CodingSessions order by StartDate").ToList(); + List sessions = connection.Query("Select * from CodingSessions").ToList(); return sessions; } diff --git a/codingTracker.jzhartman/CodingTracker.Models/CodingTracker.Models.csproj b/codingTracker.jzhartman/CodingTracker.Models/CodingTracker.Models.csproj index 787cc1f71..b80b1ca59 100644 --- a/codingTracker.jzhartman/CodingTracker.Models/CodingTracker.Models.csproj +++ b/codingTracker.jzhartman/CodingTracker.Models/CodingTracker.Models.csproj @@ -8,7 +8,6 @@ - diff --git a/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSession.cs b/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSession.cs index dad9df0b5..8d7733f3f 100644 --- a/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSession.cs +++ b/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSession.cs @@ -4,21 +4,22 @@ using System.Text; using System.Threading.Tasks; -namespace CodingTracker.Core.Models +namespace CodingTracker.Models.Entities { public class CodingSession { - public int Id { get; set; } + public long Id { get; set; } public string StartTime { get; set; } public string EndTime { get; set; } public string Duration { get; set; } - public CodingSession(int id, string startTime, string endTime, string duration) + public CodingSession(long id, string startTime, string endTime, string duration) { Id = id; StartTime = startTime; EndTime = endTime; - GetDuration(); + Duration = duration; + //GetDuration(); } private void GetDuration() diff --git a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionService.cs b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionService.cs new file mode 100644 index 000000000..9c118ab7f --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionService.cs @@ -0,0 +1,35 @@ +using CodingTracker.Data.Interfaces; +using CodingTracker.Data.Repositories; +using CodingTracker.Models.Entities; +using CodingTracker.Services.Interfaces; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CodingTracker.Services +{ + public class CodingSessionService : ICodingSessionService + { + private readonly ICodingSessionRepository _repository; + + public CodingSessionService(ICodingSessionRepository repository) + { + _repository = repository; + } + + public List GetAllCodingSessions() + { + return _repository.GetAll(); + } + //Method to run repo.GetAll then pass data up to View + + + + // Methods for calling repo methods + // Used to handle the data and create lists + // Can contain the Console.WriteLine statements -- these will be called in the View later often + + } +} diff --git a/codingTracker.jzhartman/CodingTracker.Services/CodingTracker.Services.csproj b/codingTracker.jzhartman/CodingTracker.Services/CodingTracker.Services.csproj new file mode 100644 index 000000000..d1406a2b6 --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Services/CodingTracker.Services.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + enable + enable + + + + + + + + diff --git a/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionService.cs b/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionService.cs new file mode 100644 index 000000000..de57552bc --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionService.cs @@ -0,0 +1,9 @@ +using CodingTracker.Models.Entities; + +namespace CodingTracker.Services.Interfaces +{ + public interface ICodingSessionService + { + List GetAllCodingSessions(); + } +} \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Views/CodingSessionView.cs b/codingTracker.jzhartman/CodingTracker.Views/CodingSessionView.cs new file mode 100644 index 000000000..f70164b6f --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Views/CodingSessionView.cs @@ -0,0 +1,29 @@ +using CodingTracker.Services.Interfaces; + + +namespace CodingTracker.Views +{ + public class CodingSessionView + { + private readonly ICodingSessionService _codingSessionService; + + public CodingSessionView(ICodingSessionService codingSessionService) + { + _codingSessionService = codingSessionService; + } + + public void RenderAllCodingSessions() + { + var sessions = _codingSessionService.GetAllCodingSessions(); + + int count = 1; + Console.WriteLine("A list of all coding sessions: "); + + foreach (var session in sessions) + { + Console.WriteLine($"{count}:\t{session.StartTime} to {session.EndTime} for a duration of {session.Duration}"); + count++; + } + } + } +} diff --git a/codingTracker.jzhartman/CodingTracker.Views/CodingTracker.Views.csproj b/codingTracker.jzhartman/CodingTracker.Views/CodingTracker.Views.csproj new file mode 100644 index 000000000..f6af8a530 --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Views/CodingTracker.Views.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + enable + + + + + + + diff --git a/codingTracker.jzhartman/CodingTracker/App.config b/codingTracker.jzhartman/CodingTracker/App.config index 807eb4cdb..46fe3b5cd 100644 --- a/codingTracker.jzhartman/CodingTracker/App.config +++ b/codingTracker.jzhartman/CodingTracker/App.config @@ -2,5 +2,6 @@ + diff --git a/codingTracker.jzhartman/CodingTracker/CodingTracker.Console.csproj b/codingTracker.jzhartman/CodingTracker/CodingTracker.ConsoleApp.csproj similarity index 83% rename from codingTracker.jzhartman/CodingTracker/CodingTracker.Console.csproj rename to codingTracker.jzhartman/CodingTracker/CodingTracker.ConsoleApp.csproj index d05e7e018..730955308 100644 --- a/codingTracker.jzhartman/CodingTracker/CodingTracker.Console.csproj +++ b/codingTracker.jzhartman/CodingTracker/CodingTracker.ConsoleApp.csproj @@ -16,6 +16,8 @@ + + @@ -24,8 +26,4 @@ - - - - diff --git a/codingTracker.jzhartman/CodingTracker/Program.cs b/codingTracker.jzhartman/CodingTracker/Program.cs index 2537ea067..dfc2e54e6 100644 --- a/codingTracker.jzhartman/CodingTracker/Program.cs +++ b/codingTracker.jzhartman/CodingTracker/Program.cs @@ -1,19 +1,24 @@ -using CodingTracker.Console; +using System; +using CodingTracker.ConsoleApp; using SQLitePCL; using CodingTracker.Data; using CodingTracker.Data.Interfaces; using Microsoft.Extensions.DependencyInjection; using System.Collections.Specialized; using System.Configuration; +using CodingTracker.Controller.Interfaces; +using CodingTracker.Models.Services; -namespace CodingTracker; +namespace CodingTracker.ConsoleApp; internal class Program { static void Main(string[] args) { Batteries.Init(); + var timeAndDateFormat = ConfigurationManager.AppSettings.Get("timeAndDateFormat"); var serviceProvider = Startup.ConfigureServices(); serviceProvider.GetRequiredService().Initialize(); + serviceProvider.GetRequiredService().Run(); } } diff --git a/codingTracker.jzhartman/CodingTracker/Startup.cs b/codingTracker.jzhartman/CodingTracker/Startup.cs index 5ae46edbc..d175c465e 100644 --- a/codingTracker.jzhartman/CodingTracker/Startup.cs +++ b/codingTracker.jzhartman/CodingTracker/Startup.cs @@ -1,10 +1,14 @@ using CodingTracker.Data; using CodingTracker.Data.Interfaces; using CodingTracker.Data.Repositories; +using CodingTracker.Models.Services; +using CodingTracker.Services; +using CodingTracker.Services.Interfaces; +using CodingTracker.Views; using Microsoft.Extensions.DependencyInjection; using System.Configuration; -namespace CodingTracker.Console +namespace CodingTracker.ConsoleApp { internal static class Startup { @@ -15,8 +19,15 @@ public static IServiceProvider ConfigureServices() services.AddSingleton(provider => new SqliteConnectionFactory(connectionString)); services.AddSingleton(); + services.AddSingleton(); + + + services.AddSingleton(); + + services.AddSingleton(); + + services.AddSingleton(); - services.AddTransient(); return services.BuildServiceProvider(); } diff --git a/codingTracker.jzhartman/codingTracker.jzhartman.sln b/codingTracker.jzhartman/codingTracker.jzhartman.sln index 6aef9f532..8b4fbbfc7 100644 --- a/codingTracker.jzhartman/codingTracker.jzhartman.sln +++ b/codingTracker.jzhartman/codingTracker.jzhartman.sln @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.14.36310.24 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodingTracker.Console", "CodingTracker\CodingTracker.Console.csproj", "{E718A170-B8DA-40F4-965C-5C112E649C99}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodingTracker.ConsoleApp", "CodingTracker\CodingTracker.ConsoleApp.csproj", "{E718A170-B8DA-40F4-965C-5C112E649C99}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodingTracker.Controller", "CodingTracker.Controller\CodingTracker.Controller.csproj", "{350388E8-9796-437A-84E2-01BC1F133CF8}" EndProject @@ -11,6 +11,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodingTracker.Data", "Codin EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodingTracker.Models", "CodingTracker.Models\CodingTracker.Models.csproj", "{AE0E7C34-E681-47F8-B016-4BBD48955078}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodingTracker.Views", "CodingTracker.Views\CodingTracker.Views.csproj", "{5ED55C55-FC49-437B-A8A8-B454F51A5CC9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodingTracker.Services", "CodingTracker.Services\CodingTracker.Services.csproj", "{489125D9-C868-43B6-93D9-A0A1EECAD57D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -33,6 +37,14 @@ Global {AE0E7C34-E681-47F8-B016-4BBD48955078}.Debug|Any CPU.Build.0 = Debug|Any CPU {AE0E7C34-E681-47F8-B016-4BBD48955078}.Release|Any CPU.ActiveCfg = Release|Any CPU {AE0E7C34-E681-47F8-B016-4BBD48955078}.Release|Any CPU.Build.0 = Release|Any CPU + {5ED55C55-FC49-437B-A8A8-B454F51A5CC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5ED55C55-FC49-437B-A8A8-B454F51A5CC9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5ED55C55-FC49-437B-A8A8-B454F51A5CC9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5ED55C55-FC49-437B-A8A8-B454F51A5CC9}.Release|Any CPU.Build.0 = Release|Any CPU + {489125D9-C868-43B6-93D9-A0A1EECAD57D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {489125D9-C868-43B6-93D9-A0A1EECAD57D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {489125D9-C868-43B6-93D9-A0A1EECAD57D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {489125D9-C868-43B6-93D9-A0A1EECAD57D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From df4352184e240e968fb1dbf5a2b8c514551bd278 Mon Sep 17 00:00:00 2001 From: jzhartman Date: Fri, 22 Aug 2025 22:54:47 -0400 Subject: [PATCH 04/57] DB Init only creates and seeds data if table does not exist --- .../CodingTracker.Data/DatabaseInitializer.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs b/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs index e5adf323d..c8fa5d327 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs @@ -20,8 +20,18 @@ public DatabaseInitializer(ISqliteConnectionFactory connectionfactory) public void Initialize() { - CreateTable(); - SeedData(); + if (TableExists() == false) + { + CreateTable(); + SeedData(); + } + } + + private bool TableExists() + { + using var connection = _connectionFactory.CreateConnection(); + int count = connection.ExecuteScalar("select count(*) from sqlite_master where type='table' and name='CodingSessions'"); + return count == 1; } private void CreateTable() From 425454e9b51ae21dceb68b815e644112c5c61bc5 Mon Sep 17 00:00:00 2001 From: jzhartman Date: Sun, 24 Aug 2025 21:17:17 -0400 Subject: [PATCH 05/57] Commit prior to fixing dependencies to fully decouple views and make them rely on services --- .../MenuController.cs | 11 +++++-- .../CodingTracker.Data/DatabaseInitializer.cs | 12 ++++---- .../Repositories/CodingSessionRepository.cs | 30 +++++++++++++++++-- .../TypeHandlers/DateTimeHandler.cs | 30 +++++++++++++++++++ .../Entities/CodingSession.cs | 29 ++++++++++-------- .../Configuration/DateTimeOptions.cs | 13 ++++++++ .../CodingTracker/Program.cs | 1 - .../CodingTracker/Startup.cs | 13 ++++---- 8 files changed, 108 insertions(+), 31 deletions(-) create mode 100644 codingTracker.jzhartman/CodingTracker.Data/TypeHandlers/DateTimeHandler.cs create mode 100644 codingTracker.jzhartman/CodingTracker/Configuration/DateTimeOptions.cs diff --git a/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs b/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs index 42bb29f8f..75c724341 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs @@ -10,16 +10,21 @@ namespace CodingTracker.Models.Services { public class MenuController : IMenuController { - private readonly CodingSessionView _codingSessionView; + private readonly CodingSessionView _view; public MenuController(CodingSessionView codingSessionView) { - _codingSessionView = codingSessionView; + _view = codingSessionView; } public void Run() { - _codingSessionView.RenderAllCodingSessions(); + //_view.ShowMainMenu(); + //var selection = _view.GetUserSelection(); + //_service.HandleSelection(selection); + + //All Code Below Here Is Basic Testing ONLY -- Not part of actual flow + _view.RenderAllCodingSessions(); } diff --git a/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs b/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs index c8fa5d327..9e36d8300 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs @@ -43,7 +43,7 @@ private void CreateTable() Id integer primary key not null, StartTime text not null, EndTime text not null, - Duration text not null + Duration integer not null )"; command.ExecuteNonQuery(); } @@ -54,11 +54,11 @@ private void SeedData() var command = connection.CreateCommand(); command.CommandText = @"insert into CodingSessions(StartTime, EndTime, Duration) - Values ('2025-08-01 21:00:00', '2025-08-01 23:30:00', '150'), - ('2025-08-02 21:15:00', '2025-08-02 23:30:00', '135'), - ('2025-08-03 22:00:00', '2025-08-03 23:45:00', '105'), - ('2025-08-04 21:00:00', '2025-08-04 23:30:00', '150'), - ('2025-08-05 21:00:00', '2025-08-05 23:30:00', '150'); + Values ('2025-08-01 21:00:00', '2025-08-01 23:30:00', 9000000), + ('2025-08-02 21:15:00', '2025-08-02 23:30:00', 8100000), + ('2025-08-03 22:00:00', '2025-08-03 23:45:00', 6300000), + ('2025-08-04 21:00:00', '2025-08-04 23:30:00', 9000000), + ('2025-08-05 21:00:00', '2025-08-05 23:30:00', 9000000); "; command.ExecuteNonQuery(); } diff --git a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs index 691bdc443..42a5287a9 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs @@ -14,11 +14,36 @@ public CodingSessionRepository(ISqliteConnectionFactory connectionFactory) _connectionFactory = connectionFactory; } - // Sample method public List GetAll() + { + string sql = "Select * from CodingSessions"; + return LoadData(sql); + } + + + //Not Working..... + public List GetByDateRange(DateTime startTime, DateTime endTime) + { + string sql = @"Select * from CodingSessions"; + return LoadData(sql); + } + + public CodingSession GetById(int id) + { + string sql = $"select * from CodingSessions where Id = {id}"; + return LoadData(sql).FirstOrDefault(); + } + + public CodingSession GetLongestDuration() + { + string sql = $"select * from........... "; + return LoadData(sql).FirstOrDefault(); + } + + private List LoadData(string sql) { using var connection = _connectionFactory.CreateConnection(); - List sessions = connection.Query("Select * from CodingSessions").ToList(); + List sessions = connection.Query(sql).ToList(); return sessions; } @@ -26,5 +51,6 @@ public List GetAll() // using var connection = connectionFactory.CreateConnection(); // connection.Open(); // insert Dapper query here + } } diff --git a/codingTracker.jzhartman/CodingTracker.Data/TypeHandlers/DateTimeHandler.cs b/codingTracker.jzhartman/CodingTracker.Data/TypeHandlers/DateTimeHandler.cs new file mode 100644 index 000000000..a12126ab1 --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Data/TypeHandlers/DateTimeHandler.cs @@ -0,0 +1,30 @@ +using Dapper; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CodingTracker.Data.TypeHandlers +{ + public class DateTimeHandler : SqlMapper.TypeHandler + { + private readonly string _format; + + public DateTimeHandler(string format) + { + _format = format; + } + + public override DateTime Parse(object value) + { + return DateTime.ParseExact(value.ToString(), _format, null); + } + + public override void SetValue(IDbDataParameter parameter, DateTime value) + { + parameter.Value = value.ToString(_format); + } + } +} diff --git a/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSession.cs b/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSession.cs index 8d7733f3f..cf43c2567 100644 --- a/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSession.cs +++ b/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSession.cs @@ -9,23 +9,26 @@ namespace CodingTracker.Models.Entities public class CodingSession { public long Id { get; set; } - public string StartTime { get; set; } - public string EndTime { get; set; } - public string Duration { get; set; } + public DateTime StartTime { get; set; } + public DateTime EndTime { get; set; } + public int Duration { get; set; } - public CodingSession(long id, string startTime, string endTime, string duration) - { - Id = id; - StartTime = startTime; - EndTime = endTime; - Duration = duration; - //GetDuration(); - } + //public CodingSession(long id, DateTime startTime, DateTime endTime, int duration) + //{ + // Id = id; + // StartTime = startTime; + // EndTime = endTime; + // Duration = duration; + // //GetDuration(); + //} private void GetDuration() { - var duration = DateTime.Parse(EndTime).Subtract(DateTime.Parse(StartTime)); - Duration = duration.ToString("yyyy-MM-dd HH:mm:ss"); + //var duration = DateTime.Parse(EndTime).Subtract(DateTime.Parse(StartTime)); + //Duration = duration.ToString("yyyy-MM-dd HH:mm:ss"); } + + //var durationText = TimeSpan.FromMilliseconds(ms).ToString("format"); + } } diff --git a/codingTracker.jzhartman/CodingTracker/Configuration/DateTimeOptions.cs b/codingTracker.jzhartman/CodingTracker/Configuration/DateTimeOptions.cs new file mode 100644 index 000000000..67e32096b --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker/Configuration/DateTimeOptions.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CodingTracker.ConsoleApp.Configuration +{ + public class DateTimeOptions + { + public string Format { get; set; } + } +} diff --git a/codingTracker.jzhartman/CodingTracker/Program.cs b/codingTracker.jzhartman/CodingTracker/Program.cs index dfc2e54e6..44e0bb11c 100644 --- a/codingTracker.jzhartman/CodingTracker/Program.cs +++ b/codingTracker.jzhartman/CodingTracker/Program.cs @@ -15,7 +15,6 @@ internal class Program static void Main(string[] args) { Batteries.Init(); - var timeAndDateFormat = ConfigurationManager.AppSettings.Get("timeAndDateFormat"); var serviceProvider = Startup.ConfigureServices(); serviceProvider.GetRequiredService().Initialize(); diff --git a/codingTracker.jzhartman/CodingTracker/Startup.cs b/codingTracker.jzhartman/CodingTracker/Startup.cs index d175c465e..4539e7ee9 100644 --- a/codingTracker.jzhartman/CodingTracker/Startup.cs +++ b/codingTracker.jzhartman/CodingTracker/Startup.cs @@ -1,12 +1,15 @@ using CodingTracker.Data; using CodingTracker.Data.Interfaces; using CodingTracker.Data.Repositories; +using CodingTracker.Data.TypeHandlers; using CodingTracker.Models.Services; using CodingTracker.Services; using CodingTracker.Services.Interfaces; using CodingTracker.Views; +using Dapper; using Microsoft.Extensions.DependencyInjection; using System.Configuration; +using System.Runtime.Serialization; namespace CodingTracker.ConsoleApp { @@ -14,21 +17,19 @@ internal static class Startup { public static IServiceProvider ConfigureServices() { - var services = new ServiceCollection(); var connectionString = ConfigurationManager.AppSettings.Get("connectionString"); + + var dateTimeFormat = ConfigurationManager.AppSettings.Get("timeAndDateFormat"); + SqlMapper.AddTypeHandler(new DateTimeHandler(dateTimeFormat)); + var services = new ServiceCollection(); services.AddSingleton(provider => new SqliteConnectionFactory(connectionString)); services.AddSingleton(); services.AddSingleton(); - - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - return services.BuildServiceProvider(); } From ee0532b5b074dc8f86ebd54b13ad7d16817f7ba9 Mon Sep 17 00:00:00 2001 From: jzhartman Date: Sun, 24 Aug 2025 22:28:40 -0400 Subject: [PATCH 06/57] Reconfigured app to follow desired flow pattern --- .../CodingTracker.Controller.csproj | 1 + .../MenuController.cs | 16 ++++++++++----- .../CodingTracker.Views/CodingSessionView.cs | 16 ++++----------- .../CodingTracker.Views.csproj | 2 +- .../CodingTracker.ConsoleApp.csproj | 2 +- .../CodingTracker/Program.cs | 13 ++++-------- .../CodingTracker/Startup.cs | 20 +++++++++++-------- 7 files changed, 34 insertions(+), 36 deletions(-) diff --git a/codingTracker.jzhartman/CodingTracker.Controller/CodingTracker.Controller.csproj b/codingTracker.jzhartman/CodingTracker.Controller/CodingTracker.Controller.csproj index 791819140..c14579d77 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/CodingTracker.Controller.csproj +++ b/codingTracker.jzhartman/CodingTracker.Controller/CodingTracker.Controller.csproj @@ -8,6 +8,7 @@ + diff --git a/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs b/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs index 75c724341..0b2f90590 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs @@ -1,4 +1,6 @@ using CodingTracker.Controller.Interfaces; +using CodingTracker.Services; +using CodingTracker.Services.Interfaces; using CodingTracker.Views; using System; using System.Collections.Generic; @@ -6,17 +8,20 @@ using System.Text; using System.Threading.Tasks; -namespace CodingTracker.Models.Services +namespace CodingTracker.Controller { public class MenuController : IMenuController { - private readonly CodingSessionView _view; + private readonly ICodingSessionService _service; - public MenuController(CodingSessionView codingSessionView) + public MenuController(ICodingSessionService service) { - _view = codingSessionView; + _service = service; } + // Call services for business/data + // Call views to render info + public void Run() { //_view.ShowMainMenu(); @@ -24,7 +29,8 @@ public void Run() //_service.HandleSelection(selection); //All Code Below Here Is Basic Testing ONLY -- Not part of actual flow - _view.RenderAllCodingSessions(); + var sessions = _service.GetAllCodingSessions(); + CodingSessionView.RenderAllCodingSessions(sessions); } diff --git a/codingTracker.jzhartman/CodingTracker.Views/CodingSessionView.cs b/codingTracker.jzhartman/CodingTracker.Views/CodingSessionView.cs index f70164b6f..00fa8985e 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/CodingSessionView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/CodingSessionView.cs @@ -1,21 +1,13 @@ -using CodingTracker.Services.Interfaces; +using CodingTracker.Models.Entities; +using CodingTracker.Models.Entities; namespace CodingTracker.Views { - public class CodingSessionView + public static class CodingSessionView { - private readonly ICodingSessionService _codingSessionService; - - public CodingSessionView(ICodingSessionService codingSessionService) + public static void RenderAllCodingSessions(List sessions) { - _codingSessionService = codingSessionService; - } - - public void RenderAllCodingSessions() - { - var sessions = _codingSessionService.GetAllCodingSessions(); - int count = 1; Console.WriteLine("A list of all coding sessions: "); diff --git a/codingTracker.jzhartman/CodingTracker.Views/CodingTracker.Views.csproj b/codingTracker.jzhartman/CodingTracker.Views/CodingTracker.Views.csproj index f6af8a530..27b309a28 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/CodingTracker.Views.csproj +++ b/codingTracker.jzhartman/CodingTracker.Views/CodingTracker.Views.csproj @@ -7,7 +7,7 @@ - + diff --git a/codingTracker.jzhartman/CodingTracker/CodingTracker.ConsoleApp.csproj b/codingTracker.jzhartman/CodingTracker/CodingTracker.ConsoleApp.csproj index 730955308..262884db4 100644 --- a/codingTracker.jzhartman/CodingTracker/CodingTracker.ConsoleApp.csproj +++ b/codingTracker.jzhartman/CodingTracker/CodingTracker.ConsoleApp.csproj @@ -17,7 +17,7 @@ - + diff --git a/codingTracker.jzhartman/CodingTracker/Program.cs b/codingTracker.jzhartman/CodingTracker/Program.cs index 44e0bb11c..0748d8c48 100644 --- a/codingTracker.jzhartman/CodingTracker/Program.cs +++ b/codingTracker.jzhartman/CodingTracker/Program.cs @@ -1,13 +1,8 @@ -using System; -using CodingTracker.ConsoleApp; -using SQLitePCL; -using CodingTracker.Data; +using CodingTracker.Controller; +using CodingTracker.Controller.Interfaces; using CodingTracker.Data.Interfaces; using Microsoft.Extensions.DependencyInjection; -using System.Collections.Specialized; -using System.Configuration; -using CodingTracker.Controller.Interfaces; -using CodingTracker.Models.Services; +using SQLitePCL; namespace CodingTracker.ConsoleApp; internal class Program @@ -18,6 +13,6 @@ static void Main(string[] args) var serviceProvider = Startup.ConfigureServices(); serviceProvider.GetRequiredService().Initialize(); - serviceProvider.GetRequiredService().Run(); + serviceProvider.GetRequiredService().Run(); } } diff --git a/codingTracker.jzhartman/CodingTracker/Startup.cs b/codingTracker.jzhartman/CodingTracker/Startup.cs index 4539e7ee9..db1201006 100644 --- a/codingTracker.jzhartman/CodingTracker/Startup.cs +++ b/codingTracker.jzhartman/CodingTracker/Startup.cs @@ -1,15 +1,14 @@ -using CodingTracker.Data; +using CodingTracker.Controller; +using CodingTracker.Controller.Interfaces; +using CodingTracker.Data; using CodingTracker.Data.Interfaces; using CodingTracker.Data.Repositories; using CodingTracker.Data.TypeHandlers; -using CodingTracker.Models.Services; using CodingTracker.Services; using CodingTracker.Services.Interfaces; -using CodingTracker.Views; using Dapper; using Microsoft.Extensions.DependencyInjection; using System.Configuration; -using System.Runtime.Serialization; namespace CodingTracker.ConsoleApp { @@ -23,12 +22,17 @@ public static IServiceProvider ConfigureServices() SqlMapper.AddTypeHandler(new DateTimeHandler(dateTimeFormat)); var services = new ServiceCollection(); + + //Register All Controllers + services.AddSingleton(); + + //Register All Services + services.AddSingleton(); + + + services.AddSingleton(); services.AddSingleton(provider => new SqliteConnectionFactory(connectionString)); services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); return services.BuildServiceProvider(); } From 8873911a4b1c34dea490ca16bb5713bc5aa8493d Mon Sep 17 00:00:00 2001 From: jzhartman Date: Wed, 27 Aug 2025 21:13:56 -0400 Subject: [PATCH 07/57] Reworked seed data to provide 50 randomized records --- .../CodingTracker.Data/DatabaseInitializer.cs | 33 +++++++++++++++---- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs b/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs index 9e36d8300..06b526c7e 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs @@ -53,15 +53,34 @@ private void SeedData() using var connection = _connectionFactory.CreateConnection(); var command = connection.CreateCommand(); - command.CommandText = @"insert into CodingSessions(StartTime, EndTime, Duration) - Values ('2025-08-01 21:00:00', '2025-08-01 23:30:00', 9000000), - ('2025-08-02 21:15:00', '2025-08-02 23:30:00', 8100000), - ('2025-08-03 22:00:00', '2025-08-03 23:45:00', 6300000), - ('2025-08-04 21:00:00', '2025-08-04 23:30:00', 9000000), - ('2025-08-05 21:00:00', '2025-08-05 23:30:00', 9000000); - "; + + string sql = CreateSqlString(); + + command.CommandText = sql; command.ExecuteNonQuery(); } + private string CreateSqlString() + { + string sql = "insert into CodingSessions(StartTime, EndTime, Duration)\nValues\n"; + + Random rand = new Random(); + DateTime startDate = DateTime.Parse("2025-01-01 21:00:00"); + DateTime endDate = startDate.AddHours(2); + TimeSpan duration = endDate - startDate; + + for (int i = 0; i < 50; i++) + { + if (i != 0) sql += ",\n"; + + sql += $"('{startDate.ToString("yyyy-MM-dd HH:mm:ss")}', '{endDate.ToString("yyyy-MM-dd HH:mm:ss")}', {duration.TotalSeconds})"; + startDate = startDate.AddDays(1); + endDate = startDate.AddHours(rand.Next(1, 3)).AddMinutes(rand.Next(0, 60)).AddSeconds(rand.Next(0, 60)); + duration = endDate - startDate; + } + sql += ";"; + + return sql; + } } } From af51dca552181a8e831ed629a222078f66eb77d0 Mon Sep 17 00:00:00 2001 From: jzhartman Date: Wed, 27 Aug 2025 22:28:49 -0400 Subject: [PATCH 08/57] Added overload for LoadData Got GetByDateRange working --- .../MenuController.cs | 10 ++++++- .../CodingTracker.Data/DatabaseInitializer.cs | 1 + .../Interfaces/ICodingSessionRepository.cs | 1 + .../Parameters/DateRangeQuery.cs | 8 ++++++ .../Repositories/CodingSessionRepository.cs | 21 +++++++++------ .../Entities/CodingSession.cs | 26 +------------------ .../CodingSessionService.cs | 6 +++++ .../Interfaces/ICodingSessionService.cs | 1 + .../CodingTracker.Views/CodingSessionView.cs | 6 ++--- .../CodingTracker/Program.cs | 3 +-- 10 files changed, 43 insertions(+), 40 deletions(-) create mode 100644 codingTracker.jzhartman/CodingTracker.Data/Parameters/DateRangeQuery.cs diff --git a/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs b/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs index 0b2f90590..fc618639d 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs @@ -30,7 +30,15 @@ public void Run() //All Code Below Here Is Basic Testing ONLY -- Not part of actual flow var sessions = _service.GetAllCodingSessions(); - CodingSessionView.RenderAllCodingSessions(sessions); + CodingSessionView.RenderCodingSessions(sessions); + + Console.WriteLine(); + var startTime = DateTime.Parse("2025-01-07 00:00:00"); + var endTime = DateTime.Parse("2025-02-01 23:59:59"); + sessions = _service.GetByDateRange(startTime, endTime); + CodingSessionView.RenderCodingSessions(sessions); + + } diff --git a/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs b/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs index 06b526c7e..318283ef9 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs @@ -65,6 +65,7 @@ private string CreateSqlString() string sql = "insert into CodingSessions(StartTime, EndTime, Duration)\nValues\n"; Random rand = new Random(); + DateTime startDate = DateTime.Parse("2025-01-01 21:00:00"); DateTime endDate = startDate.AddHours(2); TimeSpan duration = endDate - startDate; diff --git a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs index 00d06b6f0..4962b196f 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs @@ -5,5 +5,6 @@ namespace CodingTracker.Data.Interfaces public interface ICodingSessionRepository { List GetAll(); + List GetByDateRange(DateTime startTime, DateTime endTime); } } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Data/Parameters/DateRangeQuery.cs b/codingTracker.jzhartman/CodingTracker.Data/Parameters/DateRangeQuery.cs new file mode 100644 index 000000000..2971b3607 --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Data/Parameters/DateRangeQuery.cs @@ -0,0 +1,8 @@ +namespace CodingTracker.Data.Parameters +{ + public class DateRangeQuery + { + public DateTime StartTime { get; set; } + public DateTime EndTime { get; set; } + } +} diff --git a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs index 42a5287a9..21eb30721 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs @@ -1,11 +1,11 @@ using CodingTracker.Data.Interfaces; +using CodingTracker.Data.Parameters; using CodingTracker.Models.Entities; using Dapper; namespace CodingTracker.Data.Repositories { public class CodingSessionRepository : ICodingSessionRepository - //: ICodingSessionRepository { private readonly ISqliteConnectionFactory _connectionFactory; @@ -13,21 +13,20 @@ public CodingSessionRepository(ISqliteConnectionFactory connectionFactory) { _connectionFactory = connectionFactory; } - public List GetAll() { string sql = "Select * from CodingSessions"; return LoadData(sql); } - - //Not Working..... - public List GetByDateRange(DateTime startTime, DateTime endTime) + public List GetByDateRange(DateTime begin, DateTime finish) { - string sql = @"Select * from CodingSessions"; - return LoadData(sql); + var dateRange = new DateRangeQuery { StartTime = begin, EndTime = finish }; + string sql = @"Select * from CodingSessions where (StartTime >= @StartTime) AND (endTime <= @EndTime)"; + return LoadData(sql, dateRange); } + //Not Working..... public CodingSession GetById(int id) { string sql = $"select * from CodingSessions where Id = {id}"; @@ -46,6 +45,12 @@ private List LoadData(string sql) List sessions = connection.Query(sql).ToList(); return sessions; } + private List LoadData(string sql, U parameters) + { + using var connection = _connectionFactory.CreateConnection(); + List sessions = connection.Query(sql, parameters).ToList(); + return sessions; + } // Method here using the following snippet // using var connection = connectionFactory.CreateConnection(); @@ -53,4 +58,4 @@ private List LoadData(string sql) // insert Dapper query here } -} +} \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSession.cs b/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSession.cs index cf43c2567..26e855844 100644 --- a/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSession.cs +++ b/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSession.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace CodingTracker.Models.Entities +namespace CodingTracker.Models.Entities { public class CodingSession { @@ -12,23 +6,5 @@ public class CodingSession public DateTime StartTime { get; set; } public DateTime EndTime { get; set; } public int Duration { get; set; } - - //public CodingSession(long id, DateTime startTime, DateTime endTime, int duration) - //{ - // Id = id; - // StartTime = startTime; - // EndTime = endTime; - // Duration = duration; - // //GetDuration(); - //} - - private void GetDuration() - { - //var duration = DateTime.Parse(EndTime).Subtract(DateTime.Parse(StartTime)); - //Duration = duration.ToString("yyyy-MM-dd HH:mm:ss"); - } - - //var durationText = TimeSpan.FromMilliseconds(ms).ToString("format"); - } } diff --git a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionService.cs b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionService.cs index 9c118ab7f..fb0caee49 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionService.cs @@ -23,6 +23,12 @@ public List GetAllCodingSessions() { return _repository.GetAll(); } + + public List GetByDateRange(DateTime startTime, DateTime endTime) + { + return _repository.GetByDateRange(startTime, endTime); + } + //Method to run repo.GetAll then pass data up to View diff --git a/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionService.cs b/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionService.cs index de57552bc..3e0f01067 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionService.cs @@ -5,5 +5,6 @@ namespace CodingTracker.Services.Interfaces public interface ICodingSessionService { List GetAllCodingSessions(); + List GetByDateRange(DateTime startTime, DateTime endTime); } } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Views/CodingSessionView.cs b/codingTracker.jzhartman/CodingTracker.Views/CodingSessionView.cs index 00fa8985e..2b9cfcb50 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/CodingSessionView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/CodingSessionView.cs @@ -1,15 +1,13 @@ using CodingTracker.Models.Entities; -using CodingTracker.Models.Entities; - namespace CodingTracker.Views { public static class CodingSessionView { - public static void RenderAllCodingSessions(List sessions) + public static void RenderCodingSessions(List sessions) { int count = 1; - Console.WriteLine("A list of all coding sessions: "); + Console.WriteLine("A list of coding sessions: "); foreach (var session in sessions) { diff --git a/codingTracker.jzhartman/CodingTracker/Program.cs b/codingTracker.jzhartman/CodingTracker/Program.cs index 0748d8c48..8bce8ab22 100644 --- a/codingTracker.jzhartman/CodingTracker/Program.cs +++ b/codingTracker.jzhartman/CodingTracker/Program.cs @@ -1,5 +1,4 @@ -using CodingTracker.Controller; -using CodingTracker.Controller.Interfaces; +using CodingTracker.Controller.Interfaces; using CodingTracker.Data.Interfaces; using Microsoft.Extensions.DependencyInjection; using SQLitePCL; From 8b36c0bba2b5935c4fbfb8d9983b8b0689412d85 Mon Sep 17 00:00:00 2001 From: jzhartman Date: Wed, 27 Aug 2025 22:32:31 -0400 Subject: [PATCH 09/57] GetById now working --- .../CodingTracker.Controller/MenuController.cs | 4 ++++ .../Interfaces/ICodingSessionRepository.cs | 1 + .../CodingTracker.Services/CodingSessionService.cs | 5 +++++ .../Interfaces/ICodingSessionService.cs | 1 + .../CodingTracker.Views/CodingSessionView.cs | 5 +++++ 5 files changed, 16 insertions(+) diff --git a/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs b/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs index fc618639d..d6175b435 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs @@ -38,6 +38,10 @@ public void Run() sessions = _service.GetByDateRange(startTime, endTime); CodingSessionView.RenderCodingSessions(sessions); + Console.WriteLine(); + var session = _service.GetById(2); + CodingSessionView.RenderCodingSession(session); + } diff --git a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs index 4962b196f..5de82d84a 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs @@ -6,5 +6,6 @@ public interface ICodingSessionRepository { List GetAll(); List GetByDateRange(DateTime startTime, DateTime endTime); + CodingSession GetById(int id); } } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionService.cs b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionService.cs index fb0caee49..729330592 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionService.cs @@ -29,6 +29,11 @@ public List GetByDateRange(DateTime startTime, DateTime endTime) return _repository.GetByDateRange(startTime, endTime); } + public CodingSession GetById(int id) + { + return _repository.GetById(id); + } + //Method to run repo.GetAll then pass data up to View diff --git a/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionService.cs b/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionService.cs index 3e0f01067..b136104a0 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionService.cs @@ -6,5 +6,6 @@ public interface ICodingSessionService { List GetAllCodingSessions(); List GetByDateRange(DateTime startTime, DateTime endTime); + CodingSession GetById(int id); } } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Views/CodingSessionView.cs b/codingTracker.jzhartman/CodingTracker.Views/CodingSessionView.cs index 2b9cfcb50..9cc683b48 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/CodingSessionView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/CodingSessionView.cs @@ -15,5 +15,10 @@ public static void RenderCodingSessions(List sessions) count++; } } + + public static void RenderCodingSession(CodingSession session) + { + Console.WriteLine($"{session.Id}:\t{session.StartTime} to {session.EndTime} for a duration of {session.Duration}"); + } } } From 8dccb8f225dc94da1e49bf94424c22986443caee Mon Sep 17 00:00:00 2001 From: jzhartman Date: Wed, 27 Aug 2025 22:48:57 -0400 Subject: [PATCH 10/57] DeleteById working --- .../MenuController.cs | 9 ++++++++ .../Interfaces/ICodingSessionRepository.cs | 1 + .../Repositories/CodingSessionRepository.cs | 21 +++++++++++++++---- .../CodingSessionService.cs | 5 +++++ .../Interfaces/ICodingSessionService.cs | 1 + .../CodingTracker.Views/CodingSessionView.cs | 2 ++ 6 files changed, 35 insertions(+), 4 deletions(-) diff --git a/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs b/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs index d6175b435..fa692f7af 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs @@ -32,6 +32,14 @@ public void Run() var sessions = _service.GetAllCodingSessions(); CodingSessionView.RenderCodingSessions(sessions); + Console.WriteLine(); + int id = (int)sessions[5].Id; + Console.WriteLine($"Deleting record {id}"); + _service.DeleteById(id); + sessions = _service.GetAllCodingSessions(); + CodingSessionView.RenderCodingSessions(sessions); + + Console.WriteLine(); var startTime = DateTime.Parse("2025-01-07 00:00:00"); var endTime = DateTime.Parse("2025-02-01 23:59:59"); @@ -43,6 +51,7 @@ public void Run() CodingSessionView.RenderCodingSession(session); + } diff --git a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs index 5de82d84a..9074fa7ff 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs @@ -4,6 +4,7 @@ namespace CodingTracker.Data.Interfaces { public interface ICodingSessionRepository { + void DeleteById(int id); List GetAll(); List GetByDateRange(DateTime startTime, DateTime endTime); CodingSession GetById(int id); diff --git a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs index 21eb30721..61ee31012 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs @@ -26,17 +26,23 @@ public List GetByDateRange(DateTime begin, DateTime finish) return LoadData(sql, dateRange); } - //Not Working..... public CodingSession GetById(int id) { string sql = $"select * from CodingSessions where Id = {id}"; return LoadData(sql).FirstOrDefault(); } - public CodingSession GetLongestDuration() + public void DeleteById(int id) { - string sql = $"select * from........... "; - return LoadData(sql).FirstOrDefault(); + string sql = $"delete from CodingSessions where Id = {id}"; + SaveData(sql); + } + + //Not Working..... + public List GetLongestDuration() + { + string sql = $"select * from CodingSessions where "; + return LoadData(sql); } private List LoadData(string sql) @@ -52,6 +58,13 @@ private List LoadData(string sql, U parameters) return sessions; } + private void SaveData(string sql) + { + using var connection = _connectionFactory.CreateConnection(); + connection.Execute(sql); + } + + // Method here using the following snippet // using var connection = connectionFactory.CreateConnection(); // connection.Open(); diff --git a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionService.cs b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionService.cs index 729330592..2ea071752 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionService.cs @@ -34,6 +34,11 @@ public CodingSession GetById(int id) return _repository.GetById(id); } + public void DeleteById(int id) + { + _repository.DeleteById(id); + } + //Method to run repo.GetAll then pass data up to View diff --git a/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionService.cs b/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionService.cs index b136104a0..584092216 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionService.cs @@ -4,6 +4,7 @@ namespace CodingTracker.Services.Interfaces { public interface ICodingSessionService { + void DeleteById(int id); List GetAllCodingSessions(); List GetByDateRange(DateTime startTime, DateTime endTime); CodingSession GetById(int id); diff --git a/codingTracker.jzhartman/CodingTracker.Views/CodingSessionView.cs b/codingTracker.jzhartman/CodingTracker.Views/CodingSessionView.cs index 9cc683b48..4b26e7f9d 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/CodingSessionView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/CodingSessionView.cs @@ -20,5 +20,7 @@ public static void RenderCodingSession(CodingSession session) { Console.WriteLine($"{session.Id}:\t{session.StartTime} to {session.EndTime} for a duration of {session.Duration}"); } + + } } From 07513b63eca7fe82099f041b52800d2ed5b9c2ce Mon Sep 17 00:00:00 2001 From: jzhartman Date: Thu, 28 Aug 2025 22:41:09 -0400 Subject: [PATCH 11/57] Found bug in DateTime Type Handler and fixed Added Update StartTime feature --- .../MenuController.cs | 33 +++++++++++-------- .../Interfaces/ICodingSessionRepository.cs | 1 + .../Parameters/StartTimeUpdate.cs | 14 ++++++++ .../Repositories/CodingSessionRepository.cs | 19 +++++++++++ .../TypeHandlers/DateTimeHandler.cs | 15 ++++++++- .../CodingSessionService.cs | 5 +++ .../Interfaces/ICodingSessionService.cs | 1 + .../CodingTracker/Startup.cs | 2 ++ 8 files changed, 76 insertions(+), 14 deletions(-) create mode 100644 codingTracker.jzhartman/CodingTracker.Data/Parameters/StartTimeUpdate.cs diff --git a/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs b/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs index fa692f7af..ba98a2434 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs @@ -29,26 +29,33 @@ public void Run() //_service.HandleSelection(selection); //All Code Below Here Is Basic Testing ONLY -- Not part of actual flow - var sessions = _service.GetAllCodingSessions(); - CodingSessionView.RenderCodingSessions(sessions); - Console.WriteLine(); - int id = (int)sessions[5].Id; - Console.WriteLine($"Deleting record {id}"); - _service.DeleteById(id); - sessions = _service.GetAllCodingSessions(); - CodingSessionView.RenderCodingSessions(sessions); + + //var sessions = _service.GetAllCodingSessions(); + //CodingSessionView.RenderCodingSessions(sessions); + + //Console.WriteLine(); + //int id = (int)sessions[5].Id; + //Console.WriteLine($"Deleting record {id}"); + //_service.DeleteById(id); + //sessions = _service.GetAllCodingSessions(); + //CodingSessionView.RenderCodingSessions(sessions); - Console.WriteLine(); - var startTime = DateTime.Parse("2025-01-07 00:00:00"); + //Console.WriteLine(); + var startTime = DateTime.Parse("2025-01-07 00:00:00.1234"); var endTime = DateTime.Parse("2025-02-01 23:59:59"); + var sessions = _service.GetByDateRange(startTime, endTime); + CodingSessionView.RenderCodingSessions(sessions); + + _service.UpdateStartDateById((int)sessions[9].Id, DateTime.Now); + sessions = _service.GetByDateRange(startTime, endTime); CodingSessionView.RenderCodingSessions(sessions); - Console.WriteLine(); - var session = _service.GetById(2); - CodingSessionView.RenderCodingSession(session); + //Console.WriteLine(); + //var session = _service.GetById(2); + //CodingSessionView.RenderCodingSession(session); diff --git a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs index 9074fa7ff..23bc0116a 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs @@ -8,5 +8,6 @@ public interface ICodingSessionRepository List GetAll(); List GetByDateRange(DateTime startTime, DateTime endTime); CodingSession GetById(int id); + void UpdateStartTimeById(int id, DateTime startTime); } } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Data/Parameters/StartTimeUpdate.cs b/codingTracker.jzhartman/CodingTracker.Data/Parameters/StartTimeUpdate.cs new file mode 100644 index 000000000..fe7808f87 --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Data/Parameters/StartTimeUpdate.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CodingTracker.Data.Parameters +{ + public class StartTimeUpdate + { + public int Id { get; set; } + public DateTime StartTime { get; set; } + } +} diff --git a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs index 61ee31012..6efcaed88 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs @@ -38,7 +38,20 @@ public void DeleteById(int id) SaveData(sql); } + + + + //Not Working..... + + public void UpdateStartTimeById(int id, DateTime startTime) + { + var parameters = new StartTimeUpdate {Id = id, StartTime = startTime }; + string sql = "update CodingSessions Set StartTime = @StartTime where Id = @Id"; + SaveData(sql, parameters); + } + + public List GetLongestDuration() { string sql = $"select * from CodingSessions where "; @@ -64,6 +77,12 @@ private void SaveData(string sql) connection.Execute(sql); } + private void SaveData(string sql, T parameters) + { + using var connection = _connectionFactory.CreateConnection(); + connection.Execute(sql, parameters); + } + // Method here using the following snippet // using var connection = connectionFactory.CreateConnection(); diff --git a/codingTracker.jzhartman/CodingTracker.Data/TypeHandlers/DateTimeHandler.cs b/codingTracker.jzhartman/CodingTracker.Data/TypeHandlers/DateTimeHandler.cs index a12126ab1..28b456dfd 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/TypeHandlers/DateTimeHandler.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/TypeHandlers/DateTimeHandler.cs @@ -19,11 +19,24 @@ public DateTimeHandler(string format) public override DateTime Parse(object value) { - return DateTime.ParseExact(value.ToString(), _format, null); + if (value is string s + && DateTime.TryParseExact + ( + s, + _format, + System.Globalization.CultureInfo.InvariantCulture, + System.Globalization.DateTimeStyles.AssumeLocal, + out var dt)) + { + return dt; + } + + return Convert.ToDateTime(value); } public override void SetValue(IDbDataParameter parameter, DateTime value) { + parameter.DbType = DbType.String; parameter.Value = value.ToString(_format); } } diff --git a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionService.cs b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionService.cs index 2ea071752..58370083f 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionService.cs @@ -39,6 +39,11 @@ public void DeleteById(int id) _repository.DeleteById(id); } + public void UpdateStartDateById(int id, DateTime startTime) + { + _repository.UpdateStartTimeById(id, startTime); + } + //Method to run repo.GetAll then pass data up to View diff --git a/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionService.cs b/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionService.cs index 584092216..f40e508ed 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionService.cs @@ -8,5 +8,6 @@ public interface ICodingSessionService List GetAllCodingSessions(); List GetByDateRange(DateTime startTime, DateTime endTime); CodingSession GetById(int id); + void UpdateStartDateById(int id, DateTime startTime); } } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker/Startup.cs b/codingTracker.jzhartman/CodingTracker/Startup.cs index db1201006..7afb054d4 100644 --- a/codingTracker.jzhartman/CodingTracker/Startup.cs +++ b/codingTracker.jzhartman/CodingTracker/Startup.cs @@ -19,6 +19,8 @@ public static IServiceProvider ConfigureServices() var connectionString = ConfigurationManager.AppSettings.Get("connectionString"); var dateTimeFormat = ConfigurationManager.AppSettings.Get("timeAndDateFormat"); + SqlMapper.RemoveTypeMap(typeof(DateTime)); + SqlMapper.RemoveTypeMap(typeof(DateTime?)); SqlMapper.AddTypeHandler(new DateTimeHandler(dateTimeFormat)); var services = new ServiceCollection(); From 13300efe3372e519030ee790e2a16e8937df9eec Mon Sep 17 00:00:00 2001 From: jzhartman Date: Thu, 28 Aug 2025 22:53:31 -0400 Subject: [PATCH 12/57] Added UpdateByEndTime method --- .../MenuController.cs | 6 ++++-- .../Interfaces/ICodingSessionRepository.cs | 1 + .../Parameters/EndTimeUpdate.cs | 14 ++++++++++++++ .../Repositories/CodingSessionRepository.cs | 19 +++++++++++++------ .../CodingSessionService.cs | 7 ++++++- .../Interfaces/ICodingSessionService.cs | 3 ++- 6 files changed, 40 insertions(+), 10 deletions(-) create mode 100644 codingTracker.jzhartman/CodingTracker.Data/Parameters/EndTimeUpdate.cs diff --git a/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs b/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs index ba98a2434..a3eb343c7 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs @@ -48,9 +48,11 @@ public void Run() var sessions = _service.GetByDateRange(startTime, endTime); CodingSessionView.RenderCodingSessions(sessions); - _service.UpdateStartDateById((int)sessions[9].Id, DateTime.Now); + _service.UpdateStartTimeById((int)sessions[9].Id, DateTime.Now); + _service.UpdateEndTimeById((int)sessions[15].Id, DateTime.Now); - sessions = _service.GetByDateRange(startTime, endTime); + + sessions = _service.GetAllCodingSessions(); CodingSessionView.RenderCodingSessions(sessions); //Console.WriteLine(); diff --git a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs index 23bc0116a..7d289f3fb 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs @@ -8,6 +8,7 @@ public interface ICodingSessionRepository List GetAll(); List GetByDateRange(DateTime startTime, DateTime endTime); CodingSession GetById(int id); + void UpdateEndTimeById(int id, DateTime endTime); void UpdateStartTimeById(int id, DateTime startTime); } } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Data/Parameters/EndTimeUpdate.cs b/codingTracker.jzhartman/CodingTracker.Data/Parameters/EndTimeUpdate.cs new file mode 100644 index 000000000..26214d8cd --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Data/Parameters/EndTimeUpdate.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CodingTracker.Data.Parameters +{ + public class EndTimeUpdate + { + public int Id { get; set; } + public DateTime EndTime { get; set; } + } +} diff --git a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs index 6efcaed88..dfdebbd86 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs @@ -38,18 +38,25 @@ public void DeleteById(int id) SaveData(sql); } + public void UpdateStartTimeById(int id, DateTime startTime) + { + var parameters = new StartTimeUpdate { Id = id, StartTime = startTime }; + string sql = "update CodingSessions Set StartTime = @StartTime where Id = @Id"; + SaveData(sql, parameters); + } + public void UpdateEndTimeById(int id, DateTime endTime) + { + var parameters = new EndTimeUpdate { Id = id, EndTime = endTime }; + string sql = "update CodingSessions Set EndTime = @EndTime where Id = @Id"; + SaveData(sql, parameters); + } //Not Working..... - public void UpdateStartTimeById(int id, DateTime startTime) - { - var parameters = new StartTimeUpdate {Id = id, StartTime = startTime }; - string sql = "update CodingSessions Set StartTime = @StartTime where Id = @Id"; - SaveData(sql, parameters); - } + public List GetLongestDuration() diff --git a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionService.cs b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionService.cs index 58370083f..4143ae3da 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionService.cs @@ -39,11 +39,16 @@ public void DeleteById(int id) _repository.DeleteById(id); } - public void UpdateStartDateById(int id, DateTime startTime) + public void UpdateStartTimeById(int id, DateTime startTime) { _repository.UpdateStartTimeById(id, startTime); } + public void UpdateEndTimeById(int id, DateTime endTime) + { + _repository.UpdateEndTimeById(id, endTime); + } + //Method to run repo.GetAll then pass data up to View diff --git a/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionService.cs b/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionService.cs index f40e508ed..571fc4b1f 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionService.cs @@ -8,6 +8,7 @@ public interface ICodingSessionService List GetAllCodingSessions(); List GetByDateRange(DateTime startTime, DateTime endTime); CodingSession GetById(int id); - void UpdateStartDateById(int id, DateTime startTime); + void UpdateEndTimeById(int id, DateTime endTime); + void UpdateStartTimeById(int id, DateTime startTime); } } \ No newline at end of file From 2244e2cefb70bb65376a76468f4f064b37779a44 Mon Sep 17 00:00:00 2001 From: jzhartman Date: Thu, 28 Aug 2025 22:58:36 -0400 Subject: [PATCH 13/57] Added ORDER BY to the getall and getbydaterange sql --- .../Repositories/CodingSessionRepository.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs index dfdebbd86..d79b30e25 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs @@ -15,14 +15,14 @@ public CodingSessionRepository(ISqliteConnectionFactory connectionFactory) } public List GetAll() { - string sql = "Select * from CodingSessions"; + string sql = "Select * from CodingSessions order by StartTime"; return LoadData(sql); } public List GetByDateRange(DateTime begin, DateTime finish) { var dateRange = new DateRangeQuery { StartTime = begin, EndTime = finish }; - string sql = @"Select * from CodingSessions where (StartTime >= @StartTime) AND (endTime <= @EndTime)"; + string sql = @"Select * from CodingSessions where (StartTime >= @StartTime) AND (endTime <= @EndTime) order by StartTime"; return LoadData(sql, dateRange); } From a12183d04d2cff98a6767ca751d730768abe4e5c Mon Sep 17 00:00:00 2001 From: jzhartman Date: Fri, 29 Aug 2025 22:20:29 -0400 Subject: [PATCH 14/57] Created insert method --- .../CodingTracker.Controller/MenuController.cs | 15 +++++++++++---- .../Interfaces/ICodingSessionRepository.cs | 1 + .../Repositories/CodingSessionRepository.cs | 6 ++++++ .../Entities/CodingSession.cs | 6 ++++++ ...sionService.cs => CodingSessionDataService.cs} | 9 +++++++-- ...ionService.cs => ICodingSessionDataService.cs} | 3 ++- codingTracker.jzhartman/CodingTracker/Startup.cs | 2 +- 7 files changed, 34 insertions(+), 8 deletions(-) rename codingTracker.jzhartman/CodingTracker.Services/{CodingSessionService.cs => CodingSessionDataService.cs} (84%) rename codingTracker.jzhartman/CodingTracker.Services/Interfaces/{ICodingSessionService.cs => ICodingSessionDataService.cs} (81%) diff --git a/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs b/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs index a3eb343c7..f515f6623 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs @@ -1,4 +1,5 @@ using CodingTracker.Controller.Interfaces; +using CodingTracker.Models.Entities; using CodingTracker.Services; using CodingTracker.Services.Interfaces; using CodingTracker.Views; @@ -12,9 +13,9 @@ namespace CodingTracker.Controller { public class MenuController : IMenuController { - private readonly ICodingSessionService _service; + private readonly ICodingSessionDataService _service; - public MenuController(ICodingSessionService service) + public MenuController(ICodingSessionDataService service) { _service = service; } @@ -48,9 +49,15 @@ public void Run() var sessions = _service.GetByDateRange(startTime, endTime); CodingSessionView.RenderCodingSessions(sessions); - _service.UpdateStartTimeById((int)sessions[9].Id, DateTime.Now); - _service.UpdateEndTimeById((int)sessions[15].Id, DateTime.Now); + //_service.UpdateStartTimeById((int)sessions[9].Id, DateTime.Now); + //_service.UpdateEndTimeById((int)sessions[15].Id, DateTime.Now); + startTime = DateTime.Parse("2025-01-01 20:00:00"); + endTime = DateTime.Parse("2025-01-01 20:01:15"); + + int duration = (int)endTime.Subtract(startTime).TotalSeconds; + + _service.AddSession(new CodingSession{StartTime = startTime, EndTime = endTime, Duration = duration}); sessions = _service.GetAllCodingSessions(); CodingSessionView.RenderCodingSessions(sessions); diff --git a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs index 7d289f3fb..2c141d2a1 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs @@ -4,6 +4,7 @@ namespace CodingTracker.Data.Interfaces { public interface ICodingSessionRepository { + void AddSession(CodingSession session); void DeleteById(int id); List GetAll(); List GetByDateRange(DateTime startTime, DateTime endTime); diff --git a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs index d79b30e25..cde0b020d 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs @@ -52,6 +52,12 @@ public void UpdateEndTimeById(int id, DateTime endTime) SaveData(sql, parameters); } + public void AddSession(CodingSession session) + { + string sql = "insert into CodingSessions (StartTime, EndTime, Duration) values (@StartTime, @EndTime, @Duration)"; + SaveData(sql, session); + } + //Not Working..... diff --git a/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSession.cs b/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSession.cs index 26e855844..4ca6bfa54 100644 --- a/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSession.cs +++ b/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSession.cs @@ -6,5 +6,11 @@ public class CodingSession public DateTime StartTime { get; set; } public DateTime EndTime { get; set; } public int Duration { get; set; } + + private void CalculateDurationInSeconds() + { + Duration = (int)EndTime.Subtract(StartTime).TotalSeconds; + } + } } diff --git a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionService.cs b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs similarity index 84% rename from codingTracker.jzhartman/CodingTracker.Services/CodingSessionService.cs rename to codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs index 4143ae3da..299fd5d93 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs @@ -10,11 +10,11 @@ namespace CodingTracker.Services { - public class CodingSessionService : ICodingSessionService + public class CodingSessionDataService : ICodingSessionDataService { private readonly ICodingSessionRepository _repository; - public CodingSessionService(ICodingSessionRepository repository) + public CodingSessionDataService(ICodingSessionRepository repository) { _repository = repository; } @@ -49,6 +49,11 @@ public void UpdateEndTimeById(int id, DateTime endTime) _repository.UpdateEndTimeById(id, endTime); } + public void AddSession(CodingSession session) + { + _repository.AddSession(session); + } + //Method to run repo.GetAll then pass data up to View diff --git a/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionService.cs b/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionDataService.cs similarity index 81% rename from codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionService.cs rename to codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionDataService.cs index 571fc4b1f..9173ca25d 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionDataService.cs @@ -2,8 +2,9 @@ namespace CodingTracker.Services.Interfaces { - public interface ICodingSessionService + public interface ICodingSessionDataService { + void AddSession(CodingSession session); void DeleteById(int id); List GetAllCodingSessions(); List GetByDateRange(DateTime startTime, DateTime endTime); diff --git a/codingTracker.jzhartman/CodingTracker/Startup.cs b/codingTracker.jzhartman/CodingTracker/Startup.cs index 7afb054d4..16e982d90 100644 --- a/codingTracker.jzhartman/CodingTracker/Startup.cs +++ b/codingTracker.jzhartman/CodingTracker/Startup.cs @@ -29,7 +29,7 @@ public static IServiceProvider ConfigureServices() services.AddSingleton(); //Register All Services - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); From b518c5271986e6687fd5e9126906a1f2f78c7132 Mon Sep 17 00:00:00 2001 From: jzhartman Date: Fri, 29 Aug 2025 23:19:45 -0400 Subject: [PATCH 15/57] EOD commit Working on overall app flow -- should write it up before continuing Pushing to remote to enable mobile access to structure for writeup on the go --- .../MenuController.cs | 28 ++++++- .../Interfaces/ICodingSessionRepository.cs | 3 +- .../Repositories/CodingSessionRepository.cs | 73 +++++++++---------- .../Entities/CodingSession.cs | 6 -- .../CodingTracker.Views.csproj | 4 + .../CodingTracker.Views/Menus/MainMenuView.cs | 61 ++++++++++++++++ .../CodingTracker.Views/ViewHelpers.cs | 20 +++++ 7 files changed, 145 insertions(+), 50 deletions(-) create mode 100644 codingTracker.jzhartman/CodingTracker.Views/Menus/MainMenuView.cs create mode 100644 codingTracker.jzhartman/CodingTracker.Views/ViewHelpers.cs diff --git a/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs b/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs index f515f6623..bb1aa6a9f 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs @@ -3,6 +3,7 @@ using CodingTracker.Services; using CodingTracker.Services.Interfaces; using CodingTracker.Views; +using CodingTracker.Views.Menus; using System; using System.Collections.Generic; using System.Linq; @@ -25,13 +26,20 @@ public MenuController(ICodingSessionDataService service) public void Run() { - //_view.ShowMainMenu(); - //var selection = _view.GetUserSelection(); - //_service.HandleSelection(selection); + ViewHelpers.RenderWelcome(); + var selection = MainMenuView.RenderMainMenuAndGetSelection(); + + HandleMainMenuSelection(selection); + + //All Code Below Here Is Basic Testing ONLY -- Not part of actual flow + // + + + //var sessions = _service.GetAllCodingSessions(); //CodingSessionView.RenderCodingSessions(sessions); @@ -70,6 +78,18 @@ public void Run() } - + private void HandleMainMenuSelection(string selection) + { + switch (selection) + { + case "Track Coding": + case "Manage Entries": + case "View Reports": + case "Manage Goal": + case "Exit": + default: + break; + } + } } } diff --git a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs index 2c141d2a1..f07cf34e7 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs @@ -5,11 +5,12 @@ namespace CodingTracker.Data.Interfaces public interface ICodingSessionRepository { void AddSession(CodingSession session); - void DeleteById(int id); List GetAll(); List GetByDateRange(DateTime startTime, DateTime endTime); CodingSession GetById(int id); void UpdateEndTimeById(int id, DateTime endTime); void UpdateStartTimeById(int id, DateTime startTime); + void DeleteById(int id); + } } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs index cde0b020d..61f888360 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs @@ -13,54 +13,73 @@ public CodingSessionRepository(ISqliteConnectionFactory connectionFactory) { _connectionFactory = connectionFactory; } + private List LoadData(string sql) + { + using var connection = _connectionFactory.CreateConnection(); + List sessions = connection.Query(sql).ToList(); + return sessions; + } + private List LoadData(string sql, U parameters) + { + using var connection = _connectionFactory.CreateConnection(); + List sessions = connection.Query(sql, parameters).ToList(); + return sessions; + } + private void SaveData(string sql) + { + using var connection = _connectionFactory.CreateConnection(); + connection.Execute(sql); + } + private void SaveData(string sql, T parameters) + { + using var connection = _connectionFactory.CreateConnection(); + connection.Execute(sql, parameters); + } + + public void AddSession(CodingSession session) + { + string sql = "insert into CodingSessions (StartTime, EndTime, Duration) values (@StartTime, @EndTime, @Duration)"; + SaveData(sql, session); + } public List GetAll() { string sql = "Select * from CodingSessions order by StartTime"; return LoadData(sql); } - public List GetByDateRange(DateTime begin, DateTime finish) { var dateRange = new DateRangeQuery { StartTime = begin, EndTime = finish }; string sql = @"Select * from CodingSessions where (StartTime >= @StartTime) AND (endTime <= @EndTime) order by StartTime"; return LoadData(sql, dateRange); } - public CodingSession GetById(int id) { string sql = $"select * from CodingSessions where Id = {id}"; return LoadData(sql).FirstOrDefault(); } - - public void DeleteById(int id) - { - string sql = $"delete from CodingSessions where Id = {id}"; - SaveData(sql); - } - public void UpdateStartTimeById(int id, DateTime startTime) { var parameters = new StartTimeUpdate { Id = id, StartTime = startTime }; string sql = "update CodingSessions Set StartTime = @StartTime where Id = @Id"; SaveData(sql, parameters); } - public void UpdateEndTimeById(int id, DateTime endTime) { var parameters = new EndTimeUpdate { Id = id, EndTime = endTime }; string sql = "update CodingSessions Set EndTime = @EndTime where Id = @Id"; SaveData(sql, parameters); } - - public void AddSession(CodingSession session) + public void DeleteById(int id) { - string sql = "insert into CodingSessions (StartTime, EndTime, Duration) values (@StartTime, @EndTime, @Duration)"; - SaveData(sql, session); + string sql = $"delete from CodingSessions where Id = {id}"; + SaveData(sql); } - //Not Working..... + + + //Method Stubs to work out eventually... If needed? @@ -71,30 +90,6 @@ public List GetLongestDuration() return LoadData(sql); } - private List LoadData(string sql) - { - using var connection = _connectionFactory.CreateConnection(); - List sessions = connection.Query(sql).ToList(); - return sessions; - } - private List LoadData(string sql, U parameters) - { - using var connection = _connectionFactory.CreateConnection(); - List sessions = connection.Query(sql, parameters).ToList(); - return sessions; - } - - private void SaveData(string sql) - { - using var connection = _connectionFactory.CreateConnection(); - connection.Execute(sql); - } - - private void SaveData(string sql, T parameters) - { - using var connection = _connectionFactory.CreateConnection(); - connection.Execute(sql, parameters); - } // Method here using the following snippet diff --git a/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSession.cs b/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSession.cs index 4ca6bfa54..26e855844 100644 --- a/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSession.cs +++ b/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSession.cs @@ -6,11 +6,5 @@ public class CodingSession public DateTime StartTime { get; set; } public DateTime EndTime { get; set; } public int Duration { get; set; } - - private void CalculateDurationInSeconds() - { - Duration = (int)EndTime.Subtract(StartTime).TotalSeconds; - } - } } diff --git a/codingTracker.jzhartman/CodingTracker.Views/CodingTracker.Views.csproj b/codingTracker.jzhartman/CodingTracker.Views/CodingTracker.Views.csproj index 27b309a28..348b7bfc6 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/CodingTracker.Views.csproj +++ b/codingTracker.jzhartman/CodingTracker.Views/CodingTracker.Views.csproj @@ -6,6 +6,10 @@ enable + + + + diff --git a/codingTracker.jzhartman/CodingTracker.Views/Menus/MainMenuView.cs b/codingTracker.jzhartman/CodingTracker.Views/Menus/MainMenuView.cs new file mode 100644 index 000000000..b0d861d41 --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Views/Menus/MainMenuView.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Spectre.Console; + +namespace CodingTracker.Views.Menus +{ + public static class MainMenuView + { + public static string RenderMainMenuAndGetSelection() + { + var selection = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("Select from the options below:") + .AddChoices(new[] + { + "Track Coding", + "Manage Entries", + "View Reports", + "Manage Goal", + "Exit" + }) + ); + + return selection; + } + + + + // Move methods below to a different class soon.... Separate out the menus and their display logic? + + public static void RenderTrackMenu() + { + var mainMenu = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("How would you like to track your coding session?") + .AddChoices(new[] + { + "Enter Start and End Times", + "Begin Timer", + "Return to Main Menu" + }) + ); + } + public static void RenderMenu() + { + var mainMenu = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("How would you like to track your coding session?") + .AddChoices(new[] + { + "Enter Start and End Times", + "Begin Timer", + "Return to Main Menu" + }) + ); + } + } +} diff --git a/codingTracker.jzhartman/CodingTracker.Views/ViewHelpers.cs b/codingTracker.jzhartman/CodingTracker.Views/ViewHelpers.cs new file mode 100644 index 000000000..1be731aba --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Views/ViewHelpers.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Spectre.Console; + +namespace CodingTracker.Views +{ + public static class ViewHelpers + { + public static void RenderWelcome() + { + AnsiConsole.Markup("[bold blue]CODING TRACKER[/]"); + AnsiConsole.Markup("[bold blue]Version 1.0[/]"); + AnsiConsole.Write(new Rule()); + + } + } +} From 86e76146b7abfd53e8f49b32be1c52461a3c2ab1 Mon Sep 17 00:00:00 2001 From: jzhartman Date: Fri, 29 Aug 2025 23:23:00 -0400 Subject: [PATCH 16/57] Cleaned up an unused and obsolete project file Re-pushing to remote --- .../CodingTracker.View/CodingTracker.View.csproj | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 codingTracker.jzhartman/CodingTracker.View/CodingTracker.View.csproj diff --git a/codingTracker.jzhartman/CodingTracker.View/CodingTracker.View.csproj b/codingTracker.jzhartman/CodingTracker.View/CodingTracker.View.csproj deleted file mode 100644 index fa71b7ae6..000000000 --- a/codingTracker.jzhartman/CodingTracker.View/CodingTracker.View.csproj +++ /dev/null @@ -1,9 +0,0 @@ - - - - net8.0 - enable - enable - - - From dd93f377888421f08499674693906f6e66194f0f Mon Sep 17 00:00:00 2001 From: jzhartman Date: Sun, 31 Aug 2025 22:52:17 -0400 Subject: [PATCH 17/57] Added ValidationResult class for passing results between layers --- .../MenuController.cs | 24 ++++++------- .../Repositories/CodingSessionRepository.cs | 15 ++++++++ .../CodingTracker.Models.csproj | 4 --- .../Validation/ValidationResult.cs | 35 +++++++++++++++++++ .../CodingSessionDataService.cs | 3 ++ 5 files changed, 65 insertions(+), 16 deletions(-) create mode 100644 codingTracker.jzhartman/CodingTracker.Models/Validation/ValidationResult.cs diff --git a/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs b/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs index bb1aa6a9f..cd037a68f 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs @@ -29,7 +29,7 @@ public void Run() ViewHelpers.RenderWelcome(); var selection = MainMenuView.RenderMainMenuAndGetSelection(); - HandleMainMenuSelection(selection); + HandleSelection(selection); @@ -52,23 +52,23 @@ public void Run() //Console.WriteLine(); - var startTime = DateTime.Parse("2025-01-07 00:00:00.1234"); - var endTime = DateTime.Parse("2025-02-01 23:59:59"); - var sessions = _service.GetByDateRange(startTime, endTime); - CodingSessionView.RenderCodingSessions(sessions); + //var startTime = DateTime.Parse("2025-01-07 00:00:00.1234"); + //var endTime = DateTime.Parse("2025-02-01 23:59:59"); + //var sessions = _service.GetByDateRange(startTime, endTime); + //CodingSessionView.RenderCodingSessions(sessions); //_service.UpdateStartTimeById((int)sessions[9].Id, DateTime.Now); //_service.UpdateEndTimeById((int)sessions[15].Id, DateTime.Now); - startTime = DateTime.Parse("2025-01-01 20:00:00"); - endTime = DateTime.Parse("2025-01-01 20:01:15"); + //startTime = DateTime.Parse("2025-01-01 20:00:00"); + //endTime = DateTime.Parse("2025-01-01 20:01:15"); - int duration = (int)endTime.Subtract(startTime).TotalSeconds; + //int duration = (int)endTime.Subtract(startTime).TotalSeconds; - _service.AddSession(new CodingSession{StartTime = startTime, EndTime = endTime, Duration = duration}); + //_service.AddSession(new CodingSession{StartTime = startTime, EndTime = endTime, Duration = duration}); - sessions = _service.GetAllCodingSessions(); - CodingSessionView.RenderCodingSessions(sessions); + //sessions = _service.GetAllCodingSessions(); + //CodingSessionView.RenderCodingSessions(sessions); //Console.WriteLine(); //var session = _service.GetById(2); @@ -78,7 +78,7 @@ public void Run() } - private void HandleMainMenuSelection(string selection) + private void HandleSelection(string selection) { switch (selection) { diff --git a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs index 61f888360..b851d0e0a 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs @@ -76,6 +76,21 @@ public void DeleteById(int id) } + // NOT IMPLEMENTED YET!!!!! + + + public bool ExistsByTimeFrame(DateTime time) + { + if (true ) // If the current startTime or endTime is already included (i.e. >= StartTime && <= EndTime) + { + return true; + } + else + { + return false; + } + } + diff --git a/codingTracker.jzhartman/CodingTracker.Models/CodingTracker.Models.csproj b/codingTracker.jzhartman/CodingTracker.Models/CodingTracker.Models.csproj index b80b1ca59..fa71b7ae6 100644 --- a/codingTracker.jzhartman/CodingTracker.Models/CodingTracker.Models.csproj +++ b/codingTracker.jzhartman/CodingTracker.Models/CodingTracker.Models.csproj @@ -6,8 +6,4 @@ enable - - - - diff --git a/codingTracker.jzhartman/CodingTracker.Models/Validation/ValidationResult.cs b/codingTracker.jzhartman/CodingTracker.Models/Validation/ValidationResult.cs new file mode 100644 index 000000000..571912a9f --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Models/Validation/ValidationResult.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CodingTracker.Models.Validation +{ + public class ValidationResult + { + public bool IsValid { get;} + public T? Value { get;} + public string? Parameter { get;} + public string? Message { get;} + + private ValidationResult(bool isValid, T? value,string parameter, string message) + { + IsValid = isValid; + Value = value; + Parameter = parameter; + Message = message; + } + + public static ValidationResult Success(T value) + { + return new ValidationResult(true, value, default, default); + } + + public static ValidationResult Fail(string parameter, string message) + { + return new ValidationResult(false, default, parameter, message); + } + + } +} diff --git a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs index 299fd5d93..571a26915 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs @@ -1,6 +1,7 @@ using CodingTracker.Data.Interfaces; using CodingTracker.Data.Repositories; using CodingTracker.Models.Entities; +using CodingTracker.Models.Validation; using CodingTracker.Services.Interfaces; using System; using System.Collections.Generic; @@ -31,6 +32,8 @@ public List GetByDateRange(DateTime startTime, DateTime endTime) public CodingSession GetById(int id) { + // Validate that ID exists + return _repository.GetById(id); } From b5560323b16f4254e9158a32cad506dc235bbf0b Mon Sep 17 00:00:00 2001 From: jzhartman Date: Sun, 31 Aug 2025 23:31:31 -0400 Subject: [PATCH 18/57] Added new controller for tracksession as stub for flow --- ...nuController.cs => IMainMenuController.cs} | 2 +- .../Interfaces/ITrackSessionController.cs | 9 +++ ...enuController.cs => MainMenuController.cs} | 47 +++++++++------ .../TrackSessionController.cs | 59 +++++++++++++++++++ .../CodingTracker.Views/Menus/MainMenuView.cs | 16 +---- .../Menus/TrackSessionView.cs | 28 +++++++++ .../CodingTracker/Program.cs | 2 +- .../CodingTracker/Startup.cs | 3 +- 8 files changed, 130 insertions(+), 36 deletions(-) rename codingTracker.jzhartman/CodingTracker.Controller/Interfaces/{IMenuController.cs => IMainMenuController.cs} (67%) create mode 100644 codingTracker.jzhartman/CodingTracker.Controller/Interfaces/ITrackSessionController.cs rename codingTracker.jzhartman/CodingTracker.Controller/{MenuController.cs => MainMenuController.cs} (63%) create mode 100644 codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs create mode 100644 codingTracker.jzhartman/CodingTracker.Views/Menus/TrackSessionView.cs diff --git a/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/IMenuController.cs b/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/IMainMenuController.cs similarity index 67% rename from codingTracker.jzhartman/CodingTracker.Controller/Interfaces/IMenuController.cs rename to codingTracker.jzhartman/CodingTracker.Controller/Interfaces/IMainMenuController.cs index ab750cd5e..c2dd51195 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/IMenuController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/IMainMenuController.cs @@ -1,6 +1,6 @@ namespace CodingTracker.Controller.Interfaces { - public interface IMenuController + public interface IMainMenuController { void Run(); } diff --git a/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/ITrackSessionController.cs b/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/ITrackSessionController.cs new file mode 100644 index 000000000..745139701 --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/ITrackSessionController.cs @@ -0,0 +1,9 @@ +namespace CodingTracker.Controller.Interfaces +{ + public interface ITrackSessionController + { + void GetStartAndEndTimes(); + void GetTimesWithStopwatch(); + void Run(); + } +} \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs b/codingTracker.jzhartman/CodingTracker.Controller/MainMenuController.cs similarity index 63% rename from codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs rename to codingTracker.jzhartman/CodingTracker.Controller/MainMenuController.cs index cd037a68f..b48644130 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/MenuController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/MainMenuController.cs @@ -12,13 +12,15 @@ namespace CodingTracker.Controller { - public class MenuController : IMenuController + public class MainMenuController : IMainMenuController { private readonly ICodingSessionDataService _service; + private readonly ITrackSessionController _trackController; - public MenuController(ICodingSessionDataService service) + public MainMenuController(ICodingSessionDataService service, ITrackSessionController trackController) { _service = service; + _trackController = trackController; } // Call services for business/data @@ -26,11 +28,32 @@ public MenuController(ICodingSessionDataService service) public void Run() { - ViewHelpers.RenderWelcome(); - var selection = MainMenuView.RenderMainMenuAndGetSelection(); + bool exitApp = false; - HandleSelection(selection); + while (!exitApp) + { + ViewHelpers.RenderWelcome(); + var selection = MainMenuView.RenderMainMenuAndGetSelection(); + + switch (selection) + { + case "Track Session": // Submenu: Enter times/Stopwatch/Return + _trackController.Run(); + break; + case "Manage Entries": // Submenu: View Entries (range or all)/Update/Delete + break; + case "View Reports": // Enter Range-Period??? --> Print all records for period --> Print report data + break; + case "Manage Goal": // Print current goal+progress/Give option to change goal + break; + case "Exit": // Generic goodbye message + exitApp = true; + break; + default: + break; + } + } //All Code Below Here Is Basic Testing ONLY -- Not part of actual flow @@ -77,19 +100,5 @@ public void Run() } - - private void HandleSelection(string selection) - { - switch (selection) - { - case "Track Coding": - case "Manage Entries": - case "View Reports": - case "Manage Goal": - case "Exit": - default: - break; - } - } } } diff --git a/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs b/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs new file mode 100644 index 000000000..07bb102ff --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs @@ -0,0 +1,59 @@ +using CodingTracker.Controller.Interfaces; +using CodingTracker.Services.Interfaces; +using CodingTracker.Views; +using CodingTracker.Views.Menus; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CodingTracker.Controller +{ + public class TrackSessionController : ITrackSessionController + { + private readonly ICodingSessionDataService _service; + + public TrackSessionController(ICodingSessionDataService service) + { + _service = service; + } + + public void Run() + { + bool returnToMainMenu = false; + + while (!returnToMainMenu) + { + + ViewHelpers.RenderWelcome(); + var selection = TrackSessionView.RenderTrackSessionMenuAndGetSelection(); + + switch (selection) + { + case "Enter Start and End Times": + GetStartAndEndTimes(); + break; + case "Begin Timer": + GetTimesWithStopwatch(); + break; + case "Return to Main Menu": + returnToMainMenu = true; + break; + default: + break; + } + } + } + + public void GetStartAndEndTimes() + { + // Code here + } + + public void GetTimesWithStopwatch() + { + // Code here + } + } +} diff --git a/codingTracker.jzhartman/CodingTracker.Views/Menus/MainMenuView.cs b/codingTracker.jzhartman/CodingTracker.Views/Menus/MainMenuView.cs index b0d861d41..bb9d5d646 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/Menus/MainMenuView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/Menus/MainMenuView.cs @@ -16,7 +16,7 @@ public static string RenderMainMenuAndGetSelection() .Title("Select from the options below:") .AddChoices(new[] { - "Track Coding", + "Track Session", "Manage Entries", "View Reports", "Manage Goal", @@ -31,19 +31,7 @@ public static string RenderMainMenuAndGetSelection() // Move methods below to a different class soon.... Separate out the menus and their display logic? - public static void RenderTrackMenu() - { - var mainMenu = AnsiConsole.Prompt( - new SelectionPrompt() - .Title("How would you like to track your coding session?") - .AddChoices(new[] - { - "Enter Start and End Times", - "Begin Timer", - "Return to Main Menu" - }) - ); - } + public static void RenderMenu() { var mainMenu = AnsiConsole.Prompt( diff --git a/codingTracker.jzhartman/CodingTracker.Views/Menus/TrackSessionView.cs b/codingTracker.jzhartman/CodingTracker.Views/Menus/TrackSessionView.cs new file mode 100644 index 000000000..27aaf3ae5 --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Views/Menus/TrackSessionView.cs @@ -0,0 +1,28 @@ +using Spectre.Console; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CodingTracker.Views.Menus +{ + public class TrackSessionView + { + public static string RenderTrackSessionMenuAndGetSelection() + { + var selection = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("How would you like to track your coding session?") + .AddChoices(new[] + { + "Enter Start and End Times", + "Begin Timer", + "Return to Main Menu" + }) + ); + + return selection; + } + } +} diff --git a/codingTracker.jzhartman/CodingTracker/Program.cs b/codingTracker.jzhartman/CodingTracker/Program.cs index 8bce8ab22..f7c53c558 100644 --- a/codingTracker.jzhartman/CodingTracker/Program.cs +++ b/codingTracker.jzhartman/CodingTracker/Program.cs @@ -12,6 +12,6 @@ static void Main(string[] args) var serviceProvider = Startup.ConfigureServices(); serviceProvider.GetRequiredService().Initialize(); - serviceProvider.GetRequiredService().Run(); + serviceProvider.GetRequiredService().Run(); } } diff --git a/codingTracker.jzhartman/CodingTracker/Startup.cs b/codingTracker.jzhartman/CodingTracker/Startup.cs index 16e982d90..dec0706db 100644 --- a/codingTracker.jzhartman/CodingTracker/Startup.cs +++ b/codingTracker.jzhartman/CodingTracker/Startup.cs @@ -26,7 +26,8 @@ public static IServiceProvider ConfigureServices() var services = new ServiceCollection(); //Register All Controllers - services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); //Register All Services services.AddSingleton(); From 46e79423577ec0b1e7c9f7bf9e3f45b0d9aa4ec8 Mon Sep 17 00:00:00 2001 From: jzhartman Date: Wed, 3 Sep 2025 23:25:16 -0400 Subject: [PATCH 19/57] Can enter start and end times with validation Next step is adding session --- .../Interfaces/ITrackSessionController.cs | 6 +- .../MainMenuController.cs | 14 ++-- .../TrackSessionController.cs | 75 +++++++++++++++++-- .../Interfaces/ICodingSessionRepository.cs | 8 +- .../Parameters/DateValue.cs | 13 ++++ .../Repositories/CodingSessionRepository.cs | 61 +++++++++------ .../Entities/CodingSession.cs | 23 +++++- .../Entities/CodingSessionDataRecord.cs | 10 +++ .../CodingSessionDataService.cs | 37 ++++++--- .../Interfaces/ICodingSessionDataService.cs | 11 ++- .../CodingTracker.Views/CodingSessionView.cs | 4 +- .../Interfaces/IMainMenuView.cs | 7 ++ .../Interfaces/ITrackSessionView.cs | 11 +++ .../CodingTracker.Views/Menus/MainMenuView.cs | 7 +- .../Menus/TrackSessionView.cs | 57 +++++++++++++- .../CodingTracker/Startup.cs | 5 ++ 16 files changed, 281 insertions(+), 68 deletions(-) create mode 100644 codingTracker.jzhartman/CodingTracker.Data/Parameters/DateValue.cs create mode 100644 codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSessionDataRecord.cs create mode 100644 codingTracker.jzhartman/CodingTracker.Views/Interfaces/IMainMenuView.cs create mode 100644 codingTracker.jzhartman/CodingTracker.Views/Interfaces/ITrackSessionView.cs diff --git a/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/ITrackSessionController.cs b/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/ITrackSessionController.cs index 745139701..ff52cb1bf 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/ITrackSessionController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/ITrackSessionController.cs @@ -1,8 +1,10 @@ -namespace CodingTracker.Controller.Interfaces + +namespace CodingTracker.Controller.Interfaces { public interface ITrackSessionController { - void GetStartAndEndTimes(); + DateTime GetEndTime(DateTime startTime); + DateTime GetStartTime(); void GetTimesWithStopwatch(); void Run(); } diff --git a/codingTracker.jzhartman/CodingTracker.Controller/MainMenuController.cs b/codingTracker.jzhartman/CodingTracker.Controller/MainMenuController.cs index b48644130..4df9aef1b 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/MainMenuController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/MainMenuController.cs @@ -3,6 +3,7 @@ using CodingTracker.Services; using CodingTracker.Services.Interfaces; using CodingTracker.Views; +using CodingTracker.Views.Interfaces; using CodingTracker.Views.Menus; using System; using System.Collections.Generic; @@ -16,16 +17,15 @@ public class MainMenuController : IMainMenuController { private readonly ICodingSessionDataService _service; private readonly ITrackSessionController _trackController; + private readonly IMainMenuView _view; - public MainMenuController(ICodingSessionDataService service, ITrackSessionController trackController) + public MainMenuController(ICodingSessionDataService service, ITrackSessionController trackController, IMainMenuView view) { _service = service; _trackController = trackController; + _view = view; } - // Call services for business/data - // Call views to render info - public void Run() { bool exitApp = false; @@ -34,7 +34,7 @@ public void Run() { ViewHelpers.RenderWelcome(); - var selection = MainMenuView.RenderMainMenuAndGetSelection(); + var selection = _view.RenderMenuAndGetSelection(); switch (selection) { @@ -59,10 +59,6 @@ public void Run() //All Code Below Here Is Basic Testing ONLY -- Not part of actual flow - // - - - //var sessions = _service.GetAllCodingSessions(); //CodingSessionView.RenderCodingSessions(sessions); diff --git a/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs b/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs index 07bb102ff..dfcc7cdf2 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs @@ -1,6 +1,7 @@ using CodingTracker.Controller.Interfaces; using CodingTracker.Services.Interfaces; using CodingTracker.Views; +using CodingTracker.Views.Interfaces; using CodingTracker.Views.Menus; using System; using System.Collections.Generic; @@ -13,10 +14,12 @@ namespace CodingTracker.Controller public class TrackSessionController : ITrackSessionController { private readonly ICodingSessionDataService _service; + private readonly ITrackSessionView _view; - public TrackSessionController(ICodingSessionDataService service) + public TrackSessionController(ICodingSessionDataService service, ITrackSessionView view) { _service = service; + _view = view; } public void Run() @@ -27,12 +30,15 @@ public void Run() { ViewHelpers.RenderWelcome(); - var selection = TrackSessionView.RenderTrackSessionMenuAndGetSelection(); + var selection = _view.RenderMenuAndGetSelection(); switch (selection) { case "Enter Start and End Times": - GetStartAndEndTimes(); + var startTime = GetStartTime(); + var endTime = GetEndTime(startTime); + + break; case "Begin Timer": GetTimesWithStopwatch(); @@ -46,14 +52,73 @@ public void Run() } } - public void GetStartAndEndTimes() + public DateTime GetStartTime() { - // Code here + var output = new DateTime(); + bool startTimeValid = false; + + while (startTimeValid == false) + { + output = _view.GetStartTimeFromUser(); + + var result = _service.ValidateStartTime(output); + + if (result.IsValid) + { + startTimeValid = true; + output = result.Value; + _view.ConfirmationMessage(result.Value.ToString("yyyy-MM-dd HH:mm:ss")); + } + else + { + _view.ErrorMessage(result.Parameter, result.Message); + } + } + return output; + } + + public DateTime GetEndTime(DateTime startTime) + { + var output = new DateTime(); + bool endTimeValid = false; + + while (endTimeValid == false) + { + output = _view.GetEndTimeFromUser(); + + if (output <= startTime) + { + var parameter = "End Time"; + var message = $"The end time must be later than {startTime.ToString("yyyy-MM-dd HH:mm:ss")}"; + _view.ErrorMessage(parameter, message); + } + else + { + var result = _service.ValidateEndTime(output); + + if (result.IsValid) + { + endTimeValid = true; + output = result.Value; + _view.ConfirmationMessage(result.Value.ToString("yyyy-MM-dd HH:mm:ss")); + } + else + { + _view.ErrorMessage(result.Parameter, result.Message); + } + } + } + return output; } public void GetTimesWithStopwatch() { // Code here } + + public void AddRecord() + { + + } } } diff --git a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs index f07cf34e7..f26fe2f8d 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs @@ -5,12 +5,12 @@ namespace CodingTracker.Data.Interfaces public interface ICodingSessionRepository { void AddSession(CodingSession session); - List GetAll(); - List GetByDateRange(DateTime startTime, DateTime endTime); - CodingSession GetById(int id); + List GetAll(); + List GetByDateRange(DateTime startTime, DateTime endTime); + CodingSessionDataRecord GetById(int id); void UpdateEndTimeById(int id, DateTime endTime); void UpdateStartTimeById(int id, DateTime startTime); void DeleteById(int id); - + bool ExistsWithinTimeFrame(DateTime time); } } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Data/Parameters/DateValue.cs b/codingTracker.jzhartman/CodingTracker.Data/Parameters/DateValue.cs new file mode 100644 index 000000000..262638216 --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Data/Parameters/DateValue.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CodingTracker.Data.Parameters +{ + public class DateValue + { + public DateTime Time { get; set; } + } +} diff --git a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs index b851d0e0a..4439f85e2 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs @@ -13,6 +13,7 @@ public CodingSessionRepository(ISqliteConnectionFactory connectionFactory) { _connectionFactory = connectionFactory; } + private List LoadData(string sql) { using var connection = _connectionFactory.CreateConnection(); @@ -36,27 +37,41 @@ private void SaveData(string sql, T parameters) connection.Execute(sql, parameters); } - public void AddSession(CodingSession session) - { - string sql = "insert into CodingSessions (StartTime, EndTime, Duration) values (@StartTime, @EndTime, @Duration)"; - SaveData(sql, session); - } - public List GetAll() + + + public List GetAll() { string sql = "Select * from CodingSessions order by StartTime"; - return LoadData(sql); + return LoadData(sql); } - public List GetByDateRange(DateTime begin, DateTime finish) + public List GetByDateRange(DateTime begin, DateTime finish) { var dateRange = new DateRangeQuery { StartTime = begin, EndTime = finish }; string sql = @"Select * from CodingSessions where (StartTime >= @StartTime) AND (endTime <= @EndTime) order by StartTime"; - return LoadData(sql, dateRange); + return LoadData(sql, dateRange); } - public CodingSession GetById(int id) + public CodingSessionDataRecord GetById(int id) { string sql = $"select * from CodingSessions where Id = {id}"; - return LoadData(sql).FirstOrDefault(); + return LoadData(sql).FirstOrDefault(); + } + + + + + public void AddSession(CodingSession session) + { + string sql = "insert into CodingSessions (StartTime, EndTime, Duration) values (@StartTime, @EndTime, @Duration)"; + SaveData(sql, session); } + + + + // + // + // UPDATE METHODS WILL NEED TO ACCOUNT FOR A CHANGE IN DURATION!!!!!!!!!!!!!! + // + // public void UpdateStartTimeById(int id, DateTime startTime) { var parameters = new StartTimeUpdate { Id = id, StartTime = startTime }; @@ -79,16 +94,18 @@ public void DeleteById(int id) // NOT IMPLEMENTED YET!!!!! - public bool ExistsByTimeFrame(DateTime time) + public bool ExistsWithinTimeFrame(DateTime time) { - if (true ) // If the current startTime or endTime is already included (i.e. >= StartTime && <= EndTime) - { - return true; - } - else - { - return false; - } + var parameter = new DateValue { Time = time}; + using var connection = _connectionFactory.CreateConnection(); + + string sql = @"select count(1) from CodingSessions + where StartTime <= @Time + and EndTime >= @Time"; + + int count = connection.ExecuteScalar(sql, parameter); + + return (count > 0); } @@ -99,10 +116,10 @@ public bool ExistsByTimeFrame(DateTime time) - public List GetLongestDuration() + public List GetLongestDuration() { string sql = $"select * from CodingSessions where "; - return LoadData(sql); + return LoadData(sql); } diff --git a/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSession.cs b/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSession.cs index 26e855844..8a91a003b 100644 --- a/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSession.cs +++ b/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSession.cs @@ -1,10 +1,27 @@ -namespace CodingTracker.Models.Entities +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CodingTracker.Models.Entities { public class CodingSession { - public long Id { get; set; } public DateTime StartTime { get; set; } public DateTime EndTime { get; set; } - public int Duration { get; set; } + public long Duration { get; set; } + + public CodingSession(DateTime startTime, DateTime endTime) + { + StartTime = startTime; + EndTime = endTime; + CalculateDuration(); + } + + private void CalculateDuration() + { + Duration = (long)EndTime.Subtract(StartTime).TotalSeconds; + } } } diff --git a/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSessionDataRecord.cs b/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSessionDataRecord.cs new file mode 100644 index 000000000..7427099e2 --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSessionDataRecord.cs @@ -0,0 +1,10 @@ +namespace CodingTracker.Models.Entities +{ + public class CodingSessionDataRecord + { + public long Id { get; set; } + public DateTime StartTime { get; set; } + public DateTime EndTime { get; set; } + public int Duration { get; set; } + } +} diff --git a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs index 571a26915..7b33ca563 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using static System.Collections.Specialized.BitVector32; namespace CodingTracker.Services { @@ -20,17 +21,17 @@ public CodingSessionDataService(ICodingSessionRepository repository) _repository = repository; } - public List GetAllCodingSessions() + public List GetAllCodingSessions() { return _repository.GetAll(); } - public List GetByDateRange(DateTime startTime, DateTime endTime) + public List GetByDateRange(DateTime startTime, DateTime endTime) { return _repository.GetByDateRange(startTime, endTime); } - public CodingSession GetById(int id) + public CodingSessionDataRecord GetById(int id) { // Validate that ID exists @@ -52,18 +53,32 @@ public void UpdateEndTimeById(int id, DateTime endTime) _repository.UpdateEndTimeById(id, endTime); } - public void AddSession(CodingSession session) + public ValidationResult AddSession(CodingSession session) { + + //if(_repository.ExistsByTimeFrame())//If session.StartTime is within the bounds of an existing + //Other validation to add in later.... _repository.AddSession(session); + return ValidationResult.Success(session); } - //Method to run repo.GetAll then pass data up to View - - - - // Methods for calling repo methods - // Used to handle the data and create lists - // Can contain the Console.WriteLine statements -- these will be called in the View later often + public ValidationResult ValidateStartTime(DateTime input) + { + if (_repository.ExistsWithinTimeFrame(input)) + return ValidationResult.Fail("Start Time", "A record already exists for this time"); + else + return ValidationResult.Success(input); + } + public ValidationResult ValidateEndTime(DateTime input) + { + if (_repository.ExistsWithinTimeFrame(input)) + return ValidationResult.Fail("End Time", "A record already exists for this time"); + else + return ValidationResult.Success(input); + } + // if (input.StartTime > session.EndTime) + //return ValidationResult.Fail("Start Time", + // "ERROR: End Time cannot be earlier than Start Time"); } } diff --git a/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionDataService.cs b/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionDataService.cs index 9173ca25d..c82f95840 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionDataService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionDataService.cs @@ -1,15 +1,18 @@ using CodingTracker.Models.Entities; +using CodingTracker.Models.Validation; namespace CodingTracker.Services.Interfaces { public interface ICodingSessionDataService { - void AddSession(CodingSession session); + ValidationResult AddSession(CodingSession session); void DeleteById(int id); - List GetAllCodingSessions(); - List GetByDateRange(DateTime startTime, DateTime endTime); - CodingSession GetById(int id); + List GetAllCodingSessions(); + List GetByDateRange(DateTime startTime, DateTime endTime); + CodingSessionDataRecord GetById(int id); void UpdateEndTimeById(int id, DateTime endTime); void UpdateStartTimeById(int id, DateTime startTime); + ValidationResult ValidateEndTime(DateTime input); + ValidationResult ValidateStartTime(DateTime input); } } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Views/CodingSessionView.cs b/codingTracker.jzhartman/CodingTracker.Views/CodingSessionView.cs index 4b26e7f9d..2d9df53fc 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/CodingSessionView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/CodingSessionView.cs @@ -4,7 +4,7 @@ namespace CodingTracker.Views { public static class CodingSessionView { - public static void RenderCodingSessions(List sessions) + public static void RenderCodingSessions(List sessions) { int count = 1; Console.WriteLine("A list of coding sessions: "); @@ -16,7 +16,7 @@ public static void RenderCodingSessions(List sessions) } } - public static void RenderCodingSession(CodingSession session) + public static void RenderCodingSession(CodingSessionDataRecord session) { Console.WriteLine($"{session.Id}:\t{session.StartTime} to {session.EndTime} for a duration of {session.Duration}"); } diff --git a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IMainMenuView.cs b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IMainMenuView.cs new file mode 100644 index 000000000..da5c4cc69 --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IMainMenuView.cs @@ -0,0 +1,7 @@ +namespace CodingTracker.Views.Interfaces +{ + public interface IMainMenuView + { + string RenderMenuAndGetSelection(); + } +} \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/ITrackSessionView.cs b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/ITrackSessionView.cs new file mode 100644 index 000000000..c69dbdf90 --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/ITrackSessionView.cs @@ -0,0 +1,11 @@ +namespace CodingTracker.Views.Interfaces +{ + public interface ITrackSessionView + { + void ConfirmationMessage(string valueText); + void ErrorMessage(string parameter, string message); + DateTime GetEndTimeFromUser(); + DateTime GetStartTimeFromUser(); + string RenderMenuAndGetSelection(); + } +} \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Views/Menus/MainMenuView.cs b/codingTracker.jzhartman/CodingTracker.Views/Menus/MainMenuView.cs index bb9d5d646..d8cc3f1fd 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/Menus/MainMenuView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/Menus/MainMenuView.cs @@ -3,13 +3,14 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using CodingTracker.Views.Interfaces; using Spectre.Console; namespace CodingTracker.Views.Menus { - public static class MainMenuView + public class MainMenuView : IMainMenuView { - public static string RenderMainMenuAndGetSelection() + public string RenderMenuAndGetSelection() { var selection = AnsiConsole.Prompt( new SelectionPrompt() @@ -32,7 +33,7 @@ public static string RenderMainMenuAndGetSelection() // Move methods below to a different class soon.... Separate out the menus and their display logic? - public static void RenderMenu() + public void RenderMenu() { var mainMenu = AnsiConsole.Prompt( new SelectionPrompt() diff --git a/codingTracker.jzhartman/CodingTracker.Views/Menus/TrackSessionView.cs b/codingTracker.jzhartman/CodingTracker.Views/Menus/TrackSessionView.cs index 27aaf3ae5..1cfe8ba2b 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/Menus/TrackSessionView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/Menus/TrackSessionView.cs @@ -1,4 +1,5 @@ -using Spectre.Console; +using CodingTracker.Views.Interfaces; +using Spectre.Console; using System; using System.Collections.Generic; using System.Linq; @@ -7,9 +8,15 @@ namespace CodingTracker.Views.Menus { - public class TrackSessionView + public class TrackSessionView : ITrackSessionView { - public static string RenderTrackSessionMenuAndGetSelection() + private readonly string _dateFormat; + public TrackSessionView(string dateFormat) + { + _dateFormat = dateFormat; + } + + public string RenderMenuAndGetSelection() { var selection = AnsiConsole.Prompt( new SelectionPrompt() @@ -24,5 +31,49 @@ public static string RenderTrackSessionMenuAndGetSelection() return selection; } + + public DateTime GetStartTimeFromUser() + { + var date = AnsiConsole.Prompt( + new TextPrompt("Please enter a start time using the format [yellow]'yyyy-MM-dd HH:mm:ss'[/]:") + ); + + //Add custom validation for time format + + return date; + } + + public DateTime GetEndTimeFromUser() + { + var date = AnsiConsole.Prompt( + new TextPrompt("Please enter an end time using the format [yellow]'yyyy-MM-dd HH:mm:ss'[/]:") + ); + + //Add custom validation for time format + + return date; + } + + public void ErrorMessage(string parameter, string message) + { + AddNewLines(1); + AnsiConsole.MarkupInterpolated($"[bold red]ERROR:[/] The value for {parameter} encountered the error: [yellow]{message}[/]"); + AddNewLines(2); + } + + public void ConfirmationMessage(string valueText) + { + AddNewLines(1); + AnsiConsole.MarkupInterpolated($"[bold green]ACCEPTED[/]: Value set to {valueText}"); + AddNewLines(2); + } + + private void AddNewLines(int lines) + { + for (int i = 0; i < lines; i++) + { + AnsiConsole.WriteLine(); + } + } } } diff --git a/codingTracker.jzhartman/CodingTracker/Startup.cs b/codingTracker.jzhartman/CodingTracker/Startup.cs index dec0706db..fa53221a9 100644 --- a/codingTracker.jzhartman/CodingTracker/Startup.cs +++ b/codingTracker.jzhartman/CodingTracker/Startup.cs @@ -6,6 +6,8 @@ using CodingTracker.Data.TypeHandlers; using CodingTracker.Services; using CodingTracker.Services.Interfaces; +using CodingTracker.Views.Interfaces; +using CodingTracker.Views.Menus; using Dapper; using Microsoft.Extensions.DependencyInjection; using System.Configuration; @@ -32,6 +34,9 @@ public static IServiceProvider ConfigureServices() //Register All Services services.AddSingleton(); + //Resgister All Views + services.AddSingleton(); + services.AddSingleton(new TrackSessionView(dateTimeFormat)); services.AddSingleton(); services.AddSingleton(provider => new SqliteConnectionFactory(connectionString)); From 8888b1b6308b5c5d4fbebbc9991bfa7f773e5bb7 Mon Sep 17 00:00:00 2001 From: jzhartman Date: Sat, 6 Sep 2025 23:31:20 -0400 Subject: [PATCH 20/57] Added basic printing options for session list Next - Manage the update/delete by Id --- .../Interfaces/EntryListController.cs | 113 ++++++++++++++++++ .../Interfaces/IEntryListController.cs | 7 ++ .../Interfaces/ITrackSessionController.cs | 3 - .../MainMenuController.cs | 15 ++- .../TrackSessionController.cs | 45 +++---- .../Repositories/CodingSessionRepository.cs | 10 +- .../CodingSessionDataService.cs | 6 +- .../Interfaces/ICodingSessionDataService.cs | 2 +- .../Interfaces/IMainMenuView.cs | 7 -- .../Interfaces/IMenuView.cs | 9 ++ .../Interfaces/ITrackSessionView.cs | 1 + .../Interfaces/IUserInput.cs | 8 ++ .../CodingTracker.Views/Menus/MainMenuView.cs | 50 -------- .../CodingTracker.Views/Menus/MenuView.cs | 65 ++++++++++ .../Menus/TrackSessionView.cs | 14 +++ .../CodingTracker.Views/Messages.cs | 54 +++++++++ .../CodingTracker.Views/UserInput.cs | 40 +++++++ .../CodingTracker.Views/ViewHelpers.cs | 20 ---- .../CodingTracker/Startup.cs | 7 +- 19 files changed, 359 insertions(+), 117 deletions(-) create mode 100644 codingTracker.jzhartman/CodingTracker.Controller/Interfaces/EntryListController.cs create mode 100644 codingTracker.jzhartman/CodingTracker.Controller/Interfaces/IEntryListController.cs delete mode 100644 codingTracker.jzhartman/CodingTracker.Views/Interfaces/IMainMenuView.cs create mode 100644 codingTracker.jzhartman/CodingTracker.Views/Interfaces/IMenuView.cs create mode 100644 codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInput.cs delete mode 100644 codingTracker.jzhartman/CodingTracker.Views/Menus/MainMenuView.cs create mode 100644 codingTracker.jzhartman/CodingTracker.Views/Menus/MenuView.cs create mode 100644 codingTracker.jzhartman/CodingTracker.Views/Messages.cs create mode 100644 codingTracker.jzhartman/CodingTracker.Views/UserInput.cs delete mode 100644 codingTracker.jzhartman/CodingTracker.Views/ViewHelpers.cs diff --git a/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/EntryListController.cs b/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/EntryListController.cs new file mode 100644 index 000000000..97cab39d0 --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/EntryListController.cs @@ -0,0 +1,113 @@ +using CodingTracker.Models.Entities; +using CodingTracker.Services; +using CodingTracker.Services.Interfaces; +using CodingTracker.Views; +using CodingTracker.Views.Interfaces; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CodingTracker.Controller.Interfaces +{ + public class EntryListController : IEntryListController + { + private readonly ICodingSessionDataService _service; + private readonly IMenuView _menuView; + private readonly IUserInput _inputView; + + public EntryListController(ICodingSessionDataService service, + IMenuView menuView, + IUserInput inputView) + { + _service = service; + _menuView = menuView; + _inputView = inputView; + } + + public void Run() + { + + bool returnToPreviousMenu = false; + + while (!returnToPreviousMenu) + { + var sessions = new List(); + DateTime startTime = new DateTime(); + DateTime endTime = new DateTime(); + + var selection = _menuView.RenderEntryViewOptionsAndGetSelection(); + + switch (selection) + { + case "All": + sessions = _service.GetAllCodingSessions(); + break; + case "One Year": + sessions = GetSessionsForPastYear(); + break; + case "Year to Date": + sessions = GetSessionsForYearToDate(); + break; + case "Enter Date Range": + sessions = GetSessionsByDateRange(); + break; + case "Return to Previous Menu": + returnToPreviousMenu = true; + break; + } + + if (returnToPreviousMenu) continue; + + CodingSessionView.RenderCodingSessions(sessions); + } + + // Get date range + // Print entries + // Get Update/Delete/New Date Range/Return + + } + + private List GetSessionsForPastYear() + { + var endTime = DateTime.Now; + var startTime = endTime.AddYears(-1); + + return _service.GetByDateRange(startTime, endTime); + } + private List GetSessionsForYearToDate() + { + var endTime = DateTime.Now; + var startTime = new DateTime(endTime.Year, 1, 1); + return _service.GetByDateRange(startTime, endTime); + } + private List GetSessionsByDateRange() + { + var startTime = _inputView.GetStartTimeFromUser(); + var endTime = new DateTime(); + bool endTimeValid = false; + + while (endTimeValid == false) + { + endTime = _inputView.GetEndTimeFromUser(); + + if (endTime <= startTime) + { + var parameter = "End Time"; + var message = $"The end time must be later than {startTime.ToString("yyyy-MM-dd HH:mm:ss")}"; + Messages.ErrorMessage(parameter, message); + } + else + { + endTimeValid = true; + } + } + + return _service.GetByDateRange(startTime, endTime); + + } + + + } +} diff --git a/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/IEntryListController.cs b/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/IEntryListController.cs new file mode 100644 index 000000000..b322eedda --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/IEntryListController.cs @@ -0,0 +1,7 @@ +namespace CodingTracker.Controller.Interfaces +{ + public interface IEntryListController + { + void Run(); + } +} \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/ITrackSessionController.cs b/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/ITrackSessionController.cs index ff52cb1bf..6d4847e04 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/ITrackSessionController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/ITrackSessionController.cs @@ -3,9 +3,6 @@ namespace CodingTracker.Controller.Interfaces { public interface ITrackSessionController { - DateTime GetEndTime(DateTime startTime); - DateTime GetStartTime(); - void GetTimesWithStopwatch(); void Run(); } } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Controller/MainMenuController.cs b/codingTracker.jzhartman/CodingTracker.Controller/MainMenuController.cs index 4df9aef1b..5205b22cb 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/MainMenuController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/MainMenuController.cs @@ -17,12 +17,16 @@ public class MainMenuController : IMainMenuController { private readonly ICodingSessionDataService _service; private readonly ITrackSessionController _trackController; - private readonly IMainMenuView _view; + private readonly IEntryListController _entryListController; + private readonly IMenuView _view; - public MainMenuController(ICodingSessionDataService service, ITrackSessionController trackController, IMainMenuView view) + public MainMenuController(ICodingSessionDataService service, + ITrackSessionController trackController, IEntryListController entryListController, + IMenuView view) { _service = service; _trackController = trackController; + _entryListController = entryListController; _view = view; } @@ -33,15 +37,16 @@ public void Run() while (!exitApp) { - ViewHelpers.RenderWelcome(); - var selection = _view.RenderMenuAndGetSelection(); + Messages.RenderWelcome(); + var selection = _view.RenderMainMenuAndGetSelection(); switch (selection) { case "Track Session": // Submenu: Enter times/Stopwatch/Return _trackController.Run(); break; - case "Manage Entries": // Submenu: View Entries (range or all)/Update/Delete + case "View/Manage Entries": // Submenu: View Entries (range or all)/Update/Delete + _entryListController.Run(); break; case "View Reports": // Enter Range-Period??? --> Print all records for period --> Print report data break; diff --git a/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs b/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs index dfcc7cdf2..43b066ea4 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs @@ -1,4 +1,5 @@ using CodingTracker.Controller.Interfaces; +using CodingTracker.Models.Entities; using CodingTracker.Services.Interfaces; using CodingTracker.Views; using CodingTracker.Views.Interfaces; @@ -14,12 +15,14 @@ namespace CodingTracker.Controller public class TrackSessionController : ITrackSessionController { private readonly ICodingSessionDataService _service; - private readonly ITrackSessionView _view; + private readonly IMenuView _menuView; + private readonly IUserInput _inputView; - public TrackSessionController(ICodingSessionDataService service, ITrackSessionView view) + public TrackSessionController(ICodingSessionDataService service, IMenuView menuView, IUserInput inputView) { _service = service; - _view = view; + _menuView = menuView; + _inputView = inputView; } public void Run() @@ -29,16 +32,15 @@ public void Run() while (!returnToMainMenu) { - ViewHelpers.RenderWelcome(); - var selection = _view.RenderMenuAndGetSelection(); + Messages.RenderWelcome(); + var selection = _menuView.RenderTrackingMenuAndGetSelection(); switch (selection) { case "Enter Start and End Times": var startTime = GetStartTime(); var endTime = GetEndTime(startTime); - - + AddSession(new CodingSession(startTime, endTime)); break; case "Begin Timer": GetTimesWithStopwatch(); @@ -52,14 +54,14 @@ public void Run() } } - public DateTime GetStartTime() + private DateTime GetStartTime() { var output = new DateTime(); bool startTimeValid = false; while (startTimeValid == false) { - output = _view.GetStartTimeFromUser(); + output = _inputView.GetStartTimeFromUser(); var result = _service.ValidateStartTime(output); @@ -67,30 +69,29 @@ public DateTime GetStartTime() { startTimeValid = true; output = result.Value; - _view.ConfirmationMessage(result.Value.ToString("yyyy-MM-dd HH:mm:ss")); + Messages.ConfirmationMessage(result.Value.ToString("yyyy-MM-dd HH:mm:ss")); } else { - _view.ErrorMessage(result.Parameter, result.Message); + Messages.ErrorMessage(result.Parameter, result.Message); } } return output; } - - public DateTime GetEndTime(DateTime startTime) + private DateTime GetEndTime(DateTime startTime) { var output = new DateTime(); bool endTimeValid = false; while (endTimeValid == false) { - output = _view.GetEndTimeFromUser(); + output = _inputView.GetEndTimeFromUser(); if (output <= startTime) { var parameter = "End Time"; var message = $"The end time must be later than {startTime.ToString("yyyy-MM-dd HH:mm:ss")}"; - _view.ErrorMessage(parameter, message); + Messages.ErrorMessage(parameter, message); } else { @@ -100,25 +101,25 @@ public DateTime GetEndTime(DateTime startTime) { endTimeValid = true; output = result.Value; - _view.ConfirmationMessage(result.Value.ToString("yyyy-MM-dd HH:mm:ss")); + Messages.ConfirmationMessage(result.Value.ToString("yyyy-MM-dd HH:mm:ss")); } else { - _view.ErrorMessage(result.Parameter, result.Message); + Messages.ErrorMessage(result.Parameter, result.Message); } } } return output; } - - public void GetTimesWithStopwatch() + private void AddSession(CodingSession session) { - // Code here + _service.AddSession(session); + Messages.ActionCompleteMessage(true, "Success", "Coding session successfully added!"); } - public void AddRecord() + private void GetTimesWithStopwatch() { - + // Code here } } } diff --git a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs index 4439f85e2..4580ddd23 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs @@ -55,7 +55,11 @@ public CodingSessionDataRecord GetById(int id) string sql = $"select * from CodingSessions where Id = {id}"; return LoadData(sql).FirstOrDefault(); } - + public int GetRecordCount() + { + string sql = "select count(*) from CodingSessions"; + return LoadData(sql).First(); + } @@ -67,6 +71,8 @@ public void AddSession(CodingSession session) + + // // // UPDATE METHODS WILL NEED TO ACCOUNT FOR A CHANGE IN DURATION!!!!!!!!!!!!!! @@ -91,7 +97,6 @@ public void DeleteById(int id) } - // NOT IMPLEMENTED YET!!!!! public bool ExistsWithinTimeFrame(DateTime time) @@ -109,6 +114,7 @@ public bool ExistsWithinTimeFrame(DateTime time) } + // NOT IMPLEMENTED YET!!!!! //Method Stubs to work out eventually... If needed? diff --git a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs index 7b33ca563..14138b1db 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs @@ -53,13 +53,9 @@ public void UpdateEndTimeById(int id, DateTime endTime) _repository.UpdateEndTimeById(id, endTime); } - public ValidationResult AddSession(CodingSession session) + public void AddSession(CodingSession session) { - - //if(_repository.ExistsByTimeFrame())//If session.StartTime is within the bounds of an existing - //Other validation to add in later.... _repository.AddSession(session); - return ValidationResult.Success(session); } public ValidationResult ValidateStartTime(DateTime input) diff --git a/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionDataService.cs b/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionDataService.cs index c82f95840..64745ea7f 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionDataService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionDataService.cs @@ -5,7 +5,7 @@ namespace CodingTracker.Services.Interfaces { public interface ICodingSessionDataService { - ValidationResult AddSession(CodingSession session); + void AddSession(CodingSession session); void DeleteById(int id); List GetAllCodingSessions(); List GetByDateRange(DateTime startTime, DateTime endTime); diff --git a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IMainMenuView.cs b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IMainMenuView.cs deleted file mode 100644 index da5c4cc69..000000000 --- a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IMainMenuView.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace CodingTracker.Views.Interfaces -{ - public interface IMainMenuView - { - string RenderMenuAndGetSelection(); - } -} \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IMenuView.cs b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IMenuView.cs new file mode 100644 index 000000000..178bc531f --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IMenuView.cs @@ -0,0 +1,9 @@ +namespace CodingTracker.Views.Interfaces +{ + public interface IMenuView + { + string RenderEntryViewOptionsAndGetSelection(); + string RenderMainMenuAndGetSelection(); + string RenderTrackingMenuAndGetSelection(); + } +} \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/ITrackSessionView.cs b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/ITrackSessionView.cs index c69dbdf90..b74fb2a30 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/ITrackSessionView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/ITrackSessionView.cs @@ -2,6 +2,7 @@ { public interface ITrackSessionView { + void ActionCompleteMessage(bool isSuccess, string state, string message); void ConfirmationMessage(string valueText); void ErrorMessage(string parameter, string message); DateTime GetEndTimeFromUser(); diff --git a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInput.cs b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInput.cs new file mode 100644 index 000000000..677b9e94e --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInput.cs @@ -0,0 +1,8 @@ +namespace CodingTracker.Views.Interfaces +{ + public interface IUserInput + { + DateTime GetEndTimeFromUser(); + DateTime GetStartTimeFromUser(); + } +} \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Views/Menus/MainMenuView.cs b/codingTracker.jzhartman/CodingTracker.Views/Menus/MainMenuView.cs deleted file mode 100644 index d8cc3f1fd..000000000 --- a/codingTracker.jzhartman/CodingTracker.Views/Menus/MainMenuView.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using CodingTracker.Views.Interfaces; -using Spectre.Console; - -namespace CodingTracker.Views.Menus -{ - public class MainMenuView : IMainMenuView - { - public string RenderMenuAndGetSelection() - { - var selection = AnsiConsole.Prompt( - new SelectionPrompt() - .Title("Select from the options below:") - .AddChoices(new[] - { - "Track Session", - "Manage Entries", - "View Reports", - "Manage Goal", - "Exit" - }) - ); - - return selection; - } - - - - // Move methods below to a different class soon.... Separate out the menus and their display logic? - - - public void RenderMenu() - { - var mainMenu = AnsiConsole.Prompt( - new SelectionPrompt() - .Title("How would you like to track your coding session?") - .AddChoices(new[] - { - "Enter Start and End Times", - "Begin Timer", - "Return to Main Menu" - }) - ); - } - } -} diff --git a/codingTracker.jzhartman/CodingTracker.Views/Menus/MenuView.cs b/codingTracker.jzhartman/CodingTracker.Views/Menus/MenuView.cs new file mode 100644 index 000000000..5b5aa9639 --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Views/Menus/MenuView.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using CodingTracker.Views.Interfaces; +using Spectre.Console; + +namespace CodingTracker.Views.Menus +{ + public class MenuView : IMenuView + { + public string RenderMainMenuAndGetSelection() + { + var selection = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("Select from the options below:") + .AddChoices(new[] + { + "Track Session", + "View/Manage Entries", + "View Reports", + "Manage Goal", + "Exit" + }) + ); + + return selection; + } + + public string RenderTrackingMenuAndGetSelection() + { + var selection = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("How would you like to track your coding session?") + .AddChoices(new[] + { + "Enter Start and End Times", + "Begin Timer", + "Return to Main Menu" + }) + ); + + return selection; + } + + public string RenderEntryViewOptionsAndGetSelection() + { + var selection = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("How would you like your entries displayed?") + .AddChoices(new[] + { + "All", + "One Year", + "Year to Date", + "Enter Date Range", + "Return to Previous Menu" + }) + ); + return selection; + } + + } +} diff --git a/codingTracker.jzhartman/CodingTracker.Views/Menus/TrackSessionView.cs b/codingTracker.jzhartman/CodingTracker.Views/Menus/TrackSessionView.cs index 1cfe8ba2b..2149b4f0a 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/Menus/TrackSessionView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/Menus/TrackSessionView.cs @@ -68,6 +68,20 @@ public void ConfirmationMessage(string valueText) AddNewLines(2); } + public void ActionCompleteMessage(bool isSuccess, string state, string message) + { + AddNewLines(1); + if (isSuccess) + { + AnsiConsole.MarkupInterpolated($"[bold green]{state.ToUpper()}![/] {message}"); + } + else + { + AnsiConsole.MarkupInterpolated($"[bold red]{state.ToUpper()}![/] {message}"); + } + AddNewLines(2); + } + private void AddNewLines(int lines) { for (int i = 0; i < lines; i++) diff --git a/codingTracker.jzhartman/CodingTracker.Views/Messages.cs b/codingTracker.jzhartman/CodingTracker.Views/Messages.cs new file mode 100644 index 000000000..2923e4366 --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Views/Messages.cs @@ -0,0 +1,54 @@ +using Spectre.Console; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CodingTracker.Views +{ + public static class Messages + { + public static void RenderWelcome() + { + AnsiConsole.Markup("[bold blue]CODING TRACKER[/]"); + AnsiConsole.Markup("[bold blue]Version 1.0[/]"); + AnsiConsole.Write(new Rule()); + + } + public static void ErrorMessage(string parameter, string message) + { + AddNewLines(1); + AnsiConsole.MarkupInterpolated($"[bold red]ERROR:[/] The value for {parameter} encountered the error: [yellow]{message}[/]"); + AddNewLines(2); + } + + public static void ConfirmationMessage(string valueText) + { + AddNewLines(1); + AnsiConsole.MarkupInterpolated($"[bold green]ACCEPTED[/]: Value set to {valueText}"); + AddNewLines(2); + } + public static void ActionCompleteMessage(bool isSuccess, string state, string message) + { + AddNewLines(1); + if (isSuccess) + { + AnsiConsole.MarkupInterpolated($"[bold green]{state.ToUpper()}![/] {message}"); + } + else + { + AnsiConsole.MarkupInterpolated($"[bold red]{state.ToUpper()}![/] {message}"); + } + AddNewLines(2); + } + + private static void AddNewLines(int lines) + { + for (int i = 0; i < lines; i++) + { + AnsiConsole.WriteLine(); + } + } + } +} diff --git a/codingTracker.jzhartman/CodingTracker.Views/UserInput.cs b/codingTracker.jzhartman/CodingTracker.Views/UserInput.cs new file mode 100644 index 000000000..39828769d --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Views/UserInput.cs @@ -0,0 +1,40 @@ +using CodingTracker.Views.Interfaces; +using Spectre.Console; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CodingTracker.Views +{ + public class UserInput : IUserInput + { + private readonly string _dateFormat; + public UserInput(string dateFormat) + { + _dateFormat = dateFormat; + } + public DateTime GetStartTimeFromUser() + { + var date = AnsiConsole.Prompt( + new TextPrompt("Please enter a start time using the format [yellow]'yyyy-MM-dd HH:mm:ss'[/]:") + ); + + //Add custom validation for time format + + return date; + } + + public DateTime GetEndTimeFromUser() + { + var date = AnsiConsole.Prompt( + new TextPrompt("Please enter an end time using the format [yellow]'yyyy-MM-dd HH:mm:ss'[/]:") + ); + + //Add custom validation for time format + + return date; + } + } +} diff --git a/codingTracker.jzhartman/CodingTracker.Views/ViewHelpers.cs b/codingTracker.jzhartman/CodingTracker.Views/ViewHelpers.cs deleted file mode 100644 index 1be731aba..000000000 --- a/codingTracker.jzhartman/CodingTracker.Views/ViewHelpers.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Spectre.Console; - -namespace CodingTracker.Views -{ - public static class ViewHelpers - { - public static void RenderWelcome() - { - AnsiConsole.Markup("[bold blue]CODING TRACKER[/]"); - AnsiConsole.Markup("[bold blue]Version 1.0[/]"); - AnsiConsole.Write(new Rule()); - - } - } -} diff --git a/codingTracker.jzhartman/CodingTracker/Startup.cs b/codingTracker.jzhartman/CodingTracker/Startup.cs index fa53221a9..90456acd7 100644 --- a/codingTracker.jzhartman/CodingTracker/Startup.cs +++ b/codingTracker.jzhartman/CodingTracker/Startup.cs @@ -6,6 +6,7 @@ using CodingTracker.Data.TypeHandlers; using CodingTracker.Services; using CodingTracker.Services.Interfaces; +using CodingTracker.Views; using CodingTracker.Views.Interfaces; using CodingTracker.Views.Menus; using Dapper; @@ -30,13 +31,15 @@ public static IServiceProvider ConfigureServices() //Register All Controllers services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); //Register All Services services.AddSingleton(); //Resgister All Views - services.AddSingleton(); - services.AddSingleton(new TrackSessionView(dateTimeFormat)); + services.AddSingleton(); + services.AddSingleton(new UserInput(dateTimeFormat)); + //services.AddSingleton(new TrackSessionView(dateTimeFormat)); services.AddSingleton(); services.AddSingleton(provider => new SqliteConnectionFactory(connectionString)); From edb0527fb8a2e6fab9f3bb82230ba6dc592ae051 Mon Sep 17 00:00:00 2001 From: jzhartman Date: Sat, 6 Sep 2025 23:38:48 -0400 Subject: [PATCH 21/57] Quick edit to previous commit --- .../Interfaces/EntryListController.cs | 63 +++++++++++-------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/EntryListController.cs b/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/EntryListController.cs index 97cab39d0..ae3868103 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/EntryListController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/EntryListController.cs @@ -33,42 +33,53 @@ public void Run() while (!returnToPreviousMenu) { - var sessions = new List(); - DateTime startTime = new DateTime(); - DateTime endTime = new DateTime(); - - var selection = _menuView.RenderEntryViewOptionsAndGetSelection(); - - switch (selection) - { - case "All": - sessions = _service.GetAllCodingSessions(); - break; - case "One Year": - sessions = GetSessionsForPastYear(); - break; - case "Year to Date": - sessions = GetSessionsForYearToDate(); - break; - case "Enter Date Range": - sessions = GetSessionsByDateRange(); - break; - case "Return to Previous Menu": - returnToPreviousMenu = true; - break; - } + (returnToPreviousMenu, var sessions) = GetSessionListBasedOnUserDateSelection(); if (returnToPreviousMenu) continue; CodingSessionView.RenderCodingSessions(sessions); + + + + } - // Get date range - // Print entries + // Get Update/Delete/New Date Range/Return } + private (bool, List) GetSessionListBasedOnUserDateSelection() + { + bool returnToPreviousMenu = false; + var sessions = new List(); + DateTime startTime = new DateTime(); + DateTime endTime = new DateTime(); + + var selection = _menuView.RenderEntryViewOptionsAndGetSelection(); + + switch (selection) + { + case "All": + sessions = _service.GetAllCodingSessions(); + break; + case "One Year": + sessions = GetSessionsForPastYear(); + break; + case "Year to Date": + sessions = GetSessionsForYearToDate(); + break; + case "Enter Date Range": + sessions = GetSessionsByDateRange(); + break; + case "Return to Previous Menu": + returnToPreviousMenu = true; + break; + } + + return (returnToPreviousMenu, sessions); + } + private List GetSessionsForPastYear() { var endTime = DateTime.Now; From 8c27dc9d90cd53f7781e90767a126314d522be12 Mon Sep 17 00:00:00 2001 From: jzhartman Date: Mon, 8 Sep 2025 21:26:56 -0400 Subject: [PATCH 22/57] working commit --- .../Interfaces/EntryListController.cs | 76 +++++++++++++++++++ .../CodingSessionDataService.cs | 1 + .../Interfaces/IMenuView.cs | 2 + .../Interfaces/IUserInput.cs | 2 + .../CodingTracker.Views/Menus/MenuView.cs | 28 +++++++ .../CodingTracker.Views/UserInput.cs | 54 ++++++++++++- 6 files changed, 159 insertions(+), 4 deletions(-) diff --git a/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/EntryListController.cs b/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/EntryListController.cs index ae3868103..98f507f40 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/EntryListController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/EntryListController.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using static System.Collections.Specialized.BitVector32; namespace CodingTracker.Controller.Interfaces { @@ -39,7 +40,27 @@ public void Run() CodingSessionView.RenderCodingSessions(sessions); + bool returnToDateSelection = false; + while (!returnToDateSelection) + { + var selection = _menuView.RenderUpdateOrDeleteOptionsAndGetSelection(); + + switch (selection) + { + case "Change Record": + var recordId = _inputView.GetRecordIdFromUser("update", sessions.Count()); + ManageUserUpdate(sessions[recordId]); + break; + case "Delete Record": + + break; + case "Return to Previous Menu": + returnToDateSelection = true; + break; + } + + } } @@ -49,6 +70,61 @@ public void Run() } + private void ManageUserUpdate(CodingSessionDataRecord session) + { + DateTime newStartTime = new DateTime(); + DateTime newEndTime = new DateTime(); + + string selection = _menuView.RenderUpdateTimeFeildSelector(); + + switch (selection) + { + case "Start Time": + newStartTime = GetUpdatedStartTime(session.StartTime); + break; + case "End Time": + // Get updated endtime + break; + case "Both": + // Run both + break; + default: + break; + } + + // Run validation on both dates + // update if good + } + + private DateTime GetUpdatedStartTime(DateTime originalTime) + { + var output = new DateTime(); + bool startTimeValid = false; + + while (startTimeValid == false) + { + output = _inputView.GetUpdatedStartTimeFromUser(originalTime); + + // Allow user to enter a start date with almost no validation -- will validate later? + startTimeValid = true; + + + //var result = _service.ValidateStartTime(output); + + //if (result.IsValid) + //{ + // startTimeValid = true; + // output = result.Value; + // Messages.ConfirmationMessage(result.Value.ToString("yyyy-MM-dd HH:mm:ss")); + //} + //else + //{ + // Messages.ErrorMessage(result.Parameter, result.Message); + //} + } + return output; + } + private (bool, List) GetSessionListBasedOnUserDateSelection() { bool returnToPreviousMenu = false; diff --git a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs index 14138b1db..4c165f9e0 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs @@ -65,6 +65,7 @@ public ValidationResult ValidateStartTime(DateTime input) else return ValidationResult.Success(input); } + public ValidationResult ValidateEndTime(DateTime input) { if (_repository.ExistsWithinTimeFrame(input)) diff --git a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IMenuView.cs b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IMenuView.cs index 178bc531f..eff47245b 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IMenuView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IMenuView.cs @@ -5,5 +5,7 @@ public interface IMenuView string RenderEntryViewOptionsAndGetSelection(); string RenderMainMenuAndGetSelection(); string RenderTrackingMenuAndGetSelection(); + string RenderUpdateOrDeleteOptionsAndGetSelection(); + string RenderUpdateTimeFeildSelector(); } } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInput.cs b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInput.cs index 677b9e94e..f2cbf8dfd 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInput.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInput.cs @@ -3,6 +3,8 @@ public interface IUserInput { DateTime GetEndTimeFromUser(); + int GetRecordIdFromUser(string action, int max); DateTime GetStartTimeFromUser(); + DateTime GetUpdatedStartTimeFromUser(DateTime originalTime); } } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Views/Menus/MenuView.cs b/codingTracker.jzhartman/CodingTracker.Views/Menus/MenuView.cs index 5b5aa9639..83a43e683 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/Menus/MenuView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/Menus/MenuView.cs @@ -60,6 +60,34 @@ public string RenderEntryViewOptionsAndGetSelection() ); return selection; } + public string RenderUpdateOrDeleteOptionsAndGetSelection() + { + var selection = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("Please select the next operation:") + .AddChoices(new[] + { + "Change Record", + "Delete Record", + "Return to Previous Menu" + }) + ); + return selection; + } + + public string RenderUpdateTimeFeildSelector() + { + var selection = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("Please select an option:") + .AddChoices(new[] + { + "Start Time", + "End Time", + }) + ); + return selection; + } } } diff --git a/codingTracker.jzhartman/CodingTracker.Views/UserInput.cs b/codingTracker.jzhartman/CodingTracker.Views/UserInput.cs index 39828769d..f55c4c2a3 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/UserInput.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/UserInput.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection.Metadata; using System.Text; using System.Threading.Tasks; @@ -18,8 +19,7 @@ public UserInput(string dateFormat) public DateTime GetStartTimeFromUser() { var date = AnsiConsole.Prompt( - new TextPrompt("Please enter a start time using the format [yellow]'yyyy-MM-dd HH:mm:ss'[/]:") - ); + new TextPrompt("Please enter a start time using the format [yellow]'yyyy-MM-dd HH:mm:ss'[/]:")); //Add custom validation for time format @@ -29,12 +29,58 @@ public DateTime GetStartTimeFromUser() public DateTime GetEndTimeFromUser() { var date = AnsiConsole.Prompt( - new TextPrompt("Please enter an end time using the format [yellow]'yyyy-MM-dd HH:mm:ss'[/]:") - ); + new TextPrompt("Please enter an end time using the format [yellow]'yyyy-MM-dd HH:mm:ss'[/]:")); //Add custom validation for time format return date; } + + public int GetRecordIdFromUser(string action, int max) + { + var id = AnsiConsole.Prompt( + new TextPrompt($"Please enter the [yellow]ID[/] of the record you wish to {action.ToLower()}:") + .Validate(input => + { + if (input < 1) return Spectre.Console.ValidationResult.Error("Too low"); + else if (input > max) return Spectre.Console.ValidationResult.Error("Too high"); + else return Spectre.Console.ValidationResult.Success(); + })); + + return id; + } + + public DateTime GetUpdatedStartTimeFromUser(DateTime originalTime) + { + AnsiConsole.MarkupInterpolated($"Changing start time from [yellow]{originalTime}[/]."); + return GetEndTimeFromUser(); + } } } + + +/* + // Define the target date format. + const string dateFormat = "yyyy-MM-dd"; + + // Prompt for a date with validation. + var dateString = AnsiConsole.Prompt( + new TextPrompt("Enter a date in [green]yyyy-MM-dd[/] format:") + .PromptStyle("yellow") + .ValidationErrorMessage($"[red]Invalid date format. Please use {dateFormat}.[/]") + .Validate(input => + { + // Use DateTime.TryParseExact for robust format checking. + if (DateTime.TryParseExact(input, dateFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out _)) + { + return ValidationResult.Success(); + } + + return ValidationResult.Error(); + })); + + // After successful validation, parse the string into a DateTime object. + DateTime parsedDate = DateTime.ParseExact(dateString, dateFormat, CultureInfo.InvariantCulture); + + AnsiConsole.MarkupLine($"You entered: [green]{parsedDate:yyyy-MM-dd}[/]"); +*/ \ No newline at end of file From f78b2a965f17128071b898ed2cde6608fcb108de Mon Sep 17 00:00:00 2001 From: jzhartman Date: Wed, 10 Sep 2025 22:31:28 -0400 Subject: [PATCH 23/57] working commit --- .../{Interfaces => }/EntryListController.cs | 4 +++- .../TrackSessionController.cs | 12 +++++++++++- .../CodingTracker.Models/Entities/CodingSession.cs | 9 ++++++++- .../CodingTracker.Views/Interfaces/IUserInput.cs | 5 ++++- .../CodingTracker.Views/Menus/MenuView.cs | 2 +- .../CodingTracker.Views/UserInput.cs | 14 +++++++++++++- 6 files changed, 40 insertions(+), 6 deletions(-) rename codingTracker.jzhartman/CodingTracker.Controller/{Interfaces => }/EntryListController.cs (96%) diff --git a/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/EntryListController.cs b/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs similarity index 96% rename from codingTracker.jzhartman/CodingTracker.Controller/Interfaces/EntryListController.cs rename to codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs index 98f507f40..ab3ad1883 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/EntryListController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs @@ -45,14 +45,16 @@ public void Run() while (!returnToDateSelection) { var selection = _menuView.RenderUpdateOrDeleteOptionsAndGetSelection(); + var recordId = 0; switch (selection) { case "Change Record": - var recordId = _inputView.GetRecordIdFromUser("update", sessions.Count()); + recordId = _inputView.GetRecordIdFromUser("update", sessions.Count()); ManageUserUpdate(sessions[recordId]); break; case "Delete Record": + recordId = _inputView.GetRecordIdFromUser("delete", sessions.Count()); break; case "Return to Previous Menu": diff --git a/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs b/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs index 43b066ea4..1bc669b5e 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs @@ -40,7 +40,12 @@ public void Run() case "Enter Start and End Times": var startTime = GetStartTime(); var endTime = GetEndTime(startTime); - AddSession(new CodingSession(startTime, endTime)); + var session = new CodingSession(startTime, endTime); + + if (ConfirmSession(session)) + { + AddSession(session); + } break; case "Begin Timer": GetTimesWithStopwatch(); @@ -54,6 +59,11 @@ public void Run() } } + private bool ConfirmSession(CodingSession session) + { + return _inputView.GetAddSessionConfirmationFromUser(session); + } + private DateTime GetStartTime() { var output = new DateTime(); diff --git a/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSession.cs b/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSession.cs index 8a91a003b..f92b45395 100644 --- a/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSession.cs +++ b/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSession.cs @@ -10,18 +10,25 @@ public class CodingSession { public DateTime StartTime { get; set; } public DateTime EndTime { get; set; } - public long Duration { get; set; } + public long Duration { get; private set; } + public string DurationText { get; private set;} public CodingSession(DateTime startTime, DateTime endTime) { StartTime = startTime; EndTime = endTime; CalculateDuration(); + GenerateDurationText(); } private void CalculateDuration() { Duration = (long)EndTime.Subtract(StartTime).TotalSeconds; } + + private void GenerateDurationText() + { + DurationText = TimeSpan.FromSeconds(Duration).ToString(); + } } } diff --git a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInput.cs b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInput.cs index f2cbf8dfd..b0e627f88 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInput.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInput.cs @@ -1,9 +1,12 @@ -namespace CodingTracker.Views.Interfaces +using CodingTracker.Models.Entities; + +namespace CodingTracker.Views.Interfaces { public interface IUserInput { DateTime GetEndTimeFromUser(); int GetRecordIdFromUser(string action, int max); + bool GetAddSessionConfirmationFromUser(CodingSession session); DateTime GetStartTimeFromUser(); DateTime GetUpdatedStartTimeFromUser(DateTime originalTime); } diff --git a/codingTracker.jzhartman/CodingTracker.Views/Menus/MenuView.cs b/codingTracker.jzhartman/CodingTracker.Views/Menus/MenuView.cs index 83a43e683..274f15a81 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/Menus/MenuView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/Menus/MenuView.cs @@ -79,7 +79,7 @@ public string RenderUpdateTimeFeildSelector() { var selection = AnsiConsole.Prompt( new SelectionPrompt() - .Title("Please select an option:") + .Title("Please select an option to change:") .AddChoices(new[] { "Start Time", diff --git a/codingTracker.jzhartman/CodingTracker.Views/UserInput.cs b/codingTracker.jzhartman/CodingTracker.Views/UserInput.cs index f55c4c2a3..f9d861dec 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/UserInput.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/UserInput.cs @@ -1,4 +1,5 @@ -using CodingTracker.Views.Interfaces; +using CodingTracker.Models.Entities; +using CodingTracker.Views.Interfaces; using Spectre.Console; using System; using System.Collections.Generic; @@ -55,6 +56,17 @@ public DateTime GetUpdatedStartTimeFromUser(DateTime originalTime) AnsiConsole.MarkupInterpolated($"Changing start time from [yellow]{originalTime}[/]."); return GetEndTimeFromUser(); } + + public bool GetAddSessionConfirmationFromUser(CodingSession session) + { + var confirmation = AnsiConsole.Prompt( + new TextPrompt($"Add coding session starting at [yellow]{session.StartTime.ToString("yyyy-MM-dd HH:mm:ss")}[/] and ending at [yellow]{session.EndTime.ToString("yyyy-MM-dd HH:mm:ss")}[/] with duration [yellow]{session.DurationText}[/]?") + .AddChoice(true) + .AddChoice(false) + .WithConverter(choice => choice ? "y" : "n")); + + return confirmation; + } } } From d217eaf5905bfe743c9875a3a9496528378633c0 Mon Sep 17 00:00:00 2001 From: jzhartman Date: Wed, 10 Sep 2025 23:20:08 -0400 Subject: [PATCH 24/57] Working commit Refactored some items Working on update entry method --- .../EntryListController.cs | 30 ++---- .../TrackSessionController.cs | 4 +- .../Interfaces/IMenuView.cs | 1 - .../Interfaces/ITrackSessionView.cs | 12 --- .../Interfaces/IUserInput.cs | 5 +- .../CodingTracker.Views/Menus/MenuView.cs | 14 --- .../Menus/TrackSessionView.cs | 93 ------------------- .../CodingTracker.Views/UserInput.cs | 41 ++++---- .../CodingTracker/Startup.cs | 2 +- 9 files changed, 39 insertions(+), 163 deletions(-) delete mode 100644 codingTracker.jzhartman/CodingTracker.Views/Interfaces/ITrackSessionView.cs delete mode 100644 codingTracker.jzhartman/CodingTracker.Views/Menus/TrackSessionView.cs diff --git a/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs b/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs index ab3ad1883..9c17d9034 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs @@ -74,25 +74,15 @@ public void Run() private void ManageUserUpdate(CodingSessionDataRecord session) { - DateTime newStartTime = new DateTime(); - DateTime newEndTime = new DateTime(); + var newStartTime = _inputView.GetTimeFromUser("new start time", true); - string selection = _menuView.RenderUpdateTimeFeildSelector(); + if (newStartTime == null) newStartTime = session.StartTime; + // Run validation on start time + // Same as previous validation except it can be >= current start time - switch (selection) - { - case "Start Time": - newStartTime = GetUpdatedStartTime(session.StartTime); - break; - case "End Time": - // Get updated endtime - break; - case "Both": - // Run both - break; - default: - break; - } + var newEndTime = _inputView.GetTimeFromUser("new end time", true); + // Validate + // Run validation on both dates // update if good @@ -105,7 +95,7 @@ private DateTime GetUpdatedStartTime(DateTime originalTime) while (startTimeValid == false) { - output = _inputView.GetUpdatedStartTimeFromUser(originalTime); + //output = _inputView.GetUpdatedStartTimeFromUser(originalTime); // Allow user to enter a start date with almost no validation -- will validate later? startTimeValid = true; @@ -173,13 +163,13 @@ private List GetSessionsForYearToDate() } private List GetSessionsByDateRange() { - var startTime = _inputView.GetStartTimeFromUser(); + var startTime = _inputView.GetTimeFromUser("start time"); var endTime = new DateTime(); bool endTimeValid = false; while (endTimeValid == false) { - endTime = _inputView.GetEndTimeFromUser(); + endTime = _inputView.GetTimeFromUser("end time"); if (endTime <= startTime) { diff --git a/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs b/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs index 1bc669b5e..cb95581b9 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs @@ -71,7 +71,7 @@ private DateTime GetStartTime() while (startTimeValid == false) { - output = _inputView.GetStartTimeFromUser(); + output = _inputView.GetTimeFromUser("start time"); var result = _service.ValidateStartTime(output); @@ -95,7 +95,7 @@ private DateTime GetEndTime(DateTime startTime) while (endTimeValid == false) { - output = _inputView.GetEndTimeFromUser(); + output = _inputView.GetTimeFromUser("end time"); if (output <= startTime) { diff --git a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IMenuView.cs b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IMenuView.cs index eff47245b..0b6615c43 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IMenuView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IMenuView.cs @@ -6,6 +6,5 @@ public interface IMenuView string RenderMainMenuAndGetSelection(); string RenderTrackingMenuAndGetSelection(); string RenderUpdateOrDeleteOptionsAndGetSelection(); - string RenderUpdateTimeFeildSelector(); } } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/ITrackSessionView.cs b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/ITrackSessionView.cs deleted file mode 100644 index b74fb2a30..000000000 --- a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/ITrackSessionView.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace CodingTracker.Views.Interfaces -{ - public interface ITrackSessionView - { - void ActionCompleteMessage(bool isSuccess, string state, string message); - void ConfirmationMessage(string valueText); - void ErrorMessage(string parameter, string message); - DateTime GetEndTimeFromUser(); - DateTime GetStartTimeFromUser(); - string RenderMenuAndGetSelection(); - } -} \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInput.cs b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInput.cs index b0e627f88..f2d245d5b 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInput.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInput.cs @@ -4,10 +4,9 @@ namespace CodingTracker.Views.Interfaces { public interface IUserInput { - DateTime GetEndTimeFromUser(); int GetRecordIdFromUser(string action, int max); bool GetAddSessionConfirmationFromUser(CodingSession session); - DateTime GetStartTimeFromUser(); - DateTime GetUpdatedStartTimeFromUser(DateTime originalTime); + DateTime GetTimeFromUser(string parameterName, bool allowNull = false); + //DateTime GetUpdatedStartTimeFromUser(DateTime originalTime); } } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Views/Menus/MenuView.cs b/codingTracker.jzhartman/CodingTracker.Views/Menus/MenuView.cs index 274f15a81..7994c6b1b 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/Menus/MenuView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/Menus/MenuView.cs @@ -75,19 +75,5 @@ public string RenderUpdateOrDeleteOptionsAndGetSelection() return selection; } - public string RenderUpdateTimeFeildSelector() - { - var selection = AnsiConsole.Prompt( - new SelectionPrompt() - .Title("Please select an option to change:") - .AddChoices(new[] - { - "Start Time", - "End Time", - }) - ); - return selection; - } - } } diff --git a/codingTracker.jzhartman/CodingTracker.Views/Menus/TrackSessionView.cs b/codingTracker.jzhartman/CodingTracker.Views/Menus/TrackSessionView.cs deleted file mode 100644 index 2149b4f0a..000000000 --- a/codingTracker.jzhartman/CodingTracker.Views/Menus/TrackSessionView.cs +++ /dev/null @@ -1,93 +0,0 @@ -using CodingTracker.Views.Interfaces; -using Spectre.Console; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace CodingTracker.Views.Menus -{ - public class TrackSessionView : ITrackSessionView - { - private readonly string _dateFormat; - public TrackSessionView(string dateFormat) - { - _dateFormat = dateFormat; - } - - public string RenderMenuAndGetSelection() - { - var selection = AnsiConsole.Prompt( - new SelectionPrompt() - .Title("How would you like to track your coding session?") - .AddChoices(new[] - { - "Enter Start and End Times", - "Begin Timer", - "Return to Main Menu" - }) - ); - - return selection; - } - - public DateTime GetStartTimeFromUser() - { - var date = AnsiConsole.Prompt( - new TextPrompt("Please enter a start time using the format [yellow]'yyyy-MM-dd HH:mm:ss'[/]:") - ); - - //Add custom validation for time format - - return date; - } - - public DateTime GetEndTimeFromUser() - { - var date = AnsiConsole.Prompt( - new TextPrompt("Please enter an end time using the format [yellow]'yyyy-MM-dd HH:mm:ss'[/]:") - ); - - //Add custom validation for time format - - return date; - } - - public void ErrorMessage(string parameter, string message) - { - AddNewLines(1); - AnsiConsole.MarkupInterpolated($"[bold red]ERROR:[/] The value for {parameter} encountered the error: [yellow]{message}[/]"); - AddNewLines(2); - } - - public void ConfirmationMessage(string valueText) - { - AddNewLines(1); - AnsiConsole.MarkupInterpolated($"[bold green]ACCEPTED[/]: Value set to {valueText}"); - AddNewLines(2); - } - - public void ActionCompleteMessage(bool isSuccess, string state, string message) - { - AddNewLines(1); - if (isSuccess) - { - AnsiConsole.MarkupInterpolated($"[bold green]{state.ToUpper()}![/] {message}"); - } - else - { - AnsiConsole.MarkupInterpolated($"[bold red]{state.ToUpper()}![/] {message}"); - } - AddNewLines(2); - } - - private void AddNewLines(int lines) - { - for (int i = 0; i < lines; i++) - { - AnsiConsole.WriteLine(); - } - } - } -} diff --git a/codingTracker.jzhartman/CodingTracker.Views/UserInput.cs b/codingTracker.jzhartman/CodingTracker.Views/UserInput.cs index f9d861dec..68b4481c9 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/UserInput.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/UserInput.cs @@ -17,20 +17,18 @@ public UserInput(string dateFormat) { _dateFormat = dateFormat; } - public DateTime GetStartTimeFromUser() + public DateTime GetTimeFromUser(string parameterName, bool allowNull = false) { - var date = AnsiConsole.Prompt( - new TextPrompt("Please enter a start time using the format [yellow]'yyyy-MM-dd HH:mm:ss'[/]:")); + var date = new DateTime(); + string article = GetArticle(parameterName); + string promptText = $"Please enter {article} {parameterName} using the format [yellow]'yyyy-MM-dd HH:mm:ss'[/]:"; - //Add custom validation for time format - - return date; - } + if (allowNull) date = AnsiConsole.Prompt( + new TextPrompt(promptText) + .AllowEmpty()); - public DateTime GetEndTimeFromUser() - { - var date = AnsiConsole.Prompt( - new TextPrompt("Please enter an end time using the format [yellow]'yyyy-MM-dd HH:mm:ss'[/]:")); + else date = AnsiConsole.Prompt( + new TextPrompt(promptText)); //Add custom validation for time format @@ -44,18 +42,18 @@ public int GetRecordIdFromUser(string action, int max) .Validate(input => { if (input < 1) return Spectre.Console.ValidationResult.Error("Too low"); - else if (input > max) return Spectre.Console.ValidationResult.Error("Too high"); + else if (input >= max) return Spectre.Console.ValidationResult.Error("Too high"); else return Spectre.Console.ValidationResult.Success(); })); return id; } - public DateTime GetUpdatedStartTimeFromUser(DateTime originalTime) - { - AnsiConsole.MarkupInterpolated($"Changing start time from [yellow]{originalTime}[/]."); - return GetEndTimeFromUser(); - } + //public DateTime GetUpdatedStartTimeFromUser(DateTime originalTime) + //{ + // AnsiConsole.MarkupInterpolated($"Changing start time from [yellow]{originalTime}[/]."); + // return GetEndTimeFromUser(); + //} public bool GetAddSessionConfirmationFromUser(CodingSession session) { @@ -67,6 +65,15 @@ public bool GetAddSessionConfirmationFromUser(CodingSession session) return confirmation; } + + private string GetArticle(string noun) + { + string article = "a"; + char firstLetter = noun.ToLower()[0]; + if (firstLetter == 'a' || firstLetter == 'e' || firstLetter == 'i' || firstLetter == 'o' || firstLetter == 'u') article += "n"; + + return article; + } } } diff --git a/codingTracker.jzhartman/CodingTracker/Startup.cs b/codingTracker.jzhartman/CodingTracker/Startup.cs index 90456acd7..dc7b6aa87 100644 --- a/codingTracker.jzhartman/CodingTracker/Startup.cs +++ b/codingTracker.jzhartman/CodingTracker/Startup.cs @@ -39,7 +39,7 @@ public static IServiceProvider ConfigureServices() //Resgister All Views services.AddSingleton(); services.AddSingleton(new UserInput(dateTimeFormat)); - //services.AddSingleton(new TrackSessionView(dateTimeFormat)); + services.AddSingleton(); services.AddSingleton(provider => new SqliteConnectionFactory(connectionString)); From 1bdfe6b141b6905e9cd84e451d11088f759932b5 Mon Sep 17 00:00:00 2001 From: jzhartman Date: Sat, 13 Sep 2025 00:22:25 -0400 Subject: [PATCH 25/57] Update seems to work well for all instances Only need to change confirmation message for no changes --- .../EntryListController.cs | 80 ++++++++++++------- .../MainMenuController.cs | 41 ---------- .../Interfaces/ICodingSessionRepository.cs | 5 +- .../{StartTimeUpdate.cs => TimeUpdate.cs} | 4 +- .../Repositories/CodingSessionRepository.cs | 37 +++++---- .../CodingSessionDataService.cs | 37 ++++++--- .../Interfaces/ICodingSessionDataService.cs | 5 +- .../Interfaces/IUserInput.cs | 3 +- .../CodingTracker.Views/UserInput.cs | 63 +++++++++++++-- 9 files changed, 164 insertions(+), 111 deletions(-) rename codingTracker.jzhartman/CodingTracker.Data/Parameters/{StartTimeUpdate.cs => TimeUpdate.cs} (72%) diff --git a/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs b/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs index 9c17d9034..f7fa09852 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs @@ -51,7 +51,7 @@ public void Run() { case "Change Record": recordId = _inputView.GetRecordIdFromUser("update", sessions.Count()); - ManageUserUpdate(sessions[recordId]); + ManageUserUpdate(sessions[recordId-1]); break; case "Delete Record": recordId = _inputView.GetRecordIdFromUser("delete", sessions.Count()); @@ -74,49 +74,75 @@ public void Run() private void ManageUserUpdate(CodingSessionDataRecord session) { - var newStartTime = _inputView.GetTimeFromUser("new start time", true); + var newStartTime = GetUpdatedStartTime(session); + var newEndTime = GetUpdatedEndTime(session, newStartTime); - if (newStartTime == null) newStartTime = session.StartTime; - // Run validation on start time - // Same as previous validation except it can be >= current start time + var updatedSession = new CodingSession(newStartTime, newEndTime); - var newEndTime = _inputView.GetTimeFromUser("new end time", true); - // Validate - + if (ConfirmUpdate(session, updatedSession)) + UpdateSession(updatedSession, session.Id); - // Run validation on both dates - // update if good + // get confirmation + // if confirmed => _repository.UpdateSession(updatedsession); + // else => cancelled update message -- return to menu } - private DateTime GetUpdatedStartTime(DateTime originalTime) + private void UpdateSession(CodingSession session, long id) + { + var sessionDTO = new CodingSessionDataRecord {Id = id, StartTime = session.StartTime, EndTime = session.EndTime, Duration = (int)session.Duration }; + _service.UpdateSession(sessionDTO); + Messages.ActionCompleteMessage(true, "Success", "Coding session successfully added!"); + } + private bool ConfirmUpdate(CodingSessionDataRecord session, CodingSession updatedSession) + { + return _inputView.GetUpdateSessionConfirmationFromUser(session, updatedSession); + } + private DateTime GetUpdatedStartTime(CodingSessionDataRecord session) { var output = new DateTime(); bool startTimeValid = false; while (startTimeValid == false) { - //output = _inputView.GetUpdatedStartTimeFromUser(originalTime); - - // Allow user to enter a start date with almost no validation -- will validate later? - startTimeValid = true; + var newStartTime = _inputView.GetTimeFromUser("new start time", "current start time", true); + var result = _service.ValidateUpdatedStartTime(session, newStartTime); + if (result.IsValid) + { + startTimeValid = true; + output = result.Value; + Messages.ConfirmationMessage(result.Value.ToString("yyyy-MM-dd HH:mm:ss")); + } + else + { + Messages.ErrorMessage(result.Parameter, result.Message); + } + } + return output; + } + private DateTime GetUpdatedEndTime(CodingSessionDataRecord session, DateTime newStartTime) + { + var output = new DateTime(); + bool startTimeValid = false; - //var result = _service.ValidateStartTime(output); + while (startTimeValid == false) + { + var newEndTime = _inputView.GetTimeFromUser("new end time", "current end time", true); + var result = _service.ValidateUpdatedEndTime(session, newStartTime, newEndTime); - //if (result.IsValid) - //{ - // startTimeValid = true; - // output = result.Value; - // Messages.ConfirmationMessage(result.Value.ToString("yyyy-MM-dd HH:mm:ss")); - //} - //else - //{ - // Messages.ErrorMessage(result.Parameter, result.Message); - //} + if (result.IsValid) + { + startTimeValid = true; + output = result.Value; + Messages.ConfirmationMessage(result.Value.ToString("yyyy-MM-dd HH:mm:ss")); + } + else + { + Messages.ErrorMessage(result.Parameter, result.Message); + } } return output; } - private (bool, List) GetSessionListBasedOnUserDateSelection() { bool returnToPreviousMenu = false; diff --git a/codingTracker.jzhartman/CodingTracker.Controller/MainMenuController.cs b/codingTracker.jzhartman/CodingTracker.Controller/MainMenuController.cs index 5205b22cb..84dae9976 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/MainMenuController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/MainMenuController.cs @@ -59,47 +59,6 @@ public void Run() break; } } - - - //All Code Below Here Is Basic Testing ONLY -- Not part of actual flow - - - //var sessions = _service.GetAllCodingSessions(); - //CodingSessionView.RenderCodingSessions(sessions); - - //Console.WriteLine(); - //int id = (int)sessions[5].Id; - //Console.WriteLine($"Deleting record {id}"); - //_service.DeleteById(id); - //sessions = _service.GetAllCodingSessions(); - //CodingSessionView.RenderCodingSessions(sessions); - - - //Console.WriteLine(); - //var startTime = DateTime.Parse("2025-01-07 00:00:00.1234"); - //var endTime = DateTime.Parse("2025-02-01 23:59:59"); - //var sessions = _service.GetByDateRange(startTime, endTime); - //CodingSessionView.RenderCodingSessions(sessions); - - //_service.UpdateStartTimeById((int)sessions[9].Id, DateTime.Now); - //_service.UpdateEndTimeById((int)sessions[15].Id, DateTime.Now); - - //startTime = DateTime.Parse("2025-01-01 20:00:00"); - //endTime = DateTime.Parse("2025-01-01 20:01:15"); - - //int duration = (int)endTime.Subtract(startTime).TotalSeconds; - - //_service.AddSession(new CodingSession{StartTime = startTime, EndTime = endTime, Duration = duration}); - - //sessions = _service.GetAllCodingSessions(); - //CodingSessionView.RenderCodingSessions(sessions); - - //Console.WriteLine(); - //var session = _service.GetById(2); - //CodingSessionView.RenderCodingSession(session); - - - } } } diff --git a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs index f26fe2f8d..8a3d5d6d3 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs @@ -1,4 +1,5 @@ using CodingTracker.Models.Entities; +using System; namespace CodingTracker.Data.Interfaces { @@ -8,9 +9,9 @@ public interface ICodingSessionRepository List GetAll(); List GetByDateRange(DateTime startTime, DateTime endTime); CodingSessionDataRecord GetById(int id); - void UpdateEndTimeById(int id, DateTime endTime); - void UpdateStartTimeById(int id, DateTime startTime); + void UpdateSession(CodingSessionDataRecord session); void DeleteById(int id); bool ExistsWithinTimeFrame(DateTime time); + bool ExistsWithinTimeFrameExcludingSessionById(CodingSessionDataRecord session, DateTime newTime); } } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Data/Parameters/StartTimeUpdate.cs b/codingTracker.jzhartman/CodingTracker.Data/Parameters/TimeUpdate.cs similarity index 72% rename from codingTracker.jzhartman/CodingTracker.Data/Parameters/StartTimeUpdate.cs rename to codingTracker.jzhartman/CodingTracker.Data/Parameters/TimeUpdate.cs index fe7808f87..ce4bb0c91 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Parameters/StartTimeUpdate.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Parameters/TimeUpdate.cs @@ -6,9 +6,9 @@ namespace CodingTracker.Data.Parameters { - public class StartTimeUpdate + public class TimeUpdate { public int Id { get; set; } - public DateTime StartTime { get; set; } + public DateTime Time { get; set; } } } diff --git a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs index 4580ddd23..ba82033fa 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs @@ -69,26 +69,10 @@ public void AddSession(CodingSession session) SaveData(sql, session); } - - - - - // - // - // UPDATE METHODS WILL NEED TO ACCOUNT FOR A CHANGE IN DURATION!!!!!!!!!!!!!! - // - // - public void UpdateStartTimeById(int id, DateTime startTime) + public void UpdateSession(CodingSessionDataRecord session) { - var parameters = new StartTimeUpdate { Id = id, StartTime = startTime }; - string sql = "update CodingSessions Set StartTime = @StartTime where Id = @Id"; - SaveData(sql, parameters); - } - public void UpdateEndTimeById(int id, DateTime endTime) - { - var parameters = new EndTimeUpdate { Id = id, EndTime = endTime }; - string sql = "update CodingSessions Set EndTime = @EndTime where Id = @Id"; - SaveData(sql, parameters); + string sql = "update CodingSessions Set StartTime = @StartTime, EndTime = @EndTime, Duration = @Duration where Id = @Id"; + SaveData(sql, session); } public void DeleteById(int id) { @@ -113,6 +97,21 @@ public bool ExistsWithinTimeFrame(DateTime time) return (count > 0); } + public bool ExistsWithinTimeFrameExcludingSessionById(CodingSessionDataRecord session, DateTime newTime) + { + var parameter = new TimeUpdate { Id = (int)session.Id, Time = newTime }; + using var connection = _connectionFactory.CreateConnection(); + + string sql = @"select count(1) from CodingSessions + where StartTime <= @Time + and EndTime >= @Time + and Id != @Id"; + + int count = connection.ExecuteScalar(sql, parameter); + + return (count > 0); + } + // NOT IMPLEMENTED YET!!!!! diff --git a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs index 4c165f9e0..a3bf3365c 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs @@ -43,14 +43,9 @@ public void DeleteById(int id) _repository.DeleteById(id); } - public void UpdateStartTimeById(int id, DateTime startTime) + public void UpdateSession(CodingSessionDataRecord session) { - _repository.UpdateStartTimeById(id, startTime); - } - - public void UpdateEndTimeById(int id, DateTime endTime) - { - _repository.UpdateEndTimeById(id, endTime); + _repository.UpdateSession(session); } public void AddSession(CodingSession session) @@ -73,9 +68,31 @@ public ValidationResult ValidateEndTime(DateTime input) else return ValidationResult.Success(input); } + public ValidationResult ValidateUpdatedStartTime(CodingSessionDataRecord session, DateTime newStartTime) + { + if (newStartTime == DateTime.MinValue) + return ValidationResult.Success(session.StartTime); + + else if (_repository.ExistsWithinTimeFrameExcludingSessionById(session, newStartTime)) + return ValidationResult.Fail("Start Time", "A record already exists for this time"); + + else + return ValidationResult.Success(newStartTime); + } + + public ValidationResult ValidateUpdatedEndTime(CodingSessionDataRecord session, DateTime newStartTime, DateTime newEndTime) + { + if (newEndTime == DateTime.MinValue && session.EndTime > newStartTime) + return ValidationResult.Success(session.EndTime); + + else if (newEndTime <= newStartTime) + return ValidationResult.Fail("End Time", "End time cannot be earlier than start time."); - // if (input.StartTime > session.EndTime) - //return ValidationResult.Fail("Start Time", - // "ERROR: End Time cannot be earlier than Start Time"); + else if (_repository.ExistsWithinTimeFrameExcludingSessionById(session, newEndTime)) + return ValidationResult.Fail("End Time", "A record already exists for this time"); + + else + return ValidationResult.Success(newEndTime); + } } } diff --git a/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionDataService.cs b/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionDataService.cs index 64745ea7f..bd7b60e8a 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionDataService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionDataService.cs @@ -10,9 +10,10 @@ public interface ICodingSessionDataService List GetAllCodingSessions(); List GetByDateRange(DateTime startTime, DateTime endTime); CodingSessionDataRecord GetById(int id); - void UpdateEndTimeById(int id, DateTime endTime); - void UpdateStartTimeById(int id, DateTime startTime); + void UpdateSession(CodingSessionDataRecord session); ValidationResult ValidateEndTime(DateTime input); ValidationResult ValidateStartTime(DateTime input); + ValidationResult ValidateUpdatedEndTime(CodingSessionDataRecord session, DateTime newStartTime, DateTime newEndTime); + ValidationResult ValidateUpdatedStartTime(CodingSessionDataRecord session, DateTime updatedStartTime); } } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInput.cs b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInput.cs index f2d245d5b..2f3c86134 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInput.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInput.cs @@ -6,7 +6,8 @@ public interface IUserInput { int GetRecordIdFromUser(string action, int max); bool GetAddSessionConfirmationFromUser(CodingSession session); - DateTime GetTimeFromUser(string parameterName, bool allowNull = false); + DateTime GetTimeFromUser(string parameterName, string nullBehavior = "", bool allowNull = false); + bool GetUpdateSessionConfirmationFromUser(CodingSessionDataRecord session, CodingSession updatedSession); //DateTime GetUpdatedStartTimeFromUser(DateTime originalTime); } } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Views/UserInput.cs b/codingTracker.jzhartman/CodingTracker.Views/UserInput.cs index 68b4481c9..9d37b5faf 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/UserInput.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/UserInput.cs @@ -7,6 +7,8 @@ using System.Reflection.Metadata; using System.Text; using System.Threading.Tasks; +using static System.Collections.Specialized.BitVector32; +using static System.Runtime.InteropServices.JavaScript.JSType; namespace CodingTracker.Views { @@ -17,18 +19,18 @@ public UserInput(string dateFormat) { _dateFormat = dateFormat; } - public DateTime GetTimeFromUser(string parameterName, bool allowNull = false) + public DateTime GetTimeFromUser(string parameterName, string nullBehavior = "", bool allowNull = false) { var date = new DateTime(); - string article = GetArticle(parameterName); - string promptText = $"Please enter {article} {parameterName} using the format [yellow]'yyyy-MM-dd HH:mm:ss'[/]:"; + var promptText = GenerateEnterDatePromptText(parameterName, nullBehavior, allowNull); if (allowNull) date = AnsiConsole.Prompt( - new TextPrompt(promptText) - .AllowEmpty()); + new TextPrompt(promptText) + .AllowEmpty()); else date = AnsiConsole.Prompt( - new TextPrompt(promptText)); + new TextPrompt(promptText)); + //Add custom validation for time format @@ -42,7 +44,7 @@ public int GetRecordIdFromUser(string action, int max) .Validate(input => { if (input < 1) return Spectre.Console.ValidationResult.Error("Too low"); - else if (input >= max) return Spectre.Console.ValidationResult.Error("Too high"); + else if (input > max) return Spectre.Console.ValidationResult.Error("Too high"); else return Spectre.Console.ValidationResult.Success(); })); @@ -66,6 +68,53 @@ public bool GetAddSessionConfirmationFromUser(CodingSession session) return confirmation; } + public bool GetUpdateSessionConfirmationFromUser(CodingSessionDataRecord session, CodingSession updatedSession) + { + string promptText = GenerateUpdateSessoinConfirmationPrompt(session, updatedSession); + + var confirmation = AnsiConsole.Prompt( + new TextPrompt(promptText) + .AddChoice(true) + .AddChoice(false) + .WithConverter(choice => choice ? "y" : "n")); + + return confirmation; + } + + private string GenerateUpdateSessoinConfirmationPrompt(CodingSessionDataRecord session, CodingSession updatedSession) + { + string prompt = $"Update coding session "; + + if (session.StartTime != updatedSession.StartTime) + prompt += $"start time from [yellow]{session.StartTime.ToString("yyyy-MM-dd HH:mm:ss")}[/] to [green]{updatedSession.StartTime.ToString("yyyy-MM-dd HH:mm:ss")}[/]"; + + if (session.StartTime != updatedSession.StartTime && session.EndTime != updatedSession.EndTime) + prompt += " and "; + + if (session.EndTime != updatedSession.EndTime) + prompt += $"end time from [yellow]{session.EndTime.ToString("yyyy-MM-dd HH:mm:ss")}[/] to [green]{updatedSession.EndTime.ToString("yyyy-MM-dd HH:mm:ss")}[/]"; + + prompt += $" for a new duration of [green]{updatedSession.DurationText}[/]."; + + return prompt; + } + private string GenerateEnterDatePromptText(string parameterName, string nullBehavior, bool allowNull) + { + string article = GetArticle(parameterName); + string promptText = $"Please enter {article} {parameterName} using the format [yellow]'yyyy-MM-dd HH:mm:ss'[/]"; + + if (allowNull) + { + promptText += $".\r\nPress enter with blank line to use {nullBehavior}:"; + } + else + { + promptText += ":"; + } + + return promptText; + } + private string GetArticle(string noun) { string article = "a"; From 5f6b7aa5a76c5e694f2ace79ff9251c6ef294998 Mon Sep 17 00:00:00 2001 From: jzhartman Date: Sat, 13 Sep 2025 23:32:28 -0400 Subject: [PATCH 26/57] Completed Update and Delete Fixed usings and namespace indent in all files --- .../EntryListController.cs | 340 +++++++++--------- .../Interfaces/IEntryListController.cs | 8 +- .../Interfaces/IMainMenuController.cs | 8 +- .../Interfaces/ITrackSessionController.cs | 9 +- .../MainMenuController.cs | 86 ++--- .../TrackSessionController.cs | 188 +++++----- .../CodingTracker.Data/DatabaseInitializer.cs | 113 +++--- .../Interfaces/ICodingSessionRepository.cs | 24 +- .../Interfaces/IDatabaseInitializer.cs | 8 +- .../Interfaces/ISqliteConnectionFactory.cs | 8 +- .../Parameters/DateRangeQuery.cs | 10 +- .../Parameters/DateValue.cs | 14 +- .../Parameters/EndTimeUpdate.cs | 16 +- .../Parameters/TimeUpdate.cs | 16 +- .../Repositories/CodingSessionRepository.cs | 212 ++++++----- .../SqliteConnectionFactory.cs | 31 +- .../TypeHandlers/DateTimeHandler.cs | 55 ++- .../Entities/CodingSession.cs | 48 ++- .../Entities/CodingSessionDataRecord.cs | 14 +- .../Validation/ValidationResult.cs | 50 ++- .../CodingSessionDataService.cs | 171 +++++---- .../Interfaces/ICodingSessionDataService.cs | 26 +- .../CodingTracker.Views/CodingSessionView.cs | 30 +- .../Interfaces/IMenuView.cs | 14 +- .../Interfaces/IUserInput.cs | 17 +- .../CodingTracker.Views/Menus/MenuView.cs | 122 +++---- .../CodingTracker.Views/Messages.cs | 83 +++-- .../CodingTracker.Views/UserInput.cs | 238 ++++++------ .../Configuration/DateTimeOptions.cs | 14 +- .../CodingTracker/Startup.cs | 50 ++- 30 files changed, 950 insertions(+), 1073 deletions(-) diff --git a/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs b/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs index f7fa09852..fe0b54f5d 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs @@ -1,218 +1,224 @@ using CodingTracker.Models.Entities; -using CodingTracker.Services; using CodingTracker.Services.Interfaces; using CodingTracker.Views; using CodingTracker.Views.Interfaces; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using static System.Collections.Specialized.BitVector32; - -namespace CodingTracker.Controller.Interfaces + +namespace CodingTracker.Controller.Interfaces; + +public class EntryListController : IEntryListController { - public class EntryListController : IEntryListController + private readonly ICodingSessionDataService _service; + private readonly IMenuView _menuView; + private readonly IUserInput _inputView; + + public EntryListController(ICodingSessionDataService service, + IMenuView menuView, + IUserInput inputView) { - private readonly ICodingSessionDataService _service; - private readonly IMenuView _menuView; - private readonly IUserInput _inputView; + _service = service; + _menuView = menuView; + _inputView = inputView; + } - public EntryListController(ICodingSessionDataService service, - IMenuView menuView, - IUserInput inputView) - { - _service = service; - _menuView = menuView; - _inputView = inputView; - } + public void Run() + { + bool returnToPreviousMenu = false; - public void Run() + while (!returnToPreviousMenu) { + var dateRangeSelection = GetDateRangeSelectionFromUser(); - bool returnToPreviousMenu = false; + if (dateRangeSelection == "Return to Previous Menu") { returnToPreviousMenu = true; continue; } - while (!returnToPreviousMenu) - { - (returnToPreviousMenu, var sessions) = GetSessionListBasedOnUserDateSelection(); + (DateTime startTime, DateTime endTime) = GetDatesBasedOnUserSelection(dateRangeSelection); + var sessions = _service.GetSessionListByDateRange(startTime, endTime); - if (returnToPreviousMenu) continue; + bool returnToDateSelection = false; + while (!returnToDateSelection) + { CodingSessionView.RenderCodingSessions(sessions); - bool returnToDateSelection = false; + var selection = _menuView.RenderUpdateOrDeleteOptionsAndGetSelection(); - while (!returnToDateSelection) + switch (selection) { - var selection = _menuView.RenderUpdateOrDeleteOptionsAndGetSelection(); - var recordId = 0; - - switch (selection) - { - case "Change Record": - recordId = _inputView.GetRecordIdFromUser("update", sessions.Count()); - ManageUserUpdate(sessions[recordId-1]); - break; - case "Delete Record": - recordId = _inputView.GetRecordIdFromUser("delete", sessions.Count()); - - break; - case "Return to Previous Menu": - returnToDateSelection = true; - break; - } - + case "Change Record": + ManageSessionUpdate(sessions); + break; + case "Delete Record": + ManageSessionDelete(sessions); + break; + case "Return to Previous Menu": + returnToDateSelection = true; + break; } - - + sessions = _service.GetSessionListByDateRange(startTime, endTime); } + } + } + private void ManageSessionDelete(List sessions) + { + var recordId = _inputView.GetRecordIdFromUser("delete", sessions.Count()) - 1; - // Get Update/Delete/New Date Range/Return + if (ConfirmDelete(sessions[recordId])) + DeleteSession(sessions[recordId]); + else + Messages.ActionCancelled("deletion"); - } + } + private void DeleteSession(CodingSessionDataRecord session) + { + _service.DeleteById((int)session.Id); + } + private bool ConfirmDelete(CodingSessionDataRecord session) + { + return _inputView.GetDeleteSessionConfirmationFromUser(session); + } + private void ManageSessionUpdate(List sessions) + { + var recordId = _inputView.GetRecordIdFromUser("update", sessions.Count()) - 1; - private void ManageUserUpdate(CodingSessionDataRecord session) - { - var newStartTime = GetUpdatedStartTime(session); - var newEndTime = GetUpdatedEndTime(session, newStartTime); + var newStartTime = GetUpdatedStartTime(sessions[recordId]); + var newEndTime = GetUpdatedEndTime(sessions[recordId], newStartTime); - var updatedSession = new CodingSession(newStartTime, newEndTime); + var updatedSession = new CodingSession(newStartTime, newEndTime); - if (ConfirmUpdate(session, updatedSession)) - UpdateSession(updatedSession, session.Id); + if (ConfirmUpdate(sessions[recordId], updatedSession)) + UpdateSession(updatedSession, sessions[recordId].Id); + else + Messages.ActionCancelled("update"); - // get confirmation - // if confirmed => _repository.UpdateSession(updatedsession); - // else => cancelled update message -- return to menu - } + // get confirmation + // if confirmed => _repository.UpdateSession(updatedsession); + // else => cancelled update message -- return to menu + } + private void UpdateSession(CodingSession session, long id) + { + var sessionDTO = new CodingSessionDataRecord {Id = id, StartTime = session.StartTime, EndTime = session.EndTime, Duration = (int)session.Duration }; + _service.UpdateSession(sessionDTO); + Messages.ActionComplete(true, "Success", "Coding session successfully added!"); + } + private bool ConfirmUpdate(CodingSessionDataRecord session, CodingSession updatedSession) + { + return _inputView.GetUpdateSessionConfirmationFromUser(session, updatedSession); + } + private DateTime GetUpdatedStartTime(CodingSessionDataRecord session) + { + var output = new DateTime(); + bool startTimeValid = false; - private void UpdateSession(CodingSession session, long id) + while (startTimeValid == false) { - var sessionDTO = new CodingSessionDataRecord {Id = id, StartTime = session.StartTime, EndTime = session.EndTime, Duration = (int)session.Duration }; - _service.UpdateSession(sessionDTO); - Messages.ActionCompleteMessage(true, "Success", "Coding session successfully added!"); - } - private bool ConfirmUpdate(CodingSessionDataRecord session, CodingSession updatedSession) - { - return _inputView.GetUpdateSessionConfirmationFromUser(session, updatedSession); - } - private DateTime GetUpdatedStartTime(CodingSessionDataRecord session) - { - var output = new DateTime(); - bool startTimeValid = false; + var newStartTime = _inputView.GetTimeFromUser("new start time", "current start time", true); + var result = _service.ValidateUpdatedStartTime(session, newStartTime); - while (startTimeValid == false) + if (result.IsValid) { - var newStartTime = _inputView.GetTimeFromUser("new start time", "current start time", true); - var result = _service.ValidateUpdatedStartTime(session, newStartTime); - - if (result.IsValid) - { - startTimeValid = true; - output = result.Value; - Messages.ConfirmationMessage(result.Value.ToString("yyyy-MM-dd HH:mm:ss")); - } - else - { - Messages.ErrorMessage(result.Parameter, result.Message); - } + startTimeValid = true; + output = result.Value; + Messages.Confirmation(result.Value.ToString("yyyy-MM-dd HH:mm:ss")); } - return output; - } - private DateTime GetUpdatedEndTime(CodingSessionDataRecord session, DateTime newStartTime) - { - var output = new DateTime(); - bool startTimeValid = false; - - while (startTimeValid == false) + else { - var newEndTime = _inputView.GetTimeFromUser("new end time", "current end time", true); - var result = _service.ValidateUpdatedEndTime(session, newStartTime, newEndTime); - - if (result.IsValid) - { - startTimeValid = true; - output = result.Value; - Messages.ConfirmationMessage(result.Value.ToString("yyyy-MM-dd HH:mm:ss")); - } - else - { - Messages.ErrorMessage(result.Parameter, result.Message); - } + Messages.Error(result.Parameter, result.Message); } - return output; } - private (bool, List) GetSessionListBasedOnUserDateSelection() - { - bool returnToPreviousMenu = false; - var sessions = new List(); - DateTime startTime = new DateTime(); - DateTime endTime = new DateTime(); + return output; + } + private DateTime GetUpdatedEndTime(CodingSessionDataRecord session, DateTime newStartTime) + { + var output = new DateTime(); + bool startTimeValid = false; - var selection = _menuView.RenderEntryViewOptionsAndGetSelection(); + while (startTimeValid == false) + { + var newEndTime = _inputView.GetTimeFromUser("new end time", "current end time", true); + var result = _service.ValidateUpdatedEndTime(session, newStartTime, newEndTime); - switch (selection) + if (result.IsValid) { - case "All": - sessions = _service.GetAllCodingSessions(); - break; - case "One Year": - sessions = GetSessionsForPastYear(); - break; - case "Year to Date": - sessions = GetSessionsForYearToDate(); - break; - case "Enter Date Range": - sessions = GetSessionsByDateRange(); - break; - case "Return to Previous Menu": - returnToPreviousMenu = true; - break; + startTimeValid = true; + output = result.Value; + Messages.Confirmation(result.Value.ToString("yyyy-MM-dd HH:mm:ss")); + } + else + { + Messages.Error(result.Parameter, result.Message); } - - return (returnToPreviousMenu, sessions); } + return output; + } + private string GetDateRangeSelectionFromUser() + { + return _menuView.RenderEntryViewOptionsAndGetSelection(); + } + private (DateTime, DateTime) GetDatesBasedOnUserSelection(string selection) + { + bool returnToPreviousMenu = false; + DateTime startTime = new DateTime(); + DateTime endTime = new DateTime(); - private List GetSessionsForPastYear() - { - var endTime = DateTime.Now; - var startTime = endTime.AddYears(-1); - - return _service.GetByDateRange(startTime, endTime); - } - private List GetSessionsForYearToDate() + switch (selection) { - var endTime = DateTime.Now; - var startTime = new DateTime(endTime.Year, 1, 1); - return _service.GetByDateRange(startTime, endTime); + case "All": + (startTime, endTime) = GetAllDates(); + break; + case "One Year": + (startTime, endTime) = GetDateRangeForPastYear(); + break; + case "Year to Date": + (startTime, endTime) = GetDateRangeForYearToDate(); + break; + case "Enter Date Range": + (startTime, endTime) = GetCustomDateRange(); + break; } - private List GetSessionsByDateRange() + + return (startTime, endTime); + } + private (DateTime, DateTime) GetAllDates() + { + var startTime = DateTime.MinValue; + var endTime = DateTime.MaxValue; + return (startTime, endTime); + } + private (DateTime, DateTime) GetDateRangeForPastYear() + { + var endTime = DateTime.Now; + var startTime = endTime.AddYears(-1); + return (startTime, endTime); + } + private (DateTime, DateTime) GetDateRangeForYearToDate() + { + var endTime = DateTime.Now; + var startTime = new DateTime(endTime.Year, 1, 1); + return (startTime, endTime); + } + private (DateTime, DateTime) GetCustomDateRange() + { + var startTime = _inputView.GetTimeFromUser("start time"); + var endTime = new DateTime(); + bool endTimeValid = false; + + while (endTimeValid == false) { - var startTime = _inputView.GetTimeFromUser("start time"); - var endTime = new DateTime(); - bool endTimeValid = false; + endTime = _inputView.GetTimeFromUser("end time"); - while (endTimeValid == false) + if (endTime <= startTime) { - endTime = _inputView.GetTimeFromUser("end time"); - - if (endTime <= startTime) - { - var parameter = "End Time"; - var message = $"The end time must be later than {startTime.ToString("yyyy-MM-dd HH:mm:ss")}"; - Messages.ErrorMessage(parameter, message); - } - else - { - endTimeValid = true; - } + var parameter = "End Time"; + var message = $"The end time must be later than {startTime.ToString("yyyy-MM-dd HH:mm:ss")}"; + Messages.Error(parameter, message); + } + else + { + endTimeValid = true; } - - return _service.GetByDateRange(startTime, endTime); - } + return (startTime, endTime); } } diff --git a/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/IEntryListController.cs b/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/IEntryListController.cs index b322eedda..0b683fcdc 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/IEntryListController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/IEntryListController.cs @@ -1,7 +1,5 @@ -namespace CodingTracker.Controller.Interfaces +namespace CodingTracker.Controller.Interfaces; +public interface IEntryListController { - public interface IEntryListController - { - void Run(); - } + void Run(); } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/IMainMenuController.cs b/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/IMainMenuController.cs index c2dd51195..ec0de5201 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/IMainMenuController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/IMainMenuController.cs @@ -1,7 +1,5 @@ -namespace CodingTracker.Controller.Interfaces +namespace CodingTracker.Controller.Interfaces; +public interface IMainMenuController { - public interface IMainMenuController - { - void Run(); - } + void Run(); } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/ITrackSessionController.cs b/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/ITrackSessionController.cs index 6d4847e04..586384d9c 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/ITrackSessionController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/ITrackSessionController.cs @@ -1,8 +1,5 @@ - -namespace CodingTracker.Controller.Interfaces +namespace CodingTracker.Controller.Interfaces; +public interface ITrackSessionController { - public interface ITrackSessionController - { - void Run(); - } + void Run(); } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Controller/MainMenuController.cs b/codingTracker.jzhartman/CodingTracker.Controller/MainMenuController.cs index 84dae9976..2614e10d5 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/MainMenuController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/MainMenuController.cs @@ -1,63 +1,53 @@ using CodingTracker.Controller.Interfaces; -using CodingTracker.Models.Entities; -using CodingTracker.Services; using CodingTracker.Services.Interfaces; using CodingTracker.Views; using CodingTracker.Views.Interfaces; -using CodingTracker.Views.Menus; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -namespace CodingTracker.Controller +namespace CodingTracker.Controller; +public class MainMenuController : IMainMenuController { - public class MainMenuController : IMainMenuController + private readonly ICodingSessionDataService _service; + private readonly ITrackSessionController _trackController; + private readonly IEntryListController _entryListController; + private readonly IMenuView _view; + + public MainMenuController(ICodingSessionDataService service, + ITrackSessionController trackController, IEntryListController entryListController, + IMenuView view) { - private readonly ICodingSessionDataService _service; - private readonly ITrackSessionController _trackController; - private readonly IEntryListController _entryListController; - private readonly IMenuView _view; + _service = service; + _trackController = trackController; + _entryListController = entryListController; + _view = view; + } - public MainMenuController(ICodingSessionDataService service, - ITrackSessionController trackController, IEntryListController entryListController, - IMenuView view) - { - _service = service; - _trackController = trackController; - _entryListController = entryListController; - _view = view; - } + public void Run() + { + bool exitApp = false; - public void Run() + while (!exitApp) { - bool exitApp = false; - - while (!exitApp) - { - Messages.RenderWelcome(); - var selection = _view.RenderMainMenuAndGetSelection(); + Messages.RenderWelcome(); + var selection = _view.RenderMainMenuAndGetSelection(); - switch (selection) - { - case "Track Session": // Submenu: Enter times/Stopwatch/Return - _trackController.Run(); - break; - case "View/Manage Entries": // Submenu: View Entries (range or all)/Update/Delete - _entryListController.Run(); - break; - case "View Reports": // Enter Range-Period??? --> Print all records for period --> Print report data - break; - case "Manage Goal": // Print current goal+progress/Give option to change goal - break; - case "Exit": // Generic goodbye message - exitApp = true; - break; - default: - break; - } + switch (selection) + { + case "Track Session": // Submenu: Enter times/Stopwatch/Return + _trackController.Run(); + break; + case "View/Manage Entries": // Submenu: View Entries (range or all)/Update/Delete + _entryListController.Run(); + break; + case "View Reports": // Enter Range-Period??? --> Print all records for period --> Print report data + break; + case "Manage Goal": // Print current goal+progress/Give option to change goal + break; + case "Exit": // Generic goodbye message + exitApp = true; + break; + default: + break; } } } diff --git a/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs b/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs index cb95581b9..cbaa02512 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs @@ -3,133 +3,123 @@ using CodingTracker.Services.Interfaces; using CodingTracker.Views; using CodingTracker.Views.Interfaces; -using CodingTracker.Views.Menus; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -namespace CodingTracker.Controller +namespace CodingTracker.Controller; +public class TrackSessionController : ITrackSessionController { - public class TrackSessionController : ITrackSessionController + private readonly ICodingSessionDataService _service; + private readonly IMenuView _menuView; + private readonly IUserInput _inputView; + + public TrackSessionController(ICodingSessionDataService service, IMenuView menuView, IUserInput inputView) { - private readonly ICodingSessionDataService _service; - private readonly IMenuView _menuView; - private readonly IUserInput _inputView; + _service = service; + _menuView = menuView; + _inputView = inputView; + } - public TrackSessionController(ICodingSessionDataService service, IMenuView menuView, IUserInput inputView) - { - _service = service; - _menuView = menuView; - _inputView = inputView; - } + public void Run() + { + bool returnToMainMenu = false; - public void Run() + while (!returnToMainMenu) { - bool returnToMainMenu = false; + var selection = _menuView.RenderTrackingMenuAndGetSelection(); - while (!returnToMainMenu) + switch (selection) { + case "Enter Start and End Times": + var startTime = GetStartTime(); + var endTime = GetEndTime(startTime); + var session = new CodingSession(startTime, endTime); - Messages.RenderWelcome(); - var selection = _menuView.RenderTrackingMenuAndGetSelection(); - - switch (selection) - { - case "Enter Start and End Times": - var startTime = GetStartTime(); - var endTime = GetEndTime(startTime); - var session = new CodingSession(startTime, endTime); - - if (ConfirmSession(session)) - { - AddSession(session); - } - break; - case "Begin Timer": - GetTimesWithStopwatch(); - break; - case "Return to Main Menu": - returnToMainMenu = true; - break; - default: - break; - } + if (ConfirmSession(session)) + { + AddSession(session); + } + break; + case "Begin Timer": + GetTimesWithStopwatch(); + break; + case "Return to Main Menu": + returnToMainMenu = true; + break; + default: + break; } } + } - private bool ConfirmSession(CodingSession session) - { - return _inputView.GetAddSessionConfirmationFromUser(session); - } + private bool ConfirmSession(CodingSession session) + { + return _inputView.GetAddSessionConfirmationFromUser(session); + } - private DateTime GetStartTime() + private DateTime GetStartTime() + { + var output = new DateTime(); + bool startTimeValid = false; + + while (startTimeValid == false) { - var output = new DateTime(); - bool startTimeValid = false; + output = _inputView.GetTimeFromUser("start time"); - while (startTimeValid == false) - { - output = _inputView.GetTimeFromUser("start time"); - - var result = _service.ValidateStartTime(output); + var result = _service.ValidateStartTime(output); - if (result.IsValid) - { - startTimeValid = true; - output = result.Value; - Messages.ConfirmationMessage(result.Value.ToString("yyyy-MM-dd HH:mm:ss")); - } - else - { - Messages.ErrorMessage(result.Parameter, result.Message); - } + if (result.IsValid) + { + startTimeValid = true; + output = result.Value; + Messages.Confirmation(result.Value.ToString("yyyy-MM-dd HH:mm:ss")); + } + else + { + Messages.Error(result.Parameter, result.Message); } - return output; } - private DateTime GetEndTime(DateTime startTime) + return output; + } + private DateTime GetEndTime(DateTime startTime) + { + var output = new DateTime(); + bool endTimeValid = false; + + while (endTimeValid == false) { - var output = new DateTime(); - bool endTimeValid = false; + output = _inputView.GetTimeFromUser("end time"); - while (endTimeValid == false) + if (output <= startTime) + { + var parameter = "End Time"; + var message = $"The end time must be later than {startTime.ToString("yyyy-MM-dd HH:mm:ss")}"; + Messages.Error(parameter, message); + } + else { - output = _inputView.GetTimeFromUser("end time"); + var result = _service.ValidateEndTime(output); - if (output <= startTime) + if (result.IsValid) { - var parameter = "End Time"; - var message = $"The end time must be later than {startTime.ToString("yyyy-MM-dd HH:mm:ss")}"; - Messages.ErrorMessage(parameter, message); + endTimeValid = true; + output = result.Value; + Messages.Confirmation(result.Value.ToString("yyyy-MM-dd HH:mm:ss")); } else { - var result = _service.ValidateEndTime(output); - - if (result.IsValid) - { - endTimeValid = true; - output = result.Value; - Messages.ConfirmationMessage(result.Value.ToString("yyyy-MM-dd HH:mm:ss")); - } - else - { - Messages.ErrorMessage(result.Parameter, result.Message); - } - } + Messages.Error(result.Parameter, result.Message); + } } - return output; - } - private void AddSession(CodingSession session) - { - _service.AddSession(session); - Messages.ActionCompleteMessage(true, "Success", "Coding session successfully added!"); } + return output; + } + private void AddSession(CodingSession session) + { + _service.AddSession(session); + Messages.ActionComplete(true, "Success", "Coding session successfully added!"); + } - private void GetTimesWithStopwatch() - { - // Code here - } + private void GetTimesWithStopwatch() + { + // Code here } } diff --git a/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs b/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs index 318283ef9..6135102bf 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs @@ -1,87 +1,80 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Microsoft.Data.Sqlite; +using CodingTracker.Data.Interfaces; using Dapper; -using CodingTracker.Data.Interfaces; +using Microsoft.Data.Sqlite; -namespace CodingTracker.Data +namespace CodingTracker.Data; +public class DatabaseInitializer : IDatabaseInitializer { - public class DatabaseInitializer : IDatabaseInitializer - { - private readonly ISqliteConnectionFactory _connectionFactory; + private readonly ISqliteConnectionFactory _connectionFactory; - public DatabaseInitializer(ISqliteConnectionFactory connectionfactory) - { - _connectionFactory = connectionfactory; - } + public DatabaseInitializer(ISqliteConnectionFactory connectionfactory) + { + _connectionFactory = connectionfactory; + } - public void Initialize() + public void Initialize() + { + if (TableExists() == false) { - if (TableExists() == false) - { - CreateTable(); - SeedData(); - } + CreateTable(); + SeedData(); } + } - private bool TableExists() - { - using var connection = _connectionFactory.CreateConnection(); - int count = connection.ExecuteScalar("select count(*) from sqlite_master where type='table' and name='CodingSessions'"); - return count == 1; - } + private bool TableExists() + { + using var connection = _connectionFactory.CreateConnection(); + int count = connection.ExecuteScalar("select count(*) from sqlite_master where type='table' and name='CodingSessions'"); + return count == 1; + } - private void CreateTable() - { - using var connection = _connectionFactory.CreateConnection(); + private void CreateTable() + { + using var connection = _connectionFactory.CreateConnection(); - var command = connection.CreateCommand(); - command.CommandText = @"create table if not exists CodingSessions( + var command = connection.CreateCommand(); + command.CommandText = @"create table if not exists CodingSessions( Id integer primary key not null, StartTime text not null, EndTime text not null, Duration integer not null )"; - command.ExecuteNonQuery(); - } + command.ExecuteNonQuery(); + } - private void SeedData() - { - using var connection = _connectionFactory.CreateConnection(); + private void SeedData() + { + using var connection = _connectionFactory.CreateConnection(); - var command = connection.CreateCommand(); + var command = connection.CreateCommand(); - string sql = CreateSqlString(); + string sql = CreateSqlString(); - command.CommandText = sql; - command.ExecuteNonQuery(); - } - - private string CreateSqlString() - { - string sql = "insert into CodingSessions(StartTime, EndTime, Duration)\nValues\n"; + command.CommandText = sql; + command.ExecuteNonQuery(); + } - Random rand = new Random(); + private string CreateSqlString() + { + string sql = "insert into CodingSessions(StartTime, EndTime, Duration)\nValues\n"; - DateTime startDate = DateTime.Parse("2025-01-01 21:00:00"); - DateTime endDate = startDate.AddHours(2); - TimeSpan duration = endDate - startDate; + Random rand = new Random(); - for (int i = 0; i < 50; i++) - { - if (i != 0) sql += ",\n"; + DateTime startDate = DateTime.Parse("2025-01-01 21:00:00"); + DateTime endDate = startDate.AddHours(2); + TimeSpan duration = endDate - startDate; - sql += $"('{startDate.ToString("yyyy-MM-dd HH:mm:ss")}', '{endDate.ToString("yyyy-MM-dd HH:mm:ss")}', {duration.TotalSeconds})"; - startDate = startDate.AddDays(1); - endDate = startDate.AddHours(rand.Next(1, 3)).AddMinutes(rand.Next(0, 60)).AddSeconds(rand.Next(0, 60)); - duration = endDate - startDate; - } - sql += ";"; + for (int i = 0; i < 50; i++) + { + if (i != 0) sql += ",\n"; - return sql; + sql += $"('{startDate.ToString("yyyy-MM-dd HH:mm:ss")}', '{endDate.ToString("yyyy-MM-dd HH:mm:ss")}', {duration.TotalSeconds})"; + startDate = startDate.AddDays(1); + endDate = startDate.AddHours(rand.Next(1, 3)).AddMinutes(rand.Next(0, 60)).AddSeconds(rand.Next(0, 60)); + duration = endDate - startDate; } + sql += ";"; + + return sql; } } diff --git a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs index 8a3d5d6d3..cf4489350 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs @@ -1,17 +1,13 @@ using CodingTracker.Models.Entities; -using System; - -namespace CodingTracker.Data.Interfaces +namespace CodingTracker.Data.Interfaces; +public interface ICodingSessionRepository { - public interface ICodingSessionRepository - { - void AddSession(CodingSession session); - List GetAll(); - List GetByDateRange(DateTime startTime, DateTime endTime); - CodingSessionDataRecord GetById(int id); - void UpdateSession(CodingSessionDataRecord session); - void DeleteById(int id); - bool ExistsWithinTimeFrame(DateTime time); - bool ExistsWithinTimeFrameExcludingSessionById(CodingSessionDataRecord session, DateTime newTime); - } + void AddSession(CodingSession session); + List GetAll(); + List GetByDateRange(DateTime startTime, DateTime endTime); + CodingSessionDataRecord GetById(int id); + void UpdateSession(CodingSessionDataRecord session); + void DeleteById(int id); + bool ExistsWithinTimeFrame(DateTime time); + bool ExistsWithinTimeFrameExcludingSessionById(CodingSessionDataRecord session, DateTime newTime); } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/IDatabaseInitializer.cs b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/IDatabaseInitializer.cs index 9f7c57943..0b164e0b7 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/IDatabaseInitializer.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/IDatabaseInitializer.cs @@ -1,7 +1,5 @@ -namespace CodingTracker.Data.Interfaces +namespace CodingTracker.Data.Interfaces; +public interface IDatabaseInitializer { - public interface IDatabaseInitializer - { - void Initialize(); - } + void Initialize(); } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ISqliteConnectionFactory.cs b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ISqliteConnectionFactory.cs index 8e21fe809..6d28597f4 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ISqliteConnectionFactory.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ISqliteConnectionFactory.cs @@ -1,9 +1,7 @@ using System.Data; -namespace CodingTracker.Data.Interfaces +namespace CodingTracker.Data.Interfaces; +public interface ISqliteConnectionFactory { - public interface ISqliteConnectionFactory - { - IDbConnection CreateConnection(); - } + IDbConnection CreateConnection(); } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Data/Parameters/DateRangeQuery.cs b/codingTracker.jzhartman/CodingTracker.Data/Parameters/DateRangeQuery.cs index 2971b3607..888d1f415 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Parameters/DateRangeQuery.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Parameters/DateRangeQuery.cs @@ -1,8 +1,6 @@ -namespace CodingTracker.Data.Parameters +namespace CodingTracker.Data.Parameters; +public class DateRangeQuery { - public class DateRangeQuery - { - public DateTime StartTime { get; set; } - public DateTime EndTime { get; set; } - } + public DateTime StartTime { get; set; } + public DateTime EndTime { get; set; } } diff --git a/codingTracker.jzhartman/CodingTracker.Data/Parameters/DateValue.cs b/codingTracker.jzhartman/CodingTracker.Data/Parameters/DateValue.cs index 262638216..3c0e9b5ed 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Parameters/DateValue.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Parameters/DateValue.cs @@ -1,13 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace CodingTracker.Data.Parameters +namespace CodingTracker.Data.Parameters; +public class DateValue { - public class DateValue - { - public DateTime Time { get; set; } - } + public DateTime Time { get; set; } } diff --git a/codingTracker.jzhartman/CodingTracker.Data/Parameters/EndTimeUpdate.cs b/codingTracker.jzhartman/CodingTracker.Data/Parameters/EndTimeUpdate.cs index 26214d8cd..924cf91ec 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Parameters/EndTimeUpdate.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Parameters/EndTimeUpdate.cs @@ -1,14 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace CodingTracker.Data.Parameters +namespace CodingTracker.Data.Parameters; +public class EndTimeUpdate { - public class EndTimeUpdate - { - public int Id { get; set; } - public DateTime EndTime { get; set; } - } + public int Id { get; set; } + public DateTime EndTime { get; set; } } diff --git a/codingTracker.jzhartman/CodingTracker.Data/Parameters/TimeUpdate.cs b/codingTracker.jzhartman/CodingTracker.Data/Parameters/TimeUpdate.cs index ce4bb0c91..4fe56b78c 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Parameters/TimeUpdate.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Parameters/TimeUpdate.cs @@ -1,14 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace CodingTracker.Data.Parameters +namespace CodingTracker.Data.Parameters; +public class TimeUpdate { - public class TimeUpdate - { - public int Id { get; set; } - public DateTime Time { get; set; } - } + public int Id { get; set; } + public DateTime Time { get; set; } } diff --git a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs index ba82033fa..b8dd0ba88 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs @@ -3,136 +3,134 @@ using CodingTracker.Models.Entities; using Dapper; -namespace CodingTracker.Data.Repositories +namespace CodingTracker.Data.Repositories; +public class CodingSessionRepository : ICodingSessionRepository { - public class CodingSessionRepository : ICodingSessionRepository + private readonly ISqliteConnectionFactory _connectionFactory; + + public CodingSessionRepository(ISqliteConnectionFactory connectionFactory) + { + _connectionFactory = connectionFactory; + } + + private List LoadData(string sql) + { + using var connection = _connectionFactory.CreateConnection(); + List sessions = connection.Query(sql).ToList(); + return sessions; + } + private List LoadData(string sql, U parameters) + { + using var connection = _connectionFactory.CreateConnection(); + List sessions = connection.Query(sql, parameters).ToList(); + return sessions; + } + private void SaveData(string sql) + { + using var connection = _connectionFactory.CreateConnection(); + connection.Execute(sql); + } + private void SaveData(string sql, T parameters) + { + using var connection = _connectionFactory.CreateConnection(); + connection.Execute(sql, parameters); + } + + + + public List GetAll() + { + string sql = "Select * from CodingSessions order by StartTime"; + return LoadData(sql); + } + public List GetByDateRange(DateTime begin, DateTime finish) + { + var dateRange = new DateRangeQuery { StartTime = begin, EndTime = finish }; + string sql = @"Select * from CodingSessions where (StartTime >= @StartTime) AND (endTime <= @EndTime) order by StartTime"; + return LoadData(sql, dateRange); + } + public CodingSessionDataRecord GetById(int id) + { + string sql = $"select * from CodingSessions where Id = {id}"; + return LoadData(sql).FirstOrDefault(); + } + public int GetRecordCount() + { + string sql = "select count(*) from CodingSessions"; + return LoadData(sql).First(); + } + + + + public void AddSession(CodingSession session) + { + string sql = "insert into CodingSessions (StartTime, EndTime, Duration) values (@StartTime, @EndTime, @Duration)"; + SaveData(sql, session); + } + + public void UpdateSession(CodingSessionDataRecord session) + { + string sql = "update CodingSessions Set StartTime = @StartTime, EndTime = @EndTime, Duration = @Duration where Id = @Id"; + SaveData(sql, session); + } + public void DeleteById(int id) + { + string sql = $"delete from CodingSessions where Id = {id}"; + SaveData(sql); + } + + + + + public bool ExistsWithinTimeFrame(DateTime time) { - private readonly ISqliteConnectionFactory _connectionFactory; - - public CodingSessionRepository(ISqliteConnectionFactory connectionFactory) - { - _connectionFactory = connectionFactory; - } - - private List LoadData(string sql) - { - using var connection = _connectionFactory.CreateConnection(); - List sessions = connection.Query(sql).ToList(); - return sessions; - } - private List LoadData(string sql, U parameters) - { - using var connection = _connectionFactory.CreateConnection(); - List sessions = connection.Query(sql, parameters).ToList(); - return sessions; - } - private void SaveData(string sql) - { - using var connection = _connectionFactory.CreateConnection(); - connection.Execute(sql); - } - private void SaveData(string sql, T parameters) - { - using var connection = _connectionFactory.CreateConnection(); - connection.Execute(sql, parameters); - } - - - - public List GetAll() - { - string sql = "Select * from CodingSessions order by StartTime"; - return LoadData(sql); - } - public List GetByDateRange(DateTime begin, DateTime finish) - { - var dateRange = new DateRangeQuery { StartTime = begin, EndTime = finish }; - string sql = @"Select * from CodingSessions where (StartTime >= @StartTime) AND (endTime <= @EndTime) order by StartTime"; - return LoadData(sql, dateRange); - } - public CodingSessionDataRecord GetById(int id) - { - string sql = $"select * from CodingSessions where Id = {id}"; - return LoadData(sql).FirstOrDefault(); - } - public int GetRecordCount() - { - string sql = "select count(*) from CodingSessions"; - return LoadData(sql).First(); - } - - - - public void AddSession(CodingSession session) - { - string sql = "insert into CodingSessions (StartTime, EndTime, Duration) values (@StartTime, @EndTime, @Duration)"; - SaveData(sql, session); - } - - public void UpdateSession(CodingSessionDataRecord session) - { - string sql = "update CodingSessions Set StartTime = @StartTime, EndTime = @EndTime, Duration = @Duration where Id = @Id"; - SaveData(sql, session); - } - public void DeleteById(int id) - { - string sql = $"delete from CodingSessions where Id = {id}"; - SaveData(sql); - } - - - - - public bool ExistsWithinTimeFrame(DateTime time) - { - var parameter = new DateValue { Time = time}; - using var connection = _connectionFactory.CreateConnection(); - - string sql = @"select count(1) from CodingSessions + var parameter = new DateValue { Time = time}; + using var connection = _connectionFactory.CreateConnection(); + + string sql = @"select count(1) from CodingSessions where StartTime <= @Time and EndTime >= @Time"; - int count = connection.ExecuteScalar(sql, parameter); + int count = connection.ExecuteScalar(sql, parameter); - return (count > 0); - } + return (count > 0); + } - public bool ExistsWithinTimeFrameExcludingSessionById(CodingSessionDataRecord session, DateTime newTime) - { - var parameter = new TimeUpdate { Id = (int)session.Id, Time = newTime }; - using var connection = _connectionFactory.CreateConnection(); + public bool ExistsWithinTimeFrameExcludingSessionById(CodingSessionDataRecord session, DateTime newTime) + { + var parameter = new TimeUpdate { Id = (int)session.Id, Time = newTime }; + using var connection = _connectionFactory.CreateConnection(); - string sql = @"select count(1) from CodingSessions + string sql = @"select count(1) from CodingSessions where StartTime <= @Time and EndTime >= @Time and Id != @Id"; - int count = connection.ExecuteScalar(sql, parameter); + int count = connection.ExecuteScalar(sql, parameter); - return (count > 0); - } + return (count > 0); + } - // NOT IMPLEMENTED YET!!!!! + // NOT IMPLEMENTED YET!!!!! - //Method Stubs to work out eventually... If needed? + //Method Stubs to work out eventually... If needed? - public List GetLongestDuration() - { - string sql = $"select * from CodingSessions where "; - return LoadData(sql); - } + public List GetLongestDuration() + { + string sql = $"select * from CodingSessions where "; + return LoadData(sql); + } - // Method here using the following snippet - // using var connection = connectionFactory.CreateConnection(); - // connection.Open(); - // insert Dapper query here + // Method here using the following snippet + // using var connection = connectionFactory.CreateConnection(); + // connection.Open(); + // insert Dapper query here - } } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Data/SqliteConnectionFactory.cs b/codingTracker.jzhartman/CodingTracker.Data/SqliteConnectionFactory.cs index ed8c50ac8..ee2d3de86 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/SqliteConnectionFactory.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/SqliteConnectionFactory.cs @@ -1,28 +1,21 @@ using CodingTracker.Data.Interfaces; using Microsoft.Data.Sqlite; -using System; -using System.Collections.Generic; using System.Data; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -namespace CodingTracker.Data +namespace CodingTracker.Data; +public class SqliteConnectionFactory : ISqliteConnectionFactory { - public class SqliteConnectionFactory : ISqliteConnectionFactory - { - private readonly string _connectionString; + private readonly string _connectionString; - public SqliteConnectionFactory(string connectionString) - { - _connectionString = connectionString; - } + public SqliteConnectionFactory(string connectionString) + { + _connectionString = connectionString; + } - public IDbConnection CreateConnection() - { - var connection = new SqliteConnection(_connectionString); - connection.Open(); - return connection; - } + public IDbConnection CreateConnection() + { + var connection = new SqliteConnection(_connectionString); + connection.Open(); + return connection; } } diff --git a/codingTracker.jzhartman/CodingTracker.Data/TypeHandlers/DateTimeHandler.cs b/codingTracker.jzhartman/CodingTracker.Data/TypeHandlers/DateTimeHandler.cs index 28b456dfd..f2a28e7c5 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/TypeHandlers/DateTimeHandler.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/TypeHandlers/DateTimeHandler.cs @@ -1,43 +1,36 @@ using Dapper; -using System; -using System.Collections.Generic; using System.Data; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -namespace CodingTracker.Data.TypeHandlers +namespace CodingTracker.Data.TypeHandlers; +public class DateTimeHandler : SqlMapper.TypeHandler { - public class DateTimeHandler : SqlMapper.TypeHandler + private readonly string _format; + + public DateTimeHandler(string format) { - private readonly string _format; + _format = format; + } - public DateTimeHandler(string format) + public override DateTime Parse(object value) + { + if (value is string s + && DateTime.TryParseExact + ( + s, + _format, + System.Globalization.CultureInfo.InvariantCulture, + System.Globalization.DateTimeStyles.AssumeLocal, + out var dt)) { - _format = format; + return dt; } - public override DateTime Parse(object value) - { - if (value is string s - && DateTime.TryParseExact - ( - s, - _format, - System.Globalization.CultureInfo.InvariantCulture, - System.Globalization.DateTimeStyles.AssumeLocal, - out var dt)) - { - return dt; - } - - return Convert.ToDateTime(value); - } + return Convert.ToDateTime(value); + } - public override void SetValue(IDbDataParameter parameter, DateTime value) - { - parameter.DbType = DbType.String; - parameter.Value = value.ToString(_format); - } + public override void SetValue(IDbDataParameter parameter, DateTime value) + { + parameter.DbType = DbType.String; + parameter.Value = value.ToString(_format); } } diff --git a/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSession.cs b/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSession.cs index f92b45395..1af2ca837 100644 --- a/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSession.cs +++ b/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSession.cs @@ -1,34 +1,26 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace CodingTracker.Models.Entities +namespace CodingTracker.Models.Entities; +public class CodingSession { - public class CodingSession - { - public DateTime StartTime { get; set; } - public DateTime EndTime { get; set; } - public long Duration { get; private set; } - public string DurationText { get; private set;} + public DateTime StartTime { get; set; } + public DateTime EndTime { get; set; } + public long Duration { get; private set; } + public string DurationText { get; private set;} - public CodingSession(DateTime startTime, DateTime endTime) - { - StartTime = startTime; - EndTime = endTime; - CalculateDuration(); - GenerateDurationText(); - } + public CodingSession(DateTime startTime, DateTime endTime) + { + StartTime = startTime; + EndTime = endTime; + CalculateDuration(); + GenerateDurationText(); + } - private void CalculateDuration() - { - Duration = (long)EndTime.Subtract(StartTime).TotalSeconds; - } + private void CalculateDuration() + { + Duration = (long)EndTime.Subtract(StartTime).TotalSeconds; + } - private void GenerateDurationText() - { - DurationText = TimeSpan.FromSeconds(Duration).ToString(); - } + private void GenerateDurationText() + { + DurationText = TimeSpan.FromSeconds(Duration).ToString(); } } diff --git a/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSessionDataRecord.cs b/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSessionDataRecord.cs index 7427099e2..998eb049e 100644 --- a/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSessionDataRecord.cs +++ b/codingTracker.jzhartman/CodingTracker.Models/Entities/CodingSessionDataRecord.cs @@ -1,10 +1,8 @@ -namespace CodingTracker.Models.Entities +namespace CodingTracker.Models.Entities; +public class CodingSessionDataRecord { - public class CodingSessionDataRecord - { - public long Id { get; set; } - public DateTime StartTime { get; set; } - public DateTime EndTime { get; set; } - public int Duration { get; set; } - } + public long Id { get; set; } + public DateTime StartTime { get; set; } + public DateTime EndTime { get; set; } + public int Duration { get; set; } } diff --git a/codingTracker.jzhartman/CodingTracker.Models/Validation/ValidationResult.cs b/codingTracker.jzhartman/CodingTracker.Models/Validation/ValidationResult.cs index 571912a9f..d6e1862ab 100644 --- a/codingTracker.jzhartman/CodingTracker.Models/Validation/ValidationResult.cs +++ b/codingTracker.jzhartman/CodingTracker.Models/Validation/ValidationResult.cs @@ -1,35 +1,27 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace CodingTracker.Models.Validation +namespace CodingTracker.Models.Validation; +public class ValidationResult { - public class ValidationResult - { - public bool IsValid { get;} - public T? Value { get;} - public string? Parameter { get;} - public string? Message { get;} - - private ValidationResult(bool isValid, T? value,string parameter, string message) - { - IsValid = isValid; - Value = value; - Parameter = parameter; - Message = message; - } + public bool IsValid { get;} + public T? Value { get;} + public string? Parameter { get;} + public string? Message { get;} - public static ValidationResult Success(T value) - { - return new ValidationResult(true, value, default, default); - } + private ValidationResult(bool isValid, T? value,string parameter, string message) + { + IsValid = isValid; + Value = value; + Parameter = parameter; + Message = message; + } - public static ValidationResult Fail(string parameter, string message) - { - return new ValidationResult(false, default, parameter, message); - } + public static ValidationResult Success(T value) + { + return new ValidationResult(true, value, default, default); + } + public static ValidationResult Fail(string parameter, string message) + { + return new ValidationResult(false, default, parameter, message); } + } diff --git a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs index a3bf3365c..7b1cb96c3 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs @@ -1,98 +1,89 @@ using CodingTracker.Data.Interfaces; -using CodingTracker.Data.Repositories; using CodingTracker.Models.Entities; using CodingTracker.Models.Validation; using CodingTracker.Services.Interfaces; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using static System.Collections.Specialized.BitVector32; - -namespace CodingTracker.Services + +namespace CodingTracker.Services; +public class CodingSessionDataService : ICodingSessionDataService { - public class CodingSessionDataService : ICodingSessionDataService + private readonly ICodingSessionRepository _repository; + + public CodingSessionDataService(ICodingSessionRepository repository) + { + _repository = repository; + } + + public List GetAllCodingSessions() { - private readonly ICodingSessionRepository _repository; - - public CodingSessionDataService(ICodingSessionRepository repository) - { - _repository = repository; - } - - public List GetAllCodingSessions() - { - return _repository.GetAll(); - } - - public List GetByDateRange(DateTime startTime, DateTime endTime) - { - return _repository.GetByDateRange(startTime, endTime); - } - - public CodingSessionDataRecord GetById(int id) - { - // Validate that ID exists - - return _repository.GetById(id); - } - - public void DeleteById(int id) - { - _repository.DeleteById(id); - } - - public void UpdateSession(CodingSessionDataRecord session) - { - _repository.UpdateSession(session); - } - - public void AddSession(CodingSession session) - { - _repository.AddSession(session); - } - - public ValidationResult ValidateStartTime(DateTime input) - { - if (_repository.ExistsWithinTimeFrame(input)) - return ValidationResult.Fail("Start Time", "A record already exists for this time"); - else - return ValidationResult.Success(input); - } - - public ValidationResult ValidateEndTime(DateTime input) - { - if (_repository.ExistsWithinTimeFrame(input)) - return ValidationResult.Fail("End Time", "A record already exists for this time"); - else - return ValidationResult.Success(input); - } - public ValidationResult ValidateUpdatedStartTime(CodingSessionDataRecord session, DateTime newStartTime) - { - if (newStartTime == DateTime.MinValue) - return ValidationResult.Success(session.StartTime); - - else if (_repository.ExistsWithinTimeFrameExcludingSessionById(session, newStartTime)) - return ValidationResult.Fail("Start Time", "A record already exists for this time"); - - else - return ValidationResult.Success(newStartTime); - } - - public ValidationResult ValidateUpdatedEndTime(CodingSessionDataRecord session, DateTime newStartTime, DateTime newEndTime) - { - if (newEndTime == DateTime.MinValue && session.EndTime > newStartTime) - return ValidationResult.Success(session.EndTime); - - else if (newEndTime <= newStartTime) - return ValidationResult.Fail("End Time", "End time cannot be earlier than start time."); - - else if (_repository.ExistsWithinTimeFrameExcludingSessionById(session, newEndTime)) - return ValidationResult.Fail("End Time", "A record already exists for this time"); - - else - return ValidationResult.Success(newEndTime); - } + return _repository.GetAll(); + } + + public List GetSessionListByDateRange(DateTime startTime, DateTime endTime) + { + return _repository.GetByDateRange(startTime, endTime); + } + + public CodingSessionDataRecord GetById(int id) + { + // Validate that ID exists + + return _repository.GetById(id); + } + + public void DeleteById(int id) + { + _repository.DeleteById(id); + } + + public void UpdateSession(CodingSessionDataRecord session) + { + _repository.UpdateSession(session); + } + + public void AddSession(CodingSession session) + { + _repository.AddSession(session); + } + + public ValidationResult ValidateStartTime(DateTime input) + { + if (_repository.ExistsWithinTimeFrame(input)) + return ValidationResult.Fail("Start Time", "A record already exists for this time"); + else + return ValidationResult.Success(input); + } + + public ValidationResult ValidateEndTime(DateTime input) + { + if (_repository.ExistsWithinTimeFrame(input)) + return ValidationResult.Fail("End Time", "A record already exists for this time"); + else + return ValidationResult.Success(input); + } + public ValidationResult ValidateUpdatedStartTime(CodingSessionDataRecord session, DateTime newStartTime) + { + if (newStartTime == DateTime.MinValue) + return ValidationResult.Success(session.StartTime); + + else if (_repository.ExistsWithinTimeFrameExcludingSessionById(session, newStartTime)) + return ValidationResult.Fail("Start Time", "A record already exists for this time"); + + else + return ValidationResult.Success(newStartTime); + } + + public ValidationResult ValidateUpdatedEndTime(CodingSessionDataRecord session, DateTime newStartTime, DateTime newEndTime) + { + if (newEndTime == DateTime.MinValue && session.EndTime > newStartTime) + return ValidationResult.Success(session.EndTime); + + else if (newEndTime <= newStartTime) + return ValidationResult.Fail("End Time", "End time cannot be earlier than start time."); + + else if (_repository.ExistsWithinTimeFrameExcludingSessionById(session, newEndTime)) + return ValidationResult.Fail("End Time", "A record already exists for this time"); + + else + return ValidationResult.Success(newEndTime); } } diff --git a/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionDataService.cs b/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionDataService.cs index bd7b60e8a..69272e765 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionDataService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionDataService.cs @@ -1,19 +1,17 @@ using CodingTracker.Models.Entities; using CodingTracker.Models.Validation; -namespace CodingTracker.Services.Interfaces +namespace CodingTracker.Services.Interfaces; +public interface ICodingSessionDataService { - public interface ICodingSessionDataService - { - void AddSession(CodingSession session); - void DeleteById(int id); - List GetAllCodingSessions(); - List GetByDateRange(DateTime startTime, DateTime endTime); - CodingSessionDataRecord GetById(int id); - void UpdateSession(CodingSessionDataRecord session); - ValidationResult ValidateEndTime(DateTime input); - ValidationResult ValidateStartTime(DateTime input); - ValidationResult ValidateUpdatedEndTime(CodingSessionDataRecord session, DateTime newStartTime, DateTime newEndTime); - ValidationResult ValidateUpdatedStartTime(CodingSessionDataRecord session, DateTime updatedStartTime); - } + void AddSession(CodingSession session); + void DeleteById(int id); + List GetAllCodingSessions(); + List GetSessionListByDateRange(DateTime startTime, DateTime endTime); + CodingSessionDataRecord GetById(int id); + void UpdateSession(CodingSessionDataRecord session); + ValidationResult ValidateEndTime(DateTime input); + ValidationResult ValidateStartTime(DateTime input); + ValidationResult ValidateUpdatedEndTime(CodingSessionDataRecord session, DateTime newStartTime, DateTime newEndTime); + ValidationResult ValidateUpdatedStartTime(CodingSessionDataRecord session, DateTime updatedStartTime); } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Views/CodingSessionView.cs b/codingTracker.jzhartman/CodingTracker.Views/CodingSessionView.cs index 2d9df53fc..1b30cb3dd 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/CodingSessionView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/CodingSessionView.cs @@ -1,26 +1,24 @@ using CodingTracker.Models.Entities; -namespace CodingTracker.Views +namespace CodingTracker.Views; +public static class CodingSessionView { - public static class CodingSessionView + public static void RenderCodingSessions(List sessions) { - public static void RenderCodingSessions(List sessions) - { - int count = 1; - Console.WriteLine("A list of coding sessions: "); - - foreach (var session in sessions) - { - Console.WriteLine($"{count}:\t{session.StartTime} to {session.EndTime} for a duration of {session.Duration}"); - count++; - } - } + int count = 1; + Console.WriteLine("A list of coding sessions: "); - public static void RenderCodingSession(CodingSessionDataRecord session) + foreach (var session in sessions) { - Console.WriteLine($"{session.Id}:\t{session.StartTime} to {session.EndTime} for a duration of {session.Duration}"); + Console.WriteLine($"{count}:\t{session.StartTime} to {session.EndTime} for a duration of {session.Duration}"); + count++; } + } - + public static void RenderCodingSession(CodingSessionDataRecord session) + { + Console.WriteLine($"{session.Id}:\t{session.StartTime} to {session.EndTime} for a duration of {session.Duration}"); } + + } diff --git a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IMenuView.cs b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IMenuView.cs index 0b6615c43..db8792001 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IMenuView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IMenuView.cs @@ -1,10 +1,8 @@ -namespace CodingTracker.Views.Interfaces +namespace CodingTracker.Views.Interfaces; +public interface IMenuView { - public interface IMenuView - { - string RenderEntryViewOptionsAndGetSelection(); - string RenderMainMenuAndGetSelection(); - string RenderTrackingMenuAndGetSelection(); - string RenderUpdateOrDeleteOptionsAndGetSelection(); - } + string RenderEntryViewOptionsAndGetSelection(); + string RenderMainMenuAndGetSelection(); + string RenderTrackingMenuAndGetSelection(); + string RenderUpdateOrDeleteOptionsAndGetSelection(); } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInput.cs b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInput.cs index 2f3c86134..111f00648 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInput.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInput.cs @@ -1,13 +1,12 @@ using CodingTracker.Models.Entities; -namespace CodingTracker.Views.Interfaces +namespace CodingTracker.Views.Interfaces; +public interface IUserInput { - public interface IUserInput - { - int GetRecordIdFromUser(string action, int max); - bool GetAddSessionConfirmationFromUser(CodingSession session); - DateTime GetTimeFromUser(string parameterName, string nullBehavior = "", bool allowNull = false); - bool GetUpdateSessionConfirmationFromUser(CodingSessionDataRecord session, CodingSession updatedSession); - //DateTime GetUpdatedStartTimeFromUser(DateTime originalTime); - } + int GetRecordIdFromUser(string action, int max); + bool GetAddSessionConfirmationFromUser(CodingSession session); + DateTime GetTimeFromUser(string parameterName, string nullBehavior = "", bool allowNull = false); + bool GetUpdateSessionConfirmationFromUser(CodingSessionDataRecord session, CodingSession updatedSession); + bool GetDeleteSessionConfirmationFromUser(CodingSessionDataRecord session); + //DateTime GetUpdatedStartTimeFromUser(DateTime originalTime); } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Views/Menus/MenuView.cs b/codingTracker.jzhartman/CodingTracker.Views/Menus/MenuView.cs index 7994c6b1b..b44d2f806 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/Menus/MenuView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/Menus/MenuView.cs @@ -1,79 +1,73 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using CodingTracker.Views.Interfaces; +using CodingTracker.Views.Interfaces; using Spectre.Console; -namespace CodingTracker.Views.Menus +namespace CodingTracker.Views.Menus; +public class MenuView : IMenuView { - public class MenuView : IMenuView + public string RenderMainMenuAndGetSelection() { - public string RenderMainMenuAndGetSelection() - { - var selection = AnsiConsole.Prompt( - new SelectionPrompt() - .Title("Select from the options below:") - .AddChoices(new[] - { - "Track Session", - "View/Manage Entries", - "View Reports", - "Manage Goal", - "Exit" - }) - ); + AnsiConsole.WriteLine(); + var selection = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("Select from the options below:") + .AddChoices(new[] + { + "Track Session", + "View/Manage Entries", + "View Reports", + "Manage Goal", + "Exit" + }) + ); - return selection; - } + return selection; + } - public string RenderTrackingMenuAndGetSelection() - { - var selection = AnsiConsole.Prompt( - new SelectionPrompt() - .Title("How would you like to track your coding session?") - .AddChoices(new[] - { - "Enter Start and End Times", - "Begin Timer", - "Return to Main Menu" - }) - ); + public string RenderTrackingMenuAndGetSelection() + { + var selection = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("How would you like to track your coding session?") + .AddChoices(new[] + { + "Enter Start and End Times", + "Begin Timer", + "Return to Main Menu" + }) + ); - return selection; - } + return selection; + } - public string RenderEntryViewOptionsAndGetSelection() - { - var selection = AnsiConsole.Prompt( - new SelectionPrompt() - .Title("How would you like your entries displayed?") - .AddChoices(new[] - { - "All", - "One Year", - "Year to Date", - "Enter Date Range", - "Return to Previous Menu" - }) - ); - return selection; - } - public string RenderUpdateOrDeleteOptionsAndGetSelection() - { - var selection = AnsiConsole.Prompt( + public string RenderEntryViewOptionsAndGetSelection() + { + var selection = AnsiConsole.Prompt( new SelectionPrompt() - .Title("Please select the next operation:") + .Title("How would you like your entries displayed?") .AddChoices(new[] { - "Change Record", - "Delete Record", - "Return to Previous Menu" + "All", + "One Year", + "Year to Date", + "Enter Date Range", + "Return to Previous Menu" }) ); - return selection; - } - + return selection; + } + public string RenderUpdateOrDeleteOptionsAndGetSelection() + { + var selection = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("Please select the next operation:") + .AddChoices(new[] + { + "Change Record", + "Delete Record", + "Return to Previous Menu" + }) + ); + return selection; } + } diff --git a/codingTracker.jzhartman/CodingTracker.Views/Messages.cs b/codingTracker.jzhartman/CodingTracker.Views/Messages.cs index 2923e4366..73f8c1de1 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/Messages.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/Messages.cs @@ -1,54 +1,59 @@ using Spectre.Console; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -namespace CodingTracker.Views +namespace CodingTracker.Views; +public static class Messages { - public static class Messages + public static void RenderWelcome() { - public static void RenderWelcome() - { - AnsiConsole.Markup("[bold blue]CODING TRACKER[/]"); - AnsiConsole.Markup("[bold blue]Version 1.0[/]"); - AnsiConsole.Write(new Rule()); + AnsiConsole.Clear(); + AnsiConsole.Write(new Rule()); + AnsiConsole.MarkupLine("[bold blue]CODING TRACKER[/]"); + AnsiConsole.MarkupLine("[bold blue]Version 1.0[/]"); + AnsiConsole.Write(new Rule()); + } - } - public static void ErrorMessage(string parameter, string message) - { - AddNewLines(1); - AnsiConsole.MarkupInterpolated($"[bold red]ERROR:[/] The value for {parameter} encountered the error: [yellow]{message}[/]"); - AddNewLines(2); - } + public static void RenderLocation(string location) + { + AnsiConsole.MarkupLineInterpolated($"[bold]{location.ToUpper()}[/]"); + } + public static void Error(string parameter, string message) + { + AddNewLines(1); + AnsiConsole.MarkupInterpolated($"[bold red]ERROR:[/] The value for {parameter} encountered the error: [yellow]{message}[/]"); + AddNewLines(2); + } - public static void ConfirmationMessage(string valueText) + public static void Confirmation(string valueText) + { + AddNewLines(1); + AnsiConsole.MarkupInterpolated($"[bold green]ACCEPTED[/]: Value set to {valueText}"); + AddNewLines(2); + } + public static void ActionComplete(bool isSuccess, string state, string message) + { + AddNewLines(1); + if (isSuccess) { - AddNewLines(1); - AnsiConsole.MarkupInterpolated($"[bold green]ACCEPTED[/]: Value set to {valueText}"); - AddNewLines(2); + AnsiConsole.MarkupInterpolated($"[bold green]{state.ToUpper()}![/] {message}"); } - public static void ActionCompleteMessage(bool isSuccess, string state, string message) + else { - AddNewLines(1); - if (isSuccess) - { - AnsiConsole.MarkupInterpolated($"[bold green]{state.ToUpper()}![/] {message}"); - } - else - { - AnsiConsole.MarkupInterpolated($"[bold red]{state.ToUpper()}![/] {message}"); - } - AddNewLines(2); + AnsiConsole.MarkupInterpolated($"[bold red]{state.ToUpper()}![/] {message}"); } + AddNewLines(2); + } - private static void AddNewLines(int lines) + public static void ActionCancelled(string action) + { + AddNewLines(1); + AnsiConsole.MarkupInterpolated($"Cancelled {action} of coding session!"); + } + + private static void AddNewLines(int lines) + { + for (int i = 0; i < lines; i++) { - for (int i = 0; i < lines; i++) - { - AnsiConsole.WriteLine(); - } + AnsiConsole.WriteLine(); } } } diff --git a/codingTracker.jzhartman/CodingTracker.Views/UserInput.cs b/codingTracker.jzhartman/CodingTracker.Views/UserInput.cs index 9d37b5faf..72e46c673 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/UserInput.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/UserInput.cs @@ -1,154 +1,154 @@ using CodingTracker.Models.Entities; using CodingTracker.Views.Interfaces; using Spectre.Console; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection.Metadata; -using System.Text; -using System.Threading.Tasks; -using static System.Collections.Specialized.BitVector32; -using static System.Runtime.InteropServices.JavaScript.JSType; - -namespace CodingTracker.Views + +namespace CodingTracker.Views; +public class UserInput : IUserInput { - public class UserInput : IUserInput + private readonly string _dateFormat; + public UserInput(string dateFormat) { - private readonly string _dateFormat; - public UserInput(string dateFormat) - { - _dateFormat = dateFormat; - } - public DateTime GetTimeFromUser(string parameterName, string nullBehavior = "", bool allowNull = false) - { - var date = new DateTime(); - var promptText = GenerateEnterDatePromptText(parameterName, nullBehavior, allowNull); + _dateFormat = dateFormat; + } + public DateTime GetTimeFromUser(string parameterName, string nullBehavior = "", bool allowNull = false) + { + var date = new DateTime(); + var promptText = GenerateEnterDatePromptText(parameterName, nullBehavior, allowNull); - if (allowNull) date = AnsiConsole.Prompt( - new TextPrompt(promptText) - .AllowEmpty()); + if (allowNull) date = AnsiConsole.Prompt( + new TextPrompt(promptText) + .AllowEmpty()); - else date = AnsiConsole.Prompt( - new TextPrompt(promptText)); - + else date = AnsiConsole.Prompt( + new TextPrompt(promptText)); + - //Add custom validation for time format + //Add custom validation for time format - return date; - } + return date; + } - public int GetRecordIdFromUser(string action, int max) - { - var id = AnsiConsole.Prompt( - new TextPrompt($"Please enter the [yellow]ID[/] of the record you wish to {action.ToLower()}:") - .Validate(input => - { - if (input < 1) return Spectre.Console.ValidationResult.Error("Too low"); - else if (input > max) return Spectre.Console.ValidationResult.Error("Too high"); - else return Spectre.Console.ValidationResult.Success(); - })); + public int GetRecordIdFromUser(string action, int max) + { + var id = AnsiConsole.Prompt( + new TextPrompt($"Please enter the [yellow]ID[/] of the record you wish to {action.ToLower()}:") + .Validate(input => + { + if (input < 1) return Spectre.Console.ValidationResult.Error("Too low"); + else if (input > max) return Spectre.Console.ValidationResult.Error("Too high"); + else return Spectre.Console.ValidationResult.Success(); + })); - return id; - } + return id; + } + public bool GetAddSessionConfirmationFromUser(CodingSession session) + { + var confirmation = AnsiConsole.Prompt( + new TextPrompt($"Add coding session starting at [yellow]{session.StartTime.ToString("yyyy-MM-dd HH:mm:ss")}[/] and ending at [yellow]{session.EndTime.ToString("yyyy-MM-dd HH:mm:ss")}[/] with duration [yellow]{session.DurationText}[/]?") + .AddChoice(true) + .AddChoice(false) + .WithConverter(choice => choice ? "y" : "n")); - //public DateTime GetUpdatedStartTimeFromUser(DateTime originalTime) - //{ - // AnsiConsole.MarkupInterpolated($"Changing start time from [yellow]{originalTime}[/]."); - // return GetEndTimeFromUser(); - //} + return confirmation; + } - public bool GetAddSessionConfirmationFromUser(CodingSession session) - { - var confirmation = AnsiConsole.Prompt( - new TextPrompt($"Add coding session starting at [yellow]{session.StartTime.ToString("yyyy-MM-dd HH:mm:ss")}[/] and ending at [yellow]{session.EndTime.ToString("yyyy-MM-dd HH:mm:ss")}[/] with duration [yellow]{session.DurationText}[/]?") - .AddChoice(true) - .AddChoice(false) - .WithConverter(choice => choice ? "y" : "n")); + public bool GetUpdateSessionConfirmationFromUser(CodingSessionDataRecord session, CodingSession updatedSession) + { + string promptText = GenerateUpdateSessionConfirmationPrompt(session, updatedSession); - return confirmation; - } + var confirmation = AnsiConsole.Prompt( + new TextPrompt(promptText) + .AddChoice(true) + .AddChoice(false) + .WithConverter(choice => choice ? "y" : "n")); - public bool GetUpdateSessionConfirmationFromUser(CodingSessionDataRecord session, CodingSession updatedSession) - { - string promptText = GenerateUpdateSessoinConfirmationPrompt(session, updatedSession); + return confirmation; + } + public bool GetDeleteSessionConfirmationFromUser(CodingSessionDataRecord session) + { + string promptText = $"Confirm deletion of coding session with start time [yellow]{session.StartTime.ToString("yyyy-MM-dd HH:mm:ss")}[/]" + + $" and end time [yellow]{session.EndTime.ToString("yyyy-MM-dd HH:mm:ss")}[/]" + + $" with duration [yellow]{TimeSpan.FromSeconds(session.Duration).ToString()}[/]"; - var confirmation = AnsiConsole.Prompt( - new TextPrompt(promptText) - .AddChoice(true) - .AddChoice(false) - .WithConverter(choice => choice ? "y" : "n")); + var confirmation = AnsiConsole.Prompt( + new TextPrompt(promptText) + .AddChoice(true) + .AddChoice(false) + .WithConverter(choice => choice ? "y" : "n")); - return confirmation; - } + return confirmation; + } - private string GenerateUpdateSessoinConfirmationPrompt(CodingSessionDataRecord session, CodingSession updatedSession) - { - string prompt = $"Update coding session "; + private string GenerateUpdateSessionConfirmationPrompt(CodingSessionDataRecord session, CodingSession updatedSession) + { + string prompt = $"Update coding session "; - if (session.StartTime != updatedSession.StartTime) - prompt += $"start time from [yellow]{session.StartTime.ToString("yyyy-MM-dd HH:mm:ss")}[/] to [green]{updatedSession.StartTime.ToString("yyyy-MM-dd HH:mm:ss")}[/]"; + if (session.StartTime == updatedSession.StartTime && session.EndTime == updatedSession.EndTime) + return $"No changes were made to the coding session with start time [yellow]{session.StartTime.ToString("yyyy-MM-dd HH:mm:ss")}[/] and end time [yellow]{session.EndTime.ToString("yyyy-MM-dd HH:mm:ss")}[/]"; - if (session.StartTime != updatedSession.StartTime && session.EndTime != updatedSession.EndTime) - prompt += " and "; + if (session.StartTime != updatedSession.StartTime) + prompt += $"start time from [yellow]{session.StartTime.ToString("yyyy-MM-dd HH:mm:ss")}[/] to [green]{updatedSession.StartTime.ToString("yyyy-MM-dd HH:mm:ss")}[/]"; - if (session.EndTime != updatedSession.EndTime) - prompt += $"end time from [yellow]{session.EndTime.ToString("yyyy-MM-dd HH:mm:ss")}[/] to [green]{updatedSession.EndTime.ToString("yyyy-MM-dd HH:mm:ss")}[/]"; + if (session.StartTime != updatedSession.StartTime && session.EndTime != updatedSession.EndTime) + prompt += " and "; - prompt += $" for a new duration of [green]{updatedSession.DurationText}[/]."; + if (session.EndTime != updatedSession.EndTime) + prompt += $"end time from [yellow]{session.EndTime.ToString("yyyy-MM-dd HH:mm:ss")}[/] to [green]{updatedSession.EndTime.ToString("yyyy-MM-dd HH:mm:ss")}[/]"; - return prompt; - } - private string GenerateEnterDatePromptText(string parameterName, string nullBehavior, bool allowNull) - { - string article = GetArticle(parameterName); - string promptText = $"Please enter {article} {parameterName} using the format [yellow]'yyyy-MM-dd HH:mm:ss'[/]"; + prompt += $" for a new duration of [green]{updatedSession.DurationText}[/]."; - if (allowNull) - { - promptText += $".\r\nPress enter with blank line to use {nullBehavior}:"; - } - else - { - promptText += ":"; - } + return prompt; + } + private string GenerateEnterDatePromptText(string parameterName, string nullBehavior, bool allowNull) + { + string article = GetArticle(parameterName); + string promptText = $"Please enter {article} {parameterName} using the format [yellow]'yyyy-MM-dd HH:mm:ss'[/]"; - return promptText; + if (allowNull) + { + promptText += $".\r\nPress enter with blank line to use {nullBehavior}:"; } - - private string GetArticle(string noun) + else { - string article = "a"; - char firstLetter = noun.ToLower()[0]; - if (firstLetter == 'a' || firstLetter == 'e' || firstLetter == 'i' || firstLetter == 'o' || firstLetter == 'u') article += "n"; - - return article; + promptText += ":"; } + + return promptText; + } + + private string GetArticle(string noun) + { + string article = "a"; + char firstLetter = noun.ToLower()[0]; + if (firstLetter == 'a' || firstLetter == 'e' || firstLetter == 'i' || firstLetter == 'o' || firstLetter == 'u') article += "n"; + + return article; } } /* - // Define the target date format. - const string dateFormat = "yyyy-MM-dd"; - - // Prompt for a date with validation. - var dateString = AnsiConsole.Prompt( - new TextPrompt("Enter a date in [green]yyyy-MM-dd[/] format:") - .PromptStyle("yellow") - .ValidationErrorMessage($"[red]Invalid date format. Please use {dateFormat}.[/]") - .Validate(input => + // Define the target date format. + const string dateFormat = "yyyy-MM-dd"; + + // Prompt for a date with validation. + var dateString = AnsiConsole.Prompt( + new TextPrompt("Enter a date in [green]yyyy-MM-dd[/] format:") + .PromptStyle("yellow") + .ValidationErrorMessage($"[red]Invalid date format. Please use {dateFormat}.[/]") + .Validate(input => + { + // Use DateTime.TryParseExact for robust format checking. + if (DateTime.TryParseExact(input, dateFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out _)) { - // Use DateTime.TryParseExact for robust format checking. - if (DateTime.TryParseExact(input, dateFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out _)) - { - return ValidationResult.Success(); - } - - return ValidationResult.Error(); - })); - - // After successful validation, parse the string into a DateTime object. - DateTime parsedDate = DateTime.ParseExact(dateString, dateFormat, CultureInfo.InvariantCulture); - - AnsiConsole.MarkupLine($"You entered: [green]{parsedDate:yyyy-MM-dd}[/]"); + return ValidationResult.Success(); + } + + return ValidationResult.Error(); + })); + + // After successful validation, parse the string into a DateTime object. + DateTime parsedDate = DateTime.ParseExact(dateString, dateFormat, CultureInfo.InvariantCulture); + + AnsiConsole.MarkupLine($"You entered: [green]{parsedDate:yyyy-MM-dd}[/]"); */ \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker/Configuration/DateTimeOptions.cs b/codingTracker.jzhartman/CodingTracker/Configuration/DateTimeOptions.cs index 67e32096b..8353e0553 100644 --- a/codingTracker.jzhartman/CodingTracker/Configuration/DateTimeOptions.cs +++ b/codingTracker.jzhartman/CodingTracker/Configuration/DateTimeOptions.cs @@ -1,13 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace CodingTracker.ConsoleApp.Configuration +namespace CodingTracker.ConsoleApp.Configuration; +public class DateTimeOptions { - public class DateTimeOptions - { - public string Format { get; set; } - } + public string Format { get; set; } } diff --git a/codingTracker.jzhartman/CodingTracker/Startup.cs b/codingTracker.jzhartman/CodingTracker/Startup.cs index dc7b6aa87..b9c050188 100644 --- a/codingTracker.jzhartman/CodingTracker/Startup.cs +++ b/codingTracker.jzhartman/CodingTracker/Startup.cs @@ -13,40 +13,38 @@ using Microsoft.Extensions.DependencyInjection; using System.Configuration; -namespace CodingTracker.ConsoleApp +namespace CodingTracker.ConsoleApp; +internal static class Startup { - internal static class Startup + public static IServiceProvider ConfigureServices() { - public static IServiceProvider ConfigureServices() - { - var connectionString = ConfigurationManager.AppSettings.Get("connectionString"); - - var dateTimeFormat = ConfigurationManager.AppSettings.Get("timeAndDateFormat"); - SqlMapper.RemoveTypeMap(typeof(DateTime)); - SqlMapper.RemoveTypeMap(typeof(DateTime?)); - SqlMapper.AddTypeHandler(new DateTimeHandler(dateTimeFormat)); + var connectionString = ConfigurationManager.AppSettings.Get("connectionString"); + + var dateTimeFormat = ConfigurationManager.AppSettings.Get("timeAndDateFormat"); + SqlMapper.RemoveTypeMap(typeof(DateTime)); + SqlMapper.RemoveTypeMap(typeof(DateTime?)); + SqlMapper.AddTypeHandler(new DateTimeHandler(dateTimeFormat)); - var services = new ServiceCollection(); + var services = new ServiceCollection(); - //Register All Controllers - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + //Register All Controllers + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); - //Register All Services - services.AddSingleton(); + //Register All Services + services.AddSingleton(); - //Resgister All Views - services.AddSingleton(); - services.AddSingleton(new UserInput(dateTimeFormat)); + //Resgister All Views + services.AddSingleton(); + services.AddSingleton(new UserInput(dateTimeFormat)); - services.AddSingleton(); - services.AddSingleton(provider => new SqliteConnectionFactory(connectionString)); - services.AddSingleton(); - - return services.BuildServiceProvider(); - } + services.AddSingleton(); + services.AddSingleton(provider => new SqliteConnectionFactory(connectionString)); + services.AddSingleton(); + return services.BuildServiceProvider(); } + } From cf2a132c3b937f61bfcb1ed6395878316b98f687 Mon Sep 17 00:00:00 2001 From: jzhartman Date: Sun, 14 Sep 2025 00:07:53 -0400 Subject: [PATCH 27/57] Added basic add via stopwatch. Will need some serious UI changes --- .../TrackSessionController.cs | 60 ++++++++++++++----- .../Interfaces/IUserInput.cs | 2 + .../CodingTracker.Views/UserInput.cs | 30 ++++++++++ 3 files changed, 76 insertions(+), 16 deletions(-) diff --git a/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs b/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs index cbaa02512..44f6f471c 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs @@ -29,17 +29,12 @@ public void Run() switch (selection) { case "Enter Start and End Times": - var startTime = GetStartTime(); - var endTime = GetEndTime(startTime); - var session = new CodingSession(startTime, endTime); - - if (ConfirmSession(session)) - { - AddSession(session); - } + var session = GetNewSessionFromUserInput(); + ConfirmAndAddSession(session); break; case "Begin Timer": - GetTimesWithStopwatch(); + var stopwatchSession = GetNewSessionFromStopwatch(); + ConfirmAndAddSession(stopwatchSession); break; case "Return to Main Menu": returnToMainMenu = true; @@ -50,12 +45,14 @@ public void Run() } } - private bool ConfirmSession(CodingSession session) + private CodingSession GetNewSessionFromUserInput() { - return _inputView.GetAddSessionConfirmationFromUser(session); - } + var startTime = GetStartTimeFromUser(); + var endTime = GetEndTimeFromUser(startTime); - private DateTime GetStartTime() + return new CodingSession(startTime, endTime); + } + private DateTime GetStartTimeFromUser() { var output = new DateTime(); bool startTimeValid = false; @@ -79,7 +76,7 @@ private DateTime GetStartTime() } return output; } - private DateTime GetEndTime(DateTime startTime) + private DateTime GetEndTimeFromUser(DateTime startTime) { var output = new DateTime(); bool endTimeValid = false; @@ -112,14 +109,45 @@ private DateTime GetEndTime(DateTime startTime) } return output; } + private void ConfirmAndAddSession(CodingSession session) + { + var sessionConfirmed = _inputView.GetAddSessionConfirmationFromUser(session); + + if (sessionConfirmed) + { + AddSession(session); + } + else + { + Messages.ActionCancelled("addition"); + } + } private void AddSession(CodingSession session) { _service.AddSession(session); Messages.ActionComplete(true, "Success", "Coding session successfully added!"); } - private void GetTimesWithStopwatch() + private CodingSession GetNewSessionFromStopwatch() + { + var startTime = GetStartTimeWithStopwatch(); + Messages.Confirmation(startTime.ToString("yyyy-MM-dd HH:mm:ss")); + + var endTime = GetStopTimeWithStopwatch(); + Messages.Confirmation(endTime.ToString("yyyy-MM-dd HH:mm:ss")); + + return new CodingSession(startTime, endTime); + } + + private DateTime GetStartTimeWithStopwatch() + { + var selection = _inputView.StartStopwatch(); + return DateTime.Now; + } + + private DateTime GetStopTimeWithStopwatch() { - // Code here + var selection = _inputView.StopStopwatch(); + return DateTime.Now; } } diff --git a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInput.cs b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInput.cs index 111f00648..29f5a55c8 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInput.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInput.cs @@ -8,5 +8,7 @@ public interface IUserInput DateTime GetTimeFromUser(string parameterName, string nullBehavior = "", bool allowNull = false); bool GetUpdateSessionConfirmationFromUser(CodingSessionDataRecord session, CodingSession updatedSession); bool GetDeleteSessionConfirmationFromUser(CodingSessionDataRecord session); + string StartStopwatch(); + string StopStopwatch(); //DateTime GetUpdatedStartTimeFromUser(DateTime originalTime); } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Views/UserInput.cs b/codingTracker.jzhartman/CodingTracker.Views/UserInput.cs index 72e46c673..2fbd6fe18 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/UserInput.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/UserInput.cs @@ -28,6 +28,36 @@ public DateTime GetTimeFromUser(string parameterName, string nullBehavior = "", return date; } + public string StartStopwatch() + { + AnsiConsole.WriteLine(); + var selection = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("Press the ENTER key when you are ready to begin your coding session.") + .AddChoices(new[] + { + "Start Stopwatch", + }) + ); + + return selection; + } + + public string StopStopwatch() + { + AnsiConsole.WriteLine(); + var selection = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("Press the ENTER key when you are ready to end your coding session.") + .AddChoices(new[] + { + "Stop Stopwatch", + }) + ); + + return selection; + } + public int GetRecordIdFromUser(string action, int max) { var id = AnsiConsole.Prompt( From f40a3bc5b918221ce4dc7753a8ecbedaed97caf1 Mon Sep 17 00:00:00 2001 From: jzhartman Date: Sun, 14 Sep 2025 00:22:30 -0400 Subject: [PATCH 28/57] Very basic Report controller added --- .../Interfaces/IReportsController.cs | 6 +++++ .../ReportsController.cs | 27 +++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 codingTracker.jzhartman/CodingTracker.Controller/Interfaces/IReportsController.cs create mode 100644 codingTracker.jzhartman/CodingTracker.Controller/ReportsController.cs diff --git a/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/IReportsController.cs b/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/IReportsController.cs new file mode 100644 index 000000000..6e61f5fde --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/IReportsController.cs @@ -0,0 +1,6 @@ +namespace CodingTracker.Controller.Interfaces; + +public interface IReportsController +{ + void Run(); +} \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Controller/ReportsController.cs b/codingTracker.jzhartman/CodingTracker.Controller/ReportsController.cs new file mode 100644 index 000000000..6d5f02a4b --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Controller/ReportsController.cs @@ -0,0 +1,27 @@ +using CodingTracker.Controller.Interfaces; +using CodingTracker.Services.Interfaces; +using CodingTracker.Views.Interfaces; + +namespace CodingTracker.Controller; +public class ReportsController : IReportsController +{ + private readonly ICodingSessionDataService _codingSessionDataService; + private readonly IMenuView _menuView; + private readonly IUserInput _inputView; + public ReportsController(ICodingSessionDataService service, IMenuView menuView, IUserInput inputView) + { + _codingSessionDataService = service; + _menuView = menuView; + _inputView = inputView; + } + + public void Run() + { + bool returnToMainMenu = false; + + while (!returnToMainMenu) + { + + } + } +} From cf70ac642f5d37ad6dabcb115d0f6432d99820e8 Mon Sep 17 00:00:00 2001 From: jzhartman Date: Sun, 14 Sep 2025 23:12:17 -0400 Subject: [PATCH 29/57] Finished basics for the Report Controller --- .../EntryListController.cs | 6 +- .../MainMenuController.cs | 6 +- .../ReportsController.cs | 93 ++++++++++++++++++- .../TrackSessionController.cs | 24 ++--- .../Entities/ReportModel.cs | 67 +++++++++++++ .../CodingSessionDataService.cs | 23 +++-- .../Interfaces/ICodingSessionDataService.cs | 6 +- .../CodingTracker.Views/CodingSessionView.cs | 15 +++ .../CodingTracker.Views/Menus/MenuView.cs | 2 - .../CodingTracker.Views/Messages.cs | 1 + .../CodingTracker.Views/UserInput.cs | 5 +- .../CodingTracker/Startup.cs | 1 + 12 files changed, 209 insertions(+), 40 deletions(-) create mode 100644 codingTracker.jzhartman/CodingTracker.Models/Entities/ReportModel.cs diff --git a/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs b/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs index fe0b54f5d..dbf9fc60d 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs @@ -26,6 +26,8 @@ public void Run() while (!returnToPreviousMenu) { + Messages.RenderWelcome(); + var dateRangeSelection = GetDateRangeSelectionFromUser(); if (dateRangeSelection == "Return to Previous Menu") { returnToPreviousMenu = true; continue; } @@ -54,6 +56,8 @@ public void Run() break; } sessions = _service.GetSessionListByDateRange(startTime, endTime); + Messages.RenderWelcome(); + } } } @@ -70,7 +74,7 @@ private void ManageSessionDelete(List sessions) } private void DeleteSession(CodingSessionDataRecord session) { - _service.DeleteById((int)session.Id); + _service.DeleteSessionById((int)session.Id); } private bool ConfirmDelete(CodingSessionDataRecord session) { diff --git a/codingTracker.jzhartman/CodingTracker.Controller/MainMenuController.cs b/codingTracker.jzhartman/CodingTracker.Controller/MainMenuController.cs index 2614e10d5..03dcc4363 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/MainMenuController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/MainMenuController.cs @@ -9,15 +9,17 @@ public class MainMenuController : IMainMenuController private readonly ICodingSessionDataService _service; private readonly ITrackSessionController _trackController; private readonly IEntryListController _entryListController; + private readonly IReportsController _reportsController; private readonly IMenuView _view; public MainMenuController(ICodingSessionDataService service, - ITrackSessionController trackController, IEntryListController entryListController, + ITrackSessionController trackController, IEntryListController entryListController, IReportsController reportsController, IMenuView view) { _service = service; _trackController = trackController; _entryListController = entryListController; + _reportsController = reportsController; _view = view; } @@ -27,7 +29,6 @@ public void Run() while (!exitApp) { - Messages.RenderWelcome(); var selection = _view.RenderMainMenuAndGetSelection(); @@ -40,6 +41,7 @@ public void Run() _entryListController.Run(); break; case "View Reports": // Enter Range-Period??? --> Print all records for period --> Print report data + _reportsController.Run(); break; case "Manage Goal": // Print current goal+progress/Give option to change goal break; diff --git a/codingTracker.jzhartman/CodingTracker.Controller/ReportsController.cs b/codingTracker.jzhartman/CodingTracker.Controller/ReportsController.cs index 6d5f02a4b..991576554 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/ReportsController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/ReportsController.cs @@ -1,16 +1,18 @@ -using CodingTracker.Controller.Interfaces; +using CodingTracker.Models.Entities; +using CodingTracker.Controller.Interfaces; using CodingTracker.Services.Interfaces; +using CodingTracker.Views; using CodingTracker.Views.Interfaces; namespace CodingTracker.Controller; public class ReportsController : IReportsController { - private readonly ICodingSessionDataService _codingSessionDataService; + private readonly ICodingSessionDataService _service; private readonly IMenuView _menuView; private readonly IUserInput _inputView; public ReportsController(ICodingSessionDataService service, IMenuView menuView, IUserInput inputView) { - _codingSessionDataService = service; + _service = service; _menuView = menuView; _inputView = inputView; } @@ -21,7 +23,92 @@ public void Run() while (!returnToMainMenu) { + Messages.RenderWelcome(); + + var dateRangeSelection = GetDateRangeSelectionFromUser(); + + if (dateRangeSelection == "Return to Previous Menu") { returnToMainMenu = true; continue; } + + (DateTime startTime, DateTime endTime) = GetDatesBasedOnUserSelection(dateRangeSelection); + + var sessions = _service.GetSessionListByDateRange(startTime, endTime); + var report = new ReportModel(sessions); + + CodingSessionView.RenderCodingSessions(sessions); + CodingSessionView.RenderReportData(report); } } + + private string GetDateRangeSelectionFromUser() + { + return _menuView.RenderEntryViewOptionsAndGetSelection(); + } + private (DateTime, DateTime) GetDatesBasedOnUserSelection(string selection) + { + bool returnToPreviousMenu = false; + DateTime startTime = new DateTime(); + DateTime endTime = new DateTime(); + + switch (selection) + { + case "All": + (startTime, endTime) = GetAllDates(); + break; + case "One Year": + (startTime, endTime) = GetDateRangeForPastYear(); + break; + case "Year to Date": + (startTime, endTime) = GetDateRangeForYearToDate(); + break; + case "Enter Date Range": + (startTime, endTime) = GetCustomDateRange(); + break; + } + + return (startTime, endTime); + } + private (DateTime, DateTime) GetAllDates() + { + var startTime = DateTime.MinValue; + var endTime = DateTime.MaxValue; + return (startTime, endTime); + } + private (DateTime, DateTime) GetDateRangeForPastYear() + { + var endTime = DateTime.Now; + var startTime = endTime.AddYears(-1); + return (startTime, endTime); + } + private (DateTime, DateTime) GetDateRangeForYearToDate() + { + var endTime = DateTime.Now; + var startTime = new DateTime(endTime.Year, 1, 1); + return (startTime, endTime); + } + private (DateTime, DateTime) GetCustomDateRange() + { + var startTime = _inputView.GetTimeFromUser("start time"); + var endTime = new DateTime(); + bool endTimeValid = false; + + while (endTimeValid == false) + { + endTime = _inputView.GetTimeFromUser("end time"); + + if (endTime <= startTime) + { + var parameter = "End Time"; + var message = $"The end time must be later than {startTime.ToString("yyyy-MM-dd HH:mm:ss")}"; + Messages.Error(parameter, message); + } + else + { + endTimeValid = true; + } + } + + return (startTime, endTime); + + } } diff --git a/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs b/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs index 44f6f471c..45452e2a4 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs @@ -24,6 +24,7 @@ public void Run() while (!returnToMainMenu) { + Messages.RenderWelcome(); var selection = _menuView.RenderTrackingMenuAndGetSelection(); switch (selection) @@ -85,26 +86,17 @@ private DateTime GetEndTimeFromUser(DateTime startTime) { output = _inputView.GetTimeFromUser("end time"); - if (output <= startTime) + var result = _service.ValidateEndTime(output, startTime); + + if (result.IsValid) { - var parameter = "End Time"; - var message = $"The end time must be later than {startTime.ToString("yyyy-MM-dd HH:mm:ss")}"; - Messages.Error(parameter, message); + endTimeValid = true; + output = result.Value; + Messages.Confirmation(result.Value.ToString("yyyy-MM-dd HH:mm:ss")); } else { - var result = _service.ValidateEndTime(output); - - if (result.IsValid) - { - endTimeValid = true; - output = result.Value; - Messages.Confirmation(result.Value.ToString("yyyy-MM-dd HH:mm:ss")); - } - else - { - Messages.Error(result.Parameter, result.Message); - } + Messages.Error(result.Parameter, result.Message); } } return output; diff --git a/codingTracker.jzhartman/CodingTracker.Models/Entities/ReportModel.cs b/codingTracker.jzhartman/CodingTracker.Models/Entities/ReportModel.cs new file mode 100644 index 000000000..33191445a --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Models/Entities/ReportModel.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CodingTracker.Models.Entities; +public class ReportModel +{ + public List SessionList { get; set; } + public double TotalTime { get; set; } + public string TotalTimeText { get; set; } + public double AverageTime { get; set; } + public string AverageTimeText { get; set; } + public CodingSessionDataRecord FirstEntry { get; set; } + public CodingSessionDataRecord LastEntry { get; set; } + public int SessionCount { get; set; } + + public ReportModel(List sessionList) + { + SessionList = sessionList; + CalculateTotalTime(); + CalculateAverageTime(); + GetFirstAndLastEntry(); + GetSessionCount(); + AverageTimeText = ConvertTimeFromSecondsToText(AverageTime); + TotalTimeText = ConvertTimeFromSecondsToText(TotalTime); + } + + private void CalculateTotalTime() + { + foreach (var session in SessionList) + { + TotalTime += session.Duration; + } + } + + private void CalculateAverageTime() + { + AverageTime = TotalTime / SessionList.Count; + } + private void GetFirstAndLastEntry() + { + var orderedList = SessionList.OrderBy(s=>s.StartTime).ToList(); + + FirstEntry = orderedList.First(); + LastEntry = orderedList.Last(); + } + private void GetSessionCount() + { + SessionCount = SessionList.Count; + } + + private string ConvertTimeFromSecondsToText(double input) + { + int miliseconds = TimeSpan.FromSeconds(input).Milliseconds; + int seconds = TimeSpan.FromSeconds(input).Seconds; + + if ((double)miliseconds/1000 >= 0.5) seconds ++; + + int minutes = TimeSpan.FromSeconds(input).Minutes; + int hours = TimeSpan.FromSeconds(input).Hours + TimeSpan.FromSeconds(input).Days * 24; + + return $"{hours} hours, {minutes} minutes, {seconds} seconds"; + } + +} diff --git a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs index 7b1cb96c3..2aacb8f7e 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs @@ -13,50 +13,54 @@ public CodingSessionDataService(ICodingSessionRepository repository) _repository = repository; } + + public List GetAllCodingSessions() { return _repository.GetAll(); } - public List GetSessionListByDateRange(DateTime startTime, DateTime endTime) { return _repository.GetByDateRange(startTime, endTime); } - - public CodingSessionDataRecord GetById(int id) + public CodingSessionDataRecord GetSessionById(int id) { // Validate that ID exists return _repository.GetById(id); } - - public void DeleteById(int id) + public void DeleteSessionById(int id) { _repository.DeleteById(id); } - public void UpdateSession(CodingSessionDataRecord session) { _repository.UpdateSession(session); } - public void AddSession(CodingSession session) { _repository.AddSession(session); } + + public ValidationResult ValidateStartTime(DateTime input) { if (_repository.ExistsWithinTimeFrame(input)) return ValidationResult.Fail("Start Time", "A record already exists for this time"); + else if (input > DateTime.Now) + return ValidationResult.Fail("Start Time", "Cannot enter a future time"); else return ValidationResult.Success(input); } - - public ValidationResult ValidateEndTime(DateTime input) + public ValidationResult ValidateEndTime(DateTime input, DateTime startTime) { if (_repository.ExistsWithinTimeFrame(input)) return ValidationResult.Fail("End Time", "A record already exists for this time"); + else if (input <= startTime) + return ValidationResult.Fail("End Time", $"The end time must be later than {startTime.ToString("yyyy-MM-dd HH:mm:ss")}"); + else if (input > DateTime.Now) + return ValidationResult.Fail("Start Time", "Cannot enter a future time"); else return ValidationResult.Success(input); } @@ -71,7 +75,6 @@ public ValidationResult ValidateUpdatedStartTime(CodingSessionDataReco else return ValidationResult.Success(newStartTime); } - public ValidationResult ValidateUpdatedEndTime(CodingSessionDataRecord session, DateTime newStartTime, DateTime newEndTime) { if (newEndTime == DateTime.MinValue && session.EndTime > newStartTime) diff --git a/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionDataService.cs b/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionDataService.cs index 69272e765..1522ebc24 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionDataService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionDataService.cs @@ -5,12 +5,12 @@ namespace CodingTracker.Services.Interfaces; public interface ICodingSessionDataService { void AddSession(CodingSession session); - void DeleteById(int id); + void DeleteSessionById(int id); List GetAllCodingSessions(); List GetSessionListByDateRange(DateTime startTime, DateTime endTime); - CodingSessionDataRecord GetById(int id); + CodingSessionDataRecord GetSessionById(int id); void UpdateSession(CodingSessionDataRecord session); - ValidationResult ValidateEndTime(DateTime input); + ValidationResult ValidateEndTime(DateTime input, DateTime startTime); ValidationResult ValidateStartTime(DateTime input); ValidationResult ValidateUpdatedEndTime(CodingSessionDataRecord session, DateTime newStartTime, DateTime newEndTime); ValidationResult ValidateUpdatedStartTime(CodingSessionDataRecord session, DateTime updatedStartTime); diff --git a/codingTracker.jzhartman/CodingTracker.Views/CodingSessionView.cs b/codingTracker.jzhartman/CodingTracker.Views/CodingSessionView.cs index 1b30cb3dd..c2381b0aa 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/CodingSessionView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/CodingSessionView.cs @@ -1,4 +1,5 @@ using CodingTracker.Models.Entities; +using Spectre.Console; namespace CodingTracker.Views; public static class CodingSessionView @@ -20,5 +21,19 @@ public static void RenderCodingSession(CodingSessionDataRecord session) Console.WriteLine($"{session.Id}:\t{session.StartTime} to {session.EndTime} for a duration of {session.Duration}"); } + public static void RenderReportData(ReportModel report) + { + AnsiConsole.WriteLine(); + AnsiConsole.MarkupLineInterpolated($"[bold blue]First Entry:[/]\t{report.FirstEntry.StartTime} to {report.FirstEntry.EndTime}"); + AnsiConsole.MarkupLineInterpolated($"[bold blue]Last Entry:[/]\t{report.LastEntry.StartTime} to {report.LastEntry.EndTime}"); + AnsiConsole.MarkupLineInterpolated($"[bold blue]Total Sessions:[/]\t{report.SessionCount}"); + AnsiConsole.MarkupLineInterpolated($"[bold blue]Total Time:[/]\t{report.TotalTimeText}"); + AnsiConsole.MarkupLineInterpolated($"[bold blue]Average Session:[/]\t{report.AverageTimeText}"); + AnsiConsole.WriteLine(); + AnsiConsole.WriteLine(); + AnsiConsole.WriteLine("Press any key to continue"); + AnsiConsole.Console.Input.ReadKey(false); + } + } diff --git a/codingTracker.jzhartman/CodingTracker.Views/Menus/MenuView.cs b/codingTracker.jzhartman/CodingTracker.Views/Menus/MenuView.cs index b44d2f806..f841f9bfb 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/Menus/MenuView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/Menus/MenuView.cs @@ -6,7 +6,6 @@ public class MenuView : IMenuView { public string RenderMainMenuAndGetSelection() { - AnsiConsole.WriteLine(); var selection = AnsiConsole.Prompt( new SelectionPrompt() .Title("Select from the options below:") @@ -69,5 +68,4 @@ public string RenderUpdateOrDeleteOptionsAndGetSelection() ); return selection; } - } diff --git a/codingTracker.jzhartman/CodingTracker.Views/Messages.cs b/codingTracker.jzhartman/CodingTracker.Views/Messages.cs index 73f8c1de1..644a3ebf0 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/Messages.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/Messages.cs @@ -10,6 +10,7 @@ public static void RenderWelcome() AnsiConsole.MarkupLine("[bold blue]CODING TRACKER[/]"); AnsiConsole.MarkupLine("[bold blue]Version 1.0[/]"); AnsiConsole.Write(new Rule()); + AddNewLines(1); } public static void RenderLocation(string location) diff --git a/codingTracker.jzhartman/CodingTracker.Views/UserInput.cs b/codingTracker.jzhartman/CodingTracker.Views/UserInput.cs index 2fbd6fe18..66c71db62 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/UserInput.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/UserInput.cs @@ -131,7 +131,7 @@ private string GenerateUpdateSessionConfirmationPrompt(CodingSessionDataRecord s } private string GenerateEnterDatePromptText(string parameterName, string nullBehavior, bool allowNull) { - string article = GetArticle(parameterName); + string article = GenerateArticleForDatePromptText(parameterName); string promptText = $"Please enter {article} {parameterName} using the format [yellow]'yyyy-MM-dd HH:mm:ss'[/]"; if (allowNull) @@ -145,8 +145,7 @@ private string GenerateEnterDatePromptText(string parameterName, string nullBeha return promptText; } - - private string GetArticle(string noun) + private string GenerateArticleForDatePromptText(string noun) { string article = "a"; char firstLetter = noun.ToLower()[0]; diff --git a/codingTracker.jzhartman/CodingTracker/Startup.cs b/codingTracker.jzhartman/CodingTracker/Startup.cs index b9c050188..74257234a 100644 --- a/codingTracker.jzhartman/CodingTracker/Startup.cs +++ b/codingTracker.jzhartman/CodingTracker/Startup.cs @@ -31,6 +31,7 @@ public static IServiceProvider ConfigureServices() services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); //Register All Services services.AddSingleton(); From 0d02dbf9acff5d260039ada92e52c8e77a70a9fa Mon Sep 17 00:00:00 2001 From: jzhartman Date: Sun, 14 Sep 2025 23:14:58 -0400 Subject: [PATCH 30/57] PROGRESS REPORT COMMIT: Add session, stopwatch, Update, Delete, and Reports complete Needs Goals created Views need heavily refactored SessionsList printing needs serious help --- .../CodingTracker.Controller/ReportsController.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/codingTracker.jzhartman/CodingTracker.Controller/ReportsController.cs b/codingTracker.jzhartman/CodingTracker.Controller/ReportsController.cs index 991576554..7bd0ab0ea 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/ReportsController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/ReportsController.cs @@ -40,6 +40,7 @@ public void Run() } } + private string GetDateRangeSelectionFromUser() { return _menuView.RenderEntryViewOptionsAndGetSelection(); From a27ee92e3a08efd21691dac7002975effa8a3e10 Mon Sep 17 00:00:00 2001 From: jzhartman Date: Wed, 17 Sep 2025 21:20:35 -0400 Subject: [PATCH 31/57] Basic scaffolding for Goals Controller wired up in startup About to jack up the views... --- .../GoalsController.cs | 22 +++++++++++++++++++ .../Interfaces/IGoalsController.cs | 6 +++++ .../MainMenuController.cs | 5 ++++- .../Entities/GoalModel.cs | 15 +++++++++++++ .../CodingTracker/Startup.cs | 1 + 5 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs create mode 100644 codingTracker.jzhartman/CodingTracker.Controller/Interfaces/IGoalsController.cs create mode 100644 codingTracker.jzhartman/CodingTracker.Models/Entities/GoalModel.cs diff --git a/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs b/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs new file mode 100644 index 000000000..7db34e7a3 --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs @@ -0,0 +1,22 @@ +using CodingTracker.Controller.Interfaces; +using CodingTracker.Services.Interfaces; +using CodingTracker.Views.Interfaces; + +namespace CodingTracker.Controller; +public class GoalsController : IGoalsController +{ + private readonly ICodingSessionDataService _service; + private readonly IMenuView _menuView; + + public GoalsController(ICodingSessionDataService service, IMenuView menuView) + { + _service = service; + _menuView = menuView; + } + + public void Run() + { + Console.WriteLine("Not Implemented yet...."); + Console.ReadKey(); + } +} diff --git a/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/IGoalsController.cs b/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/IGoalsController.cs new file mode 100644 index 000000000..ee303fa7a --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/IGoalsController.cs @@ -0,0 +1,6 @@ +namespace CodingTracker.Controller.Interfaces; + +public interface IGoalsController +{ + void Run(); +} \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Controller/MainMenuController.cs b/codingTracker.jzhartman/CodingTracker.Controller/MainMenuController.cs index 03dcc4363..971cc379d 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/MainMenuController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/MainMenuController.cs @@ -10,16 +10,18 @@ public class MainMenuController : IMainMenuController private readonly ITrackSessionController _trackController; private readonly IEntryListController _entryListController; private readonly IReportsController _reportsController; + private readonly IGoalsController _goalsController; private readonly IMenuView _view; public MainMenuController(ICodingSessionDataService service, - ITrackSessionController trackController, IEntryListController entryListController, IReportsController reportsController, + ITrackSessionController trackController, IEntryListController entryListController, IReportsController reportsController, IGoalsController goalsController, IMenuView view) { _service = service; _trackController = trackController; _entryListController = entryListController; _reportsController = reportsController; + _goalsController = goalsController; _view = view; } @@ -44,6 +46,7 @@ public void Run() _reportsController.Run(); break; case "Manage Goal": // Print current goal+progress/Give option to change goal + _goalsController.Run(); break; case "Exit": // Generic goodbye message exitApp = true; diff --git a/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalModel.cs b/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalModel.cs new file mode 100644 index 000000000..3b098bb78 --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalModel.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CodingTracker.Models.Entities; +public class GoalModel +{ + public bool IsNumberOfDays { get; set; } + public bool IsTotalTime { get; set; } + public DateTime StartTime { get; set; } + public DateTime EndTime { get; set; } + +} diff --git a/codingTracker.jzhartman/CodingTracker/Startup.cs b/codingTracker.jzhartman/CodingTracker/Startup.cs index 74257234a..4517ea12b 100644 --- a/codingTracker.jzhartman/CodingTracker/Startup.cs +++ b/codingTracker.jzhartman/CodingTracker/Startup.cs @@ -32,6 +32,7 @@ public static IServiceProvider ConfigureServices() services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); //Register All Services services.AddSingleton(); From 01e42183ecf071631671cc27838094159815bb86 Mon Sep 17 00:00:00 2001 From: jzhartman Date: Wed, 17 Sep 2025 23:03:15 -0400 Subject: [PATCH 32/57] Reduced all views to instantiated classes Changed printing to all use Spectre.Console Ugly, but funcitonal --- .../EntryListController.cs | 29 +++-- .../MainMenuController.cs | 12 +- .../ReportsController.cs | 16 ++- .../TrackSessionController.cs | 24 ++-- .../Entities/ReportModel.cs | 18 --- .../CodingTracker.Views/CodingSessionView.cs | 39 ------ .../CodingTracker.Views/ConsoleOutputView.cs | 122 ++++++++++++++++++ .../Interfaces/IConsoleOutputView.cs | 14 ++ .../{IUserInput.cs => IUserInputView.cs} | 2 +- .../{Menus => }/MenuView.cs | 2 +- .../CodingTracker.Views/Messages.cs | 60 --------- .../{UserInput.cs => UserInputView.cs} | 4 +- .../CodingTracker/Startup.cs | 4 +- 13 files changed, 186 insertions(+), 160 deletions(-) delete mode 100644 codingTracker.jzhartman/CodingTracker.Views/CodingSessionView.cs create mode 100644 codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs create mode 100644 codingTracker.jzhartman/CodingTracker.Views/Interfaces/IConsoleOutputView.cs rename codingTracker.jzhartman/CodingTracker.Views/Interfaces/{IUserInput.cs => IUserInputView.cs} (95%) rename codingTracker.jzhartman/CodingTracker.Views/{Menus => }/MenuView.cs (98%) delete mode 100644 codingTracker.jzhartman/CodingTracker.Views/Messages.cs rename codingTracker.jzhartman/CodingTracker.Views/{UserInput.cs => UserInputView.cs} (98%) diff --git a/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs b/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs index dbf9fc60d..99904287b 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs @@ -9,15 +9,16 @@ public class EntryListController : IEntryListController { private readonly ICodingSessionDataService _service; private readonly IMenuView _menuView; - private readonly IUserInput _inputView; + private readonly IUserInputView _inputView; + private readonly IConsoleOutputView _outputView; public EntryListController(ICodingSessionDataService service, - IMenuView menuView, - IUserInput inputView) + IMenuView menuView, IUserInputView inputView, IConsoleOutputView outputView) { _service = service; _menuView = menuView; _inputView = inputView; + _outputView = outputView; } public void Run() @@ -26,7 +27,7 @@ public void Run() while (!returnToPreviousMenu) { - Messages.RenderWelcome(); + _outputView.WelcomeMessage(); var dateRangeSelection = GetDateRangeSelectionFromUser(); @@ -39,7 +40,7 @@ public void Run() while (!returnToDateSelection) { - CodingSessionView.RenderCodingSessions(sessions); + _outputView.PrintCodingSessionListAsTable(sessions); var selection = _menuView.RenderUpdateOrDeleteOptionsAndGetSelection(); @@ -56,7 +57,7 @@ public void Run() break; } sessions = _service.GetSessionListByDateRange(startTime, endTime); - Messages.RenderWelcome(); + _outputView.WelcomeMessage(); } } @@ -69,7 +70,7 @@ private void ManageSessionDelete(List sessions) if (ConfirmDelete(sessions[recordId])) DeleteSession(sessions[recordId]); else - Messages.ActionCancelled("deletion"); + _outputView.ActionCancelledMessage("deletion"); } private void DeleteSession(CodingSessionDataRecord session) @@ -92,7 +93,7 @@ private void ManageSessionUpdate(List sessions) if (ConfirmUpdate(sessions[recordId], updatedSession)) UpdateSession(updatedSession, sessions[recordId].Id); else - Messages.ActionCancelled("update"); + _outputView.ActionCancelledMessage("update"); // get confirmation // if confirmed => _repository.UpdateSession(updatedsession); @@ -102,7 +103,7 @@ private void UpdateSession(CodingSession session, long id) { var sessionDTO = new CodingSessionDataRecord {Id = id, StartTime = session.StartTime, EndTime = session.EndTime, Duration = (int)session.Duration }; _service.UpdateSession(sessionDTO); - Messages.ActionComplete(true, "Success", "Coding session successfully added!"); + _outputView.ActionCompleteMessage(true, "Success", "Coding session successfully added!"); } private bool ConfirmUpdate(CodingSessionDataRecord session, CodingSession updatedSession) { @@ -122,11 +123,11 @@ private DateTime GetUpdatedStartTime(CodingSessionDataRecord session) { startTimeValid = true; output = result.Value; - Messages.Confirmation(result.Value.ToString("yyyy-MM-dd HH:mm:ss")); + _outputView.ConfirmationMessage(result.Value.ToString("yyyy-MM-dd HH:mm:ss")); } else { - Messages.Error(result.Parameter, result.Message); + _outputView.ErrorMessage(result.Parameter, result.Message); } } return output; @@ -145,11 +146,11 @@ private DateTime GetUpdatedEndTime(CodingSessionDataRecord session, DateTime new { startTimeValid = true; output = result.Value; - Messages.Confirmation(result.Value.ToString("yyyy-MM-dd HH:mm:ss")); + _outputView.ConfirmationMessage(result.Value.ToString("yyyy-MM-dd HH:mm:ss")); } else { - Messages.Error(result.Parameter, result.Message); + _outputView.ErrorMessage(result.Parameter, result.Message); } } return output; @@ -214,7 +215,7 @@ private string GetDateRangeSelectionFromUser() { var parameter = "End Time"; var message = $"The end time must be later than {startTime.ToString("yyyy-MM-dd HH:mm:ss")}"; - Messages.Error(parameter, message); + _outputView.ErrorMessage(parameter, message); } else { diff --git a/codingTracker.jzhartman/CodingTracker.Controller/MainMenuController.cs b/codingTracker.jzhartman/CodingTracker.Controller/MainMenuController.cs index 971cc379d..65cd4cb9e 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/MainMenuController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/MainMenuController.cs @@ -11,18 +11,20 @@ public class MainMenuController : IMainMenuController private readonly IEntryListController _entryListController; private readonly IReportsController _reportsController; private readonly IGoalsController _goalsController; - private readonly IMenuView _view; + private readonly IMenuView _menuView; + private readonly IConsoleOutputView _outputView; public MainMenuController(ICodingSessionDataService service, ITrackSessionController trackController, IEntryListController entryListController, IReportsController reportsController, IGoalsController goalsController, - IMenuView view) + IMenuView menuView, IConsoleOutputView outputView) { _service = service; _trackController = trackController; _entryListController = entryListController; _reportsController = reportsController; _goalsController = goalsController; - _view = view; + _menuView = menuView; + _outputView = outputView; } public void Run() @@ -31,8 +33,8 @@ public void Run() while (!exitApp) { - Messages.RenderWelcome(); - var selection = _view.RenderMainMenuAndGetSelection(); + _outputView.WelcomeMessage(); + var selection = _menuView.RenderMainMenuAndGetSelection(); switch (selection) { diff --git a/codingTracker.jzhartman/CodingTracker.Controller/ReportsController.cs b/codingTracker.jzhartman/CodingTracker.Controller/ReportsController.cs index 7bd0ab0ea..3ccaa1722 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/ReportsController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/ReportsController.cs @@ -9,12 +9,15 @@ public class ReportsController : IReportsController { private readonly ICodingSessionDataService _service; private readonly IMenuView _menuView; - private readonly IUserInput _inputView; - public ReportsController(ICodingSessionDataService service, IMenuView menuView, IUserInput inputView) + private readonly IUserInputView _inputView; + private readonly IConsoleOutputView _outputView; + public ReportsController(ICodingSessionDataService service, + IMenuView menuView, IUserInputView inputView, IConsoleOutputView outputView) { _service = service; _menuView = menuView; _inputView = inputView; + _outputView = outputView; } public void Run() @@ -23,7 +26,7 @@ public void Run() while (!returnToMainMenu) { - Messages.RenderWelcome(); + _outputView.WelcomeMessage(); var dateRangeSelection = GetDateRangeSelectionFromUser(); @@ -34,8 +37,8 @@ public void Run() var sessions = _service.GetSessionListByDateRange(startTime, endTime); var report = new ReportModel(sessions); - CodingSessionView.RenderCodingSessions(sessions); - CodingSessionView.RenderReportData(report); + _outputView.PrintCodingSessionListAsTable(sessions); + _outputView.PrintReportDataAsTable(report); } } @@ -47,7 +50,6 @@ private string GetDateRangeSelectionFromUser() } private (DateTime, DateTime) GetDatesBasedOnUserSelection(string selection) { - bool returnToPreviousMenu = false; DateTime startTime = new DateTime(); DateTime endTime = new DateTime(); @@ -101,7 +103,7 @@ private string GetDateRangeSelectionFromUser() { var parameter = "End Time"; var message = $"The end time must be later than {startTime.ToString("yyyy-MM-dd HH:mm:ss")}"; - Messages.Error(parameter, message); + _outputView.ErrorMessage(parameter, message); } else { diff --git a/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs b/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs index 45452e2a4..adc1f4528 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs @@ -9,13 +9,15 @@ public class TrackSessionController : ITrackSessionController { private readonly ICodingSessionDataService _service; private readonly IMenuView _menuView; - private readonly IUserInput _inputView; + private readonly IUserInputView _inputView; + private readonly IConsoleOutputView _outputView; - public TrackSessionController(ICodingSessionDataService service, IMenuView menuView, IUserInput inputView) + public TrackSessionController(ICodingSessionDataService service, IMenuView menuView, IUserInputView inputView, IConsoleOutputView outputView) { _service = service; _menuView = menuView; _inputView = inputView; + _outputView = outputView; } public void Run() @@ -24,7 +26,7 @@ public void Run() while (!returnToMainMenu) { - Messages.RenderWelcome(); + _outputView.WelcomeMessage(); var selection = _menuView.RenderTrackingMenuAndGetSelection(); switch (selection) @@ -68,11 +70,11 @@ private DateTime GetStartTimeFromUser() { startTimeValid = true; output = result.Value; - Messages.Confirmation(result.Value.ToString("yyyy-MM-dd HH:mm:ss")); + _outputView.ConfirmationMessage(result.Value.ToString("yyyy-MM-dd HH:mm:ss")); } else { - Messages.Error(result.Parameter, result.Message); + _outputView.ErrorMessage(result.Parameter, result.Message); } } return output; @@ -92,11 +94,11 @@ private DateTime GetEndTimeFromUser(DateTime startTime) { endTimeValid = true; output = result.Value; - Messages.Confirmation(result.Value.ToString("yyyy-MM-dd HH:mm:ss")); + _outputView.ConfirmationMessage(result.Value.ToString("yyyy-MM-dd HH:mm:ss")); } else { - Messages.Error(result.Parameter, result.Message); + _outputView.ErrorMessage(result.Parameter, result.Message); } } return output; @@ -111,22 +113,22 @@ private void ConfirmAndAddSession(CodingSession session) } else { - Messages.ActionCancelled("addition"); + _outputView.ActionCancelledMessage("addition"); } } private void AddSession(CodingSession session) { _service.AddSession(session); - Messages.ActionComplete(true, "Success", "Coding session successfully added!"); + _outputView.ActionCompleteMessage(true, "Success", "Coding session successfully added!"); } private CodingSession GetNewSessionFromStopwatch() { var startTime = GetStartTimeWithStopwatch(); - Messages.Confirmation(startTime.ToString("yyyy-MM-dd HH:mm:ss")); + _outputView.ConfirmationMessage(startTime.ToString("yyyy-MM-dd HH:mm:ss")); var endTime = GetStopTimeWithStopwatch(); - Messages.Confirmation(endTime.ToString("yyyy-MM-dd HH:mm:ss")); + _outputView.ConfirmationMessage(endTime.ToString("yyyy-MM-dd HH:mm:ss")); return new CodingSession(startTime, endTime); } diff --git a/codingTracker.jzhartman/CodingTracker.Models/Entities/ReportModel.cs b/codingTracker.jzhartman/CodingTracker.Models/Entities/ReportModel.cs index 33191445a..14d926ce0 100644 --- a/codingTracker.jzhartman/CodingTracker.Models/Entities/ReportModel.cs +++ b/codingTracker.jzhartman/CodingTracker.Models/Entities/ReportModel.cs @@ -9,9 +9,7 @@ public class ReportModel { public List SessionList { get; set; } public double TotalTime { get; set; } - public string TotalTimeText { get; set; } public double AverageTime { get; set; } - public string AverageTimeText { get; set; } public CodingSessionDataRecord FirstEntry { get; set; } public CodingSessionDataRecord LastEntry { get; set; } public int SessionCount { get; set; } @@ -23,8 +21,6 @@ public ReportModel(List sessionList) CalculateAverageTime(); GetFirstAndLastEntry(); GetSessionCount(); - AverageTimeText = ConvertTimeFromSecondsToText(AverageTime); - TotalTimeText = ConvertTimeFromSecondsToText(TotalTime); } private void CalculateTotalTime() @@ -50,18 +46,4 @@ private void GetSessionCount() { SessionCount = SessionList.Count; } - - private string ConvertTimeFromSecondsToText(double input) - { - int miliseconds = TimeSpan.FromSeconds(input).Milliseconds; - int seconds = TimeSpan.FromSeconds(input).Seconds; - - if ((double)miliseconds/1000 >= 0.5) seconds ++; - - int minutes = TimeSpan.FromSeconds(input).Minutes; - int hours = TimeSpan.FromSeconds(input).Hours + TimeSpan.FromSeconds(input).Days * 24; - - return $"{hours} hours, {minutes} minutes, {seconds} seconds"; - } - } diff --git a/codingTracker.jzhartman/CodingTracker.Views/CodingSessionView.cs b/codingTracker.jzhartman/CodingTracker.Views/CodingSessionView.cs deleted file mode 100644 index c2381b0aa..000000000 --- a/codingTracker.jzhartman/CodingTracker.Views/CodingSessionView.cs +++ /dev/null @@ -1,39 +0,0 @@ -using CodingTracker.Models.Entities; -using Spectre.Console; - -namespace CodingTracker.Views; -public static class CodingSessionView -{ - public static void RenderCodingSessions(List sessions) - { - int count = 1; - Console.WriteLine("A list of coding sessions: "); - - foreach (var session in sessions) - { - Console.WriteLine($"{count}:\t{session.StartTime} to {session.EndTime} for a duration of {session.Duration}"); - count++; - } - } - - public static void RenderCodingSession(CodingSessionDataRecord session) - { - Console.WriteLine($"{session.Id}:\t{session.StartTime} to {session.EndTime} for a duration of {session.Duration}"); - } - - public static void RenderReportData(ReportModel report) - { - AnsiConsole.WriteLine(); - AnsiConsole.MarkupLineInterpolated($"[bold blue]First Entry:[/]\t{report.FirstEntry.StartTime} to {report.FirstEntry.EndTime}"); - AnsiConsole.MarkupLineInterpolated($"[bold blue]Last Entry:[/]\t{report.LastEntry.StartTime} to {report.LastEntry.EndTime}"); - AnsiConsole.MarkupLineInterpolated($"[bold blue]Total Sessions:[/]\t{report.SessionCount}"); - AnsiConsole.MarkupLineInterpolated($"[bold blue]Total Time:[/]\t{report.TotalTimeText}"); - AnsiConsole.MarkupLineInterpolated($"[bold blue]Average Session:[/]\t{report.AverageTimeText}"); - AnsiConsole.WriteLine(); - AnsiConsole.WriteLine(); - AnsiConsole.WriteLine("Press any key to continue"); - AnsiConsole.Console.Input.ReadKey(false); - } - - -} diff --git a/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs b/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs new file mode 100644 index 000000000..934646cc7 --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs @@ -0,0 +1,122 @@ +using CodingTracker.Models.Entities; +using CodingTracker.Views.Interfaces; +using Spectre.Console; + +namespace CodingTracker.Views; +public class ConsoleOutputView : IConsoleOutputView +{ + public void WelcomeMessage() + { + AnsiConsole.Clear(); + AnsiConsole.Write(new Rule()); + AnsiConsole.MarkupLine("[bold blue]CODING TRACKER[/]"); + AnsiConsole.MarkupLine("[bold blue]Version 1.0[/]"); + AnsiConsole.Write(new Rule()); + AddNewLines(1); + } + + public void ErrorMessage(string parameter, string message) + { + AddNewLines(1); + AnsiConsole.MarkupInterpolated($"[bold red]ERROR:[/] The value for {parameter} encountered the error: [yellow]{message}[/]"); + AddNewLines(2); + } + + public void ConfirmationMessage(string valueText) + { + AddNewLines(1); + AnsiConsole.MarkupInterpolated($"[bold green]ACCEPTED[/]: Value set to {valueText}"); + AddNewLines(2); + } + public void ActionCompleteMessage(bool isSuccess, string state, string message) + { + AddNewLines(1); + if (isSuccess) + { + AnsiConsole.MarkupInterpolated($"[bold green]{state.ToUpper()}![/] {message}"); + } + else + { + AnsiConsole.MarkupInterpolated($"[bold red]{state.ToUpper()}![/] {message}"); + } + AddNewLines(2); + } + + public void ActionCancelledMessage(string action) + { + AddNewLines(1); + AnsiConsole.MarkupInterpolated($"Cancelled {action} of coding session!"); + } + + + public void PrintCodingSessionListAsTable(List sessions) + { + int count = 1; + var grid = new Grid(); + grid.AddColumn(); + grid.AddColumn(); + grid.AddColumn(); + grid.AddColumn(); + grid.AddRow(new Text[] {new Text("Id").Centered(), + new Text("Start Time").Centered(), + new Text("End Time").Centered(), + new Text("Duration").Centered()}); + + foreach (var session in sessions) + { + grid.AddRow(new string[] { $"[blue]{count}[/]", + $"{session.StartTime.ToString("yyyy-MM-dd")} [yellow]{session.StartTime.ToString("HH:mm:ss")}[/]", + $"{session.EndTime.ToString("yyyy-MM-dd")} [yellow]{session.EndTime.ToString("HH:mm:ss")}[/]", + $"{ConvertTimeFromSecondsToText(session.Duration)}" }); + count++; + } + + AnsiConsole.Write(grid); + AddNewLines(2); + } + + public void PrintSingleCodingSession(CodingSessionDataRecord session) + { + Console.WriteLine($"{session.Id}:\t{session.StartTime} to {session.EndTime} for a duration of {session.Duration}"); + } + + public void PrintReportDataAsTable(ReportModel report) + { + AnsiConsole.WriteLine(); + + var grid = new Grid(); + grid.AddColumn(); + grid.AddColumn(); + grid.AddRow(new string[] { "[bold blue]First Entry:[/]", $"{report.FirstEntry.StartTime.ToString("yyyy-MM-dd")} to {report.FirstEntry.EndTime.ToString("yyyy-MM-dd")}" }); + grid.AddRow(new string[] { "[bold blue]Last Entry:[/]", $"{report.LastEntry.StartTime.ToString("yyyy-MM-dd")} to {report.LastEntry.EndTime.ToString("yyyy-MM-dd")}" }); + grid.AddRow(new string[] { "[bold blue]Total Sessions:[/]", $"{report.SessionCount}" }); + grid.AddRow(new string[] { "[bold blue]Total Time:[/]", $"{ConvertTimeFromSecondsToText(report.TotalTime)}" }); + grid.AddRow(new string[] { "[bold blue]Average Session:[/]", $"{ConvertTimeFromSecondsToText(report.AverageTime)}"}); + + AnsiConsole.Write(grid); + + AnsiConsole.WriteLine("Press any key to continue"); + AnsiConsole.Console.Input.ReadKey(false); + } + + + private void AddNewLines(int lines) + { + for (int i = 0; i < lines; i++) + { + AnsiConsole.WriteLine(); + } + } + private string ConvertTimeFromSecondsToText(double input) + { + int miliseconds = TimeSpan.FromSeconds(input).Milliseconds; + int seconds = TimeSpan.FromSeconds(input).Seconds; + + if ((double)miliseconds / 1000 >= 0.5) seconds++; + + int minutes = TimeSpan.FromSeconds(input).Minutes; + int hours = TimeSpan.FromSeconds(input).Hours + TimeSpan.FromSeconds(input).Days * 24; + + return $"[yellow]{hours,4}[/] hours [yellow]{minutes,2}[/] minutes [yellow]{seconds,2}[/] seconds"; + } +} diff --git a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IConsoleOutputView.cs b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IConsoleOutputView.cs new file mode 100644 index 000000000..4dedc8a31 --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IConsoleOutputView.cs @@ -0,0 +1,14 @@ +using CodingTracker.Models.Entities; + +namespace CodingTracker.Views.Interfaces; +public interface IConsoleOutputView +{ + void ActionCancelledMessage(string action); + void ActionCompleteMessage(bool isSuccess, string state, string message); + void ConfirmationMessage(string valueText); + void ErrorMessage(string parameter, string message); + void PrintCodingSessionListAsTable(List sessions); + void PrintReportDataAsTable(ReportModel report); + void PrintSingleCodingSession(CodingSessionDataRecord session); + void WelcomeMessage(); +} \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInput.cs b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInputView.cs similarity index 95% rename from codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInput.cs rename to codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInputView.cs index 29f5a55c8..bb7b2fd90 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInput.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInputView.cs @@ -1,7 +1,7 @@ using CodingTracker.Models.Entities; namespace CodingTracker.Views.Interfaces; -public interface IUserInput +public interface IUserInputView { int GetRecordIdFromUser(string action, int max); bool GetAddSessionConfirmationFromUser(CodingSession session); diff --git a/codingTracker.jzhartman/CodingTracker.Views/Menus/MenuView.cs b/codingTracker.jzhartman/CodingTracker.Views/MenuView.cs similarity index 98% rename from codingTracker.jzhartman/CodingTracker.Views/Menus/MenuView.cs rename to codingTracker.jzhartman/CodingTracker.Views/MenuView.cs index f841f9bfb..499706706 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/Menus/MenuView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/MenuView.cs @@ -1,7 +1,7 @@ using CodingTracker.Views.Interfaces; using Spectre.Console; -namespace CodingTracker.Views.Menus; +namespace CodingTracker.Views; public class MenuView : IMenuView { public string RenderMainMenuAndGetSelection() diff --git a/codingTracker.jzhartman/CodingTracker.Views/Messages.cs b/codingTracker.jzhartman/CodingTracker.Views/Messages.cs deleted file mode 100644 index 644a3ebf0..000000000 --- a/codingTracker.jzhartman/CodingTracker.Views/Messages.cs +++ /dev/null @@ -1,60 +0,0 @@ -using Spectre.Console; - -namespace CodingTracker.Views; -public static class Messages -{ - public static void RenderWelcome() - { - AnsiConsole.Clear(); - AnsiConsole.Write(new Rule()); - AnsiConsole.MarkupLine("[bold blue]CODING TRACKER[/]"); - AnsiConsole.MarkupLine("[bold blue]Version 1.0[/]"); - AnsiConsole.Write(new Rule()); - AddNewLines(1); - } - - public static void RenderLocation(string location) - { - AnsiConsole.MarkupLineInterpolated($"[bold]{location.ToUpper()}[/]"); - } - public static void Error(string parameter, string message) - { - AddNewLines(1); - AnsiConsole.MarkupInterpolated($"[bold red]ERROR:[/] The value for {parameter} encountered the error: [yellow]{message}[/]"); - AddNewLines(2); - } - - public static void Confirmation(string valueText) - { - AddNewLines(1); - AnsiConsole.MarkupInterpolated($"[bold green]ACCEPTED[/]: Value set to {valueText}"); - AddNewLines(2); - } - public static void ActionComplete(bool isSuccess, string state, string message) - { - AddNewLines(1); - if (isSuccess) - { - AnsiConsole.MarkupInterpolated($"[bold green]{state.ToUpper()}![/] {message}"); - } - else - { - AnsiConsole.MarkupInterpolated($"[bold red]{state.ToUpper()}![/] {message}"); - } - AddNewLines(2); - } - - public static void ActionCancelled(string action) - { - AddNewLines(1); - AnsiConsole.MarkupInterpolated($"Cancelled {action} of coding session!"); - } - - private static void AddNewLines(int lines) - { - for (int i = 0; i < lines; i++) - { - AnsiConsole.WriteLine(); - } - } -} diff --git a/codingTracker.jzhartman/CodingTracker.Views/UserInput.cs b/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs similarity index 98% rename from codingTracker.jzhartman/CodingTracker.Views/UserInput.cs rename to codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs index 66c71db62..dfe4d1fa6 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/UserInput.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs @@ -3,10 +3,10 @@ using Spectre.Console; namespace CodingTracker.Views; -public class UserInput : IUserInput +public class UserInputView : IUserInputView { private readonly string _dateFormat; - public UserInput(string dateFormat) + public UserInputView(string dateFormat) { _dateFormat = dateFormat; } diff --git a/codingTracker.jzhartman/CodingTracker/Startup.cs b/codingTracker.jzhartman/CodingTracker/Startup.cs index 4517ea12b..3fea24486 100644 --- a/codingTracker.jzhartman/CodingTracker/Startup.cs +++ b/codingTracker.jzhartman/CodingTracker/Startup.cs @@ -8,7 +8,6 @@ using CodingTracker.Services.Interfaces; using CodingTracker.Views; using CodingTracker.Views.Interfaces; -using CodingTracker.Views.Menus; using Dapper; using Microsoft.Extensions.DependencyInjection; using System.Configuration; @@ -39,7 +38,8 @@ public static IServiceProvider ConfigureServices() //Resgister All Views services.AddSingleton(); - services.AddSingleton(new UserInput(dateTimeFormat)); + services.AddSingleton(); + services.AddSingleton(new UserInputView(dateTimeFormat)); services.AddSingleton(); From 3e2cef0c532023fc02f7e364ea558d75ca0475ee Mon Sep 17 00:00:00 2001 From: jzhartman Date: Mon, 22 Sep 2025 22:35:29 -0400 Subject: [PATCH 33/57] Added validation for user date format --- .../EntryListController.cs | 4 +- .../MainMenuController.cs | 2 +- .../ReportsController.cs | 2 +- .../TrackSessionController.cs | 2 +- .../Interfaces/IMenuView.cs | 8 ++-- .../CodingTracker.Views/MenuView.cs | 8 ++-- .../CodingTracker.Views/UserInputView.cs | 41 +++++++++++++------ 7 files changed, 42 insertions(+), 25 deletions(-) diff --git a/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs b/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs index 99904287b..968e1e698 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs @@ -42,7 +42,7 @@ public void Run() { _outputView.PrintCodingSessionListAsTable(sessions); - var selection = _menuView.RenderUpdateOrDeleteOptionsAndGetSelection(); + var selection = _menuView.PrintUpdateOrDeleteOptionsAndGetSelection(); switch (selection) { @@ -157,7 +157,7 @@ private DateTime GetUpdatedEndTime(CodingSessionDataRecord session, DateTime new } private string GetDateRangeSelectionFromUser() { - return _menuView.RenderEntryViewOptionsAndGetSelection(); + return _menuView.PrintEntryViewOptionsAndGetSelection(); } private (DateTime, DateTime) GetDatesBasedOnUserSelection(string selection) { diff --git a/codingTracker.jzhartman/CodingTracker.Controller/MainMenuController.cs b/codingTracker.jzhartman/CodingTracker.Controller/MainMenuController.cs index 65cd4cb9e..32d59e7be 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/MainMenuController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/MainMenuController.cs @@ -34,7 +34,7 @@ public void Run() while (!exitApp) { _outputView.WelcomeMessage(); - var selection = _menuView.RenderMainMenuAndGetSelection(); + var selection = _menuView.PrintMainMenuAndGetSelection(); switch (selection) { diff --git a/codingTracker.jzhartman/CodingTracker.Controller/ReportsController.cs b/codingTracker.jzhartman/CodingTracker.Controller/ReportsController.cs index 3ccaa1722..6c04330bd 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/ReportsController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/ReportsController.cs @@ -46,7 +46,7 @@ public void Run() private string GetDateRangeSelectionFromUser() { - return _menuView.RenderEntryViewOptionsAndGetSelection(); + return _menuView.PrintEntryViewOptionsAndGetSelection(); } private (DateTime, DateTime) GetDatesBasedOnUserSelection(string selection) { diff --git a/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs b/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs index adc1f4528..1b279ab21 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs @@ -27,7 +27,7 @@ public void Run() while (!returnToMainMenu) { _outputView.WelcomeMessage(); - var selection = _menuView.RenderTrackingMenuAndGetSelection(); + var selection = _menuView.PrintTrackingMenuAndGetSelection(); switch (selection) { diff --git a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IMenuView.cs b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IMenuView.cs index db8792001..5735fe0fd 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IMenuView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IMenuView.cs @@ -1,8 +1,8 @@ namespace CodingTracker.Views.Interfaces; public interface IMenuView { - string RenderEntryViewOptionsAndGetSelection(); - string RenderMainMenuAndGetSelection(); - string RenderTrackingMenuAndGetSelection(); - string RenderUpdateOrDeleteOptionsAndGetSelection(); + string PrintEntryViewOptionsAndGetSelection(); + string PrintMainMenuAndGetSelection(); + string PrintTrackingMenuAndGetSelection(); + string PrintUpdateOrDeleteOptionsAndGetSelection(); } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Views/MenuView.cs b/codingTracker.jzhartman/CodingTracker.Views/MenuView.cs index 499706706..bf33541e8 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/MenuView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/MenuView.cs @@ -4,7 +4,7 @@ namespace CodingTracker.Views; public class MenuView : IMenuView { - public string RenderMainMenuAndGetSelection() + public string PrintMainMenuAndGetSelection() { var selection = AnsiConsole.Prompt( new SelectionPrompt() @@ -22,7 +22,7 @@ public string RenderMainMenuAndGetSelection() return selection; } - public string RenderTrackingMenuAndGetSelection() + public string PrintTrackingMenuAndGetSelection() { var selection = AnsiConsole.Prompt( new SelectionPrompt() @@ -38,7 +38,7 @@ public string RenderTrackingMenuAndGetSelection() return selection; } - public string RenderEntryViewOptionsAndGetSelection() + public string PrintEntryViewOptionsAndGetSelection() { var selection = AnsiConsole.Prompt( new SelectionPrompt() @@ -54,7 +54,7 @@ public string RenderEntryViewOptionsAndGetSelection() ); return selection; } - public string RenderUpdateOrDeleteOptionsAndGetSelection() + public string PrintUpdateOrDeleteOptionsAndGetSelection() { var selection = AnsiConsole.Prompt( new SelectionPrompt() diff --git a/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs b/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs index dfe4d1fa6..00db1122b 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs @@ -1,6 +1,8 @@ using CodingTracker.Models.Entities; using CodingTracker.Views.Interfaces; +using Microsoft.VisualBasic; using Spectre.Console; +using System.Globalization; namespace CodingTracker.Views; public class UserInputView : IUserInputView @@ -12,20 +14,35 @@ public UserInputView(string dateFormat) } public DateTime GetTimeFromUser(string parameterName, string nullBehavior = "", bool allowNull = false) { - var date = new DateTime(); + var timeInput = string.Empty; var promptText = GenerateEnterDatePromptText(parameterName, nullBehavior, allowNull); - if (allowNull) date = AnsiConsole.Prompt( - new TextPrompt(promptText) - .AllowEmpty()); - - else date = AnsiConsole.Prompt( - new TextPrompt(promptText)); - - - //Add custom validation for time format - - return date; + if (allowNull) timeInput = AnsiConsole.Prompt( + new TextPrompt(promptText) + .AllowEmpty() + .ValidationErrorMessage("[red]That's not a valid date format. Please use MM/dd/yyyy.[/]") + .Validate(input => + { + if (DateTime.TryParseExact(input, _dateFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out _)) + { + return ValidationResult.Success(); + } + return ValidationResult.Error(); + })); + + else timeInput = AnsiConsole.Prompt( + new TextPrompt(promptText) + .ValidationErrorMessage("[red]That's not a valid date format. Please use MM/dd/yyyy.[/]") + .Validate(input => + { + if (DateTime.TryParseExact(input, _dateFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out _)) + { + return ValidationResult.Success(); + } + return ValidationResult.Error(); + })); + + return DateTime.ParseExact(timeInput, _dateFormat, CultureInfo.InvariantCulture); } public string StartStopwatch() From 88001344b70f7c93928d147a40323ec5a65e6d3c Mon Sep 17 00:00:00 2001 From: jzhartman Date: Mon, 22 Sep 2025 22:45:29 -0400 Subject: [PATCH 34/57] Fixed error message for input validation --- .../CodingTracker.Views/UserInputView.cs | 35 +++---------------- 1 file changed, 5 insertions(+), 30 deletions(-) diff --git a/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs b/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs index 00db1122b..30c82266a 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs @@ -16,11 +16,13 @@ public DateTime GetTimeFromUser(string parameterName, string nullBehavior = "", { var timeInput = string.Empty; var promptText = GenerateEnterDatePromptText(parameterName, nullBehavior, allowNull); + var errorMessageText = $"[bold red]ERROR:[/] The value you entered does not match the required format!"; + if (allowNull) timeInput = AnsiConsole.Prompt( new TextPrompt(promptText) .AllowEmpty() - .ValidationErrorMessage("[red]That's not a valid date format. Please use MM/dd/yyyy.[/]") + .ValidationErrorMessage(errorMessageText) .Validate(input => { if (DateTime.TryParseExact(input, _dateFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out _)) @@ -32,7 +34,7 @@ public DateTime GetTimeFromUser(string parameterName, string nullBehavior = "", else timeInput = AnsiConsole.Prompt( new TextPrompt(promptText) - .ValidationErrorMessage("[red]That's not a valid date format. Please use MM/dd/yyyy.[/]") + .ValidationErrorMessage(errorMessageText) .Validate(input => { if (DateTime.TryParseExact(input, _dateFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out _)) @@ -170,31 +172,4 @@ private string GenerateArticleForDatePromptText(string noun) return article; } -} - - -/* - // Define the target date format. - const string dateFormat = "yyyy-MM-dd"; - - // Prompt for a date with validation. - var dateString = AnsiConsole.Prompt( - new TextPrompt("Enter a date in [green]yyyy-MM-dd[/] format:") - .PromptStyle("yellow") - .ValidationErrorMessage($"[red]Invalid date format. Please use {dateFormat}.[/]") - .Validate(input => - { - // Use DateTime.TryParseExact for robust format checking. - if (DateTime.TryParseExact(input, dateFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out _)) - { - return ValidationResult.Success(); - } - - return ValidationResult.Error(); - })); - - // After successful validation, parse the string into a DateTime object. - DateTime parsedDate = DateTime.ParseExact(dateString, dateFormat, CultureInfo.InvariantCulture); - - AnsiConsole.MarkupLine($"You entered: [green]{parsedDate:yyyy-MM-dd}[/]"); -*/ \ No newline at end of file +} \ No newline at end of file From c0397e0976dafda1b6c71eaf918a33919326b514 Mon Sep 17 00:00:00 2001 From: jzhartman Date: Sat, 27 Sep 2025 23:05:30 -0400 Subject: [PATCH 35/57] Sort of broke up repeated code by moving a chunk to service layer. Not happy with it, but done fiddling. Also fixed two minor UI bugs --- .../EntryListController.cs | 60 ++++------- .../ReportsController.cs | 60 ++++------- .../CodingSessionDataService.cs | 101 ++++++++++++++++-- .../Interfaces/ICodingSessionDataService.cs | 3 + .../CodingTracker.Views/ConsoleOutputView.cs | 6 +- .../CodingTracker.Views/MenuView.cs | 6 +- .../CodingTracker.Views/UserInputView.cs | 17 ++- 7 files changed, 159 insertions(+), 94 deletions(-) diff --git a/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs b/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs index 968e1e698..6a59a255d 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs @@ -161,69 +161,47 @@ private string GetDateRangeSelectionFromUser() } private (DateTime, DateTime) GetDatesBasedOnUserSelection(string selection) { - bool returnToPreviousMenu = false; DateTime startTime = new DateTime(); DateTime endTime = new DateTime(); switch (selection) { case "All": - (startTime, endTime) = GetAllDates(); - break; - case "One Year": - (startTime, endTime) = GetDateRangeForPastYear(); - break; + case "Past Year": case "Year to Date": - (startTime, endTime) = GetDateRangeForYearToDate(); + (startTime, endTime) = _service.GetBasicDateRange(selection); break; - case "Enter Date Range": - (startTime, endTime) = GetCustomDateRange(); + case "Custom Week": + case "Custom Month": + case "Custom Year": + startTime = GetRangeStartTime(); + endTime = _service.GetEndTimeForAdvancedDateRange(selection, startTime); break; } return (startTime, endTime); } - private (DateTime, DateTime) GetAllDates() - { - var startTime = DateTime.MinValue; - var endTime = DateTime.MaxValue; - return (startTime, endTime); - } - private (DateTime, DateTime) GetDateRangeForPastYear() - { - var endTime = DateTime.Now; - var startTime = endTime.AddYears(-1); - return (startTime, endTime); - } - private (DateTime, DateTime) GetDateRangeForYearToDate() - { - var endTime = DateTime.Now; - var startTime = new DateTime(endTime.Year, 1, 1); - return (startTime, endTime); - } - private (DateTime, DateTime) GetCustomDateRange() + private DateTime GetRangeStartTime() { - var startTime = _inputView.GetTimeFromUser("start time"); - var endTime = new DateTime(); - bool endTimeValid = false; + var output = new DateTime(); + bool startTimeValid = false; - while (endTimeValid == false) + while (startTimeValid == false) { - endTime = _inputView.GetTimeFromUser("end time"); + var startTime = _inputView.GetTimeFromUser("start time"); + var result = _service.ValidateDateRangeStartTime(startTime); - if (endTime <= startTime) + if (result.IsValid) { - var parameter = "End Time"; - var message = $"The end time must be later than {startTime.ToString("yyyy-MM-dd HH:mm:ss")}"; - _outputView.ErrorMessage(parameter, message); + startTimeValid = true; + output = result.Value; + _outputView.ConfirmationMessage(result.Value.ToString("yyyy-MM-dd HH:mm:ss")); } else { - endTimeValid = true; + _outputView.ErrorMessage(result.Parameter, result.Message); } } - - return (startTime, endTime); - + return output; } } diff --git a/codingTracker.jzhartman/CodingTracker.Controller/ReportsController.cs b/codingTracker.jzhartman/CodingTracker.Controller/ReportsController.cs index 6c04330bd..21866a085 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/ReportsController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/ReportsController.cs @@ -56,62 +56,42 @@ private string GetDateRangeSelectionFromUser() switch (selection) { case "All": - (startTime, endTime) = GetAllDates(); - break; - case "One Year": - (startTime, endTime) = GetDateRangeForPastYear(); - break; + case "Past Year": case "Year to Date": - (startTime, endTime) = GetDateRangeForYearToDate(); + (startTime, endTime) = _service.GetBasicDateRange(selection); break; - case "Enter Date Range": - (startTime, endTime) = GetCustomDateRange(); + case "Custom Week": + case "Custom Month": + case "Custom Year": + startTime = GetRangeStartTime(); + endTime = _service.GetEndTimeForAdvancedDateRange(selection, startTime); break; } return (startTime, endTime); } - private (DateTime, DateTime) GetAllDates() - { - var startTime = DateTime.MinValue; - var endTime = DateTime.MaxValue; - return (startTime, endTime); - } - private (DateTime, DateTime) GetDateRangeForPastYear() - { - var endTime = DateTime.Now; - var startTime = endTime.AddYears(-1); - return (startTime, endTime); - } - private (DateTime, DateTime) GetDateRangeForYearToDate() - { - var endTime = DateTime.Now; - var startTime = new DateTime(endTime.Year, 1, 1); - return (startTime, endTime); - } - private (DateTime, DateTime) GetCustomDateRange() + + private DateTime GetRangeStartTime() { - var startTime = _inputView.GetTimeFromUser("start time"); - var endTime = new DateTime(); - bool endTimeValid = false; + var output = new DateTime(); + bool startTimeValid = false; - while (endTimeValid == false) + while (startTimeValid == false) { - endTime = _inputView.GetTimeFromUser("end time"); + var startTime = _inputView.GetTimeFromUser("start time"); + var result = _service.ValidateDateRangeStartTime(startTime); - if (endTime <= startTime) + if (result.IsValid) { - var parameter = "End Time"; - var message = $"The end time must be later than {startTime.ToString("yyyy-MM-dd HH:mm:ss")}"; - _outputView.ErrorMessage(parameter, message); + startTimeValid = true; + output = result.Value; + _outputView.ConfirmationMessage(result.Value.ToString("yyyy-MM-dd HH:mm:ss")); } else { - endTimeValid = true; + _outputView.ErrorMessage(result.Parameter, result.Message); } } - - return (startTime, endTime); - + return output; } } diff --git a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs index 2aacb8f7e..6d173cd5a 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs @@ -14,7 +14,20 @@ public CodingSessionDataService(ICodingSessionRepository repository) } + // MOVE TO CRUD SERVICES FILE + public void DeleteSessionById(int id) + { + _repository.DeleteById(id); + } + public void UpdateSession(CodingSessionDataRecord session) + { + _repository.UpdateSession(session); + } + public void AddSession(CodingSession session) + { + _repository.AddSession(session); + } public List GetAllCodingSessions() { return _repository.GetAll(); @@ -29,21 +42,83 @@ public CodingSessionDataRecord GetSessionById(int id) return _repository.GetById(id); } - public void DeleteSessionById(int id) + + + + + + // Separate to new file? + // Provides Calculated Values Based On User Input + + public (DateTime, DateTime) GetBasicDateRange(string selection) { - _repository.DeleteById(id); + DateTime startTime = new DateTime(); + DateTime endTime = new DateTime(); + + switch (selection) + { + case "All": + (startTime, endTime) = GetAllDates(); + break; + case "Past Year": + (startTime, endTime) = GetDateRangeForPastYear(); + break; + case "Year to Date": + (startTime, endTime) = GetDateRangeForYearToDate(); + break; + } + + return (startTime, endTime); } - public void UpdateSession(CodingSessionDataRecord session) + public DateTime GetEndTimeForAdvancedDateRange(string selection, DateTime startTime) { - _repository.UpdateSession(session); + DateTime endTime = new DateTime(); + + switch (selection) + { + + case "Custom Week": + endTime = endTime = startTime.AddDays(7); + break; + case "Custom Month": + endTime = startTime.AddMonths(1); + break; + case "Custom Year": + endTime = startTime.AddYears(1); + break; + } + + return endTime; } - public void AddSession(CodingSession session) + private (DateTime, DateTime) GetAllDates() { - _repository.AddSession(session); + var startTime = DateTime.MinValue; + var endTime = DateTime.MaxValue; + return (startTime, endTime); } + private (DateTime, DateTime) GetDateRangeForPastYear() + { + var endTime = DateTime.Now; + var startTime = endTime.AddYears(-1); + return (startTime, endTime); + } + private (DateTime, DateTime) GetDateRangeForYearToDate() + { + var endTime = DateTime.Now; + var startTime = new DateTime(endTime.Year, 1, 1); + return (startTime, endTime); + } + + + + + + + // MOVE TO VALIDATION SERVICES FILE + public ValidationResult ValidateStartTime(DateTime input) { if (_repository.ExistsWithinTimeFrame(input)) @@ -89,4 +164,18 @@ public ValidationResult ValidateUpdatedEndTime(CodingSessionDataRecord else return ValidationResult.Success(newEndTime); } + public ValidationResult ValidateReportEndTime(DateTime input, DateTime startTime) + { + if (input <= startTime) + return ValidationResult.Fail("End Time", $"The end time must be later than {startTime.ToString("yyyy-MM-dd HH:mm:ss")}"); + else + return ValidationResult.Success(input); + } + public ValidationResult ValidateDateRangeStartTime(DateTime input) + { + if (input > DateTime.Now) + return ValidationResult.Fail("Start Time", "Cannot enter a future time"); + else + return ValidationResult.Success(input); + } } diff --git a/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionDataService.cs b/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionDataService.cs index 1522ebc24..535c27f7c 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionDataService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionDataService.cs @@ -14,4 +14,7 @@ public interface ICodingSessionDataService ValidationResult ValidateStartTime(DateTime input); ValidationResult ValidateUpdatedEndTime(CodingSessionDataRecord session, DateTime newStartTime, DateTime newEndTime); ValidationResult ValidateUpdatedStartTime(CodingSessionDataRecord session, DateTime updatedStartTime); + (DateTime, DateTime) GetBasicDateRange(string selection); + DateTime GetEndTimeForAdvancedDateRange(string selection, DateTime startTime); + ValidationResult ValidateDateRangeStartTime(DateTime input); } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs b/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs index 934646cc7..c74907880 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs @@ -41,7 +41,6 @@ public void ActionCompleteMessage(bool isSuccess, string state, string message) } AddNewLines(2); } - public void ActionCancelledMessage(string action) { AddNewLines(1); @@ -74,12 +73,10 @@ public void PrintCodingSessionListAsTable(List sessions AnsiConsole.Write(grid); AddNewLines(2); } - public void PrintSingleCodingSession(CodingSessionDataRecord session) { Console.WriteLine($"{session.Id}:\t{session.StartTime} to {session.EndTime} for a duration of {session.Duration}"); } - public void PrintReportDataAsTable(ReportModel report) { AnsiConsole.WriteLine(); @@ -95,7 +92,8 @@ public void PrintReportDataAsTable(ReportModel report) AnsiConsole.Write(grid); - AnsiConsole.WriteLine("Press any key to continue"); + AddNewLines(2); + AnsiConsole.Write("Press any key to continue... "); AnsiConsole.Console.Input.ReadKey(false); } diff --git a/codingTracker.jzhartman/CodingTracker.Views/MenuView.cs b/codingTracker.jzhartman/CodingTracker.Views/MenuView.cs index bf33541e8..a13e26c88 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/MenuView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/MenuView.cs @@ -46,9 +46,11 @@ public string PrintEntryViewOptionsAndGetSelection() .AddChoices(new[] { "All", - "One Year", + "Past Year", "Year to Date", - "Enter Date Range", + "Custom Week", + "Custom Month", + "Custom Year", "Return to Previous Menu" }) ); diff --git a/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs b/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs index 30c82266a..85f32180c 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs @@ -115,9 +115,11 @@ public bool GetUpdateSessionConfirmationFromUser(CodingSessionDataRecord session } public bool GetDeleteSessionConfirmationFromUser(CodingSessionDataRecord session) { + var duration = ConvertTimeFromSecondsToText(session.Duration); + string promptText = $"Confirm deletion of coding session with start time [yellow]{session.StartTime.ToString("yyyy-MM-dd HH:mm:ss")}[/]" + $" and end time [yellow]{session.EndTime.ToString("yyyy-MM-dd HH:mm:ss")}[/]" - + $" with duration [yellow]{TimeSpan.FromSeconds(session.Duration).ToString()}[/]"; + + $" with duration [yellow]{duration}[/]"; var confirmation = AnsiConsole.Prompt( new TextPrompt(promptText) @@ -128,6 +130,19 @@ public bool GetDeleteSessionConfirmationFromUser(CodingSessionDataRecord session return confirmation; } + private string ConvertTimeFromSecondsToText(double input) + { + int miliseconds = TimeSpan.FromSeconds(input).Milliseconds; + int seconds = TimeSpan.FromSeconds(input).Seconds; + + if ((double)miliseconds / 1000 >= 0.5) seconds++; + + int minutes = TimeSpan.FromSeconds(input).Minutes; + int hours = TimeSpan.FromSeconds(input).Hours + TimeSpan.FromSeconds(input).Days * 24; + + return $"{hours}:{minutes:00}:{seconds:00}"; + } + private string GenerateUpdateSessionConfirmationPrompt(CodingSessionDataRecord session, CodingSession updatedSession) { string prompt = $"Update coding session "; From 30c2254273220292d46905321a83334e973edb7f Mon Sep 17 00:00:00 2001 From: jzhartman Date: Fri, 3 Oct 2025 23:37:52 -0400 Subject: [PATCH 36/57] Added validation for end time overlapping existing records for AddSession. Will need to apply it to Update as well. Also applied previous UI change for Delete Confirmation message to Add confirmation message. --- .../ReportsController.cs | 4 +--- .../Interfaces/ICodingSessionRepository.cs | 1 + .../Repositories/CodingSessionRepository.cs | 11 +++++++++++ .../CodingSessionDataService.cs | 16 ++++++++++++++++ .../CodingTracker.Views/ConsoleOutputView.cs | 6 ++---- .../CodingTracker.Views/MenuView.cs | 6 +++--- .../CodingTracker.Views/UserInputView.cs | 12 ++++++------ 7 files changed, 40 insertions(+), 16 deletions(-) diff --git a/codingTracker.jzhartman/CodingTracker.Controller/ReportsController.cs b/codingTracker.jzhartman/CodingTracker.Controller/ReportsController.cs index 21866a085..66b3e38b7 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/ReportsController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/ReportsController.cs @@ -38,8 +38,7 @@ public void Run() var report = new ReportModel(sessions); _outputView.PrintCodingSessionListAsTable(sessions); - _outputView.PrintReportDataAsTable(report); - + _outputView.PrintReportDataAsTable(report); } } @@ -70,7 +69,6 @@ private string GetDateRangeSelectionFromUser() return (startTime, endTime); } - private DateTime GetRangeStartTime() { var output = new DateTime(); diff --git a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs index cf4489350..b01bd71d9 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs @@ -10,4 +10,5 @@ public interface ICodingSessionRepository void DeleteById(int id); bool ExistsWithinTimeFrame(DateTime time); bool ExistsWithinTimeFrameExcludingSessionById(CodingSessionDataRecord session, DateTime newTime); + DateTime GetStartTimeOfNextRecord(DateTime time); } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs index b8dd0ba88..93e9a213b 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs @@ -80,7 +80,18 @@ public void DeleteById(int id) } + public DateTime GetStartTimeOfNextRecord(DateTime time) + { + var parameter = new DateValue { Time = time }; + using var connection = _connectionFactory.CreateConnection(); + + string sql = @"select StartTime from CodingSessions + where StartTime > @Time + order by StartTime + limit 1"; + return LoadData(sql, parameter).FirstOrDefault(); +; } public bool ExistsWithinTimeFrame(DateTime time) { diff --git a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs index 6d173cd5a..60995d434 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs @@ -130,8 +130,12 @@ public ValidationResult ValidateStartTime(DateTime input) } public ValidationResult ValidateEndTime(DateTime input, DateTime startTime) { + var nextRecordStartTime = _repository.GetStartTimeOfNextRecord(input); + if (_repository.ExistsWithinTimeFrame(input)) return ValidationResult.Fail("End Time", "A record already exists for this time"); + else if (TimeOverlapsNextEntry(input, startTime)) + return ValidationResult.Fail("End Time", "A record already exists for this time"); else if (input <= startTime) return ValidationResult.Fail("End Time", $"The end time must be later than {startTime.ToString("yyyy-MM-dd HH:mm:ss")}"); else if (input > DateTime.Now) @@ -178,4 +182,16 @@ public ValidationResult ValidateDateRangeStartTime(DateTime input) else return ValidationResult.Success(input); } + + private bool TimeOverlapsNextEntry(DateTime input, DateTime startTime) + { + var nextRecordStartTime = _repository.GetStartTimeOfNextRecord(startTime); + + if (nextRecordStartTime == null || nextRecordStartTime == DateTime.MinValue) + return false; + else if (input >= nextRecordStartTime) + return true; + else + return false; + } } diff --git a/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs b/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs index c74907880..5b6091d86 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs @@ -14,14 +14,12 @@ public void WelcomeMessage() AnsiConsole.Write(new Rule()); AddNewLines(1); } - public void ErrorMessage(string parameter, string message) { AddNewLines(1); AnsiConsole.MarkupInterpolated($"[bold red]ERROR:[/] The value for {parameter} encountered the error: [yellow]{message}[/]"); AddNewLines(2); } - public void ConfirmationMessage(string valueText) { AddNewLines(1); @@ -46,8 +44,6 @@ public void ActionCancelledMessage(string action) AddNewLines(1); AnsiConsole.MarkupInterpolated($"Cancelled {action} of coding session!"); } - - public void PrintCodingSessionListAsTable(List sessions) { int count = 1; @@ -98,6 +94,8 @@ public void PrintReportDataAsTable(ReportModel report) } + + private void AddNewLines(int lines) { for (int i = 0; i < lines; i++) diff --git a/codingTracker.jzhartman/CodingTracker.Views/MenuView.cs b/codingTracker.jzhartman/CodingTracker.Views/MenuView.cs index a13e26c88..6fecaf1b7 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/MenuView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/MenuView.cs @@ -63,9 +63,9 @@ public string PrintUpdateOrDeleteOptionsAndGetSelection() .Title("Please select the next operation:") .AddChoices(new[] { - "Change Record", - "Delete Record", - "Return to Previous Menu" + "Change Record", + "Delete Record", + "Return to Previous Menu" }) ); return selection; diff --git a/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs b/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs index 85f32180c..505091a8a 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs @@ -46,7 +46,6 @@ public DateTime GetTimeFromUser(string parameterName, string nullBehavior = "", return DateTime.ParseExact(timeInput, _dateFormat, CultureInfo.InvariantCulture); } - public string StartStopwatch() { AnsiConsole.WriteLine(); @@ -61,7 +60,6 @@ public string StartStopwatch() return selection; } - public string StopStopwatch() { AnsiConsole.WriteLine(); @@ -76,7 +74,6 @@ public string StopStopwatch() return selection; } - public int GetRecordIdFromUser(string action, int max) { var id = AnsiConsole.Prompt( @@ -92,15 +89,16 @@ public int GetRecordIdFromUser(string action, int max) } public bool GetAddSessionConfirmationFromUser(CodingSession session) { + var duration = ConvertTimeFromSecondsToText(session.Duration); + var confirmation = AnsiConsole.Prompt( - new TextPrompt($"Add coding session starting at [yellow]{session.StartTime.ToString("yyyy-MM-dd HH:mm:ss")}[/] and ending at [yellow]{session.EndTime.ToString("yyyy-MM-dd HH:mm:ss")}[/] with duration [yellow]{session.DurationText}[/]?") + new TextPrompt($"Add coding session starting at [yellow]{session.StartTime.ToString("yyyy-MM-dd HH:mm:ss")}[/] and ending at [yellow]{session.EndTime.ToString("yyyy-MM-dd HH:mm:ss")}[/] with duration [yellow]{duration}[/]?") .AddChoice(true) .AddChoice(false) .WithConverter(choice => choice ? "y" : "n")); return confirmation; } - public bool GetUpdateSessionConfirmationFromUser(CodingSessionDataRecord session, CodingSession updatedSession) { string promptText = GenerateUpdateSessionConfirmationPrompt(session, updatedSession); @@ -130,6 +128,9 @@ public bool GetDeleteSessionConfirmationFromUser(CodingSessionDataRecord session return confirmation; } + + + private string ConvertTimeFromSecondsToText(double input) { int miliseconds = TimeSpan.FromSeconds(input).Milliseconds; @@ -142,7 +143,6 @@ private string ConvertTimeFromSecondsToText(double input) return $"{hours}:{minutes:00}:{seconds:00}"; } - private string GenerateUpdateSessionConfirmationPrompt(CodingSessionDataRecord session, CodingSession updatedSession) { string prompt = $"Update coding session "; From a37805ef7e3c781c2539acc23e55362ad1566b42 Mon Sep 17 00:00:00 2001 From: jzhartman Date: Mon, 20 Oct 2025 23:29:37 -0400 Subject: [PATCH 37/57] Intermin commit. Confirmed all validation cases for Add Session. Confirmed validation cases for Update Start Time Found error in validation for Update End Time Minor updates to UI --- .../EntryListController.cs | 5 ++-- .../Repositories/CodingSessionRepository.cs | 24 --------------- .../CodingSessionDataService.cs | 23 +++++++++----- .../CodingTracker.Views/ConsoleOutputView.cs | 20 ++++++++++--- .../Interfaces/IConsoleOutputView.cs | 2 +- .../Interfaces/IUserInputView.cs | 2 +- .../CodingTracker.Views/UserInputView.cs | 30 ++++++++++++++----- 7 files changed, 58 insertions(+), 48 deletions(-) diff --git a/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs b/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs index 6a59a255d..69da7e765 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs @@ -84,6 +84,7 @@ private bool ConfirmDelete(CodingSessionDataRecord session) private void ManageSessionUpdate(List sessions) { var recordId = _inputView.GetRecordIdFromUser("update", sessions.Count()) - 1; + _outputView.PrintCodingSessionToUpdateById(sessions[recordId], recordId+1); var newStartTime = GetUpdatedStartTime(sessions[recordId]); var newEndTime = GetUpdatedEndTime(sessions[recordId], newStartTime); @@ -116,7 +117,7 @@ private DateTime GetUpdatedStartTime(CodingSessionDataRecord session) while (startTimeValid == false) { - var newStartTime = _inputView.GetTimeFromUser("new start time", "current start time", true); + var newStartTime = _inputView.GetTimeFromUser("new start time", true); var result = _service.ValidateUpdatedStartTime(session, newStartTime); if (result.IsValid) @@ -139,7 +140,7 @@ private DateTime GetUpdatedEndTime(CodingSessionDataRecord session, DateTime new while (startTimeValid == false) { - var newEndTime = _inputView.GetTimeFromUser("new end time", "current end time", true); + var newEndTime = _inputView.GetTimeFromUser("new end time", true); var result = _service.ValidateUpdatedEndTime(session, newStartTime, newEndTime); if (result.IsValid) diff --git a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs index 93e9a213b..09ca95eb7 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs @@ -106,7 +106,6 @@ public bool ExistsWithinTimeFrame(DateTime time) return (count > 0); } - public bool ExistsWithinTimeFrameExcludingSessionById(CodingSessionDataRecord session, DateTime newTime) { var parameter = new TimeUpdate { Id = (int)session.Id, Time = newTime }; @@ -121,27 +120,4 @@ public bool ExistsWithinTimeFrameExcludingSessionById(CodingSessionDataRecord se return (count > 0); } - - - // NOT IMPLEMENTED YET!!!!! - - - //Method Stubs to work out eventually... If needed? - - - - - public List GetLongestDuration() - { - string sql = $"select * from CodingSessions where "; - return LoadData(sql); - } - - - - // Method here using the following snippet - // using var connection = connectionFactory.CreateConnection(); - // connection.Open(); - // insert Dapper query here - } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs index 60995d434..258325d86 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs @@ -130,16 +130,16 @@ public ValidationResult ValidateStartTime(DateTime input) } public ValidationResult ValidateEndTime(DateTime input, DateTime startTime) { - var nextRecordStartTime = _repository.GetStartTimeOfNextRecord(input); + //var nextRecordStartTime = _repository.GetStartTimeOfNextRecord(input); if (_repository.ExistsWithinTimeFrame(input)) return ValidationResult.Fail("End Time", "A record already exists for this time"); else if (TimeOverlapsNextEntry(input, startTime)) - return ValidationResult.Fail("End Time", "A record already exists for this time"); + return ValidationResult.Fail("End Time", "This end time overlaps an existing session"); else if (input <= startTime) return ValidationResult.Fail("End Time", $"The end time must be later than {startTime.ToString("yyyy-MM-dd HH:mm:ss")}"); else if (input > DateTime.Now) - return ValidationResult.Fail("Start Time", "Cannot enter a future time"); + return ValidationResult.Fail("End Time", "Cannot enter a future time"); else return ValidationResult.Success(input); } @@ -147,10 +147,10 @@ public ValidationResult ValidateUpdatedStartTime(CodingSessionDataReco { if (newStartTime == DateTime.MinValue) return ValidationResult.Success(session.StartTime); - else if (_repository.ExistsWithinTimeFrameExcludingSessionById(session, newStartTime)) return ValidationResult.Fail("Start Time", "A record already exists for this time"); - + else if (newStartTime > DateTime.Now) + return ValidationResult.Fail("Start Time", "Cannot enter a future time"); else return ValidationResult.Success(newStartTime); } @@ -158,13 +158,18 @@ public ValidationResult ValidateUpdatedEndTime(CodingSessionDataRecord { if (newEndTime == DateTime.MinValue && session.EndTime > newStartTime) return ValidationResult.Success(session.EndTime); - else if (newEndTime <= newStartTime) - return ValidationResult.Fail("End Time", "End time cannot be earlier than start time."); + return ValidationResult.Fail("End Time", "End time must be later than start time."); else if (_repository.ExistsWithinTimeFrameExcludingSessionById(session, newEndTime)) return ValidationResult.Fail("End Time", "A record already exists for this time"); + else if (TimeOverlapsNextEntry(newEndTime, newStartTime, session.Id)) + return ValidationResult.Fail("End Time", "This end time overlaps an existing session"); + + + else if (newEndTime > DateTime.Now) + return ValidationResult.Fail("End Time", "Cannot enter a future time"); else return ValidationResult.Success(newEndTime); } @@ -183,12 +188,14 @@ public ValidationResult ValidateDateRangeStartTime(DateTime input) return ValidationResult.Success(input); } - private bool TimeOverlapsNextEntry(DateTime input, DateTime startTime) + private bool TimeOverlapsNextEntry(DateTime input, DateTime startTime, long id = -1) { var nextRecordStartTime = _repository.GetStartTimeOfNextRecord(startTime); if (nextRecordStartTime == null || nextRecordStartTime == DateTime.MinValue) return false; + else if (id >= 0) + else if (input >= nextRecordStartTime) return true; else diff --git a/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs b/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs index 5b6091d86..463ae99a0 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs @@ -16,13 +16,11 @@ public void WelcomeMessage() } public void ErrorMessage(string parameter, string message) { - AddNewLines(1); AnsiConsole.MarkupInterpolated($"[bold red]ERROR:[/] The value for {parameter} encountered the error: [yellow]{message}[/]"); AddNewLines(2); } public void ConfirmationMessage(string valueText) { - AddNewLines(1); AnsiConsole.MarkupInterpolated($"[bold green]ACCEPTED[/]: Value set to {valueText}"); AddNewLines(2); } @@ -69,9 +67,23 @@ public void PrintCodingSessionListAsTable(List sessions AnsiConsole.Write(grid); AddNewLines(2); } - public void PrintSingleCodingSession(CodingSessionDataRecord session) + public void PrintCodingSessionToUpdateById(CodingSessionDataRecord session, int rowId) { - Console.WriteLine($"{session.Id}:\t{session.StartTime} to {session.EndTime} for a duration of {session.Duration}"); + var grid = new Grid(); + grid.AddColumn(); + grid.AddColumn(); + grid.AddColumn(); + grid.AddColumn(); + + grid.AddRow(new string[] { $"[blue]{rowId}[/]", + $"{session.StartTime.ToString("yyyy-MM-dd")} [yellow]{session.StartTime.ToString("HH:mm:ss")}[/]", + $"{session.EndTime.ToString("yyyy-MM-dd")} [yellow]{session.EndTime.ToString("HH:mm:ss")}[/]", + $"{ConvertTimeFromSecondsToText(session.Duration)}" }); + + AnsiConsole.Write("Updating Record: "); + AnsiConsole.Write(grid); + + AddNewLines(1); } public void PrintReportDataAsTable(ReportModel report) { diff --git a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IConsoleOutputView.cs b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IConsoleOutputView.cs index 4dedc8a31..f51f2074d 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IConsoleOutputView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IConsoleOutputView.cs @@ -9,6 +9,6 @@ public interface IConsoleOutputView void ErrorMessage(string parameter, string message); void PrintCodingSessionListAsTable(List sessions); void PrintReportDataAsTable(ReportModel report); - void PrintSingleCodingSession(CodingSessionDataRecord session); + void PrintCodingSessionToUpdateById(CodingSessionDataRecord session, int rowId); void WelcomeMessage(); } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInputView.cs b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInputView.cs index bb7b2fd90..26f0cc28f 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInputView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInputView.cs @@ -5,7 +5,7 @@ public interface IUserInputView { int GetRecordIdFromUser(string action, int max); bool GetAddSessionConfirmationFromUser(CodingSession session); - DateTime GetTimeFromUser(string parameterName, string nullBehavior = "", bool allowNull = false); + DateTime GetTimeFromUser(string parameterName, bool allowNull = false); bool GetUpdateSessionConfirmationFromUser(CodingSessionDataRecord session, CodingSession updatedSession); bool GetDeleteSessionConfirmationFromUser(CodingSessionDataRecord session); string StartStopwatch(); diff --git a/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs b/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs index 505091a8a..8803e53e9 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs @@ -12,26 +12,37 @@ public UserInputView(string dateFormat) { _dateFormat = dateFormat; } - public DateTime GetTimeFromUser(string parameterName, string nullBehavior = "", bool allowNull = false) + public DateTime GetTimeFromUser(string parameterName, bool allowNull = false) { var timeInput = string.Empty; - var promptText = GenerateEnterDatePromptText(parameterName, nullBehavior, allowNull); - var errorMessageText = $"[bold red]ERROR:[/] The value you entered does not match the required format!"; + var promptText = GenerateEnterDatePromptText(parameterName, allowNull); + var errorMessageText = $"[bold red]ERROR:[/] The value you entered does not match the required format!\r\n"; - if (allowNull) timeInput = AnsiConsole.Prompt( + if (allowNull) + { + timeInput = AnsiConsole.Prompt( + new TextPrompt(promptText) .AllowEmpty() .ValidationErrorMessage(errorMessageText) .Validate(input => { - if (DateTime.TryParseExact(input, _dateFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out _)) + if (String.IsNullOrWhiteSpace(input)) { return ValidationResult.Success(); } - return ValidationResult.Error(); + else if (DateTime.TryParseExact(input, _dateFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out _)) + { + return ValidationResult.Success(); + } + else + return ValidationResult.Error(); })); + if (timeInput == "") timeInput = DateTime.MinValue.ToString(_dateFormat); + } + else timeInput = AnsiConsole.Prompt( new TextPrompt(promptText) .ValidationErrorMessage(errorMessageText) @@ -163,14 +174,17 @@ private string GenerateUpdateSessionConfirmationPrompt(CodingSessionDataRecord s return prompt; } - private string GenerateEnterDatePromptText(string parameterName, string nullBehavior, bool allowNull) + private string GenerateEnterDatePromptText(string parameterName, bool allowNull) { string article = GenerateArticleForDatePromptText(parameterName); string promptText = $"Please enter {article} {parameterName} using the format [yellow]'yyyy-MM-dd HH:mm:ss'[/]"; if (allowNull) { - promptText += $".\r\nPress enter with blank line to use {nullBehavior}:"; + if (parameterName == "new start time") + promptText += $".\r\nOr, leave blank and press ENTER to keep the existing start time:"; + else if (parameterName == "new end time") + promptText += $".\r\nOr, leave blank and press ENTER to keep the existing end time:"; } else { From fb706374cfbf2e8343b3f8a99b62a5ff73e7d5a5 Mon Sep 17 00:00:00 2001 From: jzhartman Date: Mon, 20 Oct 2025 23:44:18 -0400 Subject: [PATCH 38/57] Finalized validation for Update section Next: Check validation for delete --- .../Interfaces/ICodingSessionRepository.cs | 1 + .../Repositories/CodingSessionRepository.cs | 16 ++++++++++++++++ .../CodingSessionDataService.cs | 14 ++++++++++++-- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs index b01bd71d9..2bd2c7123 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs @@ -11,4 +11,5 @@ public interface ICodingSessionRepository bool ExistsWithinTimeFrame(DateTime time); bool ExistsWithinTimeFrameExcludingSessionById(CodingSessionDataRecord session, DateTime newTime); DateTime GetStartTimeOfNextRecord(DateTime time); + DateTime GetStartTimeOfNextRecordExcludingCurrentSession(DateTime time, long id); } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs index 09ca95eb7..1daf6d1e0 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs @@ -93,6 +93,22 @@ order by StartTime return LoadData(sql, parameter).FirstOrDefault(); ; } + + public DateTime GetStartTimeOfNextRecordExcludingCurrentSession(DateTime time, long id) + { + var parameter = new TimeUpdate { Time = time, Id = (int)id }; + using var connection = _connectionFactory.CreateConnection(); + + string sql = @"select StartTime from CodingSessions + where StartTime > @Time + and Id != @Id + order by StartTime + limit 1"; + + return LoadData(sql, parameter).FirstOrDefault(); + ; + } + public bool ExistsWithinTimeFrame(DateTime time) { var parameter = new DateValue { Time = time}; diff --git a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs index 258325d86..9ab7dadaa 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs @@ -188,14 +188,24 @@ public ValidationResult ValidateDateRangeStartTime(DateTime input) return ValidationResult.Success(input); } - private bool TimeOverlapsNextEntry(DateTime input, DateTime startTime, long id = -1) + private bool TimeOverlapsNextEntry(DateTime input, DateTime startTime) { var nextRecordStartTime = _repository.GetStartTimeOfNextRecord(startTime); if (nextRecordStartTime == null || nextRecordStartTime == DateTime.MinValue) return false; - else if (id >= 0) + else if (input >= nextRecordStartTime) + return true; + else + return false; + } + private bool TimeOverlapsNextEntry(DateTime input, DateTime startTime, long sessionId) + { + var nextRecordStartTime = _repository.GetStartTimeOfNextRecordExcludingCurrentSession(startTime, sessionId); + + if (nextRecordStartTime == null || nextRecordStartTime == DateTime.MinValue) + return false; else if (input >= nextRecordStartTime) return true; else From c0d63a804c07ebb36ec04aa47e74743465570c72 Mon Sep 17 00:00:00 2001 From: jzhartman Date: Mon, 20 Oct 2025 23:47:37 -0400 Subject: [PATCH 39/57] Formatting/organizational commit --- .../EntryListController.cs | 4 -- .../Repositories/CodingSessionRepository.cs | 46 +++++++++---------- .../CodingSessionDataService.cs | 7 --- 3 files changed, 21 insertions(+), 36 deletions(-) diff --git a/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs b/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs index 69da7e765..5bd7b9738 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs @@ -95,10 +95,6 @@ private void ManageSessionUpdate(List sessions) UpdateSession(updatedSession, sessions[recordId].Id); else _outputView.ActionCancelledMessage("update"); - - // get confirmation - // if confirmed => _repository.UpdateSession(updatedsession); - // else => cancelled update message -- return to menu } private void UpdateSession(CodingSession session, long id) { diff --git a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs index 1daf6d1e0..92fa22812 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs @@ -37,7 +37,6 @@ private void SaveData(string sql, T parameters) } - public List GetAll() { string sql = "Select * from CodingSessions order by StartTime"; @@ -59,27 +58,6 @@ public int GetRecordCount() string sql = "select count(*) from CodingSessions"; return LoadData(sql).First(); } - - - - public void AddSession(CodingSession session) - { - string sql = "insert into CodingSessions (StartTime, EndTime, Duration) values (@StartTime, @EndTime, @Duration)"; - SaveData(sql, session); - } - - public void UpdateSession(CodingSessionDataRecord session) - { - string sql = "update CodingSessions Set StartTime = @StartTime, EndTime = @EndTime, Duration = @Duration where Id = @Id"; - SaveData(sql, session); - } - public void DeleteById(int id) - { - string sql = $"delete from CodingSessions where Id = {id}"; - SaveData(sql); - } - - public DateTime GetStartTimeOfNextRecord(DateTime time) { var parameter = new DateValue { Time = time }; @@ -92,8 +70,6 @@ order by StartTime return LoadData(sql, parameter).FirstOrDefault(); ; } - - public DateTime GetStartTimeOfNextRecordExcludingCurrentSession(DateTime time, long id) { var parameter = new TimeUpdate { Time = time, Id = (int)id }; @@ -108,7 +84,6 @@ order by StartTime return LoadData(sql, parameter).FirstOrDefault(); ; } - public bool ExistsWithinTimeFrame(DateTime time) { var parameter = new DateValue { Time = time}; @@ -136,4 +111,25 @@ public bool ExistsWithinTimeFrameExcludingSessionById(CodingSessionDataRecord se return (count > 0); } + + + public void AddSession(CodingSession session) + { + string sql = "insert into CodingSessions (StartTime, EndTime, Duration) values (@StartTime, @EndTime, @Duration)"; + SaveData(sql, session); + } + public void UpdateSession(CodingSessionDataRecord session) + { + string sql = "update CodingSessions Set StartTime = @StartTime, EndTime = @EndTime, Duration = @Duration where Id = @Id"; + SaveData(sql, session); + } + public void DeleteById(int id) + { + string sql = $"delete from CodingSessions where Id = {id}"; + SaveData(sql); + } + + + + } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs index 9ab7dadaa..4882856eb 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs @@ -130,8 +130,6 @@ public ValidationResult ValidateStartTime(DateTime input) } public ValidationResult ValidateEndTime(DateTime input, DateTime startTime) { - //var nextRecordStartTime = _repository.GetStartTimeOfNextRecord(input); - if (_repository.ExistsWithinTimeFrame(input)) return ValidationResult.Fail("End Time", "A record already exists for this time"); else if (TimeOverlapsNextEntry(input, startTime)) @@ -160,14 +158,10 @@ public ValidationResult ValidateUpdatedEndTime(CodingSessionDataRecord return ValidationResult.Success(session.EndTime); else if (newEndTime <= newStartTime) return ValidationResult.Fail("End Time", "End time must be later than start time."); - else if (_repository.ExistsWithinTimeFrameExcludingSessionById(session, newEndTime)) return ValidationResult.Fail("End Time", "A record already exists for this time"); - else if (TimeOverlapsNextEntry(newEndTime, newStartTime, session.Id)) return ValidationResult.Fail("End Time", "This end time overlaps an existing session"); - - else if (newEndTime > DateTime.Now) return ValidationResult.Fail("End Time", "Cannot enter a future time"); else @@ -199,7 +193,6 @@ private bool TimeOverlapsNextEntry(DateTime input, DateTime startTime) else return false; } - private bool TimeOverlapsNextEntry(DateTime input, DateTime startTime, long sessionId) { var nextRecordStartTime = _repository.GetStartTimeOfNextRecordExcludingCurrentSession(startTime, sessionId); From a532ea0e803a61ee0ca1acdbe53dd97f90e15214 Mon Sep 17 00:00:00 2001 From: jzhartman Date: Wed, 22 Oct 2025 22:10:56 -0400 Subject: [PATCH 40/57] Completed validation for Reports generator --- .../Interfaces/ICodingSessionRepository.cs | 1 + .../Repositories/CodingSessionRepository.cs | 10 ++++++++++ .../CodingTracker.Services/CodingSessionDataService.cs | 2 ++ 3 files changed, 13 insertions(+) diff --git a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs index 2bd2c7123..95f7577ee 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs @@ -12,4 +12,5 @@ public interface ICodingSessionRepository bool ExistsWithinTimeFrameExcludingSessionById(CodingSessionDataRecord session, DateTime newTime); DateTime GetStartTimeOfNextRecord(DateTime time); DateTime GetStartTimeOfNextRecordExcludingCurrentSession(DateTime time, long id); + DateTime GetStartTimeOfLastRecord(); } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs index 92fa22812..79b3c4557 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs @@ -84,6 +84,16 @@ order by StartTime return LoadData(sql, parameter).FirstOrDefault(); ; } + public DateTime GetStartTimeOfLastRecord() + { + using var connection = _connectionFactory.CreateConnection(); + + string sql = @"select StartTime from CodingSessions + order by StartTime DESC + limit 1"; + + return LoadData(sql).FirstOrDefault(); + } public bool ExistsWithinTimeFrame(DateTime time) { var parameter = new DateValue { Time = time}; diff --git a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs index 4882856eb..6fe8b58c5 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs @@ -178,6 +178,8 @@ public ValidationResult ValidateDateRangeStartTime(DateTime input) { if (input > DateTime.Now) return ValidationResult.Fail("Start Time", "Cannot enter a future time"); + else if (input > _repository.GetStartTimeOfLastRecord()) + return ValidationResult.Fail("Start Time", "There are no records within this time period."); else return ValidationResult.Success(input); } From 8e23dbb67f457eb0e2eef207aa71bf175e110686 Mon Sep 17 00:00:00 2001 From: jzhartman Date: Tue, 28 Oct 2025 22:10:56 -0400 Subject: [PATCH 41/57] Separated Repo class into base class of generics and two derived classes for CodingSessions and Goals Reworked DB initi class to accomodate new Goals table --- .../CodingTracker.Data/DatabaseInitializer.cs | 63 +++++++++++++------ .../Interfaces/IDatabaseInitializer.cs | 2 +- .../Repositories/CodingSessionRepository.cs | 34 +--------- .../Repositories/GoalRepository.cs | 15 +++++ .../Repositories/RepositoryGenerics.cs | 41 ++++++++++++ .../CodingTracker/Program.cs | 2 +- 6 files changed, 103 insertions(+), 54 deletions(-) create mode 100644 codingTracker.jzhartman/CodingTracker.Data/Repositories/GoalRepository.cs create mode 100644 codingTracker.jzhartman/CodingTracker.Data/Repositories/RepositoryGenerics.cs diff --git a/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs b/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs index 6135102bf..267e3831c 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs @@ -1,6 +1,7 @@ using CodingTracker.Data.Interfaces; using Dapper; using Microsoft.Data.Sqlite; +using static System.Runtime.InteropServices.JavaScript.JSType; namespace CodingTracker.Data; public class DatabaseInitializer : IDatabaseInitializer @@ -12,64 +13,86 @@ public DatabaseInitializer(ISqliteConnectionFactory connectionfactory) _connectionFactory = connectionfactory; } - public void Initialize() + public void Run() { - if (TableExists() == false) + InitializeTable("CodingSessions"); + InitializeTable("Goals"); + } + + private void InitializeTable(string tableName) + { + if (TableExists(tableName) == false) { - CreateTable(); - SeedData(); + CreateTable(tableName); + SeedData(tableName); } } - private bool TableExists() + private bool TableExists(string tableName) { using var connection = _connectionFactory.CreateConnection(); - int count = connection.ExecuteScalar("select count(*) from sqlite_master where type='table' and name='CodingSessions'"); + int count = connection.ExecuteScalar($"select count(*) from sqlite_master where type='table' and name='{tableName}'"); return count == 1; } - private void CreateTable() + private void CreateTable(string tableName) { + string parameters = string.Empty; + + if (tableName == "CodingSessions") + parameters = @"Id integer primary key not null, + StartTime text not null, + EndTime text not null, + Duration integer not null"; + + if (tableName == "Goals") + parameters = @"Id integer primmary key not null, + Type text not null, + StartTime text not null, + EndTime text not null, + Status text not null"; + using var connection = _connectionFactory.CreateConnection(); var command = connection.CreateCommand(); - command.CommandText = @"create table if not exists CodingSessions( - Id integer primary key not null, - StartTime text not null, - EndTime text not null, - Duration integer not null - )"; + command.CommandText = $"create table if not exists {tableName}({parameters})"; command.ExecuteNonQuery(); } - private void SeedData() + private void SeedData(string tableName) { using var connection = _connectionFactory.CreateConnection(); var command = connection.CreateCommand(); - string sql = CreateSqlString(); + string sql = string.Empty; + + if (tableName == "CodingSessions") + sql = CreateCodingSessionsSqlString(); command.CommandText = sql; command.ExecuteNonQuery(); } - private string CreateSqlString() + private string CreateCodingSessionsSqlString() { string sql = "insert into CodingSessions(StartTime, EndTime, Duration)\nValues\n"; - Random rand = new Random(); + int seedRecordCount = 100; + + DateOnly date = DateOnly.FromDateTime(DateTime.Now.AddDays(-5 * (seedRecordCount+1))); + TimeOnly time = new TimeOnly(21,0,0); + DateTime startDate = date.ToDateTime(time); - DateTime startDate = DateTime.Parse("2025-01-01 21:00:00"); DateTime endDate = startDate.AddHours(2); TimeSpan duration = endDate - startDate; - for (int i = 0; i < 50; i++) + for (int i = 0; i < seedRecordCount; i++) { if (i != 0) sql += ",\n"; sql += $"('{startDate.ToString("yyyy-MM-dd HH:mm:ss")}', '{endDate.ToString("yyyy-MM-dd HH:mm:ss")}', {duration.TotalSeconds})"; - startDate = startDate.AddDays(1); + startDate = startDate.AddDays(rand.Next(1, 6)); endDate = startDate.AddHours(rand.Next(1, 3)).AddMinutes(rand.Next(0, 60)).AddSeconds(rand.Next(0, 60)); duration = endDate - startDate; } diff --git a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/IDatabaseInitializer.cs b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/IDatabaseInitializer.cs index 0b164e0b7..4bcf7ad6f 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/IDatabaseInitializer.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/IDatabaseInitializer.cs @@ -1,5 +1,5 @@ namespace CodingTracker.Data.Interfaces; public interface IDatabaseInitializer { - void Initialize(); + void Run(); } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs index 79b3c4557..63af52a63 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs @@ -4,39 +4,9 @@ using Dapper; namespace CodingTracker.Data.Repositories; -public class CodingSessionRepository : ICodingSessionRepository +public class CodingSessionRepository : RepositoryGenerics, ICodingSessionRepository { - private readonly ISqliteConnectionFactory _connectionFactory; - - public CodingSessionRepository(ISqliteConnectionFactory connectionFactory) - { - _connectionFactory = connectionFactory; - } - - private List LoadData(string sql) - { - using var connection = _connectionFactory.CreateConnection(); - List sessions = connection.Query(sql).ToList(); - return sessions; - } - private List LoadData(string sql, U parameters) - { - using var connection = _connectionFactory.CreateConnection(); - List sessions = connection.Query(sql, parameters).ToList(); - return sessions; - } - private void SaveData(string sql) - { - using var connection = _connectionFactory.CreateConnection(); - connection.Execute(sql); - } - private void SaveData(string sql, T parameters) - { - using var connection = _connectionFactory.CreateConnection(); - connection.Execute(sql, parameters); - } - - + public CodingSessionRepository(ISqliteConnectionFactory connectionFactory) : base(connectionFactory) {} public List GetAll() { string sql = "Select * from CodingSessions order by StartTime"; diff --git a/codingTracker.jzhartman/CodingTracker.Data/Repositories/GoalRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Repositories/GoalRepository.cs new file mode 100644 index 000000000..2bb16602d --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Data/Repositories/GoalRepository.cs @@ -0,0 +1,15 @@ +using CodingTracker.Data.Interfaces; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CodingTracker.Data.Repositories; +public class GoalRepository : RepositoryGenerics +{ + public GoalRepository(ISqliteConnectionFactory connectionFactory) : base(connectionFactory) { } + + + +} diff --git a/codingTracker.jzhartman/CodingTracker.Data/Repositories/RepositoryGenerics.cs b/codingTracker.jzhartman/CodingTracker.Data/Repositories/RepositoryGenerics.cs new file mode 100644 index 000000000..d5ae50aa8 --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Data/Repositories/RepositoryGenerics.cs @@ -0,0 +1,41 @@ +using CodingTracker.Data.Interfaces; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Dapper; + +namespace CodingTracker.Data.Repositories; +public class RepositoryGenerics +{ + protected internal readonly ISqliteConnectionFactory _connectionFactory; + + public RepositoryGenerics(ISqliteConnectionFactory connectionFactory) + { + _connectionFactory = connectionFactory; + } + + protected internal List LoadData(string sql) + { + using var connection = _connectionFactory.CreateConnection(); + List sessions = connection.Query(sql).ToList(); + return sessions; + } + protected internal List LoadData(string sql, U parameters) + { + using var connection = _connectionFactory.CreateConnection(); + List sessions = connection.Query(sql, parameters).ToList(); + return sessions; + } + protected internal void SaveData(string sql) + { + using var connection = _connectionFactory.CreateConnection(); + connection.Execute(sql); + } + protected internal void SaveData(string sql, T parameters) + { + using var connection = _connectionFactory.CreateConnection(); + connection.Execute(sql, parameters); + } +} diff --git a/codingTracker.jzhartman/CodingTracker/Program.cs b/codingTracker.jzhartman/CodingTracker/Program.cs index f7c53c558..8daa3c78a 100644 --- a/codingTracker.jzhartman/CodingTracker/Program.cs +++ b/codingTracker.jzhartman/CodingTracker/Program.cs @@ -10,7 +10,7 @@ static void Main(string[] args) { Batteries.Init(); var serviceProvider = Startup.ConfigureServices(); - serviceProvider.GetRequiredService().Initialize(); + serviceProvider.GetRequiredService().Run(); serviceProvider.GetRequiredService().Run(); } From 84e6b90225ba0d51b5763bdf03f1ad87c536c250 Mon Sep 17 00:00:00 2001 From: jzhartman Date: Mon, 3 Nov 2025 23:00:20 -0500 Subject: [PATCH 42/57] Refactored repo class Added beginning stages for goals implementation --- .../GoalsController.cs | 22 +++++++++++-- .../Interfaces/IGoalRepository.cs | 9 +++++ .../Repositories/GoalRepository.cs | 25 +++++++++++++- .../TypeHandlers/GoalStatusHandler.cs | 28 ++++++++++++++++ .../TypeHandlers/GoalTypeHandler.cs | 28 ++++++++++++++++ .../CodingTracker.Models/Entities/Enums.cs | 22 +++++++++++++ .../CodingTracker.Models/Entities/GoalDTO.cs | 15 +++++++++ .../Entities/GoalModel.cs | 5 ++- .../CodingTracker.Services/GoalDataService.cs | 33 +++++++++++++++++++ .../Interfaces/IGoalDataService.cs | 9 +++++ .../CodingTracker.Views/ConsoleOutputView.cs | 33 +++++++++++++++++++ .../Interfaces/IConsoleOutputView.cs | 1 + .../CodingTracker/Startup.cs | 7 +++- 13 files changed, 229 insertions(+), 8 deletions(-) create mode 100644 codingTracker.jzhartman/CodingTracker.Data/Interfaces/IGoalRepository.cs create mode 100644 codingTracker.jzhartman/CodingTracker.Data/TypeHandlers/GoalStatusHandler.cs create mode 100644 codingTracker.jzhartman/CodingTracker.Data/TypeHandlers/GoalTypeHandler.cs create mode 100644 codingTracker.jzhartman/CodingTracker.Models/Entities/Enums.cs create mode 100644 codingTracker.jzhartman/CodingTracker.Models/Entities/GoalDTO.cs create mode 100644 codingTracker.jzhartman/CodingTracker.Services/GoalDataService.cs create mode 100644 codingTracker.jzhartman/CodingTracker.Services/Interfaces/IGoalDataService.cs diff --git a/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs b/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs index 7db34e7a3..0ce44239e 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs @@ -1,22 +1,38 @@ using CodingTracker.Controller.Interfaces; +using CodingTracker.Models.Entities; using CodingTracker.Services.Interfaces; using CodingTracker.Views.Interfaces; namespace CodingTracker.Controller; public class GoalsController : IGoalsController { - private readonly ICodingSessionDataService _service; + private readonly IGoalDataService _service; private readonly IMenuView _menuView; + private readonly IConsoleOutputView _outputView; - public GoalsController(ICodingSessionDataService service, IMenuView menuView) + public GoalsController(IGoalDataService service, + IMenuView menuView, IConsoleOutputView outputView) { _service = service; _menuView = menuView; + _outputView = outputView; } public void Run() { - Console.WriteLine("Not Implemented yet...."); + GoalModel goal = new GoalModel { + StartTime = DateTime.Now, + EndTime = DateTime.Now.AddHours(2).AddMinutes(30), + Status = GoalStatus.InProgress, + Type = GoalType.TotalTime}; + + + _service.AddGoal(goal); + + var goals = _service.GetAllGoalsByStatus(GoalStatus.InProgress); + + _outputView.PrintGoalListAsTable(goals); + Console.ReadKey(); } } diff --git a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/IGoalRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/IGoalRepository.cs new file mode 100644 index 000000000..fb1849ef5 --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/IGoalRepository.cs @@ -0,0 +1,9 @@ +using CodingTracker.Models.Entities; + +namespace CodingTracker.Data.Interfaces; +public interface IGoalRepository +{ + void AddGoal(GoalModel goal); + List GetAllGoalsByStatus(GoalStatus status); + void UpdateGoal(GoalDTO goal); +} \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Data/Repositories/GoalRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Repositories/GoalRepository.cs index 2bb16602d..175aeb040 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Repositories/GoalRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Repositories/GoalRepository.cs @@ -1,4 +1,5 @@ using CodingTracker.Data.Interfaces; +using CodingTracker.Models.Entities; using System; using System.Collections.Generic; using System.Linq; @@ -6,10 +7,32 @@ using System.Threading.Tasks; namespace CodingTracker.Data.Repositories; -public class GoalRepository : RepositoryGenerics +public class GoalRepository : RepositoryGenerics, IGoalRepository { public GoalRepository(ISqliteConnectionFactory connectionFactory) : base(connectionFactory) { } + // TODO: Setup the Goals repository + // TODO: Create Goal object with relevant members + // TODO: Create Goal input + // TODO: Create Goal output to console + // TODO: Determine how goal status will be determined + public List GetAllGoalsByStatus(GoalStatus status) + { + string sql = $"select * from Goals where Status = {status}"; + return LoadData(sql); + } + + public void AddGoal(GoalModel goal) + { + string sql = "insert into Goals (StartTime, EndTime, Type, Status) values (@StartTime, @EndTime, @Type, @Status)"; + SaveData(sql, goal); + } + + public void UpdateGoal(GoalDTO goal) + { + string sql = "update Goals set StartTime = @StartTime, EndTime = @EndTime, Type = @Type, Status = @Status where Id = @Id"; + SaveData(sql, goal); + } } diff --git a/codingTracker.jzhartman/CodingTracker.Data/TypeHandlers/GoalStatusHandler.cs b/codingTracker.jzhartman/CodingTracker.Data/TypeHandlers/GoalStatusHandler.cs new file mode 100644 index 000000000..4d0397634 --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Data/TypeHandlers/GoalStatusHandler.cs @@ -0,0 +1,28 @@ +using CodingTracker.Models.Entities; +using Dapper; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CodingTracker.Data.TypeHandlers; +public class GoalStatusHandler : SqlMapper.TypeHandler +{ + public override GoalStatus Parse(object value) + { + if (value == null || value is DBNull) + { + return default(GoalStatus); + } + + return (GoalStatus)Enum.Parse(typeof(GoalStatus), value.ToString()); + } + + public override void SetValue(IDbDataParameter parameter, GoalStatus value) + { + parameter.DbType = DbType.String; + parameter.Value = value.ToString(); + } +} diff --git a/codingTracker.jzhartman/CodingTracker.Data/TypeHandlers/GoalTypeHandler.cs b/codingTracker.jzhartman/CodingTracker.Data/TypeHandlers/GoalTypeHandler.cs new file mode 100644 index 000000000..68d8e54ac --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Data/TypeHandlers/GoalTypeHandler.cs @@ -0,0 +1,28 @@ +using CodingTracker.Models.Entities; +using Dapper; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CodingTracker.Data.TypeHandlers; +public class GoalTypeHandler : SqlMapper.TypeHandler +{ + public override GoalType Parse(object value) + { + if (value == null || value is DBNull) + { + return default(GoalType); + } + + return (GoalType)Enum.Parse(typeof(GoalType), value.ToString()); + } + + public override void SetValue(IDbDataParameter parameter, GoalType value) + { + parameter.DbType = DbType.String; + parameter.Value = value.ToString(); + } +} diff --git a/codingTracker.jzhartman/CodingTracker.Models/Entities/Enums.cs b/codingTracker.jzhartman/CodingTracker.Models/Entities/Enums.cs new file mode 100644 index 000000000..cd72b0df9 --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Models/Entities/Enums.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CodingTracker.Models.Entities; + +public enum GoalType +{ + TotalTime, + AverageTime, + DaysPerPeriod +} + +public enum GoalStatus +{ + InProgress, + Complete, + Failed, + Abandoned +} \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalDTO.cs b/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalDTO.cs new file mode 100644 index 000000000..01c88ffc6 --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalDTO.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CodingTracker.Models.Entities; +public class GoalDTO +{ + public int Id { get; set; } + public GoalType Type { get; set; } + public DateTime StartTime { get; set; } + public DateTime EndTime { get; set; } + public GoalStatus Status { get; set; } +} diff --git a/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalModel.cs b/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalModel.cs index 3b098bb78..782d88eaf 100644 --- a/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalModel.cs +++ b/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalModel.cs @@ -7,9 +7,8 @@ namespace CodingTracker.Models.Entities; public class GoalModel { - public bool IsNumberOfDays { get; set; } - public bool IsTotalTime { get; set; } + public GoalType Type { get; set; } public DateTime StartTime { get; set; } public DateTime EndTime { get; set; } - + public GoalStatus Status { get; set; } } diff --git a/codingTracker.jzhartman/CodingTracker.Services/GoalDataService.cs b/codingTracker.jzhartman/CodingTracker.Services/GoalDataService.cs new file mode 100644 index 000000000..d087712b0 --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Services/GoalDataService.cs @@ -0,0 +1,33 @@ +using CodingTracker.Data.Interfaces; +using CodingTracker.Models.Entities; +using CodingTracker.Services.Interfaces; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CodingTracker.Services; +public class GoalDataService : IGoalDataService +{ + public readonly IGoalRepository _repository; + + public GoalDataService(IGoalRepository repository) + { + _repository = repository; + } + + public void AddGoal(GoalModel goal) + { + _repository.AddGoal(goal); + } + public void UpdateGoal(GoalDTO goal) + { + _repository.UpdateGoal(goal); + } + public List GetAllGoalsByStatus(GoalStatus status) + { + return _repository.GetAllGoalsByStatus(status); + } + +} diff --git a/codingTracker.jzhartman/CodingTracker.Services/Interfaces/IGoalDataService.cs b/codingTracker.jzhartman/CodingTracker.Services/Interfaces/IGoalDataService.cs new file mode 100644 index 000000000..989944807 --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Services/Interfaces/IGoalDataService.cs @@ -0,0 +1,9 @@ +using CodingTracker.Models.Entities; + +namespace CodingTracker.Services.Interfaces; +public interface IGoalDataService +{ + void AddGoal(GoalModel goal); + List GetAllGoalsByStatus(GoalStatus status); + void UpdateGoal(GoalDTO goal); +} \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs b/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs index 463ae99a0..c58199886 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs @@ -108,6 +108,39 @@ public void PrintReportDataAsTable(ReportModel report) + public void PrintGoalListAsTable(List goals) + { + int count = 1; + var grid = new Grid(); + grid.AddColumn(); + grid.AddColumn(); + grid.AddColumn(); + grid.AddColumn(); + grid.AddRow(new Text[] {new Text("Id").Centered(), + new Text("Start Time").Centered(), + new Text("End Time").Centered(), + new Text("Status").Centered()}); + + foreach (var goal in goals) + { + grid.AddRow(new string[] { $"[blue]{count}[/]", + $"{goal.StartTime.ToString("yyyy-MM-dd")} [yellow]{goal.StartTime.ToString("HH:mm:ss")}[/]", + $"{goal.EndTime.ToString("yyyy-MM-dd")} [yellow]{goal.EndTime.ToString("HH:mm:ss")}[/]", + $"{goal.Status}", + $"{goal.Type}"}); + count++; + } + + AnsiConsole.Write(grid); + AddNewLines(2); + } + + + + + + + private void AddNewLines(int lines) { for (int i = 0; i < lines; i++) diff --git a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IConsoleOutputView.cs b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IConsoleOutputView.cs index f51f2074d..c781a73fa 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IConsoleOutputView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IConsoleOutputView.cs @@ -11,4 +11,5 @@ public interface IConsoleOutputView void PrintReportDataAsTable(ReportModel report); void PrintCodingSessionToUpdateById(CodingSessionDataRecord session, int rowId); void WelcomeMessage(); + void PrintGoalListAsTable(List goals); } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker/Startup.cs b/codingTracker.jzhartman/CodingTracker/Startup.cs index 3fea24486..d2c62cdac 100644 --- a/codingTracker.jzhartman/CodingTracker/Startup.cs +++ b/codingTracker.jzhartman/CodingTracker/Startup.cs @@ -23,6 +23,9 @@ public static IServiceProvider ConfigureServices() SqlMapper.RemoveTypeMap(typeof(DateTime)); SqlMapper.RemoveTypeMap(typeof(DateTime?)); SqlMapper.AddTypeHandler(new DateTimeHandler(dateTimeFormat)); + SqlMapper.AddTypeHandler(new GoalTypeHandler()); + SqlMapper.AddTypeHandler(new GoalStatusHandler()); + var services = new ServiceCollection(); @@ -35,13 +38,15 @@ public static IServiceProvider ConfigureServices() //Register All Services services.AddSingleton(); + services.AddSingleton(); //Resgister All Views services.AddSingleton(); services.AddSingleton(); services.AddSingleton(new UserInputView(dateTimeFormat)); - + //Register repos and data items + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(provider => new SqliteConnectionFactory(connectionString)); services.AddSingleton(); From be6f8de275cb2c9bb9a6f8e82aea6439a9b5481f Mon Sep 17 00:00:00 2001 From: jzhartman Date: Mon, 3 Nov 2025 23:15:23 -0500 Subject: [PATCH 43/57] Basic Goals Create/Read from DB --- .../CodingTracker.Data/DatabaseInitializer.cs | 2 +- .../CodingTracker.Data/Parameters/GoalStatusQuery.cs | 12 ++++++++++++ .../Repositories/GoalRepository.cs | 7 +++++-- .../CodingTracker.Views/ConsoleOutputView.cs | 4 +++- 4 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 codingTracker.jzhartman/CodingTracker.Data/Parameters/GoalStatusQuery.cs diff --git a/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs b/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs index 267e3831c..5fcc85d52 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs @@ -46,7 +46,7 @@ private void CreateTable(string tableName) Duration integer not null"; if (tableName == "Goals") - parameters = @"Id integer primmary key not null, + parameters = @"Id integer primary key not null, Type text not null, StartTime text not null, EndTime text not null, diff --git a/codingTracker.jzhartman/CodingTracker.Data/Parameters/GoalStatusQuery.cs b/codingTracker.jzhartman/CodingTracker.Data/Parameters/GoalStatusQuery.cs new file mode 100644 index 000000000..64df89802 --- /dev/null +++ b/codingTracker.jzhartman/CodingTracker.Data/Parameters/GoalStatusQuery.cs @@ -0,0 +1,12 @@ +using CodingTracker.Models.Entities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CodingTracker.Data.Parameters; +public class GoalStatusQuery +{ + public GoalStatus Status { get; set; } +} diff --git a/codingTracker.jzhartman/CodingTracker.Data/Repositories/GoalRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Repositories/GoalRepository.cs index 175aeb040..94735d850 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Repositories/GoalRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Repositories/GoalRepository.cs @@ -1,4 +1,6 @@ using CodingTracker.Data.Interfaces; +using CodingTracker.Data.Parameters; +using CodingTracker.Data.TypeHandlers; using CodingTracker.Models.Entities; using System; using System.Collections.Generic; @@ -19,8 +21,9 @@ public GoalRepository(ISqliteConnectionFactory connectionFactory) : base(connect public List GetAllGoalsByStatus(GoalStatus status) { - string sql = $"select * from Goals where Status = {status}"; - return LoadData(sql); + var goalStatus = new GoalStatusQuery {Status = status }; + string sql = $"select * from Goals where Status = @Status"; + return LoadData(sql, goalStatus); } public void AddGoal(GoalModel goal) diff --git a/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs b/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs index c58199886..1dbe524fc 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs @@ -116,10 +116,12 @@ public void PrintGoalListAsTable(List goals) grid.AddColumn(); grid.AddColumn(); grid.AddColumn(); + grid.AddColumn(); grid.AddRow(new Text[] {new Text("Id").Centered(), new Text("Start Time").Centered(), new Text("End Time").Centered(), - new Text("Status").Centered()}); + new Text("Status").Centered(), + new Text("Type").Centered()}); foreach (var goal in goals) { From a59bb30ab688bd3ef92a6128782fad02fcfa6502 Mon Sep 17 00:00:00 2001 From: jzhartman Date: Mon, 10 Nov 2025 21:56:57 -0500 Subject: [PATCH 44/57] Still basic implementation of Goals --- .../GoalsController.cs | 85 ++++++++++++++++--- .../ReportsController.cs | 7 +- .../Interfaces/IGoalRepository.cs | 2 + .../Repositories/GoalRepository.cs | 12 +++ .../TypeHandlers/GoalTypeHandler.cs | 9 +- .../CodingTracker.Services/GoalDataService.cs | 10 ++- .../Interfaces/IGoalDataService.cs | 2 + .../CodingTracker.Views/ConsoleOutputView.cs | 9 +- .../Interfaces/IConsoleOutputView.cs | 1 + .../Interfaces/IMenuView.cs | 1 + .../CodingTracker.Views/MenuView.cs | 15 ++++ 11 files changed, 127 insertions(+), 26 deletions(-) diff --git a/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs b/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs index 0ce44239e..166283260 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs @@ -6,33 +6,98 @@ namespace CodingTracker.Controller; public class GoalsController : IGoalsController { - private readonly IGoalDataService _service; + private readonly IGoalDataService _goalService; + private readonly ICodingSessionDataService _codingSessionService; private readonly IMenuView _menuView; private readonly IConsoleOutputView _outputView; - public GoalsController(IGoalDataService service, + public GoalsController( IGoalDataService goalService, ICodingSessionDataService codingSessionService, IMenuView menuView, IConsoleOutputView outputView) { - _service = service; + _goalService = goalService; + _codingSessionService = codingSessionService; _menuView = menuView; _outputView = outputView; } public void Run() { - GoalModel goal = new GoalModel { + //GenerateDummyGoal(); + + bool returnToMainMenu = false; + + while (!returnToMainMenu) + { + _outputView.WelcomeMessage(); + PrintAllActiveGoals(); + + var goalsInProgress = GetAllGoalsInProgress(); + EvaluateAllGoalsInProgress(goalsInProgress); + + var selection = _menuView.PrintGoalOptionsAndGetSelection(); + + if (selection == "Return to Previous Menu") { returnToMainMenu = true; continue; } + + + + + } + } + + private void PrintAllActiveGoals() + { + var goals = _goalService.GetAllGoalsByStatus(GoalStatus.InProgress); + + if (goals.Count <= 0) + _outputView.NoRecordsMessage("Goals"); + else + _outputView.PrintGoalListAsTable(goals); + } + + private List GetAllGoalsInProgress() + { + return _goalService.GetAllGoalsByStatus(GoalStatus.InProgress); + } + private List GetAllGoals() + { + return _goalService.GetAllGoals(); + } + + private void EvaluateAllGoalsInProgress(List goals) + { + var codingSessionsLists = new List>(); + + foreach (var goal in goals) + { + codingSessionsLists.Add(_codingSessionService.GetSessionListByDateRange(goal.StartTime, goal.EndTime)); + } + } + + private void EvaluateGoal(GoalModel goal) + { + + } + + + private void GenerateDummyGoal() + { + if (_goalService.GetGoalCount() <= 0) + { + GoalModel goal = new GoalModel + { StartTime = DateTime.Now, EndTime = DateTime.Now.AddHours(2).AddMinutes(30), Status = GoalStatus.InProgress, - Type = GoalType.TotalTime}; + Type = GoalType.DaysPerPeriod + }; + + + _goalService.AddGoal(goal); + } + } - _service.AddGoal(goal); - var goals = _service.GetAllGoalsByStatus(GoalStatus.InProgress); - _outputView.PrintGoalListAsTable(goals); - Console.ReadKey(); - } } diff --git a/codingTracker.jzhartman/CodingTracker.Controller/ReportsController.cs b/codingTracker.jzhartman/CodingTracker.Controller/ReportsController.cs index 66b3e38b7..f24f0e129 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/ReportsController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/ReportsController.cs @@ -28,7 +28,7 @@ public void Run() { _outputView.WelcomeMessage(); - var dateRangeSelection = GetDateRangeSelectionFromUser(); + var dateRangeSelection = _menuView.PrintEntryViewOptionsAndGetSelection(); if (dateRangeSelection == "Return to Previous Menu") { returnToMainMenu = true; continue; } @@ -42,11 +42,6 @@ public void Run() } } - - private string GetDateRangeSelectionFromUser() - { - return _menuView.PrintEntryViewOptionsAndGetSelection(); - } private (DateTime, DateTime) GetDatesBasedOnUserSelection(string selection) { DateTime startTime = new DateTime(); diff --git a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/IGoalRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/IGoalRepository.cs index fb1849ef5..1b42f3cc3 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/IGoalRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/IGoalRepository.cs @@ -4,6 +4,8 @@ namespace CodingTracker.Data.Interfaces; public interface IGoalRepository { void AddGoal(GoalModel goal); + List GetAllGoals(); List GetAllGoalsByStatus(GoalStatus status); + int GetGoalCount(); void UpdateGoal(GoalDTO goal); } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Data/Repositories/GoalRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Repositories/GoalRepository.cs index 94735d850..c085d67cd 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Repositories/GoalRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Repositories/GoalRepository.cs @@ -26,6 +26,12 @@ public List GetAllGoalsByStatus(GoalStatus status) return LoadData(sql, goalStatus); } + public List GetAllGoals() + { + string sql = $"select * from Goals"; + return LoadData(sql); + } + public void AddGoal(GoalModel goal) { string sql = "insert into Goals (StartTime, EndTime, Type, Status) values (@StartTime, @EndTime, @Type, @Status)"; @@ -38,4 +44,10 @@ public void UpdateGoal(GoalDTO goal) SaveData(sql, goal); } + public int GetGoalCount() + { + string sql = "select count(*) from Goals"; + return LoadData(sql).First(); + } + } diff --git a/codingTracker.jzhartman/CodingTracker.Data/TypeHandlers/GoalTypeHandler.cs b/codingTracker.jzhartman/CodingTracker.Data/TypeHandlers/GoalTypeHandler.cs index 68d8e54ac..8f0075fd2 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/TypeHandlers/GoalTypeHandler.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/TypeHandlers/GoalTypeHandler.cs @@ -1,11 +1,6 @@ using CodingTracker.Models.Entities; using Dapper; -using System; -using System.Collections.Generic; using System.Data; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace CodingTracker.Data.TypeHandlers; public class GoalTypeHandler : SqlMapper.TypeHandler @@ -17,12 +12,12 @@ public override GoalType Parse(object value) return default(GoalType); } - return (GoalType)Enum.Parse(typeof(GoalType), value.ToString()); + return (GoalType)Enum.Parse(typeof(GoalType), value.ToString(), true); } public override void SetValue(IDbDataParameter parameter, GoalType value) { - parameter.DbType = DbType.String; parameter.Value = value.ToString(); + parameter.DbType = DbType.String; } } diff --git a/codingTracker.jzhartman/CodingTracker.Services/GoalDataService.cs b/codingTracker.jzhartman/CodingTracker.Services/GoalDataService.cs index d087712b0..a330f8468 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/GoalDataService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/GoalDataService.cs @@ -10,7 +10,7 @@ namespace CodingTracker.Services; public class GoalDataService : IGoalDataService { - public readonly IGoalRepository _repository; + private readonly IGoalRepository _repository; public GoalDataService(IGoalRepository repository) { @@ -29,5 +29,13 @@ public List GetAllGoalsByStatus(GoalStatus status) { return _repository.GetAllGoalsByStatus(status); } + public List GetAllGoals() + { + return _repository.GetAllGoals(); + } + public int GetGoalCount() + { + return _repository.GetGoalCount(); + } } diff --git a/codingTracker.jzhartman/CodingTracker.Services/Interfaces/IGoalDataService.cs b/codingTracker.jzhartman/CodingTracker.Services/Interfaces/IGoalDataService.cs index 989944807..6aeae1868 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/Interfaces/IGoalDataService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/Interfaces/IGoalDataService.cs @@ -4,6 +4,8 @@ namespace CodingTracker.Services.Interfaces; public interface IGoalDataService { void AddGoal(GoalModel goal); + List GetAllGoals(); List GetAllGoalsByStatus(GoalStatus status); + int GetGoalCount(); void UpdateGoal(GoalDTO goal); } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs b/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs index 1dbe524fc..e52685560 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs @@ -1,6 +1,7 @@ using CodingTracker.Models.Entities; using CodingTracker.Views.Interfaces; using Spectre.Console; +using System.Reflection.Metadata; namespace CodingTracker.Views; public class ConsoleOutputView : IConsoleOutputView @@ -104,10 +105,14 @@ public void PrintReportDataAsTable(ReportModel report) AnsiConsole.Write("Press any key to continue... "); AnsiConsole.Console.Input.ReadKey(false); } + - - + public void NoRecordsMessage(string recordType) + { + AnsiConsole.MarkupInterpolated($"No {recordType} records exist!"); + AddNewLines(2); + } public void PrintGoalListAsTable(List goals) { int count = 1; diff --git a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IConsoleOutputView.cs b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IConsoleOutputView.cs index c781a73fa..3a9afecc3 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IConsoleOutputView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IConsoleOutputView.cs @@ -12,4 +12,5 @@ public interface IConsoleOutputView void PrintCodingSessionToUpdateById(CodingSessionDataRecord session, int rowId); void WelcomeMessage(); void PrintGoalListAsTable(List goals); + void NoRecordsMessage(string recordType); } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IMenuView.cs b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IMenuView.cs index 5735fe0fd..febef417d 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IMenuView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IMenuView.cs @@ -2,6 +2,7 @@ public interface IMenuView { string PrintEntryViewOptionsAndGetSelection(); + string PrintGoalOptionsAndGetSelection(); string PrintMainMenuAndGetSelection(); string PrintTrackingMenuAndGetSelection(); string PrintUpdateOrDeleteOptionsAndGetSelection(); diff --git a/codingTracker.jzhartman/CodingTracker.Views/MenuView.cs b/codingTracker.jzhartman/CodingTracker.Views/MenuView.cs index 6fecaf1b7..310f76b34 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/MenuView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/MenuView.cs @@ -70,4 +70,19 @@ public string PrintUpdateOrDeleteOptionsAndGetSelection() ); return selection; } + + public string PrintGoalOptionsAndGetSelection() + { + var selection = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("Please select the next operation:") + .AddChoices(new[] + { + "Change Record", + "Delete Record", + "Return to Previous Menu" + }) + ); + return selection; + } } From 5daff53c26dd206a34e7d0f75c2fc1c7251ade40 Mon Sep 17 00:00:00 2001 From: jzhartman Date: Mon, 10 Nov 2025 23:06:44 -0500 Subject: [PATCH 45/57] Interim commit --- .../GoalsController.cs | 99 ++++++++++++++++++- .../CodingTracker.Data/DatabaseInitializer.cs | 7 +- .../Repositories/GoalRepository.cs | 2 +- .../CodingTracker.Models/Entities/Enums.cs | 3 +- .../CodingTracker.Models/Entities/GoalDTO.cs | 1 + .../Entities/GoalModel.cs | 1 + .../CodingSessionDataService.cs | 34 +++---- .../Interfaces/ICodingSessionDataService.cs | 2 + .../Interfaces/IMenuView.cs | 1 + .../CodingTracker.Views/MenuView.cs | 20 +++- .../CodingTracker.Views/UserInputView.cs | 10 ++ 11 files changed, 150 insertions(+), 30 deletions(-) diff --git a/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs b/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs index 166283260..072387261 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs @@ -10,19 +10,21 @@ public class GoalsController : IGoalsController private readonly ICodingSessionDataService _codingSessionService; private readonly IMenuView _menuView; private readonly IConsoleOutputView _outputView; + private readonly IUserInputView _inputView; public GoalsController( IGoalDataService goalService, ICodingSessionDataService codingSessionService, - IMenuView menuView, IConsoleOutputView outputView) + IMenuView menuView, IConsoleOutputView outputView, IUserInputView inputView) { _goalService = goalService; _codingSessionService = codingSessionService; _menuView = menuView; _outputView = outputView; + _inputView = inputView; } public void Run() { - //GenerateDummyGoal(); + GenerateDummyGoal(); bool returnToMainMenu = false; @@ -36,14 +38,102 @@ public void Run() var selection = _menuView.PrintGoalOptionsAndGetSelection(); - if (selection == "Return to Previous Menu") { returnToMainMenu = true; continue; } + switch (selection) + { + case "Add Goal": + AddGoal(); + break; + case "Delete Goal": + case "Extend Goal": + case "Return to Previous Menu": + returnToMainMenu = true; + break; + + } + + + + + } + } + + private void AddGoal() + { + var startTime = GetStartTimeFromUser(); + var endTime = GetEndTimeFromUser(startTime); + var goalType = GetGoalTypeFromUser(); + + + + } + + private DateTime GetStartTimeFromUser() + { + var output = new DateTime(); + bool startTimeValid = false; + + while (startTimeValid == false) + { + output = _inputView.GetTimeFromUser("Goal start time"); + var result = _codingSessionService.ValidateGoalStartTime(output); + if (result.IsValid) + { + startTimeValid = true; + output = result.Value; + _outputView.ConfirmationMessage(result.Value.ToString("yyyy-MM-dd HH:mm:ss")); + } + else + { + _outputView.ErrorMessage(result.Parameter, result.Message); + } + } + return output; + } + private DateTime GetEndTimeFromUser(DateTime startTime) + { + var output = new DateTime(); + bool endTimeValid = false; + + while (endTimeValid == false) + { + output = _inputView.GetTimeFromUser("Goal end time"); + var result = _codingSessionService.ValidateGoalEndTime(output, startTime); + if (result.IsValid) + { + endTimeValid = true; + output = result.Value; + _outputView.ConfirmationMessage(result.Value.ToString("yyyy-MM-dd HH:mm:ss")); + } + else + { + _outputView.ErrorMessage(result.Parameter, result.Message); + } + } + return output; + } + + private GoalType GetGoalTypeFromUser() + { + var selection = _menuView.PrintGoalTypesAndGetSelection(); + + switch (selection) + { + case "Total Time": + return GoalType.TotalTime; + case "Average Time": + return GoalType.AverageTime; + case "Days Per Period": + return GoalType.DaysPerPeriod; + default: + return new GoalType(); } } + private void PrintAllActiveGoals() { var goals = _goalService.GetAllGoalsByStatus(GoalStatus.InProgress); @@ -88,7 +178,8 @@ private void GenerateDummyGoal() StartTime = DateTime.Now, EndTime = DateTime.Now.AddHours(2).AddMinutes(30), Status = GoalStatus.InProgress, - Type = GoalType.DaysPerPeriod + Type = GoalType.DaysPerPeriod, + Value = 5 }; diff --git a/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs b/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs index 5fcc85d52..f507b972f 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs @@ -27,14 +27,12 @@ private void InitializeTable(string tableName) SeedData(tableName); } } - private bool TableExists(string tableName) { using var connection = _connectionFactory.CreateConnection(); int count = connection.ExecuteScalar($"select count(*) from sqlite_master where type='table' and name='{tableName}'"); return count == 1; } - private void CreateTable(string tableName) { string parameters = string.Empty; @@ -50,7 +48,8 @@ private void CreateTable(string tableName) Type text not null, StartTime text not null, EndTime text not null, - Status text not null"; + Status text not null, + Value integer not null"; using var connection = _connectionFactory.CreateConnection(); @@ -58,7 +57,6 @@ private void CreateTable(string tableName) command.CommandText = $"create table if not exists {tableName}({parameters})"; command.ExecuteNonQuery(); } - private void SeedData(string tableName) { using var connection = _connectionFactory.CreateConnection(); @@ -73,7 +71,6 @@ private void SeedData(string tableName) command.CommandText = sql; command.ExecuteNonQuery(); } - private string CreateCodingSessionsSqlString() { string sql = "insert into CodingSessions(StartTime, EndTime, Duration)\nValues\n"; diff --git a/codingTracker.jzhartman/CodingTracker.Data/Repositories/GoalRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Repositories/GoalRepository.cs index c085d67cd..059f581df 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Repositories/GoalRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Repositories/GoalRepository.cs @@ -34,7 +34,7 @@ public List GetAllGoals() public void AddGoal(GoalModel goal) { - string sql = "insert into Goals (StartTime, EndTime, Type, Status) values (@StartTime, @EndTime, @Type, @Status)"; + string sql = "insert into Goals (StartTime, EndTime, Type, Status, Value) values (@StartTime, @EndTime, @Type, @Status, @Value)"; SaveData(sql, goal); } diff --git a/codingTracker.jzhartman/CodingTracker.Models/Entities/Enums.cs b/codingTracker.jzhartman/CodingTracker.Models/Entities/Enums.cs index cd72b0df9..1a53df01c 100644 --- a/codingTracker.jzhartman/CodingTracker.Models/Entities/Enums.cs +++ b/codingTracker.jzhartman/CodingTracker.Models/Entities/Enums.cs @@ -10,7 +10,8 @@ public enum GoalType { TotalTime, AverageTime, - DaysPerPeriod + DaysPerPeriod, + Unknown } public enum GoalStatus diff --git a/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalDTO.cs b/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalDTO.cs index 01c88ffc6..cff580f81 100644 --- a/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalDTO.cs +++ b/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalDTO.cs @@ -12,4 +12,5 @@ public class GoalDTO public DateTime StartTime { get; set; } public DateTime EndTime { get; set; } public GoalStatus Status { get; set; } + public int Value { get; set; } } diff --git a/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalModel.cs b/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalModel.cs index 782d88eaf..2d4fd6e7b 100644 --- a/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalModel.cs +++ b/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalModel.cs @@ -11,4 +11,5 @@ public class GoalModel public DateTime StartTime { get; set; } public DateTime EndTime { get; set; } public GoalStatus Status { get; set; } + public int Value { get; set; } } diff --git a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs index 6fe8b58c5..9b5aadbfe 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs @@ -14,8 +14,7 @@ public CodingSessionDataService(ICodingSessionRepository repository) } - // MOVE TO CRUD SERVICES FILE - + // CRUD SERVICES public void DeleteSessionById(int id) { _repository.DeleteById(id); @@ -38,18 +37,12 @@ public List GetSessionListByDateRange(DateTime startTim } public CodingSessionDataRecord GetSessionById(int id) { - // Validate that ID exists - return _repository.GetById(id); } - - - // Separate to new file? - // Provides Calculated Values Based On User Input - + // DATA SERVICES public (DateTime, DateTime) GetBasicDateRange(string selection) { DateTime startTime = new DateTime(); @@ -111,14 +104,7 @@ public DateTime GetEndTimeForAdvancedDateRange(string selection, DateTime startT - - - - - - - // MOVE TO VALIDATION SERVICES FILE - + // VALIDATION SERVICES public ValidationResult ValidateStartTime(DateTime input) { if (_repository.ExistsWithinTimeFrame(input)) @@ -174,6 +160,20 @@ public ValidationResult ValidateReportEndTime(DateTime input, DateTime else return ValidationResult.Success(input); } + public ValidationResult ValidateGoalStartTime(DateTime input) + { + if (input > DateTime.Now) + return ValidationResult.Fail("Start Time", "Cannot enter a future time"); + else + return ValidationResult.Success(input); + } + public ValidationResult ValidateGoalEndTime(DateTime input, DateTime startTime) + { + if (input > DateTime.Now) + return ValidationResult.Fail("End Time", "Goal must end at a future time"); + else + return ValidationResult.Success(input); + } public ValidationResult ValidateDateRangeStartTime(DateTime input) { if (input > DateTime.Now) diff --git a/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionDataService.cs b/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionDataService.cs index 535c27f7c..00c6dfaca 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionDataService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/Interfaces/ICodingSessionDataService.cs @@ -17,4 +17,6 @@ public interface ICodingSessionDataService (DateTime, DateTime) GetBasicDateRange(string selection); DateTime GetEndTimeForAdvancedDateRange(string selection, DateTime startTime); ValidationResult ValidateDateRangeStartTime(DateTime input); + ValidationResult ValidateGoalStartTime(DateTime input); + ValidationResult ValidateGoalEndTime(DateTime input, DateTime startTime); } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IMenuView.cs b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IMenuView.cs index febef417d..bddbeba9e 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IMenuView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IMenuView.cs @@ -3,6 +3,7 @@ public interface IMenuView { string PrintEntryViewOptionsAndGetSelection(); string PrintGoalOptionsAndGetSelection(); + string PrintGoalTypesAndGetSelection(); string PrintMainMenuAndGetSelection(); string PrintTrackingMenuAndGetSelection(); string PrintUpdateOrDeleteOptionsAndGetSelection(); diff --git a/codingTracker.jzhartman/CodingTracker.Views/MenuView.cs b/codingTracker.jzhartman/CodingTracker.Views/MenuView.cs index 310f76b34..77f26b2f1 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/MenuView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/MenuView.cs @@ -78,11 +78,27 @@ public string PrintGoalOptionsAndGetSelection() .Title("Please select the next operation:") .AddChoices(new[] { - "Change Record", - "Delete Record", + "Add Goal", + "Delete Goal", + "Extend Goal", "Return to Previous Menu" }) ); return selection; } + + public string PrintGoalTypesAndGetSelection() + { + var selection = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("Please select the next operation:") + .AddChoices(new[] + { + "Total Time", + "Average Time", + "Days Per Period" + }) + ); + return selection; + } } diff --git a/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs b/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs index 8803e53e9..57cbcbb76 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs @@ -138,6 +138,16 @@ public bool GetDeleteSessionConfirmationFromUser(CodingSessionDataRecord session return confirmation; } + public bool GetAddGoalConfirmationFromUser(GoalModel goal) + { + var confirmation = AnsiConsole.Prompt( + new TextPrompt($"Add goal starting at [yellow]{goal.StartTime.ToString("yyyy-MM-dd HH:mm:ss")}[/] and ending at [yellow]{goal.EndTime.ToString("yyyy-MM-dd HH:mm:ss")}[/] with duration [yellow]{goal.Value}[/]?") + .AddChoice(true) + .AddChoice(false) + .WithConverter(choice => choice ? "y" : "n")); + + return confirmation; + } From 8d19ef36a7b3d144b6b967b7e047503294168b87 Mon Sep 17 00:00:00 2001 From: jzhartman Date: Mon, 10 Nov 2025 23:12:29 -0500 Subject: [PATCH 46/57] End of night -- Working on Add Goal method --- .../GoalsController.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs b/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs index 072387261..7d4567216 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs @@ -62,9 +62,27 @@ private void AddGoal() var startTime = GetStartTimeFromUser(); var endTime = GetEndTimeFromUser(startTime); var goalType = GetGoalTypeFromUser(); + var goalValue = GetGoalValueFromUser(); + // Confirm, then add or cancel! + _goalService.AddGoal( + new GoalModel + { + StartTime = startTime, + EndTime = endTime, + Type = goalType, + Value = goalValue, + Status = GoalStatus.InProgress + } + ); + + } + + private int GetGoalValueFromUser() + { + throw new NotImplementedException(); } private DateTime GetStartTimeFromUser() From b66d6bbca6a2456d698805ad455e7cdc062160ba Mon Sep 17 00:00:00 2001 From: jzhartman Date: Tue, 11 Nov 2025 22:55:59 -0500 Subject: [PATCH 47/57] Working on AddGoal Fixed date input --- .../CodingTracker.Controller/GoalsController.cs | 3 +-- .../CodingTracker.Services/CodingSessionDataService.cs | 10 ++++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs b/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs index 7d4567216..7dee33b36 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs @@ -132,8 +132,7 @@ private DateTime GetEndTimeFromUser(DateTime startTime) } } return output; - } - + } private GoalType GetGoalTypeFromUser() { var selection = _menuView.PrintGoalTypesAndGetSelection(); diff --git a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs index 9b5aadbfe..8b8cd7b0a 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs @@ -162,15 +162,17 @@ public ValidationResult ValidateReportEndTime(DateTime input, DateTime } public ValidationResult ValidateGoalStartTime(DateTime input) { - if (input > DateTime.Now) - return ValidationResult.Fail("Start Time", "Cannot enter a future time"); - else + //if (input > DateTime.Now) + // return ValidationResult.Fail("Start Time", "Cannot enter a future time"); + //else return ValidationResult.Success(input); } public ValidationResult ValidateGoalEndTime(DateTime input, DateTime startTime) { - if (input > DateTime.Now) + if (input < DateTime.Now) return ValidationResult.Fail("End Time", "Goal must end at a future time"); + else if (input <= startTime) + return ValidationResult.Fail("End Time", "Goal end time cannot be before start time"); else return ValidationResult.Success(input); } From 93817eb291971eb2f8e95c3eaaefe01c6c93f423 Mon Sep 17 00:00:00 2001 From: jzhartman Date: Wed, 12 Nov 2025 23:15:33 -0500 Subject: [PATCH 48/57] Working on AddGoal GetGoalValueFromUser built but needs tested Possibly change goalValue to TimeSpan --- .../GoalsController.cs | 40 +++++++++++++++++-- .../CodingSessionDataService.cs | 4 ++ .../Interfaces/IUserInputView.cs | 2 + .../CodingTracker.Views/UserInputView.cs | 31 ++++++++++++++ 4 files changed, 74 insertions(+), 3 deletions(-) diff --git a/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs b/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs index 7dee33b36..bef425063 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs @@ -1,5 +1,6 @@ using CodingTracker.Controller.Interfaces; using CodingTracker.Models.Entities; +using CodingTracker.Models.Validation; using CodingTracker.Services.Interfaces; using CodingTracker.Views.Interfaces; @@ -62,7 +63,7 @@ private void AddGoal() var startTime = GetStartTimeFromUser(); var endTime = GetEndTimeFromUser(startTime); var goalType = GetGoalTypeFromUser(); - var goalValue = GetGoalValueFromUser(); + var goalValue = GetGoalValueFromUser(startTime, endTime, goalType); // Confirm, then add or cancel! @@ -80,11 +81,44 @@ private void AddGoal() } - private int GetGoalValueFromUser() + private int GetGoalValueFromUser(DateTime startTime, DateTime endTime, GoalType goalType) { - throw new NotImplementedException(); + int goalValue = -1; + bool valueIsValid = false; + + while (valueIsValid == false) + { + TimeSpan maxTime = new TimeSpan(); + TimeSpan valueTime = new TimeSpan(); + + switch (goalType) + { + case GoalType.AverageTime: + maxTime = new TimeSpan(23, 59, 59); + valueTime = _inputView.GetGoalValueTime(goalType, maxTime); + goalValue = (int)valueTime.TotalSeconds; + break; + case GoalType.TotalTime: + maxTime = endTime - startTime; + valueTime = _inputView.GetGoalValueTime(goalType, maxTime); + goalValue = (int)valueTime.TotalSeconds; + break; + case GoalType.DaysPerPeriod: + maxTime = endTime - startTime; + int maxDays = maxTime.Days; + goalValue = _inputView.GetGoalValueForDaysPerPeriod(maxDays); + break; + default: + break; + } + + + } + + return goalValue; } + private DateTime GetStartTimeFromUser() { var output = new DateTime(); diff --git a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs index 8b8cd7b0a..6b14fd388 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs @@ -160,6 +160,8 @@ public ValidationResult ValidateReportEndTime(DateTime input, DateTime else return ValidationResult.Success(input); } + + public ValidationResult ValidateGoalStartTime(DateTime input) { //if (input > DateTime.Now) @@ -173,6 +175,8 @@ public ValidationResult ValidateGoalEndTime(DateTime input, DateTime s return ValidationResult.Fail("End Time", "Goal must end at a future time"); else if (input <= startTime) return ValidationResult.Fail("End Time", "Goal end time cannot be before start time"); + else if (input >= startTime.AddYears(1)) + return ValidationResult.Fail("End Time", "Goal time duration cannot exceed one year"); else return ValidationResult.Success(input); } diff --git a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInputView.cs b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInputView.cs index 26f0cc28f..eb1a0811d 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInputView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInputView.cs @@ -10,5 +10,7 @@ public interface IUserInputView bool GetDeleteSessionConfirmationFromUser(CodingSessionDataRecord session); string StartStopwatch(); string StopStopwatch(); + int GetGoalValueForDaysPerPeriod(int maxDays); + TimeSpan GetGoalValueTime(GoalType goalType, TimeSpan maxTime); //DateTime GetUpdatedStartTimeFromUser(DateTime originalTime); } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs b/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs index 57cbcbb76..58f2655f4 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs @@ -98,6 +98,37 @@ public int GetRecordIdFromUser(string action, int max) return id; } + + public TimeSpan GetGoalValueTime(GoalType goalType, TimeSpan maxTime) + { + string promptText = "Please enter the value for the "; + if (goalType == GoalType.TotalTime) promptText += "total time coded within this timeframe using the format [yellow]d.HH:mm:ss[/]:"; + if (goalType == GoalType.AverageTime) promptText += "average daily time within this timeframe using the format [yellow]HH:mm:ss[/]:"; + + var goalValue = AnsiConsole.Prompt( + new TextPrompt(promptText) + .Validate(input => + { + if (input.TotalMilliseconds < 1) return Spectre.Console.ValidationResult.Error("Value must be greater than zero!"); + else if (input > maxTime) return Spectre.Console.ValidationResult.Error($"Input cannot exceed {maxTime}!"); + else return Spectre.Console.ValidationResult.Success(); + })); + + return goalValue; + } + public int GetGoalValueForDaysPerPeriod(int maxValue) + { + var goalValue = AnsiConsole.Prompt( + new TextPrompt($"Please enter the goal value for the days per period:") + .Validate(input => + { + if (input < 1) return Spectre.Console.ValidationResult.Error("Value must be greater than zero!"); + else if (input > maxValue) return Spectre.Console.ValidationResult.Error($"Value cannot be greater than {maxValue}"); + else return Spectre.Console.ValidationResult.Success(); + })); + + return goalValue; + } public bool GetAddSessionConfirmationFromUser(CodingSession session) { var duration = ConvertTimeFromSecondsToText(session.Duration); From 477fd5d994fe1ca7fcd73260c095acf4479d64e4 Mon Sep 17 00:00:00 2001 From: jzhartman Date: Tue, 18 Nov 2025 23:28:44 -0500 Subject: [PATCH 49/57] Completed basic AddGoal methoid Next: Evaluate Goals --- .../GoalsController.cs | 124 ++++++++++++------ .../CodingTracker.Models/Entities/GoalDTO.cs | 2 +- .../Entities/GoalModel.cs | 2 +- .../CodingTracker.Services/GoalDataService.cs | 47 ++++++- .../Interfaces/IGoalDataService.cs | 2 + .../CodingTracker.Views/ConsoleOutputView.cs | 5 + .../Interfaces/IConsoleOutputView.cs | 1 + .../Interfaces/IUserInputView.cs | 5 +- .../CodingTracker.Views/UserInputView.cs | 37 +++--- 9 files changed, 158 insertions(+), 67 deletions(-) diff --git a/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs b/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs index bef425063..686e68867 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs @@ -3,6 +3,8 @@ using CodingTracker.Models.Validation; using CodingTracker.Services.Interfaces; using CodingTracker.Views.Interfaces; +using System; +using static System.Collections.Specialized.BitVector32; namespace CodingTracker.Controller; public class GoalsController : IGoalsController @@ -42,7 +44,8 @@ public void Run() switch (selection) { case "Add Goal": - AddGoal(); + var goal = GetGoalDataFromUser(); + ConfirmAddGoal(goal); break; case "Delete Goal": case "Extend Goal": @@ -58,66 +61,103 @@ public void Run() } } - private void AddGoal() + private GoalModel GetGoalDataFromUser() { var startTime = GetStartTimeFromUser(); var endTime = GetEndTimeFromUser(startTime); var goalType = GetGoalTypeFromUser(); - var goalValue = GetGoalValueFromUser(startTime, endTime, goalType); + var goalValue = GetGoalValue(startTime, endTime, goalType); + return new GoalModel + { + StartTime = startTime, + EndTime = endTime, + Type = goalType, + Value = goalValue, + Status = GoalStatus.InProgress + }; + } - // Confirm, then add or cancel! - - _goalService.AddGoal( - new GoalModel - { - StartTime = startTime, - EndTime = endTime, - Type = goalType, - Value = goalValue, - Status = GoalStatus.InProgress - } - ); + private void ConfirmAddGoal(GoalModel goal) + { + var goalConfirmed = _inputView.GetAddGoalConfirmationFromUser(goal); + if (goalConfirmed) + { + _goalService.AddGoal(goal); + } + else + { + _outputView.GoalCancelledMessage("addition"); + } } - private int GetGoalValueFromUser(DateTime startTime, DateTime endTime, GoalType goalType) + private long GetGoalValue(DateTime startTime, DateTime endTime, GoalType goalType) { - int goalValue = -1; + long output = -1; bool valueIsValid = false; + var maxValue = GetMaxGoalValueByType(goalType, startTime, endTime); + while (valueIsValid == false) { - TimeSpan maxTime = new TimeSpan(); - TimeSpan valueTime = new TimeSpan(); + var input = GetGoalValueFromUser(startTime, endTime, goalType); + var result = _goalService.ValidateGoalValueInput(goalType, input, maxValue); - switch (goalType) + if (result.IsValid) { - case GoalType.AverageTime: - maxTime = new TimeSpan(23, 59, 59); - valueTime = _inputView.GetGoalValueTime(goalType, maxTime); - goalValue = (int)valueTime.TotalSeconds; - break; - case GoalType.TotalTime: - maxTime = endTime - startTime; - valueTime = _inputView.GetGoalValueTime(goalType, maxTime); - goalValue = (int)valueTime.TotalSeconds; - break; - case GoalType.DaysPerPeriod: - maxTime = endTime - startTime; - int maxDays = maxTime.Days; - goalValue = _inputView.GetGoalValueForDaysPerPeriod(maxDays); - break; - default: - break; + valueIsValid = true; + output = result.Value; + _outputView.ConfirmationMessage(result.Value.ToString()); //ToDo: Fix printing -- This prints all values as seconds } + else + { + _outputView.ErrorMessage(result.Parameter, result.Message); + } + } + + return output; + } - } - return goalValue; + private long GetGoalValueFromUser(DateTime startTime, DateTime endTime, GoalType goalType) + { + switch (goalType) + { + case GoalType.AverageTime: + case GoalType.TotalTime: + return _inputView.GetGoalValueTime(goalType); + + case GoalType.DaysPerPeriod: + return _inputView.GetGoalValueForDaysPerPeriod(); + + default: + return -1; + } } + private long GetMaxGoalValueByType(GoalType goalType, DateTime startTime, DateTime endTime) + { + TimeSpan maxTime = new TimeSpan(); + + switch (goalType) + { + case GoalType.AverageTime: + maxTime = new TimeSpan(23, 59, 59); + return (long)maxTime.TotalSeconds; + case GoalType.TotalTime: + maxTime = endTime - startTime; + return (long)maxTime.TotalSeconds; + + case GoalType.DaysPerPeriod: + maxTime = endTime - startTime; + return (long)maxTime.TotalDays; + + default: + return -1; + } + } private DateTime GetStartTimeFromUser() { @@ -226,11 +266,11 @@ private void GenerateDummyGoal() { GoalModel goal = new GoalModel { - StartTime = DateTime.Now, - EndTime = DateTime.Now.AddHours(2).AddMinutes(30), + StartTime = DateTime.Now.AddDays(-5), + EndTime = DateTime.Now.AddDays(5), Status = GoalStatus.InProgress, Type = GoalType.DaysPerPeriod, - Value = 5 + Value = (long)TimeSpan.FromDays(5).TotalSeconds }; diff --git a/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalDTO.cs b/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalDTO.cs index cff580f81..5ca359723 100644 --- a/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalDTO.cs +++ b/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalDTO.cs @@ -12,5 +12,5 @@ public class GoalDTO public DateTime StartTime { get; set; } public DateTime EndTime { get; set; } public GoalStatus Status { get; set; } - public int Value { get; set; } + public long Value { get; set; } } diff --git a/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalModel.cs b/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalModel.cs index 2d4fd6e7b..a55748f14 100644 --- a/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalModel.cs +++ b/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalModel.cs @@ -11,5 +11,5 @@ public class GoalModel public DateTime StartTime { get; set; } public DateTime EndTime { get; set; } public GoalStatus Status { get; set; } - public int Value { get; set; } + public long Value { get; set; } } diff --git a/codingTracker.jzhartman/CodingTracker.Services/GoalDataService.cs b/codingTracker.jzhartman/CodingTracker.Services/GoalDataService.cs index a330f8468..385b9d256 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/GoalDataService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/GoalDataService.cs @@ -1,5 +1,6 @@ using CodingTracker.Data.Interfaces; using CodingTracker.Models.Entities; +using CodingTracker.Models.Validation; using CodingTracker.Services.Interfaces; using System; using System.Collections.Generic; @@ -38,4 +39,48 @@ public int GetGoalCount() return _repository.GetGoalCount(); } -} + + + + // Unit Conversions + public long GetGoalValueFromTimeSpan(TimeSpan time) + { + return (long)time.TotalSeconds; + } + public int GetGoalDaysFromSeconds(long seconds) + { + return (int)TimeSpan.FromSeconds(seconds).TotalDays; + } + public TimeSpan GetGoalTimeFromSeconds(long seconds) + { + return TimeSpan.FromSeconds(seconds); + } + + + //Validation + public ValidationResult ValidateGoalValueInput(GoalType goalType, long input, long maxTime) + { + if (input < 1) + return ValidationResult.Fail("Goal Value", "Goal value cannot be zero or lower."); + else if (input > maxTime) + return ValidationResult.Fail("Goal Value", $"Goal value exceeds maximum time {TimeSpan.FromSeconds(maxTime)}"); + else + return ValidationResult.Success(input); + + } + + //public ValidationResult ValidateGoalValueInput(TimeSpan input, TimeSpan maxTime) + //{ + // if (input.TotalSeconds < 1) + // return ValidationResult.Fail("Goal Value", "Goal value cannot be zero or lower."); + // else if (input > maxTime) + // return ValidationResult.Fail("Goal Value", $"Goal value exceeds maximum time {maxTime}"); + // else + // return ValidationResult.Success(input); + + // // if (input.TotalSeconds < 1) return Spectre.Console.ValidationResult.Error("Value must be greater than zero!"); + // // else if (input > maxTime) return Spectre.Console.ValidationResult.Error($"Input cannot exceed {maxTime}!"); + // // else return Spectre.Console.ValidationResult.Success(); + //} + +} \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Services/Interfaces/IGoalDataService.cs b/codingTracker.jzhartman/CodingTracker.Services/Interfaces/IGoalDataService.cs index 6aeae1868..60a6e0fd1 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/Interfaces/IGoalDataService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/Interfaces/IGoalDataService.cs @@ -1,4 +1,5 @@ using CodingTracker.Models.Entities; +using CodingTracker.Models.Validation; namespace CodingTracker.Services.Interfaces; public interface IGoalDataService @@ -8,4 +9,5 @@ public interface IGoalDataService List GetAllGoalsByStatus(GoalStatus status); int GetGoalCount(); void UpdateGoal(GoalDTO goal); + ValidationResult ValidateGoalValueInput(GoalType goalType, long input, long maxTime); } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs b/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs index e52685560..ec74fb421 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs @@ -43,6 +43,11 @@ public void ActionCancelledMessage(string action) AddNewLines(1); AnsiConsole.MarkupInterpolated($"Cancelled {action} of coding session!"); } + public void GoalCancelledMessage(string action) + { + AddNewLines(1); + AnsiConsole.MarkupInterpolated($"Cancelled {action} of goal!"); + } public void PrintCodingSessionListAsTable(List sessions) { int count = 1; diff --git a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IConsoleOutputView.cs b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IConsoleOutputView.cs index 3a9afecc3..5e7f72d6f 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IConsoleOutputView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IConsoleOutputView.cs @@ -13,4 +13,5 @@ public interface IConsoleOutputView void WelcomeMessage(); void PrintGoalListAsTable(List goals); void NoRecordsMessage(string recordType); + void GoalCancelledMessage(string action); } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInputView.cs b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInputView.cs index eb1a0811d..edd45eee3 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInputView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInputView.cs @@ -10,7 +10,8 @@ public interface IUserInputView bool GetDeleteSessionConfirmationFromUser(CodingSessionDataRecord session); string StartStopwatch(); string StopStopwatch(); - int GetGoalValueForDaysPerPeriod(int maxDays); - TimeSpan GetGoalValueTime(GoalType goalType, TimeSpan maxTime); + long GetGoalValueTime(GoalType goalType); + long GetGoalValueForDaysPerPeriod(); + bool GetAddGoalConfirmationFromUser(GoalModel goal); //DateTime GetUpdatedStartTimeFromUser(DateTime originalTime); } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs b/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs index 58f2655f4..3302fef67 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs @@ -99,36 +99,26 @@ public int GetRecordIdFromUser(string action, int max) return id; } - public TimeSpan GetGoalValueTime(GoalType goalType, TimeSpan maxTime) + public long GetGoalValueTime(GoalType goalType) { string promptText = "Please enter the value for the "; - if (goalType == GoalType.TotalTime) promptText += "total time coded within this timeframe using the format [yellow]d.HH:mm:ss[/]:"; - if (goalType == GoalType.AverageTime) promptText += "average daily time within this timeframe using the format [yellow]HH:mm:ss[/]:"; + if (goalType == GoalType.TotalTime) promptText += "total time to code within this timeframe using the format [yellow]d.HH:mm:ss[/]:"; + if (goalType == GoalType.AverageTime) promptText += "average daily time coding within this timeframe using the format [yellow]HH:mm:ss[/]:"; var goalValue = AnsiConsole.Prompt( - new TextPrompt(promptText) - .Validate(input => - { - if (input.TotalMilliseconds < 1) return Spectre.Console.ValidationResult.Error("Value must be greater than zero!"); - else if (input > maxTime) return Spectre.Console.ValidationResult.Error($"Input cannot exceed {maxTime}!"); - else return Spectre.Console.ValidationResult.Success(); - })); + new TextPrompt(promptText)); - return goalValue; + return (long)goalValue.TotalSeconds; } - public int GetGoalValueForDaysPerPeriod(int maxValue) + + public long GetGoalValueForDaysPerPeriod() { var goalValue = AnsiConsole.Prompt( - new TextPrompt($"Please enter the goal value for the days per period:") - .Validate(input => - { - if (input < 1) return Spectre.Console.ValidationResult.Error("Value must be greater than zero!"); - else if (input > maxValue) return Spectre.Console.ValidationResult.Error($"Value cannot be greater than {maxValue}"); - else return Spectre.Console.ValidationResult.Success(); - })); + new TextPrompt($"Please enter the goal value for the days per period:")); return goalValue; } + public bool GetAddSessionConfirmationFromUser(CodingSession session) { var duration = ConvertTimeFromSecondsToText(session.Duration); @@ -171,8 +161,15 @@ public bool GetDeleteSessionConfirmationFromUser(CodingSessionDataRecord session } public bool GetAddGoalConfirmationFromUser(GoalModel goal) { + string valueText = string.Empty; + + if (goal.Type == GoalType.TotalTime || goal.Type == GoalType.AverageTime) + valueText = TimeSpan.FromSeconds(goal.Value).ToString(); + if (goal.Type == GoalType.DaysPerPeriod) + valueText = TimeSpan.FromSeconds(goal.Value).TotalDays.ToString(); + var confirmation = AnsiConsole.Prompt( - new TextPrompt($"Add goal starting at [yellow]{goal.StartTime.ToString("yyyy-MM-dd HH:mm:ss")}[/] and ending at [yellow]{goal.EndTime.ToString("yyyy-MM-dd HH:mm:ss")}[/] with duration [yellow]{goal.Value}[/]?") + new TextPrompt($"Add [yellow]{goal.Type}[/] goal starting at [yellow]{goal.StartTime.ToString("yyyy-MM-dd HH:mm:ss")}[/] and ending at [yellow]{goal.EndTime.ToString("yyyy-MM-dd HH:mm:ss")}[/] with value [yellow]{valueText}[/]?") .AddChoice(true) .AddChoice(false) .WithConverter(choice => choice ? "y" : "n")); From e4cb06e8697086c270f8b3655463ecf65534381e Mon Sep 17 00:00:00 2001 From: jzhartman Date: Wed, 19 Nov 2025 23:24:41 -0500 Subject: [PATCH 50/57] Evaluate Goals methods are in progress --- .../GoalsController.cs | 212 ++++++++++-------- .../CodingTracker.Data/DatabaseInitializer.cs | 4 +- .../Repositories/GoalRepository.cs | 14 +- .../CodingTracker.Models/Entities/GoalDTO.cs | 4 +- .../Entities/GoalModel.cs | 4 +- .../CodingTracker.Services/GoalDataService.cs | 98 ++++++-- .../Interfaces/IGoalDataService.cs | 3 + .../CodingTracker.Views/MenuView.cs | 1 + .../CodingTracker.Views/UserInputView.cs | 4 +- .../CodingTracker/Program.cs | 1 + 10 files changed, 224 insertions(+), 121 deletions(-) diff --git a/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs b/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs index 686e68867..af3a74003 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs @@ -36,8 +36,8 @@ public void Run() _outputView.WelcomeMessage(); PrintAllActiveGoals(); - var goalsInProgress = GetAllGoalsInProgress(); - EvaluateAllGoalsInProgress(goalsInProgress); + var goalsInProgress = _goalService.GetAllGoalsByStatus(GoalStatus.InProgress); + EvaluateGoals(goalsInProgress); var selection = _menuView.PrintGoalOptionsAndGetSelection(); @@ -48,7 +48,14 @@ public void Run() ConfirmAddGoal(goal); break; case "Delete Goal": + // TODO: Create Handle Delete Goal Option + break; case "Extend Goal": + // TODO: Handle Extend Goal Option (basic update of end time only) + break; + case "View Completed Goals": + // TODO: Handle View Completed Goals + break; case "Return to Previous Menu": returnToMainMenu = true; break; @@ -73,25 +80,76 @@ private GoalModel GetGoalDataFromUser() StartTime = startTime, EndTime = endTime, Type = goalType, - Value = goalValue, - Status = GoalStatus.InProgress + GoalValue = goalValue, + Status = GoalStatus.InProgress, + CurrentValue = 0, + Progress = 0 }; } + private DateTime GetStartTimeFromUser() + { + var output = new DateTime(); + bool startTimeValid = false; - private void ConfirmAddGoal(GoalModel goal) + while (startTimeValid == false) + { + output = _inputView.GetTimeFromUser("Goal start time"); + + var result = _codingSessionService.ValidateGoalStartTime(output); + + if (result.IsValid) + { + startTimeValid = true; + output = result.Value; + _outputView.ConfirmationMessage(result.Value.ToString("yyyy-MM-dd HH:mm:ss")); + } + else + { + _outputView.ErrorMessage(result.Parameter, result.Message); + } + } + return output; + } + private DateTime GetEndTimeFromUser(DateTime startTime) { - var goalConfirmed = _inputView.GetAddGoalConfirmationFromUser(goal); + var output = new DateTime(); + bool endTimeValid = false; - if (goalConfirmed) + while (endTimeValid == false) { - _goalService.AddGoal(goal); + output = _inputView.GetTimeFromUser("Goal end time"); + + var result = _codingSessionService.ValidateGoalEndTime(output, startTime); + + if (result.IsValid) + { + endTimeValid = true; + output = result.Value; + _outputView.ConfirmationMessage(result.Value.ToString("yyyy-MM-dd HH:mm:ss")); + } + else + { + _outputView.ErrorMessage(result.Parameter, result.Message); + } } - else + return output; + } + private GoalType GetGoalTypeFromUser() + { + var selection = _menuView.PrintGoalTypesAndGetSelection(); + + switch (selection) { - _outputView.GoalCancelledMessage("addition"); + case "Total Time": + return GoalType.TotalTime; + case "Average Time": + return GoalType.AverageTime; + case "Days Per Period": + return GoalType.DaysPerPeriod; + default: + return new GoalType(); } } - private long GetGoalValue(DateTime startTime, DateTime endTime, GoalType goalType) { long output = -1; @@ -118,9 +176,6 @@ private long GetGoalValue(DateTime startTime, DateTime endTime, GoalType goalTyp return output; } - - - private long GetGoalValueFromUser(DateTime startTime, DateTime endTime, GoalType goalType) { switch (goalType) @@ -131,7 +186,7 @@ private long GetGoalValueFromUser(DateTime startTime, DateTime endTime, GoalType case GoalType.DaysPerPeriod: return _inputView.GetGoalValueForDaysPerPeriod(); - + default: return -1; } @@ -139,7 +194,7 @@ private long GetGoalValueFromUser(DateTime startTime, DateTime endTime, GoalType private long GetMaxGoalValueByType(GoalType goalType, DateTime startTime, DateTime endTime) { TimeSpan maxTime = new TimeSpan(); - + switch (goalType) { case GoalType.AverageTime: @@ -158,71 +213,30 @@ private long GetMaxGoalValueByType(GoalType goalType, DateTime startTime, DateTi return -1; } } - - private DateTime GetStartTimeFromUser() + private void ConfirmAddGoal(GoalModel goal) { - var output = new DateTime(); - bool startTimeValid = false; + var goalConfirmed = _inputView.GetAddGoalConfirmationFromUser(goal); - while (startTimeValid == false) + if (goalConfirmed) { - output = _inputView.GetTimeFromUser("Goal start time"); - - var result = _codingSessionService.ValidateGoalStartTime(output); - - if (result.IsValid) - { - startTimeValid = true; - output = result.Value; - _outputView.ConfirmationMessage(result.Value.ToString("yyyy-MM-dd HH:mm:ss")); - } - else - { - _outputView.ErrorMessage(result.Parameter, result.Message); - } + _goalService.AddGoal(goal); + } + else + { + _outputView.GoalCancelledMessage("addition"); } - return output; } - private DateTime GetEndTimeFromUser(DateTime startTime) - { - var output = new DateTime(); - bool endTimeValid = false; - while (endTimeValid == false) - { - output = _inputView.GetTimeFromUser("Goal end time"); - var result = _codingSessionService.ValidateGoalEndTime(output, startTime); - if (result.IsValid) - { - endTimeValid = true; - output = result.Value; - _outputView.ConfirmationMessage(result.Value.ToString("yyyy-MM-dd HH:mm:ss")); - } - else - { - _outputView.ErrorMessage(result.Parameter, result.Message); - } - } - return output; - } - private GoalType GetGoalTypeFromUser() - { - var selection = _menuView.PrintGoalTypesAndGetSelection(); - switch (selection) - { - case "Total Time": - return GoalType.TotalTime; - case "Average Time": - return GoalType.AverageTime; - case "Days Per Period": - return GoalType.DaysPerPeriod; - default: - return new GoalType(); - } - } + + + + + + + private void PrintAllActiveGoals() @@ -235,29 +249,49 @@ private void PrintAllActiveGoals() _outputView.PrintGoalListAsTable(goals); } - private List GetAllGoalsInProgress() - { - return _goalService.GetAllGoalsByStatus(GoalStatus.InProgress); - } - private List GetAllGoals() - { - return _goalService.GetAllGoals(); - } - private void EvaluateAllGoalsInProgress(List goals) + private void EvaluateGoals(List goals) { - var codingSessionsLists = new List>(); + UpdateGoalStatusAndProgress(goals); //TODO: Remember to update the DB!!!!! + // Print Goals that have been completed! + // Print Goals that have failed -- Be nice + // Print all remaining goals with statuses + } + private void UpdateGoalStatusAndProgress(List goals) + { foreach (var goal in goals) { - codingSessionsLists.Add(_codingSessionService.GetSessionListByDateRange(goal.StartTime, goal.EndTime)); + var codingSessions = _codingSessionService.GetSessionListByDateRange(goal.StartTime, goal.EndTime); + + switch (goal.Type) + { + case GoalType.TotalTime: + _goalService.EvaluateTotalTimeGoal(goal, codingSessions); + break; + case GoalType.AverageTime: + _goalService.EvaluateAverageTimeGoal(goal, codingSessions); + break; + case GoalType.DaysPerPeriod: + _goalService.EvaluateDaysPerPeriodGoal(goal, codingSessions); + break; + default: + _outputView.ErrorMessage("Goal Type", "Invalid Goal Type detected!"); + break; + } } } - private void EvaluateGoal(GoalModel goal) - { - - } + + + + + + + + + + private void GenerateDummyGoal() @@ -270,7 +304,9 @@ private void GenerateDummyGoal() EndTime = DateTime.Now.AddDays(5), Status = GoalStatus.InProgress, Type = GoalType.DaysPerPeriod, - Value = (long)TimeSpan.FromDays(5).TotalSeconds + GoalValue = (long)TimeSpan.FromDays(5).TotalSeconds, + CurrentValue = 3, + Progress = 60 }; diff --git a/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs b/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs index f507b972f..9f4106437 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs @@ -49,7 +49,9 @@ private void CreateTable(string tableName) StartTime text not null, EndTime text not null, Status text not null, - Value integer not null"; + GoalValue integer not null, + CurrentValue integer not null, + Progress real not null"; using var connection = _connectionFactory.CreateConnection(); diff --git a/codingTracker.jzhartman/CodingTracker.Data/Repositories/GoalRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Repositories/GoalRepository.cs index 059f581df..d86a372f6 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Repositories/GoalRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Repositories/GoalRepository.cs @@ -1,24 +1,12 @@ using CodingTracker.Data.Interfaces; using CodingTracker.Data.Parameters; -using CodingTracker.Data.TypeHandlers; using CodingTracker.Models.Entities; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace CodingTracker.Data.Repositories; public class GoalRepository : RepositoryGenerics, IGoalRepository { public GoalRepository(ISqliteConnectionFactory connectionFactory) : base(connectionFactory) { } - // TODO: Setup the Goals repository - // TODO: Create Goal object with relevant members - // TODO: Create Goal input - // TODO: Create Goal output to console - // TODO: Determine how goal status will be determined - public List GetAllGoalsByStatus(GoalStatus status) { var goalStatus = new GoalStatusQuery {Status = status }; @@ -34,7 +22,7 @@ public List GetAllGoals() public void AddGoal(GoalModel goal) { - string sql = "insert into Goals (StartTime, EndTime, Type, Status, Value) values (@StartTime, @EndTime, @Type, @Status, @Value)"; + string sql = "insert into Goals (StartTime, EndTime, Type, Status, GoalValue, CurrentValue, Progress) values (@StartTime, @EndTime, @Type, @Status, @GoalValue, @CurrentValue, @Progress)"; SaveData(sql, goal); } diff --git a/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalDTO.cs b/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalDTO.cs index 5ca359723..26272fb27 100644 --- a/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalDTO.cs +++ b/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalDTO.cs @@ -12,5 +12,7 @@ public class GoalDTO public DateTime StartTime { get; set; } public DateTime EndTime { get; set; } public GoalStatus Status { get; set; } - public long Value { get; set; } + public long GoalValue { get; set; } + public long CurrentValue { get; set; } + public double Progress { get; set; } } diff --git a/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalModel.cs b/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalModel.cs index a55748f14..e67fcef99 100644 --- a/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalModel.cs +++ b/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalModel.cs @@ -11,5 +11,7 @@ public class GoalModel public DateTime StartTime { get; set; } public DateTime EndTime { get; set; } public GoalStatus Status { get; set; } - public long Value { get; set; } + public long GoalValue { get; set; } + public long CurrentValue { get; set; } + public double Progress { get; set; } } diff --git a/codingTracker.jzhartman/CodingTracker.Services/GoalDataService.cs b/codingTracker.jzhartman/CodingTracker.Services/GoalDataService.cs index 385b9d256..aa975aa74 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/GoalDataService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/GoalDataService.cs @@ -57,6 +57,89 @@ public TimeSpan GetGoalTimeFromSeconds(long seconds) } + + // Evaluations + public void EvaluateTotalTimeGoal(GoalDTO goal, List codingSessions) + { + var timeRemaining = (goal.EndTime - DateTime.Now).TotalSeconds; + + goal.CurrentValue = SumTotalTimeFromSessions(codingSessions); + goal.Progress = (goal.CurrentValue / goal.GoalValue) * 100; + + if (goal.Progress >= 100 && timeRemaining < 0) + goal.Status = GoalStatus.Complete; + + else if (timeRemaining > 0 && (timeRemaining + goal.CurrentValue) >= goal.GoalValue) + goal.Status = GoalStatus.InProgress; + + else + goal.Status = GoalStatus.Failed; + } + public void EvaluateAverageTimeGoal(GoalDTO goal, List codingSessions) + { + var timeRemaining = (goal.EndTime - DateTime.Now).TotalSeconds; + var daysRemaining = (goal.EndTime - DateTime.Now).TotalDays; + + var totalTime = SumTotalTimeFromSessions(codingSessions); + goal.CurrentValue = (long)(totalTime / (goal.EndTime - goal.StartTime).TotalDays); + goal.Progress = (goal.CurrentValue / goal.GoalValue) * 100; + + if (goal.Progress >= 100 && timeRemaining < 0) + goal.Status = GoalStatus.Complete; + + else if (timeRemaining > 0 && (totalTime + timeRemaining)/daysRemaining >= goal.GoalValue) + goal.Status = GoalStatus.InProgress; + + else + goal.Status = GoalStatus.Failed; + } + + public void EvaluateDaysPerPeriodGoal(GoalDTO goal, List codingSessions) + { + var daysRemaining = (goal.EndTime - DateTime.Now).TotalDays; + + goal.CurrentValue = GetUniqueDaysPerPeriod(codingSessions) * 86400; + goal.Progress = (goal.CurrentValue / goal.GoalValue) * 100; //TODO: explicitly convert to double so percent is correct + + if (goal.Progress >= 100 && daysRemaining < 0) + goal.Status = GoalStatus.Complete; + + else if (daysRemaining > 0 && (goal.CurrentValue + daysRemaining) >= goal.GoalValue) + goal.Status = GoalStatus.InProgress; + + else + goal.Status = GoalStatus.Failed; + } + + // TODO: Address cases where codingSessions is empty + private int GetUniqueDaysPerPeriod(List codingSessions) + { + int uniqueDays = 1; + + for (int i = 1; i < codingSessions.Count; i++) + { + if (codingSessions[i].StartTime.Date != codingSessions[i - 1].StartTime.Date) + uniqueDays++; + } + + return uniqueDays; + } + private long SumTotalTimeFromSessions(List codingSessions) + { + long totalTime = 0; + + foreach (var session in codingSessions) + { + totalTime += session.Duration; + } + + return totalTime; + } + + + + + //Validation public ValidationResult ValidateGoalValueInput(GoalType goalType, long input, long maxTime) { @@ -68,19 +151,4 @@ public ValidationResult ValidateGoalValueInput(GoalType goalType, long inp return ValidationResult.Success(input); } - - //public ValidationResult ValidateGoalValueInput(TimeSpan input, TimeSpan maxTime) - //{ - // if (input.TotalSeconds < 1) - // return ValidationResult.Fail("Goal Value", "Goal value cannot be zero or lower."); - // else if (input > maxTime) - // return ValidationResult.Fail("Goal Value", $"Goal value exceeds maximum time {maxTime}"); - // else - // return ValidationResult.Success(input); - - // // if (input.TotalSeconds < 1) return Spectre.Console.ValidationResult.Error("Value must be greater than zero!"); - // // else if (input > maxTime) return Spectre.Console.ValidationResult.Error($"Input cannot exceed {maxTime}!"); - // // else return Spectre.Console.ValidationResult.Success(); - //} - } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Services/Interfaces/IGoalDataService.cs b/codingTracker.jzhartman/CodingTracker.Services/Interfaces/IGoalDataService.cs index 60a6e0fd1..ca487f71a 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/Interfaces/IGoalDataService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/Interfaces/IGoalDataService.cs @@ -5,6 +5,9 @@ namespace CodingTracker.Services.Interfaces; public interface IGoalDataService { void AddGoal(GoalModel goal); + void EvaluateAverageTimeGoal(GoalDTO goal, List codingSessions); + void EvaluateDaysPerPeriodGoal(GoalDTO goal, List codingSessions); + void EvaluateTotalTimeGoal(GoalDTO goal, List codingSessions); List GetAllGoals(); List GetAllGoalsByStatus(GoalStatus status); int GetGoalCount(); diff --git a/codingTracker.jzhartman/CodingTracker.Views/MenuView.cs b/codingTracker.jzhartman/CodingTracker.Views/MenuView.cs index 77f26b2f1..83732d25a 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/MenuView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/MenuView.cs @@ -81,6 +81,7 @@ public string PrintGoalOptionsAndGetSelection() "Add Goal", "Delete Goal", "Extend Goal", + "View Completed Goals", "Return to Previous Menu" }) ); diff --git a/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs b/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs index 3302fef67..39440d3f3 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs @@ -164,9 +164,9 @@ public bool GetAddGoalConfirmationFromUser(GoalModel goal) string valueText = string.Empty; if (goal.Type == GoalType.TotalTime || goal.Type == GoalType.AverageTime) - valueText = TimeSpan.FromSeconds(goal.Value).ToString(); + valueText = TimeSpan.FromSeconds(goal.GoalValue).ToString(); if (goal.Type == GoalType.DaysPerPeriod) - valueText = TimeSpan.FromSeconds(goal.Value).TotalDays.ToString(); + valueText = TimeSpan.FromSeconds(goal.GoalValue).TotalDays.ToString(); var confirmation = AnsiConsole.Prompt( new TextPrompt($"Add [yellow]{goal.Type}[/] goal starting at [yellow]{goal.StartTime.ToString("yyyy-MM-dd HH:mm:ss")}[/] and ending at [yellow]{goal.EndTime.ToString("yyyy-MM-dd HH:mm:ss")}[/] with value [yellow]{valueText}[/]?") diff --git a/codingTracker.jzhartman/CodingTracker/Program.cs b/codingTracker.jzhartman/CodingTracker/Program.cs index 8daa3c78a..dfb710f94 100644 --- a/codingTracker.jzhartman/CodingTracker/Program.cs +++ b/codingTracker.jzhartman/CodingTracker/Program.cs @@ -6,6 +6,7 @@ namespace CodingTracker.ConsoleApp; internal class Program { + // TODO: Configure and run Code Cleanup static void Main(string[] args) { Batteries.Init(); From b764d57bc3ab39cc44530309a3a173c3e1358bc9 Mon Sep 17 00:00:00 2001 From: jzhartman Date: Mon, 24 Nov 2025 22:58:02 -0500 Subject: [PATCH 51/57] Completed evaluate goal. Working on delete goal. Next is update goal endt time --- .../GoalsController.cs | 41 ++++++++++++------- .../Interfaces/IGoalRepository.cs | 2 + .../Repositories/GoalRepository.cs | 11 +++++ .../CodingTracker.Services/GoalDataService.cs | 12 +++++- .../Interfaces/IGoalDataService.cs | 2 + .../CodingTracker.Views/ConsoleOutputView.cs | 32 +++++++++++++++ .../Interfaces/IConsoleOutputView.cs | 1 + .../CodingTracker.Views/UserInputView.cs | 39 ++++++++++-------- 8 files changed, 105 insertions(+), 35 deletions(-) diff --git a/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs b/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs index af3a74003..41e41e544 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs @@ -34,11 +34,12 @@ public void Run() while (!returnToMainMenu) { _outputView.WelcomeMessage(); - PrintAllActiveGoals(); var goalsInProgress = _goalService.GetAllGoalsByStatus(GoalStatus.InProgress); EvaluateGoals(goalsInProgress); + + var selection = _menuView.PrintGoalOptionsAndGetSelection(); switch (selection) @@ -49,6 +50,7 @@ public void Run() break; case "Delete Goal": // TODO: Create Handle Delete Goal Option + ManageGoalDelete(); break; case "Extend Goal": // TODO: Handle Extend Goal Option (basic update of end time only) @@ -228,34 +230,40 @@ private void ConfirmAddGoal(GoalModel goal) } + private void ManageGoalDelete() + { + _outputView.WelcomeMessage(); + var goals = _goalService.GetAllGoalsByStatus(GoalStatus.InProgress); + PrintGoalsList(goals); + var recordId = _inputView.GetRecordIdFromUser("delete", goals.Count()) - 1; + // Confirm delete + if (true) + _goalService.DeleteGoalById(recordId); + else + //cancellation message + } - - - - - private void PrintAllActiveGoals() + private void PrintGoalsList(List goals) { - var goals = _goalService.GetAllGoalsByStatus(GoalStatus.InProgress); - if (goals.Count <= 0) - _outputView.NoRecordsMessage("Goals"); + _outputView.NoRecordsMessage("goals"); else _outputView.PrintGoalListAsTable(goals); } + + private void EvaluateGoals(List goals) { - UpdateGoalStatusAndProgress(goals); //TODO: Remember to update the DB!!!!! - // Print Goals that have been completed! - // Print Goals that have failed -- Be nice - // Print all remaining goals with statuses + UpdateGoalStatusAndProgress(goals); + PrintGoalsList(goals); } private void UpdateGoalStatusAndProgress(List goals) @@ -279,10 +287,13 @@ private void UpdateGoalStatusAndProgress(List goals) _outputView.ErrorMessage("Goal Type", "Invalid Goal Type detected!"); break; } - } - } + _goalService.EvaluateGoal(goal); + if (goal.Status == GoalStatus.Complete || goal.Status == GoalStatus.Failed) + _outputView.GoalEvaluationMessage(goal); + } + } diff --git a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/IGoalRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/IGoalRepository.cs index 1b42f3cc3..7aa53e853 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/IGoalRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/IGoalRepository.cs @@ -4,6 +4,8 @@ namespace CodingTracker.Data.Interfaces; public interface IGoalRepository { void AddGoal(GoalModel goal); + void DeleteById(int id); + void EvaluateGoal(GoalDTO goal); List GetAllGoals(); List GetAllGoalsByStatus(GoalStatus status); int GetGoalCount(); diff --git a/codingTracker.jzhartman/CodingTracker.Data/Repositories/GoalRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Repositories/GoalRepository.cs index d86a372f6..32c1d92df 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Repositories/GoalRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Repositories/GoalRepository.cs @@ -31,6 +31,17 @@ public void UpdateGoal(GoalDTO goal) string sql = "update Goals set StartTime = @StartTime, EndTime = @EndTime, Type = @Type, Status = @Status where Id = @Id"; SaveData(sql, goal); } + public void DeleteById(int id) + { + string sql = $"delete from Goals where Id = {id}"; + SaveData(sql); + } + + public void EvaluateGoal(GoalDTO goal) + { + string sql = "update Goals set Status = @Status, GoalValue = @GoalValue, CurrentValue = @CurrentValue, Progress = @Progress where Id = @Id"; + SaveData(sql, goal); + } public int GetGoalCount() { diff --git a/codingTracker.jzhartman/CodingTracker.Services/GoalDataService.cs b/codingTracker.jzhartman/CodingTracker.Services/GoalDataService.cs index aa975aa74..5d564cc2b 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/GoalDataService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/GoalDataService.cs @@ -26,6 +26,14 @@ public void UpdateGoal(GoalDTO goal) { _repository.UpdateGoal(goal); } + public void DeleteGoalById(int id) + { + _repository.DeleteById(id); + } + public void EvaluateGoal(GoalDTO goal) + { + _repository.EvaluateGoal(goal); + } public List GetAllGoalsByStatus(GoalStatus status) { return _repository.GetAllGoalsByStatus(status); @@ -82,7 +90,7 @@ public void EvaluateAverageTimeGoal(GoalDTO goal, List var totalTime = SumTotalTimeFromSessions(codingSessions); goal.CurrentValue = (long)(totalTime / (goal.EndTime - goal.StartTime).TotalDays); - goal.Progress = (goal.CurrentValue / goal.GoalValue) * 100; + goal.Progress = ((double)goal.CurrentValue / goal.GoalValue) * 100; if (goal.Progress >= 100 && timeRemaining < 0) goal.Status = GoalStatus.Complete; @@ -99,7 +107,7 @@ public void EvaluateDaysPerPeriodGoal(GoalDTO goal, List= 100 && daysRemaining < 0) goal.Status = GoalStatus.Complete; diff --git a/codingTracker.jzhartman/CodingTracker.Services/Interfaces/IGoalDataService.cs b/codingTracker.jzhartman/CodingTracker.Services/Interfaces/IGoalDataService.cs index ca487f71a..ce74f7f76 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/Interfaces/IGoalDataService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/Interfaces/IGoalDataService.cs @@ -5,8 +5,10 @@ namespace CodingTracker.Services.Interfaces; public interface IGoalDataService { void AddGoal(GoalModel goal); + void DeleteGoalById(int id); void EvaluateAverageTimeGoal(GoalDTO goal, List codingSessions); void EvaluateDaysPerPeriodGoal(GoalDTO goal, List codingSessions); + void EvaluateGoal(GoalDTO goal); void EvaluateTotalTimeGoal(GoalDTO goal, List codingSessions); List GetAllGoals(); List GetAllGoalsByStatus(GoalStatus status); diff --git a/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs b/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs index ec74fb421..e2851e188 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs @@ -48,6 +48,8 @@ public void GoalCancelledMessage(string action) AddNewLines(1); AnsiConsole.MarkupInterpolated($"Cancelled {action} of goal!"); } + + public void PrintCodingSessionListAsTable(List sessions) { int count = 1; @@ -147,6 +149,36 @@ public void PrintGoalListAsTable(List goals) AddNewLines(2); } + public void GoalEvaluationMessage(GoalDTO goal) + { + string goalValueText = string.Empty; + string currentValueText = string.Empty; + string preamble = string.Empty; + + + if (goal.Type == GoalType.TotalTime || goal.Type == GoalType.AverageTime) + { + goalValueText = TimeSpan.FromSeconds(goal.GoalValue).ToString(); + currentValueText = TimeSpan.FromSeconds(goal.CurrentValue).ToString(); + } + if (goal.Type == GoalType.DaysPerPeriod) + { + goalValueText = TimeSpan.FromSeconds(goal.GoalValue).TotalDays.ToString(); + currentValueText = TimeSpan.FromSeconds(goal.CurrentValue).TotalDays.ToString(); + } + + if (goal.Status == GoalStatus.Complete) + preamble = "[bold green]CONGRATULATIONS![/] Successfully completed"; + if (goal.Status == GoalStatus.Failed) + preamble = "[bold red]FAILED[/] Did not successfully complete"; + + string message = $"{preamble} the goal to reach [yellow]{goalValueText}[/] [blue]{goal.Type}[/]\n\r" + + $"Between the times [green]{goal.StartTime.ToString("yyyy-MM-dd HH:mm:ss")}[/] and [red]{goal.EndTime.ToString("yyyy-MM-dd HH:mm:ss")}[/]\n\r" + + $"With a total completed value of [yellow]{currentValueText}[/] for a total of [yellow]{goal.Progress:f1}%[/]"; + + AnsiConsole.Markup(message); + AddNewLines(2); + } diff --git a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IConsoleOutputView.cs b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IConsoleOutputView.cs index 5e7f72d6f..28587fbbc 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IConsoleOutputView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IConsoleOutputView.cs @@ -14,4 +14,5 @@ public interface IConsoleOutputView void PrintGoalListAsTable(List goals); void NoRecordsMessage(string recordType); void GoalCancelledMessage(string action); + void GoalEvaluationMessage(GoalDTO goal); } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs b/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs index 39440d3f3..c7bea99ab 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs @@ -99,25 +99,7 @@ public int GetRecordIdFromUser(string action, int max) return id; } - public long GetGoalValueTime(GoalType goalType) - { - string promptText = "Please enter the value for the "; - if (goalType == GoalType.TotalTime) promptText += "total time to code within this timeframe using the format [yellow]d.HH:mm:ss[/]:"; - if (goalType == GoalType.AverageTime) promptText += "average daily time coding within this timeframe using the format [yellow]HH:mm:ss[/]:"; - - var goalValue = AnsiConsole.Prompt( - new TextPrompt(promptText)); - - return (long)goalValue.TotalSeconds; - } - - public long GetGoalValueForDaysPerPeriod() - { - var goalValue = AnsiConsole.Prompt( - new TextPrompt($"Please enter the goal value for the days per period:")); - return goalValue; - } public bool GetAddSessionConfirmationFromUser(CodingSession session) { @@ -159,6 +141,27 @@ public bool GetDeleteSessionConfirmationFromUser(CodingSessionDataRecord session return confirmation; } + + + + public long GetGoalValueTime(GoalType goalType) + { + string promptText = "Please enter the value for the "; + if (goalType == GoalType.TotalTime) promptText += "total time to code within this timeframe using the format [yellow]d.HH:mm:ss[/]:"; + if (goalType == GoalType.AverageTime) promptText += "average daily time coding within this timeframe using the format [yellow]HH:mm:ss[/]:"; + + var goalValue = AnsiConsole.Prompt( + new TextPrompt(promptText)); + + return (long)goalValue.TotalSeconds; + } + public long GetGoalValueForDaysPerPeriod() + { + var goalValue = AnsiConsole.Prompt( + new TextPrompt($"Please enter the goal value for the days per period:")); + + return goalValue; + } public bool GetAddGoalConfirmationFromUser(GoalModel goal) { string valueText = string.Empty; From eb36cf9168187c4f4b8e5629e3e9e206e3458b46 Mon Sep 17 00:00:00 2001 From: jzhartman Date: Tue, 25 Nov 2025 22:33:40 -0500 Subject: [PATCH 52/57] Goal Delete done. Print Completed Goal List done --- .../GoalsController.cs | 27 ++++++++---- .../CodingTracker.Services/GoalDataService.cs | 4 ++ .../CodingTracker.Views/ConsoleOutputView.cs | 4 -- .../Interfaces/IUserInputView.cs | 2 + .../CodingTracker.Views/UserInputView.cs | 43 ++++++++++++++++--- 5 files changed, 62 insertions(+), 18 deletions(-) diff --git a/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs b/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs index 41e41e544..7182af0f2 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs @@ -39,7 +39,6 @@ public void Run() EvaluateGoals(goalsInProgress); - var selection = _menuView.PrintGoalOptionsAndGetSelection(); switch (selection) @@ -49,14 +48,13 @@ public void Run() ConfirmAddGoal(goal); break; case "Delete Goal": - // TODO: Create Handle Delete Goal Option ManageGoalDelete(); break; case "Extend Goal": // TODO: Handle Extend Goal Option (basic update of end time only) break; case "View Completed Goals": - // TODO: Handle View Completed Goals + ViewCompletedGoals(); break; case "Return to Previous Menu": returnToMainMenu = true; @@ -70,6 +68,15 @@ public void Run() } } + private void ViewCompletedGoals() + { + var goals = _goalService.GetAllGoalsByStatus(GoalStatus.Complete); + + _outputView.WelcomeMessage(); + PrintGoalsList(goals); + _inputView.PressAnyKeyToContinue(); + } + private GoalModel GetGoalDataFromUser() { var startTime = GetStartTimeFromUser(); @@ -226,6 +233,7 @@ private void ConfirmAddGoal(GoalModel goal) else { _outputView.GoalCancelledMessage("addition"); + _inputView.PressAnyKeyToContinue(); } } @@ -233,18 +241,21 @@ private void ConfirmAddGoal(GoalModel goal) private void ManageGoalDelete() { _outputView.WelcomeMessage(); - var goals = _goalService.GetAllGoalsByStatus(GoalStatus.InProgress); + var goals = _goalService.GetAllGoals(); PrintGoalsList(goals); var recordId = _inputView.GetRecordIdFromUser("delete", goals.Count()) - 1; - // Confirm delete - - if (true) + if (_inputView.GetDeleteGoalConfirmationFromUser(goals[recordId])) + { _goalService.DeleteGoalById(recordId); + } else - //cancellation message + { + _outputView.GoalCancelledMessage("deletion"); + _inputView.PressAnyKeyToContinue(); + } } diff --git a/codingTracker.jzhartman/CodingTracker.Services/GoalDataService.cs b/codingTracker.jzhartman/CodingTracker.Services/GoalDataService.cs index 5d564cc2b..1b80ef610 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/GoalDataService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/GoalDataService.cs @@ -106,6 +106,10 @@ public void EvaluateDaysPerPeriodGoal(GoalDTO goal, List (goal.EndTime - goal.StartTime).TotalDays) + daysRemaining = (goal.EndTime - goal.StartTime).TotalDays; + + goal.CurrentValue = GetUniqueDaysPerPeriod(codingSessions) * 86400; goal.Progress = ((double)goal.CurrentValue / goal.GoalValue) * 100; diff --git a/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs b/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs index e2851e188..71e65c296 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs @@ -148,7 +148,6 @@ public void PrintGoalListAsTable(List goals) AnsiConsole.Write(grid); AddNewLines(2); } - public void GoalEvaluationMessage(GoalDTO goal) { string goalValueText = string.Empty; @@ -182,9 +181,6 @@ public void GoalEvaluationMessage(GoalDTO goal) - - - private void AddNewLines(int lines) { for (int i = 0; i < lines; i++) diff --git a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInputView.cs b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInputView.cs index edd45eee3..62500e637 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInputView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInputView.cs @@ -13,5 +13,7 @@ public interface IUserInputView long GetGoalValueTime(GoalType goalType); long GetGoalValueForDaysPerPeriod(); bool GetAddGoalConfirmationFromUser(GoalModel goal); + bool GetDeleteGoalConfirmationFromUser(GoalDTO goal); + void PressAnyKeyToContinue(); //DateTime GetUpdatedStartTimeFromUser(DateTime originalTime); } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs b/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs index c7bea99ab..905a858d7 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs @@ -164,15 +164,25 @@ public long GetGoalValueForDaysPerPeriod() } public bool GetAddGoalConfirmationFromUser(GoalModel goal) { - string valueText = string.Empty; + string promptText = $"Add {GenerateGoalConfirmationPromptText( new GoalDTO {Id = 0, Type = goal.Type, StartTime = goal.StartTime, + EndTime = goal.EndTime, Status = goal.Status, + GoalValue = goal.GoalValue, CurrentValue = goal.CurrentValue, + Progress = goal.Progress})}"; - if (goal.Type == GoalType.TotalTime || goal.Type == GoalType.AverageTime) - valueText = TimeSpan.FromSeconds(goal.GoalValue).ToString(); - if (goal.Type == GoalType.DaysPerPeriod) - valueText = TimeSpan.FromSeconds(goal.GoalValue).TotalDays.ToString(); + var confirmation = AnsiConsole.Prompt( + new TextPrompt($"{promptText}?") + .AddChoice(true) + .AddChoice(false) + .WithConverter(choice => choice ? "y" : "n")); + + return confirmation; + } + public bool GetDeleteGoalConfirmationFromUser(GoalDTO goal) + { + string promptText = $"Confirm deletion of {GenerateGoalConfirmationPromptText(goal)}"; var confirmation = AnsiConsole.Prompt( - new TextPrompt($"Add [yellow]{goal.Type}[/] goal starting at [yellow]{goal.StartTime.ToString("yyyy-MM-dd HH:mm:ss")}[/] and ending at [yellow]{goal.EndTime.ToString("yyyy-MM-dd HH:mm:ss")}[/] with value [yellow]{valueText}[/]?") + new TextPrompt(promptText) .AddChoice(true) .AddChoice(false) .WithConverter(choice => choice ? "y" : "n")); @@ -180,9 +190,30 @@ public bool GetAddGoalConfirmationFromUser(GoalModel goal) return confirmation; } + public void PressAnyKeyToContinue() + { + AnsiConsole.WriteLine(); + AnsiConsole.Markup("[yellow]Press any key to continue...[/]"); + Console.ReadKey(true); + } + private string GenerateGoalConfirmationPromptText(GoalDTO goal) + { + string valueText = string.Empty; + + if (goal.Type == GoalType.TotalTime || goal.Type == GoalType.AverageTime) + valueText = TimeSpan.FromSeconds(goal.GoalValue).ToString(); + if (goal.Type == GoalType.DaysPerPeriod) + valueText = TimeSpan.FromDays(goal.GoalValue).ToString("%d"); + + string promptText = "[yellow]" + goal.Type + "[/] goal " + + "starting at [yellow]" + goal.StartTime.ToString("yyyy-MM-dd HH:mm:ss") + "[/] " + + "and ending at [yellow]" + goal.EndTime.ToString("yyyy-MM-dd HH:mm:ss") + "[/] " + + "with value [yellow]" + valueText + "[/]"; + return promptText; + } private string ConvertTimeFromSecondsToText(double input) { int miliseconds = TimeSpan.FromSeconds(input).Milliseconds; From 49e31512b8ab270cd27c9554b98b29ab15739110 Mon Sep 17 00:00:00 2001 From: jzhartman Date: Tue, 25 Nov 2025 23:10:33 -0500 Subject: [PATCH 53/57] Fixed some UI Goals almost done Needs ExtendGoal method and then proofing --- .../GoalsController.cs | 13 ++--- .../CodingTracker.Services/GoalDataService.cs | 8 ++-- .../CodingTracker.Views/ConsoleOutputView.cs | 47 +++++++++++-------- 3 files changed, 39 insertions(+), 29 deletions(-) diff --git a/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs b/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs index 7182af0f2..695694b52 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs @@ -175,7 +175,7 @@ private long GetGoalValue(DateTime startTime, DateTime endTime, GoalType goalTyp { valueIsValid = true; output = result.Value; - _outputView.ConfirmationMessage(result.Value.ToString()); //ToDo: Fix printing -- This prints all values as seconds + _outputView.ConfirmationMessage(result.Value.ToString()); } else { @@ -258,19 +258,16 @@ private void ManageGoalDelete() } } - - private void PrintGoalsList(List goals) { + _outputView.WelcomeMessage(); + if (goals.Count <= 0) _outputView.NoRecordsMessage("goals"); else _outputView.PrintGoalListAsTable(goals); } - - - private void EvaluateGoals(List goals) { UpdateGoalStatusAndProgress(goals); @@ -302,7 +299,11 @@ private void UpdateGoalStatusAndProgress(List goals) _goalService.EvaluateGoal(goal); if (goal.Status == GoalStatus.Complete || goal.Status == GoalStatus.Failed) + { + _outputView.WelcomeMessage(); _outputView.GoalEvaluationMessage(goal); + _inputView.PressAnyKeyToContinue(); + } } } diff --git a/codingTracker.jzhartman/CodingTracker.Services/GoalDataService.cs b/codingTracker.jzhartman/CodingTracker.Services/GoalDataService.cs index 1b80ef610..6d9c365a3 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/GoalDataService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/GoalDataService.cs @@ -110,7 +110,7 @@ public void EvaluateDaysPerPeriodGoal(GoalDTO goal, List= 100 && daysRemaining < 0) @@ -123,9 +123,11 @@ public void EvaluateDaysPerPeriodGoal(GoalDTO goal, List codingSessions) + private int GetUniqueDaysPerPeriod(List codingSessions) { + if (codingSessions.Count == 0) + return 0; + int uniqueDays = 1; for (int i = 1; i < codingSessions.Count; i++) diff --git a/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs b/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs index 71e65c296..45521c928 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs @@ -129,19 +129,30 @@ public void PrintGoalListAsTable(List goals) grid.AddColumn(); grid.AddColumn(); grid.AddColumn(); + grid.AddColumn(); + grid.AddColumn(); + grid.AddColumn(); grid.AddRow(new Text[] {new Text("Id").Centered(), + new Text("Type").Centered(), + new Text("Status").Centered(), new Text("Start Time").Centered(), new Text("End Time").Centered(), - new Text("Status").Centered(), - new Text("Type").Centered()}); + new Text("Goal Value").Centered(), + new Text("Current Value").Centered(), + new Text("Progress").Centered()}); + foreach (var goal in goals) { + grid.AddRow(new string[] { $"[blue]{count}[/]", + $"{goal.Type}", + $"{goal.Status}", $"{goal.StartTime.ToString("yyyy-MM-dd")} [yellow]{goal.StartTime.ToString("HH:mm:ss")}[/]", $"{goal.EndTime.ToString("yyyy-MM-dd")} [yellow]{goal.EndTime.ToString("HH:mm:ss")}[/]", - $"{goal.Status}", - $"{goal.Type}"}); + $"{GenerateValueText(goal.Type, goal.GoalValue)}", + $"{GenerateValueText(goal.Type, goal.CurrentValue)}", + $"{goal.Progress:f1}%"}); count++; } @@ -150,37 +161,33 @@ public void PrintGoalListAsTable(List goals) } public void GoalEvaluationMessage(GoalDTO goal) { - string goalValueText = string.Empty; - string currentValueText = string.Empty; string preamble = string.Empty; - - if (goal.Type == GoalType.TotalTime || goal.Type == GoalType.AverageTime) - { - goalValueText = TimeSpan.FromSeconds(goal.GoalValue).ToString(); - currentValueText = TimeSpan.FromSeconds(goal.CurrentValue).ToString(); - } - if (goal.Type == GoalType.DaysPerPeriod) - { - goalValueText = TimeSpan.FromSeconds(goal.GoalValue).TotalDays.ToString(); - currentValueText = TimeSpan.FromSeconds(goal.CurrentValue).TotalDays.ToString(); - } - if (goal.Status == GoalStatus.Complete) preamble = "[bold green]CONGRATULATIONS![/] Successfully completed"; if (goal.Status == GoalStatus.Failed) preamble = "[bold red]FAILED[/] Did not successfully complete"; - string message = $"{preamble} the goal to reach [yellow]{goalValueText}[/] [blue]{goal.Type}[/]\n\r" + + string message = $"{preamble} the goal to reach [yellow]{GenerateValueText(goal.Type, goal.GoalValue)}[/] [blue]{goal.Type}[/]\n\r" + $"Between the times [green]{goal.StartTime.ToString("yyyy-MM-dd HH:mm:ss")}[/] and [red]{goal.EndTime.ToString("yyyy-MM-dd HH:mm:ss")}[/]\n\r" + - $"With a total completed value of [yellow]{currentValueText}[/] for a total of [yellow]{goal.Progress:f1}%[/]"; + $"With a total completed value of [yellow]{GenerateValueText(goal.Type, goal.CurrentValue)}[/] for a total of [yellow]{goal.Progress:f1}%[/]"; AnsiConsole.Markup(message); AddNewLines(2); } + private string GenerateValueText(GoalType goalType, long value) + { + var valueText = string.Empty; + + if (goalType == GoalType.TotalTime || goalType == GoalType.AverageTime) + valueText = TimeSpan.FromSeconds(value).ToString(); + if (goalType == GoalType.DaysPerPeriod) + valueText = TimeSpan.FromDays(value).ToString("%d"); + return valueText; + } private void AddNewLines(int lines) { for (int i = 0; i < lines; i++) From 56835a1b4057f1c8238edf233991034d08a44211 Mon Sep 17 00:00:00 2001 From: jzhartman Date: Mon, 1 Dec 2025 23:33:49 -0500 Subject: [PATCH 54/57] Added goals table seed data Bug fixes and testing complete Added goal extension to future items --- .../EntryListController.cs | 4 +- .../GoalsController.cs | 50 ------------------- .../CodingTracker.Data/DatabaseInitializer.cs | 29 +++++++++-- .../CodingTracker.Services/GoalDataService.cs | 2 +- .../CodingTracker.Views/ConsoleOutputView.cs | 5 +- .../CodingTracker.Views/MenuView.cs | 1 - .../CodingTracker.Views/UserInputView.cs | 5 +- 7 files changed, 35 insertions(+), 61 deletions(-) diff --git a/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs b/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs index 5bd7b9738..c3ddb1c2c 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs @@ -113,7 +113,7 @@ private DateTime GetUpdatedStartTime(CodingSessionDataRecord session) while (startTimeValid == false) { - var newStartTime = _inputView.GetTimeFromUser("new start time", true); + var newStartTime = _inputView.GetTimeFromUser("new [green]Start Time[/]", true); var result = _service.ValidateUpdatedStartTime(session, newStartTime); if (result.IsValid) @@ -136,7 +136,7 @@ private DateTime GetUpdatedEndTime(CodingSessionDataRecord session, DateTime new while (startTimeValid == false) { - var newEndTime = _inputView.GetTimeFromUser("new end time", true); + var newEndTime = _inputView.GetTimeFromUser("new [red]End Time[/]", true); var result = _service.ValidateUpdatedEndTime(session, newStartTime, newEndTime); if (result.IsValid) diff --git a/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs b/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs index 695694b52..cb908d445 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs @@ -1,10 +1,7 @@ using CodingTracker.Controller.Interfaces; using CodingTracker.Models.Entities; -using CodingTracker.Models.Validation; using CodingTracker.Services.Interfaces; using CodingTracker.Views.Interfaces; -using System; -using static System.Collections.Specialized.BitVector32; namespace CodingTracker.Controller; public class GoalsController : IGoalsController @@ -27,8 +24,6 @@ public GoalsController( IGoalDataService goalService, ICodingSessionDataService public void Run() { - GenerateDummyGoal(); - bool returnToMainMenu = false; while (!returnToMainMenu) @@ -38,7 +33,6 @@ public void Run() var goalsInProgress = _goalService.GetAllGoalsByStatus(GoalStatus.InProgress); EvaluateGoals(goalsInProgress); - var selection = _menuView.PrintGoalOptionsAndGetSelection(); switch (selection) @@ -50,21 +44,13 @@ public void Run() case "Delete Goal": ManageGoalDelete(); break; - case "Extend Goal": - // TODO: Handle Extend Goal Option (basic update of end time only) - break; case "View Completed Goals": ViewCompletedGoals(); break; case "Return to Previous Menu": returnToMainMenu = true; break; - } - - - - } } @@ -76,7 +62,6 @@ private void ViewCompletedGoals() PrintGoalsList(goals); _inputView.PressAnyKeyToContinue(); } - private GoalModel GetGoalDataFromUser() { var startTime = GetStartTimeFromUser(); @@ -306,39 +291,4 @@ private void UpdateGoalStatusAndProgress(List goals) } } } - - - - - - - - - - - - private void GenerateDummyGoal() - { - if (_goalService.GetGoalCount() <= 0) - { - GoalModel goal = new GoalModel - { - StartTime = DateTime.Now.AddDays(-5), - EndTime = DateTime.Now.AddDays(5), - Status = GoalStatus.InProgress, - Type = GoalType.DaysPerPeriod, - GoalValue = (long)TimeSpan.FromDays(5).TotalSeconds, - CurrentValue = 3, - Progress = 60 - }; - - - _goalService.AddGoal(goal); - } - } - - - - - } diff --git a/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs b/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs index 9f4106437..60626eef5 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs @@ -1,4 +1,5 @@ using CodingTracker.Data.Interfaces; +using CodingTracker.Models.Entities; using Dapper; using Microsoft.Data.Sqlite; using static System.Runtime.InteropServices.JavaScript.JSType; @@ -7,6 +8,8 @@ namespace CodingTracker.Data; public class DatabaseInitializer : IDatabaseInitializer { private readonly ISqliteConnectionFactory _connectionFactory; + private readonly int _seedRecordCount = 100; + public DatabaseInitializer(ISqliteConnectionFactory connectionfactory) { @@ -70,23 +73,26 @@ private void SeedData(string tableName) if (tableName == "CodingSessions") sql = CreateCodingSessionsSqlString(); + if (tableName == "Goals") + sql = CreateGoalsSqlString(); + command.CommandText = sql; command.ExecuteNonQuery(); } + private string CreateCodingSessionsSqlString() { string sql = "insert into CodingSessions(StartTime, EndTime, Duration)\nValues\n"; Random rand = new Random(); - int seedRecordCount = 100; - DateOnly date = DateOnly.FromDateTime(DateTime.Now.AddDays(-5 * (seedRecordCount+1))); + DateOnly date = DateOnly.FromDateTime(DateTime.Now.AddDays(-5 * (_seedRecordCount+1))); TimeOnly time = new TimeOnly(21,0,0); DateTime startDate = date.ToDateTime(time); DateTime endDate = startDate.AddHours(2); TimeSpan duration = endDate - startDate; - for (int i = 0; i < seedRecordCount; i++) + for (int i = 0; i < _seedRecordCount; i++) { if (i != 0) sql += ",\n"; @@ -97,6 +103,23 @@ private string CreateCodingSessionsSqlString() } sql += ";"; + return sql; + } + private string CreateGoalsSqlString() + { + + var startTime = DateTime.Now.AddDays(-4 * (_seedRecordCount + 1)); + var endTime = startTime.AddDays(30); + + string sql = "insert into Goals(Type, StartTime, EndTime, Status, GoalValue, CurrentValue, Progress) "; + + sql += "Values "; + sql += $"(2, '{startTime.ToString("yyyy-MM-dd HH:mm:ss")}', '{endTime.ToString("yyyy-MM-dd HH:mm:ss")}', 0, 15, 0, 0),"; + sql += $"(0, '{startTime.ToString("yyyy-MM-dd HH:mm:ss")}', '{endTime.ToString("yyyy-MM-dd HH:mm:ss")}', 0, 108000, 0, 0),"; + sql += $"(1, '{startTime.ToString("yyyy-MM-dd HH:mm:ss")}', '{endTime.ToString("yyyy-MM-dd HH:mm:ss")}', 0, 1800, 0, 0)"; + + sql += ";"; + return sql; } } diff --git a/codingTracker.jzhartman/CodingTracker.Services/GoalDataService.cs b/codingTracker.jzhartman/CodingTracker.Services/GoalDataService.cs index 6d9c365a3..345ab9a97 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/GoalDataService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/GoalDataService.cs @@ -72,7 +72,7 @@ public void EvaluateTotalTimeGoal(GoalDTO goal, List co var timeRemaining = (goal.EndTime - DateTime.Now).TotalSeconds; goal.CurrentValue = SumTotalTimeFromSessions(codingSessions); - goal.Progress = (goal.CurrentValue / goal.GoalValue) * 100; + goal.Progress = ((double)goal.CurrentValue / goal.GoalValue) * 100; if (goal.Progress >= 100 && timeRemaining < 0) goal.Status = GoalStatus.Complete; diff --git a/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs b/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs index 45521c928..e19daa71a 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs @@ -88,6 +88,7 @@ public void PrintCodingSessionToUpdateById(CodingSessionDataRecord session, int $"{session.EndTime.ToString("yyyy-MM-dd")} [yellow]{session.EndTime.ToString("HH:mm:ss")}[/]", $"{ConvertTimeFromSecondsToText(session.Duration)}" }); + AddNewLines(1); AnsiConsole.Write("Updating Record: "); AnsiConsole.Write(grid); @@ -100,8 +101,8 @@ public void PrintReportDataAsTable(ReportModel report) var grid = new Grid(); grid.AddColumn(); grid.AddColumn(); - grid.AddRow(new string[] { "[bold blue]First Entry:[/]", $"{report.FirstEntry.StartTime.ToString("yyyy-MM-dd")} to {report.FirstEntry.EndTime.ToString("yyyy-MM-dd")}" }); - grid.AddRow(new string[] { "[bold blue]Last Entry:[/]", $"{report.LastEntry.StartTime.ToString("yyyy-MM-dd")} to {report.LastEntry.EndTime.ToString("yyyy-MM-dd")}" }); + grid.AddRow(new string[] { "[bold blue]First Entry:[/]", $"[green]{report.FirstEntry.StartTime.ToString("yyyy-MM-dd HH:mm:ss")}[/] to [red]{report.FirstEntry.EndTime.ToString("yyyy-MM-dd HH:mm:ss")}[/]" }); + grid.AddRow(new string[] { "[bold blue]Last Entry:[/]", $"[green]{report.LastEntry.StartTime.ToString("yyyy-MM-dd HH:mm:ss")}[/] to [red]{report.LastEntry.EndTime.ToString("yyyy-MM-dd HH:mm:ss")}[/]" }); grid.AddRow(new string[] { "[bold blue]Total Sessions:[/]", $"{report.SessionCount}" }); grid.AddRow(new string[] { "[bold blue]Total Time:[/]", $"{ConvertTimeFromSecondsToText(report.TotalTime)}" }); grid.AddRow(new string[] { "[bold blue]Average Session:[/]", $"{ConvertTimeFromSecondsToText(report.AverageTime)}"}); diff --git a/codingTracker.jzhartman/CodingTracker.Views/MenuView.cs b/codingTracker.jzhartman/CodingTracker.Views/MenuView.cs index 83732d25a..5a4736c6d 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/MenuView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/MenuView.cs @@ -80,7 +80,6 @@ public string PrintGoalOptionsAndGetSelection() { "Add Goal", "Delete Goal", - "Extend Goal", "View Completed Goals", "Return to Previous Menu" }) diff --git a/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs b/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs index 905a858d7..36b660c3b 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs @@ -91,8 +91,8 @@ public int GetRecordIdFromUser(string action, int max) new TextPrompt($"Please enter the [yellow]ID[/] of the record you wish to {action.ToLower()}:") .Validate(input => { - if (input < 1) return Spectre.Console.ValidationResult.Error("Too low"); - else if (input > max) return Spectre.Console.ValidationResult.Error("Too high"); + if (input < 1) return Spectre.Console.ValidationResult.Error($"[red]ERROR:[/] A record for this value does not exist. Please enter a value between [yellow]1[/] and [yellow]{max}[/].\r\n"); + else if (input > max) return Spectre.Console.ValidationResult.Error($"[red]ERROR:[/] A record for this value does not exist. Please enter a value between [yellow]1[/] and [yellow]{max}[/].\r\n"); else return Spectre.Console.ValidationResult.Success(); })); @@ -127,6 +127,7 @@ public bool GetUpdateSessionConfirmationFromUser(CodingSessionDataRecord session } public bool GetDeleteSessionConfirmationFromUser(CodingSessionDataRecord session) { + AnsiConsole.WriteLine(); var duration = ConvertTimeFromSecondsToText(session.Duration); string promptText = $"Confirm deletion of coding session with start time [yellow]{session.StartTime.ToString("yyyy-MM-dd HH:mm:ss")}[/]" From eb60174673744ddd48f0ba6ae03b3aeb6b5e77a0 Mon Sep 17 00:00:00 2001 From: jzhartman Date: Mon, 1 Dec 2025 23:41:02 -0500 Subject: [PATCH 55/57] Removed and sorted usings Doublechecked namespace indentation --- .../CodingTracker.Controller/EntryListController.cs | 1 - .../Interfaces/IGoalsController.cs | 1 - .../Interfaces/IReportsController.cs | 1 - .../CodingTracker.Controller/MainMenuController.cs | 11 +++++------ .../CodingTracker.Controller/ReportsController.cs | 5 ++--- .../TrackSessionController.cs | 1 - .../CodingTracker.Data/DatabaseInitializer.cs | 2 -- .../Interfaces/ICodingSessionRepository.cs | 1 + .../CodingTracker.Data/Parameters/GoalStatusQuery.cs | 5 ----- .../Repositories/CodingSessionRepository.cs | 4 ---- .../Repositories/RepositoryGenerics.cs | 5 ----- .../TypeHandlers/GoalStatusHandler.cs | 5 ----- .../CodingTracker.Models/Entities/Enums.cs | 9 +-------- .../CodingTracker.Models/Entities/GoalDTO.cs | 8 +------- .../CodingTracker.Models/Entities/GoalModel.cs | 8 +------- .../CodingTracker.Models/Entities/ReportModel.cs | 8 +------- .../CodingTracker.Services/GoalDataService.cs | 5 ----- .../CodingTracker.Views/ConsoleOutputView.cs | 1 - .../CodingTracker.Views/Interfaces/IUserInputView.cs | 1 - .../CodingTracker.Views/UserInputView.cs | 1 - codingTracker.jzhartman/CodingTracker/Program.cs | 1 - 21 files changed, 12 insertions(+), 72 deletions(-) diff --git a/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs b/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs index c3ddb1c2c..358d70324 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/EntryListController.cs @@ -1,6 +1,5 @@ using CodingTracker.Models.Entities; using CodingTracker.Services.Interfaces; -using CodingTracker.Views; using CodingTracker.Views.Interfaces; namespace CodingTracker.Controller.Interfaces; diff --git a/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/IGoalsController.cs b/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/IGoalsController.cs index ee303fa7a..7ad682c66 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/IGoalsController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/IGoalsController.cs @@ -1,5 +1,4 @@ namespace CodingTracker.Controller.Interfaces; - public interface IGoalsController { void Run(); diff --git a/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/IReportsController.cs b/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/IReportsController.cs index 6e61f5fde..c177cd555 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/IReportsController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/Interfaces/IReportsController.cs @@ -1,5 +1,4 @@ namespace CodingTracker.Controller.Interfaces; - public interface IReportsController { void Run(); diff --git a/codingTracker.jzhartman/CodingTracker.Controller/MainMenuController.cs b/codingTracker.jzhartman/CodingTracker.Controller/MainMenuController.cs index 32d59e7be..4fbeeb666 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/MainMenuController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/MainMenuController.cs @@ -1,6 +1,5 @@ using CodingTracker.Controller.Interfaces; using CodingTracker.Services.Interfaces; -using CodingTracker.Views; using CodingTracker.Views.Interfaces; namespace CodingTracker.Controller; @@ -38,19 +37,19 @@ public void Run() switch (selection) { - case "Track Session": // Submenu: Enter times/Stopwatch/Return + case "Track Session": _trackController.Run(); break; - case "View/Manage Entries": // Submenu: View Entries (range or all)/Update/Delete + case "View/Manage Entries": _entryListController.Run(); break; - case "View Reports": // Enter Range-Period??? --> Print all records for period --> Print report data + case "View Reports": _reportsController.Run(); break; - case "Manage Goal": // Print current goal+progress/Give option to change goal + case "Manage Goal": _goalsController.Run(); break; - case "Exit": // Generic goodbye message + case "Exit": exitApp = true; break; default: diff --git a/codingTracker.jzhartman/CodingTracker.Controller/ReportsController.cs b/codingTracker.jzhartman/CodingTracker.Controller/ReportsController.cs index f24f0e129..638576bb0 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/ReportsController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/ReportsController.cs @@ -1,7 +1,6 @@ -using CodingTracker.Models.Entities; -using CodingTracker.Controller.Interfaces; +using CodingTracker.Controller.Interfaces; +using CodingTracker.Models.Entities; using CodingTracker.Services.Interfaces; -using CodingTracker.Views; using CodingTracker.Views.Interfaces; namespace CodingTracker.Controller; diff --git a/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs b/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs index 1b279ab21..226e6cbf1 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs @@ -1,7 +1,6 @@ using CodingTracker.Controller.Interfaces; using CodingTracker.Models.Entities; using CodingTracker.Services.Interfaces; -using CodingTracker.Views; using CodingTracker.Views.Interfaces; namespace CodingTracker.Controller; diff --git a/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs b/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs index 60626eef5..1e80dec32 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/DatabaseInitializer.cs @@ -1,8 +1,6 @@ using CodingTracker.Data.Interfaces; -using CodingTracker.Models.Entities; using Dapper; using Microsoft.Data.Sqlite; -using static System.Runtime.InteropServices.JavaScript.JSType; namespace CodingTracker.Data; public class DatabaseInitializer : IDatabaseInitializer diff --git a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs index 95f7577ee..0a86ac9d1 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Interfaces/ICodingSessionRepository.cs @@ -1,4 +1,5 @@ using CodingTracker.Models.Entities; + namespace CodingTracker.Data.Interfaces; public interface ICodingSessionRepository { diff --git a/codingTracker.jzhartman/CodingTracker.Data/Parameters/GoalStatusQuery.cs b/codingTracker.jzhartman/CodingTracker.Data/Parameters/GoalStatusQuery.cs index 64df89802..ac342f4af 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Parameters/GoalStatusQuery.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Parameters/GoalStatusQuery.cs @@ -1,9 +1,4 @@ using CodingTracker.Models.Entities; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace CodingTracker.Data.Parameters; public class GoalStatusQuery diff --git a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs index 63af52a63..6498e0c28 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs @@ -108,8 +108,4 @@ public void DeleteById(int id) string sql = $"delete from CodingSessions where Id = {id}"; SaveData(sql); } - - - - } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Data/Repositories/RepositoryGenerics.cs b/codingTracker.jzhartman/CodingTracker.Data/Repositories/RepositoryGenerics.cs index d5ae50aa8..13993ac33 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Repositories/RepositoryGenerics.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Repositories/RepositoryGenerics.cs @@ -1,9 +1,4 @@ using CodingTracker.Data.Interfaces; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Dapper; namespace CodingTracker.Data.Repositories; diff --git a/codingTracker.jzhartman/CodingTracker.Data/TypeHandlers/GoalStatusHandler.cs b/codingTracker.jzhartman/CodingTracker.Data/TypeHandlers/GoalStatusHandler.cs index 4d0397634..24dda737b 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/TypeHandlers/GoalStatusHandler.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/TypeHandlers/GoalStatusHandler.cs @@ -1,11 +1,6 @@ using CodingTracker.Models.Entities; using Dapper; -using System; -using System.Collections.Generic; using System.Data; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace CodingTracker.Data.TypeHandlers; public class GoalStatusHandler : SqlMapper.TypeHandler diff --git a/codingTracker.jzhartman/CodingTracker.Models/Entities/Enums.cs b/codingTracker.jzhartman/CodingTracker.Models/Entities/Enums.cs index 1a53df01c..f58b1fe7d 100644 --- a/codingTracker.jzhartman/CodingTracker.Models/Entities/Enums.cs +++ b/codingTracker.jzhartman/CodingTracker.Models/Entities/Enums.cs @@ -1,11 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace CodingTracker.Models.Entities; - +namespace CodingTracker.Models.Entities; public enum GoalType { TotalTime, diff --git a/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalDTO.cs b/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalDTO.cs index 26272fb27..c8c2537ef 100644 --- a/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalDTO.cs +++ b/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalDTO.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace CodingTracker.Models.Entities; +namespace CodingTracker.Models.Entities; public class GoalDTO { public int Id { get; set; } diff --git a/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalModel.cs b/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalModel.cs index e67fcef99..aa8697aa5 100644 --- a/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalModel.cs +++ b/codingTracker.jzhartman/CodingTracker.Models/Entities/GoalModel.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace CodingTracker.Models.Entities; +namespace CodingTracker.Models.Entities; public class GoalModel { public GoalType Type { get; set; } diff --git a/codingTracker.jzhartman/CodingTracker.Models/Entities/ReportModel.cs b/codingTracker.jzhartman/CodingTracker.Models/Entities/ReportModel.cs index 14d926ce0..6cdbee599 100644 --- a/codingTracker.jzhartman/CodingTracker.Models/Entities/ReportModel.cs +++ b/codingTracker.jzhartman/CodingTracker.Models/Entities/ReportModel.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace CodingTracker.Models.Entities; +namespace CodingTracker.Models.Entities; public class ReportModel { public List SessionList { get; set; } diff --git a/codingTracker.jzhartman/CodingTracker.Services/GoalDataService.cs b/codingTracker.jzhartman/CodingTracker.Services/GoalDataService.cs index 345ab9a97..0cb572b07 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/GoalDataService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/GoalDataService.cs @@ -2,11 +2,6 @@ using CodingTracker.Models.Entities; using CodingTracker.Models.Validation; using CodingTracker.Services.Interfaces; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace CodingTracker.Services; public class GoalDataService : IGoalDataService diff --git a/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs b/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs index e19daa71a..46aac9e6c 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/ConsoleOutputView.cs @@ -1,7 +1,6 @@ using CodingTracker.Models.Entities; using CodingTracker.Views.Interfaces; using Spectre.Console; -using System.Reflection.Metadata; namespace CodingTracker.Views; public class ConsoleOutputView : IConsoleOutputView diff --git a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInputView.cs b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInputView.cs index 62500e637..93c01ff54 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInputView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInputView.cs @@ -15,5 +15,4 @@ public interface IUserInputView bool GetAddGoalConfirmationFromUser(GoalModel goal); bool GetDeleteGoalConfirmationFromUser(GoalDTO goal); void PressAnyKeyToContinue(); - //DateTime GetUpdatedStartTimeFromUser(DateTime originalTime); } \ No newline at end of file diff --git a/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs b/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs index 36b660c3b..17a6ffb0b 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs @@ -1,6 +1,5 @@ using CodingTracker.Models.Entities; using CodingTracker.Views.Interfaces; -using Microsoft.VisualBasic; using Spectre.Console; using System.Globalization; diff --git a/codingTracker.jzhartman/CodingTracker/Program.cs b/codingTracker.jzhartman/CodingTracker/Program.cs index dfb710f94..8daa3c78a 100644 --- a/codingTracker.jzhartman/CodingTracker/Program.cs +++ b/codingTracker.jzhartman/CodingTracker/Program.cs @@ -6,7 +6,6 @@ namespace CodingTracker.ConsoleApp; internal class Program { - // TODO: Configure and run Code Cleanup static void Main(string[] args) { Batteries.Init(); From 1cfb88fa69ab620c34110cd230a430851b51194f Mon Sep 17 00:00:00 2001 From: jzhartman <129107535+jzhartman@users.noreply.github.com> Date: Tue, 2 Dec 2025 23:08:47 -0500 Subject: [PATCH 56/57] Create README.md for Coding Tracker project Added README.md with project overview, requirements, technologies, operation details, challenges, and future enhancements. --- README.md | 146 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 000000000..c4dae85d9 --- /dev/null +++ b/README.md @@ -0,0 +1,146 @@ +# Coding Tracker + + +## Overview +This is a basic console app meant for creating a log of coding sessions performed by a user. It is build on the requirements outlined in The C# Academy's Coding Tracker project (https://www.thecsharpacademy.com/project/13/coding-tracker). +This project was very similar to the previous project within the C# Academy roadmap, the Habit Logger. Due to the simmilarity, I attempted to use this as an opportunity to work with some design patterns. Admittedly, this caused the application to become much more complicated than was neccessary. However, it proved to be a very usefuly learning opportunity. + + +## Requirements: + +Base Requirements +* This is an application where you’ll log your daily coding time. +* Users need to be able to input the date and time of the coding session. +* The application should store and retrieve data from a real database. +* When the application starts, it should create a sqlite database, if one isn’t present. +* It should also create a table in the database, where the codingn sessions will be logged. +* The users should be able to insert, delete, update and view their logged sessions. +* You should handle all possible errors so that the application never crashes. +* Your project needs to contain a Read Me file where you'll explain how your app works. Here's a nice example: +* To show the data on the console, you should use the "Spectre.Console" library. +* You're required to have separate classes in different files (ex. UserInput.cs, Validation.cs, CodingController.cs) +* You should tell the user the specific format you want the date and time to be logged and not allow any other format. +* You'll need to create a configuration file that you'll contain your database path and connection strings. +* 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 +* The user shouldn't input the duration of the session. It should be calculated based on the Start and End times, in a separate "CalculateDuration" method. +* The user should be able to input the start and end times manually. +* You need to use Dapper ORM for the data access instead of ADO.NET. (This requirement was included in Feb/2024) +* When reading from the database, you can't use an anonymous object, you have to read your table into a List of Coding Sessions. +* Follow the DRY Principle, and avoid code repetition. + +Challenge Requirements + +* Add the possibility of tracking the coding time via a stopwatch so the user can track the session as it happens. +* Let the users filter their coding records per period (weeks, days, years) and/or order ascending or descending. +* Create reports where the users can see their total and average coding session per period. +* Create the ability to set coding goals and show how far the users are from reaching their goal, along with how many hours a day they would have to code to reach their goal. You can do it via SQL queries or with C#. + + +## Technologies + +C# +SQLite Database Conenction +Dapper ORM +Spectre.Console + + +## Operation + +### Main Menu +The application opens into the main menu. Behind the scenes, it checks for the existence of a SQLite DB. If none exists, it will create one and seed it with some basic starter data. +Once initialized, the user can select between the menu options: + +Track Session --> Allows the user to insert new records into the DB +View/Manage Entries --> Allows the user to view records in the DB and either update or delete them +View Reports --> Provides some basic analytics for the user to see their progress over a specified time period +Manage Goals --> Evaluate, view, create, and delete goals +Exit --> Closes application + +### Track Session +This is the portion where the user can insert records into the database. There is a submenu that allows to select an input method. + +The option "Enter Start and End Times" will prompt the user for a start time, then and end time and calculate the duration. It will then print the data to the user and ask for confirmation before adding it to the database. + +The option "Start Time" will take a timestamp of the current time. The display will now wait for the user to press any key in order to stop the timer. Once the timer is stopped, it uses the start time and stop time to calculate a duration. This is followed by a confirmation prior to inserting into the DB. + +### View/Manage Entries +This option first requires the user to select a time period. Once selected, a list of coding sessions is printed. The user can then update or delete individual records within the list. + +#### Selecting the Time Period +The options for time period are abbreviated below: + +* All --> Retrieves all coding sessions in the database. +* Past Year --> Retrieves all coding sessions from now until (now - 12 months) (day exclusive). +* YTD --> Retrieves all coding sessions from the current calendar year (based on current date/time). +* Custom Week/Month/Year --> Retrieves all coding sessions with a the specified period. User is prompted for the start date and then the end date is calculated based on the period selected. + +All retrieved data is ordered by the start time in ascending format (built into the SQL query). + +#### Printing the Records +Once the time period is selected, the list of coding sessions is printed to the screen with an index. The user is then given an additional menu to update or delete any record in that list. + +#### Updating a Record +A record is selected to update based on the printed index. The user will be asked to enter a new start time. They can either enter a time or submit a blank entry to keep the current start time. +They are then asked to enter an end time. They can either enter a time or submit a blank entry to keep the current end time. +Once both times are provided, a confirmation will appear with the new start time, end time, and duration. Confirming will update the record in the database. + +#### Deleting a Record +A record is selected to delete based on the printed index. Once a record is selected, a confirmation will appear with the data for the record. Confirming will delete the record from the database. + +### View Reports +Reports give the user the ability to view the total session count, the total time spent coding, and the average time spent coding for each day in the period. This section begins by prompting the user for a time period. It then prints the seesions from that period, followed by the report data. + +#### Selecting the Time Period +The options for time period are abbreviated below: + +* All --> Retrieves all coding sessions in the database. +* Past Year --> Retrieves all coding sessions from now until (now - 12 months) (day exclusive). +* YTD --> Retrieves all coding sessions from the current calendar year (based on current date/time). +* Custom Week/Month/Year --> Retrieves all coding sessions with a the specified period. User is prompted for the start date and then the end date is calculated based on the period selected. + +All retrieved data is ordered by the start time in ascending format (built into the SQL query). + +#### Printing the Data +Once the time period is selected, the list of coding sessions is printed to the screen with an index. This is followed by the report data. + +### Manage Goals +Goals are a way for the user to set targets for their coding sessions. There are three types of goals: Total Time, Average Daily Time, and Days Per Period. Upon selecting this option, all active goals are evaluated. Any goals that are completed or failed are individually reported to the user. +Once any/all newly completed or failed goals are reported, a list of these goals and all active goals will print. From here the user can then choose to Add a Goal, Delete a Goal, or View Completed goals. Note that navigating away from this screen and returning will remove the newly completed/failed goals from the list. + +#### Add Goals +User selects the start time, end time, goal type (Total Time, Average Time, or Days Per Period), and then the Targe Value. Data is validated and then added to the goals table in the DB. + +Note: Goals cannot have an end time that has already expired. It MUST be a future time. The logic is that there is no real accomplishment in creating a goal for events that have already occurred. + +#### Delete Goal +Allows the user to select and deleted a goal based on the list. The list printed here is all In Progress, Completed, and Failed goals. + +#### View Complete Goals +This prints a list of all Completed goals so the user can see their shining accomplishments (Hooray!) + + +## Challenges + +My goal was to attempt to work with some design patterns for the creation of this application. Based on some of the reading in the requirements, I decided to use a version of MVC. +This utilized a set of models for storing and moving data, a view for printing data, and a controller for navigating between these. I also ended up adding in a service layer. The overall structure I went with is outlined below: + +image + +For the data access, I created a Sqlite Connection Factory. This was injected into a repository for Coding Sessions and one for Goals. This allowed me to inject my connection string in a single place, and use that connection for all CRUD operations. + +My coding session and goals repositories inherited from a Generic Repository that contained generic methods for Dapper query and execute commands. (Note that the overloaded methods were needed to fullfill a project requirement that disallowed the use of anonymous objects). + +I utilized custom type handlers for Dapper in order to more easily work with DateTime values and my own enums. + +For validation, I created my own generic validation results class (this was inspired by how Spectre.Console validated input). This was a really neat way to handle validation and reporting between layers. + +UI was done using Spectre.Console. Given additional time, I would love to clean it up and make it more showy, but see that as less important in my learning journey. + +## Future Enhancements + +* UI should be redesigned to be more user friendly + +* The separation of the "View/Manage Entries" and "View Reports" sections has a lot of redundencies. They should be combined in the future. + +* There is no protection in the program for a corrupted DB file. This will need to be fixed. + From fee5e588d85e74b14f6f748ac63655c2191ff07a Mon Sep 17 00:00:00 2001 From: jzhartman Date: Tue, 2 Dec 2025 23:31:35 -0500 Subject: [PATCH 57/57] First round of codacy fixes --- .../CodingTracker.Controller/GoalsController.cs | 4 ++-- .../TrackSessionController.cs | 4 ++-- .../Repositories/CodingSessionRepository.cs | 3 +-- .../CodingSessionDataService.cs | 6 +++--- .../Interfaces/IUserInputView.cs | 4 ++-- .../CodingTracker.Views/UserInputView.cs | 16 +++++++--------- 6 files changed, 17 insertions(+), 20 deletions(-) diff --git a/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs b/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs index cb908d445..2d3b362c2 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/GoalsController.cs @@ -153,7 +153,7 @@ private long GetGoalValue(DateTime startTime, DateTime endTime, GoalType goalTyp while (valueIsValid == false) { - var input = GetGoalValueFromUser(startTime, endTime, goalType); + var input = GetGoalValueFromUser(goalType); var result = _goalService.ValidateGoalValueInput(goalType, input, maxValue); if (result.IsValid) @@ -170,7 +170,7 @@ private long GetGoalValue(DateTime startTime, DateTime endTime, GoalType goalTyp return output; } - private long GetGoalValueFromUser(DateTime startTime, DateTime endTime, GoalType goalType) + private long GetGoalValueFromUser(GoalType goalType) { switch (goalType) { diff --git a/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs b/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs index 226e6cbf1..874fc1458 100644 --- a/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs +++ b/codingTracker.jzhartman/CodingTracker.Controller/TrackSessionController.cs @@ -134,13 +134,13 @@ private CodingSession GetNewSessionFromStopwatch() private DateTime GetStartTimeWithStopwatch() { - var selection = _inputView.StartStopwatch(); + _inputView.StartStopwatch(); return DateTime.Now; } private DateTime GetStopTimeWithStopwatch() { - var selection = _inputView.StopStopwatch(); + _inputView.StopStopwatch(); return DateTime.Now; } } diff --git a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs index 6498e0c28..acea1f67b 100644 --- a/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs +++ b/codingTracker.jzhartman/CodingTracker.Data/Repositories/CodingSessionRepository.cs @@ -39,7 +39,7 @@ order by StartTime limit 1"; return LoadData(sql, parameter).FirstOrDefault(); -; } + } public DateTime GetStartTimeOfNextRecordExcludingCurrentSession(DateTime time, long id) { var parameter = new TimeUpdate { Time = time, Id = (int)id }; @@ -52,7 +52,6 @@ order by StartTime limit 1"; return LoadData(sql, parameter).FirstOrDefault(); - ; } public DateTime GetStartTimeOfLastRecord() { diff --git a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs index 6b14fd388..4fd4fa3ce 100644 --- a/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs +++ b/codingTracker.jzhartman/CodingTracker.Services/CodingSessionDataService.cs @@ -164,9 +164,9 @@ public ValidationResult ValidateReportEndTime(DateTime input, DateTime public ValidationResult ValidateGoalStartTime(DateTime input) { - //if (input > DateTime.Now) - // return ValidationResult.Fail("Start Time", "Cannot enter a future time"); - //else + if (input < DateTime.MinValue) + return ValidationResult.Fail("Start Time", "Invalid time!"); + else return ValidationResult.Success(input); } public ValidationResult ValidateGoalEndTime(DateTime input, DateTime startTime) diff --git a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInputView.cs b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInputView.cs index 93c01ff54..fd45df42b 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInputView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/Interfaces/IUserInputView.cs @@ -8,8 +8,8 @@ public interface IUserInputView DateTime GetTimeFromUser(string parameterName, bool allowNull = false); bool GetUpdateSessionConfirmationFromUser(CodingSessionDataRecord session, CodingSession updatedSession); bool GetDeleteSessionConfirmationFromUser(CodingSessionDataRecord session); - string StartStopwatch(); - string StopStopwatch(); + void StartStopwatch(); + void StopStopwatch(); long GetGoalValueTime(GoalType goalType); long GetGoalValueForDaysPerPeriod(); bool GetAddGoalConfirmationFromUser(GoalModel goal); diff --git a/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs b/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs index 17a6ffb0b..dded91e91 100644 --- a/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs +++ b/codingTracker.jzhartman/CodingTracker.Views/UserInputView.cs @@ -56,10 +56,10 @@ public DateTime GetTimeFromUser(string parameterName, bool allowNull = false) return DateTime.ParseExact(timeInput, _dateFormat, CultureInfo.InvariantCulture); } - public string StartStopwatch() + public void StartStopwatch() { AnsiConsole.WriteLine(); - var selection = AnsiConsole.Prompt( + AnsiConsole.Prompt( new SelectionPrompt() .Title("Press the ENTER key when you are ready to begin your coding session.") .AddChoices(new[] @@ -67,13 +67,11 @@ public string StartStopwatch() "Start Stopwatch", }) ); - - return selection; } - public string StopStopwatch() + public void StopStopwatch() { AnsiConsole.WriteLine(); - var selection = AnsiConsole.Prompt( + AnsiConsole.Prompt( new SelectionPrompt() .Title("Press the ENTER key when you are ready to end your coding session.") .AddChoices(new[] @@ -81,8 +79,6 @@ public string StopStopwatch() "Stop Stopwatch", }) ); - - return selection; } public int GetRecordIdFromUser(string action, int max) { @@ -167,7 +163,9 @@ public bool GetAddGoalConfirmationFromUser(GoalModel goal) string promptText = $"Add {GenerateGoalConfirmationPromptText( new GoalDTO {Id = 0, Type = goal.Type, StartTime = goal.StartTime, EndTime = goal.EndTime, Status = goal.Status, GoalValue = goal.GoalValue, CurrentValue = goal.CurrentValue, - Progress = goal.Progress})}"; + Progress = goal.Progress + }) + }"; var confirmation = AnsiConsole.Prompt( new TextPrompt($"{promptText}?")