diff --git a/FlashcardsApp/FlashcardsApp.sln b/FlashcardsApp/FlashcardsApp.sln
new file mode 100644
index 00000000..08b4e9af
--- /dev/null
+++ b/FlashcardsApp/FlashcardsApp.sln
@@ -0,0 +1,22 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.12.35707.178 d17.12
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FlashcardsApp", "FlashcardsApp\FlashcardsApp.csproj", "{E87AE218-DC73-4974-9E12-5FE3DE56D12B}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {E87AE218-DC73-4974-9E12-5FE3DE56D12B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E87AE218-DC73-4974-9E12-5FE3DE56D12B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E87AE218-DC73-4974-9E12-5FE3DE56D12B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E87AE218-DC73-4974-9E12-5FE3DE56D12B}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/FlashcardsApp/FlashcardsApp/DTOs/FlashcardDTO.cs b/FlashcardsApp/FlashcardsApp/DTOs/FlashcardDTO.cs
new file mode 100644
index 00000000..8c6a6888
--- /dev/null
+++ b/FlashcardsApp/FlashcardsApp/DTOs/FlashcardDTO.cs
@@ -0,0 +1,10 @@
+namespace FlashcardsApp.DTOs;
+
+internal class FlashcardDTO
+{
+ public int DisplayNumber { get; set; }
+ public int FlashcardId { get; set; }
+ public string Front { get; set; }
+ public string Back { get; set; }
+ public DateTime CreatedDate { get; set; }
+}
diff --git a/FlashcardsApp/FlashcardsApp/FlashcardsApp.csproj b/FlashcardsApp/FlashcardsApp/FlashcardsApp.csproj
new file mode 100644
index 00000000..47fd89d8
--- /dev/null
+++ b/FlashcardsApp/FlashcardsApp/FlashcardsApp.csproj
@@ -0,0 +1,24 @@
+
+
+
+ Exe
+ net9.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
diff --git a/FlashcardsApp/FlashcardsApp/Models/Flashcard.cs b/FlashcardsApp/FlashcardsApp/Models/Flashcard.cs
new file mode 100644
index 00000000..c1342ff7
--- /dev/null
+++ b/FlashcardsApp/FlashcardsApp/Models/Flashcard.cs
@@ -0,0 +1,10 @@
+namespace FlashcardsApp.Models;
+
+internal class Flashcard
+{
+ public int FlashcardId { get; set; }
+ public int StackId { get; set; }
+ public string Front { get; set; }
+ public string Back { get; set; }
+ public DateTime CreatedDate { get; set; }
+}
diff --git a/FlashcardsApp/FlashcardsApp/Models/Stack.cs b/FlashcardsApp/FlashcardsApp/Models/Stack.cs
new file mode 100644
index 00000000..d6dd572f
--- /dev/null
+++ b/FlashcardsApp/FlashcardsApp/Models/Stack.cs
@@ -0,0 +1,9 @@
+namespace FlashcardsApp.Models;
+
+internal class Stack
+{
+ public int StackId { get; set; }
+ public string Name { get; set; }
+ public string Description { get; set; }
+ public DateTime CreatedDate { get; set; }
+}
diff --git a/FlashcardsApp/FlashcardsApp/Models/StudySession.cs b/FlashcardsApp/FlashcardsApp/Models/StudySession.cs
new file mode 100644
index 00000000..75801bcd
--- /dev/null
+++ b/FlashcardsApp/FlashcardsApp/Models/StudySession.cs
@@ -0,0 +1,9 @@
+namespace FlashcardsApp.Models;
+
+internal class StudySession
+{
+ public int SessionId { get; set; }
+ public int StackId { get; set; }
+ public string Score { get; set; }
+ public DateTime StudyDate { get; set; }
+}
diff --git a/FlashcardsApp/FlashcardsApp/Program.cs b/FlashcardsApp/FlashcardsApp/Program.cs
new file mode 100644
index 00000000..8eb95405
--- /dev/null
+++ b/FlashcardsApp/FlashcardsApp/Program.cs
@@ -0,0 +1,45 @@
+using Microsoft.Extensions.Configuration;
+using FlashcardsApp.Services;
+using FlashcardsApp.UI.Core;
+
+namespace FlashcardsApp;
+
+class Program
+{
+ static IConfiguration? config; // a variable of type IConfiguration
+ static DatabaseService? databaseService;
+ static void Main(string[] args)
+ {
+ try
+ {
+ config = new ConfigurationBuilder() // Starting with an empty configuration but will return an IConfiguration object
+ .SetBasePath(Directory.GetCurrentDirectory()) // tells the builder where to look for configuration files (appsettings.json)
+ .AddJsonFile("appsettings.json") // what file it should be reading
+ .Build(); // creates the the IConfiguration object
+
+ string? connectionString = config.GetConnectionString("Default"); //GetConnectionString("Default") specifically looks for "ConnectionString" in appsettings.json
+ // and return the value associated with "Default"
+ if (connectionString == null)
+ {
+ throw new Exception("Connection string not found\n");
+
+ }
+
+ databaseService = new(connectionString);
+ databaseService.TestConnection();
+
+ Console.WriteLine("Successfully connected to server!\n");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error loading database: {ex.Message}\n");
+ Console.WriteLine("Press Any Key to Exit...");
+ Console.ReadKey();
+ return;
+ }
+
+ MenuHandler menuHandler = new(databaseService);
+
+ menuHandler.MainMenu();
+ }
+}
\ No newline at end of file
diff --git a/FlashcardsApp/FlashcardsApp/Services/DatabaseService.cs b/FlashcardsApp/FlashcardsApp/Services/DatabaseService.cs
new file mode 100644
index 00000000..92eb4159
--- /dev/null
+++ b/FlashcardsApp/FlashcardsApp/Services/DatabaseService.cs
@@ -0,0 +1,308 @@
+using Dapper;
+using FlashcardsApp.DTOs;
+using FlashcardsApp.Models;
+using FlashcardsApp.UI.Helpers;
+using Microsoft.Data.SqlClient; //for sql server connection
+
+namespace FlashcardsApp.Services;
+
+internal class DatabaseService
+{
+ private readonly string _connectionString;
+
+ internal DatabaseService(string connectionString)
+ {
+ _connectionString = connectionString;
+ }
+
+ public void TestConnection()
+ {
+ using (var connection = new SqlConnection(_connectionString))
+ {
+ connection.Open();
+ }
+ }
+
+ internal void DeleteFlashcard(int stackId, int flashcardId)
+ {
+ using (var connection = new SqlConnection(_connectionString))
+ {
+ connection.Open();
+
+ var sql = "DELETE FROM Flashcards WHERE StackId = @StackId AND FlashcardId = @FlashcardId";
+
+ var rowsAffected = connection.Execute(sql, new { StackId = stackId, FlashcardId = flashcardId });
+
+ if (rowsAffected > 0)
+ {
+ Console.WriteLine("\nDeletion successful.");
+ Console.WriteLine($"\n{rowsAffected} row(s) deleted.");
+ }
+ else
+ {
+ Console.WriteLine("\nDeletion has failed");
+ }
+ }
+ }
+
+ internal void DeleteStack(int stackId)
+ {
+ using (var connection = new SqlConnection(_connectionString))
+ {
+ connection.Open();
+
+ var sql = "DELETE FROM Stacks WHERE StackId = @StackId";
+
+ var rowsAffected = connection.Execute(sql, new { StackId = stackId });
+
+ if (rowsAffected > 0)
+ {
+ Console.WriteLine("\nDeletion successful.");
+ Console.WriteLine($"\n{rowsAffected} row(s) deleted.");
+ }
+ else
+ {
+ Console.WriteLine("\nDeletion has failed");
+ }
+ }
+ }
+
+
+ internal void GetAllStacks()
+ {
+ using (var connection = new SqlConnection(_connectionString))
+ {
+ connection.Open();
+
+ var sql = "SELECT * FROM Stacks";
+
+ var stacks = connection.Query(sql).ToList();
+
+ TableVisualization.ShowStacksTable(stacks);
+ }
+ }
+
+ internal List GetAllStacksAsList()
+ {
+ using (var connection = new SqlConnection(_connectionString))
+ {
+ connection.Open();
+
+ var sql = "SELECT * FROM Stacks";
+
+ var stacks = connection.Query(sql).ToList();
+
+ return stacks;
+ }
+ }
+
+ internal Flashcard? GetFlashcardByFlashcardId(int flashcardId, int stackId)
+ {
+ using (var connection = new SqlConnection(_connectionString))
+ {
+ connection.Open();
+
+ var sql = "SELECT FlashcardId, StackId, Front, Back, CreatedDate FROM Flashcards WHERE StackId = @StackId AND FlashcardId = @FlashcardId";
+
+ var flashcard = connection.QuerySingleOrDefault(sql, new { StackId = stackId, FlashcardId = flashcardId });
+
+ if (flashcard == null)
+ {
+ Console.WriteLine($"\nNo flashcard found with ID: {flashcardId} in stack {stackId}");
+ return null;
+ }
+
+ return flashcard;
+ }
+ }
+
+
+ internal List GetFlashcardsByID(int stackId)
+ {
+ using (var connection = new SqlConnection(_connectionString))
+ {
+ connection.Open();
+
+ var sql = @"
+ SELECT
+ ROW_NUMBER() OVER (ORDER BY FlashcardId) as DisplayNumber,
+ FlashcardId,
+ Front,
+ Back,
+ CreatedDate
+ FROM Flashcards
+ WHERE StackId = @StackId";
+
+ var flashcards = connection.Query(sql, new { StackId = stackId }).ToList();
+
+ TableVisualization.ShowFlashcardsTable(flashcards);
+
+ return flashcards;
+ }
+ }
+
+ internal List GetFlashcardsByStackID(int stackId)
+ {
+ using (var connection = new SqlConnection(_connectionString))
+ {
+ connection.Open();
+
+ var sql = @"
+ SELECT
+ FlashcardId,
+ StackId,
+ Front,
+ Back,
+ CreatedDate
+ FROM Flashcards
+ WHERE StackId = @StackId";
+
+ var flashcards = connection.Query(sql, new { StackId = stackId }).ToList();
+
+ return flashcards;
+ }
+ }
+
+ internal void GetStudyHistory()
+ {
+ using (var connection = new SqlConnection(_connectionString))
+ {
+ connection.Open();
+
+ var sql = "SELECT * FROM StudySessions";
+
+ var sessions = connection.Query(sql).ToList();
+
+ var sql2 = "SELECT * FROM Stacks";
+
+ var stacks = connection.Query(sql2).ToList();
+
+ if (sessions.Count > 0)
+ {
+ TableVisualization.ShowStudySessionsTable(sessions, stacks);
+ }
+ else
+ {
+ Console.WriteLine("\nNo sessions to view!");
+ }
+ }
+ }
+
+ internal void Post(Stack stack)
+ {
+ using (var connection = new SqlConnection(_connectionString))
+ {
+ connection.Open();
+
+ var sql = "INSERT INTO Stacks (Name, Description, CreatedDate) VALUES (@Name, @Description, @CreatedDate)";
+
+ var rowsAffected = connection.Execute(sql, new
+ {
+ Name = stack.Name,
+ Description = stack.Description,
+ CreatedDate = stack.CreatedDate,
+ });
+
+ if (rowsAffected > 0)
+ {
+ Console.WriteLine("\nSuccessfully added stack!");
+ Console.WriteLine($"\n{rowsAffected} row(s) inserted.");
+ }
+ else
+ {
+ Console.WriteLine("\nFailed to add stack!");
+ }
+ }
+ }
+
+ internal void PostFlashcard(Flashcard flashcard)
+ {
+ using (var connection = new SqlConnection(_connectionString))
+ {
+ connection.Open();
+
+ var checkSql = @"SELECT COUNT(1)
+ FROM Flashcards
+ WHERE StackId = @StackId
+ AND Front = @Front";
+
+ var exists = connection.ExecuteScalar(checkSql, new
+ {
+ StackId = flashcard.StackId,
+ Front = flashcard.Front
+ }) > 0;
+
+ if (exists)
+ {
+ Console.WriteLine("\nA flashcard with this front already exists in this stack!");
+ return;
+ }
+
+ var sql = "INSERT INTO Flashcards (StackId, Front, Back, CreatedDate) VALUES (@StackId, @Front, @Back, @CreatedDate)";
+
+ var rowsAffected = connection.Execute(sql, new
+ {
+ StackId = flashcard.StackId,
+ Front = flashcard.Front,
+ Back = flashcard.Back,
+ CreatedDate = flashcard.CreatedDate
+ });
+
+ if (rowsAffected > 0)
+ {
+ Console.WriteLine("\nSuccessfully added flashcard to stack!");
+ Console.WriteLine($"\n{rowsAffected} row(s) inserted.");
+ }
+ else
+ {
+ Console.WriteLine("\nFailed to add flashcard!");
+ }
+ }
+ }
+
+ internal void PostStudySession(StudySession session)
+ {
+ using (var connection = new SqlConnection(_connectionString))
+ {
+ connection.Open();
+
+ var sql = "INSERT INTO StudySessions (StackId, Score, StudyDate) VALUES (@StackId, @Score, @StudyDate)";
+
+ var rowsAffected = connection.Execute(sql, new
+ {
+ StackId = session.StackId,
+ Score = session.Score,
+ StudyDate = session.StudyDate,
+ });
+
+ if (rowsAffected > 0)
+ {
+ Console.WriteLine("\nStudy Session was successfully uploaded!");
+ Console.WriteLine($"\n{rowsAffected} row(s) inserted.");
+ }
+ else
+ {
+ Console.WriteLine("\nFailed to upload session!");
+ }
+ }
+ }
+
+ internal void UpdateFlashcard(int stackId, int flashcardId, Flashcard flashcard)
+ {
+ using (var connection = new SqlConnection(_connectionString))
+ {
+ connection.Open();
+
+ var sql = @"UPDATE Flashcards SET Front = @Front, Back = @Back, CreatedDate = @CreatedDate WHERE StackId = @StackId AND FlashcardId = @FlashcardId";
+
+ var rowsAffected = connection.Execute(sql, new
+ {
+ Front = flashcard.Front,
+ Back = flashcard.Back,
+ CreatedDate = flashcard.CreatedDate,
+ StackId = stackId,
+ FlashcardId = flashcardId
+ });
+ }
+ }
+}
diff --git a/FlashcardsApp/FlashcardsApp/UI/Core/InputValidator.cs b/FlashcardsApp/FlashcardsApp/UI/Core/InputValidator.cs
new file mode 100644
index 00000000..af507d57
--- /dev/null
+++ b/FlashcardsApp/FlashcardsApp/UI/Core/InputValidator.cs
@@ -0,0 +1,93 @@
+using Spectre.Console;
+
+namespace FlashcardsApp.UI.Core;
+
+public class InputValidator
+{
+ public string GetStackName()
+ {
+ while (true)
+ {
+ Console.Write("\nEnter a name for the stack of flashcards OR 0 to return: ");
+ string? name = Console.ReadLine();
+
+ if (name == "0")
+ return string.Empty;
+
+ if (string.IsNullOrEmpty(name))
+ {
+ Console.WriteLine("\nName cannot be empty.");
+ continue;
+ }
+
+ if (name.Length > 100)
+ {
+ Console.WriteLine("\nName cannot be longer than 100 characters.");
+ continue;
+ }
+
+ return name;
+ }
+ }
+
+ public string GetStackDescription()
+ {
+ Console.Write("\nIf you would like to add a description type it here or press enter: ");
+ string? description;
+
+ do
+ {
+ description = Console.ReadLine();
+
+ if (string.IsNullOrWhiteSpace(description))
+ {
+ return "No description provided";
+ }
+
+ if (description.Length > 500)
+ {
+ Console.WriteLine("\nDescription cannot be longer than 500 characters");
+ Console.Write("\nEnter description again: ");
+ }
+ } while (description.Length > 500);
+
+ return description;
+ }
+
+ public string GetFlashcardContent(string side)
+ {
+ while (true)
+ {
+ Console.Write($"\n\nEnter the content for the {side} of the card OR 0 to return: ");
+ string? content = Console.ReadLine();
+
+ if (content == "0")
+ return string.Empty;
+
+ if (string.IsNullOrEmpty(content))
+ {
+ Console.WriteLine($"\n{side} cannot be empty.");
+ continue;
+ }
+
+ if (content.Length > 500)
+ {
+ Console.WriteLine($"\n{side} cannot be longer than 500 characters.");
+ continue;
+ }
+
+ return content;
+ }
+ }
+
+ public bool GetConfirmation(string message)
+ {
+ var choice = AnsiConsole.Prompt(
+ new SelectionPrompt()
+ .Title(message)
+ .AddChoices(new[] { "Yes", "No" }));
+
+ return choice == "Yes";
+ }
+}
+
diff --git a/FlashcardsApp/FlashcardsApp/UI/Core/MenuHandler.cs b/FlashcardsApp/FlashcardsApp/UI/Core/MenuHandler.cs
new file mode 100644
index 00000000..c1ed0b9f
--- /dev/null
+++ b/FlashcardsApp/FlashcardsApp/UI/Core/MenuHandler.cs
@@ -0,0 +1,64 @@
+using FlashcardsApp.Services;
+using FlashcardsApp.UI.Features;
+using Spectre.Console;
+
+namespace FlashcardsApp.UI.Core;
+
+internal class MenuHandler
+{
+ private readonly DatabaseService _databaseService;
+ private readonly StackUI _stackUI;
+ private readonly FlashcardUI _flashcardUI;
+ private readonly StudyUI _studyUI;
+
+ internal MenuHandler(DatabaseService databaseService)
+ {
+ _databaseService = databaseService;
+ _stackUI = new StackUI(databaseService);
+ _flashcardUI = new FlashcardUI(databaseService);
+ _studyUI = new StudyUI(databaseService);
+ }
+
+ internal void MainMenu()
+ {
+ Console.Clear();
+ bool closeApp = false;
+ while (closeApp == false)
+ {
+ var choice = AnsiConsole.Prompt(
+ new SelectionPrompt()
+ .Title("MAIN MENU")
+ .AddChoices(new[] {
+ "View All Stacks",
+ "Create New Stack",
+ "Manage Stack",
+ "Study a Stack",
+ "View Study History",
+ "Close Application"
+ }));
+
+ switch (choice)
+ {
+ case "View All Stacks":
+ _databaseService.GetAllStacks();
+ break;
+ case "Create New Stack":
+ _stackUI.CreateStack();
+ break;
+ case "Manage Stack":
+ _stackUI.StacksListMenu();
+ break;
+ case "Study a Stack":
+ _studyUI.StudyMain();
+ break;
+ case "View Study History":
+ _databaseService.GetStudyHistory();
+ break;
+ case "Close Application":
+ closeApp = true;
+ Environment.Exit(0);
+ break;
+ }
+ }
+ }
+}
diff --git a/FlashcardsApp/FlashcardsApp/UI/Features/FlashcardUI.cs b/FlashcardsApp/FlashcardsApp/UI/Features/FlashcardUI.cs
new file mode 100644
index 00000000..f9b552bd
--- /dev/null
+++ b/FlashcardsApp/FlashcardsApp/UI/Features/FlashcardUI.cs
@@ -0,0 +1,151 @@
+using FlashcardsApp.DTOs;
+using FlashcardsApp.Models;
+using FlashcardsApp.Services;
+using FlashcardsApp.UI.Core;
+using Spectre.Console;
+
+namespace FlashcardsApp.UI.Features;
+
+internal class FlashcardUI
+{
+ private readonly DatabaseService _databaseService;
+ private readonly InputValidator _validator;
+
+ internal FlashcardUI(DatabaseService databaseService)
+ {
+ _databaseService = databaseService;
+ _validator = new InputValidator();
+ }
+
+ public void ViewCards(int stackId)
+ {
+ _databaseService.GetFlashcardsByID(stackId);
+ }
+
+ public void CreateFlashcard(int stackId)
+ {
+ Flashcard flashcard = new();
+
+ string front = GetFrontContent();
+ if (string.IsNullOrEmpty(front)) return;
+
+ string back = GetBackContent();
+ if (string.IsNullOrEmpty(back)) return;
+
+ flashcard.StackId = stackId;
+ flashcard.Front = front;
+ flashcard.Back = back;
+ flashcard.CreatedDate = DateTime.Now;
+
+ _databaseService.PostFlashcard(flashcard);
+ Console.WriteLine("\nFlashcard created successfully!");
+ }
+
+ public void UpdateFlashcard(int stackId)
+ {
+ List flashcards = _databaseService.GetFlashcardsByID(stackId);
+
+ if (!flashcards.Any())
+ {
+ Console.WriteLine("\nNo flashcards in this stack!");
+ return;
+ }
+
+ Dictionary cardMapping = flashcards.ToDictionary(
+ f => $"Front: {f.Front}\t\t| Back: {f.Back}",
+ f => f.FlashcardId);
+
+ var choices = cardMapping.Keys.ToList();
+ choices.Add("Return to Stack Menu");
+
+ var selectedCard = AnsiConsole.Prompt(
+ new SelectionPrompt()
+ .Title("Select a card to update.")
+ .AddChoices(choices));
+
+ if (selectedCard == "Return to Stack Menu")
+ {
+ return;
+ }
+
+ int flashcardId = cardMapping[selectedCard];
+
+ Flashcard? flashcard = _databaseService.GetFlashcardByFlashcardId(flashcardId, stackId);
+ if (flashcard == null)
+ {
+ return;
+ }
+
+ bool finished = false;
+ while (!finished)
+ {
+ var choice = AnsiConsole.Prompt(
+ new SelectionPrompt()
+ .Title("Select what to update, save, or exit.")
+ .AddChoices(new[]
+ {
+ "Front",
+ "Back",
+ "Save Changes",
+ "Return to Stack Menu"
+ }));
+
+ switch (choice)
+ {
+ case "Front":
+ string newFront = GetFrontContent();
+ if (!string.IsNullOrEmpty(newFront))
+ flashcard.Front = newFront;
+ break;
+ case "Back":
+ string newBack = GetBackContent();
+ if (!string.IsNullOrEmpty(newBack))
+ flashcard.Back = newBack;
+ break;
+ case "Save Changes":
+ flashcard.CreatedDate = DateTime.Now;
+ _databaseService.UpdateFlashcard(stackId, flashcardId, flashcard);
+ finished = true;
+ break;
+ case "Return to Stack Menu":
+ return;
+ }
+ }
+ }
+
+ public void DeleteFlashcard(int stackId)
+ {
+ List flashcards = _databaseService.GetFlashcardsByID(stackId);
+
+ Dictionary cardMapping = flashcards.ToDictionary(
+ f => $"Front: {f.Front}\t\t| Back: {f.Back}",
+ f => f.FlashcardId);
+
+ var choices = cardMapping.Keys.ToList();
+ choices.Add("Return to Stack Menu");
+
+ var selectedCard = AnsiConsole.Prompt(
+ new SelectionPrompt()
+ .Title("Select a card to delete.")
+ .AddChoices(choices));
+
+ if (selectedCard == "Return to Stack Menu")
+ {
+ return;
+ }
+
+ int flashcardId = cardMapping[selectedCard];
+ _databaseService.DeleteFlashcard(stackId, flashcardId);
+ }
+
+ private string GetFrontContent()
+ {
+ return _validator.GetFlashcardContent("front");
+ }
+
+ private string GetBackContent()
+ {
+ return _validator.GetFlashcardContent("back");
+ }
+
+}
diff --git a/FlashcardsApp/FlashcardsApp/UI/Features/StackUI.cs b/FlashcardsApp/FlashcardsApp/UI/Features/StackUI.cs
new file mode 100644
index 00000000..0f5d5f7a
--- /dev/null
+++ b/FlashcardsApp/FlashcardsApp/UI/Features/StackUI.cs
@@ -0,0 +1,179 @@
+using FlashcardsApp.Models;
+using FlashcardsApp.Services;
+using FlashcardsApp.UI.Core;
+using Spectre.Console;
+
+namespace FlashcardsApp.UI.Features;
+
+internal class StackUI
+{
+ private readonly DatabaseService _databaseService;
+ private readonly FlashcardUI _flashcardUI;
+ private readonly InputValidator _validator;
+
+ internal StackUI(DatabaseService databaseService)
+ {
+ _databaseService = databaseService;
+ _flashcardUI = new FlashcardUI(databaseService);
+ _validator = new InputValidator();
+ }
+
+ public void StacksListMenu()
+ {
+ while (true)
+ {
+ Console.Clear();
+ _databaseService.GetAllStacks();
+
+ List stacks = _databaseService.GetAllStacksAsList();
+ if (!stacks.Any())
+ {
+ Console.WriteLine("\nNo stacks found. Create a stack first!");
+ Console.WriteLine("\nPress Enter to return to main menu...");
+ Console.ReadLine();
+ return;
+ }
+
+ List stackNames = stacks.Select(s => s.Name).ToList();
+ stackNames.Add("Return to Main Menu");
+
+ var selectedStack = AnsiConsole.Prompt(
+ new SelectionPrompt()
+ .Title("Select a stack to manage.")
+ .AddChoices(stackNames));
+
+ if (selectedStack == "Return to Main Menu")
+ {
+ return;
+ }
+
+ ManageStack(selectedStack);
+ }
+ }
+
+ private void ManageStack(string selectedStack)
+ {
+ while (true)
+ {
+ Console.Clear();
+
+ var action = AnsiConsole.Prompt(
+ new SelectionPrompt()
+ .Title($"Managing stack: {selectedStack}")
+ .AddChoices(new[]
+ {
+ "View Cards",
+ "Add Card",
+ "Update Card",
+ "Delete Card",
+ "Delete this Stack",
+ "Return to Stack Menu",
+ "Return to Main Menu"
+ }));
+
+ int stackId = GetStackId(selectedStack);
+ if (stackId == -1)
+ {
+ Console.WriteLine("\nError accessing stack. Returning to stack menu...");
+ Console.WriteLine("\nPress Enter to continue...");
+ Console.ReadLine();
+ return;
+ }
+
+ switch (action)
+ {
+ case "View Cards":
+ _flashcardUI.ViewCards(stackId);
+ break;
+ case "Add Card":
+ _flashcardUI.CreateFlashcard(stackId);
+ break;
+ case "Update Card":
+ _flashcardUI.UpdateFlashcard(stackId);
+ break;
+ case "Delete Card":
+ _flashcardUI.DeleteFlashcard(stackId);
+ break;
+ case "Delete this Stack":
+ if (DeleteStack(stackId, selectedStack))
+ return;
+ break;
+ case "Return to Stack Menu":
+ return;
+ case "Return to Main Menu":
+ return;
+ }
+
+ Console.WriteLine("\nPress Enter to continue...");
+ Console.ReadLine();
+ }
+ }
+
+ public void CreateStack()
+ {
+ string name = _validator.GetStackName();
+ if (string.IsNullOrEmpty(name))
+ return;
+
+ string description = _validator.GetStackDescription();
+
+ Stack stack = new()
+ {
+ Name = name,
+ Description = description,
+ CreatedDate = DateTime.Now
+ };
+
+ try
+ {
+ _databaseService.Post(stack);
+ Console.WriteLine("\nStack created successfully!");
+ Console.WriteLine("\nPress Enter to continue...");
+ Console.ReadLine();
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"\nError creating stack: {ex.Message}");
+ Console.WriteLine("\nPress Enter to continue...");
+ Console.ReadLine();
+ }
+ }
+
+ private bool DeleteStack(int stackId, string selectedStack)
+ {
+ if (_validator.GetConfirmation($"\nAre you sure you want to delete '{selectedStack}' stack?"))
+ {
+ try
+ {
+ _databaseService.DeleteStack(stackId);
+ Console.WriteLine("\nStack deleted successfully!");
+ Console.WriteLine("\nPress Enter to continue...");
+ Console.ReadLine();
+ return true;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"\nError deleting stack: {ex.Message}");
+ Console.WriteLine("\nPress Enter to continue...");
+ Console.ReadLine();
+ return false;
+ }
+ }
+ return false;
+ }
+
+ private int GetStackId(string selectedStack)
+ {
+ try
+ {
+ List stacks = _databaseService.GetAllStacksAsList();
+ Stack stack = stacks.First(s => s.Name == selectedStack);
+ return stack.StackId;
+ }
+ catch (Exception)
+ {
+ Console.WriteLine("\nError finding stack.");
+ return -1;
+ }
+ }
+}
diff --git a/FlashcardsApp/FlashcardsApp/UI/Features/StudyUI.cs b/FlashcardsApp/FlashcardsApp/UI/Features/StudyUI.cs
new file mode 100644
index 00000000..edf7f071
--- /dev/null
+++ b/FlashcardsApp/FlashcardsApp/UI/Features/StudyUI.cs
@@ -0,0 +1,149 @@
+using FlashcardsApp.Models;
+using FlashcardsApp.Services;
+using FlashcardsApp.UI.Core;
+using Spectre.Console;
+
+namespace FlashcardsApp.UI.Features;
+
+internal class StudyUI
+{
+ private readonly DatabaseService _databaseService;
+ private readonly InputValidator _inputValidator;
+
+ internal StudyUI(DatabaseService databaseService)
+ {
+ _databaseService = databaseService;
+ _inputValidator = new InputValidator();
+ }
+
+ internal void StudyMain()
+ {
+ bool continueStudy = true;
+ while (continueStudy)
+ {
+ if (!GetStackToStudy(out int stackId))
+ {
+ return;
+ }
+
+ if (RunStudySession(stackId, out string finalScore))
+ {
+ AddStudySession(stackId, finalScore);
+ }
+
+ continueStudy = _inputValidator.GetConfirmation("Would you like to study a stack again?");
+ }
+ }
+
+ private bool GetStackToStudy(out int stackId)
+ {
+ stackId = -1;
+ Console.Clear();
+ _databaseService.GetAllStacks();
+
+ List stacks = _databaseService.GetAllStacksAsList();
+ if (!stacks.Any())
+ {
+ Console.WriteLine("\nNo stacks found. Create a stack first!");
+ Console.WriteLine("\nPress Enter to return to main menu...");
+ Console.ReadLine();
+ return false;
+ }
+
+ Dictionary stackMapping = stacks.ToDictionary(
+ s => $"Name: {s.Name} | {s.Description}",
+ s => s.StackId);
+
+ var choices = stackMapping.Keys.ToList();
+ choices.Add("Return to Main Menu");
+
+ var selectedStack = AnsiConsole.Prompt(
+ new SelectionPrompt()
+ .Title("Select a stack to study.")
+ .AddChoices(choices));
+
+ if (selectedStack == "Return to Main Menu")
+ return false;
+
+ stackId = stackMapping[selectedStack];
+ return true;
+ }
+
+ private bool RunStudySession(int stackId, out string finalScore)
+ {
+ finalScore = "";
+ List flashcards = _databaseService.GetFlashcardsByStackID(stackId);
+ if (!flashcards.Any())
+ {
+ Console.WriteLine("\nNo flashcards in this stack!");
+ Console.WriteLine("\nPress Enter to continue...");
+ Console.ReadLine();
+ return false;
+ }
+
+ var studyData = PrepareStudyData(flashcards);
+ int score = StudyFlashcards(studyData);
+
+ finalScore = $"{score}/{studyData.Count}";
+ Console.WriteLine($"\nYou got a score of {finalScore}");
+ return true;
+ }
+
+ private Dictionary PrepareStudyData(List flashcards)
+ {
+ return flashcards.ToDictionary(
+ f => $"Front: {f.Front}",
+ f => $"{f.Back}");
+ }
+
+ private int StudyFlashcards(Dictionary cardMapping)
+ {
+ int score = 0;
+ var flashcardFront = cardMapping.Keys.ToList();
+
+ Console.WriteLine($"\nPress Enter to Start");
+ Console.ReadLine();
+
+ foreach (var front in flashcardFront)
+ {
+ score += ProcessFlashcard(front, cardMapping[front]);
+ }
+
+ return score;
+ }
+
+ private int ProcessFlashcard(string front, string correctAnswer)
+ {
+ Console.Clear();
+ Console.WriteLine(front);
+ Console.Write("\nAnswer: ");
+ string? answer = Console.ReadLine()?.Trim().ToLower();
+
+ bool isCorrect = answer == correctAnswer.ToLower();
+ ShowAnswerFeedback(answer, correctAnswer, isCorrect);
+
+ return isCorrect ? 1 : 0;
+ }
+
+ private void ShowAnswerFeedback(string userAnswer, string correctAnswer, bool isCorrect)
+ {
+ Console.WriteLine($"\nAnswer = {correctAnswer.ToLower()}\n" +
+ $"Your Answer: {userAnswer}\n");
+
+ AnsiConsole.MarkupLine(isCorrect ? "[green]Correct![/]" : "[red]Incorrect![/]");
+
+ Console.WriteLine("\nPress enter to Continue");
+ Console.ReadLine();
+ }
+
+ private void AddStudySession(int stackId, string finalScore)
+ {
+ StudySession session = new StudySession();
+
+ session.StackId = stackId;
+ session.Score = finalScore;
+ session.StudyDate = DateTime.Now;
+
+ _databaseService.PostStudySession(session);
+ }
+}
diff --git a/FlashcardsApp/FlashcardsApp/UI/Helpers/TableVisualization.cs b/FlashcardsApp/FlashcardsApp/UI/Helpers/TableVisualization.cs
new file mode 100644
index 00000000..48f7af57
--- /dev/null
+++ b/FlashcardsApp/FlashcardsApp/UI/Helpers/TableVisualization.cs
@@ -0,0 +1,99 @@
+using FlashcardsApp.DTOs;
+using FlashcardsApp.Models;
+using Spectre.Console;
+
+namespace FlashcardsApp.UI.Helpers;
+
+internal class TableVisualization
+{
+ internal static void ShowStacksTable(List stacks)
+ {
+ Console.WriteLine("\n");
+
+ var table = new Table();
+
+ table.AddColumn("ID");
+ table.AddColumn("Name");
+ table.AddColumn("Description");
+ table.AddColumn("Date Created");
+
+ foreach (var stack in stacks)
+ {
+ table.AddRow(
+ stack.StackId.ToString(),
+ stack.Name,
+ stack.Description,
+ stack.CreatedDate.ToString()
+ );
+ }
+
+ table.Border(TableBorder.Square);
+ table.BorderColor(Color.Blue);
+
+ AnsiConsole.Write(table);
+ Console.WriteLine("\n");
+ }
+
+ internal static void ShowFlashcardsTable(List flashcards)
+ {
+ Console.WriteLine("\n");
+
+ var table = new Table();
+
+ table.AddColumn("ID");
+ table.AddColumn("Front");
+ table.AddColumn("Back");
+ table.AddColumn("Date Created");
+
+ foreach (var card in flashcards)
+ {
+ var idPoperty = typeof(FlashcardDTO).GetProperty("DisplayNumber");
+ var frontProperty = typeof(FlashcardDTO).GetProperty("Front");
+ var backProperty = typeof(FlashcardDTO).GetProperty("Back");
+ var createdDateProperty = typeof(FlashcardDTO).GetProperty("CreatedDate");
+
+ table.AddRow(
+ idPoperty?.GetValue(card)?.ToString() ?? "NULL",
+ frontProperty?.GetValue(card)?.ToString() ?? "NULL",
+ backProperty?.GetValue(card)?.ToString() ?? "NULL",
+ createdDateProperty?.GetValue(card)?.ToString() ?? "NULL"
+ );
+ }
+
+ table.Border(TableBorder.Square);
+ table.BorderColor(Color.Green);
+
+ AnsiConsole.Write(table);
+ Console.WriteLine("\n");
+ }
+
+ internal static void ShowStudySessionsTable(List sessions, List stacks)
+ {
+ Console.Write("\n");
+
+ Dictionary stackDictionary = stacks.ToDictionary(s => s.StackId, s => s.Name);
+
+ var table = new Table();
+
+ table.AddColumn("Stack Studied");
+ table.AddColumn("Score");
+ table.AddColumn("Study Date");
+
+ foreach (var session in sessions)
+ {
+ string stackName = stackDictionary.GetValueOrDefault(session.StackId, "NULL");
+
+ table.AddRow(
+ stackName,
+ session.Score?.ToString() ?? "NULL",
+ session.StudyDate.ToString()
+ );
+ }
+
+ table.Border(TableBorder.Square);
+ table.BorderColor(Color.Orange1);
+
+ AnsiConsole.Write(table);
+ Console.WriteLine("\n");
+ }
+}
diff --git a/FlashcardsApp/FlashcardsApp/appsettings.json b/FlashcardsApp/FlashcardsApp/appsettings.json
new file mode 100644
index 00000000..11f20947
--- /dev/null
+++ b/FlashcardsApp/FlashcardsApp/appsettings.json
@@ -0,0 +1,5 @@
+{
+ "ConnectionStrings": {
+ "Default": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=FlashcardsDB;Integrated Security=True"
+ }
+}