Compare commits

...
2 Commits
Author SHA1 Message Date
poprhythm 87db67f979 Refactor MudPaper component styling across various features for consistency
Updated the MudPaper component styling in multiple files to use a consistent padding class of "pa-3 pa-md-6" instead of "pa-6". This change enhances the visual consistency of the UI across the Calendar, Events, Students, and Teams components, improving the overall user experience.
2026-01-05 14:01:46 -05:00
poprhythm 2aaefb2491 Implement enhanced static file caching and improve calendar event loading with detailed logging
This commit introduces a new static file caching strategy in Program.cs, optimizing cache headers for Blazor assets to improve performance and ensure fresh content after deployments. Additionally, the Calendar component in Index.razor has been updated to include comprehensive logging for event loading, handling null occurrences, and error management during calendar item creation. The CalendarEventItem model is also initialized to prevent null reference issues. These changes enhance the application's reliability and user experience.
2026-01-05 13:21:03 -05:00
25 changed files with 249 additions and 86 deletions
@@ -19,7 +19,7 @@
<MudGrid>
<MudItem xs="12" md="6">
<MudPaper Elevation="2" Class="pa-6">
<MudPaper Elevation="2" Class="pa-3 pa-md-6">
<MudText Typo="Typo.h5" Class="mb-4">Paste Event Occurrence Data</MudText>
<MudStack Spacing="3">
<MudTextField
@@ -53,7 +53,7 @@
</MudItem>
<MudItem xs="12" md="6">
<MudPaper Elevation="2" Class="pa-6">
<MudPaper Elevation="2" Class="pa-3 pa-md-6">
<MudText Typo="Typo.h5" Class="mb-4">Parsed Results</MudText>
@if (_isParsing)
@@ -4,7 +4,9 @@
@using WebApp.Models
@using WebApp.Services
@using Heron.MudCalendar
@using Microsoft.Extensions.Logging
@inject IEventOccurrenceService EventOccurrenceService
@inject ILogger<Index> Logger
<PageHeader Title="Event Calendar" Description="View competition schedules and event occurrences" Icon="@AppIcons.EventCalendar">
<ActionButtons>
@@ -12,7 +14,7 @@
</ActionButtons>
</PageHeader>
<MudPaper Elevation="2" Class="pa-6">
<MudPaper Elevation="2" Class="pa-3 pa-md-6">
@if (_calendarItems == null)
{
<MudProgressLinear Indeterminate="true" />
@@ -23,7 +25,7 @@
<MudCalendar T="CalendarEventItem"
Items="_calendarItems"
View="CalendarView.Day"
CurrentDay=_calendarDate
CurrentDay="@_calendarDate"
/>
}
</MudPaper>
@@ -39,29 +41,115 @@
private async Task LoadCalendarEvents()
{
try
{
Logger.LogInformation("Loading calendar events");
var occurrences = await EventOccurrenceService.GetEventOccurrencesAsync();
_calendarItems = occurrences
.Select(occ => new CalendarEventItem(occ))
.ToList();
if (occurrences == null)
{
Logger.LogWarning("Service returned null occurrences");
_calendarItems = new List<CalendarEventItem>();
return;
}
Logger.LogDebug("Received {Count} occurrences from service", occurrences.Count());
var items = new List<CalendarEventItem>();
foreach (var occ in occurrences)
{
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);
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, occurrences.Count());
// Find the next date with events
_calendarDate = GetNextDateWithEvents();
}
catch (Exception ex)
{
Logger.LogError(ex, "Error loading calendar events");
_calendarItems = new List<CalendarEventItem>();
}
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 => item.Start.Date >= today)
.Where(item =>
{
try
{
return item != null && item.Start.Date >= today;
}
catch (Exception ex)
{
Logger.LogWarning(ex, "Error checking item date, skipping item");
return false;
}
})
.OrderBy(item => item.Start)
.FirstOrDefault();
return nextEvent?.Start.Date ?? _calendarItems.OrderBy(item => item.Start).First().Start.Date;
if (nextEvent != null)
{
return nextEvent.Start.Date;
}
// 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;
}
catch (Exception ex)
{
Logger.LogError(ex, "Error in GetNextDateWithEvents");
return DateTime.Today;
}
}
}
@@ -14,14 +14,14 @@
@if (_isLoading)
{
<MudPaper Elevation="2" Class="pa-6">
<MudPaper Elevation="2" Class="pa-3 pa-md-6">
<MudProgressLinear Indeterminate="true" Color="Color.Primary"/>
<MudText Typo="Typo.body1" Class="mt-4">Loading career mapping data...</MudText>
</MudPaper>
}
else if (_networkData == null || !_networkData.Nodes.Any())
{
<MudPaper Elevation="2" Class="pa-6">
<MudPaper Elevation="2" Class="pa-3 pa-md-6">
<MudText Typo="Typo.h6" Class="mb-4">No Career Field Mappings Found</MudText>
<MudText Typo="Typo.body1" Class="mud-text-secondary">
No events have related careers assigned that match any career fields. Edit events to add related careers.
@@ -30,7 +30,7 @@ else if (_networkData == null || !_networkData.Nodes.Any())
}
else
{
<MudPaper Elevation="2" Class="pa-6">
<MudPaper Elevation="2" Class="pa-3 pa-md-6">
<MudText Typo="Typo.h6" Class="mb-4">Event-Career Field Relationships</MudText>
<MudText Typo="Typo.body2" Class="mb-4 mud-text-secondary">
This diagram shows the connections between events and their related career fields.
@@ -25,7 +25,7 @@
<ValidationErrorDisplay Errors="_validationErrors" />
<MudStack Spacing="4">
<MudPaper Elevation="2" Class="pa-6">
<MudPaper Elevation="2" Class="pa-3 pa-md-6">
<MudText Typo="Typo.h5" Class="mb-4">Basic Information</MudText>
<MudGrid Spacing="3">
<MudItem xs="12" sm="6">
@@ -49,7 +49,7 @@
</MudGrid>
</MudPaper>
<MudPaper Elevation="2" Class="pa-6">
<MudPaper Elevation="2" Class="pa-3 pa-md-6">
<MudText Typo="Typo.h5" Class="mb-4">Event Details</MudText>
<MudGrid Spacing="3">
<MudItem xs="12">
@@ -67,7 +67,7 @@
</MudGrid>
</MudPaper>
<MudPaper Elevation="2" Class="pa-6">
<MudPaper Elevation="2" Class="pa-3 pa-md-6">
<MudText Typo="Typo.h5" Class="mb-4">Team Configuration</MudText>
<MudGrid Spacing="3">
<MudItem xs="12">
@@ -88,7 +88,7 @@
</MudGrid>
</MudPaper>
<MudPaper Elevation="2" Class="pa-6">
<MudPaper Elevation="2" Class="pa-3 pa-md-6">
<MudText Typo="Typo.h5" Class="mb-4">Competition Details</MudText>
<MudGrid Spacing="3">
<MudItem xs="12">
@@ -27,7 +27,7 @@
</ActionButtons>
</PageHeader>
<MudPaper Elevation="2" Class="pa-6">
<MudPaper Elevation="2" Class="pa-3 pa-md-6">
<MudText Typo="Typo.h5" Class="mb-4">Basic Information</MudText>
<MudGrid Spacing="3">
<MudItem xs="12" sm="6" md="4">
+4 -4
View File
@@ -32,7 +32,7 @@
<ValidationErrorDisplay Errors="_validationErrors" />
<MudStack Spacing="4">
<MudPaper Elevation="2" Class="pa-6">
<MudPaper Elevation="2" Class="pa-3 pa-md-6">
<MudText Typo="Typo.h5" Class="mb-4">Basic Information</MudText>
<MudGrid Spacing="3">
<MudItem xs="12" sm="6">
@@ -56,7 +56,7 @@
</MudGrid>
</MudPaper>
<MudPaper Elevation="2" Class="pa-6">
<MudPaper Elevation="2" Class="pa-3 pa-md-6">
<MudText Typo="Typo.h5" Class="mb-4">Event Details</MudText>
<MudGrid Spacing="3">
<MudItem xs="12">
@@ -74,7 +74,7 @@
</MudGrid>
</MudPaper>
<MudPaper Elevation="2" Class="pa-6">
<MudPaper Elevation="2" Class="pa-3 pa-md-6">
<MudText Typo="Typo.h5" Class="mb-4">Team Configuration</MudText>
<MudGrid Spacing="3">
<MudItem xs="12">
@@ -95,7 +95,7 @@
</MudGrid>
</MudPaper>
<MudPaper Elevation="2" Class="pa-6">
<MudPaper Elevation="2" Class="pa-3 pa-md-6">
<MudText Typo="Typo.h5" Class="mb-4">Competition Details</MudText>
<MudGrid Spacing="3">
<MudItem xs="12">
@@ -15,7 +15,7 @@
</ActionButtons>
</PageHeader>
<MudPaper Elevation="2" Class="pa-6">
<MudPaper Elevation="2" Class="pa-3 pa-md-6">
<MudDataGrid T="EventDefinition"
ServerData="ServerReload"
@ref="_dataGrid"
@@ -11,9 +11,9 @@
<PageHeader Title="@($"{Configuration["ChapterSettings:Shortname"]} TSA Schedule {Configuration["ChapterSettings:CompetitionYear"]}")" />
<MudPaper Elevation="2" Class="pa-6 mt-4">
<MudPaper Elevation="2" Class="pa-3 pa-md-6 mt-4">
<MudGrid>
<MudItem xs="7" sm="8" lg="9">
<MudItem xs="12" sm="8" lg="9">
<MudText Typo="Typo.h4">Time Slots</MudText>
<MudPaper Class="pa-2 ma-2" Elevation="3">
<MudGrid>
@@ -81,7 +81,7 @@
</RowTemplate>
</MudTable>
</MudItem>
<MudItem xs="5" sm="4" lg="3">
<MudItem xs="12" sm="4" lg="3">
<MudStack>
<StudentTextBoxSelector Students="@_students"
SelectedStudents="_absentStudents"
@@ -22,7 +22,7 @@
<ValidationErrorDisplay Errors="_validationErrors" />
<MudPaper Elevation="2" Class="pa-6">
<MudPaper Elevation="2" Class="pa-3 pa-md-6">
<MudText Typo="Typo.h5" Class="mb-4">Student Information</MudText>
<MudGrid Spacing="3">
<MudItem xs="12" sm="6">
@@ -26,7 +26,7 @@
</ActionButtons>
</PageHeader>
<MudPaper Elevation="2" Class="pa-6">
<MudPaper Elevation="2" Class="pa-3 pa-md-6">
<MudText Typo="Typo.h5" Class="mb-4">Student Information</MudText>
<MudGrid Spacing="3">
<MudItem xs="12" sm="6" md="4">
@@ -32,7 +32,7 @@
<MudGrid>
<MudItem xs="12" sm="7">
<MudPaper Elevation="2" Class="pa-6">
<MudPaper Elevation="2" Class="pa-3 pa-md-6">
<MudTextField T="string" Label="First Name" @bind-Value="Student.FirstName" For="@(() => Student.FirstName)"></MudTextField>
<MudTextField T="string" Label="Last Name" @bind-Value="Student.LastName" For="@(() => Student.LastName)"></MudTextField>
<MudTextField T="string" Label="Email Adress" @bind-Value="Student.Email" For="@(() => Student.Email)"></MudTextField>
@@ -18,7 +18,7 @@
else
{
<MudStack Spacing="4">
<MudPaper Elevation="2" Class="pa-6">
<MudPaper Elevation="2" Class="pa-3 pa-md-6">
<MudText Typo="Typo.h5" Class="mb-4">Students by Rank</MudText>
<MudTable Items="_students" Hover="true" Striped="true" Breakpoint="Breakpoint.Sm" LoadingProgressColor="Color.Info">
<HeaderContent>
@@ -64,7 +64,7 @@ else
</MudTable>
</MudPaper>
<MudPaper Elevation="2" Class="pa-6">
<MudPaper Elevation="2" Class="pa-3 pa-md-6">
<MudText Typo="Typo.h5" Class="mb-4">Events by Student</MudText>
<MudTable Items="_eventStudentRankings" Hover="true" Striped="true" Breakpoint="Breakpoint.Sm" LoadingProgressColor="Color.Info">
<HeaderContent>
@@ -15,7 +15,7 @@
</ActionButtons>
</PageHeader>
<MudPaper Elevation="2" Class="pa-6">
<MudPaper Elevation="2" Class="pa-3 pa-md-6">
<MudDataGrid T="Student"
ServerData="ServerReload"
@ref="_dataGrid"
@@ -30,7 +30,7 @@
<PropertyColumn Property="@(e => e.LastName)" Title="Name" Sortable="true">
<CellTemplate>
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Spacing="1">
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
<div class="d-flex align-center flex-wrap" style="gap: 0.25rem;">
<MudLink Href="@($"/students/details?id={context.Item.Id}&returnUrl=/students")"
Underline="Underline.Hover"
Color="Color.Primary">
@@ -40,7 +40,7 @@
{
<MudChip T="string" Size="Size.Small" Icon="@(AppIcons.OfficerRoleIcon(context.Item.OfficerRole.Value))">@context.Item.OfficerRole</MudChip>
}
</MudStack>
</div>
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
<IconButtonWithTooltip Icon="@Icons.Material.Filled.Edit"
TooltipText="Edit"
@@ -19,7 +19,7 @@
</ActionButtons>
</PageHeader>
<MudPaper Elevation="2" Class="pa-6">
<MudPaper Elevation="2" Class="pa-3 pa-md-6">
<MudText Typo="Typo.body2" Class="mb-2">
<MudIcon Icon="@AppIcons.Captain" Size="Size.Small" /> = Team Captain
</MudText>
@@ -78,8 +78,9 @@
var teamsToDisplay = _showRegionalOnly
? context.Item.Teams.Where(t => t?.Event is { RegionalEvent: true }).OrderBy(t => t.Event.Name)
: context.Item.Teams.Where(t => t?.Event != null).OrderBy(t => t.Event.Name);
foreach (var team in teamsToDisplay)
}
<div class="d-flex flex-wrap" style="gap: 0.25rem;">
@foreach (var team in teamsToDisplay)
{
var isCaptain = team.Captain != null && team.Captain.Equals(context.Item.Student);
var teamMembers = string.Join(", ", team.Students.Select(s => s.FirstName));
@@ -97,7 +98,7 @@
</MudChip>
</MudTooltip>
}
}
</div>
</CellTemplate>
</TemplateColumn>
</Columns>
@@ -21,7 +21,7 @@
</ActionButtons>
</PageHeader>
<MudPaper Elevation="2" Class="pa-6 mt-4">
<MudPaper Elevation="2" Class="pa-3 pa-md-6 mt-4">
<MudGrid>
<MudItem Style="width:160px;">
<MudNumericField @bind-Value="_parameters.TeamSizeLimit"
@@ -117,7 +117,7 @@
<MudButton Class="ma-3" OnClick="Solve" Variant="Variant.Filled" Color="Color.Primary" Disabled="@_isSolving">Solve</MudButton>
</MudPaper>
<MudPaper Elevation="2" Class="pa-6 mt-4">
<MudPaper Elevation="2" Class="pa-3 pa-md-6 mt-4">
<MudGrid>
<MudItem xs="12" lg="8">
<MudText Typo="Typo.h5" Class="mb-4">Students</MudText>
@@ -1,4 +1,5 @@
@using WebApp.Models
<div class="d-flex flex-wrap" style="gap: 0.25rem;">
@foreach (var student in
Team.Students
.OrderBy(e =>
@@ -20,7 +21,7 @@
Class="mx-1 my-1">
@if (eventRank.HasValue)
{
<span style="@($"display: inline-block; width: 12px; height: 12px; border-radius: 50%; background-color: {color}; margin-right: 6px;")")></span>
<span style="@($"display: inline-block; width: 12px; height: 12px; border-radius: 50%; background-color: {color}; margin-right: 6px;")")"></span>
}
@student.FirstName
@if (captain && Team.Event.EventFormat != EventFormat.Individual)
@@ -30,6 +31,7 @@
</MudChip>
</MudTooltip>
}
</div>
@code {
[Parameter]
@@ -30,7 +30,7 @@
<MudGrid>
<MudItem xs="12" sm="7">
<MudPaper Elevation="2" Class="pa-6">
<MudPaper Elevation="2" Class="pa-3 pa-md-6">
<MudSelect T="EventDefinition" Value="@Team.Event" ValueChanged="OnEventChanged" Label="Event">
@foreach (var evt in _events)
@@ -29,7 +29,7 @@
</ActionButtons>
</PageHeader>
<MudPaper Elevation="2" Class="pa-6">
<MudPaper Elevation="2" Class="pa-3 pa-md-6">
<MudText Typo="Typo.h5" Class="mb-4">Team Information</MudText>
<MudGrid Spacing="3">
<MudItem xs="12" sm="6">
+1 -1
View File
@@ -29,7 +29,7 @@
<MudGrid>
<MudItem xs="12" sm="7">
<MudPaper Elevation="2" Class="pa-6">
<MudPaper Elevation="2" Class="pa-3 pa-md-6">
<StudentToggleSelector Students="@_students"
@bind-SelectedStudents="_selectedStudents"
Title="Students"
+1 -1
View File
@@ -16,7 +16,7 @@
}
else
{
<MudPaper Elevation="2" Class="pa-6">
<MudPaper Elevation="2" Class="pa-3 pa-md-6">
@foreach (var student in _students)
{
<MudContainer Class="pagebreak">
+1 -1
View File
@@ -21,7 +21,7 @@
</ActionButtons>
</PageHeader>
<MudPaper Elevation="2" Class="pa-6">
<MudPaper Elevation="2" Class="pa-3 pa-md-6">
<MudDataGrid T="Team"
ServerData="ServerReload"
@ref="_dataGrid"
@@ -9,7 +9,9 @@
<MudLayout>
<MudAppBar Class="no-print">
<MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start" OnClick="@((e) => DrawerToggle())" />
<MudText Typo="Typo.h6" Class="text-truncate appbar-title">
TSA Chapter Organizer - @Configuration["ChapterSettings:Name"]
</MudText>
<MudSpacer />
<AuthorizeView>
<form action="Auth/CookieLogout" method="post">
+4
View File
@@ -24,6 +24,10 @@ public class CalendarEventItem : CalendarItem
/// </summary>
public CalendarEventItem()
{
// Initialize base class properties to avoid null reference issues
Text = string.Empty;
Start = DateTime.MinValue;
End = DateTime.MinValue;
}
public CalendarEventItem(Core.Entities.EventOccurrence occurrence, Core.Entities.EventDefinition? eventDefinition = null)
+26 -1
View File
@@ -1,5 +1,6 @@
using Data;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.EntityFrameworkCore;
using MudBlazor.Services;
using Serilog;
@@ -268,7 +269,31 @@ app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseStaticFiles();
// Configure static files with proper cache headers for Blazor assets
app.UseStaticFiles(new StaticFileOptions
{
OnPrepareResponse = ctx =>
{
// Blazor framework files: Use ETags with short cache and revalidation
// This allows caching for performance but ensures fresh files after deployments
// Browser will check ETag on each request (304 Not Modified if unchanged)
if (ctx.File.Name.Contains("_framework") ||
ctx.File.Name.Contains("blazor") ||
ctx.File.Name.EndsWith(".dll") ||
ctx.File.Name.EndsWith(".wasm"))
{
// Cache for 12 hours, but must revalidate (check ETag) before using
// If file hasn't changed, browser gets 304 Not Modified (no download)
// If file changed, browser downloads new version
ctx.Context.Response.Headers.Append("Cache-Control", "public, max-age=43200, must-revalidate");
}
else
{
// Other static files (CSS, images, etc.) can be cached long-term
ctx.Context.Response.Headers.Append("Cache-Control", "public, max-age=31536000");
}
}
});
app.UseAntiforgery();
app.MapRazorComponents<App>()
+41
View File
@@ -56,6 +56,19 @@
display: flex;
gap: 0.5rem;
align-items: center;
flex-wrap: wrap;
}
@media (max-width: 600px) {
.page-header-actions {
width: 100%;
margin-top: 0.5rem;
}
.page-header > div > div:first-of-type {
flex-direction: column;
align-items: flex-start !important;
}
}
.form-actions {
@@ -66,6 +79,17 @@
margin-bottom: 1rem;
}
@media (max-width: 600px) {
.form-actions {
flex-direction: column;
width: 100%;
}
.form-actions > * {
width: 100%;
}
}
.icon-button-hover-error:hover {
color: var(--mud-palette-error) !important;
}
@@ -88,3 +112,20 @@
opacity: 1;
}
}
/* Toolbar title responsive styling */
.appbar-title {
max-width: calc(100vw - 150px);
}
@media (max-width: 600px) {
.appbar-title {
font-size: 0.875rem;
max-width: calc(100vw - 120px);
}
/* Reduce base font size on mobile */
body {
font-size: 0.875rem;
}
}