diff --git a/HabitTracker.sln b/HabitTracker.sln new file mode 100644 index 00000000..261b6db6 --- /dev/null +++ b/HabitTracker.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.11.35327.3 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HabitTracker", "HabitTracker\HabitTracker.csproj", "{92CE25FA-B589-48E7-B35A-A426F7E1CFDF}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {92CE25FA-B589-48E7-B35A-A426F7E1CFDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {92CE25FA-B589-48E7-B35A-A426F7E1CFDF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {92CE25FA-B589-48E7-B35A-A426F7E1CFDF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {92CE25FA-B589-48E7-B35A-A426F7E1CFDF}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {875D0802-5575-4A76-8935-8CFBF1874BF4} + EndGlobalSection +EndGlobal diff --git a/HabitTracker/Credits.cs b/HabitTracker/Credits.cs new file mode 100644 index 00000000..56ffa692 --- /dev/null +++ b/HabitTracker/Credits.cs @@ -0,0 +1,14 @@ +namespace HabitTracker +{ + internal class Credits + { + static public void GetCredits() + { + var credits = new List(); + credits.Add("Created by Nikita Kostin.\n"); + credits.ForEach(i => Console.Write(i)); + Console.Write("Press any key to go back to the main menu..."); + Console.ReadKey(); + } + } +} diff --git a/HabitTracker/Data/DbConnection.cs b/HabitTracker/Data/DbConnection.cs new file mode 100644 index 00000000..5cc5ed7a --- /dev/null +++ b/HabitTracker/Data/DbConnection.cs @@ -0,0 +1,172 @@ +using Microsoft.Data.Sqlite; + +namespace HabitTracker.Data +{ + public class DbConnection + { + private static SqliteConnection _connection; + + private DbConnection() { } + enum Frequency + { + Daily, + Weekly, + Monthly + } + + public static SqliteConnection GetConnection(bool isStart = false) + { + if (_connection == null) + { + const string dbPath = "testDb.db"; + _connection = new SqliteConnection($"Data Source={dbPath}"); + _connection.Open(); + Console.WriteLine("The database has been connected."); + + if (isStart) + { + DbConnection.CreateTable("Habits"); + DbConnection.CreateTable("Records"); + DbConnection.SeedData(); + } + + + _connection.Close(); + } + return _connection; + } + + public static void CloseConnection() + { + if (_connection != null) + { + _connection.Close(); + _connection.Dispose(); + _connection = null; + Console.WriteLine("The database connection has been closed."); + } + } + + public static void CreateTable(string name) + { + try + { + var command = _connection.CreateCommand(); + if (name == "Habits") + { + command.CommandText = + @" + CREATE TABLE IF NOT EXISTS Habits ( + Id INTEGER PRIMARY KEY AUTOINCREMENT, + Name TEXT NOT NULL, + Frequency TEXT NOT NULL, + TimesPerPeriod INTEGER NOT NULL, + StartDate TEXT NOT NULL + ) + "; + command.ExecuteNonQuery(); + } + else + { + command.CommandText = + @" + CREATE TABLE IF NOT EXISTS Records ( + Id INTEGER PRIMARY KEY AUTOINCREMENT, + Name TEXT NOT NULL, + HabitDate TEXT NOT NULL, + HabitId INTEGER, + FOREIGN KEY (HabitId) REFERENCES Habits(Id) ON DELETE CASCADE + ) + "; + command.ExecuteNonQuery(); + } + + } + catch (Exception ex) + { + Console.WriteLine("Error creating table: " + ex.Message); + } + + } + + + public static void SeedData() + { + bool create = false; + using (var command = _connection.CreateCommand()) + { + command.CommandText = "SELECT COUNT(*) FROM Habits"; + long habitCount = (long)command.ExecuteScalar(); + + if (habitCount == 0) + { + create = true; + Console.WriteLine("Seeding data into the Habits table."); + var random = new Random(); + + for (int i = 0; i < 100; i++) + { + string habitName = $"Habit {i + 1}"; + Frequency frequency = (Frequency)random.Next(0, 3); + string frequencyString = frequency.ToString(); + int timesPerPeriod = random.Next(1, 5); + string startDate = DateTime.Now.AddDays(-random.Next(0, 100)).ToString("yyyy-MM-dd"); + + command.CommandText = + @" + INSERT INTO Habits (Name, Frequency, TimesPerPeriod, StartDate) + VALUES (@habitName, @frequency, @timesPerPeriod, @startDate) + "; + + command.Parameters.Clear(); + command.Parameters.AddWithValue("@habitName", habitName); + command.Parameters.AddWithValue("@frequency", frequencyString); + command.Parameters.AddWithValue("@timesPerPeriod", timesPerPeriod); + command.Parameters.AddWithValue("@startDate", startDate); + + command.ExecuteNonQuery(); + } + } + } + + if (create) + { + using (var command = _connection.CreateCommand()) + { + command.CommandText = "SELECT Id, Name FROM Habits"; + using (var reader = command.ExecuteReader()) + { + while (reader.Read()) + { + int habitId = reader.GetInt32(0); + string habitName = reader.GetString(1); + + using (var insertCommand = _connection.CreateCommand()) + { + for (int j = 0; j < 10; j++) + { + string habitDate = DateTime.Now.AddDays(-new Random().Next(0, 30)).ToString("yyyy-MM-dd"); + + insertCommand.CommandText = + @" + INSERT INTO Records (Name, HabitDate, HabitId) + VALUES (@habitName, @habitDate, @habitId) + "; + + insertCommand.Parameters.Clear(); + insertCommand.Parameters.AddWithValue("@habitName", habitName); + insertCommand.Parameters.AddWithValue("@habitDate", habitDate); + insertCommand.Parameters.AddWithValue("@habitId", habitId); + + insertCommand.ExecuteNonQuery(); + } + } + } + } + } + } + } + + + } +} diff --git a/HabitTracker/Habit.cs b/HabitTracker/Habit.cs new file mode 100644 index 00000000..0e84f635 --- /dev/null +++ b/HabitTracker/Habit.cs @@ -0,0 +1,330 @@ +namespace HabitTracker +{ + public class Habit + { + enum Frequency + { + Daily, + Weekly, + Monthly + } + + private static string GetInput (string logName, string dataType = "string") + { + Console.WriteLine($"Enter the {logName}:"); + string input = Console.ReadLine (); + + switch (dataType) + { + case "frequency": + Frequency frequencyValue; + + while (!Enum.TryParse(input, ignoreCase: true, out frequencyValue) || !Enum.IsDefined(typeof(Frequency), frequencyValue)) + { + Console.WriteLine("Invalid input. Please enter a valid frequency (daily, weekly, monthly): "); + input = Console.ReadLine(); + } + + input = frequencyValue.ToString(); + break; + case "string": + while (input == null) + { + Console.WriteLine($"Please, make sure you enter a correct {logName}: "); + input = Console.ReadLine(); + } + break; + } + + return input.Trim(); + } + + private static int GetInputInt (string logName, string dataType = "def") + { + Console.WriteLine($"Enter the {logName}:"); + string input = Console.ReadLine(); + int inputInt = 0; + + switch (dataType) + { + case "def": + while (input == null || !int.TryParse(input, out inputInt)) + { + Console.WriteLine($"Please, make sure you enter the {logName}:"); + input = Console.ReadLine(); + } + break; + + } + + return inputInt; + } + + private static DateTime GetInputDate(string logName, string dataType = "startDate") + { + Console.WriteLine($"Enter the {logName}:"); + string input = Console.ReadLine(); + DateTime startDateValue = new DateTime(2000, 01, 01); + + + switch (dataType) + { + case "startDate": + while (input == null || !DateTime.TryParse(input, out startDateValue)) + { + Console.WriteLine($"Please, make sure you enter the {logName}:"); + input = Console.ReadLine(); + } + break; + + } + + return startDateValue; + } + + private static string GetUpdateCommand(string table, string var1, string var2) + { + return $@"UPDATE {table} SET {var1} = @{var1} WHERE {var2} = @{var2}"; + } + private static string GetAllFrom(string table, string var1 = null) + { + return $"SELECT * FROM {table}{(var1 != null ? $" WHERE {var1} = @{var1}" : "")}"; + } + public static void CreateHabit() + { + string name = Habit.GetInput("title of your new habit"); + string frequency = Habit.GetInput("frequency to track it by (weekly, daily, monthly)", "frequency"); + int timesInt = Habit.GetInputInt("times per period you would like to perform the habit"); + DateTime startDateValue = Habit.GetInputDate("desired start date of your new habit (format YYYY-MM-DD)", "startDate"); + + try + { + var connection = Data.DbConnection.GetConnection(); + connection.Open(); + var command = connection.CreateCommand(); + command.CommandText = + @$" + INSERT INTO habits (Name, Frequency, TimesPerPeriod, StartDate) + VALUES (@name, @frequency, @timesInt, @startDate) + "; + + command.Parameters.AddWithValue("@name", name); + command.Parameters.AddWithValue("@frequency", frequency); + command.Parameters.AddWithValue("@timesInt", timesInt); + command.Parameters.AddWithValue("@startDate", startDateValue); + + command.ExecuteNonQuery(); + + Console.WriteLine("The habit has been added."); + } + catch (Exception ex) + { + Console.WriteLine(ex.ToString()); + } + + } + public static void UpdateHabit() + { + ShowAll("Habits"); + int idInt = Habit.GetInputInt("ID of the habit you would like to edit"); + + try + { + var connection = Data.DbConnection.GetConnection(); + var command = connection.CreateCommand(); + command.CommandText = Habit.GetAllFrom("habits", "id"); + + command.Parameters.AddWithValue("@id", idInt); + + using (var reader = command.ExecuteReader()) + { + if (reader.Read()) + { + Console.WriteLine($"Row with Id {idInt} was found successfully."); + int fieldToEditInt = Habit.GetInputInt("number of the field to edit\t1 - Name\t2 - Frequency\t3 - Times Per Period\t4 - Start Date"); + var updateCommand = connection.CreateCommand(); + switch (fieldToEditInt) + { + case 1: + string name = Habit.GetInput("a new title of your habit"); + updateCommand.CommandText = Habit.GetUpdateCommand("habits", "name", "id"); + updateCommand.Parameters.AddWithValue("@name", name); + updateCommand.Parameters.AddWithValue("@id", idInt); + updateCommand.ExecuteNonQuery(); + break; + case 2: + string frequency = Habit.GetInput("a new frequency to track the habit (weekly, daily, monthly)", "frequency"); + updateCommand.CommandText = Habit.GetUpdateCommand("habits", "frequency", "id"); + updateCommand.Parameters.AddWithValue("@frequency", frequency); + updateCommand.Parameters.AddWithValue("@id", idInt); + updateCommand.ExecuteNonQuery(); + break; + case 3: + int timesInt = Habit.GetInputInt("a new times per period you would like to perform the habit"); + updateCommand.CommandText = Habit.GetUpdateCommand("habits", "TimesPerPeriod", "id"); + updateCommand.Parameters.AddWithValue("@TimesPerPeriod", timesInt); + updateCommand.Parameters.AddWithValue("@id", idInt); + updateCommand.ExecuteNonQuery(); + break; + case 4: + DateTime startDateValue = Habit.GetInputDate("a new start date of your new habit (format YYYY-MM-DD)", "startDate"); + updateCommand.CommandText = Habit.GetUpdateCommand("habits", "startDate", "id"); + updateCommand.Parameters.AddWithValue("@startDate", startDateValue); + updateCommand.Parameters.AddWithValue("@id", idInt); + updateCommand.ExecuteNonQuery(); + break; + default: + break; + } + + + } + else + { + Console.WriteLine($"No row found with Id {idInt}."); + } + } + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + } + } + public static void ShowAll(string db) + { + try + { + var connection = Data.DbConnection.GetConnection(); + connection.Open(); + var command = connection.CreateCommand(); + command.CommandText = Habit.GetAllFrom(db); + if (db == "Habits") Console.WriteLine("Id\tName\t\tFrequency\tTimes/Period\tStart Date"); + else Console.WriteLine("Id\tName\t\t\tDate"); + using (var reader = command.ExecuteReader()) + { + while (reader.Read()) + { + if (db == "Habits") + { + int id = reader.GetInt32(0); + string name = reader.GetString(1); + string frequency = reader.GetString(2); + int timesPerPeriod = reader.GetInt32(3); + string startDate = reader.GetString(4); + + Console.WriteLine($"{id}\t{name}\t\t{frequency}\t\t{timesPerPeriod}\t\t{startDate}"); + } else + { + int id = reader.GetInt32(0); + string name = reader.GetString(1); + string date = reader.GetString(2); + + Console.WriteLine($"{id}\t{name}\t\t{date}"); + } + + } + } + } + catch (Exception ex) + { + Console.WriteLine(ex.ToString()); + } + } + public static void RemoveHabit() + { + ShowAll("Habits"); + int idInt = Habit.GetInputInt("ID of the habit you would like to remove"); + + try + { + using (var connection = Data.DbConnection.GetConnection()) { + var command = connection.CreateCommand(); + command.CommandText = + @$" + DELETE FROM habits + WHERE id = @id + "; + + command.Parameters.AddWithValue("@id", idInt); + + int result = command.ExecuteNonQuery(); + + if (result > 0) + { + Console.WriteLine($"The habit with Id {idInt} was deleted successfully."); + } + else + { + Console.WriteLine($"No row found with Id {idInt}."); + } + } + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + } + } + + public static void AddHabitRecord() + { + int idInt = Habit.GetInputInt("ID of the habit you would like to add a record for:"); + + try + { + using (var connection = Data.DbConnection.GetConnection()) + { + string name = "null"; + connection.Open(); + using (var command = connection.CreateCommand()) + { + command.CommandText = + @$" + SELECT * FROM habits + WHERE id = @id + "; + + command.Parameters.AddWithValue("@id", idInt); + + var result = command.ExecuteScalar(); + if (result == null) + { + Console.WriteLine("The habit with such an id could not be found"); + return; + } + using (var reader = command.ExecuteReader()) + { + while (reader.Read()) + { + name = reader.GetString(1); + } + } + } + using (var insertCommand = connection.CreateCommand()) + { + insertCommand.CommandText = + @$" + INSERT INTO records (Name, HabitDate, HabitId) + VALUES (@name, @date, @idInt) + "; + + insertCommand.Parameters.AddWithValue("@name", name); + insertCommand.Parameters.AddWithValue("@date", DateTime.Now); + insertCommand.Parameters.AddWithValue("@idInt", idInt); + + insertCommand.ExecuteNonQuery(); + + Console.WriteLine("The record has been added."); + } + } + + + + + } + catch (Exception ex) + { + Console.Write(ex.Message); + } + } + } +} diff --git a/HabitTracker/HabitTracker.csproj b/HabitTracker/HabitTracker.csproj new file mode 100644 index 00000000..d9ca5eeb --- /dev/null +++ b/HabitTracker/HabitTracker.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + diff --git a/HabitTracker/Menu.cs b/HabitTracker/Menu.cs new file mode 100644 index 00000000..5c1b9107 --- /dev/null +++ b/HabitTracker/Menu.cs @@ -0,0 +1,82 @@ +namespace HabitTracker +{ + public class Menu + { + public static void ShowMenu() + { + int option = 1; + ConsoleKeyInfo key; + bool isSelected = false; + string color = "\u001b[32m"; + + while (!isSelected) + { + Console.WriteLine("\nPlease, choose an option from below. Use your keyboard's arrows to navigate: "); + Console.WriteLine($"{(option == 1 ? color : " ")}Current Habits\u001b[0m"); + Console.WriteLine($"{(option == 2 ? color : " ")}Create a Habit\u001b[0m"); + Console.WriteLine($"{(option == 3 ? color : " ")}Show all Habit Records\u001b[0m"); + Console.WriteLine($"{(option == 4 ? color : " ")}Create a Record in a Habit\u001b[0m"); + Console.WriteLine($"{(option == 5 ? color : " ")}Edit a Habit\u001b[0m"); + Console.WriteLine($"{(option == 6 ? color : " ")}Remove a Habit\u001b[0m"); + Console.WriteLine($"{(option == 7 ? color : " ")}Export Report\u001b[0m"); + Console.WriteLine($"{(option == 8 ? color : " ")}Credits\u001b[0m"); + + key = Console.ReadKey(); + Console.Clear(); + Console.WriteLine("\x1b[3J"); + switch (key.Key) + { + case ConsoleKey.DownArrow: + if (option == 8) break; + option++; + break; + case ConsoleKey.UpArrow: + if (option == 1) break; + option--; + break; + case ConsoleKey.Enter: + isSelected = true; + SelectMenuItem(option); + break; + } + } + + } + + public static void SelectMenuItem(int option) + { + Console.Clear(); + Console.WriteLine("\x1b[3J"); + switch (option) + { + case 1: + Habit.ShowAll("Habits"); + break; + case 2: + Habit.CreateHabit(); + break; + case 3: + Habit.ShowAll("Records"); + break; + case 4: + Habit.AddHabitRecord(); + break; + case 5: + Habit.UpdateHabit(); + break; + case 6: + Habit.RemoveHabit(); + break; + case 7: + Report.GetReport(); + break; + case 8: + Credits.GetCredits(); + break; + } + ShowMenu(); + } + } + + +} diff --git a/HabitTracker/Program.cs b/HabitTracker/Program.cs new file mode 100644 index 00000000..9b10b8fb --- /dev/null +++ b/HabitTracker/Program.cs @@ -0,0 +1,13 @@ +using HabitTracker.Data; + +namespace HabitTracker +{ + internal class Program + { + static void Main(string[] args) + { + DbConnection.GetConnection(true); + Menu.ShowMenu(); + } + } +} diff --git a/HabitTracker/Report.cs b/HabitTracker/Report.cs new file mode 100644 index 00000000..1cdf8c03 --- /dev/null +++ b/HabitTracker/Report.cs @@ -0,0 +1,149 @@ +namespace HabitTracker +{ + public class Report + { + enum Frequency + { + Daily, + Weekly, + Monthly + } + static public void GetReport() + { + var connection = Data.DbConnection.GetConnection(); + connection.Open(); + var command = connection.CreateCommand(); + command.CommandText = + @$" + SELECT Name, HabitId, COUNT(*) AS RecordCount + FROM Records + GROUP BY HabitId + ORDER BY RecordCount DESC + LIMIT 5; + "; + Console.WriteLine("The most reported habits:"); + using (var reader = command.ExecuteReader()) + { + while (reader.Read()) + { + string habitName = reader.GetString(0); + int recordCount = reader.GetInt32(2); + Console.WriteLine($"{habitName} - performed {recordCount} Times"); + } + } + + + command.CommandText = @" + SELECT Name, HabitId, COUNT(*) AS RecordCount + FROM Records + GROUP BY HabitId + ORDER BY RecordCount ASC + LIMIT 5; + "; + Console.WriteLine("\nThe least reported habits:"); + using (var reader = command.ExecuteReader()) + { + while (reader.Read()) + { + string habitName = reader.GetString(0); + int recordCount = reader.GetInt32(2); + Console.WriteLine($"{habitName} - performed {recordCount} Times"); + } + } + + + command.CommandText = + @$" + SELECT strftime('%H',HabitDate) AS ActiveHour, COUNT(*) AS EntryCount + FROM Records + GROUP BY ActiveHour + ORDER BY EntryCount DESC + LIMIT 1; + "; + object? result = command.ExecuteScalar(); + int hour = result != null ? Convert.ToInt32(result) : 0; + Console.WriteLine($"\nThe most active hour of the day is: {hour}:00"); + + + command.CommandText = +@$" + SELECT strftime('%w',HabitDate) AS ActiveDay, COUNT(*) AS EntryCount + FROM Records + GROUP BY ActiveDay + ORDER BY EntryCount DESC + LIMIT 1; + "; + + result = command.ExecuteScalar(); + DayOfWeek day = result != null ? (DayOfWeek)Convert.ToInt32(result) : 0; + Console.WriteLine($"\nThe most active day of the week is: {day}"); + + command.CommandText = + @$" + SELECT Name, HabitDate, HabitId, (JULIANDAY(HabitDate) - JULIANDAY(LAG (HabitDate) OVER (PARTITION BY HabitId ORDER BY HabitDate))) * 24 AS Break + FROM Records + ORDER BY Break DESC + LIMIT 1; + "; + + using (var reader = command.ExecuteReader()) + { + while (reader.Read()) + { + string habitName = reader.GetString(0); + int recordCount = reader.GetInt32(3); + int id = reader.GetInt32(2); + Console.WriteLine($"\nThe longest break was taken for the {habitName} habit with an id of {id} - which was not performed for {recordCount} Hours"); + } + } + + + Console.WriteLine("\nComletion rate statistics:"); + Dictionary habits = new Dictionary(); + command.CommandText = + @$" + SELECT Id, Frequency, TimesPerPeriod, StartDate, + (JULIANDAY('now') - JULIANDAY(StartDate)) * 24 * TimesPerPeriod / + CASE + WHEN Frequency = 'Daily' THEN 24 + WHEN Frequency = 'Weekly' THEN 168 + WHEN Frequency = 'Monthly' THEN 720 + END AS ExpectedNumberOfReps + FROM Habits; + "; + using (var reader = command.ExecuteReader()) + { + while (reader.Read()) + { + int id = reader.GetInt32(reader.GetOrdinal("Id")); + float expectedReps = reader.GetFloat(reader.GetOrdinal("ExpectedNumberOfReps")); + habits.Add(id, expectedReps); + } + } + command.CommandText = + $@" + SELECT Name, HabitId, COUNT(*) + FROM Records + GROUP BY HabitId; + "; + using (var reader = command.ExecuteReader()) + { + while (reader.Read()) + { + string name = reader.GetString(0); + int id = reader.GetInt32(1); + double completionRate = Math.Round((reader.GetInt32(2) / habits[id] * 100), 2); + Console.WriteLine($"The '{name}' habit with an id of {id} completion rate is {completionRate}%."); + } + } + + + + + + + } + + + } +} diff --git a/README.md b/README.md new file mode 100644 index 00000000..4e46346e --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +# HabitLogger - Habit Tracker Console Application + +Welcome to HabitLogger, a console-based habit tracking application written in C#. This application is part of The C# Academy projects. + +# Table of Contents + +- Features +- Usage +- Future Enhancements +- Resources Used + +# Features + +- Habit Tracking: Record daily habits and keep track of your activities. +- Date-wise Records: Track habits based on specific dates. +- Review Progress: View a summary of your tracked habits. +- Simple Console Interface: Use the arrows on your keyboard to navigate. + +# Usage + +- Check Current Habits: Check all current habits. +- Add Habit: Add a new habit to track. +- View Habit Records: View all recorded entries for a habit.] +- Add Habit Record: Add a new habit record to the list. +- Delete Habit: Remove a habit from your tracking list. +- Update Habit: Update a habit based on its ID. +- Generate a report: Generate a report of some useful and interesting information based on your habits.