diff --git a/.gitignore b/.gitignore
index 0b26c11d..e1293c09 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,6 +13,7 @@
*.user
*.userosscache
*.sln.docstates
+*.db
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
diff --git a/DataConnection.cs b/DataConnection.cs
new file mode 100644
index 00000000..fc002850
--- /dev/null
+++ b/DataConnection.cs
@@ -0,0 +1,31 @@
+using Microsoft.Data.Sqlite;
+
+namespace Habit_Tracker_App;
+
+public class DataConnection
+{
+ static string databaseName = "Habit-Tracker";
+ string tableName = "drinking_water";
+ static string connectionString = $"Data Source={databaseName}.db";
+ public DataConnection()
+ {
+ CreateTable();
+ }
+ public void CreateTable()
+ {
+ using (var conn = new SqliteConnection(connectionString))
+ {
+ conn.Open();
+ var tableCmd = conn.CreateCommand();
+
+ tableCmd.CommandText = @$"CREATE TABLE IF NOT EXISTS {tableName}(
+ Id INTEGER PRIMARY KEY AUTOINCREMENT,
+ Date TEXT,
+ Quantity INTEGER
+ )";
+
+ tableCmd.ExecuteNonQuery();
+ conn.Close();
+ }
+ }
+}
diff --git a/Habit-Tracker.db b/Habit-Tracker.db
new file mode 100644
index 00000000..554c7a7a
Binary files /dev/null and b/Habit-Tracker.db differ
diff --git a/Habit.cs b/Habit.cs
new file mode 100644
index 00000000..6cd09506
--- /dev/null
+++ b/Habit.cs
@@ -0,0 +1,8 @@
+namespace Habit_Tracker_App;
+
+internal class Habit
+{
+ public int HabitId { get; set; }
+ public required string Date { get; set; }
+ public int Quantity { get; set; }
+}
\ No newline at end of file
diff --git a/Habit_Tracker.slnx b/Habit_Tracker.slnx
new file mode 100644
index 00000000..6067de6f
--- /dev/null
+++ b/Habit_Tracker.slnx
@@ -0,0 +1,3 @@
+
+
+
diff --git a/Habit_Tracker_App.csproj b/Habit_Tracker_App.csproj
new file mode 100644
index 00000000..a182699b
--- /dev/null
+++ b/Habit_Tracker_App.csproj
@@ -0,0 +1,21 @@
+
+
+
+ Exe
+ net10.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
diff --git a/Habit_Tracker_App.sln b/Habit_Tracker_App.sln
new file mode 100644
index 00000000..c5598968
--- /dev/null
+++ b/Habit_Tracker_App.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 18
+VisualStudioVersion = 18.0.11222.15 d18.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Habit_Tracker_App", "Habit_Tracker_App.csproj", "{030116D8-7B25-5231-1781-6774979F91DA}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {030116D8-7B25-5231-1781-6774979F91DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {030116D8-7B25-5231-1781-6774979F91DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {030116D8-7B25-5231-1781-6774979F91DA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {030116D8-7B25-5231-1781-6774979F91DA}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {C7362BB7-ADA6-47C2-9FC5-2E3BD7EC1258}
+ EndGlobalSection
+EndGlobal
diff --git a/MenuUI.cs b/MenuUI.cs
new file mode 100644
index 00000000..37fe75f8
--- /dev/null
+++ b/MenuUI.cs
@@ -0,0 +1,52 @@
+using Spectre.Console;
+
+namespace Habit_Tracker_App;
+public static class MenuUI
+{
+ public static void WelcomeMessage()
+ {
+ AnsiConsole.MarkupLine("[bold orange3]Welcome to the Habit Tracker Application![/]\n");
+ AnsiConsole.MarkupLine("[bold orange3]In this application you will manage the drinking water habit. To Continue, please press Enter... [/]");
+ Console.ReadKey();
+ }
+ public static string GetMenuChoice()
+ {
+ Console.Clear();
+ string userInput;
+
+ while (true)
+ {
+ var menuTitle = new Rule("[bold orange3]Main Menu[/]");
+ menuTitle.Justification = Justify.Left;
+ AnsiConsole.Write(menuTitle);
+ Console.WriteLine();
+ AnsiConsole.MarkupLine($"[green]1[/] - Add an entry");
+ AnsiConsole.MarkupLine($"[green]2[/] - View saved entries");
+ AnsiConsole.MarkupLine($"[green]3[/] - Update an entry");
+ AnsiConsole.MarkupLine($"[green]4[/] - Delete an entry");
+ AnsiConsole.MarkupLine($"[red]X[/] - Exit the application");
+ AnsiConsole.Markup("[green]Your Selection: [/]");
+ userInput = Console.ReadLine().ToLower();
+
+ switch (userInput)
+ {
+ case "1":
+ case "2":
+ case "3":
+ case "4":
+ case "x":
+ return userInput;
+ default:
+ AnsiConsole.MarkupLine("[red]Invalid entry. Please type a [/][green]menu choice[/]");
+ break;
+ }
+ }
+ }
+ public static void AddSpace(int number)
+ {
+ for (int i = 0; i < number; i++)
+ {
+ Console.WriteLine();
+ }
+ }
+}
diff --git a/Program.cs b/Program.cs
new file mode 100644
index 00000000..aab6dcbe
--- /dev/null
+++ b/Program.cs
@@ -0,0 +1,314 @@
+using Microsoft.Data.Sqlite;
+using Spectre.Console;
+using System.Text.RegularExpressions;
+
+namespace Habit_Tracker_App;
+public class Program
+{
+ static string databaseName = "Habit-Tracker";
+ static string tableName = "drinking_water";
+ static string connectionString = $"Data Source={databaseName}.db";
+ DataConnection conn = new();
+ static void Main(string[] args)
+ {
+ string userChoice = "";
+ MenuUI.WelcomeMessage();
+
+ // Main Loop
+ while (userChoice != "x")
+ {
+ userChoice = MenuUI.GetMenuChoice();
+ MenuUI.AddSpace(3);
+
+ switch (userChoice)
+ {
+ case "1": // Add entry
+ InsertRecord();
+ Console.ReadLine();
+ break;
+ case "2": // View saved entries
+ ViewAllRecords();
+ AnsiConsole.MarkupLine("[bold orange3]Press Enter to continue...[/]");
+ Console.ReadKey();
+ break;
+ case "3": // Update an entry
+ UpdateRecord();
+ AnsiConsole.MarkupLine("[bold orange3]Press Enter to continue...[/]");
+ Console.ReadKey();
+ break;
+ case "4": // Delete an entry
+ DeleteRecord();
+ AnsiConsole.MarkupLine("[bold orange3]Press Enter to continue...[/]");
+ Console.ReadKey();
+ break;
+ case "x": // Exit application
+ AnsiConsole.MarkupLine("[bold red]Press Enter to exit...[/]");
+ Console.ReadKey();
+ break;
+ default:
+ AnsiConsole.MarkupLine("[bold red]Error - Invalid entry[/]");
+ Console.ReadKey();
+ break;
+ }
+ }
+ }
+
+ private static void DeleteRecord()
+ {
+ int idSelection = GetValidRecordID();
+ int rowCount;
+
+ using (var conn = new SqliteConnection(connectionString))
+ {
+ conn.Open();
+ var command = conn.CreateCommand();
+ command.CommandText = @$"DELETE FROM {tableName}
+ WHERE Id = '{idSelection}'";
+ rowCount = command.ExecuteNonQuery();
+ conn.Close();
+ }
+ AnsiConsole.Markup($"[bold orange3]You Deleted {rowCount} Row(s).[/]");
+ }
+ private static void UpdateRecord()
+ {
+ string inputDate;
+ int inputQuantity;
+ int rowsAffected;
+ bool exitLoop = false;
+
+ //Prompt and return valid record ID
+ int idSelection = GetValidRecordID();
+ GetSingleRecord(idSelection);
+
+ //Get valid date entry
+ do
+ {
+ inputDate = GetDate();
+ if (inputDate == "")
+ continue;
+ else
+ exitLoop = true;
+ }
+ while (exitLoop == false);
+
+ //Get valid quantity entry
+ do
+ {
+ inputQuantity = GetQuantity();
+ if (inputQuantity == 0)
+ continue;
+ else
+ exitLoop = true;
+ }
+ while (exitLoop == false);
+
+ using (var conn = new SqliteConnection(connectionString))
+ {
+ conn.Open();
+ var command = conn.CreateCommand();
+ command.CommandText = @$"UPDATE {tableName}
+ SET Date = '{inputDate}', Quantity = '{inputQuantity}'
+ WHERE Id = '{idSelection}'";
+ rowsAffected = command.ExecuteNonQuery();
+ conn.Close();
+ }
+ AnsiConsole.Markup($"[bold orange3]{rowsAffected} record updated.[/]");
+ }
+ private static void ViewAllRecords()
+ {
+ List tableData = GetAllRecords();
+
+ var recordsTable = new Spectre.Console.Table();
+ recordsTable.AddColumn("[bold orange3]ID[/]")
+ .AddColumn("[bold orange3]Date[/]")
+ .AddColumn("[bold orange3]Glasses Drank[/]");
+
+ foreach (Habit row in tableData)
+ {
+ recordsTable = recordsTable.AddRow(row.HabitId.ToString(), row.Date, row.Quantity.ToString());
+ }
+ AnsiConsole.Write(recordsTable);
+ }
+ private static List GetAllRecords()
+ {
+ // This was moved out of ViewAllRecords method so it could also be used for record selection in other methods.
+ List tableData = new List();
+ try
+ {
+ using (var conn = new SqliteConnection(connectionString))
+ {
+ conn.Open();
+ var command = conn.CreateCommand();
+ command.CommandText = @$"SELECT Id, Date, Quantity FROM {tableName}";
+
+ using (SqliteDataReader reader = command.ExecuteReader())
+ {
+ if (reader.HasRows)
+ {
+ while (reader.Read())
+ {
+ tableData.Add(new Habit
+ {
+ HabitId = reader.GetInt32(0),
+ Date = reader.GetString(1),
+ Quantity = reader.GetInt32(2)
+ });
+ }
+ }
+ else
+ {
+ AnsiConsole.MarkupLine($"[bold red]No records found[/]");
+ Console.ReadLine();
+ }
+ }
+ conn.Close();
+ return tableData;
+ }
+ }
+ catch (Exception ex)
+ {
+ AnsiConsole.MarkupLine($"[bold red]An error occurred: {ex.Message}[/]");
+ return new List();
+ }
+ }
+ private static void GetSingleRecord(int ID) //IMPROVE - Could create overload function with GetAllRecords if no parameters passed
+ {
+ List tableData = new();
+ try
+ {
+ using (var conn = new SqliteConnection(connectionString))
+ {
+ conn.Open();
+ var command = conn.CreateCommand();
+ command.CommandText = @$"SELECT Id, Date, Quantity FROM {tableName}
+ WHERE Id = {ID}";
+ using (SqliteDataReader reader = command.ExecuteReader())
+ {
+ if (reader.HasRows)
+ {
+ while (reader.Read())
+ {
+ tableData.Add(new Habit
+ {
+ HabitId = reader.GetInt32(0),
+ Date = reader.GetString(1),
+ Quantity = reader.GetInt32(2)
+ });
+ }
+ }
+ else
+ {
+ AnsiConsole.MarkupLine($"[bold red]No records found[/]");
+ Console.ReadLine();
+ }
+ }
+ conn.Close();
+ }
+ }
+ catch (Exception ex)
+ {
+ AnsiConsole.MarkupLine($"[bold red]An error occurred: {ex.Message}[/]");
+ }
+ }
+ private static void InsertRecord()
+ {
+ string inputDate;
+ int inputQuantity;
+ bool exitLoop = false;
+ //Get valid date entry
+ do
+ {
+ inputDate = GetDate();
+ if (inputDate == "")
+ continue;
+ else
+ exitLoop = true;
+ }
+ while (exitLoop == false);
+ //Get valid quantity entry
+
+ do
+ {
+ inputQuantity = GetQuantity();
+ if (inputQuantity == 0)
+ continue;
+ else
+ exitLoop = true;
+ }
+ while (exitLoop == false);
+
+ //Save to database
+ try
+ {
+ using (var conn = new SqliteConnection(connectionString))
+ {
+ conn.Open();
+ var command = conn.CreateCommand();
+ command.CommandText = @$"INSERT INTO {tableName}(Date, Quantity)
+ VALUES('{inputDate}','{inputQuantity}')";
+ command.ExecuteNonQuery();
+ conn.Close();
+ }
+ AnsiConsole.MarkupLine("[bold orange3]Record Saved![/]");
+ AnsiConsole.MarkupLine("[bold orange3]Details:[/]");
+ AnsiConsole.MarkupLine($"[bold orange3]Date: [/]{inputDate}");
+ AnsiConsole.MarkupLine($"[bold orange3]Quantity: [/]{inputQuantity}");
+ AnsiConsole.MarkupLine($"[bold orange3]Date: [/]{inputDate}");
+ }
+ catch (Exception ex)
+ {
+ AnsiConsole.MarkupLine($"[bold red]An error occurred: {ex.Message}[/]");
+ }
+ }
+ private static string GetDate()
+ {
+ AnsiConsole.Markup("[green]Please enter the date of your entry (MM-DD-YYYY): [/]");
+ string? input = Console.ReadLine();
+
+ if (Regex.IsMatch(input, @"\d{2}-\d{2}-\d{4}"))
+ return input;
+ else
+ AnsiConsole.MarkupLine("[bold red]Invalid Input. Please try again[/]");
+ return "";
+ }
+ private static int GetQuantity()
+ {
+ AnsiConsole.Markup("[green]Please enter whole number of glasses to log [/]");
+ int quantity;
+
+ while(!int.TryParse(Console.ReadLine(), out quantity))
+ {
+ AnsiConsole.MarkupLine("[bold red]Invalid Input. Please try again[/]");
+ }
+ return quantity;
+ }
+ private static int GetValidRecordID()
+ {
+ // Retrieve all record ID's from DB
+ List recordsTable = GetAllRecords();
+ List validRecord = new();
+ foreach (Habit record in recordsTable)
+ {
+ validRecord.Add(record.HabitId);
+ }
+ ViewAllRecords();
+
+ string input;
+ int validInput;
+ bool exitLoop = false;
+
+ // Validate selection to current record ID's
+ do
+ {
+ AnsiConsole.Markup("[bold orange3]Please select the ID of the record: [/]");
+ input = Console.ReadLine();
+
+ if (!int.TryParse(input, out validInput) || !validRecord.Contains(validInput))
+ AnsiConsole.MarkupLine("[bold red]Error - Invalid entry[/]");
+ else
+ exitLoop = true;
+ }
+ while (exitLoop == false);
+ return validInput;
+ }
+}
\ No newline at end of file
diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json
new file mode 100644
index 00000000..ef03e5df
--- /dev/null
+++ b/Properties/launchSettings.json
@@ -0,0 +1,8 @@
+{
+ "profiles": {
+ "Habit_Tracker_App": {
+ "commandName": "Project",
+ "workingDirectory": "C:\\Users\\tripp\\source\\repos\\TheCSharpAcademy\\ConsoleProjects\\Habit-Tracker\\"
+ }
+ }
+}
\ No newline at end of file
diff --git a/ReadMe.md b/ReadMe.md
new file mode 100644
index 00000000..7a104949
--- /dev/null
+++ b/ReadMe.md
@@ -0,0 +1,33 @@
+# Habit Tracker Created by Atizzle07
+
+A simple console-based habit tracker written in C#.
+It uses **SQLite** for data persistence and Spectre Console for basic UI enhancement
+
+This project is based on the Habit Tracker exercise from **CSharp Academy**, with a few personal tweaks and improvements.
+
+---
+## Features
+- Add new records (date + quantity)
+- View all records in a formatted table
+- Update and Delete existing entries
+- Data stored locally in a SQLite database file
+- Basic error handling and input validation
+---
+## Tech Stack
+
+- Application Type: .NET (Console App)
+- Language: C#
+- Database: SQLite
+- Console UI: [Spectre.Console](https://spectreconsole.net/)
+---
+## Project Files and Structure
+
+- `Program.cs` – Application entry point and main menu loop.
+- `Habit.cs` – Model class representing a habit record. Contains these properties:
+ - `HabitId`, `Date`, `Quantity`).
+- `DataConnection.cs` – Handles SQLite connection. Creates DB if it doesn't already exist.
+- `MenuUI.cs` - Handles Main Menu display
+
+---
+
+