diff --git a/Flashcards.davetn657/Controllers/CardController.cs b/Flashcards.davetn657/Controllers/CardController.cs new file mode 100644 index 00000000..73e45835 --- /dev/null +++ b/Flashcards.davetn657/Controllers/CardController.cs @@ -0,0 +1,197 @@ +using Flashcards.davetn657.Models; +using Flashcards.davetn657.Models.DTOs; +using Flashcards.davetn657.Models.Enums; +using Microsoft.Data.SqlClient; +using Microsoft.Extensions.Configuration; +using System.Data; + +namespace Flashcards.davetn657.Controllers; + +public class CardController +{ + private IConfiguration configuration; + private string? connectionString; + + public CardController() + { + this.configuration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json") + .Build(); + this.connectionString = configuration.GetConnectionString("DatabaseConnection"); + } + + internal void AddCard(CardDto card, StackDto stack) + { + using (SqlConnection connection = new SqlConnection(connectionString)) + { + connection.Open(); + + var tableCmd = connection.CreateCommand(); + tableCmd.CommandText = @"INSERT INTO CARDS (StackId, CardQuestion, CardAnswer) + VALUES (@StackId, @Question, @Answer)"; + + tableCmd.Parameters.Add("@StackId", SqlDbType.Int).Value = stack.Id; + tableCmd.Parameters.Add("@Question", SqlDbType.Text).Value = card.Question; + tableCmd.Parameters.Add("@Answer", SqlDbType.Text).Value = card.Answer; + + tableCmd.ExecuteNonQuery(); + + connection.Close(); + } + } + + internal void RemoveCard(CardDto card) + { + using (var connection = new SqlConnection(connectionString)) + { + connection.Open(); + + var tableCmd = connection.CreateCommand(); + tableCmd.CommandText = @"DELETE FROM CARDS + WHERE CardId = @Id"; + + tableCmd.Parameters.Add("@Id", SqlDbType.Int).Value = card.Id; + + tableCmd.ExecuteNonQuery(); + + connection.Close(); + } + + } + + internal void RemoveCard(StackDto stack) + { + using (var connection = new SqlConnection(connectionString)) + { + connection.Open(); + + var tableCmd = connection.CreateCommand(); + tableCmd.CommandText = @"DELETE FROM CARDS + WHERE StackId = @Id"; + + tableCmd.Parameters.Add("@Id", SqlDbType.Int).Value = stack.Id; + + tableCmd.ExecuteNonQuery(); + + connection.Close(); + } + + } + + internal void EditCard(CardDto card, Enum option) + { + using(var connection = new SqlConnection(connectionString)) + { + connection.Open(); + + var tableCmd = connection.CreateCommand(); + + if (option.Equals(EditCardOptions.ChangeAnswer)) + { + tableCmd.CommandText = @"UPDATE CARDS + SET CardAnswer = @Answer + WHERE CardId = @id"; + tableCmd.Parameters.Add("@Answer", SqlDbType.Text).Value = card.Answer; + } + else if(option.Equals(EditCardOptions.ChangeQuestion)) + { + tableCmd.CommandText = @"UPDATE CARDS + SET CardQuestion = @Question + WHERE CardId = @id"; + tableCmd.Parameters.Add("@Question", SqlDbType.Text).Value = card.Question; + } + + tableCmd.Parameters.Add("@Id", SqlDbType.Int).Value = card.Id; + tableCmd.ExecuteNonQuery(); + + connection.Close(); + } + } + + internal void ChangeTime(CardDto card, DateTime timeChange) + { + using (var connection = new SqlConnection(connectionString)) + { + connection.Open(); + + var tableCmd = connection.CreateCommand(); + tableCmd.CommandText = @"UPDATE CARDS + SET LastAppearance = @today, + NextAppearance = @time + WHERE CardId = @id"; + + tableCmd.Parameters.Add("@today", SqlDbType.DateTime).Value = DateTime.Now; + tableCmd.Parameters.Add("@time", SqlDbType.DateTime).Value = timeChange; + tableCmd.Parameters.Add("@id", SqlDbType.Int).Value = card.Id; + + tableCmd.ExecuteNonQuery(); + + connection.Close(); + } + } + + internal Dictionary ReadAllCards() + { + var allCards = new Dictionary(); + + using (var connection = new SqlConnection(connectionString)) + { + connection.Open(); + + var tabldCmd = connection.CreateCommand(); + tabldCmd.CommandText = @"SELECT * FROM CARDS"; + + var reader = tabldCmd.ExecuteReader(); + + while (reader.Read()) + { + var card = new CardDto(); + card.Id = reader.GetInt32("CardId"); + card.Question = reader.GetString("CardQuestion"); + card.Answer = reader.GetString("CardAnswer"); + card.LastAppearance = reader.GetDateTime("LastAppearance"); + card.NextAppearance = reader.GetDateTime("NextAppearance"); + allCards.Add(card.Question, card); + } + + connection.Close(); + } + + return allCards; + } + + internal Dictionary ReadAllCards(StudyDto session) + { + var allCards = new Dictionary(); + + using(var connection = new SqlConnection(connectionString)) + { + connection.Open(); + + var tableCmd = connection.CreateCommand(); + tableCmd.CommandText = @"SELECT * FROM CARDS + WHERE StackId = @id + ORDER BY NextAppearance DESC"; + + tableCmd.Parameters.Add("@Id", SqlDbType.Int).Value = session.StackId; + + var reader = tableCmd.ExecuteReader(); + + while (reader.Read()) + { + var card = new CardDto(); + card.Id = reader.GetInt32("CardId"); + card.Question = reader.GetString("CardQuestion"); + card.Answer = reader.GetString("CardAnswer"); + card.LastAppearance = reader.GetDateTime("LastAppearance"); + card.NextAppearance = reader.GetDateTime("NextAppearance"); + allCards.Add(card.Question, card); + } + + connection.Close(); + } + + return allCards; + } +} diff --git a/Flashcards.davetn657/Controllers/ScoreController.cs b/Flashcards.davetn657/Controllers/ScoreController.cs new file mode 100644 index 00000000..b836791e --- /dev/null +++ b/Flashcards.davetn657/Controllers/ScoreController.cs @@ -0,0 +1,77 @@ +using Flashcards.davetn657.Models.DTOs; +using Microsoft.Data.SqlClient; +using Microsoft.Extensions.Configuration; +using System.Data; + +namespace Flashcards.davetn657.Controllers; + +public class ScoreController +{ + private IConfiguration configuration; + private string? connectionString; + + public ScoreController() + { + this.configuration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json") + .Build(); + this.connectionString = configuration.GetConnectionString("DatabaseConnection"); + } + + internal void AddScore(StudyDto session, ScoreDto score) + { + using (var connection = new SqlConnection(connectionString)) + { + connection.Open(); + + var tableCmd = connection.CreateCommand(); + tableCmd.CommandText = @"INSERT INTO SCORES (SessionId, Score) + VALUES (@id, @score)"; + + tableCmd.Parameters.Add("@id", SqlDbType.Int).Value = session.Id; + tableCmd.Parameters.Add("@score", SqlDbType.Int).Value = score.Score; + + tableCmd.ExecuteNonQuery(); + + connection.Close(); + } + } + + internal List GetScores(int numberOfDays) + { + var pastScores = new List(); + + using (var connection = new SqlConnection(connectionString)) + { + connection.Open(); + + var tableCmd = connection.CreateCommand(); + tableCmd.CommandText = @"SELECT Sessions.StackId, Sessions.SessionName, Scores.Score, Scores.CreateDate + FROM Sessions + LEFT JOIN Scores ON Sessions.SessionId = Scores.SessionId + WHERE CAST(Scores.CreateDate AS DATE) >= DATEADD(day, @numDays, GETDATE()) + GROUP BY Sessions.StackId, Sessions.SessionName, Scores.Score, Scores.CreateDate + ORDER BY Scores.CreateDate DESC"; + + tableCmd.Parameters.Add("@numDays", SqlDbType.Int).Value = -numberOfDays; + + var reader = tableCmd.ExecuteReader(); + + while (reader.Read()) + { + var data = new ScoreDto(); + data.SessionId = reader.GetInt32("StackId"); + data.Name = reader.GetString("SessionName"); + data.Score = reader.GetInt32("Score"); + data.CreateDate = reader.GetDateTime("CreateDate"); + + pastScores.Add(data); + } + + connection.Close(); + } + + return pastScores; + } +} diff --git a/Flashcards.davetn657/Controllers/StackController.cs b/Flashcards.davetn657/Controllers/StackController.cs new file mode 100644 index 00000000..de3e53e4 --- /dev/null +++ b/Flashcards.davetn657/Controllers/StackController.cs @@ -0,0 +1,156 @@ +using Flashcards.davetn657.Models.DTOs; +using Microsoft.Data.SqlClient; +using Microsoft.Extensions.Configuration; +using System.Data; + +namespace Flashcards.davetn657.Controllers; +public class StackController +{ + private IConfiguration configuration; + private string? connectionString; + + public StackController() + { + this.configuration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json") + .Build(); + this.connectionString = configuration.GetConnectionString("DatabaseConnection"); + } + + internal void AddStack(string name) + { + using(var connection = new SqlConnection(connectionString)) + { + connection.Open(); + + var tableCmd = connection.CreateCommand(); + + tableCmd.CommandText = @"INSERT INTO STACKS (StackName) + VALUES (@Name)"; + + tableCmd.Parameters.Add("@Name", SqlDbType.Text).Value = name; + + tableCmd.ExecuteNonQuery(); + + connection.Close(); + } + } + + internal void RemoveStack(StackDto stack) + { + using(var connection = new SqlConnection(connectionString)) + { + connection.Open(); + + var tableCmd = connection.CreateCommand(); + tableCmd.CommandText = @"DELETE FROM STACKS + WHERE + StackId = @Id"; + + tableCmd.Parameters.Add("@Id", SqlDbType.Int).Value = stack.Id; + + tableCmd.ExecuteNonQuery(); + + connection.Close(); + } + } + + internal void RemoveStack(StackDto stack, CardController cardController, StudyController sessionController) + { + using (var connection = new SqlConnection(connectionString)) + { + connection.Open(); + + var tableCmd = connection.CreateCommand(); + tableCmd.CommandText = @"DELETE FROM STACKS + WHERE + StackId = @Id AND + StackName = @Name"; + + tableCmd.Parameters.Add("@Id", SqlDbType.Int).Value = stack.Id; + tableCmd.Parameters.Add("@Name", SqlDbType.Text).Value = stack.Name; + + cardController.RemoveCard(stack); + sessionController.RemoveSession(stack); + tableCmd.ExecuteNonQuery(); + + connection.Close(); + } + } + + internal void EditStack(StackDto stack) + { + using (var connection = new SqlConnection(connectionString)) + { + connection.Open(); + + var tableCmd = connection.CreateCommand(); + tableCmd.CommandText = @"UPDATE STACKS + SET StackName = @NewName + WHERE StackId = @Id"; + + tableCmd.Parameters.Add("@NewName", SqlDbType.Text).Value = stack.Name; + tableCmd.Parameters.Add("@Id", SqlDbType.Int).Value = stack.Id; + + tableCmd.ExecuteNonQuery(); + + connection.Close(); + } + } + + internal Dictionary ReadAllStacks() + { + var allStacks = new Dictionary(); + + using (var connection = new SqlConnection(connectionString)) + { + connection.Open(); + + var tableCmd = connection.CreateCommand(); + tableCmd.CommandText = @"SELECT * FROM STACKS"; + + var reader = tableCmd.ExecuteReader(); + + while (reader.Read()) + { + var stack = new StackDto(); + stack.Id = reader.GetInt32("StackId"); + stack.Name = reader.GetString("StackName"); + + allStacks.Add(stack.Name, stack); + } + + connection.Close(); + } + + return allStacks; + } + internal StackDto ReadAllStacks(StudyDto session) + { + var stack = new StackDto(); + + using (var connection = new SqlConnection(connectionString)) + { + connection.Open(); + + var tableCmd = connection.CreateCommand(); + tableCmd.CommandText = @"SELECT * FROM STACKS + WHERE StackId = @id"; + + tableCmd.Parameters.Add("@id", SqlDbType.Int).Value = session.StackId; + + var reader = tableCmd.ExecuteReader(); + + while (reader.Read()) + { + stack.Id = reader.GetInt32("StackId"); + stack.Name = reader.GetString("StackName"); + } + + connection.Close(); + } + + return stack; + } +} diff --git a/Flashcards.davetn657/Controllers/StudyController.cs b/Flashcards.davetn657/Controllers/StudyController.cs new file mode 100644 index 00000000..a3881720 --- /dev/null +++ b/Flashcards.davetn657/Controllers/StudyController.cs @@ -0,0 +1,106 @@ +using Flashcards.davetn657.Models.DTOs; +using Microsoft.Data.SqlClient; +using Microsoft.Extensions.Configuration; +using System.Data; + +namespace Flashcards.davetn657.Controllers; + +public class StudyController +{ + private IConfiguration configuration; + private string? connectionString; + + public StudyController() + { + this.configuration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json") + .Build(); + this.connectionString = configuration.GetConnectionString("DatabaseConnection"); + } + + internal void AddSession(StudyDto session) + { + using(var connection = new SqlConnection(connectionString)) + { + connection.Open(); + + var tableCmd = connection.CreateCommand(); + tableCmd.CommandText = @"INSERT INTO SESSIONS (StackId, SessionName) + VALUES (@id, @Name)"; + + tableCmd.Parameters.Add("@id", SqlDbType.Int).Value = session.StackId; + tableCmd.Parameters.Add("@Name", SqlDbType.Text).Value = session.Name; + tableCmd.ExecuteNonQuery(); + + connection.Close(); + } + } + + internal void RemoveSession(StackDto stack) + { + using (var connection = new SqlConnection(connectionString)) + { + connection.Open(); + var tableCmd = connection.CreateCommand(); + + tableCmd.CommandText = @"DELETE FROM SESSIONS + WHERE SessionId = @Id"; + + tableCmd.Parameters.Add("@Id", SqlDbType.Text).Value = stack.Id; + + tableCmd.ExecuteNonQuery(); + + connection.Close(); + } + } + + + internal void EditSession(StudyDto session) + { + using (var connection = new SqlConnection(connectionString)) + { + connection.Open(); + + var tableCmd = connection.CreateCommand(); + tableCmd.CommandText = @"UPDATE SESSIONS + SET StudyName = @Name + WHERE StudyId = @Id"; + + tableCmd.Parameters.Add("@Name", SqlDbType.Text).Value = session.Name; + tableCmd.Parameters.Add("@Id", SqlDbType.Int).Value = session.Id; + tableCmd.ExecuteNonQuery(); + + connection.Close(); + } + } + + internal Dictionary ReadAllSessions() + { + var allSessions = new Dictionary(); + + using(var connection = new SqlConnection(connectionString)) + { + connection.Open(); + + var tableCmd = connection.CreateCommand(); + tableCmd.CommandText = @"SELECT * FROM SESSIONS"; + + var reader = tableCmd.ExecuteReader(); + + while (reader.Read()) + { + var session = new StudyDto(); + session.Id = reader.GetInt32("SessionId"); + session.StackId = reader.GetInt32("StackId"); + session.Name = reader.GetString("SessionName"); + + allSessions.Add(session.Name, session); + } + + connection.Close(); + } + + return allSessions; + } +} \ No newline at end of file diff --git a/Flashcards.davetn657/Flashcards.davetn657.csproj b/Flashcards.davetn657/Flashcards.davetn657.csproj new file mode 100644 index 00000000..25dea488 --- /dev/null +++ b/Flashcards.davetn657/Flashcards.davetn657.csproj @@ -0,0 +1,17 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + diff --git a/Flashcards.davetn657/Flashcards.davetn657.sln b/Flashcards.davetn657/Flashcards.davetn657.sln new file mode 100644 index 00000000..3e93d416 --- /dev/null +++ b/Flashcards.davetn657/Flashcards.davetn657.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36705.20 d17.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flashcards.davetn657", "Flashcards.davetn657.csproj", "{24ED61BB-F123-42B5-BDBD-81949CE56058}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {24ED61BB-F123-42B5-BDBD-81949CE56058}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {24ED61BB-F123-42B5-BDBD-81949CE56058}.Debug|Any CPU.Build.0 = Debug|Any CPU + {24ED61BB-F123-42B5-BDBD-81949CE56058}.Release|Any CPU.ActiveCfg = Release|Any CPU + {24ED61BB-F123-42B5-BDBD-81949CE56058}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {67AA5100-C966-4C99-9966-C07253D916DA} + EndGlobalSection +EndGlobal diff --git a/Flashcards.davetn657/Models/DTOs/CardDTO.cs b/Flashcards.davetn657/Models/DTOs/CardDTO.cs new file mode 100644 index 00000000..c0cf082d --- /dev/null +++ b/Flashcards.davetn657/Models/DTOs/CardDTO.cs @@ -0,0 +1,10 @@ +namespace Flashcards.davetn657.Models.DTOs; + +public class CardDto +{ + public int Id { get; set; } + public string? Question { get; set; } + public string? Answer { get; set; } + public DateTime LastAppearance { get; set; } + public DateTime NextAppearance { get; set; } +} diff --git a/Flashcards.davetn657/Models/DTOs/ScoreDTO.cs b/Flashcards.davetn657/Models/DTOs/ScoreDTO.cs new file mode 100644 index 00000000..d76149b4 --- /dev/null +++ b/Flashcards.davetn657/Models/DTOs/ScoreDTO.cs @@ -0,0 +1,9 @@ +namespace Flashcards.davetn657.Models.DTOs; + +internal class ScoreDto +{ + public int SessionId { get; set; } + public string Name { get; set; } + public int Score { get; set; } + public DateTime CreateDate { get; set; } +} diff --git a/Flashcards.davetn657/Models/DTOs/StackDTO.cs b/Flashcards.davetn657/Models/DTOs/StackDTO.cs new file mode 100644 index 00000000..5494ada9 --- /dev/null +++ b/Flashcards.davetn657/Models/DTOs/StackDTO.cs @@ -0,0 +1,6 @@ +namespace Flashcards.davetn657.Models.DTOs; +public class StackDto +{ + public int Id { get; set; } + public string? Name { get; set; } +} diff --git a/Flashcards.davetn657/Models/DTOs/StudyDTO.cs b/Flashcards.davetn657/Models/DTOs/StudyDTO.cs new file mode 100644 index 00000000..5f5efc61 --- /dev/null +++ b/Flashcards.davetn657/Models/DTOs/StudyDTO.cs @@ -0,0 +1,9 @@ +namespace Flashcards.davetn657.Models.DTOs; + +public class StudyDto +{ + public int Id { get; set; } + public int StackId { get; set; } + public string Name { get; set; } + public int Score { get; set; } +} \ No newline at end of file diff --git a/Flashcards.davetn657/Models/Enums/OptionUtils.cs b/Flashcards.davetn657/Models/Enums/OptionUtils.cs new file mode 100644 index 00000000..1b792687 --- /dev/null +++ b/Flashcards.davetn657/Models/Enums/OptionUtils.cs @@ -0,0 +1,47 @@ +using System.ComponentModel; +using System.Reflection; + +namespace Flashcards.davetn657.Models.Enums; +public class OptionUtils +{ + public static string GetStringValue(Enum value) + { + FieldInfo? info = value.GetType().GetField(value.ToString()); + DescriptionAttribute[] attributes = (DescriptionAttribute[])info.GetCustomAttributes(typeof(DescriptionAttribute), false); + if (attributes.Length > 0) + { + return attributes[0].Description; + } + else + { + return string.Empty; + } + } + + public static List GetAllStringValues(Type enumType) + { + var enumValues = enumType.GetEnumValues(); + List allValues = new List(); + + foreach (var value in enumValues) + { + allValues.Add(GetStringValue((Enum)value)); + } + + return allValues; + } + + public static Enum GetEnumValue(string description, Type enumType) + { + var enumValues = enumType.GetEnumValues(); + + foreach (var value in enumValues) + { + if(GetStringValue((Enum)value).Equals(description)) + { + return (Enum)value; + } + } + throw new Exception("Not Found."); + } +} diff --git a/Flashcards.davetn657/Models/Enums/Options.cs b/Flashcards.davetn657/Models/Enums/Options.cs new file mode 100644 index 00000000..83c36b3e --- /dev/null +++ b/Flashcards.davetn657/Models/Enums/Options.cs @@ -0,0 +1,62 @@ +using System.ComponentModel; +namespace Flashcards.davetn657.Models.Enums; + +public enum MainMenuOptions +{ + [Description("Start studying")]StartStudy, + [Description("Manage data")] ManageData, + [Description("Exit")] ExitApp +} + +public enum ManageDataOptions +{ + [Description("Return")] Return, + [Description("Manage study sessions")]ManageSessions, + [Description("Manage stacks")]ManageStacks +} + +public enum ManageStudySessionOptions +{ + [Description("Return")] Return, + [Description("Create a new session")]CreateSession +} + +public enum ManageStackOptions +{ + [Description("Return")] Return, + [Description("Create stack")] CreateStack +} + +public enum EditStackOptions +{ + [Description("Return")] Return, + [Description("Rename stack")]RenameStack, + [Description("Add a card")]CreateCard, + [Description("Edit card")]ChooseCard, + [Description("Delete stack")]DeleteStack +} + +public enum EditCardOptions +{ + [Description("Return")] Return, + [Description("Change question")]ChangeQuestion, + [Description("Change answer")]ChangeAnswer, + [Description("Delete card")]DeleteCard +} + +public enum ChooseDataOptions +{ + [Description("Return")]Return +} + +public enum FlashcardOptions +{ + [Description("Reveal card")]Reveal, + [Description("Return")]Return +} + +public enum RevealedFlashcardOptions +{ + [Description("Need to study more")]StudyAgain, + [Description("I understood it well")]Understood, +} \ No newline at end of file diff --git a/Flashcards.davetn657/Models/Globals.cs b/Flashcards.davetn657/Models/Globals.cs new file mode 100644 index 00000000..c3445981 --- /dev/null +++ b/Flashcards.davetn657/Models/Globals.cs @@ -0,0 +1,7 @@ +using System.Globalization; + +namespace Flashcards.davetn657.Models; +public class Globals +{ + public static readonly string DATE_FORMAT = "dd/MM/yyyy"; +} \ No newline at end of file diff --git a/Flashcards.davetn657/Program.cs b/Flashcards.davetn657/Program.cs new file mode 100644 index 00000000..8ca23843 --- /dev/null +++ b/Flashcards.davetn657/Program.cs @@ -0,0 +1,19 @@ +using Flashcards.davetn657.Controllers; +using Flashcards.davetn657.Views; + +namespace Flashcards.davetn657; +class Program +{ + public static void Main(string[] args) + { + var studyController = new StudyController(); + var stackController = new StackController(); + var cardController = new CardController(); + var scoreController = new ScoreController(); + + var manageDataview = new ManageDataView(studyController, stackController, cardController); + var startStudySession = new StartStudySessionView(studyController, cardController, scoreController); + MainView view = new MainView(manageDataview, startStudySession); + view.StartApp(); + } +} diff --git a/Flashcards.davetn657/Properties/launchSettings.json b/Flashcards.davetn657/Properties/launchSettings.json new file mode 100644 index 00000000..30f1fad6 --- /dev/null +++ b/Flashcards.davetn657/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "Flashcards.davetn657": { + "commandName": "Project", + "workingDirectory": "C:\\Users\\davei\\Documents\\.Coding\\.C#Academy\\CodeReviews.Console.Flashcards\\Flashcards.davetn657" + } + } +} \ No newline at end of file diff --git a/Flashcards.davetn657/README.md b/Flashcards.davetn657/README.md new file mode 100644 index 00000000..2f7083ac --- /dev/null +++ b/Flashcards.davetn657/README.md @@ -0,0 +1,51 @@ +# Flashcards Console Application + +## Overview + +A console based app that uses spaced repetition to promote learning. + +Created for the C# Academy based learning + +## Requirements + +- Users are able to create stacks of flashcards +- Stacks and flashcards tables. Both tables should be linked by a foreign key +- Stacks should have unique names +- Every flashcard must be linked to a stack. +- If a stack is deleted all related flashcards should be deleted +- Use DTOs +- Study Session area, where users can study their stacks +- Stacks and Sessions should have tables and should be linked by a foreign key + +### Technologies + +- C# +- SQLServer +- Spectre.Console + +## Features + +Features user friendly user interface allowing navigation through menu options + +Launching the application will display a menu with the following options: + +Start studying -> Opens a new menu where users will be able to see the past +weeks study sessions and choose which stack to study + +Manage data -> Opens a menu where users will be able to create/edit new +study sessions and stacks. Users are able to create new +cards by editing a stack + +Exit -> Closes application + +## Looking back + +### Positives + +- When I first started the project it was alot of fun learning new tools. +- Tried to stick to a MVC design, I felt I did well + +### Improvements + +- Due to poor planning of the UI I ended up refactoring my UI alot +- At the start of the project I had trouble connecting to SQLserver in Visual Studio diff --git a/Flashcards.davetn657/SqlScript.txt b/Flashcards.davetn657/SqlScript.txt new file mode 100644 index 00000000..726b3638 --- /dev/null +++ b/Flashcards.davetn657/SqlScript.txt @@ -0,0 +1,41 @@ +CREATE TABLE Stacks( + StackId INT IDENTITY(1,1) PRIMARY KEY, + StackName NVARCHAR(50) NOT NULL, + CreateDate DATETIME2 DEFAULT GETDATE() +); + +CREATE TABLE Cards( + CardId INT IDENTITY(1,1) PRIMARY KEY, + StackId INT NOT NULL, + CardQuestion NVARCHAR(100) NOT NULL, + CardAnswer NVARCHAR(100) NOT NULL, + LastAppearance DATETIME2 DEFAULT GETDATE(), + NextAppearance DATETIME2 DEFAULT GETDATE(), + CREATEDATE DATETIME2 DEFAULT GETDATE(), + CONSTRAINT FK_Cards_StackId + FOREIGN KEY (StackId) + REFERENCES Stacks(StackId) + ON DELETE CASCADE +); + +CREATE TABLE Sessions( + SessionId INT IDENTITY(1,1) PRIMARY KEY, + StackId INT NOT NULL, + SessionName NVARCHAR(50) NOT NULL, + CreateDate DATETIME2 DEFAULT GETDATE(), + CONSTRAINT FK_Session_StackId + FOREIGN KEY (StackId) + REFERENCES Stacks(StackId) + ON DELETE CASCADE +); + +CREATE TABLE Scores( + ScoreId INT IDENTITY(1,1) PRIMARY KEY, + SessionId INT NOT NULL, + Score INT DEFAULT 0, + CreateDate DATETIME2 DEFAULT GETDATE(), + CONSTRAINT FK_Scores_SessionId + FOREIGN KEY (SessionId) + REFERENCES Sessions(SessionId) + ON DELETE CASCADE +); \ No newline at end of file diff --git a/Flashcards.davetn657/Views/MainView.cs b/Flashcards.davetn657/Views/MainView.cs new file mode 100644 index 00000000..3ec1efbd --- /dev/null +++ b/Flashcards.davetn657/Views/MainView.cs @@ -0,0 +1,45 @@ +using Flashcards.davetn657.Models.Enums; +using Spectre.Console; + +namespace Flashcards.davetn657.Views; + +public class MainView : UserInterface +{ + private readonly ManageDataView _manageDataView; + private readonly StartStudySessionView _startStudySession; + + public MainView(ManageDataView manageDataView, StartStudySessionView startStudySession) + { + _manageDataView = manageDataView; + _startStudySession = startStudySession; + } + + public void StartApp() + { + var endApp = false; + + while (!endApp) + { + TitleCard("Main Menu"); + + var menuOptions = OptionUtils.GetAllStringValues(typeof(MainMenuOptions)); + + var input = AnsiConsole.Prompt(new SelectionPrompt().AddChoices(menuOptions)); + var inputValue = OptionUtils.GetEnumValue(input, typeof(MainMenuOptions)); + + + switch (inputValue) + { + case MainMenuOptions.StartStudy: + _startStudySession.ChooseStudySession(); + break; + case MainMenuOptions.ManageData: + _manageDataView.ChooseDataToManage(); + break; + case MainMenuOptions.ExitApp: + endApp = true; + break; + } + } + } +} \ No newline at end of file diff --git a/Flashcards.davetn657/Views/ManageDataView.cs b/Flashcards.davetn657/Views/ManageDataView.cs new file mode 100644 index 00000000..87a5174c --- /dev/null +++ b/Flashcards.davetn657/Views/ManageDataView.cs @@ -0,0 +1,320 @@ +using Flashcards.davetn657.Controllers; +using Flashcards.davetn657.Models.DTOs; +using Flashcards.davetn657.Models.Enums; +using Spectre.Console; + +namespace Flashcards.davetn657.Views; + +public class ManageDataView : UserInterface +{ + private readonly StudyController _studyController; + private readonly StackController _stackController; + private readonly CardController _cardController; + + public ManageDataView(StudyController studyController, StackController stackController, CardController cardController) + { + _studyController = studyController; + _stackController = stackController; + _cardController = cardController; + } + + internal void ChooseDataToManage() + { + var endEdit = false; + + while (!endEdit) + { + TitleCard("Edit Data"); + + var menuOptions = OptionUtils.GetAllStringValues(typeof(ManageDataOptions)); + var input = AnsiConsole.Prompt(new SelectionPrompt().AddChoices(menuOptions)); + var selectedOption = OptionUtils.GetEnumValue(input, typeof(ManageDataOptions)); + + switch (selectedOption) + { + case ManageDataOptions.ManageSessions: + ManageStudySession(); + break; + case ManageDataOptions.ManageStacks: + ManageStack(); + break; + case ManageDataOptions.Return: + endEdit = true; + break; + } + } + } + + // STUDY SESSIONS + + private void ManageStudySession() + { + var endEdit = false; + + while (!endEdit) + { + TitleCard("Study Session"); + + var options = OptionUtils.GetAllStringValues(typeof(ManageStudySessionOptions)); + var sessions = _studyController.ReadAllSessions(); + var menuOptions = options.Concat(sessions.Keys); + + var input = AnsiConsole.Prompt(new SelectionPrompt().AddChoices(menuOptions)); + + if (sessions.ContainsKey(input)) + { + SessionDetails(sessions[input]); + } + else + { + var optionSelected = OptionUtils.GetEnumValue(input, typeof(ManageStudySessionOptions)); + + switch (optionSelected) + { + case ManageStudySessionOptions.CreateSession: + CreateSession(); + break; + case ManageStudySessionOptions.Return: + endEdit = true; + break; + } + } + } + } + + private void CreateSession() + { + TitleCard("Create a new session"); + + var input = AnsiConsole.Ask("Name your session (or type r to cancel):"); + + if (input.Equals('r')) return; + + TitleCard("What stack to add?"); + AnsiConsole.WriteLine("Choose which stack to add onto the study session"); + + var session = new StudyDto(); + session.Name = input; + + var options = OptionUtils.GetAllStringValues(typeof(ChooseDataOptions)); + var stackOptions = _stackController.ReadAllStacks(); + var menuOptions = options.Concat(stackOptions.Keys); + + input = AnsiConsole.Prompt(new SelectionPrompt().AddChoices(menuOptions)); + + if (stackOptions.ContainsKey(input)) + { + session.StackId = stackOptions[input].Id; + + _studyController.AddSession(session); + AnsiConsole.Prompt(new TextPrompt("Press Enter to return...").AllowEmpty()); + } + } + + private void SessionDetails(StudyDto session) + { + TitleCard(session.Name); + + var stack = _stackController.ReadAllStacks(session); + var cards = _cardController.ReadAllCards(session); + + var table = new Table(); + table.AddColumn("Stack Id"); + table.AddColumn("Stack"); + table.AddColumn("# of Cards"); + + table.AddRow(new string[] { stack.Id.ToString(), stack.Name, cards.Count.ToString() }); + + AnsiConsole.Write(table); + + AnsiConsole.Prompt(new TextPrompt("Press Enter to return...").AllowEmpty()); + } + + // STACKS + + private void ManageStack() + { + var endEdit = false; + + while (!endEdit) + { + TitleCard("Manage Stacks"); + + var options = OptionUtils.GetAllStringValues(typeof(ManageStackOptions)); + var stacks = _stackController.ReadAllStacks(); + var menuOptions = options.Concat(stacks.Keys); + + var input = AnsiConsole.Prompt(new SelectionPrompt().AddChoices(menuOptions)); + + if (stacks.ContainsKey(input)) + { + EditStack(stacks[input]); + } + else + { + var optionSelected = OptionUtils.GetEnumValue(input, typeof(ManageStackOptions)); + + switch (optionSelected) + { + case ManageStackOptions.CreateStack: + var name = ChooseName(stacks.Keys); + if (name != string.Empty) + { + _stackController.AddStack(name); + AnsiConsole.WriteLine($"Stack named {name} created!\n"); + } + AnsiConsole.Prompt(new TextPrompt("Press Enter to return...").AllowEmpty()); + break; + case ManageStackOptions.Return: + endEdit = true; + break; + } + } + } + + } + + private void EditStack(StackDto stack) + { + var endEdit = false; + + while (!endEdit) + { + TitleCard($"Edit : {stack.Name}"); + + var menuOptions = OptionUtils.GetAllStringValues(typeof(EditStackOptions)); + + var input = AnsiConsole.Prompt(new SelectionPrompt().AddChoices(menuOptions)); + var optionSelected = OptionUtils.GetEnumValue(input, typeof(EditStackOptions)); + + switch (optionSelected) + { + case EditStackOptions.RenameStack: + var stacks = _stackController.ReadAllStacks(); + var name = ChooseName(stacks.Keys); + if (name != string.Empty) + { + stack.Name = name; + _stackController.EditStack(stack); + AnsiConsole.WriteLine($"Stack renamed to: {name}\n"); + } + AnsiConsole.Prompt(new TextPrompt("Press Enter to return...").AllowEmpty()); + break; + case EditStackOptions.CreateCard: + CreateCard(stack); + break; + case EditStackOptions.ChooseCard: + ChooseCard(stack); + break; + case EditStackOptions.DeleteStack: + _stackController.RemoveStack(stack); + AnsiConsole.WriteLine("Successfully removed stack!"); + AnsiConsole.Prompt(new TextPrompt("Press Enter to return...").AllowEmpty()); + endEdit = true; + break; + case EditStackOptions.Return: + endEdit = true; + break; + } + } + } + + // CARDS + + internal void CreateCard(StackDto stack) + { + TitleCard("Create a new Card"); + + var input = string.Empty; + var card = new CardDto(); + + input = AnsiConsole.Ask("Input question details (type: r to cancel):"); + if (input.ToLower() == "r") return; + card.Question = input; + + input = AnsiConsole.Ask("Input answer details (type: r to cancel):"); + if (input.ToLower() == "r") return; + card.Answer = input; + + _cardController.AddCard(card, stack); + } + + internal void ChooseCard(StackDto stack) + { + var endEdit = false; + + while (!endEdit) + { + TitleCard($"{stack.Name} Cards"); + + var options = OptionUtils.GetAllStringValues(typeof(ChooseDataOptions)); + var cards = _cardController.ReadAllCards(); + var menuOptions = options.Concat(cards.Keys); + + var input = AnsiConsole.Prompt(new SelectionPrompt().AddChoices(menuOptions)); + + if (cards.ContainsKey(input)) + { + EditCard(cards[input]); + } + else + { + endEdit = true; + } + } + } + + internal void EditCard(CardDto card) + { + var endEdit = false; + + while (!endEdit) + { + TitleCard($"Edit Card : {card.Question}"); + + var input = AnsiConsole.Prompt(new SelectionPrompt().AddChoices(OptionUtils.GetAllStringValues(typeof(EditCardOptions)))); + var selectedOption = OptionUtils.GetEnumValue(input, typeof(EditCardOptions)); + + switch (selectedOption) + { + case EditCardOptions.ChangeQuestion: + ChangeCardDetails(card, selectedOption); + break; + case EditCardOptions.ChangeAnswer: + ChangeCardDetails(card, selectedOption); + break; + case EditCardOptions.DeleteCard: + _cardController.RemoveCard(card); + AnsiConsole.WriteLine("Successfully removed card!"); + AnsiConsole.Prompt(new TextPrompt("Press Enter to return...").AllowEmpty()); + endEdit = true; + break; + case EditCardOptions.Return: + endEdit = true; + break; + } + } + + } + + private void ChangeCardDetails(CardDto card, Enum option) + { + TitleCard(OptionUtils.GetStringValue(option)); + + var input = AnsiConsole.Ask("Enter details (type: r to return):"); + + if (input.ToLower() == "r") return; + + switch (option) + { + case EditCardOptions.ChangeQuestion: + card.Question = input; + break; + case EditCardOptions.ChangeAnswer: + card.Answer = input; + break; + } + + _cardController.EditCard(card, option); + } +} \ No newline at end of file diff --git a/Flashcards.davetn657/Views/StartStudySessionView.cs b/Flashcards.davetn657/Views/StartStudySessionView.cs new file mode 100644 index 00000000..fc09d12f --- /dev/null +++ b/Flashcards.davetn657/Views/StartStudySessionView.cs @@ -0,0 +1,190 @@ +using Flashcards.davetn657.Controllers; +using Flashcards.davetn657.Models; +using Flashcards.davetn657.Models.DTOs; +using Flashcards.davetn657.Models.Enums; +using Microsoft.IdentityModel.Tokens; +using Spectre.Console; + +namespace Flashcards.davetn657.Views; + +public class StartStudySessionView : UserInterface +{ + private readonly StudyController _studyController; + private readonly CardController _cardController; + private readonly ScoreController _scoreController; + + public StartStudySessionView(StudyController studyController, CardController cardController, ScoreController scoreController) + { + _studyController = studyController; + _cardController = cardController; + _scoreController = scoreController; + } + + internal void ChooseStudySession() + { + DisplayWeeklySummary(); + + var options = OptionUtils.GetAllStringValues(typeof(ChooseDataOptions)); + var session = _studyController.ReadAllSessions(); + var menuOptions = options.Concat(session.Keys); + + var input = AnsiConsole.Prompt(new SelectionPrompt().AddChoices(menuOptions)); + + if (session.ContainsKey(input)) + { + GenerateCards(session[input]); + } + else return; + } + + private void GenerateCards(StudyDto session) + { + var cards = _cardController.ReadAllCards(session); + var menuOptions = OptionUtils.GetAllStringValues(typeof(FlashcardOptions)); + var revealedMenuOptions = OptionUtils.GetAllStringValues(typeof(RevealedFlashcardOptions)); + + var score = new ScoreDto() + { + SessionId = session.Id, + Score = 0 + }; + + if (cards.IsNullOrEmpty()) + { + AnsiConsole.WriteLine("!!! This Study Session Has No Cards !!!\n!!! Please Add Cards in the Manage Data Options !!!"); + AnsiConsole.Prompt(new TextPrompt("Press Enter to return...").AllowEmpty()); + return; + } + + while (true) + { + TitleCard("Session in Progress..."); + + var currentCard = cards.FirstOrDefault().Value; + + var selectedOption = OptionUtils.GetEnumValue(DisplayCard(currentCard.Question, menuOptions), typeof(FlashcardOptions)); + + if (selectedOption.Equals(FlashcardOptions.Return)) break; + + TitleCard("The answer is..."); + + selectedOption = OptionUtils.GetEnumValue(DisplayCard(currentCard.Answer, revealedMenuOptions), typeof(RevealedFlashcardOptions)); + + var timeChange = new DateTime(); + var lastCard = cards.LastOrDefault().Value; + + AnsiConsole.WriteLine("How well did you understand?"); + switch (selectedOption) + { + case RevealedFlashcardOptions.StudyAgain: + timeChange = lastCard.NextAppearance.AddMinutes(10); + break; + case RevealedFlashcardOptions.Understood: + timeChange = lastCard.NextAppearance.AddHours(1); + break; + } + + _cardController.ChangeTime(currentCard, timeChange); + score.Score++; + + cards.Remove(currentCard.Question); + + if (cards.Count == 0) + { + cards = _cardController.ReadAllCards(session); + } + } + + _scoreController.AddScore(session, score); + + } + + private string DisplayCard(string cardDetails, List menuOptions) + { + var panel = new Panel(cardDetails) + { + Border = BoxBorder.Ascii + }; + + AnsiConsole.Write(panel); + var input = AnsiConsole.Prompt(new SelectionPrompt().AddChoices(menuOptions)); + + return input; + } + + //Should Display past week session summary + //should display breakdown of session types + private void DisplayWeeklySummary() + { + var allScoresBar = new BarChart() + .Label("Study Sessions in the Past Week") + .LeftAlignLabel(); + + var sessionsBreakdown = new BreakdownChart() + .Compact() + .ShowPercentage(); + + var sessions = new Dictionary(); + var dailyScores = new Dictionary(); + var totalScore = 0; + + var scores = _scoreController.GetScores(7); + if(scores.IsNullOrEmpty()) return; + + var colors = new Color[] + { + Color.Yellow, + Color.Green, + Color.Blue, + Color.Gold3, + Color.White, + Color.Red, + Color.Violet + }; + + foreach (var score in scores) + { + if (sessions.ContainsKey(score.Name)) + { + sessions[score.Name] += score.Score; + } + else + { + sessions.Add(score.Name, score.Score); + } + + if (dailyScores.ContainsKey(score.CreateDate.Date)) + { + dailyScores[score.CreateDate.Date] += score.Score; + } + else + { + dailyScores.Add(score.CreateDate.Date, score.Score); + } + totalScore += score.Score; + } + + var colorCount = colors.Count() - 1; + var randomHexDigit = new Random(); + + foreach (var score in dailyScores) + { + allScoresBar.AddItem(score.Key.ToString(Globals.DATE_FORMAT), score.Value, colors[colorCount]); + + colorCount--; + } + + foreach(var session in sessions) + { + var percentageOfTotalSessions = Math.Round(((float)session.Value / (float)totalScore) * 100, 2); + + sessionsBreakdown.AddItem(session.Key, percentageOfTotalSessions, Color.FromInt32(randomHexDigit.Next(1, 250))); + } + + var dailyScorePanel = new Panel(allScoresBar).Border(BoxBorder.Ascii); + + AnsiConsole.Clear(); + AnsiConsole.Write(dailyScorePanel); + AnsiConsole.Write(sessionsBreakdown); + } +} \ No newline at end of file diff --git a/Flashcards.davetn657/Views/UserInterface.cs b/Flashcards.davetn657/Views/UserInterface.cs new file mode 100644 index 00000000..76d94354 --- /dev/null +++ b/Flashcards.davetn657/Views/UserInterface.cs @@ -0,0 +1,39 @@ +using Spectre.Console; + +namespace Flashcards.davetn657.Views; +public class UserInterface +{ + internal string ChooseName(IEnumerable namesInUse) + { + TitleCard("Choose a Name"); + + var input = string.Empty; + + while (true) + { + input = AnsiConsole.Ask("Type a Name (type: r to return):"); + if (input.ToLower() == "r") + { + return string.Empty; + } + + if (!namesInUse.Contains(input)) + { + return input; + } + + AnsiConsole.Markup("[red]Name Already in Use or Not Available Choose a Different Name![/]\n"); + } + } + + internal void TitleCard(string title) + { + var titleFiglet = new FigletText(title); + + var centered = Align.Center(titleFiglet); + + AnsiConsole.Clear(); + AnsiConsole.Write(centered); + } +} + diff --git a/Flashcards.davetn657/appsettings.json b/Flashcards.davetn657/appsettings.json new file mode 100644 index 00000000..5f40f1b6 --- /dev/null +++ b/Flashcards.davetn657/appsettings.json @@ -0,0 +1,5 @@ +{ + "ConnectionStrings": { + "DatabaseConnection": "Server=(localdb)\\Flashcards;Database=FlashcardsDB;TrustServerCertificate=True;" + } +} \ No newline at end of file