@page "/calendar" @attribute [Authorize] @using WebApp.Models @using WebApp.Services @using Heron.MudCalendar @using Microsoft.Extensions.Logging @using WebApp.Authentication @using Core.Utility @inject IEventOccurrenceService EventOccurrenceService @inject ITeamMeetingHistoryService TeamMeetingHistoryService @inject ILogger Logger @inject IDialogService DialogService Import Admin @if (_calendarItems == null) { Loading calendar events... } else { Month Day @* *@
@context.Text
@*
*@
@* *@
@context.Text
@*
*@
@* *@
@context.Text
@*
*@
}
@code { private List? _calendarItems; private DateTime _calendarDate = DateTime.Today; private CalendarView _currentView = CalendarView.Month; protected override async Task OnInitializedAsync() { await LoadCalendarEvents(); } private async Task LoadCalendarEvents() { try { Logger.LogInformation("Loading calendar events"); var occurrences = await EventOccurrenceService.GetEventOccurrencesAsync(); 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); List items = []; // Add event occurrences foreach (var occ in eventOccurrences) { try { if (string.IsNullOrEmpty(occ.Name)) { Logger.LogWarning("Occurrence with Id={Id} has null or empty Name", occ.Id); } // Get student first names for this event definition var studentFirstNames = occ.EventDefinition != null && teamsByEventId.TryGetValue(occ.EventDefinition.Id, out var teams) ? TeamStudentNameFormatter.FormatStudentListForEvent( occ.EventDefinition, teams, new TeamStudentNameFormatter.FormatOptions { CaptainIndicator = TeamStudentNameFormatter.CaptainIndicatorStyle.Star, Ordering = TeamStudentNameFormatter.OrderingStyle.Alphabetical }) : []; var calendarEventItem = new CalendarEventItem(occ, studentFirstNames); items.Add(new CalendarItemWrapper(calendarEventItem)); } catch (Exception ex) { Logger.LogError(ex, "Error creating CalendarEventItem for occurrence Id={Id}, Name={Name}", occ?.Id, occ?.Name); // Continue processing other items } } // Load and add meeting histories try { Logger.LogInformation("Loading meeting histories"); var meetingHistories = await TeamMeetingHistoryService.GetMeetingHistoriesAsync(); foreach (var meetingHistory in meetingHistories) { try { var calendarMeetingItem = new CalendarMeetingItem(meetingHistory); items.Add(new CalendarItemWrapper(calendarMeetingItem)); } catch (Exception ex) { Logger.LogError(ex, "Error creating CalendarMeetingItem for meeting history Id={Id}, Date={Date}", meetingHistory?.Id, meetingHistory?.MeetingDate); // Continue processing other items } } Logger.LogInformation("Added {Count} meeting histories to calendar", meetingHistories.Count()); } catch (Exception ex) { Logger.LogError(ex, "Error loading meeting histories"); // Continue - don't fail the entire calendar load if meetings fail } _calendarItems = items; Logger.LogInformation("Created {Count} calendar items from {OccurrenceCount} occurrences and meetings", _calendarItems.Count, eventOccurrences.Count()); // Find the next date with events _calendarDate = GetNextDateWithEvents(); } catch (Exception ex) { Logger.LogError(ex, "Error loading calendar events"); _calendarItems = []; } finally { StateHasChanged(); } } private DateTime GetNextDateWithEvents() { try { if (_calendarItems == null || !_calendarItems.Any()) { Logger.LogDebug("No calendar items available, returning today's date"); return DateTime.Today; } var today = DateTime.Today; var nextItem = _calendarItems .Where(item => { try { return item.Start.Date >= today; } catch (Exception ex) { Logger.LogWarning(ex, "Error checking item date, skipping item"); return false; } }) .OrderBy(item => item.Start) .FirstOrDefault(); if (nextItem != null) { return nextItem.Start.Date; } // Fallback to first item if no future items var firstItem = _calendarItems .OrderBy(item => item.Start) .FirstOrDefault(); return firstItem != null ? firstItem.Start.Date : DateTime.Today; } catch (Exception ex) { Logger.LogError(ex, "Error in GetNextDateWithEvents"); return DateTime.Today; } } private string GetEventTooltip(CalendarEventItem item) { List parts = []; 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(CalendarItemWrapper wrapper) { if (_calendarItems == null) return; if (wrapper.ItemType == CalendarItemType.Event && wrapper.EventItem != null) { await ShowEventDetails(wrapper.EventItem); } else if (wrapper.ItemType == CalendarItemType.Meeting && wrapper.MeetingItem != null) { await ShowMeetingDetails(wrapper.MeetingItem); } } 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("Event Details", parameters, options); } private async Task ShowMeetingDetails(CalendarMeetingItem item) { if (item.MeetingHistoryData == null) return; var parameters = new DialogParameters { ["MeetingHistoryId"] = item.MeetingHistoryData.Id }; var options = new DialogOptions { CloseOnEscapeKey = true, CloseButton = true, MaxWidth = MaxWidth.Large, FullWidth = true }; await DialogService.ShowAsync("Meeting Details", parameters, options); } }