Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
091f094
feat: Initialize project
StressedBread Apr 26, 2026
bbed116
feat: add connection string, database and table initialization
StressedBread Apr 26, 2026
67b5cbf
feat: add main menu and enum to string converter
StressedBread Apr 26, 2026
7edc543
refactor: improve database setup and error handling
StressedBread Apr 28, 2026
519cab8
chore: add model and cleanup code
StressedBread Apr 28, 2026
f7e680a
feat(stacks): add modular stacks management architecture
StressedBread Apr 28, 2026
c67d310
refactor(database): modularize DB init and query handling
StressedBread Apr 28, 2026
36765bf
feat(database,ui): refactor stacks to use DB and add menu options
StressedBread Apr 28, 2026
e5e0900
feat: add FlashcardsController and basic data query class
StressedBread Apr 28, 2026
777c324
feat: added flashcard managing in stack
StressedBread May 15, 2026
917aeea
feat(ui): add global manage flashcards menu with edit/delete
StressedBread May 15, 2026
661cc83
feat: add study session feature to UI and database
StressedBread May 17, 2026
5db05bd
feat(Database, Controller): add study session tracking
StressedBread May 17, 2026
c31c306
feat: add study session viewing functionality
StressedBread May 17, 2026
86cbd0c
refactor(models): remove StudySessionModel class
StressedBread May 17, 2026
b87cf3c
refactor: simplify stack handling in controllers
StressedBread May 17, 2026
bdae011
refactor: improve flashcard and stack controllers
StressedBread May 17, 2026
72b8571
chore: remove helpers folder
StressedBread May 17, 2026
c143ee9
refactor: ensure proper disposal of SqlConnection in DatabaseAccess
StressedBread May 17, 2026
5794d0d
refactor: use dbo schema in FlashcardsQueries SQL
StressedBread May 17, 2026
74f6a6a
feat(add): added full reporting system on study sessions and average …
StressedBread May 18, 2026
097fd17
refactor: changed study stack selection to use text
StressedBread May 18, 2026
a30d7a3
chore: code cleanup and minor refactor
StressedBread May 18, 2026
edc24cf
added README
StressedBread May 18, 2026
923b7ec
moved README
StressedBread May 18, 2026
78d95d7
moved README
StressedBread May 18, 2026
016ce0f
Update README.md
StressedBread May 18, 2026
b61fabd
Update README.md
StressedBread May 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Flashcards

A C# console application for creating and studying flashcard stacks, with session tracking and monthly performance reports.


## Features

- **Stack Management** — Create, browse, and delete named flashcard stacks
- **Card Management** — Add, edit, and delete flashcards within any stack, or manage all cards across stacks from a single view
- **Study Mode** — Practice with randomised flashcards and get instant right/wrong feedback
- **Study History** — View past sessions filtered by stack or across all stacks
- **Monthly Reports** — Reports showing sessions count or average score per month per stack

## Getting Started

### Prerequisites

- .NET 8 SDK
- SQL Server (LocalDB is sufficient)
### Installation

1. **Clone the repository**
```bash
git clone <your-repository-url>
cd StressedBread.Flashcards
```

2. **Restore dependencies**
```bash
dotnet restore
```

3. **Run the application**
```bash
dotnet run
```

On first launch the app will automatically create the `FlashcardsStressedBread` database and all required tables. Subsequent launches safely skip this step.

## How to Use

### 1. Creating Your First Stack

- Select **Manage Stacks** from the main menu
- Type the name of a new stack and press Enter
### 2. Adding Flashcards

- After selecting a stack, select **Add Flashcard**
- Enter a question and an answer
- Repeat to fill your stack
### 3. Studying

- Select **Study** from the main menu
- Enter the name of the stack you want to practise
- Type your answer for each card shown — the app tells you instantly if you are right and reveals the correct answer if not
- Cards are presented in random order; each card appears once per session
- Enter `0` at any point to end the session early
- Your score is saved automatically when the session ends but not if exited early
### 4. Tracking Progress

- Select **View Study Sessions** from the main menu
- Enter `1` to see all sessions across every stack, or type a stack name to filter
### 5. Monthly Reports

- Select **View Reports** from the main menu
- Choose a report type:
- **Sessions Per Month** — how many sessions you completed each month
- **Average Score Per Month** — your average score across sessions each month
- Enter a stack name and a year to generate the report

## Technologies Used

