Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions CodingTracker/CodingController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
using Spectre.Console;
using System.Globalization;

namespace CodingTracker
{
public class CodingController
{
private readonly Database _db;

public CodingController(Database db)
{
_db = db;
}

public string CalculateDuration(string timeStart, string timeEnd)
{
DateTime parseTimeStart = DateTime.ParseExact(timeStart, "dd.MM.yyyy HH:mm", CultureInfo.InvariantCulture);
DateTime parseTimeEnd = DateTime.ParseExact(timeEnd, "dd.MM.yyyy HH:mm", CultureInfo.InvariantCulture);

TimeSpan duration = parseTimeEnd - parseTimeStart;

var durationString = $"{duration.TotalHours.ToString()} hours {duration.Minutes.ToString()} minutes";

return durationString;
}

public void CreateSession()
{
var v = new Validation();
var isGetCorrectDates = false;
var timeStart = "";
var timeEnd = "";
var isCorrectDates = true;

while (!isGetCorrectDates)
{
timeStart = AnsiConsole.Prompt(new TextPrompt<string>("Enter the date and time of the session start [grey](dd.MM.yyyy HH:mm)[/]:"));
timeEnd = AnsiConsole.Prompt(new TextPrompt<string>("Enter the date and time of the session end [grey](dd.MM.yyyy HH:mm)[/]:"));

isCorrectDates = v.ValidationDates(timeStart, timeEnd);
isGetCorrectDates = isCorrectDates;
}

string durationSession = CalculateDuration(timeStart, timeEnd);
_db.CreateRecordSession(timeStart, timeEnd, durationSession);
}

public void ReadAllSessions()
{
List<CodingSession> listSessions = _db.ReadListSessions();
if (listSessions.Count() == 0)
{
AnsiConsole.MarkupLine("[red]Oops! There are no saved sessions yet.[/]");
return;
}

var tableSessions = new Table();
tableSessions.AddColumn("ID");
tableSessions.AddColumn("Time of the session Start");
tableSessions.AddColumn("Time of the session End");
tableSessions.AddColumn("Duration of the session");

foreach (CodingSession session in listSessions)
{
tableSessions.AddRow(session.ID.ToString(), session.StartTime.ToString(), session.EndTime.ToString(), session.Duration.ToString());
}
AnsiConsole.Write(tableSessions);
}

public void UpdateSession()
{
var v = new Validation();
List<CodingSession> listSessions = _db.ReadListSessions();
if (listSessions.Count() == 0)
{
AnsiConsole.MarkupLine("[red]Oops! There are no saved sessions yet.[/]");
return;
}

CodingSession choiceSession = AnsiConsole.Prompt(
new SelectionPrompt<CodingSession>()
.Title("Which session do you want to update?")
.AddChoices(listSessions)
);
var choiceChange = AnsiConsole.Prompt(
new SelectionPrompt<string>()
.Title("What do you want to change?")
.AddChoices(new[] {
"Time of session start", "Time of session end"
}));

var newTimeStart = choiceSession.StartTime;
var newTimeEnd = choiceSession.EndTime;
var newDuration = choiceSession.Duration;
var isGetCorrectDates = false;

switch (choiceChange)
{
case "Time of session start":
while (!isGetCorrectDates)
{
newTimeStart = AnsiConsole.Prompt(new TextPrompt<string>("Enter a new session start date and time [grey](dd.MM.yyyy HH:mm)[/]:"));
var isCorrectDate = v.ValidationDates(newTimeStart, newTimeEnd);
isGetCorrectDates = isCorrectDate;
}

newDuration = CalculateDuration(newTimeStart, newTimeEnd);

_db.UpdateRecordSession(choiceSession.ID, newTimeStart, newTimeEnd, newDuration);
break;
case "Time of session end":
while (!isGetCorrectDates)
{
newTimeEnd = AnsiConsole.Prompt(new TextPrompt<string>("Enter a new session end date and time [grey](dd.MM.yyyy HH:mm)[/]:"));
var isCorrectDate = v.ValidationDates(newTimeStart, newTimeEnd);
isGetCorrectDates = isCorrectDate;
}

newDuration = CalculateDuration(newTimeStart, newTimeEnd);

_db.UpdateRecordSession(choiceSession.ID, newTimeStart, newTimeEnd, newDuration);
break;
}
}

public void DeleteSession()
{
List<CodingSession> listSessions = _db.ReadListSessions();
if (listSessions.Count() == 0)
{
AnsiConsole.MarkupLine("[red]Oops! There are no saved sessions yet.[/]");
return;
}

CodingSession choiceSession = AnsiConsole.Prompt(
new SelectionPrompt<CodingSession>()
.Title("Which session do you want to delete?")
.AddChoices(listSessions)
);

_db.DeleteRecordSession(choiceSession.ID);
}
}
}
14 changes: 14 additions & 0 deletions CodingTracker/CodingSession.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace CodingTracker
{
public class CodingSession
{
public int ID { get; set; }
public string StartTime { get; set; } = "";
public string EndTime { get; set; } = "";
public string Duration { get; set; } = "";
public override string ToString()
{
return $"{ID}. {StartTime: dd.MM.yyyy HH:mm} → {EndTime: dd.MM.yyyy HH:mm} ({Duration})";
}
}
}
18 changes: 18 additions & 0 deletions CodingTracker/CodingTracker.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Dapper" Version="2.1.66" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.1" />
<PackageReference Include="Spectre.Console" Version="0.54.0" />
</ItemGroup>

