f8c22690d4
This commit introduces two new utility classes: StudentNameFormatter and TeamStudentNameFormatter, which provide methods for formatting student names with options for indicating absence and overlaps. The Team class has been updated to remove the StudentsFirstNames property, and various components across the WebApp have been refactored to utilize the new formatting utilities. This enhances the maintainability and readability of the code while improving the presentation of student names in the UI.
264 lines
9.4 KiB
Plaintext
264 lines
9.4 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 ILogger<Index> Logger
|
|
@inject IDialogService DialogService
|
|
|
|
<PageHeader Title="Event Calendar" Description="View competition schedules and event occurrences" Icon="@AppIcons.EventCalendar">
|
|
<ActionButtons>
|
|
<MudButton StartIcon="@Icons.Material.Filled.ImportExport" Href="calendar/event-occurrences/import" Variant="Variant.Filled" Color="Color.Primary">Import</MudButton>
|
|
<AuthorizeView Roles="@AuthRoles.Administrator">
|
|
<MudButton StartIcon="@Icons.Material.Filled.AdminPanelSettings" Href="calendar/admin" Variant="Variant.Outlined" Color="Color.Default">Admin</MudButton>
|
|
</AuthorizeView>
|
|
</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">
|
|
<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()
|
|
{
|
|
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<CalendarEventItem> items = [];
|
|
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 calendarItem = new CalendarEventItem(occ, studentFirstNames);
|
|
items.Add(calendarItem);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.LogError(ex, "Error creating CalendarEventItem for occurrence Id={Id}, Name={Name}",
|
|
occ?.Id, occ?.Name);
|
|
// Continue processing other items
|
|
}
|
|
}
|
|
|
|
_calendarItems = items;
|
|
Logger.LogInformation("Created {Count} calendar items from {OccurrenceCount} occurrences",
|
|
_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 nextEvent = _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 (nextEvent != null)
|
|
{
|
|
return nextEvent.Start.Date;
|
|
}
|
|
|
|
// Fallback to first event if no future events
|
|
var firstEvent = _calendarItems
|
|
.OrderBy(item => item.Start)
|
|
.FirstOrDefault();
|
|
|
|
return firstEvent != null ? firstEvent.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(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);
|
|
}
|
|
}
|
|
|