diff --git a/Management.Test/Features/ModuleTests.cs b/Management.Test/Features/ModuleTests.cs
new file mode 100644
index 0000000..6785c80
--- /dev/null
+++ b/Management.Test/Features/ModuleTests.cs
@@ -0,0 +1,40 @@
+public class ModuleTests
+{
+ [Test]
+ public void CanAddModule()
+ {
+ var manager = new ModuleManager();
+ var module = new CourseModule("First Module", new LocalAssignment[] { });
+ manager.AddModule(module);
+
+ manager.Modules.Count().Should().Be(1);
+ manager.Modules.First().Should().Be(module);
+ }
+
+ [Test]
+ public void CanAddAssignmentToCorrectModule()
+ {
+ var manager = new ModuleManager();
+ manager.AddModule(new CourseModule("First Module", new LocalAssignment[] { }));
+ manager.AddModule(new CourseModule("Second Module", new LocalAssignment[] { }));
+ manager.AddModule(new CourseModule("Third Module", new LocalAssignment[] { }));
+ manager.AddModule(new CourseModule("Fourth Module", new LocalAssignment[] { }));
+
+ var assignment = new LocalAssignment
+ {
+ name = "testname",
+ description = "testDescription",
+ published = false,
+ lock_at_due_date = true,
+ rubric = new RubricItem[] { },
+ lock_at = null,
+ due_at = DateTime.Now,
+ points_possible = 10,
+ submission_types = new SubmissionType[] { SubmissionType.online_text_entry }
+ };
+
+ manager.AddAssignment(3, assignment);
+ manager.Modules.Count().Should().Be(4);
+ manager.Modules.ElementAt(3).Assignments.Count().Should().Be(1);
+ }
+}
\ No newline at end of file
diff --git a/Management.Web/Pages/Modules.razor b/Management.Web/Pages/Modules.razor
index f79cec8..9799e63 100644
--- a/Management.Web/Pages/Modules.razor
+++ b/Management.Web/Pages/Modules.razor
@@ -1,3 +1,31 @@
@page "/modules"
+@using Management.Web.Shared.Module
+@using System.Linq
+@inject IModuleManager moduleManager
+
+@code {
+ private bool showNewModule { get; set; } = false;
+
+}
Weather forecast
+
+@if (!showNewModule)
+{
+
+}
+else
+{
+
+}
+
+@if (showNewModule)
+{
+
+}
+
+@foreach (var i in moduleManager.Modules.Select((_value, i) => i))
+{
+
+
+}
diff --git a/Management.Web/Program.cs b/Management.Web/Program.cs
index 59692c1..a677f10 100644
--- a/Management.Web/Program.cs
+++ b/Management.Web/Program.cs
@@ -1,5 +1,6 @@
global using System.Text.Json.Serialization;
global using System.Text.Json;
+global using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
@@ -15,6 +16,7 @@ builder.Services.AddServerSideBlazor();
builder.Services.AddSingleton();
builder.Services.AddSingleton();
builder.Services.AddSingleton();
+builder.Services.AddSingleton();
var app = builder.Build();
diff --git a/Management.Web/Shared/Module/Assignment/NewAssignment.razor b/Management.Web/Shared/Module/Assignment/NewAssignment.razor
new file mode 100644
index 0000000..cd2ffb8
--- /dev/null
+++ b/Management.Web/Shared/Module/Assignment/NewAssignment.razor
@@ -0,0 +1,38 @@
+@inject IModuleManager moduleManager
+
+@code {
+
+ [Parameter, EditorRequired]
+ public int ModuleIndex { get; set; }
+
+ [Parameter]
+ public EventCallback OnSubmit { get; set; }
+
+ [Required]
+ [StringLength(50, ErrorMessage = "Name too long (50 character limit).")]
+ private string Name { get; set; } = "";
+
+ private async Task submitHandler()
+ {
+ var newAssignment = new LocalAssignment
+ {
+ name = Name,
+ description = "testDescription",
+ published = false,
+ lock_at_due_date = true,
+ rubric = new RubricItem[] { },
+ lock_at = null,
+ due_at = DateTime.Now,
+ points_possible = 10,
+ submission_types = new SubmissionType[] { SubmissionType.online_text_entry }
+ };
+ moduleManager.AddAssignment(ModuleIndex, newAssignment);
+ await OnSubmit.InvokeAsync();
+ }
+}
+
+
diff --git a/Management.Web/Shared/Module/ModuleDetail.razor b/Management.Web/Shared/Module/ModuleDetail.razor
new file mode 100644
index 0000000..d545234
--- /dev/null
+++ b/Management.Web/Shared/Module/ModuleDetail.razor
@@ -0,0 +1,40 @@
+@using Management.Web.Shared.Module.Assignment
+
+
+@inject IModuleManager moduleManager
+
+@code {
+ [Parameter, EditorRequired]
+ public int ModuleIndex { get; set; }
+
+ private bool showAddAssignment { get; set; } = false;
+
+ private CourseModule? module
+ {
+ get
+ {
+ return moduleManager.Modules.ElementAtOrDefault(ModuleIndex);
+ }
+ }
+}
+
+@if (module != null)
+{
+ @module.Name
+
+ @if (showAddAssignment)
+ {
+
+ }
+
+ Assignments
+ @foreach (var assignment in module.Assignments)
+ {
+ @assignment.name
+ }
+
+}
\ No newline at end of file
diff --git a/Management.Web/Shared/Module/NewModule.razor b/Management.Web/Shared/Module/NewModule.razor
new file mode 100644
index 0000000..2d7555d
--- /dev/null
+++ b/Management.Web/Shared/Module/NewModule.razor
@@ -0,0 +1,27 @@
+@inject IModuleManager moduleManager
+
+@code {
+
+ [Required]
+ [StringLength(50, ErrorMessage = "Name too long (50 character limit).")]
+ private string Name { get; set; } = "";
+
+ [Parameter]
+ public EventCallback OnSubmit { get; set; }
+
+ private async Task submitHandler()
+ {
+ var module = new CourseModule(Name: Name, Assignments: new LocalAssignment[] { });
+ moduleManager.AddModule(module);
+ Name = "";
+ await OnSubmit.InvokeAsync();
+ }
+}
+
+New Module
+
+
diff --git a/Management/Features/Modules/IModuleManager.cs b/Management/Features/Modules/IModuleManager.cs
new file mode 100644
index 0000000..ee16e90
--- /dev/null
+++ b/Management/Features/Modules/IModuleManager.cs
@@ -0,0 +1,6 @@
+public interface IModuleManager
+{
+ IEnumerable Modules { get; }
+ public void AddModule(CourseModule newModule);
+ public void AddAssignment(int moduleIndex, LocalAssignment assignment);
+}
diff --git a/Management/Features/Modules/ModuleManager.cs b/Management/Features/Modules/ModuleManager.cs
new file mode 100644
index 0000000..2746238
--- /dev/null
+++ b/Management/Features/Modules/ModuleManager.cs
@@ -0,0 +1,22 @@
+public class ModuleManager : IModuleManager
+{
+ public IEnumerable Modules { get; internal set; } = new CourseModule[] { };
+
+ public void AddAssignment(int moduleIndex, LocalAssignment assignment)
+ {
+ var newAssignments = Modules.ElementAt(moduleIndex).Assignments.Append(assignment);
+ var newModule = Modules.ElementAt(moduleIndex) with { Assignments = newAssignments };
+ if (newModule == null)
+ throw new Exception($"cannot get module at index {moduleIndex}");
+
+ Modules = Modules
+ .Take(moduleIndex)
+ .Append(newModule)
+ .Concat(Modules.Skip(moduleIndex + 1));
+ }
+
+ public void AddModule(CourseModule newModule)
+ {
+ Modules = Modules.Append(newModule);
+ }
+}
\ No newline at end of file
diff --git a/Management/Models/CourseModule.cs b/Management/Models/CourseModule.cs
new file mode 100644
index 0000000..9e38b0f
--- /dev/null
+++ b/Management/Models/CourseModule.cs
@@ -0,0 +1,11 @@
+using System.ComponentModel.DataAnnotations;
+
+public record CourseModule(
+ [property: Required]
+ [property: StringLength(50, ErrorMessage = "Name too long (50 character limit).")]
+ string Name,
+ IEnumerable? Assignments
+)
+{
+ public IEnumerable Assignments = Assignments ?? new LocalAssignment[] { };
+}
\ No newline at end of file
diff --git a/Management/Models/LocalAssignment.cs b/Management/Models/LocalAssignment.cs
new file mode 100644
index 0000000..816c07b
--- /dev/null
+++ b/Management/Models/LocalAssignment.cs
@@ -0,0 +1,31 @@
+public record RubricItem(
+ int Points,
+ string Label
+);
+
+public enum SubmissionType
+{
+ online_quiz,
+ none,
+ on_paper,
+ discussion_topic,
+ external_tool,
+ online_upload,
+ online_text_entry,
+ online_url,
+ media_recording,
+ student_annotation,
+}
+
+public record LocalAssignment
+{
+ public string name { get; init; } = "";
+ public string description { get; init; } = "";
+ public bool published { get; init; }
+ public bool lock_at_due_date { get; init; }
+ public IEnumerable rubric { get; init; } = new RubricItem[] { };
+ public DateTime? lock_at { get; init; }
+ public DateTime due_at { get; init; }
+ public int points_possible { get; init; }
+ public IEnumerable submission_types { get; init; } = new SubmissionType[] { };
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index 192ac61..d0da9ee 100644
--- a/README.md
+++ b/README.md
@@ -5,4 +5,14 @@ install specflow template `dotnet new install Specflow.Templates.DotNet`
view templates with `dotnet new -l`
-find outdated packages `dotnet list package --outdated`
\ No newline at end of file
+find outdated packages `dotnet list package --outdated`
+
+
+Development command: `dotnet watch --project Management.Web/`
+
+
+# Razor Hack
+
+Apparently the VSCode razor extension was compiled with a preview of dotnet 6... and only uses openssl 1.1. After installing openssl1.1 you can tell vscode to provide it with `export CLR_OPENSSL_VERSION_OVERRIDE=1.1; code ~/projects/canvasManagement`.
+
+The issue can be tracked [here](https://github.com/dotnet/razor/issues/6241)
\ No newline at end of file