</Project>
3 changes: 3 additions & 0 deletions CodingTracker/CodingTracker.slnx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<Solution>
<Project Path="CodingTracker.csproj" />
</Solution>
74 changes: 74 additions & 0 deletions CodingTracker/Database.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using Dapper;
using Microsoft.Extensions.Configuration;
using Spectre.Console;
using System.Data;
using Microsoft.Data.Sqlite;

namespace CodingTracker
{
public class Database
{
private string _connectionString = null!;
public void Initialize()
{
IConfigurationRoot configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.Build();

var connectionString = configuration.GetConnectionString("Default");

if (connectionString == null)
{
AnsiConsole.MarkupLine("[red]Oops! Connection string not found.[/]");
return;
}

_connectionString = connectionString;

using IDbConnection connection = new SqliteConnection(_connectionString);

var request = "CREATE TABLE IF NOT EXISTS CodingSessions (ID INTEGER PRIMARY KEY AUTOINCREMENT, StartTime TEXT NOT NULL, EndTime TEXT NOT NULL, Duration TEXT NOT NULL);";

connection.Execute(request);
}

public List<CodingSession> ReadListSessions()
{
using IDbConnection connection = new SqliteConnection(_connectionString);

var request = "SELECT * FROM CodingSessions";

var sessions = connection.Query<CodingSession>(request);

return sessions.ToList();
}

public void CreateRecordSession(string timeStart, string timeEnd, string durationSession)
{
using IDbConnection connection = new SqliteConnection(_connectionString);

var request = "INSERT INTO CodingSessions (StartTime, EndTime, Duration) VALUES (@TimeStart, @TimeEnd, @DurationSession)";

connection.Execute(request, new { TimeStart = timeStart, TimeEnd = timeEnd, DurationSession = durationSession });
}

public void UpdateRecordSession(int id, string timeStart, string timeEnd, string durationSession)
{
using IDbConnection connection = new SqliteConnection(_connectionString);

var request = "UPDATE CodingSessions SET StartTime = @TimeStart, EndTime = @TimeEnd, Duration = @DurationSession WHERE ID = @ID";

connection.Execute(request, new { TimeStart = timeStart, TimeEnd = timeEnd, DurationSession = durationSession, ID = id });
}

public void DeleteRecordSession(int id)
{
using IDbConnection connection = new SqliteConnection(_connectionString);

var request = "DELETE FROM CodingSessions WHERE ID = @id";

connection.Execute(request, new { id });
}
}
}
7 changes: 7 additions & 0 deletions CodingTracker/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"profiles": {
"CodingTracker": {
"commandName": "Project"
}
}
}
44 changes: 44 additions & 0 deletions CodingTracker/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Habit Tracker

Habit Tracker is a console-based CRUD application for tracking coding sessions.
The project is built with **C#**, **Dapper**, and **Spectre.Console**, focusing on clean architecture, simplicity, and practical use of database-driven applications.

The application allows users to manage coding sessions while automatically calculating the duration based on the provided start and end times.

---

## Features

The user can:

- Create a new coding session
- View all existing coding sessions
- Update an existing coding session
- Delete a coding session

### Automatic Duration Calculation

- The user enters a start date and time
- The user enters an end date and time
- The application calculates the session duration automatically

---

## Technologies Used

- C#
- .NET
- Dapper
- Spectre.Console
- SQL Server / LocalDB

---

## Getting Started

### Clone the Repository

```bash
git clone <repository-url>
cd HabitTracker
```
47 changes: 47 additions & 0 deletions CodingTracker/UserInput.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using Spectre.Console;

namespace CodingTracker
{
internal class UserInput
{
public static void Main()
{
var db = new Database();
db.Initialize();
var cc = new CodingController(db);
var isRunning = true;
while (isRunning)
{
AnsiConsole.Write(new Rule("[bold violet]MAIN MENU[/]").RuleStyle("violet").LeftJustified());

var choiceAction = AnsiConsole.Prompt(
new SelectionPrompt<string>()
.Title("[yellow]What do you want to do?[/]")
.PageSize(5)
.AddChoices(new[] {
"Exit", "Create a new session", "Read all existing sessions",
"Update the session", "Delete the session"
}));

switch (choiceAction) {
case "Exit":
isRunning = false;
AnsiConsole.Clear();
break;
case "Create a new session":
cc.CreateSession();
break;
case "Read all existing sessions":
cc.ReadAllSessions();
break;
case "Update the session":
cc.UpdateSession();
break;
case "Delete the session":
cc.DeleteSession();
break;
}
}
}
}
}
28 changes: 28 additions & 0 deletions CodingTracker/Validation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using Spectre.Console;
using System.Globalization;

namespace CodingTracker
{
internal class Validation
{
public bool ValidationDates(string timeStart, string timeEnd)
{
if (!DateTime.TryParseExact(timeStart, "dd.MM.yyyy HH:mm", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime parsedTimeStart))
{
AnsiConsole.MarkupLine("[red]Oops! Incorrect session start date format.[/]");
return false;
}
if (!DateTime.TryParseExact(timeEnd, "dd.MM.yyyy HH:mm", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime parsedTimeEnd))
{
AnsiConsole.MarkupLine("[red]Oops! Incorrect session end date format.[/]");
return false;
}
if (parsedTimeStart >= parsedTimeEnd)
{
AnsiConsole.MarkupLine("[red]Oops! Session start date cannot be greater than or equal to the end date.[/]");
return false;
}
return true;
}
}
}
Loading