diff --git a/WebApp/Components/Features/EventCalendar/Index.razor b/WebApp/Components/Features/EventCalendar/Index.razor
new file mode 100644
index 0000000..3fbc7e5
--- /dev/null
+++ b/WebApp/Components/Features/EventCalendar/Index.razor
@@ -0,0 +1,63 @@
+@page "/event-calendar"
+@attribute [Authorize]
+@using WebApp.Components.Shared.Components
+@using WebApp.Models
+@using WebApp.Services
+@using Heron.MudCalendar
+@inject IEventOccurrenceService EventOccurrenceService
+
+
+
+
+ @if (_calendarItems == null)
+ {
+
+ Loading calendar events...
+ }
+ else
+ {
+
+ }
+
+
+@code {
+ private List? _calendarItems;
+ private DateTime _calendarDate = DateTime.Today;
+
+ protected override async Task OnInitializedAsync()
+ {
+ await LoadCalendarEvents();
+ }
+
+ private async Task LoadCalendarEvents()
+ {
+ var occurrences = await EventOccurrenceService.GetEventOccurrencesAsync();
+ _calendarItems = occurrences
+ .Select(occ => new CalendarEventItem(occ))
+ .ToList();
+
+ // Find the next date with events
+ _calendarDate = GetNextDateWithEvents();
+ }
+
+ private DateTime GetNextDateWithEvents()
+ {
+ if (_calendarItems == null || !_calendarItems.Any())
+ {
+ return DateTime.Today;
+ }
+
+ var today = DateTime.Today;
+ var nextEvent = _calendarItems
+ .Where(item => item.Start.Date >= today)
+ .OrderBy(item => item.Start)
+ .FirstOrDefault();
+
+ return nextEvent?.Start.Date ?? _calendarItems.OrderBy(item => item.Start).First().Start.Date;
+ }
+}
+
diff --git a/WebApp/Components/Shared/Layout/NavMenu.razor b/WebApp/Components/Shared/Layout/NavMenu.razor
index 5b4df0a..c4451b2 100644
--- a/WebApp/Components/Shared/Layout/NavMenu.razor
+++ b/WebApp/Components/Shared/Layout/NavMenu.razor
@@ -17,6 +17,7 @@
Handout
Schedule
+ Event Calendar
Event Ranking
Team Assignment
diff --git a/WebApp/Components/_Imports.razor b/WebApp/Components/_Imports.razor
index dae51f2..241d5f5 100644
--- a/WebApp/Components/_Imports.razor
+++ b/WebApp/Components/_Imports.razor
@@ -22,6 +22,7 @@
@using WebApp.Components.Features.Events
@using WebApp.Components.Features.Events.Components
@using WebApp.Components.Features.MeetingSchedule
+@using WebApp.Components.Features.EventCalendar
@using MudBlazor
@using Core.Entities
@using Data
diff --git a/WebApp/Models/CalendarEventItem.cs b/WebApp/Models/CalendarEventItem.cs
new file mode 100644
index 0000000..f7a0aaf
--- /dev/null
+++ b/WebApp/Models/CalendarEventItem.cs
@@ -0,0 +1,55 @@
+using Heron.MudCalendar;
+using MudBlazor;
+
+namespace WebApp.Models;
+
+///
+/// Calendar event item model for Heron.MudCalendar component.
+/// Maps from EventOccurrence entity to calendar event format.
+///
+public class CalendarEventItem : CalendarItem
+{
+ ///
+ /// Gets the original EventOccurrence data.
+ ///
+ public Core.Entities.EventOccurrence? EventOccurrenceData { get; set; }
+
+ ///
+ /// Gets the associated EventDefinition if available.
+ ///
+ public Core.Entities.EventDefinition? EventDefinition { get; set; }
+
+ ///
+ /// Parameterless constructor required by Heron.MudCalendar component.
+ ///
+ public CalendarEventItem()
+ {
+ }
+
+ public CalendarEventItem(Core.Entities.EventOccurrence occurrence, Core.Entities.EventDefinition? eventDefinition = null)
+ {
+ // Set base class properties that the calendar component uses
+ Text = GetEventTitle(occurrence, eventDefinition);
+ Start = occurrence.StartTime;
+ End = occurrence.EndTime ?? occurrence.StartTime.AddHours(1);
+ this.EventOccurrenceData = occurrence;
+ this.EventDefinition = eventDefinition;
+ }
+
+ private static string GetEventTitle(Core.Entities.EventOccurrence occurrence, Core.Entities.EventDefinition? eventDefinition)
+ {
+ var title = occurrence.Name;
+
+ if (eventDefinition != null && !string.IsNullOrEmpty(eventDefinition.Name))
+ {
+ title = $"{eventDefinition.Name} - {title}";
+ }
+
+ if (!string.IsNullOrEmpty(occurrence.Location))
+ {
+ title += $" ({occurrence.Location})";
+ }
+
+ return title;
+ }
+}
diff --git a/WebApp/Program.cs b/WebApp/Program.cs
index 941ad4d..4e10945 100644
--- a/WebApp/Program.cs
+++ b/WebApp/Program.cs
@@ -172,6 +172,7 @@ builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddScoped();
builder.Services.AddScoped();
+builder.Services.AddScoped();
// State container for maintaining state per user connection (Blazor Server)
builder.Services.AddScoped();
diff --git a/WebApp/Services/EventOccurrenceService.cs b/WebApp/Services/EventOccurrenceService.cs
new file mode 100644
index 0000000..16d3c6f
--- /dev/null
+++ b/WebApp/Services/EventOccurrenceService.cs
@@ -0,0 +1,92 @@
+using Core.Entities;
+
+namespace WebApp.Services;
+
+public class EventOccurrenceService : IEventOccurrenceService
+{
+ public Task> GetEventOccurrencesAsync()
+ {
+ // Mock data for demonstration purposes
+ // This will be replaced with database-backed implementation later
+ var mockOccurrences = new List
+ {
+ new EventOccurrence
+ {
+ Name = "Opening Ceremony",
+ Date = "March 15",
+ Time = "8:00 a.m.",
+ StartTime = new DateTime(2026, 3, 15, 8, 0, 0),
+ EndTime = new DateTime(2026, 3, 15, 9, 0, 0),
+ Location = "Main Auditorium"
+ },
+ new EventOccurrence
+ {
+ Name = "Sign-up",
+ Date = "March 15",
+ Time = "9:30 a.m.",
+ StartTime = new DateTime(2026, 3, 15, 9, 30, 0),
+ EndTime = new DateTime(2026, 3, 15, 11, 0, 0),
+ Location = "Registration Hall"
+ },
+ new EventOccurrence
+ {
+ Name = "Judging",
+ Date = "March 15",
+ Time = "1:00 p.m.",
+ StartTime = new DateTime(2026, 3, 15, 13, 0, 0),
+ EndTime = new DateTime(2026, 3, 15, 17, 0, 0),
+ Location = "Exhibition Hall"
+ },
+ new EventOccurrence
+ {
+ Name = "Submit",
+ Date = "March 16",
+ Time = "8:00 a.m.",
+ StartTime = new DateTime(2026, 3, 16, 8, 0, 0),
+ EndTime = new DateTime(2026, 3, 16, 10, 0, 0),
+ Location = "Submission Desk"
+ },
+ new EventOccurrence
+ {
+ Name = "Competition 1",
+ Date = "March 16",
+ Time = "10:30 a.m.",
+ StartTime = new DateTime(2026, 3, 16, 10, 30, 0),
+ EndTime = new DateTime(2026, 3, 16, 12, 0, 0),
+ Location = "Competition Hall"
+ },
+ new EventOccurrence
+ {
+ Name = "Competition 2",
+ Date = "March 16",
+ Time = "10:30 a.m.",
+ StartTime = new DateTime(2026, 3, 16, 11, 30, 0),
+ EndTime = new DateTime(2026, 3, 16, 13, 0, 0),
+ Location = "Competition Hall"
+ },
+ new EventOccurrence
+ {
+ Name = "Awards Ceremony",
+ Date = "March 17",
+ Time = "2:00 p.m.",
+ StartTime = new DateTime(2026, 3, 17, 14, 0, 0),
+ EndTime = new DateTime(2026, 3, 17, 16, 0, 0),
+ Location = "Main Auditorium"
+ }
+ };
+
+ return Task.FromResult>(mockOccurrences);
+ }
+
+ public Task> GetEventOccurrencesForDateRangeAsync(DateTime start, DateTime end)
+ {
+ return GetEventOccurrencesAsync().ContinueWith(task =>
+ {
+ var allOccurrences = task.Result;
+ return allOccurrences.Where(eo =>
+ eo.StartTime.Date >= start.Date &&
+ eo.StartTime.Date <= end.Date);
+ });
+ }
+}
+
diff --git a/WebApp/Services/IEventOccurrenceService.cs b/WebApp/Services/IEventOccurrenceService.cs
new file mode 100644
index 0000000..47a8f31
--- /dev/null
+++ b/WebApp/Services/IEventOccurrenceService.cs
@@ -0,0 +1,17 @@
+using Core.Entities;
+
+namespace WebApp.Services;
+
+public interface IEventOccurrenceService
+{
+ ///
+ /// Gets all event occurrences.
+ ///
+ Task> GetEventOccurrencesAsync();
+
+ ///
+ /// Gets event occurrences within the specified date range.
+ ///
+ Task> GetEventOccurrencesForDateRangeAsync(DateTime start, DateTime end);
+}
+
diff --git a/WebApp/WebApp.csproj b/WebApp/WebApp.csproj
index 08638d1..f938bcf 100644
--- a/WebApp/WebApp.csproj
+++ b/WebApp/WebApp.csproj
@@ -14,6 +14,7 @@
+