From 1d2bca05f37a9764b829b6889d591dc50d89b0c3 Mon Sep 17 00:00:00 2001 From: Sephydev Date: Tue, 5 May 2026 12:05:43 +0200 Subject: [PATCH 1/2] Finished the project --- STUDY.CodingTracker/.gitattributes | 63 +++ STUDY.CodingTracker/.gitignore | 363 ++++++++++++++++++ STUDY.CodingTracker/README.md | 70 ++++ .../ReadMeAssets/CodingSessionsDisplay.png | Bin 0 -> 21139 bytes STUDY.CodingTracker/ReadMeAssets/MainMenu.png | Bin 0 -> 12072 bytes .../CodingTrackerTests.cs | 128 ++++++ .../STUDY.CodingTracker.UnitTest.csproj | 27 ++ STUDY.CodingTracker/STUDY.CodingTracker.slnx | 4 + .../Controllers/CodingSessionController.cs | 166 ++++++++ .../STUDY.CodingTracker/Helper/Enums.cs | 31 ++ .../STUDY.CodingTracker/Helper/StopWatch.cs | 43 +++ .../STUDY.CodingTracker/Helper/UserInput.cs | 28 ++ .../Helper/Verification.cs | 90 +++++ .../Models/CodingSessionModel.cs | 52 +++ .../STUDY.CodingTracker/Program.cs | 5 + .../Properties/launchSettings.json | 8 + .../STUDY.CodingTracker.csproj | 18 + .../STUDY.CodingTracker/UserInterface.cs | 337 ++++++++++++++++ .../STUDY.CodingTracker/appsettings.json | 6 + .../STUDY.CodingTracker/coding-tracker.db | Bin 0 -> 12288 bytes 20 files changed, 1439 insertions(+) create mode 100644 STUDY.CodingTracker/.gitattributes create mode 100644 STUDY.CodingTracker/.gitignore create mode 100644 STUDY.CodingTracker/README.md create mode 100644 STUDY.CodingTracker/ReadMeAssets/CodingSessionsDisplay.png create mode 100644 STUDY.CodingTracker/ReadMeAssets/MainMenu.png create mode 100644 STUDY.CodingTracker/STUDY.CodingTracker.UnitTest/CodingTrackerTests.cs create mode 100644 STUDY.CodingTracker/STUDY.CodingTracker.UnitTest/STUDY.CodingTracker.UnitTest.csproj create mode 100644 STUDY.CodingTracker/STUDY.CodingTracker.slnx create mode 100644 STUDY.CodingTracker/STUDY.CodingTracker/Controllers/CodingSessionController.cs create mode 100644 STUDY.CodingTracker/STUDY.CodingTracker/Helper/Enums.cs create mode 100644 STUDY.CodingTracker/STUDY.CodingTracker/Helper/StopWatch.cs create mode 100644 STUDY.CodingTracker/STUDY.CodingTracker/Helper/UserInput.cs create mode 100644 STUDY.CodingTracker/STUDY.CodingTracker/Helper/Verification.cs create mode 100644 STUDY.CodingTracker/STUDY.CodingTracker/Models/CodingSessionModel.cs create mode 100644 STUDY.CodingTracker/STUDY.CodingTracker/Program.cs create mode 100644 STUDY.CodingTracker/STUDY.CodingTracker/Properties/launchSettings.json create mode 100644 STUDY.CodingTracker/STUDY.CodingTracker/STUDY.CodingTracker.csproj create mode 100644 STUDY.CodingTracker/STUDY.CodingTracker/UserInterface.cs create mode 100644 STUDY.CodingTracker/STUDY.CodingTracker/appsettings.json create mode 100644 STUDY.CodingTracker/STUDY.CodingTracker/coding-tracker.db diff --git a/STUDY.CodingTracker/.gitattributes b/STUDY.CodingTracker/.gitattributes new file mode 100644 index 000000000..1ff0c4230 --- /dev/null +++ b/STUDY.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/STUDY.CodingTracker/.gitignore b/STUDY.CodingTracker/.gitignore new file mode 100644 index 000000000..9491a2fda --- /dev/null +++ b/STUDY.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/STUDY.CodingTracker/README.md b/STUDY.CodingTracker/README.md new file mode 100644 index 000000000..66a1ffb6a --- /dev/null +++ b/STUDY.CodingTracker/README.md @@ -0,0 +1,70 @@ +# Coding Tracker + +My first C# console application using Object Oriented Programming, Dapper and Spectre.Console. + +Console based CRUD application to track occurrences of different coding sessions. Developed using C#, SQLite and NUnit. + +## Given Requirements + - This application has the same requirements as the previous project, except that now you'll be logging your daily coding time. + - To show the data on the console, you should use the Spectre.Console library. + - You're required to have separate classes in different files (i.e. 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 called appsettings.json, which will contain your database path and connection strings (and any other configs you might need). + - You'll need to create a CodingSession class in a separate file. It will contain the properties of your coding session: Id, StartTime, EndTime, Duration. When reading from the database, you can't use an anonymous object, you have to read your table into a List of CodingSession. + - The user shouldn't input the duration of the session. It should be calculated based on the Start and End times + - 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. + - Follow the DRY Principle, and avoid code repetition. + - Your project needs to contain a ReadMe file where you'll explain how your app works and tell a little about your thought progress. + +## Optional Challenging 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. + - If you already have a bit of experience with programming, we highly recommend you get into the habit of writing unit tests for a few methods in your project. Any method that outputs data and doesn't talk to a database can be unit tested. + +## Features + - SQLite database connection + -- The program uses a SQLite DB connection to store and read information. + -- If no database exists, or the correct table does not exist, it will be created on program start. + - A console based UI where users can navigate by entering options. + ![Main menu](./ReadMeAssets/MainMenu.png) + - CRUD DB functions + -- From the main menu users can Create, Read, Update or Delete entries for whichever coding session they want. They need to enter the starting date and hour and the ending date and hour (format : dd-MMM-yyyy hh:MM). + -- User input are automatically checked to make sure they are in the correct and realistic format. + - Registered Habit output. + ![Coding Sessions Display](./ReadMeAssets/CodingSessionsDisplay.png) + +## How to run it + +### Prerequisites + +- [.NET SDK](https://dotnet.microsoft.com/download) 10.0 or later + +### Steps + +```bash +git clone https://github.com/Sephydev/STUDY.CodingTracker.git +cd HabbitLogger/STUDY.CodingTracker +dotnet run +``` + +## Challenges + - It was my first time using Dapper. I had to search for a good documentation to see the differences with ADO.NET. + - Managing `DateTime` and `TimeSpan` type of data was also challenging because of the differences with typical type like `int` or `string`. I mainly used Microsoft Learn documentation to solve the different problems I encountered. + +## Lesson Learned + - Coming from a JS background, I now see the differences with OOP programming. I can now see the benefits of OOP, notably how well the code is now organized and easy to read. + - I understand now the benefits of using Dapper. It allow me to "skip" some code that I used using ADO.NET, making my code more readable and making my work done quicker. + - Spectre.Console is a really good console package, making the UI more intuitive and beautiful, while being less tedious than using Console methods. It also give me basic validation. + +## Areas to improve + - I need to get better with git commit. I have a tendency of forgetting to commit when something is done. In this case, I commit when two or three things is done. It is not optimal. + - I have the feeling that my code could be more organized too. I will train that by practicing on new projects. + +## Resources Used + - C# Academy for the specs and related articles : https://www.thecsharpacademy.com/project/13/coding-tracker + - SQLitetutorial to learn the basic SQL command : https://www.sqlitetutorial.net/ + - Microsoft Learn on DateTime Struct official documentation to learn basic usage of DateTime: https://learn.microsoft.com/fr-fr/dotnet/api/system.datetime?view=net-8.0 + - Microsoft Learn on TimeSpan Struct official documentation to learn basic usage of TimeSpan: https://learn.microsoft.com/fr-fr/dotnet/api/system.timespan?view=net-8.0 + - This article helped me a lot seeing the differences between Dapper and ADO.NET, and to understand basic use of Dapper : https://medium.com/@pavanpitthdiya/the-ultimate-guide-to-dapper-in-net-everything-you-need-to-know-2025-edition-295ab8a4ced8 + - Spectre.Console documentation : https://spectreconsole.net/console \ No newline at end of file diff --git a/STUDY.CodingTracker/ReadMeAssets/CodingSessionsDisplay.png b/STUDY.CodingTracker/ReadMeAssets/CodingSessionsDisplay.png new file mode 100644 index 0000000000000000000000000000000000000000..1a5065d45814ea8479246e277a8d79fe584153c2 GIT binary patch literal 21139 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 + + + net10.0 + latest + enable + enable + false + + + + + + + + + + + + + + + + + + + diff --git a/STUDY.CodingTracker/STUDY.CodingTracker.slnx b/STUDY.CodingTracker/STUDY.CodingTracker.slnx new file mode 100644 index 000000000..0d57064d0 --- /dev/null +++ b/STUDY.CodingTracker/STUDY.CodingTracker.slnx @@ -0,0 +1,4 @@ + + + + diff --git a/STUDY.CodingTracker/STUDY.CodingTracker/Controllers/CodingSessionController.cs b/STUDY.CodingTracker/STUDY.CodingTracker/Controllers/CodingSessionController.cs new file mode 100644 index 000000000..21579da8a --- /dev/null +++ b/STUDY.CodingTracker/STUDY.CodingTracker/Controllers/CodingSessionController.cs @@ -0,0 +1,166 @@ +using Dapper; +using Microsoft.Data.Sqlite; +using Microsoft.Extensions.Configuration; +using STUDY.CodingTracker.Helper; +using STUDY.CodingTracker.Models; +using System.Globalization; + +namespace STUDY.CodingTracker.Controllers; + +internal class CodingSessionController +{ + private readonly IConfiguration _config; + private readonly string _connectionString; + + public CodingSessionController(IConfiguration config) + { + _config = config; + var section = _config.GetSection("DataBaseSettings"); + _connectionString = section["connectionString"] + section["databasePath"]; + + CreateTable(); + } + + public List GetCodingSessions(FilterChoice filterChoice, int periodNum, OrderChoice orderChoice) + { + List codingSessions = new(); + List filteredCodingSessions = new List(); + + string order = orderChoice == OrderChoice.Descending ? "ORDER BY STARTTIME DESC" : orderChoice == OrderChoice.Ascending ? "ORDER BY STARTTIME ASC" : ""; + + string command = $@" + SELECT STARTTIME start, + ENDTIME end, + ID newId, + DURATION newDuration FROM codingSessions + {order} + "; + + try + { + using var connection = new SqliteConnection(_connectionString); + + connection.Open(); + + codingSessions = connection.Query(command).AsList(); + } + catch (SqliteException e) + { + DBErrorMessage("getting the saved coding sessions", e.Message); + } + + filteredCodingSessions = FilterCodingSession(filterChoice, periodNum, codingSessions); + + return filteredCodingSessions; + } + + private static List FilterCodingSession(FilterChoice filterChoice, int periodNum, List codingSessions) + { + switch (filterChoice) + { + case FilterChoice.Week: + return codingSessions.FindAll(c => ISOWeek.GetWeekOfYear(c.startTime) == periodNum); + case FilterChoice.Day: + return codingSessions.FindAll(c => c.startTime.Day == periodNum); + case FilterChoice.Year: + return codingSessions.FindAll(c => c.startTime.Year == periodNum); + default: + return codingSessions; + } + } + + public bool AddCodingSession(CodingSessionModel codingSession) + { + bool success = false; + + try + { + using var connection = new SqliteConnection(_connectionString); + + connection.Open(); + + string sql = "INSERT INTO codingSessions (STARTTIME, ENDTIME, DURATION) VALUES (@StartTime, @EndTime, @Duration)"; + var parameters = new { @StartTime = codingSession.startTime, @EndTime = codingSession.endTime, @Duration = codingSession.duration }; + connection.Execute(sql, parameters); + + success = true; + } + catch (SqliteException e) + { + DBErrorMessage("adding the coding session", e.Message); + } + + return success; + } + + public int DeleteCodingSession(int idToDelete) + { + int numberOfRowsDeleted = 0; + + try + { + using var connection = new SqliteConnection(_connectionString); + + connection.Open(); + + numberOfRowsDeleted = connection.Execute("DELETE FROM codingSessions WHERE ID = @IdToDelete", new { @IdToDelete = idToDelete }); + } + catch (SqliteException e) + { + DBErrorMessage("deleting the coding session", e.Message); + } + + return numberOfRowsDeleted; + } + + public int UpdateCodingSession(int idToUpdate, CodingSessionModel updatedCodingSession) + { + int numberOfRowsUpdated = 0; + + try + { + using var connection = new SqliteConnection(_connectionString); + + connection.Open(); + + string sql = "UPDATE codingSessions SET STARTTIME = @StartTime, ENDTIME = @EndTime, DURATION = @Duration WHERE ID = @IdToUpdate"; + var parameters = new { @StartTime = updatedCodingSession.startTime, @EndTime = updatedCodingSession.endTime, @Duration = updatedCodingSession.duration, @IdToUpdate = idToUpdate }; + + numberOfRowsUpdated = connection.Execute(sql, parameters); + } + catch (SqliteException e) + { + DBErrorMessage("updating the coding session", e.Message); + } + + return numberOfRowsUpdated; + } + + private void CreateTable() + { + try + { + using var connection = new SqliteConnection(_connectionString); + + connection.Open(); + + connection.Execute( + @"CREATE TABLE IF NOT EXISTS codingSessions( + ID INTEGER PRIMARY KEY AUTOINCREMENT, + STARTTIME TEXT, + ENDTIME TEXT, + DURATION TEXT + )" + ); + } + catch (SqliteException e) + { + DBErrorMessage("creating the DB Table", e.Message); + } + } + + private void DBErrorMessage(string action, string errorMessage) + { + Console.WriteLine($"An error occured while {action}. Error: {errorMessage}"); + } +} diff --git a/STUDY.CodingTracker/STUDY.CodingTracker/Helper/Enums.cs b/STUDY.CodingTracker/STUDY.CodingTracker/Helper/Enums.cs new file mode 100644 index 000000000..84adccb9f --- /dev/null +++ b/STUDY.CodingTracker/STUDY.CodingTracker/Helper/Enums.cs @@ -0,0 +1,31 @@ +namespace STUDY.CodingTracker.Helper; + +public enum MainMenuChoice +{ + ViewCodingSessions, + AddCodingSession, + DeleteCodingSession, + UpdateCodingSession, + Exit +} + +public enum FilterChoice +{ + Week, + Day, + Year, + None +} + +public enum StopwatchChoice +{ + Yes, + No +} + +public enum OrderChoice +{ + Descending, + Ascending, + None +} \ No newline at end of file diff --git a/STUDY.CodingTracker/STUDY.CodingTracker/Helper/StopWatch.cs b/STUDY.CodingTracker/STUDY.CodingTracker/Helper/StopWatch.cs new file mode 100644 index 000000000..52b7cce29 --- /dev/null +++ b/STUDY.CodingTracker/STUDY.CodingTracker/Helper/StopWatch.cs @@ -0,0 +1,43 @@ +using Spectre.Console; +using System.Timers; + +namespace STUDY.CodingTracker.Helper; + +internal class StopWatch +{ + private static System.Timers.Timer _timer; + private TimeSpan duration = new TimeSpan(0, 0, 0); + + public StopWatch() + { + duration = new TimeSpan(0, 0, 0); + } + + public TimeSpan LaunchTimer() + { + SetTimer(); + + Console.ReadLine(); + + _timer.Stop(); + _timer.Dispose(); + + return duration; + } + + private void SetTimer() + { + _timer = new System.Timers.Timer(1000); + _timer.Elapsed += OnTimedEvent; + _timer.AutoReset = true; + _timer.Enabled = true; + } + + private void OnTimedEvent(Object source, ElapsedEventArgs e) + { + duration = duration.Add(new TimeSpan(0, 0, 1)); + Console.Clear(); + AnsiConsole.MarkupLine("Press Any Key to stop the timer."); + AnsiConsole.MarkupLine($"Duration: {duration}"); + } +} diff --git a/STUDY.CodingTracker/STUDY.CodingTracker/Helper/UserInput.cs b/STUDY.CodingTracker/STUDY.CodingTracker/Helper/UserInput.cs new file mode 100644 index 000000000..e3022a27b --- /dev/null +++ b/STUDY.CodingTracker/STUDY.CodingTracker/Helper/UserInput.cs @@ -0,0 +1,28 @@ +using Spectre.Console; + +namespace STUDY.CodingTracker.Helper; + +internal static class UserInput +{ + public static string GetUserDateInput(string period) + { + string dateInput = AnsiConsole.Ask($"Please enter the {period} date (format: dd/MM/yyyy HH:mm):"); + return dateInput; + } + + public static string GetUserIDInput(string operation) + { + string idInput = AnsiConsole.Ask($"Please enter the id of the coding session you want to {operation}:"); + + return idInput; + } + + public static string GetUserFilterPeriod(FilterChoice filterChoice) + { + Console.Clear(); + + string periodInput = AnsiConsole.Ask($"Please enter the {filterChoice} number you want the coding sessions displayed be filtered on:"); + + return periodInput; + } +} diff --git a/STUDY.CodingTracker/STUDY.CodingTracker/Helper/Verification.cs b/STUDY.CodingTracker/STUDY.CodingTracker/Helper/Verification.cs new file mode 100644 index 000000000..945db6212 --- /dev/null +++ b/STUDY.CodingTracker/STUDY.CodingTracker/Helper/Verification.cs @@ -0,0 +1,90 @@ +using System.Globalization; + +namespace STUDY.CodingTracker.Helper; + +public static class Verification +{ + public static (bool correct, DateTime date) VerifyDate(string dateInput) + { + bool correct = false; + DateTime date = new DateTime(); + + if (dateInput != null && DateTime.TryParseExact(dateInput, "dd/MM/yyyy HH:mm", new CultureInfo("en-US"), DateTimeStyles.None, out date)) + { + correct = true; + } + + return (correct, date); + } + + public static (bool correct, DateTime date) VerifyEndDate(string endDateInput, DateTime startDate) + { + bool correct = false; + var formatVerificationResult = VerifyDate(endDateInput); + + if (formatVerificationResult.correct && DateTime.Compare(formatVerificationResult.date, startDate) > 0) + { + correct = true; + } + + return (correct, formatVerificationResult.date); + } + + public static (bool correct, int id) VerifyId(string idInput) + { + bool correct = false; + int id = 0; + + if (idInput != null && int.TryParse(idInput, out id) && id > 0) + { + correct = true; + } + + return (correct, id); + } + + private static (bool, int) VerifyPeriod(string periodInput) + { + bool correct = false; + int periodNum = 0; + + if (periodInput != null && int.TryParse(periodInput, out periodNum)) + { + correct = true; + } + return (correct, periodNum); + } + + public static (bool correct, int periodNum) VerifyWeek(string weekInput) + { + bool correct = false; + (bool correct, int weekNum) basicVerificationResult = VerifyPeriod(weekInput); + + if (basicVerificationResult.correct && basicVerificationResult.weekNum > 0 && basicVerificationResult.weekNum < 54) + correct = true; + + return (correct, basicVerificationResult.weekNum); + } + + public static (bool correct, int periodNum) VerifyDay(string dayInput) + { + bool correct = false; + (bool correct, int dayNum) basicVerificationResult = VerifyPeriod(dayInput); + + if (basicVerificationResult.correct && basicVerificationResult.dayNum > 0 && basicVerificationResult.dayNum < 32) + correct = true; + + return (correct, basicVerificationResult.dayNum); + } + + public static (bool correct, int periodNum) VerifyYear(string yearInput) + { + bool correct = false; + (bool correct, int yearNum) basicVerificationResult = VerifyPeriod(yearInput); + + if (basicVerificationResult.correct && basicVerificationResult.yearNum >= 1900 && basicVerificationResult.yearNum <= DateTime.Now.Year) + correct = true; + + return (correct, basicVerificationResult.yearNum); + } +} diff --git a/STUDY.CodingTracker/STUDY.CodingTracker/Models/CodingSessionModel.cs b/STUDY.CodingTracker/STUDY.CodingTracker/Models/CodingSessionModel.cs new file mode 100644 index 000000000..978e5b76e --- /dev/null +++ b/STUDY.CodingTracker/STUDY.CodingTracker/Models/CodingSessionModel.cs @@ -0,0 +1,52 @@ +namespace STUDY.CodingTracker.Models; + +internal class CodingSessionModel +{ + public Int64 id { get; set; } + public DateTime startTime { get; set; } + public DateTime endTime { get; set; } + public TimeSpan duration { get; } + + public CodingSessionModel (string start, string end, Int64 newId, string newDuration) + { + id = newId; + + startTime = ConvertStringToDateTime(start); + + endTime = ConvertStringToDateTime(end); + + duration = ConvertStringToTimeSpan(newDuration); + } + + public CodingSessionModel (DateTime start, DateTime end, TimeSpan newDuration) + { + startTime = start; + endTime = end; + duration = newDuration; + } + + public CodingSessionModel (DateTime start, DateTime end) + { + startTime = start; + endTime = end; + duration = endTime.Subtract(startTime); + } + + private DateTime ConvertStringToDateTime(string date) + { + DateTime convertedDate; + + DateTime.TryParse(date, out convertedDate); + return convertedDate; + } + + private TimeSpan ConvertStringToTimeSpan(string time) + { + TimeSpan convertedTime; + + TimeSpan.TryParse(time, out convertedTime); + + return convertedTime; + } + +} diff --git a/STUDY.CodingTracker/STUDY.CodingTracker/Program.cs b/STUDY.CodingTracker/STUDY.CodingTracker/Program.cs new file mode 100644 index 000000000..450022d08 --- /dev/null +++ b/STUDY.CodingTracker/STUDY.CodingTracker/Program.cs @@ -0,0 +1,5 @@ +using STUDY.CodingTracker; + +UserInterface ui = new UserInterface(); + +ui.MainMenu(); diff --git a/STUDY.CodingTracker/STUDY.CodingTracker/Properties/launchSettings.json b/STUDY.CodingTracker/STUDY.CodingTracker/Properties/launchSettings.json new file mode 100644 index 000000000..1dcdfa2e7 --- /dev/null +++ b/STUDY.CodingTracker/STUDY.CodingTracker/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "STUDY.CodingTracker": { + "commandName": "Project", + "workingDirectory": "C:\\C# Academy\\STUDY.CodingTracker\\STUDY.CodingTracker\\" + } + } +} \ No newline at end of file diff --git a/STUDY.CodingTracker/STUDY.CodingTracker/STUDY.CodingTracker.csproj b/STUDY.CodingTracker/STUDY.CodingTracker/STUDY.CodingTracker.csproj new file mode 100644 index 000000000..58e0862a6 --- /dev/null +++ b/STUDY.CodingTracker/STUDY.CodingTracker/STUDY.CodingTracker.csproj @@ -0,0 +1,18 @@ + + + + Exe + net10.0 + enable + enable + + + + + + + + + + + diff --git a/STUDY.CodingTracker/STUDY.CodingTracker/UserInterface.cs b/STUDY.CodingTracker/STUDY.CodingTracker/UserInterface.cs new file mode 100644 index 000000000..1754bfbeb --- /dev/null +++ b/STUDY.CodingTracker/STUDY.CodingTracker/UserInterface.cs @@ -0,0 +1,337 @@ +using Microsoft.Extensions.Configuration; +using Spectre.Console; +using STUDY.CodingTracker.Controllers; +using STUDY.CodingTracker.Helper; +using STUDY.CodingTracker.Models; +using System.Globalization; + +namespace STUDY.CodingTracker; + +internal class UserInterface +{ + private CodingSessionController _codingSessionController; + + public UserInterface() + { + // Can't move it to CodingSessionController because of "Directory.GetCurrentDirectory()" + IConfiguration config = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: false, + reloadOnChange: true) + .Build(); + + _codingSessionController = new CodingSessionController(config); + } + + public void MainMenu() + { + while (true) + { + MainMenuChoice choice = GetMainMenuChoice(); + + switch (choice) + { + case MainMenuChoice.ViewCodingSessions: + FilterChoice filterChoice = AskFilter(); + int periodNum = AskPeriodNum(filterChoice); + OrderChoice orderChoice = AskOrder(); + + DisplayCodingSessionsTable(filterChoice, periodNum, orderChoice); + DisplayPressKeyToContinue(); + break; + case MainMenuChoice.AddCodingSession: + AskUserStopwatchChoice(); + DisplayPressKeyToContinue(); + break; + case MainMenuChoice.DeleteCodingSession: + DisplayDeleteCodingSessionUI(); + DisplayPressKeyToContinue(); + break; + case MainMenuChoice.UpdateCodingSession: + DisplayUpdateCodingSessionUI(); + DisplayPressKeyToContinue(); + break; + case MainMenuChoice.Exit: + AnsiConsole.MarkupLine("Thank you for using the app! See you soon!"); + return; + } + } + } + + private MainMenuChoice GetMainMenuChoice() + { + Console.Clear(); + + AnsiConsole.MarkupLine("Welcome to [cyan]Coding Tracker[/]!"); + AnsiConsole.MarkupLine("-----------------------------------"); + + MainMenuChoice choice = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("Please select one of the option:") + .AddChoices(Enum.GetValues()) + ); + + return choice; + } + + private FilterChoice AskFilter() + { + Console.Clear(); + + FilterChoice filterChoice = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("Please select one of the filter:") + .AddChoices(Enum.GetValues()) + ); + + return filterChoice; + } + + private int AskPeriodNum(FilterChoice filterChoice) + { + while (true) + { + string userInput = ""; + int periodNum = 0; + (bool correct, int periodNum) validationResult = (false, 0); + + if (filterChoice != FilterChoice.None) + { + userInput = UserInput.GetUserFilterPeriod(filterChoice); + } + else + { + return periodNum; + } + + switch (filterChoice) + { + case FilterChoice.Week: + validationResult = Verification.VerifyWeek(userInput); + break; + case FilterChoice.Day: + validationResult = Verification.VerifyDay(userInput); + break; + case FilterChoice.Year: + validationResult = Verification.VerifyYear(userInput); + break; + } + + if (validationResult.correct) + { + return validationResult.periodNum; + } + else + { + AnsiConsole.MarkupLine("[red]Please enter a correct number based on the period you've chosen.[/]"); + DisplayPressKeyToContinue(); + } + } + } + + private OrderChoice AskOrder() + { + OrderChoice orderChoice = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("Please select one of the order:") + .AddChoices(Enum.GetValues()) + ); + + return orderChoice; + } + + private void DisplayCodingSessionsTable(FilterChoice filterChoice, int periodNum, OrderChoice orderChoice) + { + Console.Clear(); + + var codingSessions = _codingSessionController.GetCodingSessions(filterChoice, periodNum, orderChoice); + + var table = new Table().RoundedBorder().BorderColor(Color.Gold1); + + table.AddColumn("[DarkOrange]ID[/]"); + table.AddColumn("[DarkOrange]Start Time[/]"); + table.AddColumn("[DarkOrange]End Time[/]"); + table.AddColumn("[DarkOrange]Duration[/]"); + + foreach (CodingSessionModel codingSession in codingSessions) + { + table.AddRow( + $"[yellow]{codingSession.id.ToString()}[/]", + codingSession.startTime.ToString("dd/MMM/yyyy HH:mm:ss"), + codingSession.endTime.ToString("dd/MMM/yyyy HH:mm:ss"), + codingSession.duration.ToString() + ); + } + + AnsiConsole.Write(table); + } + + private void AskUserStopwatchChoice() + { + StopwatchChoice choice = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("Do you wish to use the stopwatch?") + .AddChoices(Enum.GetValues()) + ); + + if (choice == StopwatchChoice.Yes) + ManageStopWatch(); + else + DisplayManualAddingCodingSessionUI(); + } + + private void ManageStopWatch() + { + bool success; + StopWatch stopwatch = new StopWatch(); + + DateTime startTime = DateTime.Now; + + TimeSpan duration = stopwatch.LaunchTimer(); + + DateTime endTime = DateTime.Now; + + CodingSessionModel newCodingSession = new CodingSessionModel(startTime, endTime, duration); + + success = _codingSessionController.AddCodingSession(newCodingSession); + + DisplaySuccessResultWhenAddingSession(success); + } + + private void DisplayManualAddingCodingSessionUI() + { + bool success; + + CodingSessionModel newCodingSession = CreateCodingSession(); + + success = _codingSessionController.AddCodingSession(newCodingSession); + + DisplaySuccessResultWhenAddingSession(success); + } + + private void DisplaySuccessResultWhenAddingSession(bool success) + { + if (success) + { + AnsiConsole.MarkupLine("[green]Coding session added successfully![/]"); + return; + } + + AnsiConsole.MarkupLine("[red]Coding session was not added...[/]"); + } + + private void DisplayDeleteCodingSessionUI() + { + FilterChoice filterChoice = AskFilter(); + int periodNum = AskPeriodNum(filterChoice); + OrderChoice orderChoice = AskOrder(); + + while (true) + { + DisplayCodingSessionsTable(filterChoice, periodNum, orderChoice); + + string idToDelete = UserInput.GetUserIDInput("delete"); + var verificationResult = Verification.VerifyId(idToDelete); + + if (!verificationResult.correct) + { + DisplayInputIDErrorMessage(); + continue; + } + int numberOfRows = _codingSessionController.DeleteCodingSession(verificationResult.id); + + if (numberOfRows > 0) + { + AnsiConsole.MarkupLine("[green]Coding session deleted successfully![/]"); + return; + } + + AnsiConsole.MarkupLine("[red]The id you entered doesn't exist.[/]"); + DisplayPressKeyToContinue(); + } + } + + private void DisplayUpdateCodingSessionUI() + { + FilterChoice filterChoice = AskFilter(); + int periodNum = AskPeriodNum(filterChoice); + OrderChoice orderChoice = AskOrder(); + + while (true) + { + DisplayCodingSessionsTable(filterChoice, periodNum, orderChoice); + + string idToUpdate = UserInput.GetUserIDInput("update"); + var verificationResult = Verification.VerifyId(idToUpdate); + + if (!verificationResult.correct) + { + DisplayInputIDErrorMessage(); + continue; + } + + CodingSessionModel updatedCodingSession = CreateCodingSession(); + + int numberOfRows = _codingSessionController.UpdateCodingSession(verificationResult.id, updatedCodingSession); + + if (numberOfRows > 0) + { + AnsiConsole.MarkupLine("[green]Coding session updated successfully![/]"); + return; + } + + AnsiConsole.MarkupLine("[red]The id you entered doesn't exist.[/]"); + } + } + + private void DisplayPressKeyToContinue() + { + AnsiConsole.MarkupLine("(Press Any Key to Continue.)"); + Console.ReadKey(); + } + + private CodingSessionModel CreateCodingSession() + { + while (true) + { + Console.Clear(); + + string startTime = UserInput.GetUserDateInput("start"); + var verificationResultStartTime = Verification.VerifyDate(startTime); + + if (!verificationResultStartTime.correct) + { + DisplayInputDateErrorMessage(); + continue; + } + + string endTime = UserInput.GetUserDateInput("end"); + var verificationResultEndTime = Verification.VerifyEndDate(endTime, verificationResultStartTime.date); + + if (!verificationResultEndTime.correct) + { + DisplayInputDateErrorMessage(); + continue; + } + + CodingSessionModel newCodingSession = new CodingSessionModel(verificationResultStartTime.date, verificationResultEndTime.date); + + return newCodingSession; + } + } + + private void DisplayInputIDErrorMessage() + { + AnsiConsole.MarkupLine("[red]You've inputted the ID in a wrong format. Please try again![/]"); + AnsiConsole.MarkupLine("Good format: [green]It must be a whole positive number.[/]"); + DisplayPressKeyToContinue(); + } + + private void DisplayInputDateErrorMessage() + { + AnsiConsole.MarkupLine("[red]You've inputted the date in a wrong format and/or in the wrong order. Please try again![/]"); + AnsiConsole.MarkupLine("Good format: [green]dd/MM/yyyy HH:mm:ss[/] (d = day, M = month, y = year, H = hour, m = minute, s = second)"); + AnsiConsole.MarkupLine("The start time must be [green]earlier[/] than end time."); + DisplayPressKeyToContinue(); + } +} diff --git a/STUDY.CodingTracker/STUDY.CodingTracker/appsettings.json b/STUDY.CodingTracker/STUDY.CodingTracker/appsettings.json new file mode 100644 index 000000000..521d664ec --- /dev/null +++ b/STUDY.CodingTracker/STUDY.CodingTracker/appsettings.json @@ -0,0 +1,6 @@ +{ + "DatabaseSettings": { + "connectionString": "Data Source=", + "databasePath": "./coding-tracker.db" + } +} \ No newline at end of file diff --git a/STUDY.CodingTracker/STUDY.CodingTracker/coding-tracker.db b/STUDY.CodingTracker/STUDY.CodingTracker/coding-tracker.db new file mode 100644 index 0000000000000000000000000000000000000000..88a68f801d1fc80109d3448738b712dc2a8ab967 GIT binary patch literal 12288 zcmeI&&2G~`5CGtH(zF#)Yy^j%koH)swY5K9C*Bjrv5-aM7RQyy35DXKpf*uScmP1+ zA$S|k9C#5Pg4v`garl#ZgxSP#c6Mhx`Fx6!!PBGhyrSpRH01BW03ZTGk1Wp>; zdj|*Ba6Wo*QO&L{;M?r;S#|Zcnw)J;n@N$zWlGEV(NRh__U=zcm(?L;y6;|FJA1Bc zeQ*{v&!#WOlUIXkHXBbTv-+%2t5{##Z{MLCBkR#DFVn|qK~IXT9~VRVBpuTDv^>so zDAiB%^6=-hK^Yfinf0Ob^x3aAqN~m8O8Q|aLpU~;po74dN_4^?G8GJm zA9f=abwenomSD?{IE6~mT Date: Tue, 5 May 2026 12:06:39 +0200 Subject: [PATCH 2/2] Add username to project directory name --- .../.gitattributes | 0 .../.gitignore | 0 .../README.md | 0 .../ReadMeAssets/CodingSessionsDisplay.png | Bin .../ReadMeAssets/MainMenu.png | Bin .../CodingTrackerTests.cs | 0 .../STUDY.CodingTracker.UnitTest.csproj | 0 .../STUDY.CodingTracker.slnx | 0 .../Controllers/CodingSessionController.cs | 0 .../STUDY.CodingTracker/Helper/Enums.cs | 0 .../STUDY.CodingTracker/Helper/StopWatch.cs | 0 .../STUDY.CodingTracker/Helper/UserInput.cs | 0 .../STUDY.CodingTracker/Helper/Verification.cs | 0 .../Models/CodingSessionModel.cs | 0 .../STUDY.CodingTracker/Program.cs | 0 .../Properties/launchSettings.json | 0 .../STUDY.CodingTracker/STUDY.CodingTracker.csproj | 0 .../STUDY.CodingTracker/UserInterface.cs | 0 .../STUDY.CodingTracker/appsettings.json | 0 .../STUDY.CodingTracker/coding-tracker.db | Bin 20 files changed, 0 insertions(+), 0 deletions(-) rename {STUDY.CodingTracker => STUDY.CodingTracker.Sephydev}/.gitattributes (100%) rename {STUDY.CodingTracker => STUDY.CodingTracker.Sephydev}/.gitignore (100%) rename {STUDY.CodingTracker => STUDY.CodingTracker.Sephydev}/README.md (100%) rename {STUDY.CodingTracker => STUDY.CodingTracker.Sephydev}/ReadMeAssets/CodingSessionsDisplay.png (100%) rename {STUDY.CodingTracker => STUDY.CodingTracker.Sephydev}/ReadMeAssets/MainMenu.png (100%) rename {STUDY.CodingTracker => STUDY.CodingTracker.Sephydev}/STUDY.CodingTracker.UnitTest/CodingTrackerTests.cs (100%) rename {STUDY.CodingTracker => STUDY.CodingTracker.Sephydev}/STUDY.CodingTracker.UnitTest/STUDY.CodingTracker.UnitTest.csproj (100%) rename {STUDY.CodingTracker => STUDY.CodingTracker.Sephydev}/STUDY.CodingTracker.slnx (100%) rename {STUDY.CodingTracker => STUDY.CodingTracker.Sephydev}/STUDY.CodingTracker/Controllers/CodingSessionController.cs (100%) rename {STUDY.CodingTracker => STUDY.CodingTracker.Sephydev}/STUDY.CodingTracker/Helper/Enums.cs (100%) rename {STUDY.CodingTracker => STUDY.CodingTracker.Sephydev}/STUDY.CodingTracker/Helper/StopWatch.cs (100%) rename {STUDY.CodingTracker => STUDY.CodingTracker.Sephydev}/STUDY.CodingTracker/Helper/UserInput.cs (100%) rename {STUDY.CodingTracker => STUDY.CodingTracker.Sephydev}/STUDY.CodingTracker/Helper/Verification.cs (100%) rename {STUDY.CodingTracker => STUDY.CodingTracker.Sephydev}/STUDY.CodingTracker/Models/CodingSessionModel.cs (100%) rename {STUDY.CodingTracker => STUDY.CodingTracker.Sephydev}/STUDY.CodingTracker/Program.cs (100%) rename {STUDY.CodingTracker => STUDY.CodingTracker.Sephydev}/STUDY.CodingTracker/Properties/launchSettings.json (100%) rename {STUDY.CodingTracker => STUDY.CodingTracker.Sephydev}/STUDY.CodingTracker/STUDY.CodingTracker.csproj (100%) rename {STUDY.CodingTracker => STUDY.CodingTracker.Sephydev}/STUDY.CodingTracker/UserInterface.cs (100%) rename {STUDY.CodingTracker => STUDY.CodingTracker.Sephydev}/STUDY.CodingTracker/appsettings.json (100%) rename {STUDY.CodingTracker => STUDY.CodingTracker.Sephydev}/STUDY.CodingTracker/coding-tracker.db (100%) diff --git a/STUDY.CodingTracker/.gitattributes b/STUDY.CodingTracker.Sephydev/.gitattributes similarity index 100% rename from STUDY.CodingTracker/.gitattributes rename to STUDY.CodingTracker.Sephydev/.gitattributes diff --git a/STUDY.CodingTracker/.gitignore b/STUDY.CodingTracker.Sephydev/.gitignore similarity index 100% rename from STUDY.CodingTracker/.gitignore rename to STUDY.CodingTracker.Sephydev/.gitignore diff --git a/STUDY.CodingTracker/README.md b/STUDY.CodingTracker.Sephydev/README.md similarity index 100% rename from STUDY.CodingTracker/README.md rename to STUDY.CodingTracker.Sephydev/README.md diff --git a/STUDY.CodingTracker/ReadMeAssets/CodingSessionsDisplay.png b/STUDY.CodingTracker.Sephydev/ReadMeAssets/CodingSessionsDisplay.png similarity index 100% rename from STUDY.CodingTracker/ReadMeAssets/CodingSessionsDisplay.png rename to STUDY.CodingTracker.Sephydev/ReadMeAssets/CodingSessionsDisplay.png diff --git a/STUDY.CodingTracker/ReadMeAssets/MainMenu.png b/STUDY.CodingTracker.Sephydev/ReadMeAssets/MainMenu.png similarity index 100% rename from STUDY.CodingTracker/ReadMeAssets/MainMenu.png rename to STUDY.CodingTracker.Sephydev/ReadMeAssets/MainMenu.png diff --git a/STUDY.CodingTracker/STUDY.CodingTracker.UnitTest/CodingTrackerTests.cs b/STUDY.CodingTracker.Sephydev/STUDY.CodingTracker.UnitTest/CodingTrackerTests.cs similarity index 100% rename from STUDY.CodingTracker/STUDY.CodingTracker.UnitTest/CodingTrackerTests.cs rename to STUDY.CodingTracker.Sephydev/STUDY.CodingTracker.UnitTest/CodingTrackerTests.cs diff --git a/STUDY.CodingTracker/STUDY.CodingTracker.UnitTest/STUDY.CodingTracker.UnitTest.csproj b/STUDY.CodingTracker.Sephydev/STUDY.CodingTracker.UnitTest/STUDY.CodingTracker.UnitTest.csproj similarity index 100% rename from STUDY.CodingTracker/STUDY.CodingTracker.UnitTest/STUDY.CodingTracker.UnitTest.csproj rename to STUDY.CodingTracker.Sephydev/STUDY.CodingTracker.UnitTest/STUDY.CodingTracker.UnitTest.csproj diff --git a/STUDY.CodingTracker/STUDY.CodingTracker.slnx b/STUDY.CodingTracker.Sephydev/STUDY.CodingTracker.slnx similarity index 100% rename from STUDY.CodingTracker/STUDY.CodingTracker.slnx rename to STUDY.CodingTracker.Sephydev/STUDY.CodingTracker.slnx diff --git a/STUDY.CodingTracker/STUDY.CodingTracker/Controllers/CodingSessionController.cs b/STUDY.CodingTracker.Sephydev/STUDY.CodingTracker/Controllers/CodingSessionController.cs similarity index 100% rename from STUDY.CodingTracker/STUDY.CodingTracker/Controllers/CodingSessionController.cs rename to STUDY.CodingTracker.Sephydev/STUDY.CodingTracker/Controllers/CodingSessionController.cs diff --git a/STUDY.CodingTracker/STUDY.CodingTracker/Helper/Enums.cs b/STUDY.CodingTracker.Sephydev/STUDY.CodingTracker/Helper/Enums.cs similarity index 100% rename from STUDY.CodingTracker/STUDY.CodingTracker/Helper/Enums.cs rename to STUDY.CodingTracker.Sephydev/STUDY.CodingTracker/Helper/Enums.cs diff --git a/STUDY.CodingTracker/STUDY.CodingTracker/Helper/StopWatch.cs b/STUDY.CodingTracker.Sephydev/STUDY.CodingTracker/Helper/StopWatch.cs similarity index 100% rename from STUDY.CodingTracker/STUDY.CodingTracker/Helper/StopWatch.cs rename to STUDY.CodingTracker.Sephydev/STUDY.CodingTracker/Helper/StopWatch.cs diff --git a/STUDY.CodingTracker/STUDY.CodingTracker/Helper/UserInput.cs b/STUDY.CodingTracker.Sephydev/STUDY.CodingTracker/Helper/UserInput.cs similarity index 100% rename from STUDY.CodingTracker/STUDY.CodingTracker/Helper/UserInput.cs rename to STUDY.CodingTracker.Sephydev/STUDY.CodingTracker/Helper/UserInput.cs diff --git a/STUDY.CodingTracker/STUDY.CodingTracker/Helper/Verification.cs b/STUDY.CodingTracker.Sephydev/STUDY.CodingTracker/Helper/Verification.cs similarity index 100% rename from STUDY.CodingTracker/STUDY.CodingTracker/Helper/Verification.cs rename to STUDY.CodingTracker.Sephydev/STUDY.CodingTracker/Helper/Verification.cs diff --git a/STUDY.CodingTracker/STUDY.CodingTracker/Models/CodingSessionModel.cs b/STUDY.CodingTracker.Sephydev/STUDY.CodingTracker/Models/CodingSessionModel.cs similarity index 100% rename from STUDY.CodingTracker/STUDY.CodingTracker/Models/CodingSessionModel.cs rename to STUDY.CodingTracker.Sephydev/STUDY.CodingTracker/Models/CodingSessionModel.cs diff --git a/STUDY.CodingTracker/STUDY.CodingTracker/Program.cs b/STUDY.CodingTracker.Sephydev/STUDY.CodingTracker/Program.cs similarity index 100% rename from STUDY.CodingTracker/STUDY.CodingTracker/Program.cs rename to STUDY.CodingTracker.Sephydev/STUDY.CodingTracker/Program.cs diff --git a/STUDY.CodingTracker/STUDY.CodingTracker/Properties/launchSettings.json b/STUDY.CodingTracker.Sephydev/STUDY.CodingTracker/Properties/launchSettings.json similarity index 100% rename from STUDY.CodingTracker/STUDY.CodingTracker/Properties/launchSettings.json rename to STUDY.CodingTracker.Sephydev/STUDY.CodingTracker/Properties/launchSettings.json diff --git a/STUDY.CodingTracker/STUDY.CodingTracker/STUDY.CodingTracker.csproj b/STUDY.CodingTracker.Sephydev/STUDY.CodingTracker/STUDY.CodingTracker.csproj similarity index 100% rename from STUDY.CodingTracker/STUDY.CodingTracker/STUDY.CodingTracker.csproj rename to STUDY.CodingTracker.Sephydev/STUDY.CodingTracker/STUDY.CodingTracker.csproj diff --git a/STUDY.CodingTracker/STUDY.CodingTracker/UserInterface.cs b/STUDY.CodingTracker.Sephydev/STUDY.CodingTracker/UserInterface.cs similarity index 100% rename from STUDY.CodingTracker/STUDY.CodingTracker/UserInterface.cs rename to STUDY.CodingTracker.Sephydev/STUDY.CodingTracker/UserInterface.cs diff --git a/STUDY.CodingTracker/STUDY.CodingTracker/appsettings.json b/STUDY.CodingTracker.Sephydev/STUDY.CodingTracker/appsettings.json similarity index 100% rename from STUDY.CodingTracker/STUDY.CodingTracker/appsettings.json rename to STUDY.CodingTracker.Sephydev/STUDY.CodingTracker/appsettings.json diff --git a/STUDY.CodingTracker/STUDY.CodingTracker/coding-tracker.db b/STUDY.CodingTracker.Sephydev/STUDY.CodingTracker/coding-tracker.db similarity index 100% rename from STUDY.CodingTracker/STUDY.CodingTracker/coding-tracker.db rename to STUDY.CodingTracker.Sephydev/STUDY.CodingTracker/coding-tracker.db