@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 @(_showRegionalOnly ? "Showing Regional Only" : "Show Regional Only") = Team Captain Show Columns: @context.Item.Student.LastNameFirstName @if (_showGrade) { } @if (_showRegionalId) { } @if (_showStateId) { } @if (_showNationalId) { } @{ 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) { var isCaptain = team.Captain != null && team.Captain.Equals(context.Item.Student); var teamMembers = string.Join(", ", team.Students.Select(s => s.FirstName)); @team @if (isCaptain && team.Event.EventFormat != EventFormat.Individual) { } } } @code { MudDataGrid _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 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> ServerReload(GridState 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 { TotalItems = totalItems, Items = pagedData }; } finally { _isLoading = false; } } /// /// Dictionary mapping column property names to their corresponding sort expressions. /// Used to provide type-safe sorting for data grid columns. /// /// /// 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. /// private static readonly Dictionary> 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 } }; /// /// Applies sorting to the student team data based on the provided sort definitions. /// /// The list of StudentTeamInfo records to sort. /// Collection of sort definitions from the MudDataGrid state. /// /// An IEnumerable of StudentTeamInfo sorted according to the first sort definition, /// or sorted by LastName if no valid sort definition is provided. /// /// /// 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). /// private IEnumerable ApplySorting( List data, ICollection> 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 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() }; return ValidationService.ValidateStudentStatistics(stats, ValidationContext.StudentRegistration); } public class StudentTeamInfo { public required Student Student { get; init; } public List Teams { get; init; } = []; } }