Refactor event occurrence parsing to unify section header handling and event count tracking

This commit updates the EventOccurrenceParseResult and EventOccurrenceParserResult classes to consolidate the handling of skipped section headers and event counts into a single set of properties. The previous separate lists and counts for middle school and high school sections have been replaced with a unified approach, improving clarity and maintainability. Additionally, the EventOccurrenceParserService has been modified to reflect these changes, ensuring consistent behavior across the application. This refactor enhances the overall structure of the event parsing logic.
This commit is contained in:
2026-01-10 18:19:16 -05:00
parent b7e812bb63
commit ecd6173a44
11 changed files with 528 additions and 472 deletions
+154 -31
View File
@@ -1,12 +1,13 @@
@page "/calendar"
@attribute [Authorize]
@using WebApp.Components.Shared.Components
@using WebApp.Models
@using WebApp.Services
@using Heron.MudCalendar
@using Microsoft.Extensions.Logging
@using WebApp.Authentication
@inject IEventOccurrenceService EventOccurrenceService
@inject ILogger<Index> Logger
@inject IDialogService DialogService
<PageHeader Title="Event Calendar" Description="View competition schedules and event occurrences" Icon="@AppIcons.EventCalendar">
<ActionButtons>
@@ -25,17 +26,53 @@
}
else
{
<MudCalendar T="CalendarEventItem"
Items="_calendarItems"
View="CalendarView.Day"
CurrentDay="@_calendarDate"
/>
<MudStack Spacing="2">
<MudButtonGroup Variant="Variant.Outlined" Size="Size.Small">
<MudButton Color="@(_currentView == CalendarView.Month ? Color.Primary : Color.Default)"
OnClick="() => { _currentView = CalendarView.Month; StateHasChanged(); }">
Month
</MudButton>
<MudButton Color="@(_currentView == CalendarView.Day ? Color.Primary : Color.Default)"
OnClick="() => { _currentView = CalendarView.Day; StateHasChanged(); }">
Day
</MudButton>
</MudButtonGroup>
<MudCalendar T="CalendarEventItem"
Items="_calendarItems"
View="_currentView"
CurrentDay="@_calendarDate"
Class="event-calendar"
ItemClicked="OnItemClicked">
<MonthTemplate>
@* <MudTooltip Text="@GetEventTooltip(context)"> *@
<div class="d-flex gap-1">
<MudIcon Icon="@Icons.Material.Filled.Circle" Color="Color.Secondary" Size="Size.Small"/>
<div>@context.Text</div>
</div>
@* </MudTooltip> *@
</MonthTemplate>
<WeekTemplate>
@* <MudTooltip Text="@GetEventTooltip(context)"> *@
<div style="width: 100%; height: 100%;">
@context.Text
</div>
@* </MudTooltip> *@
</WeekTemplate>
<DayTemplate>
@* <MudTooltip Text="@GetEventTooltip(context)"> *@
<div>@context.Text</div>
@* </MudTooltip> *@
</DayTemplate>
</MudCalendar>
</MudStack>
}
</MudPaper>
@code {
private List<CalendarEventItem>? _calendarItems;
private DateTime _calendarDate = DateTime.Today;
private CalendarView _currentView = CalendarView.Month;
protected override async Task OnInitializedAsync()
{
@@ -48,33 +85,34 @@
{
Logger.LogInformation("Loading calendar events");
var occurrences = await EventOccurrenceService.GetEventOccurrencesAsync();
if (occurrences == null)
{
Logger.LogWarning("Service returned null occurrences");
_calendarItems = new List<CalendarEventItem>();
return;
}
Logger.LogDebug("Received {Count} occurrences from service", occurrences.Count());
var eventOccurrences = occurrences as EventOccurrence[] ?? occurrences.ToArray();
Logger.LogDebug("Received {Count} occurrences from service", eventOccurrences.Count());
// Get all unique event definition IDs that have occurrences
var eventDefinitionIds = eventOccurrences
.Where(occ => occ?.EventDefinition?.Id != null)
.Select(occ => occ!.EventDefinition!.Id)
.Distinct()
.ToList();
// Load teams for all event definitions
var teamsByEventId = await EventOccurrenceService.GetTeamsByEventDefinitionIdsAsync(eventDefinitionIds);
var items = new List<CalendarEventItem>();
foreach (var occ in occurrences)
foreach (var occ in eventOccurrences)
{
try
{
if (occ == null)
{
Logger.LogWarning("Null occurrence found, skipping");
continue;
}
if (string.IsNullOrEmpty(occ.Name))
{
Logger.LogWarning("Occurrence with Id={Id} has null or empty Name", occ.Id);
}
var calendarItem = new CalendarEventItem(occ, occ.EventDefinition);
// Get student first names for this event definition
var studentFirstNames = StudentFirstNames(occ.EventDefinition, teamsByEventId);
var calendarItem = new CalendarEventItem(occ, studentFirstNames);
items.Add(calendarItem);
}
catch (Exception ex)
@@ -87,7 +125,7 @@
_calendarItems = items;
Logger.LogInformation("Created {Count} calendar items from {OccurrenceCount} occurrences",
_calendarItems.Count, occurrences.Count());
_calendarItems.Count, eventOccurrences.Count());
// Find the next date with events
_calendarDate = GetNextDateWithEvents();
@@ -103,6 +141,35 @@
}
}
private static List<string> StudentFirstNames(EventDefinition ed, Dictionary<int, List<Team>> teamsByEventId)
{
var studentFirstNames = new List<string>();
if (ed?.Id == null || !teamsByEventId.TryGetValue(ed.Id, out var teams)) return studentFirstNames;
// Get all unique student first names from all teams for this event
// Include captain indicator (*) for team events
var allStudents = teams
.SelectMany(t => t.Students)
.DistinctBy(s => s.Id) // Ensure uniqueness by student ID
.Select(s =>
{
var isCaptain = teams.Any(t => t.Captain?.Id == s.Id);
var name = s.FirstName;
// Add star for captain in team events (EventFormat == Team)
if (isCaptain && ed.EventFormat == Core.Entities.EventFormat.Team)
{
name += "*";
}
return name;
})
.OrderBy(name => name)
.ToList();
studentFirstNames = allStudents;
return studentFirstNames;
}
private DateTime GetNextDateWithEvents()
{
try
@@ -119,7 +186,7 @@
{
try
{
return item != null && item.Start.Date >= today;
return item.Start.Date >= today;
}
catch (Exception ex)
{
@@ -137,16 +204,10 @@
// Fallback to first event if no future events
var firstEvent = _calendarItems
.Where(item => item != null)
.OrderBy(item => item.Start)
.FirstOrDefault();
if (firstEvent != null)
{
return firstEvent.Start.Date;
}
return DateTime.Today;
return firstEvent != null ? firstEvent.Start.Date : DateTime.Today;
}
catch (Exception ex)
{
@@ -154,5 +215,67 @@
return DateTime.Today;
}
}
private string GetEventTooltip(CalendarEventItem item)
{
var parts = new List<string>();
if (!string.IsNullOrEmpty(item.EventDefinition?.Name))
{
parts.Add($"Event: {item.EventDefinition.Name}");
}
if (!string.IsNullOrEmpty(item.EventOccurrenceData?.Name))
{
parts.Add($"Occurrence: {item.EventOccurrenceData.Name}");
}
if (!string.IsNullOrEmpty(item.EventOccurrenceData?.Location))
{
parts.Add($"Location: {item.EventOccurrenceData.Location}");
}
if (item.EventOccurrenceData?.StartTime != null)
{
parts.Add($"Time: {item.EventOccurrenceData.StartTime:g}");
}
if (item.StudentFirstNames.Any())
{
parts.Add($"Students: {string.Join(", ", item.StudentFirstNames)}");
}
return string.Join("\n", parts);
}
private async Task OnItemClicked(CalendarEventItem calendarEventItem)
{
if (_calendarItems == null)
return;
await ShowEventDetails(calendarEventItem);
}
private async Task ShowEventDetails(CalendarEventItem item)
{
if (item.EventOccurrenceData == null) return;
var parameters = new DialogParameters
{
["EventOccurrence"] = item.EventOccurrenceData,
["EventDefinition"] = item.EventDefinition,
["StudentFirstNames"] = item.StudentFirstNames
};
var options = new DialogOptions
{
CloseOnEscapeKey = true,
CloseButton = true,
MaxWidth = MaxWidth.Medium,
FullWidth = true
};
await DialogService.ShowAsync<EventOccurrenceDetailsDialog>("Event Details", parameters, options);
}
}