e53403c934
This commit updates several components to replace traditional HTML layout elements with MudBlazor's MudStack component for improved styling and responsiveness. Changes include the CareerMapping.razor, Index.razor, Registration.razor, TeamStudents.razor, and TeamToggleSelector.razor files. These modifications enhance the visual consistency and maintainability of the UI by adhering to the project's design standards.
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);
|
|
}
|
|
<MudStack Row="true" Spacing="1" Wrap="Wrap.Wrap">
|
|
@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>
|
|
}
|
|
</MudStack>
|
|
</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; } = [];
|
|
}
|
|
}
|