Files
chapter-organizer/WebApp/Components/Features/Calendar/Index.razor
T
poprhythm 680f61241a Refactor Calendar component to simplify view selection UI
This commit removes the Month and Day button group from the Calendar component, streamlining the user interface. The change enhances the overall layout and focuses on the MudCalendar display, improving user experience by reducing visual clutter. This update aligns with the ongoing efforts to refine component interactions and maintain a clean UI.
2026-01-25 21:59:43 -05:00

318 lines
11 KiB
Plaintext

@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<Index> Logger
@inject IDialogService DialogService
<PageHeader Title="Event Calendar" Description="View competition schedules and event occurrences" Icon="@AppIcons.EventCalendar">
<ActionButtons>
<MudTooltip Text="Import">
<MudButton StartIcon="@Icons.Material.Filled.ImportExport" Href="calendar/event-occurrences/import" Variant="Variant.Filled" Color="Color.Primary">Import</MudButton>
</MudTooltip>
<AuthorizeView Roles="@AuthRoles.Administrator">
<MudTooltip Text="Admin">
<MudButton StartIcon="@Icons.Material.Filled.AdminPanelSettings" Href="calendar/admin" Variant="Variant.Outlined" Color="Color.Default">Admin</MudButton>
</MudTooltip>
</AuthorizeView>
<PageNoteButton PageIdentifier="Event Calendar" />
</ActionButtons>
</PageHeader>
<MudPaper Elevation="2" Class="pa-3 pa-md-6">
@if (_calendarItems == null)
{
<MudProgressLinear Indeterminate="true" />
<MudText>Loading calendar events...</MudText>
}
else
{
<MudStack Spacing="2">
<MudCalendar T="CalendarItemWrapper"
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<CalendarItemWrapper>? _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<CalendarItemWrapper> 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<string> 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<EventOccurrenceDetailsDialog>("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<MeetingHistoryDetailDialog>("Meeting Details", parameters, options);
}
}