Feature-based folder structure
1. Created feature-based folder structure - Components now organized by domain feature 2. Moved all components - 20+ files moved to new locations 3. Updated _Imports.razor - Added all new namespace paths for global component access 4. Updated CustomThemes.cs namespace - Changed from WebApp.Components.Layout to WebApp.Components.Shared.Layout 5. Removed old using directives - Cleaned up Login.razor and Routes.razor 6. Removed empty directories - Cleaned up old folder structure
This commit is contained in:
@@ -1,62 +0,0 @@
|
||||
@page "/events/create"
|
||||
@attribute [Authorize]
|
||||
@inject AppDbContext context
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<PageTitle>Create Event - TSA Chapter Organizer</PageTitle>
|
||||
|
||||
<MudText Typo="Typo.h3">Create</MudText>
|
||||
<MudText Typo="Typo.h4">Event</MudText>
|
||||
<MudDivider />
|
||||
|
||||
|
||||
<EditForm Model="EventDefinition" OnValidSubmit="OnValidSubmit" Enhance>
|
||||
<AntiforgeryToken />
|
||||
<DataAnnotationsValidator />
|
||||
<MudGrid>
|
||||
<MudItem xs="12" sm="7">
|
||||
<MudPaper Class="pa-4">
|
||||
<MudTextField T="string" Label="Event Name" @bind-Value="EventDefinition.Name" For="@(() => EventDefinition.Name)"></MudTextField>
|
||||
<MudTextField T="string" Label="Short Name" @bind-Value="EventDefinition.ShortName" For="@(() => EventDefinition.ShortName)"></MudTextField>
|
||||
|
||||
<MudText Typo="Typo.subtitle2" Class="mt-4 mb-2">Format</MudText>
|
||||
<MudRadioGroup T="EventFormat" @bind-Value="@EventDefinition.EventFormat" For="@(() => EventDefinition.EventFormat)">
|
||||
@foreach (EventFormat format in Enum.GetValues(typeof(EventFormat)))
|
||||
{
|
||||
<MudRadio T="EventFormat" Value="@format">@format.ToString()</MudRadio>
|
||||
}
|
||||
</MudRadioGroup>
|
||||
|
||||
<MudTextField T="string" Label="Description" AutoGrow="true" @bind-Value="EventDefinition.Description" For="@(() => EventDefinition.Description)" Class="mt-4"></MudTextField>
|
||||
<MudTextField T="string" Label="Theme" AutoGrow="true" @bind-Value="EventDefinition.Theme" For="@(() => EventDefinition.Theme)"></MudTextField>
|
||||
<MudTextField T="string" Label="Documentation" @bind-Value="EventDefinition.Documentation" For="@(() => EventDefinition.Documentation)"></MudTextField>
|
||||
<MudNumericField T="int?" Label="Level of Effort" @bind-Value="EventDefinition.LevelOfEffort" For="@(() => EventDefinition.LevelOfEffort)"></MudNumericField>
|
||||
<MudDivider></MudDivider>
|
||||
<MudTextField T="string" Label="Nationals Eligibility" @bind-Value="EventDefinition.Eligibility" For="@(() => EventDefinition.Eligibility)"></MudTextField>
|
||||
<MudNumericField T="int" Label="Minimum Team Size" @bind-Value="EventDefinition.MinTeamSize" For="@(() => EventDefinition.MinTeamSize)"></MudNumericField>
|
||||
<MudNumericField T="int" Label="Maxiumum Team Size" @bind-Value="EventDefinition.MaxTeamSize" For="@(() => EventDefinition.MaxTeamSize)"></MudNumericField>
|
||||
<MudNumericField T="int" Label="Team Count at Regionals" @bind-Value="EventDefinition.ChapterEligibilityCountRegionals" For="@(() => EventDefinition.ChapterEligibilityCountRegionals)"></MudNumericField>
|
||||
<MudNumericField T="int" Label="Team Count at State" @bind-Value="EventDefinition.ChapterEligibilityCountState" For="@(() => EventDefinition.ChapterEligibilityCountState)"></MudNumericField>
|
||||
<MudDivider></MudDivider>
|
||||
<MudTextField T="string" Label="Semifinalist Activity" @bind-Value="EventDefinition.SemifinalistActivity" For="@(() => EventDefinition.SemifinalistActivity)"></MudTextField>
|
||||
<MudCheckBox T="bool" Label="On Site Activity" @bind-Value="EventDefinition.OnSiteActivity" For="@(() => EventDefinition.OnSiteActivity)"></MudCheckBox>
|
||||
<MudCheckBox T="bool" Label="Requires Presubmission" @bind-Value="EventDefinition.Presubmission" For="@(() => EventDefinition.Presubmission)"></MudCheckBox>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
<MudButton StartIcon="@Icons.Material.Filled.ArrowBack" Href="events">Back</MudButton>
|
||||
<MudButton ButtonType="ButtonType.Submit" StartIcon="@Icons.Material.Filled.Save">Save</MudButton>
|
||||
</EditForm>
|
||||
|
||||
|
||||
@code {
|
||||
[SupplyParameterFromForm]
|
||||
private EventDefinition EventDefinition { get; set; } = new();
|
||||
|
||||
private void OnValidSubmit()
|
||||
{
|
||||
context.Events.Add(EventDefinition);
|
||||
context.SaveChanges();
|
||||
NavigationManager.NavigateTo("/events");
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
@page "/events/details"
|
||||
@attribute [Authorize]
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@inject AppDbContext context
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<PageTitle>Event Details - TSA Chapter Organizer</PageTitle>
|
||||
|
||||
<MudText Typo="Typo.h3">Details</MudText>
|
||||
<MudText Typo="Typo.h4">Event Definition</MudText>
|
||||
<MudDivider />
|
||||
|
||||
@if (eventdefinition is null)
|
||||
{
|
||||
<MudText><em>Loading...</em></MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudPaper Class="pa-4 mt-4">
|
||||
<MudGrid>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudText Typo="Typo.subtitle2">Name</MudText>
|
||||
<MudText>@eventdefinition.Name</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudText Typo="Typo.subtitle2">Short Name</MudText>
|
||||
<MudText>@eventdefinition.ShortName</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudText Typo="Typo.subtitle2">Event Format</MudText>
|
||||
<MudText>@eventdefinition.EventFormat</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudText Typo="Typo.subtitle2">Min Team Size</MudText>
|
||||
<MudText>@eventdefinition.MinTeamSize</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudText Typo="Typo.subtitle2">Max Team Size</MudText>
|
||||
<MudText>@eventdefinition.MaxTeamSize</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudText Typo="Typo.subtitle2">Chapter Eligibility Count (State)</MudText>
|
||||
<MudText>@eventdefinition.ChapterEligibilityCountState</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudText Typo="Typo.subtitle2">Regional Event</MudText>
|
||||
<MudText>@eventdefinition.RegionalEvent</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudText Typo="Typo.subtitle2">Presubmission Required</MudText>
|
||||
<MudText>@eventdefinition.Presubmission</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudText Typo="Typo.subtitle2">Level of Effort</MudText>
|
||||
<MudText>@eventdefinition.LevelOfEffort</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudText Typo="Typo.subtitle2">Semifinalist Activity</MudText>
|
||||
<MudText>@eventdefinition.SemifinalistActivity</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudText Typo="Typo.subtitle2">Notes</MudText>
|
||||
<MudText>@eventdefinition.Notes</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudText Typo="Typo.subtitle2">Documentation</MudText>
|
||||
<MudText>@eventdefinition.Documentation</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudText Typo="Typo.subtitle2">Eligibility</MudText>
|
||||
<MudText>@eventdefinition.Eligibility</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudText Typo="Typo.subtitle2">Theme</MudText>
|
||||
<MudText>@eventdefinition.Theme</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudText Typo="Typo.subtitle2">Description</MudText>
|
||||
<MudText>@eventdefinition.Description</MudText>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudPaper>
|
||||
|
||||
<div class="mt-4">
|
||||
<MudButton StartIcon="@Icons.Material.Filled.Edit" Href="@($"/events/edit?id={eventdefinition.Id}")" Variant="Variant.Filled" Color="Color.Primary">Edit</MudButton>
|
||||
<MudButton StartIcon="@Icons.Material.Filled.ArrowBack" Href="/events" Variant="Variant.Text">Back to List</MudButton>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
private EventDefinition? eventdefinition;
|
||||
|
||||
[SupplyParameterFromQuery]
|
||||
private int Id { get; set; }
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
eventdefinition = await context.Events.FirstOrDefaultAsync(m => m.Id == Id);
|
||||
|
||||
if (eventdefinition is null)
|
||||
{
|
||||
NavigationManager.NavigateTo("notfound");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
@page "/events/edit"
|
||||
@attribute [Authorize]
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@inject AppDbContext context
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<PageTitle>Edit Event - TSA Chapter Organizer</PageTitle>
|
||||
|
||||
<MudText Typo="Typo.h3">Edit</MudText>
|
||||
<MudText Typo="Typo.h4">Event</MudText>
|
||||
<MudDivider />
|
||||
|
||||
|
||||
<EditForm Model="EventDefinition" OnValidSubmit="OnValidSubmit" Enhance>
|
||||
<AntiforgeryToken />
|
||||
<DataAnnotationsValidator />
|
||||
<MudGrid>
|
||||
<MudItem xs="12" sm="7">
|
||||
<MudPaper Class="pa-4">
|
||||
<MudTextField T="string" Label="Event Name" @bind-Value="EventDefinition.Name" For="@(() => EventDefinition.Name)"></MudTextField>
|
||||
<MudTextField T="string" Label="Short Name" @bind-Value="EventDefinition.ShortName" For="@(() => EventDefinition.ShortName)"></MudTextField>
|
||||
|
||||
<MudText Typo="Typo.subtitle2" Class="mt-4 mb-2">Format</MudText>
|
||||
<MudRadioGroup T="EventFormat" @bind-Value="@EventDefinition.EventFormat" For="@(() => EventDefinition.EventFormat)">
|
||||
@foreach (EventFormat format in Enum.GetValues(typeof(EventFormat)))
|
||||
{
|
||||
<MudRadio T="EventFormat" Value="@format">@format.ToString()</MudRadio>
|
||||
}
|
||||
</MudRadioGroup>
|
||||
|
||||
<MudTextField T="string" Label="Description" AutoGrow="true" @bind-Value="EventDefinition.Description" For="@(() => EventDefinition.Description)" Class="mt-4"></MudTextField>
|
||||
<MudTextField T="string" Label="Theme" AutoGrow="true" @bind-Value="EventDefinition.Theme" For="@(() => EventDefinition.Theme)"></MudTextField>
|
||||
<MudTextField T="string" Label="Documentation" @bind-Value="EventDefinition.Documentation" For="@(() => EventDefinition.Documentation)"></MudTextField>
|
||||
<MudNumericField T="int?" Label="Level of Effort" @bind-Value="EventDefinition.LevelOfEffort" For="@(() => EventDefinition.LevelOfEffort)"></MudNumericField>
|
||||
<MudDivider></MudDivider>
|
||||
<MudTextField T="string" Label="Nationals Eligibility" @bind-Value="EventDefinition.Eligibility" For="@(() => EventDefinition.Eligibility)"></MudTextField>
|
||||
<MudNumericField T="int" Label="Minimum Team Size" @bind-Value="EventDefinition.MinTeamSize" For="@(() => EventDefinition.MinTeamSize)"></MudNumericField>
|
||||
<MudNumericField T="int" Label="Maxiumum Team Size" @bind-Value="EventDefinition.MaxTeamSize" For="@(() => EventDefinition.MaxTeamSize)"></MudNumericField>
|
||||
<MudNumericField T="int" Label="Team Count at Regionals" @bind-Value="EventDefinition.ChapterEligibilityCountRegionals" For="@(() => EventDefinition.ChapterEligibilityCountRegionals)"></MudNumericField>
|
||||
<MudNumericField T="int" Label="Team Count at State" @bind-Value="EventDefinition.ChapterEligibilityCountState" For="@(() => EventDefinition.ChapterEligibilityCountState)"></MudNumericField>
|
||||
<MudDivider></MudDivider>
|
||||
<MudTextField T="string" Label="Semifinalist Activity" @bind-Value="EventDefinition.SemifinalistActivity" For="@(() => EventDefinition.SemifinalistActivity)"></MudTextField>
|
||||
<MudCheckBox T="bool" Label="On Site Activity" @bind-Value="EventDefinition.OnSiteActivity" For="@(() => EventDefinition.OnSiteActivity)"></MudCheckBox>
|
||||
<MudCheckBox T="bool" Label="Requires Presubmission" @bind-Value="EventDefinition.Presubmission" For="@(() => EventDefinition.Presubmission)"></MudCheckBox>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
<MudButton StartIcon="@Icons.Material.Filled.ArrowBack" Href="events">Back</MudButton>
|
||||
<MudButton ButtonType="ButtonType.Submit" StartIcon="@Icons.Material.Filled.Save">Save</MudButton>
|
||||
</EditForm>
|
||||
|
||||
|
||||
@code {
|
||||
[SupplyParameterFromQuery]
|
||||
private int Id { get; set; }
|
||||
|
||||
[SupplyParameterFromForm]
|
||||
private EventDefinition? EventDefinition { get; set; }
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
EventDefinition ??= await context.Events.FirstOrDefaultAsync(m => m.Id == Id);
|
||||
|
||||
if (EventDefinition is null)
|
||||
{
|
||||
NavigationManager.NavigateTo("notfound");
|
||||
}
|
||||
}
|
||||
|
||||
// To protect from overposting attacks, enable the specific properties you want to bind to.
|
||||
// For more information, see https://learn.microsoft.com/aspnet/core/blazor/forms/#mitigate-overposting-attacks.
|
||||
private void OnValidSubmit()
|
||||
{
|
||||
context.Attach(EventDefinition!).State = EntityState.Modified;
|
||||
|
||||
try
|
||||
{
|
||||
context.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
if (!EventDefinitionExists(EventDefinition!.Id))
|
||||
{
|
||||
NavigationManager.NavigateTo("notfound");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
NavigationManager.NavigateTo("/events");
|
||||
}
|
||||
|
||||
private bool EventDefinitionExists(int id)
|
||||
{
|
||||
return context.Events.Any(e => e.Id == id);
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
@page "/events"
|
||||
@attribute [Authorize]
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using WebApp.Models
|
||||
@inject AppDbContext Context
|
||||
@inject IDialogService DialogService
|
||||
@inject ISnackbar Snackbar
|
||||
|
||||
<PageTitle>Events - TSA Chapter Organizer</PageTitle>
|
||||
|
||||
<MudText Typo="Typo.h3">Events</MudText>
|
||||
|
||||
<MudButton StartIcon="@Icons.Material.Filled.Create" Href="events/create">Create New</MudButton>
|
||||
<MudButton StartIcon="@Icons.Material.Filled.Print" Href="events/printout">Printable Descriptions</MudButton>
|
||||
|
||||
<MudDataGrid T="EventDefinition" ServerData="ServerReload" @ref="_dataGrid" Filterable="true" RowsPerPage="50" >
|
||||
<Columns>
|
||||
<PropertyColumn Property="@(e => e.Name)" Title="Event Name" Sortable="true" />
|
||||
<PropertyColumn Property="@(e => e.EventFormat)" Title="Event Format" />
|
||||
|
||||
<TemplateColumn Title="Team Size" CellStyle="white-space:nowrap">
|
||||
<CellTemplate>
|
||||
<MudTooltip Text="@context.Item.Eligibility">
|
||||
[@context.Item.MinTeamSize - @context.Item.MaxTeamSize]
|
||||
</MudTooltip>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<PropertyColumn Property="@(e => e.ChapterEligibilityCountState)" Title="State#" />
|
||||
<TemplateColumn Title="Attributes" Sortable="false">
|
||||
<CellTemplate>
|
||||
<EventAttributes EventDefinition="context.Item"></EventAttributes>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
|
||||
<PropertyColumn Property="@(e => e.LevelOfEffort)" Title="Level of Effort" />
|
||||
<TemplateColumn>
|
||||
<CellTemplate>
|
||||
<CrudActions DetailsHref="@($"/events/details?id={context.Item!.Id}")"
|
||||
EditHref="@($"/events/edit?id={context.Item!.Id}")"
|
||||
DeleteOnClick="() => DeleteEventDefinition(context.Item!)">
|
||||
</CrudActions>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
</Columns>
|
||||
<PagerContent>
|
||||
<MudDataGridPager T="EventDefinition"></MudDataGridPager>
|
||||
</PagerContent>
|
||||
</MudDataGrid>
|
||||
|
||||
@code {
|
||||
MudDataGrid<EventDefinition> _dataGrid = null!;
|
||||
|
||||
private async Task<GridData<EventDefinition>> ServerReload(GridState<EventDefinition> state)
|
||||
{
|
||||
|
||||
var query = Context.Events.OrderBy(e => e.Name).Where(state.FilterDefinitions).OrderBy(state.SortDefinitions);
|
||||
|
||||
var totalItems = await query.CountAsync();
|
||||
var pagedData = await query.Skip(state.Page * state.PageSize).Take(state.PageSize).ToArrayAsync();
|
||||
|
||||
return new GridData<EventDefinition>
|
||||
{
|
||||
TotalItems = totalItems,
|
||||
Items = pagedData
|
||||
};
|
||||
}
|
||||
|
||||
private async Task DeleteEventDefinition(EventDefinition evt)
|
||||
{
|
||||
//_isRowBlocked = true;
|
||||
|
||||
var result = await DialogService
|
||||
.ShowMessageBox("Delete Event",
|
||||
(MarkupString)$"Are you sure want to delete <b>{evt.Name}</b>? This cannot be undone.",
|
||||
yesText:"Yes",
|
||||
noText:"Cancel");
|
||||
|
||||
if (result == true)
|
||||
{
|
||||
Context.Events.Remove(evt!);
|
||||
await Context.SaveChangesAsync();
|
||||
Snackbar.Add($"Delete event: Delete of Event {evt.Name}", Severity.Info);
|
||||
}
|
||||
|
||||
//_isRowBlocked = false;
|
||||
StateHasChanged();
|
||||
await _dataGrid.ReloadServerData();
|
||||
}
|
||||
}
|
||||
@@ -1,179 +0,0 @@
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@attribute [Authorize]
|
||||
@page "/events/printout"
|
||||
@inject IConfiguration Configuration
|
||||
@inject AppDbContext Context
|
||||
|
||||
<PageTitle>TSA Events @Configuration["ChapterSettings:CompetitionYear"]</PageTitle>
|
||||
|
||||
<MudText Typo="Typo.h3">TSA Events @Configuration["ChapterSettings:CompetitionYear"]</MudText>
|
||||
<MudText Typo="Typo.h5" Class="mb-4">Yearly theme: Unity Through Community</MudText>
|
||||
|
||||
@if (_events == null)
|
||||
{
|
||||
<p><em>Loading...</em></p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudContainer>
|
||||
@foreach (var evt in _events)
|
||||
{
|
||||
<MudContainer Class="mt-3 mb-1 nobrk">
|
||||
<MudGrid>
|
||||
<MudItem xs="4">
|
||||
<MudStack>
|
||||
<MudItem>
|
||||
<MudText Class="d-flex py-1" Typo="Typo.h5">@evt.Name</MudText>
|
||||
</MudItem>
|
||||
@if (evt.RegionalEvent)
|
||||
{
|
||||
<MudItem>
|
||||
<MudText Class="d-flex" Typo="Typo.caption"><i>Regional Event</i></MudText>
|
||||
</MudItem>
|
||||
}
|
||||
</MudStack>
|
||||
</MudItem>
|
||||
<MudItem xs="2">
|
||||
|
||||
<MudText>
|
||||
@if (evt.EventFormat is EventFormat.Team)
|
||||
{
|
||||
<strong>@evt.EventFormat</strong>
|
||||
<br/>
|
||||
<p>Size: <strong>@evt.TeamSize</strong></p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<strong>@evt.EventFormat</strong>
|
||||
}
|
||||
</MudText>
|
||||
|
||||
</MudItem>
|
||||
<MudItem xs="3">
|
||||
Eligibility: @evt.Eligibility
|
||||
</MudItem>
|
||||
<MudItem xs="1">
|
||||
<strong> Effort</strong>: @evt.LevelOfEffort
|
||||
</MudItem>
|
||||
<MudItem xs="2">
|
||||
<strong>Activity</strong>: @evt.SemifinalistActivity
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12">
|
||||
<MudText Class="d-flex py-1" Style="white-space:pre-wrap;">@evt.Description</MudText>
|
||||
</MudItem>
|
||||
@if (!string.IsNullOrEmpty(evt.Theme))
|
||||
{
|
||||
<MudItem xs="3">
|
||||
<MudText Class="d-flex py-1">
|
||||
<i>Theme for 2025-26:</i>
|
||||
</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="8">
|
||||
<MudText Class="d-flex py-1" Style="white-space:pre-wrap;">@evt.Theme</MudText>
|
||||
</MudItem>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrEmpty(evt.Documentation))
|
||||
{
|
||||
<MudItem xs="3">
|
||||
<MudText Class="d-flex py-1">
|
||||
<i>Materials:</i>
|
||||
</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="8">
|
||||
<MudText Class="d-flex py-1" Style="white-space:pre-wrap;">@evt.Documentation</MudText>
|
||||
</MudItem>
|
||||
}
|
||||
|
||||
</MudGrid>
|
||||
</MudContainer>
|
||||
<MudDivider />
|
||||
}
|
||||
</MudContainer>
|
||||
|
||||
<MudContainer>
|
||||
@foreach (var evt in _events)
|
||||
{
|
||||
<MudContainer Class="mt-3 mb-1 nobrk">
|
||||
<MudGrid>
|
||||
<MudItem xs="4">
|
||||
<MudStack>
|
||||
<MudItem>
|
||||
<MudText Class="d-flex py-1" Typo="Typo.h5">@evt.Name</MudText>
|
||||
</MudItem>
|
||||
@if (evt.RegionalEvent)
|
||||
{
|
||||
<MudItem>
|
||||
<MudText Class="d-flex" Typo="Typo.caption"><i>Regional Event</i></MudText>
|
||||
</MudItem>
|
||||
}
|
||||
</MudStack>
|
||||
</MudItem>
|
||||
<MudItem xs="2">
|
||||
|
||||
<MudText>
|
||||
@if (evt.EventFormat is EventFormat.Team)
|
||||
{
|
||||
<strong>@evt.EventFormat</strong>
|
||||
<br />
|
||||
<p>Size: <strong>@evt.TeamSize</strong></p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<strong>@evt.EventFormat</strong>
|
||||
}
|
||||
</MudText>
|
||||
|
||||
</MudItem>
|
||||
<MudItem xs="3">
|
||||
Eligibility: @evt.Eligibility
|
||||
</MudItem>
|
||||
<MudItem xs="1">
|
||||
<strong> Effort</strong>: @evt.LevelOfEffort
|
||||
</MudItem>
|
||||
<MudItem xs="2">
|
||||
<strong>Activity</strong>: @evt.SemifinalistActivity
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12">
|
||||
<MudText Class="d-flex py-1" Style="white-space:pre-wrap;">@evt.Description</MudText>
|
||||
</MudItem>
|
||||
@if (!string.IsNullOrEmpty(evt.Theme))
|
||||
{
|
||||
<MudItem xs="3">
|
||||
<MudText Class="d-flex py-1">
|
||||
<i>Theme for 2025-26:</i>
|
||||
</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="8">
|
||||
<MudText Class="d-flex py-1" Style="white-space:pre-wrap;">@evt.Theme</MudText>
|
||||
</MudItem>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrEmpty(evt.Documentation))
|
||||
{
|
||||
<MudItem xs="3">
|
||||
<MudText Class="d-flex py-1">
|
||||
<i>Materials:</i>
|
||||
</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="8">
|
||||
<MudText Class="d-flex py-1" Style="white-space:pre-wrap;">@evt.Documentation</MudText>
|
||||
</MudItem>
|
||||
}
|
||||
|
||||
</MudGrid>
|
||||
</MudContainer>
|
||||
<MudDivider />
|
||||
}
|
||||
</MudContainer>
|
||||
}
|
||||
@code {
|
||||
private EventDefinition[]? _events;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_events = await Context.Events.OrderBy(e => e.Name).ToArrayAsync();
|
||||
}
|
||||
}
|
||||
@@ -1,340 +0,0 @@
|
||||
@page "/meeting-schedule"
|
||||
@attribute [Authorize]
|
||||
@using System.Text
|
||||
@using Core.Calculation
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@inject IConfiguration Configuration
|
||||
@inject AppDbContext Context
|
||||
@inject ClipboardService ClipboardService
|
||||
|
||||
<PageTitle>@Configuration["ChapterSettings:Shortname"] TSA Schedule @Configuration["ChapterSettings:CompetitionYear"]</PageTitle>
|
||||
|
||||
<MudText Typo="Typo.h3">@Configuration["ChapterSettings:Shortname"] TSA Schedule @Configuration["ChapterSettings:CompetitionYear"]</MudText>
|
||||
|
||||
<MudPaper Class="pa-4 mt-5">
|
||||
<MudGrid>
|
||||
<MudItem xs="7" sm="8" lg="9">
|
||||
<MudText Typo="Typo.h4">Time Slots</MudText>
|
||||
<MudPaper Class="pa-2 ma-2" Elevation="3">
|
||||
<MudGrid>
|
||||
<MudItem xs="6" sm="3" lg="2">
|
||||
<MudNumericField @bind-Value="_parameters.TimeSlots"
|
||||
Label="Time Slots" Min="1" Max="4">
|
||||
</MudNumericField>
|
||||
</MudItem>
|
||||
<MudFlexBreak/>
|
||||
<MudItem xs="12" sm="6" lg="4">
|
||||
<MudTooltip Text="Schedule teams with Level of Effort >= 3" Inline="false">
|
||||
<MudButton Variant="Variant.Outlined" OnClick="AddHighLevelOfEffort" FullWidth="true">Add High Effort</MudButton>
|
||||
</MudTooltip>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" lg="4">
|
||||
<MudButton Variant="Variant.Outlined" OnClick="AddRegionals" FullWidth="true">Add Regionals</MudButton>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" lg="4">
|
||||
<MudButton Variant="Variant.Outlined" OnClick="RemoveIndividual" FullWidth="true">Remove Individual</MudButton>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" lg="4">
|
||||
<MudButton Variant="Variant.Outlined" OnClick="RemoveLowLevelOfEffort" FullWidth="true">Remove Low Effort</MudButton>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" lg="4">
|
||||
<MudButton Variant="Variant.Outlined" OnClick="Invert" FullWidth="true">Invert</MudButton>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" lg="4">
|
||||
<MudButton Variant="Variant.Outlined" Color="Color.Warning" OnClick="Reset" FullWidth="true">Reset</MudButton>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudButton Variant="Variant.Filled" Class="ma-3" OnClick="Solve" Color="Color.Primary" Disabled="@_isSolving">Solve</MudButton>
|
||||
<MudTooltip Text="Copy to Clipboard">
|
||||
<MudIconButton OnClick="CopyToClipboard" Icon="@Icons.Material.Filled.ContentCopy"></MudIconButton>
|
||||
</MudTooltip>
|
||||
</MudItem>
|
||||
|
||||
</MudGrid>
|
||||
</MudPaper>
|
||||
|
||||
<MudTable T="TeamScheduleTimeSlot" ServerData="SolveSchedule" @ref="_solutionData">
|
||||
<HeaderContent>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd>
|
||||
<MudGrid>
|
||||
<MudItem xs="12" lg="6">
|
||||
<ScheduledTeamsList TimeSlotName="@context.Name"
|
||||
Teams="@context.Teams"
|
||||
ScheduledTeams="@_scheduledTeams"
|
||||
AbsentStudents="@_absentStudents"
|
||||
StudentHasOverlaps="@context.StudentHasOverlaps"
|
||||
OnToggleTeam="@ToggleRequiredTeam" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" lg="6">
|
||||
<UnscheduledStudentsList UnscheduledStudents="@context.UnscheduledStudents"
|
||||
AbsentStudents="@_absentStudents"
|
||||
ScheduledTeams="@_scheduledTeams"
|
||||
PossibleAdditions="@_possibleAdditions"
|
||||
UnassignedTeams="@_solution.StudentUnassignedTeams"
|
||||
OnToggleTeam="@ToggleRequiredTeam" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
</MudItem>
|
||||
<MudItem xs="5" sm="4" lg="3">
|
||||
<MudStack>
|
||||
<StudentTextBoxSelector Students="@_students"
|
||||
@bind-SelectedStudents="_absentStudents"
|
||||
Title="Absent Students"
|
||||
Label="Search for absent students"
|
||||
ShowFullName="true"/>
|
||||
<MudDivider Class="my-4"/>
|
||||
<TeamToggleSelector Teams="@_teams"
|
||||
@bind-SelectedTeams="_scheduledTeams"
|
||||
Title="Scheduled Teams"
|
||||
ShowEventAttributes="true" />
|
||||
</MudStack>
|
||||
</MudItem>
|
||||
|
||||
</MudGrid>
|
||||
</MudPaper>
|
||||
|
||||
@code {
|
||||
private Team[]? _teams;
|
||||
private Student[]? _students;
|
||||
MudTable<TeamScheduleTimeSlot> _solutionData;
|
||||
private TeamSchedulerSolution _solution;
|
||||
private TeamSchedulerOptions _parameters;
|
||||
bool _isSolving;
|
||||
private IEnumerable<Team> _scheduledTeams = [];
|
||||
private IEnumerable<Student> _absentStudents = [];
|
||||
private IEnumerable<Team> _possibleAdditions = [];
|
||||
|
||||
private void AddRegionals()
|
||||
{
|
||||
_scheduledTeams
|
||||
= _teams.Where(e => e.Event.RegionalEvent).Concat(_scheduledTeams).Distinct();
|
||||
}
|
||||
|
||||
private void AddHighLevelOfEffort()
|
||||
{
|
||||
_scheduledTeams
|
||||
= _teams.Where(e => e.Event.LevelOfEffort >= 3).Concat(_scheduledTeams).Distinct();
|
||||
}
|
||||
|
||||
private void RemoveIndividual()
|
||||
{
|
||||
_scheduledTeams
|
||||
= _scheduledTeams.Where(t => t.Event.EventFormat != EventFormat.Individual);
|
||||
}
|
||||
|
||||
private void RemoveLowLevelOfEffort()
|
||||
{
|
||||
_scheduledTeams
|
||||
= _scheduledTeams.Where(t => t.Event.LevelOfEffort > 1);
|
||||
}
|
||||
|
||||
private void Invert()
|
||||
{
|
||||
var rt = _scheduledTeams.ToArray();
|
||||
_scheduledTeams
|
||||
= _teams.Where(t => !rt.Contains(t));
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
_scheduledTeams = [];
|
||||
}
|
||||
|
||||
private void ToggleRequiredTeam(Team unassignedTeam)
|
||||
{
|
||||
if (_scheduledTeams.Contains(unassignedTeam))
|
||||
_scheduledTeams = _scheduledTeams.Where(t => t != unassignedTeam);
|
||||
else
|
||||
{
|
||||
_scheduledTeams = _scheduledTeams.Concat(new[] { unassignedTeam });
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_parameters =
|
||||
new TeamSchedulerOptions(
|
||||
2,
|
||||
mustIncludeEvents:
|
||||
[
|
||||
// "Medical Technology", "Electrical Applications" , "RegionalTeam",
|
||||
// ,"Dragster", "Flight"
|
||||
],
|
||||
extended:
|
||||
[
|
||||
// "Invention", "Construction Challenge", "Mechanical", "Mass", "Micro"
|
||||
//"STEM"
|
||||
//"Community", "Vlogging"// "Microcontroller"
|
||||
],
|
||||
omittedEvents:
|
||||
[
|
||||
// "Vlogging", "Junior", "Community Service Video", "Digital Photography",
|
||||
// "STEM"
|
||||
|
||||
//"Leadership",// "Electrical", //"Construction"
|
||||
// "Forensic",
|
||||
//"CAD"
|
||||
//"I&I Team 1", "I&I Team 2"//, "Website Design",
|
||||
],
|
||||
absentStudents:
|
||||
[
|
||||
]
|
||||
);
|
||||
|
||||
_teams
|
||||
= await Context.Teams
|
||||
.Include(e => e.Event)
|
||||
.Include(e => e.Students)
|
||||
.OrderBy(e => e.Event.Name)
|
||||
.ThenBy(e => e.Identifier)
|
||||
.ToArrayAsync();
|
||||
|
||||
_students =
|
||||
await Context.Students
|
||||
.Include(e => e.Teams)
|
||||
.ThenInclude(e => e.Captain)
|
||||
.Include(e => e.EventRankings)
|
||||
.ThenInclude(e => e.EventDefinition)
|
||||
.OrderBy(e => e.FirstName).ToArrayAsync();
|
||||
}
|
||||
|
||||
private async Task<TableData<TeamScheduleTimeSlot>> SolveSchedule(TableState arg1, CancellationToken arg2)
|
||||
{
|
||||
_isSolving = true;
|
||||
|
||||
// Check if there are any teams to schedule
|
||||
if (!_scheduledTeams.Any())
|
||||
{
|
||||
_isSolving = false;
|
||||
_solution = new TeamSchedulerSolution([], [], "No teams selected");
|
||||
return new TableData<TeamScheduleTimeSlot> { Items = [] };
|
||||
}
|
||||
|
||||
// Filter out absent students
|
||||
var availableStudents = _students.Where(s => !_absentStudents.Contains(s)).ToArray();
|
||||
|
||||
// Check if there are any available students
|
||||
if (availableStudents.Length == 0)
|
||||
{
|
||||
_isSolving = false;
|
||||
_solution = new TeamSchedulerSolution([], [], "No available students");
|
||||
return new TableData<TeamScheduleTimeSlot> { Items = [] };
|
||||
}
|
||||
|
||||
// Update parameters with absent student names
|
||||
_parameters.AbsentStudents = _absentStudents.Select(s => s.FirstNameLastName).ToArray();
|
||||
|
||||
var teamScheduler = new TeamScheduler(_scheduledTeams, _parameters.TimeSlots, availableStudents);
|
||||
_solution = teamScheduler.Solve();
|
||||
|
||||
// Try recommendation strategies in priority order
|
||||
var scheduler = new UnassignedStudentScheduler(_teams, _solution.TimeSlots);
|
||||
var strategies = new[]
|
||||
{
|
||||
UnassignedScheduleStrategy.LevelOfEffort,
|
||||
UnassignedScheduleStrategy.BiggestGroup,
|
||||
UnassignedScheduleStrategy.AnyNotMeetingAlready,
|
||||
UnassignedScheduleStrategy.IndividualEvents
|
||||
};
|
||||
|
||||
_possibleAdditions = strategies
|
||||
.Select(strategy => scheduler.ScheduleStrategy(strategy))
|
||||
.FirstOrDefault(result => result.Any()) ?? [];
|
||||
|
||||
await InvokeAsync(StateHasChanged); // let the UI know that the solution has been found
|
||||
|
||||
_isSolving = false;
|
||||
return new TableData<TeamScheduleTimeSlot> { Items = _solution.TimeSlots };
|
||||
}
|
||||
|
||||
private void Solve()
|
||||
{
|
||||
_solutionData.ReloadServerData();
|
||||
}
|
||||
|
||||
async Task CopyToClipboard()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
foreach (var timeslot in _solution.TimeSlots)
|
||||
{
|
||||
AppendScheduledTeams(sb, timeslot);
|
||||
AppendUnscheduledStudents(sb, timeslot);
|
||||
sb.Append(Environment.NewLine);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await ClipboardService.WriteTextAsync(sb.ToString());
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine("Cannot write text to clipboard");
|
||||
}
|
||||
}
|
||||
|
||||
private void AppendScheduledTeams(StringBuilder sb, TeamScheduleTimeSlot timeslot)
|
||||
{
|
||||
foreach (var scheduledTeam in timeslot.Teams.OrderBy(e => e.ToString()))
|
||||
{
|
||||
var teamName = scheduledTeam.ToString();
|
||||
|
||||
if (scheduledTeam.Event.EventFormat is EventFormat.Individual)
|
||||
{
|
||||
sb.Append(teamName);
|
||||
}
|
||||
else
|
||||
{
|
||||
var studentsList = FormatStudentList(scheduledTeam, timeslot);
|
||||
sb.Append($"{teamName} - {studentsList}");
|
||||
}
|
||||
sb.Append(Environment.NewLine);
|
||||
}
|
||||
}
|
||||
|
||||
private string FormatStudentList(Team team, TeamScheduleTimeSlot timeslot)
|
||||
{
|
||||
return string.Join(", ",
|
||||
team.Students
|
||||
.OrderBy(e => e == team.Captain)
|
||||
.ThenBy(e => e.FirstName)
|
||||
.Select(e => FormatStudentName(e, timeslot)));
|
||||
}
|
||||
|
||||
private string FormatStudentName(Student student, TeamScheduleTimeSlot timeslot)
|
||||
{
|
||||
var name = student.FirstName;
|
||||
if (timeslot.StudentHasOverlaps(student))
|
||||
name += "*";
|
||||
if (_absentStudents.Contains(student))
|
||||
name += " (absent)";
|
||||
return name;
|
||||
}
|
||||
|
||||
private void AppendUnscheduledStudents(StringBuilder sb, TeamScheduleTimeSlot timeslot)
|
||||
{
|
||||
if (!timeslot.UnscheduledStudents.Any())
|
||||
return;
|
||||
|
||||
sb.Append("--Unscheduled");
|
||||
sb.Append(Environment.NewLine);
|
||||
|
||||
foreach (var student in timeslot.UnscheduledStudents)
|
||||
{
|
||||
var studentName = student.FirstName;
|
||||
if (_absentStudents.Contains(student))
|
||||
studentName += " (absent)";
|
||||
|
||||
var unassignedTeams = _solution.StudentUnassignedTeams(student);
|
||||
var teamsList = string.Join(", ", unassignedTeams.Select(e => e.ToString()));
|
||||
|
||||
sb.Append($"{studentName} - {teamsList}");
|
||||
sb.Append(Environment.NewLine);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
@using Core.Calculation
|
||||
|
||||
<MudStack>
|
||||
<MudText Typo="Typo.h6">@TimeSlotName</MudText>
|
||||
@foreach (var team in Teams.OrderBy(e => e.ToString()))
|
||||
{
|
||||
var removed = !ScheduledTeams.Contains(team);
|
||||
|
||||
<MudLink Typo="Typo.body1"
|
||||
Class="d-flex align-center"
|
||||
Color="Color.Default"
|
||||
OnClick="@(() => OnToggleTeam.InvokeAsync(team))">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Clear"
|
||||
Size="Size.Small"
|
||||
Class="@(removed ? "" : "d-none")">
|
||||
</MudIcon>
|
||||
@team -
|
||||
@foreach (var student in team.Students)
|
||||
{
|
||||
var overlap = StudentHasOverlaps(student);
|
||||
var isAbsent = AbsentStudents.Contains(student);
|
||||
var color = overlap ? Color.Warning : Color.Default;
|
||||
var suffix = GetStudentSuffix(overlap, isAbsent);
|
||||
|
||||
if (student != team.Students.First())
|
||||
{
|
||||
<MudText>, </MudText>
|
||||
}
|
||||
<MudText Typo="Typo.body2" Color="@color">
|
||||
@student.FirstName@suffix
|
||||
</MudText>
|
||||
}
|
||||
</MudLink>
|
||||
}
|
||||
</MudStack>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public string TimeSlotName { get; set; } = string.Empty;
|
||||
|
||||
[Parameter]
|
||||
public IEnumerable<Team> Teams { get; set; } = [];
|
||||
|
||||
[Parameter]
|
||||
public IEnumerable<Team> ScheduledTeams { get; set; } = [];
|
||||
|
||||
[Parameter]
|
||||
public IEnumerable<Student> AbsentStudents { get; set; } = [];
|
||||
|
||||
[Parameter]
|
||||
public Func<Student, bool> StudentHasOverlaps { get; set; } = null!;
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<Team> OnToggleTeam { get; set; }
|
||||
|
||||
private string GetStudentSuffix(bool overlap, bool isAbsent)
|
||||
{
|
||||
var suffix = overlap ? "*" : "";
|
||||
suffix += isAbsent ? " (absent)" : "";
|
||||
return suffix;
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
@using Core.Calculation
|
||||
|
||||
@if (UnscheduledStudents.Any())
|
||||
{
|
||||
<MudText Typo="Typo.body1" HtmlTag="strong">Unscheduled</MudText>
|
||||
<MudStack>
|
||||
@foreach (var student in UnscheduledStudents)
|
||||
{
|
||||
var isAbsent = AbsentStudents.Contains(student);
|
||||
<MudItem>
|
||||
<MudText Typo="Typo.body1" HtmlTag="i">
|
||||
@student.FirstName@(isAbsent ? " (absent)" : "")
|
||||
</MudText>
|
||||
@foreach (var unassignedTeam in UnassignedTeams(student))
|
||||
{
|
||||
var isPossibleAddition = PossibleAdditions.Contains(unassignedTeam, new TeamIdComparer());
|
||||
var isScheduled = ScheduledTeams.Contains(unassignedTeam);
|
||||
var color = isPossibleAddition ? Color.Success : Color.Default;
|
||||
|
||||
if (unassignedTeam != UnassignedTeams(student).First())
|
||||
{
|
||||
<span>, </span>
|
||||
}
|
||||
<MudLink Typo="Typo.body2"
|
||||
Color="@color"
|
||||
OnClick="@(() => OnToggleTeam.InvokeAsync(unassignedTeam))">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Check"
|
||||
Size="Size.Small"
|
||||
Class="@(isScheduled ? "" : "d-none")">
|
||||
</MudIcon>
|
||||
@unassignedTeam
|
||||
</MudLink>
|
||||
}
|
||||
</MudItem>
|
||||
}
|
||||
</MudStack>
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public IEnumerable<Student> UnscheduledStudents { get; set; } = [];
|
||||
|
||||
[Parameter]
|
||||
public IEnumerable<Student> AbsentStudents { get; set; } = [];
|
||||
|
||||
[Parameter]
|
||||
public IEnumerable<Team> ScheduledTeams { get; set; } = [];
|
||||
|
||||
[Parameter]
|
||||
public IEnumerable<Team> PossibleAdditions { get; set; } = [];
|
||||
|
||||
[Parameter]
|
||||
public Func<Student, IEnumerable<Team>> UnassignedTeams { get; set; } = null!;
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<Team> OnToggleTeam { get; set; }
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
@page "/students/create"
|
||||
@attribute [Authorize]
|
||||
@inject AppDbContext Context
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<PageTitle>Create Student - TSA Chapter Organizer</PageTitle>
|
||||
|
||||
<MudText Typo="Typo.h3">Create</MudText>
|
||||
<MudText Typo="Typo.h4">Student</MudText>
|
||||
<MudDivider />
|
||||
|
||||
|
||||
<EditForm Model="Student" OnValidSubmit="OnValidSubmit" Enhance>
|
||||
<AntiforgeryToken />
|
||||
<DataAnnotationsValidator />
|
||||
<MudGrid>
|
||||
<MudItem xs="12" sm="7">
|
||||
<MudPaper Class="pa-4">
|
||||
<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>
|
||||
<MudTextField T="string" Label="Phone Number" @bind-Value="Student.PhoneNumber" For="@(() => Student.PhoneNumber)"></MudTextField>
|
||||
<MudTextField T="int" Label="Grade" @bind-Value="Student.Grade" For="@(() => Student.Grade)"></MudTextField>
|
||||
<MudTextField T="int" Label="TSA Year" @bind-Value="Student.TsaYear" For="@(() => Student.TsaYear)"></MudTextField>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
<MudButton StartIcon="@Icons.Material.Filled.ArrowBack" Href="students">Back</MudButton>
|
||||
<MudButton ButtonType="ButtonType.Submit" StartIcon="@Icons.Material.Filled.Save">Save</MudButton>
|
||||
</EditForm>
|
||||
|
||||
@code {
|
||||
[SupplyParameterFromForm]
|
||||
private Student Student { get; set; } = new() { TsaYear = 1 };
|
||||
|
||||
private void OnValidSubmit(EditContext context)
|
||||
{
|
||||
|
||||
Context.Students.Add(Student);
|
||||
Context.SaveChanges();
|
||||
NavigationManager.NavigateTo("/students");
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
@page "/students/details"
|
||||
@attribute [Authorize]
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using Core.Entities
|
||||
@using Data
|
||||
@inject AppDbContext context
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<PageTitle>Student Details - TSA Chapter Organizer</PageTitle>
|
||||
|
||||
<MudText Typo="Typo.h3">Details</MudText>
|
||||
<MudText Typo="Typo.h4">Student</MudText>
|
||||
<MudDivider />
|
||||
|
||||
@if (student is null)
|
||||
{
|
||||
<MudText><em>Loading...</em></MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudPaper Class="pa-4 mt-4">
|
||||
<MudGrid>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudText Typo="Typo.subtitle2">First Name</MudText>
|
||||
<MudText>@student.FirstName</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudText Typo="Typo.subtitle2">Last Name</MudText>
|
||||
<MudText>@student.LastName</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudText Typo="Typo.subtitle2">Grade</MudText>
|
||||
<MudText>@student.Grade</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudText Typo="Typo.subtitle2">Email</MudText>
|
||||
<MudText>@student.Email</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudText Typo="Typo.subtitle2">Phone Number</MudText>
|
||||
<MudText>@student.PhoneNumber</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudText Typo="Typo.subtitle2">TSA Year</MudText>
|
||||
<MudText>@student.TsaYear</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudText Typo="Typo.subtitle2">State ID</MudText>
|
||||
<MudText>@student.StateId</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudText Typo="Typo.subtitle2">Regional ID</MudText>
|
||||
<MudText>@student.RegionalId</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudText Typo="Typo.subtitle2">National ID</MudText>
|
||||
<MudText>@student.NationalId</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudText Typo="Typo.subtitle2">Officer Role</MudText>
|
||||
<MudText>@student.OfficerRole</MudText>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudPaper>
|
||||
|
||||
<div class="mt-4">
|
||||
<MudButton StartIcon="@Icons.Material.Filled.Edit" Href="@($"/students/edit?id={student.Id}")" Variant="Variant.Filled" Color="Color.Primary">Edit</MudButton>
|
||||
<MudButton StartIcon="@Icons.Material.Filled.ArrowBack" Href="/students" Variant="Variant.Text">Back to List</MudButton>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
private Student? student;
|
||||
|
||||
[SupplyParameterFromQuery]
|
||||
private int Id { get; set; }
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
student = await context.Students.FirstOrDefaultAsync(m => m.Id == Id);
|
||||
|
||||
if (student is null)
|
||||
{
|
||||
NavigationManager.NavigateTo("notfound");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
@page "/students/edit"
|
||||
@attribute [Authorize]
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@inject AppDbContext Context
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<PageTitle>Edit Student - TSA Chapter Organizer</PageTitle>
|
||||
|
||||
<MudText Typo="Typo.h3">Edit</MudText>
|
||||
<MudText Typo="Typo.h4">Student@(@Student == null ? "" : $" ({Student.Name})")</MudText>
|
||||
|
||||
|
||||
|
||||
@if (Student is null)
|
||||
{
|
||||
<p><em>Loading...</em></p>
|
||||
}
|
||||
else
|
||||
{
|
||||
/* https://www.mudblazor.com/components/form */
|
||||
/* https://medium.com/@husainalbar/applying-mudblazor-for-crud-operations-in-our-blazor-project-a343037a52ef */
|
||||
<EditForm method="post" Model="Student" OnValidSubmit="UpdateStudent" FormName="edit" Enhance>
|
||||
<AntiforgeryToken />
|
||||
<DataAnnotationsValidator/>
|
||||
<MudGrid>
|
||||
<MudItem xs="12" sm="7">
|
||||
<MudPaper Class="pa-4">
|
||||
<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>
|
||||
<MudTextField T="string" Label="Phone Number" @bind-Value="Student.PhoneNumber" For="@(() => Student.PhoneNumber)"></MudTextField>
|
||||
<MudTextField T="int" Label="Grade" @bind-Value="Student.Grade" For="@(() => Student.Grade)"></MudTextField>
|
||||
<MudTextField T="int" Label="TSA Year" @bind-Value="Student.TsaYear" For="@(() => Student.TsaYear)"></MudTextField>
|
||||
<MudTextField T="string" Label="Regional Id" @bind-Value="Student.RegionalId" For="@(() => Student.RegionalId)"></MudTextField>
|
||||
<MudTextField T="string" Label="State Id" @bind-Value="Student.StateId" For="@(() => Student.StateId)"></MudTextField>
|
||||
<MudTextField T="string" Label="National Id" @bind-Value="Student.NationalId" For="@(() => Student.NationalId)"></MudTextField>
|
||||
<MudSelect T="OfficerRole?" @bind-Value="@Student.OfficerRole" Label="Officer Role">
|
||||
|
||||
<MudSelectItem T="OfficerRole?" Value="@null">-not an officer-</MudSelectItem>
|
||||
@foreach (var officerRole in Enum.GetValues(typeof(OfficerRole)).Cast<OfficerRole>())
|
||||
{
|
||||
<MudSelectItem T="OfficerRole?" Value="@(officerRole)"></MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
<MudButton StartIcon="@Icons.Material.Filled.ArrowBack" Href="students">Back</MudButton>
|
||||
<MudButton StartIcon="@Icons.Material.Filled.Save" OnClick="UpdateStudent">Save</MudButton>
|
||||
</EditForm>
|
||||
}
|
||||
|
||||
@code {
|
||||
[SupplyParameterFromQuery]
|
||||
private int Id { get; set; }
|
||||
|
||||
[SupplyParameterFromForm]
|
||||
private Student? Student { get; set; }
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
Student ??= await Context.Students.FirstOrDefaultAsync(m => m.Id == Id);
|
||||
|
||||
if (Student is null)
|
||||
{
|
||||
NavigationManager.NavigateTo("notfound");
|
||||
}
|
||||
}
|
||||
|
||||
// To protect from overposting attacks, enable the specific properties you want to bind to.
|
||||
// For more information, see https://learn.microsoft.com/aspnet/core/blazor/forms/#mitigate-overposting-attacks.
|
||||
private async Task UpdateStudent()
|
||||
{
|
||||
if (Student.OfficerRole == 0)
|
||||
Student.OfficerRole = null;
|
||||
|
||||
Context.Attach(Student!).State = EntityState.Modified;
|
||||
|
||||
try
|
||||
{
|
||||
await Context.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
if (!StudentExists(Student!.Id))
|
||||
{
|
||||
NavigationManager.NavigateTo("notfound");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
NavigationManager.NavigateTo("/students");
|
||||
}
|
||||
|
||||
private bool StudentExists(int id)
|
||||
{
|
||||
return Context.Students.Any(e => e.Id == id);
|
||||
}
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
@page "/students/event-ranking"
|
||||
@attribute [Authorize]
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using WebApp.Models
|
||||
@inject AppDbContext Context
|
||||
@rendermode InteractiveServer
|
||||
|
||||
<PageTitle>Student Event Ranks - TSA Chapter Organizer</PageTitle>
|
||||
|
||||
<MudText Typo="Typo.h3"><MudIcon Icon="@AppIcons.EventRank"></MudIcon> Student Event Ranks</MudText>
|
||||
|
||||
@if (_students == null)
|
||||
{
|
||||
<p><em>Loading...</em></p>
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
<MudTable Items="_students" Hover="true" Breakpoint="Breakpoint.Sm" LoadingProgressColor="Color.Info">
|
||||
<HeaderContent>
|
||||
|
||||
<MudTh>Name</MudTh>
|
||||
<MudTh>Grade</MudTh>
|
||||
<MudTh>TSA Year</MudTh>
|
||||
<MudTh></MudTh>
|
||||
<MudTh>Warnings</MudTh>
|
||||
@for (var i = 1; i <= 10; i++)
|
||||
{
|
||||
var ii = i;
|
||||
<MudTh>@AppIcons.GetOrdinal(ii)</MudTh>
|
||||
}
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
|
||||
<MudTd>@context.FirstName</MudTd>
|
||||
<MudTh>@context.Grade</MudTh>
|
||||
<MudTh>@context.TsaYear</MudTh>
|
||||
<MudTd><MudButton StartIcon="@Icons.Material.Filled.TableChart" Href="@($"students/event-ranking-edit/{context.Id}")">Edit</MudButton></MudTd>
|
||||
<MudTd>
|
||||
@if (!context.RankedEvents.Any(re => re.OnSiteActivity))
|
||||
{
|
||||
<MudTooltip Text="No On-Site Activity">
|
||||
<MudText Style="font-weight:bolder" Color="Color.Warning">@AppIcons.OnSiteActivity</MudText>
|
||||
</MudTooltip>
|
||||
}
|
||||
@if (!context.RankedEvents.Any(re => re.RegionalEvent))
|
||||
{
|
||||
<MudTooltip Text="No Regional Event">
|
||||
<MudText Style="font-weight:bolder" Color="Color.Warning">@AppIcons.RegionalEvent</MudText>
|
||||
</MudTooltip>
|
||||
}
|
||||
|
||||
@if (context.RankedEvents.All(re => re.EventFormat != EventFormat.Individual))
|
||||
{
|
||||
<MudTooltip Text="No Individual Event">
|
||||
<MudText Style="font-weight:bolder" Color="Color.Warning">@AppIcons.IndividualEvent</MudText>
|
||||
</MudTooltip>
|
||||
}
|
||||
</MudTd>
|
||||
@for (var i = 1; i <= 10; i++)
|
||||
{
|
||||
var ii = i;
|
||||
var st = context.EventRankings.FirstOrDefault(e => e.Rank == i);
|
||||
<MudTd Class="@($"event-rank-{ii})")">
|
||||
@if (st != null)
|
||||
{
|
||||
<span>@st.EventDefinition.ShortName
|
||||
<EventAttributes EventDefinition="@st.EventDefinition"></EventAttributes>
|
||||
</span>
|
||||
}
|
||||
</MudTd>
|
||||
}
|
||||
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
<MudTable Items="_eventStudentRankings" Hover="true" Breakpoint="Breakpoint.Sm" LoadingProgressColor="Color.Info">
|
||||
<HeaderContent>
|
||||
<MudTh>Event</MudTh>
|
||||
<MudTh>Level of Effort</MudTh>
|
||||
<MudTh>Individual</MudTh>
|
||||
<MudTh>Regional</MudTh>
|
||||
<MudTh>On-site Activity</MudTh>
|
||||
<MudTh>Team Size</MudTh>
|
||||
@for (var i = 0; i < _maxEventStudentRankings; i++)
|
||||
{
|
||||
var i1 = i + 1;
|
||||
<MudTh>@i1</MudTh>
|
||||
}
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd>@context.Event.Name</MudTd>
|
||||
<MudTd>@context.Event.LevelOfEffort</MudTd>
|
||||
<MudTd>@if (context.Event.EventFormat == EventFormat.Individual) { <span>ⓘ</span> }</MudTd >
|
||||
<MudTd>@if (context.Event.RegionalEvent) { <span>ⓡ</span> }</MudTd >
|
||||
<MudTd>@if (context.Event.OnSiteActivity) { <span>ⓐ</span> }</MudTd >
|
||||
<MudTd>[@context.Event.MinTeamSize-@context.Event.MaxTeamSize]</MudTd >
|
||||
@for (var j = 0; j < _maxEventStudentRankings; j++)
|
||||
{
|
||||
var student = j < context.StudentRanking.Length ? context.StudentRanking[j] : null;
|
||||
var eventClass = student != null ? $"event-rank-{student.Item2}" : "";
|
||||
<MudTd Class="@eventClass">
|
||||
@if (student != null)
|
||||
{
|
||||
@student.Item1.FirstName
|
||||
}
|
||||
</MudTd>
|
||||
}
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
}
|
||||
@code {
|
||||
private Student[]? _students;
|
||||
|
||||
private class EventStudentRankings {
|
||||
public EventDefinition Event {get; set; }
|
||||
public Tuple<Student,int> [] StudentRanking { get; set; }
|
||||
}
|
||||
|
||||
private EventStudentRankings[] _eventStudentRankings;
|
||||
private int _maxEventStudentRankings;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_students =
|
||||
await Context.Students
|
||||
.Include(e => e.EventRankings)
|
||||
.ThenInclude(e => e.EventDefinition)
|
||||
.OrderBy(e => e.FirstName).ToArrayAsync();
|
||||
|
||||
_eventStudentRankings =
|
||||
_students.SelectMany(s =>
|
||||
s.EventRankings,
|
||||
(student, ranking) => new { e = ranking.EventDefinition, a = Tuple.Create(student, ranking.Rank) }
|
||||
)
|
||||
.GroupBy(e => e.e)
|
||||
.Select(e =>
|
||||
new EventStudentRankings
|
||||
{
|
||||
Event = e.Key,
|
||||
StudentRanking = e.Select(er => er.a).OrderBy(ser => ser.Item2).ThenByDescending(ser => ser.Item1.Grade + ser.Item1.TsaYear).ToArray()
|
||||
})
|
||||
.OrderBy(e => e.Event.Name)
|
||||
.ToArray();
|
||||
|
||||
var events = await Context.Events.ToArrayAsync();
|
||||
var remainingEvents =
|
||||
events
|
||||
.Where(e => _eventStudentRankings.All(est => est.Event.Id != e.Id))
|
||||
.Select(e => new EventStudentRankings { Event = e, StudentRanking = Array.Empty<Tuple<Student, int>>() })
|
||||
.OrderBy(e => e.Event.Name)
|
||||
.ToArray();
|
||||
|
||||
_eventStudentRankings = _eventStudentRankings.Concat(remainingEvents).ToArray();
|
||||
|
||||
_maxEventStudentRankings = _eventStudentRankings.Max(esr => esr.StudentRanking.Length);
|
||||
}
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
@page "/students/event-ranking-edit/{StudentId:int}"
|
||||
@attribute [Authorize]
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using BlazorSortableList
|
||||
@using WebApp.Models
|
||||
@inject AppDbContext Context
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<PageTitle>Student Event Ranks - TSA Chapter Organizer</PageTitle>
|
||||
|
||||
<MudText Typo="Typo.h3"><MudIcon Icon="@AppIcons.EventRank"></MudIcon> Student Event Ranks</MudText>
|
||||
|
||||
<div>
|
||||
@if (_student == null)
|
||||
{
|
||||
<p><em>Loading...</em></p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Typo="Typo.h4">@_student.Name</MudText>
|
||||
<MudText Color="Color.Warning">Warning: drag and drop is currently a bit squirrely - double check!</MudText>
|
||||
|
||||
<MudButton StartIcon="@Icons.Material.Filled.ArrowBack" Href="students/event-ranking">Back</MudButton>
|
||||
<MudButton StartIcon="@Icons.Material.Filled.Save" OnClick="Save">Save</MudButton>
|
||||
|
||||
/* https://github.com/AlexNek/BlazorSortableList */
|
||||
<MudGrid>
|
||||
<MudItem xs="6" md="4" xl="3" Class="ranked-event-column">
|
||||
<SortableList
|
||||
Group="GroupId" Id="ListId1" Context="item"
|
||||
Items="_rankedEvents" OnRemove="RankedEventsRemove" OnUpdate="Update">
|
||||
<SortableItemTemplate>
|
||||
<MudCard Outlined="true">
|
||||
<MudCardContent>@item.Name</MudCardContent>
|
||||
</MudCard>
|
||||
</SortableItemTemplate>
|
||||
</SortableList>
|
||||
</MudItem>
|
||||
<MudItem xs="6" md="4" xl="3">
|
||||
<SortableList
|
||||
Group="GroupId" Id="ListId2" Context="item"
|
||||
Items="_availableEvents" OnRemove="AvailableEventsRemove" Sort="false">
|
||||
<SortableItemTemplate>
|
||||
<MudCard Outlined="true">
|
||||
<MudCardContent>@item.Name</MudCardContent>
|
||||
</MudCard>
|
||||
</SortableItemTemplate>
|
||||
|
||||
</SortableList>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
}
|
||||
</div>
|
||||
|
||||
@code {
|
||||
|
||||
private const string ListId1 = "SharedListId1";
|
||||
private const string ListId2 = "SharedListId2";
|
||||
private const string GroupId = "CommonGroup";
|
||||
|
||||
[Parameter] public int? StudentId { get; set; }
|
||||
|
||||
private Student? _student;
|
||||
private List<EventDefinition>? _events;
|
||||
|
||||
public List<EventDefinition> _rankedEvents = [];
|
||||
public List<EventDefinition> _availableEvents = [];
|
||||
|
||||
SharedSortableListGroup _group;
|
||||
|
||||
private void RankedEventsRemove((int oldIndex, int newIndex) indices)
|
||||
{
|
||||
// get the item at the old index in list 1
|
||||
var item = _rankedEvents[indices.oldIndex];
|
||||
|
||||
// add it to the new index in list 2
|
||||
_availableEvents.Insert(indices.newIndex, item);
|
||||
|
||||
// remove the item from the old index in list 1
|
||||
_rankedEvents.Remove(_rankedEvents[indices.oldIndex]);
|
||||
}
|
||||
|
||||
private void AvailableEventsRemove((int oldIndex, int newIndex) indices)
|
||||
{
|
||||
// get the item at the old index in list 2
|
||||
var item = _availableEvents[indices.oldIndex];
|
||||
|
||||
// add it to the new index in list 1
|
||||
_rankedEvents.Insert(indices.newIndex, item);
|
||||
|
||||
// remove the item from the old index in list 2
|
||||
_availableEvents.Remove(_availableEvents[indices.oldIndex]);
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_student =
|
||||
await Context.Students
|
||||
.Include(e => e.EventRankings)
|
||||
.Where(e => e.Id == StudentId).FirstAsync();
|
||||
_events =
|
||||
await Context.Events
|
||||
.OrderBy(e => e.Name)
|
||||
.ToListAsync();
|
||||
|
||||
_rankedEvents = _student.EventRankings.OrderBy(e => e.Rank).Select(e => e.EventDefinition).ToList();
|
||||
_availableEvents = _events.Where(e => !_rankedEvents.Contains(e)).ToList();
|
||||
|
||||
_group = new SharedSortableListGroup(StateHasChanged);
|
||||
_group.AddModel(ListId1, new SortableListModel<EventDefinition>(_rankedEvents) { Group = GroupId });
|
||||
_group.AddModel(ListId2, new SortableListModel<EventDefinition>(_availableEvents) { Group = GroupId });
|
||||
}
|
||||
|
||||
private void Update((int oldIndex, int newIndex) indices)
|
||||
{
|
||||
var (oldIndex, newIndex) = indices;
|
||||
|
||||
var items = _rankedEvents;
|
||||
var itemToMove = items[oldIndex];
|
||||
items.RemoveAt(oldIndex);
|
||||
|
||||
if (newIndex < items.Count)
|
||||
{
|
||||
items.Insert(newIndex, itemToMove);
|
||||
}
|
||||
else
|
||||
{
|
||||
items.Add(itemToMove);
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
|
||||
async Task Save()
|
||||
{
|
||||
if (_student == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
_student.EventRankings.Clear();
|
||||
for (var index = 0; index < _rankedEvents.Count; index++)
|
||||
{
|
||||
var evt = _rankedEvents[index];
|
||||
_student.EventRankings.Add(new StudentEventRanking
|
||||
{
|
||||
EventDefinition = evt,
|
||||
Student = _student,
|
||||
Rank = index + 1
|
||||
});
|
||||
}
|
||||
|
||||
Context.Students.Update(_student);
|
||||
|
||||
await Context.SaveChangesAsync();
|
||||
|
||||
NavigationManager.NavigateTo("/students/event-ranking");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
@page "/students"
|
||||
@attribute [Authorize]
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using WebApp.Models
|
||||
@inject AppDbContext Context
|
||||
@inject IDialogService DialogService
|
||||
@inject ISnackbar Snackbar
|
||||
|
||||
<PageTitle>Students - TSA Chapter Organizer</PageTitle>
|
||||
|
||||
<MudText Typo="Typo.h3">Students</MudText>
|
||||
|
||||
<MudButton StartIcon="@Icons.Material.Filled.Create" Href="students/create">Create New</MudButton>
|
||||
<MudButton StartIcon="@AppIcons.EventRank" Href="students/event-ranking">Event Rankings</MudButton>
|
||||
|
||||
<MudDataGrid T="Student" ServerData="ServerReload" @ref="_dataGrid" Filterable="true" RowsPerPage="25">
|
||||
<Columns>
|
||||
@* <PropertyColumn Property="@(e => e.Name)" Title="First Name" SortBy="e => e.FirstName" /> *@
|
||||
<TemplateColumn Title="Name" SortBy="e => e.LastName" Sortable="true" >
|
||||
<CellTemplate>
|
||||
@context.Item.LastNameFirstName
|
||||
@if (context.Item.OfficerRole != null)
|
||||
{
|
||||
<MudChip T="string" Icon="@(AppIcons.OfficerRoleIcon(context.Item.OfficerRole.Value))">@context.Item.OfficerRole</MudChip>
|
||||
}
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="Grade (TSA Year)" SortBy="e => e.Grade" Sortable="true">
|
||||
<CellTemplate>
|
||||
@context.Item.Grade (@context.Item.TsaYear)
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn>
|
||||
<CellTemplate>
|
||||
<CrudActions DetailsHref="@($"/students/details?id={context.Item!.Id}")"
|
||||
EditHref="@($"/students/edit?id={context.Item!.Id}")"
|
||||
DeleteOnClick="() => DeleteStudent(context.Item!)">
|
||||
</CrudActions>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
</Columns>
|
||||
<PagerContent>
|
||||
<MudDataGridPager T="Student"></MudDataGridPager>
|
||||
</PagerContent>
|
||||
</MudDataGrid>
|
||||
|
||||
@code {
|
||||
MudDataGrid<Student> _dataGrid = null!;
|
||||
|
||||
private async Task<GridData<Student>> ServerReload(GridState<Student> state)
|
||||
{
|
||||
|
||||
var query =
|
||||
Context.Students.OrderBy(e => e.LastName)
|
||||
.Where(state.FilterDefinitions).OrderBy(state.SortDefinitions);
|
||||
|
||||
var totalItems = await query.CountAsync();
|
||||
var pagedData = await query.Skip(state.Page * state.PageSize).Take(state.PageSize).ToArrayAsync();
|
||||
|
||||
return new GridData<Student>
|
||||
{
|
||||
TotalItems = totalItems,
|
||||
Items = pagedData
|
||||
};
|
||||
}
|
||||
|
||||
private async Task DeleteStudent(Student student)
|
||||
{
|
||||
//_isRowBlocked = true;
|
||||
|
||||
var result = await DialogService
|
||||
.ShowMessageBox("Delete student",
|
||||
(MarkupString)$"Are you sure want to delete <b>{student.Name}</b>? This cannot be undone.",
|
||||
yesText:"Yes",
|
||||
noText:"Cancel");
|
||||
|
||||
if (result == true)
|
||||
{
|
||||
Context.Students.Remove(student!);
|
||||
await Context.SaveChangesAsync();
|
||||
Snackbar.Add($"Delete event: Delete of Student {student.Name}", Severity.Info);
|
||||
}
|
||||
|
||||
//_isRowBlocked = false;
|
||||
StateHasChanged();
|
||||
await _dataGrid.ReloadServerData();
|
||||
}
|
||||
}
|
||||
@@ -1,464 +0,0 @@
|
||||
@page "/teams/assignment"
|
||||
@attribute [Authorize]
|
||||
@using Core.Calculation
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using WebApp.Models
|
||||
@using EventAssignment = Core.Calculation.EventAssignment
|
||||
@inject AppDbContext Context
|
||||
@inject IDialogService DialogService
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<PageTitle>Event Assignment - TSA Chapter Organizer</PageTitle>
|
||||
|
||||
<MudText Typo="Typo.h3">Assignment</MudText>
|
||||
|
||||
<MudGrid>
|
||||
<MudItem><MudText>Optimized team assignments based on the student event rankings</MudText></MudItem>
|
||||
<MudItem><MudButton StartIcon="@Icons.Material.Filled.Edit" Href="students/event-ranking">Edit Student Event Rankings</MudButton></MudItem>
|
||||
</MudGrid>
|
||||
|
||||
<MudPaper Class="pa-4 mt-5">
|
||||
<MudGrid>
|
||||
<MudItem Style="width:160px;">
|
||||
<MudNumericField @bind-Value="_parameters.TeamSizeLimit"
|
||||
Label="Team Size Limit" Min="3" Max="8"></MudNumericField>
|
||||
</MudItem>
|
||||
<MudItem>
|
||||
<MudTooltip Text="Require at least one On-Site Event">
|
||||
<MudSwitch @bind-Value="_parameters.RequireOnSite" Color="Color.Info"
|
||||
Label="On-Site" />
|
||||
</MudTooltip>
|
||||
</MudItem>
|
||||
<MudItem>
|
||||
<MudTooltip Text="Require at least one Regional Event">
|
||||
<MudSwitch @bind-Value="_parameters.RequireRegional" Color="Color.Info"
|
||||
Label="Regional" />
|
||||
</MudTooltip>
|
||||
</MudItem>
|
||||
<MudItem>
|
||||
<MudStack Style="width:100px;">
|
||||
<MudTooltip Text="Student Event Count Assignment Range">
|
||||
<MudInputLabel>Event Count</MudInputLabel>
|
||||
</MudTooltip>
|
||||
<MudNumericField @bind-Value="_parameters.EventsLowerBound"
|
||||
Label="At Least" Min="2" Max="4"></MudNumericField>
|
||||
|
||||
<MudNumericField @bind-Value="_parameters.EventsUpperBound"
|
||||
Label="Up to" Min="3" Max="5"></MudNumericField>
|
||||
</MudStack>
|
||||
</MudItem>
|
||||
<MudItem>
|
||||
<MudStack Style="width:100px;">
|
||||
<MudTooltip Text="Student Level of Effort Range">
|
||||
<MudInputLabel>LOE</MudInputLabel>
|
||||
</MudTooltip>
|
||||
<MudNumericField @bind-Value="_parameters.EffortLowerBound"
|
||||
Label="At Least" Min="4" Max="7"></MudNumericField>
|
||||
|
||||
<MudNumericField @bind-Value="_parameters.EffortUpperBound"
|
||||
Label="Up to" Min="7" Max="12"></MudNumericField>
|
||||
</MudStack>
|
||||
</MudItem>
|
||||
<MudItem>
|
||||
<MudInputLabel>Assignment Requirements</MudInputLabel>
|
||||
<MudTable T="AssignmentRequirement" ServerData="ReloadAssignmentRequirements" @ref="_assignmentRequirementData">
|
||||
|
||||
<RowTemplate Context="item">
|
||||
<MudTd Class="align-center">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.RemoveCircle" Size="Size.Small"
|
||||
OnClick="() => RemoveRequireEvent(item)"></MudIconButton>
|
||||
</MudTd>
|
||||
<MudTd Class="align-center">
|
||||
@item.Student.FirstName
|
||||
@item.EventDefinition.ShortName
|
||||
@if (item.Requirement == Requirement.Include)
|
||||
{
|
||||
<MudIcon Class="ml-3" Icon="@Icons.Material.Filled.ThumbUp" Size="Size.Small"></MudIcon>
|
||||
}
|
||||
@if (item.Requirement == Requirement.Exclude)
|
||||
{
|
||||
<MudIcon Class="ml-3" Icon="@Icons.Material.Filled.ThumbDownAlt" Size="Size.Small"></MudIcon>
|
||||
}
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
</MudItem>
|
||||
<MudItem>
|
||||
<MudInputLabel>Two Team Events</MudInputLabel>
|
||||
<MudTable T="EventDefinition" ServerData="ReloadEventTwoTeam" @ref="_eventTwoTeamData">
|
||||
|
||||
<RowTemplate Context="item">
|
||||
<MudTd Class="align-center">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.RemoveCircle" Size="Size.Small"
|
||||
OnClick="() => RemoveTwoTeam(item)"></MudIconButton>
|
||||
</MudTd>
|
||||
<MudTd Class="align-center">@item.ShortName</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
</MudItem>
|
||||
<MudItem>
|
||||
<MudInputLabel>Omitted Events</MudInputLabel>
|
||||
<MudTable T="EventDefinition" ServerData="ReloadOmittedEvents" @ref="_eventOmittedData">
|
||||
|
||||
<RowTemplate Context="item">
|
||||
<MudTd Class="align-center">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.RemoveCircle" Size="Size.Small"
|
||||
OnClick="() => RemoveOmitted(item)"></MudIconButton>
|
||||
</MudTd>
|
||||
<MudTd Class="align-center">@item.ShortName</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
<MudButton Class="ma-3" OnClick="Solve" Variant="Variant.Filled" Color="Color.Primary" Disabled="@_isSolving">Solve</MudButton>
|
||||
</MudPaper>
|
||||
|
||||
<MudGrid>
|
||||
<MudItem xs="12" lg="8">
|
||||
<MudText Typo="Typo.h4">Students</MudText>
|
||||
|
||||
<MudTable T="StudentEventStatistics" ServerData="ReloadStatistics" @ref="_statisticData" >
|
||||
<ColGroup>
|
||||
<col style="width: 150px;" />
|
||||
<col style="width: 50px;" />
|
||||
<col style="width: 50px;" />
|
||||
<col style="width: 60px;" />
|
||||
</ColGroup>
|
||||
<HeaderContent>
|
||||
<MudTh>Student</MudTh>
|
||||
<MudTh><MudTooltip Text="How many events they're assigned'">Event Count</MudTooltip></MudTh>
|
||||
<MudTh><MudTooltip Text="Level of Effort Total">LOE Sum</MudTooltip></MudTh>
|
||||
<MudTh><MudTooltip Text="Assignment Warnings">Warnings</MudTooltip></MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd><b>@context.Student.FirstName</b></MudTd>
|
||||
<MudTd>@context.EventCount</MudTd>
|
||||
<MudTd>@context.TotalLevelOfEffort</MudTd>
|
||||
<MudTd>@if (!context.HasOnSiteActivity)
|
||||
{
|
||||
<MudTooltip Text="No On-Site Activity">
|
||||
<MudText Style="font-weight:bolder" Color="Color.Warning">@AppIcons.OnSiteActivity</MudText>
|
||||
</MudTooltip>
|
||||
}
|
||||
@if (!context.HasRegionalEvent)
|
||||
{
|
||||
<MudTooltip Text="No Regional Event">
|
||||
<MudText Style="font-weight:bolder" Color="Color.Warning">@AppIcons.RegionalEvent</MudText>
|
||||
</MudTooltip>
|
||||
}
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
<ChildRowContent>
|
||||
<MudTr><td colspan="4">
|
||||
@{
|
||||
var allStudentEvents =
|
||||
context.Student.EventRankings
|
||||
.OrderBy(e => e.Rank)
|
||||
.Select(e => e.EventDefinition)
|
||||
.Concat(context.Events)
|
||||
.Distinct();
|
||||
}
|
||||
@foreach (var e in
|
||||
allStudentEvents
|
||||
.OrderBy(e =>
|
||||
context.Student.EventRankings
|
||||
.Find(ser => ser.EventDefinition == e)?.Rank ?? 10))
|
||||
{
|
||||
var eventRank = context.Student.EventRankings.Find(er => er.EventDefinition == e)?.Rank;
|
||||
var isAssigned = context.Events.Contains(e);
|
||||
|
||||
var color = AppIcons.RankedEventColor(eventRank ?? 0);
|
||||
var style = string.Empty;
|
||||
|
||||
if (isAssigned)
|
||||
{
|
||||
style += "border-color:black; border-width:thin;";
|
||||
if (eventRank.HasValue)
|
||||
{
|
||||
style += $"background:{color};";
|
||||
if (eventRank == 1)
|
||||
style += $"color:black";
|
||||
}
|
||||
else
|
||||
style += $"background:{Colors.Gray.Lighten3};";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (eventRank.HasValue)
|
||||
style += $"border-color:{color}; border-width:medium; color:{Colors.Gray.Lighten1};";
|
||||
}
|
||||
|
||||
<MudPaper Class="d-inline-flex align-center pa-2 mx-3 my-1 border-solid" Style="@(style)">
|
||||
@e.ShortName
|
||||
@AppIcons.EventAttributes(e)
|
||||
@{
|
||||
var isIncluded = _assignmentRequirements
|
||||
.Find(ar =>
|
||||
ar.EventDefinition == e
|
||||
&& ar.Student == context.Student
|
||||
&& ar.Requirement == Requirement.Include) == null;
|
||||
var isExcluded = _assignmentRequirements
|
||||
.Find(ar =>
|
||||
ar.EventDefinition == e
|
||||
&& ar.Student == context.Student
|
||||
&& ar.Requirement == Requirement.Exclude) == null;
|
||||
}
|
||||
@if (isIncluded)
|
||||
{
|
||||
<MudTooltip Text="@($"Add requirement for {context.Student.FirstName} in {e.ShortName}")">
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.ThumbUpAlt" Class="ml-3" Size="Size.Small" Color="Color.Default"
|
||||
OnClick="() => RequireEvent(e, context.Student, Requirement.Include)"></MudIconButton>
|
||||
</MudTooltip>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudTooltip Text="@($"Remove requirement for {context.Student.FirstName} in {e.ShortName}")">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.ThumbUpAlt" Class="ml-3" Size="Size.Small" Color="Color.Dark"
|
||||
OnClick="() => RemoveRequireEvent(e, context.Student, Requirement.Include)"></MudIconButton>
|
||||
</MudTooltip>
|
||||
}
|
||||
|
||||
@if (isExcluded)
|
||||
{
|
||||
<MudTooltip Text="@($"Add restriction against {context.Student.FirstName} in {e.ShortName}")">
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.ThumbDownAlt" Size="Size.Small" Color="Color.Default"
|
||||
OnClick="() => RequireEvent(e, context.Student, Requirement.Exclude)"></MudIconButton>
|
||||
</MudTooltip>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudTooltip Text="@($"Remove restriction against {context.Student.FirstName} in {e.ShortName}")">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.ThumbDownAlt" Size="Size.Small" Color="Color.Dark"
|
||||
OnClick="() => RemoveRequireEvent(e, context.Student, Requirement.Exclude)"></MudIconButton>
|
||||
</MudTooltip>
|
||||
}
|
||||
</MudPaper>
|
||||
}
|
||||
<MudDivider Style="border-width:3px" />
|
||||
</td></MudTr>
|
||||
</ChildRowContent>
|
||||
</MudTable>
|
||||
</MudItem>
|
||||
<MudItem xs="12" lg="4">
|
||||
<MudText Typo="Typo.h4">Teams</MudText>
|
||||
<MudTable T="Team" ServerData="SolveAssignments" @ref="_teamData">
|
||||
<ColGroup>
|
||||
<col style="width: 200px;" />
|
||||
<col style="width: 40px; white-space:nowrap" />
|
||||
</ColGroup>
|
||||
<HeaderContent>
|
||||
<MudTh>Team</MudTh>
|
||||
<MudTh><MudTooltip Text="Number of Student Rankings, Number of TimeSlots [Eligibility Lower Bound-Upper Bound]"><MudText Style="white-space:nowrap;">R, # [LB-UB]</MudText> </MudTooltip></MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
@{
|
||||
var thresholds = _eventAssignmentThresholds.First(e => e.Event == context.Event);
|
||||
}
|
||||
<MudTd>
|
||||
<b>@context.Event.Name</b>
|
||||
<EventAttributes EventDefinition="context.Event"></EventAttributes>
|
||||
</MudTd>
|
||||
<MudTd Style="white-space:nowrap">
|
||||
|
||||
@thresholds.StudentRankingCount, [@thresholds.LowerBound-@thresholds.UpperBound] × @thresholds.TeamCount
|
||||
|
||||
@if (_eventTwoTeams.Contains(context.Event))
|
||||
{
|
||||
<MudIconButton Class="ml-3" Size="Size.Small" Color="Color.Default"
|
||||
Icon="@Icons.Material.Filled.PlusOne" Variant="Variant.Filled"
|
||||
OnClick="() => RemoveTwoTeam(context.Event)"></MudIconButton>
|
||||
}
|
||||
else if (thresholds.TeamCount != 2 && context.Event.EventFormat == EventFormat.Team)
|
||||
{
|
||||
<MudIconButton Class="ml-3" Size="Size.Small" Color="Color.Default"
|
||||
Icon="@Icons.Material.Filled.PlusOne"
|
||||
OnClick="() => AddTwoTeam(context.Event)"></MudIconButton>
|
||||
}
|
||||
<MudIconButton Icon="@Icons.Material.Outlined.Remove" Size="Size.Small" Color="Color.Default"
|
||||
OnClick="() => AddOmitted(context.Event)"></MudIconButton>
|
||||
</MudTd>
|
||||
|
||||
</RowTemplate>
|
||||
<ChildRowContent>
|
||||
<MudTr>
|
||||
<td colspan="2">
|
||||
<TeamStudents Team="@context"></TeamStudents>
|
||||
<MudDivider Style="border-width:3px"/>
|
||||
</td>
|
||||
</MudTr>
|
||||
</ChildRowContent>
|
||||
<NoRecordsContent>
|
||||
<MudText Color="Color.Warning">Solution status: @_solutionStatus</MudText>
|
||||
</NoRecordsContent>
|
||||
<LoadingContent>
|
||||
<MudText>Loading...</MudText>
|
||||
</LoadingContent>
|
||||
</MudTable>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
<MudTooltip Text="This will overwrite existing teams">
|
||||
<MudButton Class="ma-3" StartIcon="@Icons.Material.Filled.Warning" Variant="Variant.Filled" Size="Size.Large" OnClick="SaveTeams" Color="Color.Warning">Save</MudButton>
|
||||
</MudTooltip>
|
||||
@code {
|
||||
public bool TestSwitch { get; set; } = false;
|
||||
|
||||
private readonly AssignmentParameters _parameters = new() { RequireOnSite = false, RequireRegional = false };
|
||||
|
||||
private List<EventDefinition>? _events;
|
||||
private List<Student>? _students;
|
||||
private List<EventAssignmentThresholds> _eventAssignmentThresholds = [];
|
||||
|
||||
MudTable<Team> _teamData;
|
||||
private Team[] _teams = [];
|
||||
MudTable<StudentEventStatistics> _statisticData;
|
||||
private List<StudentEventStatistics> _statistics = [];
|
||||
MudTable<AssignmentRequirement> _assignmentRequirementData;
|
||||
private List<AssignmentRequirement> _assignmentRequirements = [];
|
||||
MudTable<EventDefinition> _eventTwoTeamData;
|
||||
private List<EventDefinition> _eventTwoTeams = [];
|
||||
MudTable<EventDefinition> _eventOmittedData;
|
||||
private List<EventDefinition> _eventOmitted = [];
|
||||
|
||||
private string _solutionStatus = string.Empty;
|
||||
|
||||
private bool _isSolving = false;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_events =
|
||||
await Context.Events
|
||||
.OrderBy(e => e.Name)
|
||||
.ToListAsync();
|
||||
_students =
|
||||
await Context.Students
|
||||
.Where(e => e.FirstName != "test")
|
||||
.Include(e => e.EventRankings)
|
||||
.ThenInclude(e => e.EventDefinition)
|
||||
.Where(e => e.EventRankings.Any())
|
||||
.OrderBy(e => e.FirstName).ToListAsync();
|
||||
}
|
||||
|
||||
private async Task AddTeam()
|
||||
{
|
||||
//Context.TimeSlots.Add(Team);
|
||||
await Context.SaveChangesAsync();
|
||||
//NavigationManager.NavigateTo("/teams");
|
||||
}
|
||||
|
||||
private void Solve()
|
||||
{
|
||||
_teamData.ReloadServerData();
|
||||
}
|
||||
|
||||
private async Task<TableData<Team>> SolveAssignments(TableState arg1, CancellationToken arg2)
|
||||
{
|
||||
_isSolving = true;
|
||||
var eventAssignment = new EventAssignment(_events, _students, _parameters);
|
||||
foreach (var requirement in _assignmentRequirements)
|
||||
{
|
||||
eventAssignment.AddAssignmentRequirement(requirement);
|
||||
}
|
||||
eventAssignment.RemoveEvents(_eventOmitted);
|
||||
|
||||
eventAssignment.AllowTwoTeams(_eventTwoTeams);
|
||||
|
||||
var solution = await eventAssignment.Solve();
|
||||
_solutionStatus = solution.Status;
|
||||
_statistics =
|
||||
StudentEventStatistics.Generate(solution.Teams)
|
||||
.OrderByDescending(s => s.Student.Grade + s.Student.TsaYear)
|
||||
.ThenBy(s => s.Student.FirstName).ToList();
|
||||
_eventAssignmentThresholds = solution.AssignmentThresholds;
|
||||
await _statisticData.ReloadServerData();
|
||||
_isSolving = false;
|
||||
await InvokeAsync(StateHasChanged); // let the UI know that the solution has been found
|
||||
_teams = solution.Teams;
|
||||
|
||||
return new TableData<Team> { Items = _teams };
|
||||
}
|
||||
|
||||
private async Task<TableData<StudentEventStatistics>> ReloadStatistics(TableState arg1, CancellationToken arg2)
|
||||
{
|
||||
return new TableData<StudentEventStatistics> {Items = _statistics};
|
||||
}
|
||||
|
||||
private async Task<TableData<AssignmentRequirement>> ReloadAssignmentRequirements(TableState arg1, CancellationToken arg2)
|
||||
{
|
||||
return new TableData<AssignmentRequirement> { Items = _assignmentRequirements };
|
||||
}
|
||||
|
||||
private async Task<TableData<EventDefinition>> ReloadEventTwoTeam(TableState arg1, CancellationToken arg2)
|
||||
{
|
||||
return new TableData<EventDefinition> { Items = _eventTwoTeams };
|
||||
}
|
||||
|
||||
|
||||
private async Task<TableData<EventDefinition>> ReloadOmittedEvents(TableState arg1, CancellationToken arg2)
|
||||
{
|
||||
return new TableData<EventDefinition> { Items = _eventOmitted };
|
||||
}
|
||||
|
||||
private void RequireEvent(EventDefinition evt, Student student, Requirement requirement)
|
||||
{
|
||||
_assignmentRequirements.Add(new AssignmentRequirement(evt, student, requirement));
|
||||
_assignmentRequirementData.ReloadServerData();
|
||||
}
|
||||
|
||||
private void RemoveRequireEvent(EventDefinition evt, Student student, Requirement requirement)
|
||||
{
|
||||
var assignmentRequirement =
|
||||
_assignmentRequirements
|
||||
.Find(ar => ar.EventDefinition == evt && ar.Student == student && ar.Requirement == requirement);
|
||||
if (assignmentRequirement != null) RemoveRequireEvent(assignmentRequirement);
|
||||
}
|
||||
|
||||
private void RemoveRequireEvent(AssignmentRequirement assignmentRequirement)
|
||||
{
|
||||
_assignmentRequirements.Remove(assignmentRequirement);
|
||||
_assignmentRequirementData.ReloadServerData();
|
||||
}
|
||||
|
||||
private void AddTwoTeam(EventDefinition evt)
|
||||
{
|
||||
_eventTwoTeams.Add(evt);
|
||||
_eventTwoTeamData.ReloadServerData();
|
||||
}
|
||||
|
||||
private void RemoveTwoTeam(EventDefinition evt)
|
||||
{
|
||||
_eventTwoTeams.Remove(evt);
|
||||
_eventTwoTeamData.ReloadServerData();
|
||||
}
|
||||
|
||||
|
||||
private void AddOmitted(EventDefinition evt)
|
||||
{
|
||||
_eventOmitted.Add(evt);
|
||||
_eventOmittedData.ReloadServerData();
|
||||
}
|
||||
|
||||
private void RemoveOmitted(EventDefinition evt)
|
||||
{
|
||||
_eventOmitted.Remove(evt);
|
||||
_eventOmittedData.ReloadServerData();
|
||||
}
|
||||
|
||||
public async Task SaveTeams()
|
||||
{
|
||||
var result = await DialogService
|
||||
.ShowMessageBox("Save Teams",
|
||||
(MarkupString)$"Are you sure want to save these teams? Current teams will be erased. This cannot be undone.",
|
||||
yesText: "Yes",
|
||||
noText: "Cancel");
|
||||
|
||||
if (result == true)
|
||||
{
|
||||
await Context.Teams.ExecuteDeleteAsync();
|
||||
await Context.Teams.AddRangeAsync(_teams);
|
||||
await Context.SaveChangesAsync();
|
||||
|
||||
NavigationManager.NavigateTo("/teams");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
@page "/teams/create"
|
||||
@attribute [Authorize]
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@inject AppDbContext Context
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<PageTitle>Create Team - TSA Chapter Organizer</PageTitle>
|
||||
|
||||
<MudText Typo="Typo.h3">Create</MudText>
|
||||
<MudText Typo="Typo.h4">Team</MudText>
|
||||
<MudDivider />
|
||||
|
||||
<EditForm method="post" Model="Team" OnValidSubmit="AddTeam" FormName="create" Enhance>
|
||||
<AntiforgeryToken />
|
||||
<DataAnnotationsValidator />
|
||||
<MudGrid>
|
||||
<MudItem xs="12" sm="7">
|
||||
<MudPaper Class="pa-4">
|
||||
<MudSelect T="EventDefinition" Value="@Team.Event" ValueChanged="OnEventChanged" Label="Event">
|
||||
|
||||
@foreach (var evt in _events)
|
||||
{
|
||||
<MudSelectItem T="EventDefinition" Value="@(evt)"></MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
@if (_existingTeams?.Count == 1)
|
||||
{
|
||||
<MudAlert Severity="Severity.Info" Class="my-2">
|
||||
A team for @Team.Event.Name already exists. This will be team 2, and the existing team will become team 1.
|
||||
</MudAlert>
|
||||
}
|
||||
else if (_existingTeams?.Count >= 2)
|
||||
{
|
||||
<MudAlert Severity="Severity.Error" Class="my-2">
|
||||
Two teams for @Team.Event.Name already exist. Cannot create a third team.
|
||||
</MudAlert>
|
||||
}
|
||||
|
||||
<MudDivider Class="my-4" />
|
||||
|
||||
<StudentToggleSelector Students="@_students"
|
||||
@bind-SelectedStudents="_selectedStudents"
|
||||
Title="Students"
|
||||
ShowFullName="true" />
|
||||
|
||||
<MudStack Class="mt-4">
|
||||
<TeamCaptainSelector Students="@_selectedStudents"
|
||||
@bind-SelectedCaptain="Team.Captain"
|
||||
Title="Captain" />
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
@if (!string.IsNullOrEmpty(_errorMessage))
|
||||
{
|
||||
<MudAlert Severity="Severity.Error" Class="mt-3">@_errorMessage</MudAlert>
|
||||
}
|
||||
<MudButton StartIcon="@Icons.Material.Filled.ArrowBack" Href="students">Back</MudButton>
|
||||
<MudButton StartIcon="@Icons.Material.Filled.Add" OnClick="AddTeam">Add</MudButton>
|
||||
</EditForm>
|
||||
|
||||
@code {
|
||||
[SupplyParameterFromForm]
|
||||
private Team Team { get; set; } = new();
|
||||
|
||||
private List<EventDefinition>? _events;
|
||||
private List<Student> _students = [];
|
||||
private IEnumerable<Student> _selectedStudents = [];
|
||||
private string? _errorMessage;
|
||||
private List<Team>? _existingTeams;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_events =
|
||||
await Context.Events
|
||||
.OrderBy(e => e.Name)
|
||||
.ToListAsync();
|
||||
|
||||
_students = await Context.Students.ToListAsync();
|
||||
}
|
||||
|
||||
private async Task OnEventChanged(EventDefinition selectedEvent)
|
||||
{
|
||||
Team.Event = selectedEvent;
|
||||
|
||||
// Clear any previous error messages
|
||||
_errorMessage = null;
|
||||
|
||||
// Get all existing teams for this event
|
||||
_existingTeams = await Context.Teams
|
||||
.Include(t => t.Event)
|
||||
.Where(t => t.Event.Id == selectedEvent.Id)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
private async Task AddTeam()
|
||||
{
|
||||
// Clear previous error message
|
||||
_errorMessage = null;
|
||||
|
||||
// Get current count of teams for this event
|
||||
var existingTeamCount = await Context.Teams
|
||||
.CountAsync(t => t.Event.Id == Team.Event.Id);
|
||||
|
||||
// Prohibit creation of third team
|
||||
if (existingTeamCount >= 2)
|
||||
{
|
||||
_errorMessage = $"Cannot create a third team for {Team.Event.Name}. Maximum of 2 teams allowed.";
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle automatic numbering based on event format
|
||||
if (Team.Event.EventFormat == EventFormat.Individual && _selectedStudents.Count() == 1)
|
||||
{
|
||||
// For individual events, use student's first name as identifier
|
||||
var student = _selectedStudents.First();
|
||||
Team.Identifier = student.FirstName;
|
||||
Team.Captain = student;
|
||||
}
|
||||
else if (existingTeamCount == 1)
|
||||
{
|
||||
// This is the second team - assign numbers
|
||||
var existingTeam = await Context.Teams
|
||||
.FirstOrDefaultAsync(t => t.Event.Id == Team.Event.Id);
|
||||
|
||||
if (existingTeam != null)
|
||||
{
|
||||
// Update existing team to number 1
|
||||
existingTeam.Identifier = "1";
|
||||
Context.Teams.Update(existingTeam);
|
||||
}
|
||||
|
||||
// Set new team to number 2
|
||||
Team.Identifier = "2";
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is the first team - no number
|
||||
Team.Identifier = null;
|
||||
}
|
||||
|
||||
// Add selected students to the team
|
||||
foreach (var student in _selectedStudents)
|
||||
{
|
||||
Team.Students.Add(student);
|
||||
}
|
||||
|
||||
Context.Teams.Add(Team);
|
||||
|
||||
await Context.SaveChangesAsync();
|
||||
NavigationManager.NavigateTo("/teams");
|
||||
}
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
@page "/teams/edit"
|
||||
@attribute [Authorize]
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@inject AppDbContext Context
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<PageTitle>Edit Team - TSA Chapter Organizer</PageTitle>
|
||||
|
||||
<MudText Typo="Typo.h3">Edit</MudText>
|
||||
<MudText Typo="Typo.h4">Team @(Team.ToString())</MudText>
|
||||
|
||||
@if (Team is null)
|
||||
{
|
||||
<p><em>Loading...</em></p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<EditForm method="post" Model="Team" OnValidSubmit="UpdateTeam" FormName="edit" Enhance>
|
||||
<AntiforgeryToken />
|
||||
<DataAnnotationsValidator/>
|
||||
<MudGrid>
|
||||
<MudItem xs="12" sm="7">
|
||||
<MudPaper Class="pa-4">
|
||||
<StudentToggleSelector Students="@_students"
|
||||
@bind-SelectedStudents="_selectedStudents"
|
||||
Title="Students"
|
||||
ShowFullName="true" />
|
||||
<MudStack Class="mt-4">
|
||||
<TeamCaptainSelector Students="@_selectedStudents"
|
||||
@bind-SelectedCaptain="Team.Captain"
|
||||
Title="Captain" />
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
<MudButton StartIcon="@Icons.Material.Filled.ArrowBack" Href="teams">Back</MudButton>
|
||||
<MudButton StartIcon="@Icons.Material.Filled.Save" OnClick="UpdateTeam">Save</MudButton>
|
||||
</EditForm>
|
||||
}
|
||||
|
||||
@code {
|
||||
[SupplyParameterFromQuery]
|
||||
private int Id { get; set; }
|
||||
|
||||
[SupplyParameterFromForm]
|
||||
private Team? Team { get; set; }
|
||||
|
||||
private IEnumerable<Student> _selectedStudents = [];
|
||||
private List<Student> _students = [];
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
Team ??= await Context.Teams
|
||||
.Include(e => e.Event)
|
||||
.Include(e => e.Students)
|
||||
.FirstOrDefaultAsync(m => m.Id == Id);
|
||||
_students = await Context.Students.ToListAsync();
|
||||
_selectedStudents = Team.Students.ToList();
|
||||
|
||||
if (Team is null)
|
||||
{
|
||||
NavigationManager.NavigateTo("notfound");
|
||||
}
|
||||
|
||||
switch (Team!.Event.EventFormat)
|
||||
{
|
||||
case EventFormat.Individual when Team.Students.Count == 1:
|
||||
Team.Captain ??= Team.Students[0];
|
||||
Team.Identifier ??= Team.Captain.FirstName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// To protect from overposting attacks, enable the specific properties you want to bind to.
|
||||
// For more information, see https://learn.microsoft.com/aspnet/core/blazor/forms/#mitigate-overposting-attacks.
|
||||
private async Task UpdateTeam()
|
||||
{
|
||||
//Context.Attach(Team!).Entity = EntityState.Modified;
|
||||
Team.Students.Clear();
|
||||
foreach (var s in _selectedStudents)
|
||||
{
|
||||
Team.Students.Add(s);
|
||||
}
|
||||
|
||||
// Update identifier for individual events
|
||||
if (Team.Event.EventFormat == EventFormat.Individual && Team.Students.Count == 1)
|
||||
{
|
||||
Team.Captain ??= Team.Students[0];
|
||||
Team.Identifier = Team.Captain.FirstName;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await Context.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
if (!TeamExists(Team!.Id))
|
||||
{
|
||||
NavigationManager.NavigateTo("notfound");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
NavigationManager.NavigateTo("/teams");
|
||||
}
|
||||
|
||||
private bool TeamExists(int id)
|
||||
{
|
||||
return Context.Teams.Any(e => e.Id == id);
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
@page "/teams/handout"
|
||||
@attribute [Authorize]
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using WebApp.Models
|
||||
@inject IConfiguration Configuration
|
||||
@inject AppDbContext Context
|
||||
|
||||
<PageTitle>@Configuration["ChapterSettings:Shortname"] TSA Teams @Configuration["ChapterSettings:CompetitionYear"]</PageTitle>
|
||||
|
||||
<MudText Typo="Typo.h3">@Configuration["ChapterSettings:Shortname"] TSA Teams @Configuration["ChapterSettings:CompetitionYear"]</MudText>
|
||||
<MudText Typo="Typo.h5" Class="mb-4">Yearly theme: Unity Through Community</MudText>
|
||||
|
||||
@if (_teams == null)
|
||||
{
|
||||
<p><em>Loading...</em></p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudContainer>
|
||||
@foreach (var studentForEvents in _students)
|
||||
{
|
||||
<MudContainer Class="pagebreak">
|
||||
<MudText Typo="Typo.h4">@studentForEvents.Name</MudText>
|
||||
@foreach (var team in studentForEvents.Teams)
|
||||
{
|
||||
<MudContainer Class="mt-3 mb-1 nobrk">
|
||||
<MudGrid>
|
||||
<MudItem xs="6">
|
||||
<MudStack>
|
||||
<MudItem>
|
||||
<MudText Class="d-flex py-1" Typo="Typo.h5">
|
||||
@team.ToString()
|
||||
</MudText>
|
||||
</MudItem>
|
||||
|
||||
@if (team.Event.RegionalEvent)
|
||||
{
|
||||
<MudItem>
|
||||
<MudText Class="d-flex" Typo="Typo.caption"><i>Regional Event</i></MudText>
|
||||
</MudItem>
|
||||
}
|
||||
</MudStack>
|
||||
</MudItem>
|
||||
<MudItem xs="2">
|
||||
<MudText>
|
||||
<strong>@team.Event.EventFormat</strong>
|
||||
</MudText>
|
||||
|
||||
</MudItem>
|
||||
<MudItem xs="1">
|
||||
<strong>Effort</strong>: @AppIcons.LevelOfEffortIcon(@team.Event.LevelOfEffort)
|
||||
</MudItem>
|
||||
<MudItem xs="3">
|
||||
<strong>Activity</strong>: @team.Event.SemifinalistActivity
|
||||
</MudItem>
|
||||
@if (team.Event.EventFormat == EventFormat.Team)
|
||||
{
|
||||
<MudItem xs="12">
|
||||
<MudText Class="d-flex py-1" Typo="Typo.h6">
|
||||
Team Members: @string.Join(", ", team.Students.OrderByDescending(e => e.Grade + e.TsaYear).Select(e => e.FirstName))
|
||||
</MudText>
|
||||
</MudItem>
|
||||
}
|
||||
<MudItem xs="12">
|
||||
<MudText Class="d-flex py-1" Style="white-space:pre-wrap;">@team.Event.Description</MudText>
|
||||
</MudItem>
|
||||
@if (!string.IsNullOrEmpty(team.Event.Theme))
|
||||
{
|
||||
<MudItem xs="3">
|
||||
<MudText Class="d-flex py-1">
|
||||
<i>Theme for 2025-26:</i>
|
||||
</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="8">
|
||||
<MudText Class="d-flex py-1" Style="white-space:pre-wrap;">@team.Event.Theme</MudText>
|
||||
</MudItem>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrEmpty(team.Event.Documentation))
|
||||
{
|
||||
<MudItem xs="3">
|
||||
<MudText Class="d-flex py-1">
|
||||
<i>Materials:</i>
|
||||
</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="8">
|
||||
<MudText Class="d-flex py-1" Style="white-space:pre-wrap;">@team.Event.Documentation</MudText>
|
||||
</MudItem>
|
||||
}
|
||||
|
||||
</MudGrid>
|
||||
</MudContainer>
|
||||
<MudDivider/>
|
||||
}
|
||||
</MudContainer>
|
||||
}
|
||||
</MudContainer>
|
||||
}
|
||||
@code {
|
||||
private Team[]? _teams;
|
||||
private int _maxTeamSize;
|
||||
private Student[]? _students;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_teams
|
||||
= await Context.Teams
|
||||
.Include(e => e.Event)
|
||||
.Include(e => e.Students)
|
||||
.OrderBy(e => e.Event.Name)
|
||||
.ThenBy(e => e.Identifier)
|
||||
.ToArrayAsync();
|
||||
|
||||
_maxTeamSize = _teams.Max(t => t.Students.Count);
|
||||
_students =
|
||||
await Context.Students
|
||||
.Include(e => e.Teams)
|
||||
.ThenInclude(e => e.Captain)
|
||||
.Include(e => e.EventRankings)
|
||||
.ThenInclude(e => e.EventDefinition)
|
||||
.OrderBy(e => e.FirstName).ToArrayAsync();
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@page "/teams"
|
||||
@attribute [Authorize]
|
||||
@inject AppDbContext Context
|
||||
@inject IDialogService DialogService
|
||||
@inject ISnackbar Snackbar
|
||||
|
||||
<PageTitle>TimeSlots</PageTitle>
|
||||
|
||||
<MudText Typo="Typo.h3">Teams</MudText>
|
||||
|
||||
<MudButton StartIcon="@Icons.Material.Filled.Create" Href="teams/create">Create New</MudButton>
|
||||
<MudButton StartIcon="@Icons.Material.Filled.Assignment" Href="teams/assignment">Assignment</MudButton>
|
||||
<MudButton StartIcon="@Icons.Material.Filled.Print" Href="teams/printout">Printout</MudButton>
|
||||
|
||||
|
||||
<MudDataGrid T="Team" ServerData="ServerReload" @ref="_dataGrid" Filterable="true" RowsPerPage="35">
|
||||
<Columns>
|
||||
<TemplateColumn Title="Event">
|
||||
<CellTemplate>
|
||||
@context.Item.ToString()
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="Attributes">
|
||||
<CellTemplate>
|
||||
<EventAttributes EventDefinition="@context.Item.Event"></EventAttributes>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="Students">
|
||||
<CellTemplate>
|
||||
<TeamStudents Team="@context.Item"></TeamStudents>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn>
|
||||
<CellTemplate>
|
||||
<CrudActions
|
||||
EditHref="@($"/teams/edit?id={context.Item!.Id}")"
|
||||
DeleteOnClick="() => DeleteTeam(context.Item!)">
|
||||
</CrudActions>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
</Columns>
|
||||
<PagerContent>
|
||||
<MudDataGridPager T="Team"></MudDataGridPager>
|
||||
</PagerContent>
|
||||
</MudDataGrid>
|
||||
|
||||
@code {
|
||||
MudDataGrid<Team> _dataGrid = null!;
|
||||
|
||||
private async Task<GridData<Team>> ServerReload(GridState<Team> state)
|
||||
{
|
||||
var query
|
||||
= Context.Teams
|
||||
.Include(e => e.Event)
|
||||
.Include(e => e.Students)
|
||||
.ThenInclude(e => e.EventRankings)
|
||||
.OrderBy(e => e.Event.Name)
|
||||
.ThenBy(e => e.Identifier)
|
||||
.Where(state.FilterDefinitions)
|
||||
.OrderBy(state.SortDefinitions);
|
||||
|
||||
var totalItems = await query.CountAsync();
|
||||
var pagedData = await query.Skip(state.Page * state.PageSize).Take(state.PageSize).ToArrayAsync();
|
||||
|
||||
return new GridData<Team>
|
||||
{
|
||||
TotalItems = totalItems,
|
||||
Items = pagedData
|
||||
};
|
||||
}
|
||||
|
||||
private async Task DeleteTeam(Team team)
|
||||
{
|
||||
//_isRowBlocked = true;
|
||||
|
||||
var result = await DialogService
|
||||
.ShowMessageBox("Delete team",
|
||||
(MarkupString)$"Are you sure want to delete <b>{team}</b>? This cannot be undone.",
|
||||
yesText: "Yes",
|
||||
noText: "Cancel");
|
||||
|
||||
if (result == true)
|
||||
{
|
||||
// If deleting a numbered team (1 or 2), clear the identifier of the remaining team
|
||||
if (team.Identifier == "1" || team.Identifier == "2")
|
||||
{
|
||||
var remainingTeam = await Context.Teams
|
||||
.Include(t => t.Event)
|
||||
.FirstOrDefaultAsync(t => t.Event.Id == team.Event.Id && t.Id != team.Id);
|
||||
|
||||
if (remainingTeam != null)
|
||||
{
|
||||
remainingTeam.Identifier = null;
|
||||
Context.Teams.Update(remainingTeam);
|
||||
}
|
||||
}
|
||||
|
||||
Context.Teams.Remove(team!);
|
||||
await Context.SaveChangesAsync();
|
||||
Snackbar.Add($"Delete event: Delete of Team {team}", Severity.Info);
|
||||
}
|
||||
|
||||
//_isRowBlocked = false;
|
||||
StateHasChanged();
|
||||
await _dataGrid.ReloadServerData();
|
||||
}
|
||||
}
|
||||
@@ -1,236 +0,0 @@
|
||||
@page "/teams/printout"
|
||||
@attribute [Authorize]
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using WebApp.Models
|
||||
@inject IConfiguration Configuration
|
||||
@inject AppDbContext Context
|
||||
|
||||
<PageTitle>@Configuration["ChapterSettings:Shortname"] TSA Teams @Configuration["ChapterSettings:CompetitionYear"]</PageTitle>
|
||||
|
||||
<MudText Typo="Typo.h3">@Configuration["ChapterSettings:Shortname"] TSA Teams @Configuration["ChapterSettings:CompetitionYear"]</MudText>
|
||||
<MudText Typo="Typo.h5" Class="mb-4">Yearly theme: Unity Through Community</MudText>
|
||||
|
||||
<Legend></Legend>
|
||||
@if (_teams == null)
|
||||
{
|
||||
<p><em>Loading...</em></p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudContainer Class="mt-3 mb-1 nobrk">
|
||||
|
||||
<MudTable Items="_teams">
|
||||
<HeaderContent>
|
||||
<MudTh>Team</MudTh>
|
||||
<MudTh></MudTh>
|
||||
@for (var i = 0; i <= _maxTeamSize; i++)
|
||||
{
|
||||
<MudTh></MudTh>
|
||||
}
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
|
||||
<MudTd>
|
||||
@context.ToString()
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
<EventAttributes EventDefinition="context.Event"></EventAttributes>
|
||||
</MudTd>
|
||||
|
||||
@{
|
||||
var students
|
||||
= context.Students
|
||||
.OrderByDescending(s => s == context.Captain)
|
||||
.ThenBy(s => s.EventRankings.Find(e => e.EventDefinition == context.Event)?.Rank ?? int.MaxValue)
|
||||
.ThenByDescending(e => e.Grade)
|
||||
.ThenBy(e => e.FirstName)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
@for (var i = 0; i <= _maxTeamSize; i++)
|
||||
{
|
||||
var student = i < students.Length ? students[i] : null;
|
||||
if (student != null)
|
||||
{
|
||||
var rank = student.EventRankings
|
||||
.Find(e => e.EventDefinition == context.Event)?.Rank ?? int.MaxValue;
|
||||
|
||||
<MudTd Class="@(EventRankClass(rank))">
|
||||
@student.Name @if(context?.Captain == student) {<span> (Cpt)</span>}
|
||||
</MudTd>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudTd></MudTd>
|
||||
}
|
||||
}
|
||||
<MudTd>
|
||||
@if (context.Event.EventFormat == EventFormat.Team)
|
||||
{
|
||||
@if (context.Students.Count < context.Event.MinTeamSize)
|
||||
{
|
||||
<span>Min Team Size: @context.Event.MinTeamSize</span>
|
||||
}
|
||||
|
||||
@if (context.Students.Count > context.Event.MaxTeamSize)
|
||||
{
|
||||
<span>Max Team Size: @context.Event.MaxTeamSize</span>
|
||||
}
|
||||
}
|
||||
else if (context.Event.EventFormat == EventFormat.Individual
|
||||
&& context.Students.Count > context.Event.ChapterEligibilityCountState)
|
||||
{
|
||||
<span>Max Team Count State: @context.Event.ChapterEligibilityCountState</span>
|
||||
}
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
|
||||
</MudContainer>
|
||||
|
||||
<MudContainer Class="mt-3 mb-1 nobrk pagebreak">
|
||||
|
||||
<MudTable Items="_students">
|
||||
<HeaderContent>
|
||||
<MudTh>Student</MudTh>
|
||||
<MudTh>Effort</MudTh>
|
||||
@for (var i = 0; i <= 5; i++)
|
||||
{
|
||||
<MudTh></MudTh>
|
||||
}
|
||||
<MudTh></MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
|
||||
<MudTd>@context.Name</MudTd>
|
||||
<MudTd>@context.Teams.Sum(e => e.Event.LevelOfEffort)</MudTd>
|
||||
@{
|
||||
var teams = context.Teams
|
||||
.OrderBy(e =>
|
||||
context.EventRankings.Find(ser => ser.EventDefinition == e.Event)?.Rank ?? int.MaxValue
|
||||
).ToArray();
|
||||
}
|
||||
@for (var i = 0; i <= 5; i++)
|
||||
{
|
||||
var team = i < teams.Length ? teams[i] : null;
|
||||
|
||||
@if (team != null)
|
||||
{
|
||||
var rank = context.EventRankings
|
||||
.Find(e => e.EventDefinition == team.Event)?.Rank ?? int.MaxValue;
|
||||
<MudTh Class="@(EventRankClass(rank))">
|
||||
@team.ToString()
|
||||
<EventAttributes EventDefinition="team.Event"></EventAttributes>
|
||||
|
||||
@if (rank == int.MaxValue)
|
||||
{
|
||||
<span>❔</span>
|
||||
}
|
||||
</MudTh>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudTh></MudTh>
|
||||
}
|
||||
|
||||
}
|
||||
<MudTd>
|
||||
@if (!context.Teams.Select(e => e.Event).Any(re => re.OnSiteActivity))
|
||||
{
|
||||
<span>No On-Site Activity</span>
|
||||
}
|
||||
@if (!context.Teams.Select(e => e.Event).Any(re => re.RegionalEvent))
|
||||
{
|
||||
<span>No Regional Event</span>
|
||||
}
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
|
||||
</MudContainer>
|
||||
|
||||
|
||||
<MudContainer Class="mt-3 mb-1 nobrk pagebreak">
|
||||
|
||||
<MudTable Items="_students">
|
||||
<HeaderContent>
|
||||
<MudTh>Student</MudTh>
|
||||
<MudTh>Grade, TSA Year</MudTh>
|
||||
<MudTh>Level of Effort</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
|
||||
<MudTd>@context.Name</MudTd>
|
||||
<MudTd>@AppIcons.GetOrdinal(context.Grade), @context.TsaYear</MudTd>
|
||||
<MudTd>@context.Teams.Sum(e => e.Event.LevelOfEffort)
|
||||
@if (!context.Teams.Select(e => e.Event).Any(re => re.OnSiteActivity))
|
||||
{
|
||||
<span>No On-Site Activity</span>
|
||||
}
|
||||
@if (!context.Teams.Select(e => e.Event).Any(re => re.RegionalEvent))
|
||||
{
|
||||
<span>No Regional Event</span>
|
||||
}
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
<ChildRowContent>
|
||||
<MudTr>
|
||||
<MudTable Items="@context.Teams.OrderBy(e =>
|
||||
context.EventRankings.Find(ser => ser.EventDefinition == e.Event)?.Rank ?? int.MaxValue
|
||||
)" Context="team">
|
||||
<RowTemplate>
|
||||
@{ var rank = context.EventRankings
|
||||
.Find(e => e.EventDefinition == team.Event)?.Rank ?? int.MaxValue; }
|
||||
<MudTd Class="@(EventRankClass(rank))">
|
||||
@team.ToString()
|
||||
@AppIcons.EventEffort(team.Event)
|
||||
@AppIcons.EventAttributes(team.Event)
|
||||
|
||||
@if (rank == int.MaxValue)
|
||||
{
|
||||
<span>❔</span>
|
||||
}
|
||||
</MudTd>
|
||||
<MudTd>@string.Join(", ", team.Students.Where(e => e != context).Select(e => e.FirstName))</MudTd>
|
||||
<MudTd></MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
</MudTr>
|
||||
</ChildRowContent>
|
||||
</MudTable>
|
||||
|
||||
</MudContainer>
|
||||
}
|
||||
@code {
|
||||
private Team[]? _teams;
|
||||
private int _maxTeamSize;
|
||||
private Student[]? _students;
|
||||
private bool _rankColorEnabled;
|
||||
|
||||
private string EventRankClass(int rank)
|
||||
{
|
||||
if (!_rankColorEnabled)
|
||||
return "";
|
||||
return "event-rank-" + rank;
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_teams
|
||||
= await Context.Teams
|
||||
.Include(e => e.Event)
|
||||
.Include(e => e.Students)
|
||||
.OrderBy(e => e.Event.Name)
|
||||
.ThenBy(e => e.Identifier)
|
||||
.ToArrayAsync();
|
||||
|
||||
_maxTeamSize = _teams.Max(t => t.Students.Count);
|
||||
_students =
|
||||
await Context.Students
|
||||
.Include(e => e.Teams)
|
||||
.ThenInclude(e => e.Captain)
|
||||
.Include(e => e.EventRankings)
|
||||
.ThenInclude(e => e.EventDefinition)
|
||||
.OrderBy(e => e.FirstName).ToArrayAsync();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user