- **Framework**: .NET 8
- **Language**: C# 12
- **Database**: SQL Server with Dapper
- **UI**: Spectre.Console

## Configuration

The app requires two connection strings in `appsettings.json`:

| Key | Purpose |
|---|---|
| `DefaultConnection` | Used on startup to create the flashcards database (point at `master` or any existing DB) |
| `StressedBreadFlashcards` | Used for all application data (points at `FlashcardsStressedBread`) |

```json
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=master;Trusted_Connection=True;TrustServerCertificate=True;",
"StressedBreadFlashcards": "Server=localhost;Database=FlashcardsStressedBread;Trusted_Connection=True;TrustServerCertificate=True;"
}
}
```
25 changes: 25 additions & 0 deletions StressedBread.Flashcards/StressedBread.Flashcards.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.13.35818.85 d17.13
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StressedBread.Flashcards", "StressedBread.Flashcards\StressedBread.Flashcards.csproj", "{489F1B7A-B649-4061-B283-C142928D0875}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{489F1B7A-B649-4061-B283-C142928D0875}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{489F1B7A-B649-4061-B283-C142928D0875}.Debug|Any CPU.Build.0 = Debug|Any CPU
{489F1B7A-B649-4061-B283-C142928D0875}.Release|Any CPU.ActiveCfg = Release|Any CPU
{489F1B7A-B649-4061-B283-C142928D0875}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A4886650-09CF-4FC3-8C26-721B221BB8E0}
EndGlobalSection
EndGlobal
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
using StressedBread.Flashcards.Data;
using StressedBread.Flashcards.Data.Queries;
using StressedBread.Flashcards.DTOs;
using StressedBread.Flashcards.UI;
using static StressedBread.Flashcards.Enums;

