From ec56d71ec0ff7acb2e5d1206ad4ab5528c56d581 Mon Sep 17 00:00:00 2001 From: dofe Date: Tue, 29 Sep 2026 10:36:37 +0600 Subject: [PATCH 1/2] Initial commit --- CodingTracker/.gitattributes | 63 +++ CodingTracker/.gitignore | 363 ++++++++++++++++++ CodingTracker/CodingTracker.slnx | 3 + .../CodingTracker/CodingTracker.csproj | 23 ++ .../Controllers/BaseController.cs | 124 ++++++ .../Controllers/CodingSessionsController.cs | 71 ++++ .../Controllers/ICodingSessionsController.cs | 14 + CodingTracker/CodingTracker/DbContext.cs | 43 +++ .../CodingTracker/Models/CodingSession.cs | 21 + .../CodingTracker/Models/Enums/MenuAction.cs | 18 + CodingTracker/CodingTracker/Program.cs | 31 ++ .../Repository/CodingSessionsRepository.cs | 62 +++ .../Repository/ICodingSessionsRepository.cs | 16 + .../CodingTracker/UI/UserInterface.cs | 56 +++ CodingTracker/CodingTracker/appsettings.json | 5 + 15 files changed, 913 insertions(+) create mode 100644 CodingTracker/.gitattributes create mode 100644 CodingTracker/.gitignore create mode 100644 CodingTracker/CodingTracker.slnx create mode 100644 CodingTracker/CodingTracker/CodingTracker.csproj create mode 100644 CodingTracker/CodingTracker/Controllers/BaseController.cs create mode 100644 CodingTracker/CodingTracker/Controllers/CodingSessionsController.cs create mode 100644 CodingTracker/CodingTracker/Controllers/ICodingSessionsController.cs create mode 100644 CodingTracker/CodingTracker/DbContext.cs create mode 100644 CodingTracker/CodingTracker/Models/CodingSession.cs create mode 100644 CodingTracker/CodingTracker/Models/Enums/MenuAction.cs create mode 100644 CodingTracker/CodingTracker/Program.cs create mode 100644 CodingTracker/CodingTracker/Repository/CodingSessionsRepository.cs create mode 100644 CodingTracker/CodingTracker/Repository/ICodingSessionsRepository.cs create mode 100644 CodingTracker/CodingTracker/UI/UserInterface.cs create mode 100644 CodingTracker/CodingTracker/appsettings.json diff --git a/CodingTracker/.gitattributes b/CodingTracker/.gitattributes new file mode 100644 index 000000000..1ff0c4230 --- /dev/null +++ b/CodingTracker/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/CodingTracker/.gitignore b/CodingTracker/.gitignore new file mode 100644 index 000000000..9491a2fda --- /dev/null +++ b/CodingTracker/.gitignore @@ -0,0 +1,363 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Oo]ut/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd \ No newline at end of file diff --git a/CodingTracker/CodingTracker.slnx b/CodingTracker/CodingTracker.slnx new file mode 100644 index 000000000..c4e83d0e1 --- /dev/null +++ b/CodingTracker/CodingTracker.slnx @@ -0,0 +1,3 @@ + + + diff --git a/CodingTracker/CodingTracker/CodingTracker.csproj b/CodingTracker/CodingTracker/CodingTracker.csproj new file mode 100644 index 000000000..0ab5f1c1c --- /dev/null +++ b/CodingTracker/CodingTracker/CodingTracker.csproj @@ -0,0 +1,23 @@ + + + + Exe + net10.0 + enable + enable + + + + + + + + + + + + PreserveNewest + + + + diff --git a/CodingTracker/CodingTracker/Controllers/BaseController.cs b/CodingTracker/CodingTracker/Controllers/BaseController.cs new file mode 100644 index 000000000..2d1c7a0bd --- /dev/null +++ b/CodingTracker/CodingTracker/Controllers/BaseController.cs @@ -0,0 +1,124 @@ +using CodingTracker.Models; +using Spectre.Console; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CodingTracker.Controllers +{ + internal abstract class BaseController + { + protected internal void DisplayMessage(string message, string color = "yellow") + { + AnsiConsole.MarkupLine($"[{color}]{message}[/]"); + Console.WriteLine(); + } + + protected internal void DisplayError(string message, string color = "red") + { + AnsiConsole.MarkupLine($"[{color}]Error: {message}[/]"); + Console.WriteLine(); + } + + protected internal void DisplayTable(List sessions, string title) + { + Table table = new Table() + .Border(TableBorder.Rounded) + .Title($"{title}"); + + table.AddColumn("Id"); + table.AddColumn("Start Time"); + table.AddColumn("End Time"); + table.AddColumn("Duration"); + + foreach(var session in sessions) + { + table.AddRow( + session.Id.ToString(), + session.StartTime.ToString("yyyy-MM-dd HH:mm"), + session.EndTime.ToString("yyyy-MM-dd HH:mm"), + session.Duration.ToString(@"hh\:mm")); + } + + AnsiConsole.Write(table); + } + protected internal DateTime GetDateTimeInput(string text, DateTime? defaultValue = null) + { + string fullText = defaultValue.HasValue + ? $"{text} (Current: {defaultValue.Value.ToString("yyyy-MM-dd HH:mm")}, Press Enter to keep) " : text; + + string userInput = AnsiConsole.Prompt( + new TextPrompt($"{fullText}") + .AllowEmpty() + .Validate(input => + { + if (string.IsNullOrWhiteSpace(input) && defaultValue.HasValue) + { + return ValidationResult.Success(); + } + + if (DateTime.TryParse(input, out _)) + { + return ValidationResult.Success(); + } + + return ValidationResult.Error("[red]Invalid date format. Example: 14:00 or 2026-09-27 14:00[/]"); + }) + ); + + if (string.IsNullOrWhiteSpace(userInput) && defaultValue.HasValue) + { + return defaultValue.Value; + } + + return DateTime.Parse(userInput); + } + + protected internal (DateTime startTime, DateTime endTime) DateTimeInput() + { + while (true) + { + DateTime startTime = GetDateTimeInput("Enter the start time (like 14:00 or 2026-09-27 14:00): "); + DateTime endTime = GetDateTimeInput("Enter the end time (like 15:00 or 2026-09-27 15:00): "); + + if (endTime >= startTime) + { + DisplayMessage("The input was saved."); + return (startTime, endTime); + } + + DisplayError("End time cannot be earlier than start time. Please enter both again."); + } + } + + protected internal CodingSession DateTimeUpdateInput(CodingSession session) + { + while (true) + { + DateTime newStartTime = GetDateTimeInput("Enter the start time", session.StartTime); + DateTime newEndTime = GetDateTimeInput("Enter the end time", session.EndTime); + + if (newEndTime >= newStartTime) + { + DisplayMessage("The input was saved."); + return new CodingSession + { + Id = session.Id, + StartTime = newStartTime, + EndTime = newEndTime + }; + } + + DisplayError("End time cannot be earlier than start time. Please re-enter."); + } + + } + + protected internal int InputId() + { + int inputNum = AnsiConsole.Ask("Enter the ID to proceed: "); + return inputNum; + } + + } +} diff --git a/CodingTracker/CodingTracker/Controllers/CodingSessionsController.cs b/CodingTracker/CodingTracker/Controllers/CodingSessionsController.cs new file mode 100644 index 000000000..b3e87bd4a --- /dev/null +++ b/CodingTracker/CodingTracker/Controllers/CodingSessionsController.cs @@ -0,0 +1,71 @@ +using CodingTracker.Models; +using CodingTracker.Repository; +using CodingTracker.UI; + +namespace CodingTracker.Controllers +{ + internal class CodingSessionsController : BaseController, ICodingSessionsController + { + private readonly ICodingSessionsRepository _repository; + + public CodingSessionsController(ICodingSessionsRepository repository) + { + _repository = repository; + } + + public void ViewSessions() + { + var sessions = _repository.GetAll(); + + if (sessions.Any()) + { + DisplayTable(sessions, "ALL OF THEM"); + } + else + { + DisplayMessage("Sessions was not found :(", color: "blue"); + } + } + + public void AddSession() + { + var dateTimeUserInput = DateTimeInput(); + + var newSession = new CodingSession { StartTime = dateTimeUserInput.startTime, EndTime = dateTimeUserInput.endTime }; + + int newId = _repository.Create(newSession); + + DisplayMessage($"Session #{newId} was added successfully!"); + } + + public void DeleteSession() + { + ViewSessions(); + int userIdInput = InputId(); + + bool isDeleted = _repository.Delete(userIdInput); + + if (isDeleted) DisplayMessage($"The session with this ID - {userIdInput} was deleted."); + else DisplayError($"The session with that ID - {userIdInput} does not exist."); + } + public void UpdateSession() + { + ViewSessions(); + int userIdInput = InputId(); + var session = _repository.GetById(userIdInput); + + if (session is null) + { + DisplayError("No session with this ID was found"); + return; + } + + CodingSession updatedSession = DateTimeUpdateInput(session); + + bool isUpdated = _repository.Update(updatedSession); + + if (isUpdated) DisplayMessage($"The session with this ID - {userIdInput} was updated."); + else DisplayError($"The session with that ID - {userIdInput} does not exist."); + } + } +} diff --git a/CodingTracker/CodingTracker/Controllers/ICodingSessionsController.cs b/CodingTracker/CodingTracker/Controllers/ICodingSessionsController.cs new file mode 100644 index 000000000..61bd41897 --- /dev/null +++ b/CodingTracker/CodingTracker/Controllers/ICodingSessionsController.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CodingTracker.Controllers +{ + internal interface ICodingSessionsController + { + void ViewSessions(); + void AddSession(); + void DeleteSession(); + void UpdateSession(); + } +} diff --git a/CodingTracker/CodingTracker/DbContext.cs b/CodingTracker/CodingTracker/DbContext.cs new file mode 100644 index 000000000..be0b29776 --- /dev/null +++ b/CodingTracker/CodingTracker/DbContext.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Text; +using Dapper; +using Microsoft.Data.Sqlite; +using Microsoft.Extensions.Configuration; + +namespace CodingTracker +{ + internal class DbContext + { + private readonly string _connectionString; + + public DbContext(string connectionString) + { + _connectionString = connectionString; + } + + public SqliteConnection CreateConnection() + { + return new SqliteConnection(_connectionString); + } + + + public void InitialTable() + { + + using(var connection = CreateConnection()) + { + var tableCmd = """ + CREATE TABLE IF NOT EXISTS CodingSession ( + Id INTEGER PRIMARY KEY AUTOINCREMENT, + StartTime TEXT NOT NULL, + EndTime TEXT NOT NULL); + + """; + + connection.Execute(tableCmd); + } + } + } +} diff --git a/CodingTracker/CodingTracker/Models/CodingSession.cs b/CodingTracker/CodingTracker/Models/CodingSession.cs new file mode 100644 index 000000000..68ff49a40 --- /dev/null +++ b/CodingTracker/CodingTracker/Models/CodingSession.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CodingTracker.Models +{ + public class CodingSession + { + public int Id { get; set; } + public DateTime StartTime { get; set; } + public DateTime EndTime { get; set; } + public TimeSpan Duration + { + get + { + if(EndTime < StartTime) return TimeSpan.Zero; + return EndTime - StartTime; + } + } + } +} diff --git a/CodingTracker/CodingTracker/Models/Enums/MenuAction.cs b/CodingTracker/CodingTracker/Models/Enums/MenuAction.cs new file mode 100644 index 000000000..5dc6cbc05 --- /dev/null +++ b/CodingTracker/CodingTracker/Models/Enums/MenuAction.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CodingTracker.Models.Enums +{ + internal class MenuAction + { + internal enum MainMenuAction + { + ViewSessions, + AddSession, + DeleteSession, + UpdateSession, + Exit + } + } +} diff --git a/CodingTracker/CodingTracker/Program.cs b/CodingTracker/CodingTracker/Program.cs new file mode 100644 index 000000000..fa89a78c0 --- /dev/null +++ b/CodingTracker/CodingTracker/Program.cs @@ -0,0 +1,31 @@ +using CodingTracker.Controllers; +using CodingTracker.Repository; +using CodingTracker.UI; +using Microsoft.Extensions.Configuration; +using System.Globalization; + +namespace CodingTracker; + +internal class Program +{ + static void Main(string[] args) + { + var config = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json") + .Build(); + + string connectionString = config.GetConnectionString("DefaultConnection"); + + DbContext context = new(connectionString); + context.InitialTable(); + + CodingSessionsRepository repository = new(context); + + var controller = new CodingSessionsController(repository); + + var userInterface = new UserInterface(controller); + + userInterface.MainMenu(); + } +} diff --git a/CodingTracker/CodingTracker/Repository/CodingSessionsRepository.cs b/CodingTracker/CodingTracker/Repository/CodingSessionsRepository.cs new file mode 100644 index 000000000..1899dc914 --- /dev/null +++ b/CodingTracker/CodingTracker/Repository/CodingSessionsRepository.cs @@ -0,0 +1,62 @@ +using CodingTracker.Controllers; +using CodingTracker.Models; +using Dapper; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CodingTracker.Repository +{ + internal class CodingSessionsRepository : BaseController, ICodingSessionsRepository + { + private readonly DbContext _context; + + public CodingSessionsRepository(DbContext context) + { + _context = context; + } + + public List GetAll() + { + using var connection = _context.CreateConnection(); + string sql = "SELECT * FROM CodingSession"; + return connection.Query(sql).ToList(); + } + + public CodingSession? GetById(int id) + { + using var connection = _context.CreateConnection(); + string sql = "SELECT * FROM CodingSession WHERE Id = @Id"; + return connection.QueryFirstOrDefault(sql, new {Id = id}); + + } + + public int Create(CodingSession session) + { + using var connection = _context.CreateConnection(); + string sql = "INSERT INTO CodingSession (StartTime, EndTime) VALUES (@StartTime, @EndTime);" + + "SELECT last_insert_rowid();"; + + return connection.ExecuteScalar(sql, session); + } + + public bool Delete(int id) + { + using var connection = _context.CreateConnection(); + string sql = "DELETE FROM CodingSession WHERE Id = @Id"; + + int rows = connection.Execute(sql, new { Id = id }); + return rows > 0; + } + + public bool Update(CodingSession session) + { + using var connection = _context.CreateConnection(); + string sql = "UPDATE CodingSession SET StartTime = @StartTime, EndTime = @EndTime WHERE Id = @Id"; + + int rows = connection.Execute(sql, session); + + return rows > 0; + } + } +} diff --git a/CodingTracker/CodingTracker/Repository/ICodingSessionsRepository.cs b/CodingTracker/CodingTracker/Repository/ICodingSessionsRepository.cs new file mode 100644 index 000000000..077061ae8 --- /dev/null +++ b/CodingTracker/CodingTracker/Repository/ICodingSessionsRepository.cs @@ -0,0 +1,16 @@ +using CodingTracker.Models; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CodingTracker.Repository +{ + internal interface ICodingSessionsRepository + { + List GetAll(); + CodingSession? GetById(int id); + int Create(CodingSession session); + bool Delete(int id); + bool Update(CodingSession session); + } +} diff --git a/CodingTracker/CodingTracker/UI/UserInterface.cs b/CodingTracker/CodingTracker/UI/UserInterface.cs new file mode 100644 index 000000000..b3f6e5230 --- /dev/null +++ b/CodingTracker/CodingTracker/UI/UserInterface.cs @@ -0,0 +1,56 @@ +using static CodingTracker.Models.Enums.MenuAction; +using Spectre.Console; +using CodingTracker.Controllers; + +namespace CodingTracker.UI +{ + internal class UserInterface + { + private readonly ICodingSessionsController _controller; + + public UserInterface(ICodingSessionsController codingSessionsController) + { + _controller = codingSessionsController; + } + + internal void MainMenu() + { + AnsiConsole.Clear(); + bool closeApp = false; + while (!closeApp) + { + AnsiConsole.MarkupLine("Coding tracker\n"); + + var choice = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("Select an action:") + .AddChoices(Enum.GetValues())); + + switch (choice) + { + case MainMenuAction.Exit: + AnsiConsole.Clear(); + AnsiConsole.MarkupLine("Thank you!"); + closeApp = true; + break; + case MainMenuAction.ViewSessions: + AnsiConsole.Clear(); + _controller.ViewSessions(); + break; + case MainMenuAction.AddSession: + AnsiConsole.Clear(); + _controller.AddSession(); + break; + case MainMenuAction.DeleteSession: + AnsiConsole.Clear(); + _controller.DeleteSession(); + break; + case MainMenuAction.UpdateSession: + AnsiConsole.Clear(); + _controller.UpdateSession(); + break; + } + } + } + } +} diff --git a/CodingTracker/CodingTracker/appsettings.json b/CodingTracker/CodingTracker/appsettings.json new file mode 100644 index 000000000..6912bb94f --- /dev/null +++ b/CodingTracker/CodingTracker/appsettings.json @@ -0,0 +1,5 @@ +{ + "ConnectionStrings": { + "DefaultConnection": "Data Source = codingTracker.db; Foreign Keys=True;" + } +} \ No newline at end of file From 0662bfaec575996070e76ffb9645594a502f2754 Mon Sep 17 00:00:00 2001 From: dofe <96130259+dofelor@users.noreply.github.com> Date: Tue, 29 Sep 2026 10:47:45 +0600 Subject: [PATCH 2/2] Add project description to README Added project overview and architecture details. --- README.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 000000000..78fc162d7 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# CODING TRACKER +I have to say, it was a really cool project in terms of design and architecture. +Since I had already submitted my habit tracker for review by then, creating a coding tracker based on it was straightforward—Dapper isn't all that different from ADO.NET. +The real challenge lay in separating concerns and adhering to the Single Responsibility Principle (SRP). Looking at the commit history of the original repository (https://github.com/dofelor/CodingTracker), you can see that I refactored the code along the way. I consulted an AI about SRP compliance and whether I was on the right track; it noted that the logic was sound but suggested adding an extra layer for context, so I implemented the Repository pattern. It all makes sense—I tried to pass everything necessary using abstractions. The only potential issue is that the controller still handles both input and output, though it no longer initializes the database itself; the repository handles that now. Ultimately, I learned that the foundation relies on four layers of abstraction. +That’s all for now.