diff --git a/WebApp/Components/Features/Calendar/Index.razor b/WebApp/Components/Features/Calendar/Index.razor index da5fe85..7cfe1bc 100644 --- a/WebApp/Components/Features/Calendar/Index.razor +++ b/WebApp/Components/Features/Calendar/Index.razor @@ -7,6 +7,7 @@ @using WebApp.Authentication @using Core.Utility @inject IEventOccurrenceService EventOccurrenceService +@inject ITeamMeetingHistoryService TeamMeetingHistoryService @inject ILogger Logger @inject IDialogService DialogService @@ -44,7 +45,7 @@ - @code { - private List? _calendarItems; + private List? _calendarItems; private DateTime _calendarDate = DateTime.Today; private CalendarView _currentView = CalendarView.Month; @@ -105,7 +106,9 @@ // Load teams for all event definitions var teamsByEventId = await EventOccurrenceService.GetTeamsByEventDefinitionIdsAsync(eventDefinitionIds); - List items = []; + List items = []; + + // Add event occurrences foreach (var occ in eventOccurrences) { try @@ -127,8 +130,8 @@ }) : []; - var calendarItem = new CalendarEventItem(occ, studentFirstNames); - items.Add(calendarItem); + var calendarEventItem = new CalendarEventItem(occ, studentFirstNames); + items.Add(new CalendarItemWrapper(calendarEventItem)); } catch (Exception ex) { @@ -138,8 +141,37 @@ } } + // 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", + Logger.LogInformation("Created {Count} calendar items from {OccurrenceCount} occurrences and meetings", _calendarItems.Count, eventOccurrences.Count()); // Find the next date with events @@ -168,7 +200,7 @@ } var today = DateTime.Today; - var nextEvent = _calendarItems + var nextItem = _calendarItems .Where(item => { try @@ -184,17 +216,17 @@ .OrderBy(item => item.Start) .FirstOrDefault(); - if (nextEvent != null) + if (nextItem != null) { - return nextEvent.Start.Date; + return nextItem.Start.Date; } - // Fallback to first event if no future events - var firstEvent = _calendarItems + // Fallback to first item if no future items + var firstItem = _calendarItems .OrderBy(item => item.Start) .FirstOrDefault(); - return firstEvent != null ? firstEvent.Start.Date : DateTime.Today; + return firstItem != null ? firstItem.Start.Date : DateTime.Today; } catch (Exception ex) { @@ -235,12 +267,19 @@ return string.Join("\n", parts); } - private async Task OnItemClicked(CalendarEventItem calendarEventItem) + private async Task OnItemClicked(CalendarItemWrapper wrapper) { if (_calendarItems == null) return; - await ShowEventDetails(calendarEventItem); + 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) @@ -264,5 +303,25 @@ 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); + } } diff --git a/WebApp/Models/CalendarItemWrapper.cs b/WebApp/Models/CalendarItemWrapper.cs new file mode 100644 index 0000000..2e9040b --- /dev/null +++ b/WebApp/Models/CalendarItemWrapper.cs @@ -0,0 +1,71 @@ +using Heron.MudCalendar; + +namespace WebApp.Models; + +/// +/// Type discriminator for calendar item types. +/// +public enum CalendarItemType +{ + Event, + Meeting +} + +/// +/// Wrapper class for calendar items that can hold either a CalendarEventItem or CalendarMeetingItem. +/// This allows MudCalendar to display mixed item types while maintaining type safety. +/// +public class CalendarItemWrapper : CalendarItem +{ + /// + /// Gets the type of calendar item this wrapper contains. + /// + public CalendarItemType ItemType { get; private set; } + + /// + /// Gets the wrapped CalendarEventItem if ItemType is Event, otherwise null. + /// + public CalendarEventItem? EventItem { get; private set; } + + /// + /// Gets the wrapped CalendarMeetingItem if ItemType is Meeting, otherwise null. + /// + public CalendarMeetingItem? MeetingItem { get; private set; } + + /// + /// Parameterless constructor required by Heron.MudCalendar component. + /// + public CalendarItemWrapper() + { + // Initialize base class properties to avoid null reference issues + Text = string.Empty; + Start = DateTime.MinValue; + End = DateTime.MinValue; + } + + /// + /// Creates a wrapper for a CalendarEventItem. + /// + public CalendarItemWrapper(CalendarEventItem eventItem) + { + ItemType = CalendarItemType.Event; + EventItem = eventItem; + // Delegate properties to wrapped item + Text = eventItem.Text; + Start = eventItem.Start; + End = eventItem.End; + } + + /// + /// Creates a wrapper for a CalendarMeetingItem. + /// + public CalendarItemWrapper(CalendarMeetingItem meetingItem) + { + ItemType = CalendarItemType.Meeting; + MeetingItem = meetingItem; + // Delegate properties to wrapped item + Text = meetingItem.Text; + Start = meetingItem.Start; + End = meetingItem.End; + } +} diff --git a/WebApp/Models/CalendarMeetingItem.cs b/WebApp/Models/CalendarMeetingItem.cs new file mode 100644 index 0000000..4bd5a30 --- /dev/null +++ b/WebApp/Models/CalendarMeetingItem.cs @@ -0,0 +1,38 @@ +using Core.Entities; +using Heron.MudCalendar; + +namespace WebApp.Models; + +/// +/// Calendar meeting item model for Heron.MudCalendar component. +/// Maps from TeamMeetingHistory entity to calendar event format. +/// +public class CalendarMeetingItem : CalendarItem +{ + /// + /// Gets the original TeamMeetingHistory data. + /// + public TeamMeetingHistory? MeetingHistoryData { get; set; } + + /// + /// Parameterless constructor required by Heron.MudCalendar component. + /// + public CalendarMeetingItem() + { + // Initialize base class properties to avoid null reference issues + Text = string.Empty; + Start = DateTime.MinValue; + End = DateTime.MinValue; + } + + public CalendarMeetingItem(TeamMeetingHistory meetingHistory) + { + MeetingHistoryData = meetingHistory; + // Set base class properties that the calendar component uses + Text = "Team Meeting"; + // Use meeting date at 3:00 PM as default time + Start = meetingHistory.MeetingDate.Date.AddHours(15); + // Default to 1 hour duration + End = Start.AddHours(1); + } +}