namespace StressedBread.Flashcards.Controllers;
internal class FlashcardsController
{
private readonly FlashcardsUI _flashcardsUI;
private readonly DatabaseAccess _databaseAccess;
private readonly FlashcardsQueries _flashcardsQueries;

private List<FlashcardsDTO> _flashcards = new();
private List<AllFlashcardsDTO> _allFlashcards = new();

internal FlashcardsController(FlashcardsUI flashcardsUI, DatabaseAccess databaseAccess, FlashcardsQueries flashcardsQueries)
{
_flashcardsUI = flashcardsUI;
_databaseAccess = databaseAccess;
_flashcardsQueries = flashcardsQueries;
}

internal List<FlashcardsDTO> GetFlashcardsByStackId(int stackId)
{
return _databaseAccess.Reader<FlashcardsDTO>(_flashcardsQueries.GetFlashcardsByStackIdQuery(), new { StackId = stackId });
}

internal FlashcardsDTO? GetFlashcardById(int flashcardId)
{
return _databaseAccess.Reader<FlashcardsDTO>(_flashcardsQueries.GetFlashcardByIdQuery(), new { Id = flashcardId }).FirstOrDefault();
}

internal void ViewFlashcards(int stackId)
{
_flashcards = GetFlashcardsByStackId(stackId);
_flashcardsUI.ViewFlashcards(_flashcards);
}

internal void AddFlashcard(int stackId)
{
var (question, answer) = _flashcardsUI.AddFlashcardView();
_databaseAccess.ExecuteQuery(_flashcardsQueries.AddFlashcardQuery(), new { Question = question, Answer = answer, StackId = stackId });
}

internal void EditFlashcard(int stackId)
{
_flashcards = GetFlashcardsByStackId(stackId);
int selectedDisplayId = _flashcardsUI.FlashcardsStackView(_flashcards);

if (selectedDisplayId == 0)
return;

int selectedRealId = selectedDisplayId > 0 && selectedDisplayId <= _flashcards.Count ? _flashcards[selectedDisplayId - 1].Id : -1;

EditFlashcardById(selectedRealId);
}

internal void EditFlashcardById(int flashcardId)
{
var currentFlashcard = GetFlashcardById(flashcardId);

if (flashcardId == -1 || currentFlashcard == null)
{
_flashcardsUI.InvalidFlashcardIdMessage();
return;
}

var (question, answer) = _flashcardsUI.EditFlashcardView(currentFlashcard);

if (String.Equals(question, "0")) question = currentFlashcard.Question;
if (String.Equals(answer, "0")) answer = currentFlashcard.Answer;

_databaseAccess.ExecuteQuery(_flashcardsQueries.EditFlashcardQuery(), new { Id = flashcardId, Question = question, Answer = answer});
}

internal void DeleteFlashcard(int stackId)
{
_flashcards = GetFlashcardsByStackId(stackId);
int selectedDisplayId = _flashcardsUI.FlashcardsStackView(_flashcards);

if (selectedDisplayId == 0)
return;

int selectedRealId = selectedDisplayId > 0 && selectedDisplayId <= _flashcards.Count ? _flashcards[selectedDisplayId - 1].Id : -1;

DeleteFlashcardById(selectedRealId);
}

internal void DeleteFlashcardById(int flashcardId)
{
if (flashcardId == -1)
{
_flashcardsUI.InvalidFlashcardIdMessage();
return;
}

_databaseAccess.ExecuteQuery(_flashcardsQueries.DeleteFlashcardQuery(), new { Id = flashcardId });
}

internal void FlashcardsMenu()
{
while (true)
{
_allFlashcards = _databaseAccess.Reader<AllFlashcardsDTO>(_flashcardsQueries.GetAllFlashcardsQuery());
int selectedFlashcardId = _flashcardsUI.ViewAllFlashcards(_allFlashcards);

if (selectedFlashcardId == 0)
return;

var selectedFlashcard = _allFlashcards.First(f => f.Id == selectedFlashcardId);

var option = _flashcardsUI.ManageFlashcardMenuView(selectedFlashcard.Question);

switch (option)
{
case FlashcardMenuOption.EditFlashcard:
EditFlashcardById(selectedFlashcardId);
break;
case FlashcardMenuOption.DeleteFlashcard:
DeleteFlashcardById(selectedFlashcardId);
break;
case FlashcardMenuOption.BackToMainMenu:
return;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using StressedBread.Flashcards.Data;
using StressedBread.Flashcards.Data.Queries;
using StressedBread.Flashcards.Models;
using StressedBread.Flashcards.UI;
using static StressedBread.Flashcards.Enums;

namespace StressedBread.Flashcards.Controllers;
internal class ReportsController
{
private readonly ReportsMenu _reportsMenu;
private readonly DatabaseAccess _databaseAccess;
private readonly ReportQueries _reportQueries;
private readonly StacksQueries _stacksQueries;

private List<StacksModel> _stacks = new List<StacksModel>();
internal ReportsController(ReportsMenu reportsMenu, DatabaseAccess databaseAccess, ReportQueries reportQueries, StacksQueries stacksQueries)
{
_reportsMenu = reportsMenu;
_databaseAccess = databaseAccess;
_reportQueries = reportQueries;
_stacksQueries = stacksQueries;
}

internal List<StacksModel> GetAllStacks()
{
return _databaseAccess.Reader<StacksModel>(_stacksQueries.GetAllStacksQuery());
}

internal void ViewReports()
{
while (true)
{
var selection = _reportsMenu.ReportsMainMenuView();

switch (selection)
{
case ReportMenuOption.SessionsPerMonthPerStack:
SessionsPerMonthPerStackReport();
break;
case ReportMenuOption.AverageScorePerMonthPerStack:
AverageScorePerMonthPerStackReport();
break;
case ReportMenuOption.BackToMainMenu:
return;
}
}
}

internal void SessionsPerMonthPerStackReport()
{
_stacks = GetAllStacks();
string result = _reportsMenu.StacksViewReport(_stacks);

if (String.Equals(result.Trim(), "0", StringComparison.OrdinalIgnoreCase))
return;

string year = _reportsMenu.YearToFilter();
var reportModel = _databaseAccess.ReportQuery(_reportQueries.SessionsPerMonthPerStackQuery(), new { Year = year, StackName = result });

if (reportModel == null)
{
_reportsMenu.InvalidReport();
return;
}

_reportsMenu.ReportView(reportModel, year);
}

internal void AverageScorePerMonthPerStackReport()
{
_stacks = GetAllStacks();
string result = _reportsMenu.StacksViewReport(_stacks);

if (String.Equals(result.Trim(), "0", StringComparison.OrdinalIgnoreCase))
return;

string year = _reportsMenu.YearToFilter();
var reportModel = _databaseAccess.ReportQuery(_reportQueries.AverageScorePerMonthPerStackQuery(), new { Year = year, StackName = result });

if (reportModel == null)
{
_reportsMenu.InvalidReport();
return;
}

_reportsMenu.ReportView(reportModel, year);
}
}
Loading