87db67f979
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.
285 lines
12 KiB
Plaintext
285 lines
12 KiB
Plaintext
@page "/students/teams"
|
|
@attribute [Authorize]
|
|
@using Microsoft.EntityFrameworkCore
|
|
@using WebApp.Models
|
|
@using WebApp.Components.Shared.Components
|
|
@using Core.Validation
|
|
@inject AppDbContext Context
|
|
@inject WebApp.LocalStorageService LocalStorage
|
|
@inject ValidationService ValidationService
|
|
|
|
<PageHeader Title="Registration" Icon="@AppIcons.Registration">
|
|
<ActionButtons>
|
|
<MudButton StartIcon="@Icons.Material.Filled.FilterAlt"
|
|
Variant="@(_showRegionalOnly ? Variant.Filled : Variant.Outlined)"
|
|
Color="Color.Primary"
|
|
OnClick="ToggleRegionalFilter">
|
|
@(_showRegionalOnly ? "Showing Regional Only" : "Show Regional Only")
|
|
</MudButton>
|
|
</ActionButtons>
|
|
</PageHeader>
|
|
|
|
<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>
|
|
|
|
<MudText Typo="Typo.body2" Class="mb-3">
|
|
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Spacing="1">
|
|
<strong>Show Columns:</strong>
|
|
<MudCheckBox Value="_showGrade" ValueChanged="async (bool val) => await OnColumnToggle(nameof(_showGrade), val, v => _showGrade = v)" Label="Grade" Dense="true" Size="Size.Small"/>
|
|
<MudCheckBox Value="_showRegionalId" ValueChanged="async (bool val) => await OnColumnToggle(nameof(_showRegionalId), val, v => _showRegionalId = v)" Label="Regional ID" Dense="true" Size="Size.Small"/>
|
|
<MudCheckBox Value="_showStateId" ValueChanged="async (bool val) => await OnColumnToggle(nameof(_showStateId), val, v => _showStateId = v)" Label="State ID" Dense="true" Size="Size.Small"/>
|
|
<MudCheckBox Value="_showNationalId" ValueChanged="async (bool val) => await OnColumnToggle(nameof(_showNationalId), val, v => _showNationalId = v)" Label="National ID" Dense="true" Size="Size.Small"/>
|
|
</MudStack>
|
|
</MudText>
|
|
|
|
<MudDataGrid T="StudentTeamInfo" ServerData="ServerReload" @ref="_dataGrid" @key="_gridKey" Filterable="true" RowsPerPage="35" Dense="true" Striped="true" Hover="true" Loading="@_isLoading" LoadingProgressColor="Color.Primary">
|
|
<Columns>
|
|
<PropertyColumn Property="@(e => e.Student.LastName)" Title="Student" Sortable="true">
|
|
|
|
<CellTemplate>
|
|
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Spacing="1">
|
|
@context.Item.Student.LastNameFirstName
|
|
<MudTooltip Text="Edit">
|
|
<MudIconButton Icon="@Icons.Material.Filled.Edit"
|
|
Size="Size.Small"
|
|
Href="@($"/students/edit?id={context.Item.Student.Id}&returnUrl=/students/teams")"
|
|
Style="margin-left: 4px;"/>
|
|
</MudTooltip>
|
|
</MudStack>
|
|
</CellTemplate>
|
|
|
|
</PropertyColumn>
|
|
<TemplateColumn Title="Warnings" Sortable="false">
|
|
<CellTemplate>
|
|
<ValidationWarnings Warnings="@GetRegistrationWarnings(context.Item.Student)" />
|
|
</CellTemplate>
|
|
</TemplateColumn>
|
|
@if (_showGrade)
|
|
{
|
|
<PropertyColumn Property="@(e => e.Student.Grade)" Title="Grade" Sortable="true" />
|
|
}
|
|
@if (_showRegionalId)
|
|
{
|
|
<PropertyColumn Property="@(e => e.Student.RegionalId)" Title="Regional ID" Sortable="true" />
|
|
}
|
|
@if (_showStateId)
|
|
{
|
|
<PropertyColumn Property="@(e => e.Student.StateId)" Title="State ID" Sortable="true" />
|
|
}
|
|
@if (_showNationalId)
|
|
{
|
|
<PropertyColumn Property="@(e => e.Student.NationalId)" Title="National ID" Sortable="true" />
|
|
}
|
|
<TemplateColumn Title="Events">
|
|
<CellTemplate>
|
|
@{
|
|
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);
|
|
}
|
|
<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));
|
|
|
|
<MudTooltip Text="@teamMembers">
|
|
<MudChip Size="Size.Small"
|
|
Color="Color.Default"
|
|
Href="@($"/teams/edit?id={team.Id}")"
|
|
Style="cursor: pointer;">
|
|
@team
|
|
@if (isCaptain && team.Event.EventFormat != EventFormat.Individual)
|
|
{
|
|
<MudIcon Icon="@AppIcons.Captain" Size="Size.Small" Style="margin-left: 4px;" />
|
|
}
|
|
</MudChip>
|
|
</MudTooltip>
|
|
}
|
|
</div>
|
|
</CellTemplate>
|
|
</TemplateColumn>
|
|
</Columns>
|
|
<PagerContent>
|
|
<MudDataGridPager T="StudentTeamInfo" PageSizeOptions="new int[] { 10, 25, 35, 50, 100 }"></MudDataGridPager>
|
|
</PagerContent>
|
|
</MudDataGrid>
|
|
</MudPaper>
|
|
|
|
@code {
|
|
MudDataGrid<StudentTeamInfo> _dataGrid = null!;
|
|
private bool _isLoading = true;
|
|
private bool _showRegionalOnly;
|
|
private bool _showGrade;
|
|
private bool _showRegionalId;
|
|
private bool _showStateId;
|
|
private bool _showNationalId;
|
|
private bool _preferencesLoaded = false;
|
|
|
|
// TODO: Remove this workaround once MudBlazor fixes dynamic column ordering
|
|
// https://dev.to/the_real_slim_janey/get-in-line-customizing-column-order-in-mudblazor-3ail
|
|
// To remove:
|
|
// 1. Delete this _gridKey field
|
|
// 2. Remove "_gridKey++;" from all checkbox ValueChanged handlers (lines 26-29)
|
|
// 3. Remove "@key="_gridKey"" attribute from MudDataGrid (line 32)
|
|
private int _gridKey = 0;
|
|
|
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
{
|
|
if (!_preferencesLoaded)
|
|
{
|
|
await LoadColumnPreferences();
|
|
_preferencesLoaded = true;
|
|
_gridKey++; // Force grid recreation with loaded preferences
|
|
StateHasChanged();
|
|
}
|
|
}
|
|
|
|
protected override void OnInitialized()
|
|
{
|
|
// Reset flag when component is initialized/re-initialized
|
|
_preferencesLoaded = false;
|
|
}
|
|
|
|
private async Task LoadColumnPreferences()
|
|
{
|
|
_showGrade = await LocalStorage.GetBoolAsync("Registration_ShowGrade", false);
|
|
_showRegionalId = await LocalStorage.GetBoolAsync("Registration_ShowRegionalId", false);
|
|
_showStateId = await LocalStorage.GetBoolAsync("Registration_ShowStateId", false);
|
|
_showNationalId = await LocalStorage.GetBoolAsync("Registration_ShowNationalId", false);
|
|
}
|
|
|
|
private async Task OnColumnToggle(string columnName, bool value, Action<bool> setter)
|
|
{
|
|
setter(value);
|
|
_gridKey++;
|
|
await LocalStorage.SetBoolAsync($"Registration_{columnName}", value);
|
|
StateHasChanged();
|
|
}
|
|
|
|
private async Task ToggleRegionalFilter()
|
|
{
|
|
_showRegionalOnly = !_showRegionalOnly;
|
|
StateHasChanged();
|
|
await Task.Delay(10);
|
|
await _dataGrid.ReloadServerData();
|
|
}
|
|
|
|
private async Task<GridData<StudentTeamInfo>> ServerReload(GridState<StudentTeamInfo> state)
|
|
{
|
|
_isLoading = true;
|
|
try
|
|
{
|
|
// Load all students with their teams
|
|
var students = await Context.Students
|
|
.Include(s => s.Teams)
|
|
.ThenInclude(t => t.Event)
|
|
.Include(s => s.Teams)
|
|
.ThenInclude(t => t.Captain)
|
|
.ToListAsync();
|
|
|
|
// Filter to only students with teams
|
|
var studentTeams = students
|
|
.Where(s => s.Teams.Any(t => t?.Event != null && (!_showRegionalOnly || t.Event.RegionalEvent)))
|
|
.Select(s => new StudentTeamInfo
|
|
{
|
|
Student = s,
|
|
Teams = s.Teams?.Where(t => t?.Event != null).ToList() ?? []
|
|
})
|
|
.ToList();
|
|
|
|
// Apply sorting
|
|
var sortedData = ApplySorting(studentTeams, state.SortDefinitions);
|
|
|
|
var totalItems = sortedData.Count();
|
|
var pagedData = sortedData.Skip(state.Page * state.PageSize).Take(state.PageSize).ToArray();
|
|
|
|
return new GridData<StudentTeamInfo>
|
|
{
|
|
TotalItems = totalItems,
|
|
Items = pagedData
|
|
};
|
|
}
|
|
finally
|
|
{
|
|
_isLoading = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Dictionary mapping column property names to their corresponding sort expressions.
|
|
/// Used to provide type-safe sorting for data grid columns.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Each entry maps a property path (e.g., "Student.LastName") to a function that
|
|
/// extracts the comparable value from a StudentTeamInfo instance.
|
|
/// Nullable strings are coalesced to empty strings to ensure consistent sorting behavior.
|
|
/// </remarks>
|
|
private static readonly Dictionary<string, Func<StudentTeamInfo, IComparable>> SortExpressions = new()
|
|
{
|
|
{ "Student.LastName", s => s.Student.LastName },
|
|
{ "Student.Grade", s => s.Student.Grade },
|
|
{ "Student.RegionalId", s => s.Student.RegionalId ?? string.Empty },
|
|
{ "Student.StateId", s => s.Student.StateId ?? string.Empty },
|
|
{ "Student.NationalId", s => s.Student.NationalId ?? string.Empty }
|
|
};
|
|
|
|
/// <summary>
|
|
/// Applies sorting to the student team data based on the provided sort definitions.
|
|
/// </summary>
|
|
/// <param name="data">The list of StudentTeamInfo records to sort.</param>
|
|
/// <param name="sortDefinitions">Collection of sort definitions from the MudDataGrid state.</param>
|
|
/// <returns>
|
|
/// An IEnumerable of StudentTeamInfo sorted according to the first sort definition,
|
|
/// or sorted by LastName if no valid sort definition is provided.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// If no sort definitions are provided, or if the requested property is not found in SortExpressions,
|
|
/// the data will default to sorting by Student.LastName in ascending order.
|
|
/// Only the first sort definition is applied (multi-column sorting is not supported).
|
|
/// </remarks>
|
|
private IEnumerable<StudentTeamInfo> ApplySorting(
|
|
List<StudentTeamInfo> data,
|
|
ICollection<SortDefinition<StudentTeamInfo>> sortDefinitions)
|
|
{
|
|
if (!sortDefinitions.Any())
|
|
{
|
|
return data.OrderBy(s => s.Student.LastName);
|
|
}
|
|
|
|
var sortDef = sortDefinitions.First();
|
|
var propertyName = sortDef.SortBy;
|
|
|
|
if (!SortExpressions.TryGetValue(propertyName, out var sortExpression))
|
|
{
|
|
return data.OrderBy(s => s.Student.LastName);
|
|
}
|
|
|
|
return sortDef.Descending
|
|
? data.OrderByDescending(sortExpression)
|
|
: data.OrderBy(sortExpression);
|
|
}
|
|
|
|
private List<ValidationWarning> GetRegistrationWarnings(Student student)
|
|
{
|
|
// Create StudentEventStatistics from the student's teams
|
|
var stats = new StudentEventStatistics
|
|
{
|
|
Student = student,
|
|
Events = student.Teams?.Where(t => t?.Event != null)
|
|
.Select(t => t.Event)
|
|
.ToList() ?? new List<EventDefinition>()
|
|
};
|
|
|
|
return ValidationService.ValidateStudentStatistics(stats, ValidationContext.StudentRegistration);
|
|
}
|
|
|
|
public class StudentTeamInfo
|
|
{
|
|
public required Student Student { get; init; }
|
|
public List<Team> Teams { get; init; } = [];
|
|
}
|
|
}
|