Add Blazor WebApp and rework data handling to utilize Entity Framework
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
@page "/Error"
|
||||
@using System.Diagnostics
|
||||
|
||||
<PageTitle>Error</PageTitle>
|
||||
|
||||
<h1 class="text-danger">Error.</h1>
|
||||
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||
|
||||
@if (ShowRequestId)
|
||||
{
|
||||
<p>
|
||||
<strong>Request ID:</strong> <code>@RequestId</code>
|
||||
</p>
|
||||
}
|
||||
|
||||
<h3>Development Mode</h3>
|
||||
<p>
|
||||
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
|
||||
</p>
|
||||
<p>
|
||||
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
|
||||
It can result in displaying sensitive information from exceptions to end users.
|
||||
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
|
||||
and restarting the app.
|
||||
</p>
|
||||
|
||||
@code{
|
||||
[CascadingParameter]
|
||||
private HttpContext? HttpContext { get; set; }
|
||||
|
||||
private string? RequestId { get; set; }
|
||||
private bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||
|
||||
protected override void OnInitialized() =>
|
||||
RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
@page "/events/create"
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using Core.Entities
|
||||
@using Data
|
||||
@inject AppDbContext context
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<PageTitle>Create</PageTitle>
|
||||
|
||||
<h1>Create</h1>
|
||||
|
||||
<h2>EventDefinition</h2>
|
||||
<hr />
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<EditForm method="post" Model="EventDefinition" OnValidSubmit="AddEventDefinition" FormName="create" Enhance>
|
||||
<DataAnnotationsValidator />
|
||||
<ValidationSummary class="text-danger" role="alert"/>
|
||||
<div class="mb-3">
|
||||
<label for="name" class="form-label">Name:</label>
|
||||
<InputText id="name" @bind-Value="EventDefinition.Name" class="form-control" />
|
||||
<ValidationMessage For="() => EventDefinition.Name" class="text-danger" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="shortname" class="form-label">ShortName:</label>
|
||||
<InputText id="shortname" @bind-Value="EventDefinition.ShortName" class="form-control" />
|
||||
<ValidationMessage For="() => EventDefinition.ShortName" class="text-danger" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="eventformat" class="form-label">EventFormat:</label>
|
||||
<InputSelect @bind-Value="@EventDefinition.EventFormat">
|
||||
@foreach (var format in Enum.GetValues(typeof(EventFormat)))
|
||||
{
|
||||
<option value="@format">@(@format.ToString())</option>
|
||||
}
|
||||
</InputSelect>
|
||||
<ValidationMessage For="() => EventDefinition.EventFormat" class="text-danger" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="minteamsize" class="form-label">MinTeamSize:</label>
|
||||
<InputNumber id="minteamsize" @bind-Value="EventDefinition.MinTeamSize" class="form-control" />
|
||||
<ValidationMessage For="() => EventDefinition.MinTeamSize" class="text-danger" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="maxteamsize" class="form-label">MaxTeamSize:</label>
|
||||
<InputNumber id="maxteamsize" @bind-Value="EventDefinition.MaxTeamSize" class="form-control" />
|
||||
<ValidationMessage For="() => EventDefinition.MaxTeamSize" class="text-danger" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="semifinalistactivity" class="form-label">SemifinalistActivity:</label>
|
||||
<InputText id="semifinalistactivity" @bind-Value="EventDefinition.SemifinalistActivity" class="form-control" />
|
||||
<ValidationMessage For="() => EventDefinition.SemifinalistActivity" class="text-danger" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="notes" class="form-label">Notes:</label>
|
||||
<InputText id="notes" @bind-Value="EventDefinition.Notes" class="form-control" />
|
||||
<ValidationMessage For="() => EventDefinition.Notes" class="text-danger" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="maxteamcountstate" class="form-label">MaxTeamCountState:</label>
|
||||
<InputNumber id="maxteamcountstate" @bind-Value="EventDefinition.MaxTeamCountState" class="form-control" />
|
||||
<ValidationMessage For="() => EventDefinition.MaxTeamCountState" class="text-danger" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="regionalevent" class="form-label">RegionalEvent:</label>
|
||||
<InputCheckbox id="regionalevent" @bind-Value="EventDefinition.RegionalEvent" class="form-check-input" />
|
||||
<ValidationMessage For="() => EventDefinition.RegionalEvent" class="text-danger" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="regionalpresubmit" class="form-label">RegionalPresubmit:</label>
|
||||
<InputCheckbox id="regionalpresubmit" @bind-Value="EventDefinition.RegionalPresubmit" class="form-check-input" />
|
||||
<ValidationMessage For="() => EventDefinition.RegionalPresubmit" class="text-danger" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="statepresubmission" class="form-label">StatePresubmission:</label>
|
||||
<InputCheckbox id="statepresubmission" @bind-Value="EventDefinition.StatePresubmission" class="form-check-input" />
|
||||
<ValidationMessage For="() => EventDefinition.StatePresubmission" class="text-danger" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="statepretesting" class="form-label">StatePretesting:</label>
|
||||
<InputCheckbox id="statepretesting" @bind-Value="EventDefinition.StatePretesting" class="form-check-input" />
|
||||
<ValidationMessage For="() => EventDefinition.StatePretesting" class="text-danger" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="statepreliminaryround" class="form-label">StatePreliminaryRound:</label>
|
||||
<InputCheckbox id="statepreliminaryround" @bind-Value="EventDefinition.StatePreliminaryRound" class="form-check-input" />
|
||||
<ValidationMessage For="() => EventDefinition.StatePreliminaryRound" class="text-danger" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="documentation" class="form-label">Documentation:</label>
|
||||
<InputText id="documentation" @bind-Value="EventDefinition.Documentation" class="form-control" />
|
||||
<ValidationMessage For="() => EventDefinition.Documentation" class="text-danger" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="eligibility" class="form-label">Eligibility:</label>
|
||||
<InputText id="eligibility" @bind-Value="EventDefinition.Eligibility" class="form-control" />
|
||||
<ValidationMessage For="() => EventDefinition.Eligibility" class="text-danger" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="theme" class="form-label">Theme:</label>
|
||||
<InputText id="theme" @bind-Value="EventDefinition.Theme" class="form-control" />
|
||||
<ValidationMessage For="() => EventDefinition.Theme" class="text-danger" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="description" class="form-label">Description:</label>
|
||||
<InputText id="description" @bind-Value="EventDefinition.Description" class="form-control" />
|
||||
<ValidationMessage For="() => EventDefinition.Description" class="text-danger" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="levelofeffort" class="form-label">LevelOfEffort:</label>
|
||||
<InputNumber id="levelofeffort" @bind-Value="EventDefinition.LevelOfEffort" class="form-control" />
|
||||
<ValidationMessage For="() => EventDefinition.LevelOfEffort" class="text-danger" />
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Create</button>
|
||||
</EditForm>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<a href="/events">Back to List</a>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[SupplyParameterFromForm]
|
||||
private EventDefinition EventDefinition { get; set; } = new();
|
||||
|
||||
// To protect from overposting attacks, see https://learn.microsoft.com/aspnet/core/blazor/forms/#mitigate-overposting-attacks.
|
||||
private async Task AddEventDefinition()
|
||||
{
|
||||
context.Events.Add(EventDefinition);
|
||||
await context.SaveChangesAsync();
|
||||
NavigationManager.NavigateTo("/events");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
@page "/events/delete"
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using Core.Entities
|
||||
@using Data
|
||||
@inject AppDbContext context
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<PageTitle>Delete</PageTitle>
|
||||
|
||||
<h1>Delete</h1>
|
||||
|
||||
<p>Are you sure you want to delete this?</p>
|
||||
<div>
|
||||
<h2>EventDefinition</h2>
|
||||
<hr />
|
||||
@if (eventdefinition is null)
|
||||
{
|
||||
<p><em>Loading...</em></p>
|
||||
}
|
||||
else {
|
||||
<dl class="row">
|
||||
<dt class="col-sm-2">Name</dt>
|
||||
<dd class="col-sm-10">@eventdefinition.Name</dd>
|
||||
</dl>
|
||||
<dl class="row">
|
||||
<dt class="col-sm-2">Short Name</dt>
|
||||
<dd class="col-sm-10">@eventdefinition.ShortName</dd>
|
||||
</dl>
|
||||
<dl class="row">
|
||||
<dt class="col-sm-2">Event Format</dt>
|
||||
<dd class="col-sm-10">@eventdefinition.EventFormat</dd>
|
||||
</dl>
|
||||
<dl class="row">
|
||||
<dt class="col-sm-2">Min Team Size</dt>
|
||||
<dd class="col-sm-10">@eventdefinition.MinTeamSize</dd>
|
||||
</dl>
|
||||
<dl class="row">
|
||||
<dt class="col-sm-2">Max Team Size</dt>
|
||||
<dd class="col-sm-10">@eventdefinition.MaxTeamSize</dd>
|
||||
</dl>
|
||||
<dl class="row">
|
||||
<dt class="col-sm-2">SemifinalistActivity</dt>
|
||||
<dd class="col-sm-10">@eventdefinition.SemifinalistActivity</dd>
|
||||
</dl>
|
||||
<dl class="row">
|
||||
<dt class="col-sm-2">Notes</dt>
|
||||
<dd class="col-sm-10">@eventdefinition.Notes</dd>
|
||||
</dl>
|
||||
<dl class="row">
|
||||
<dt class="col-sm-2">MaxTeamCountState</dt>
|
||||
<dd class="col-sm-10">@eventdefinition.MaxTeamCountState</dd>
|
||||
</dl>
|
||||
<dl class="row">
|
||||
<dt class="col-sm-2">RegionalEvent</dt>
|
||||
<dd class="col-sm-10">@eventdefinition.RegionalEvent</dd>
|
||||
</dl>
|
||||
<dl class="row">
|
||||
<dt class="col-sm-2">RegionalPresubmit</dt>
|
||||
<dd class="col-sm-10">@eventdefinition.RegionalPresubmit</dd>
|
||||
</dl>
|
||||
<dl class="row">
|
||||
<dt class="col-sm-2">StatePresubmission</dt>
|
||||
<dd class="col-sm-10">@eventdefinition.StatePresubmission</dd>
|
||||
</dl>
|
||||
<dl class="row">
|
||||
<dt class="col-sm-2">StatePretesting</dt>
|
||||
<dd class="col-sm-10">@eventdefinition.StatePretesting</dd>
|
||||
</dl>
|
||||
<dl class="row">
|
||||
<dt class="col-sm-2">StatePreliminaryRound</dt>
|
||||
<dd class="col-sm-10">@eventdefinition.StatePreliminaryRound</dd>
|
||||
</dl>
|
||||
<dl class="row">
|
||||
<dt class="col-sm-2">Documentation</dt>
|
||||
<dd class="col-sm-10">@eventdefinition.Documentation</dd>
|
||||
</dl>
|
||||
<dl class="row">
|
||||
<dt class="col-sm-2">Eligibility</dt>
|
||||
<dd class="col-sm-10">@eventdefinition.Eligibility</dd>
|
||||
</dl>
|
||||
<dl class="row">
|
||||
<dt class="col-sm-2">Theme</dt>
|
||||
<dd class="col-sm-10">@eventdefinition.Theme</dd>
|
||||
</dl>
|
||||
<dl class="row">
|
||||
<dt class="col-sm-2">Description</dt>
|
||||
<dd class="col-sm-10">@eventdefinition.Description</dd>
|
||||
</dl>
|
||||
<dl class="row">
|
||||
<dt class="col-sm-2">LevelOfEffort</dt>
|
||||
<dd class="col-sm-10">@eventdefinition.LevelOfEffort</dd>
|
||||
</dl>
|
||||
<EditForm method="post" Model="eventdefinition" OnValidSubmit="DeleteEventDefinition" FormName="delete" Enhance>
|
||||
<button type="submit" class="btn btn-danger" disabled="@(eventdefinition is null)">Delete</button> |
|
||||
<a href="/events">Back to List</a>
|
||||
</EditForm>
|
||||
}
|
||||
</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");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteEventDefinition()
|
||||
{
|
||||
context.Events.Remove(eventdefinition!);
|
||||
await context.SaveChangesAsync();
|
||||
NavigationManager.NavigateTo("/events");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
@page "/events/details"
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using Core.Entities
|
||||
@using Data
|
||||
@inject AppDbContext context
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<PageTitle>Details</PageTitle>
|
||||
|
||||
<h1>Details</h1>
|
||||
|
||||
<div>
|
||||
<h2>EventDefinition</h2>
|
||||
<hr />
|
||||
@if (eventdefinition is null)
|
||||
{
|
||||
<p><em>Loading...</em></p>
|
||||
}
|
||||
else {
|
||||
<dl class="row">
|
||||
<dt class="col-sm-2">Name</dt>
|
||||
<dd class="col-sm-10">@eventdefinition.Name</dd>
|
||||
<dt class="col-sm-2">ShortName</dt>
|
||||
<dd class="col-sm-10">@eventdefinition.ShortName</dd>
|
||||
<dt class="col-sm-2">EventFormat</dt>
|
||||
<dd class="col-sm-10">@eventdefinition.EventFormat</dd>
|
||||
<dt class="col-sm-2">MinTeamSize</dt>
|
||||
<dd class="col-sm-10">@eventdefinition.MinTeamSize</dd>
|
||||
<dt class="col-sm-2">MaxTeamSize</dt>
|
||||
<dd class="col-sm-10">@eventdefinition.MaxTeamSize</dd>
|
||||
<dt class="col-sm-2">SemifinalistActivity</dt>
|
||||
<dd class="col-sm-10">@eventdefinition.SemifinalistActivity</dd>
|
||||
<dt class="col-sm-2">Notes</dt>
|
||||
<dd class="col-sm-10">@eventdefinition.Notes</dd>
|
||||
<dt class="col-sm-2">MaxTeamCountState</dt>
|
||||
<dd class="col-sm-10">@eventdefinition.MaxTeamCountState</dd>
|
||||
<dt class="col-sm-2">RegionalEvent</dt>
|
||||
<dd class="col-sm-10">@eventdefinition.RegionalEvent</dd>
|
||||
<dt class="col-sm-2">RegionalPresubmit</dt>
|
||||
<dd class="col-sm-10">@eventdefinition.RegionalPresubmit</dd>
|
||||
<dt class="col-sm-2">StatePresubmission</dt>
|
||||
<dd class="col-sm-10">@eventdefinition.StatePresubmission</dd>
|
||||
<dt class="col-sm-2">StatePretesting</dt>
|
||||
<dd class="col-sm-10">@eventdefinition.StatePretesting</dd>
|
||||
<dt class="col-sm-2">StatePreliminaryRound</dt>
|
||||
<dd class="col-sm-10">@eventdefinition.StatePreliminaryRound</dd>
|
||||
<dt class="col-sm-2">Documentation</dt>
|
||||
<dd class="col-sm-10">@eventdefinition.Documentation</dd>
|
||||
<dt class="col-sm-2">Eligibility</dt>
|
||||
<dd class="col-sm-10">@eventdefinition.Eligibility</dd>
|
||||
<dt class="col-sm-2">Theme</dt>
|
||||
<dd class="col-sm-10">@eventdefinition.Theme</dd>
|
||||
<dt class="col-sm-2">Description</dt>
|
||||
<dd class="col-sm-10">@eventdefinition.Description</dd>
|
||||
<dt class="col-sm-2">LevelOfEffort</dt>
|
||||
<dd class="col-sm-10">@eventdefinition.LevelOfEffort</dd>
|
||||
</dl>
|
||||
<div>
|
||||
<a href="@($"/eventdefinitions/edit?id={eventdefinition.Id}")">Edit</a> |
|
||||
<a href="@($"/events")">Back to List</a>
|
||||
</div>
|
||||
}
|
||||
</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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
@page "/events/edit"
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using Core.Entities
|
||||
@using Data
|
||||
@inject AppDbContext context
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<PageTitle>Edit</PageTitle>
|
||||
|
||||
<h1>Edit</h1>
|
||||
|
||||
<h2>EventDefinition</h2>
|
||||
<hr />
|
||||
@if (EventDefinition is null)
|
||||
{
|
||||
<p><em>Loading...</em></p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<EditForm method="post" Model="EventDefinition" OnValidSubmit="UpdateEventDefinition" FormName="edit" Enhance>
|
||||
<DataAnnotationsValidator />
|
||||
<ValidationSummary role="alert"/>
|
||||
<input type="hidden" name="EventDefinition.Id" value="@EventDefinition.Id" />
|
||||
<div class="mb-3">
|
||||
<label for="name" class="form-label">Name:</label>
|
||||
<InputText id="name" @bind-Value="EventDefinition.Name" class="form-control" />
|
||||
<ValidationMessage For="() => EventDefinition.Name" class="text-danger" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="shortname" class="form-label">ShortName:</label>
|
||||
<InputText id="shortname" @bind-Value="EventDefinition.ShortName" class="form-control" />
|
||||
<ValidationMessage For="() => EventDefinition.ShortName" class="text-danger" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="eventformat" class="form-label">EventFormat:</label>
|
||||
<InputSelect @bind-Value="@EventDefinition.EventFormat">
|
||||
@foreach (var format in Enum.GetValues(typeof(EventFormat)))
|
||||
{
|
||||
<option value="@format">@(@format.ToString())</option>
|
||||
}
|
||||
</InputSelect>
|
||||
<ValidationMessage For="() => EventDefinition.EventFormat" class="text-danger" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="minteamsize" class="form-label">MinTeamSize:</label>
|
||||
<InputNumber id="minteamsize" @bind-Value="EventDefinition.MinTeamSize" class="form-control" />
|
||||
<ValidationMessage For="() => EventDefinition.MinTeamSize" class="text-danger" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="maxteamsize" class="form-label">MaxTeamSize:</label>
|
||||
<InputNumber id="maxteamsize" @bind-Value="EventDefinition.MaxTeamSize" class="form-control" />
|
||||
<ValidationMessage For="() => EventDefinition.MaxTeamSize" class="text-danger" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="semifinalistactivity" class="form-label">SemifinalistActivity:</label>
|
||||
<InputText id="semifinalistactivity" @bind-Value="EventDefinition.SemifinalistActivity" class="form-control" />
|
||||
<ValidationMessage For="() => EventDefinition.SemifinalistActivity" class="text-danger" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="notes" class="form-label">Notes:</label>
|
||||
<InputText id="notes" @bind-Value="EventDefinition.Notes" class="form-control" />
|
||||
<ValidationMessage For="() => EventDefinition.Notes" class="text-danger" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="maxteamcountstate" class="form-label">MaxTeamCountState:</label>
|
||||
<InputNumber id="maxteamcountstate" @bind-Value="EventDefinition.MaxTeamCountState" class="form-control" />
|
||||
<ValidationMessage For="() => EventDefinition.MaxTeamCountState" class="text-danger" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="regionalevent" class="form-label">RegionalEvent:</label>
|
||||
<InputCheckbox id="regionalevent" @bind-Value="EventDefinition.RegionalEvent" class="form-check-input" />
|
||||
<ValidationMessage For="() => EventDefinition.RegionalEvent" class="text-danger" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="regionalpresubmit" class="form-label">RegionalPresubmit:</label>
|
||||
<InputCheckbox id="regionalpresubmit" @bind-Value="EventDefinition.RegionalPresubmit" class="form-check-input" />
|
||||
<ValidationMessage For="() => EventDefinition.RegionalPresubmit" class="text-danger" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="statepresubmission" class="form-label">StatePresubmission:</label>
|
||||
<InputCheckbox id="statepresubmission" @bind-Value="EventDefinition.StatePresubmission" class="form-check-input" />
|
||||
<ValidationMessage For="() => EventDefinition.StatePresubmission" class="text-danger" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="statepretesting" class="form-label">StatePretesting:</label>
|
||||
<InputCheckbox id="statepretesting" @bind-Value="EventDefinition.StatePretesting" class="form-check-input" />
|
||||
<ValidationMessage For="() => EventDefinition.StatePretesting" class="text-danger" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="statepreliminaryround" class="form-label">StatePreliminaryRound:</label>
|
||||
<InputCheckbox id="statepreliminaryround" @bind-Value="EventDefinition.StatePreliminaryRound" class="form-check-input" />
|
||||
<ValidationMessage For="() => EventDefinition.StatePreliminaryRound" class="text-danger" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="documentation" class="form-label">Documentation:</label>
|
||||
<InputText id="documentation" @bind-Value="EventDefinition.Documentation" class="form-control" />
|
||||
<ValidationMessage For="() => EventDefinition.Documentation" class="text-danger" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="eligibility" class="form-label">Eligibility:</label>
|
||||
<InputText id="eligibility" @bind-Value="EventDefinition.Eligibility" class="form-control" />
|
||||
<ValidationMessage For="() => EventDefinition.Eligibility" class="text-danger" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="theme" class="form-label">Theme:</label>
|
||||
<InputText id="theme" @bind-Value="EventDefinition.Theme" class="form-control" />
|
||||
<ValidationMessage For="() => EventDefinition.Theme" class="text-danger" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="description" class="form-label">Description:</label>
|
||||
<InputText id="description" @bind-Value="EventDefinition.Description" class="form-control" />
|
||||
<ValidationMessage For="() => EventDefinition.Description" class="text-danger" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="levelofeffort" class="form-label">LevelOfEffort:</label>
|
||||
<InputNumber id="levelofeffort" @bind-Value="EventDefinition.LevelOfEffort" class="form-control" />
|
||||
<ValidationMessage For="() => EventDefinition.LevelOfEffort" class="text-danger" />
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
</EditForm>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div>
|
||||
<a href="/events">Back to List</a>
|
||||
</div>
|
||||
|
||||
@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 async Task UpdateEventDefinition()
|
||||
{
|
||||
context.Attach(EventDefinition!).State = EntityState.Modified;
|
||||
|
||||
try
|
||||
{
|
||||
await 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
@using Core.Entities
|
||||
@using Data
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@page "/events/descriptions"
|
||||
@inject IConfiguration Configuration
|
||||
@inject AppDbContext Context
|
||||
@rendermode InteractiveServer
|
||||
|
||||
<PageTitle>TSA Events @Configuration["ChapterSettings:CompetitionYear"]</PageTitle>
|
||||
|
||||
<h1>TSA Events @Configuration["ChapterSettings:CompetitionYear"]</h1>
|
||||
|
||||
@if (_events == null)
|
||||
{
|
||||
<p><em>Loading...</em></p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div>
|
||||
@foreach (var evt in _events)
|
||||
{
|
||||
<div class="container nobrk">
|
||||
@if (evt.RegionalEvent)
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<i>Regional Event</i>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div div class="row">
|
||||
<div class="col-4">
|
||||
<h5>@evt.Name</h5>
|
||||
</div>
|
||||
<div class="col-2">
|
||||
@if (evt.EventFormat is EventFormat.Team)
|
||||
{
|
||||
<html><strong>@evt.EventFormat</strong><br/>Size: <strong>@evt.TeamSize</strong></html>
|
||||
}
|
||||
else
|
||||
{
|
||||
<html>
|
||||
<strong>@evt.EventFormat</strong>
|
||||
</html>
|
||||
}
|
||||
|
||||
</div>
|
||||
<div class="col">
|
||||
Eligibility: @evt.Eligibility
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<strong> Effort</strong>: @evt.LevelOfEffort
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<strong>Activity</strong>: @evt.SemifinalistActivity
|
||||
</div>
|
||||
</div>
|
||||
<div div class="row mt-3">
|
||||
<div class="col">@evt.Description</div></div>
|
||||
@if (!string.IsNullOrEmpty(evt.Theme))
|
||||
{
|
||||
<div div class="row mt-2">
|
||||
<div class="col-3 text-center"><i>Theme for 2025-26:</i></div>
|
||||
<div class="col" style="white-space:pre-wrap;">@evt.Theme</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrEmpty(evt.Documentation))
|
||||
{
|
||||
<div div class="row mt-2">
|
||||
<div class="col-3 text-center"><i>Materials:</i></div>
|
||||
<div class="col">@evt.Documentation</div>
|
||||
</div>
|
||||
}
|
||||
<hr/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@code {
|
||||
private EventDefinition[]? _events = null;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_events = await Context.Events.OrderBy(e => e.Name).Where(e => e.Name != "Chapter Team").ToArrayAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
@page "/events"
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@inject AppDbContext Context
|
||||
|
||||
|
||||
<PageTitle>Events - TSA Chapter Organizer</PageTitle>
|
||||
|
||||
<MudText Typo="Typo.h3">Events</MudText>
|
||||
|
||||
<MudButton StartIcon="@Icons.Material.Filled.Create" Href="events/create">Create New</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" />
|
||||
<PropertyColumn Property="@(e => e.LevelOfEffort)" Title="Level of Effort" />
|
||||
<PropertyColumn Property="@(e => e.SemifinalistActivity)" Title="On-site Activity" />
|
||||
<PropertyColumn Property="@(e => e.RegionalEvent)" Title="Regional Event" />
|
||||
<TemplateColumn Title="Team Size" CellStyle="white-space:nowrap">
|
||||
<CellTemplate>
|
||||
[@context.Item.MinTeamSize - @context.Item.MaxTeamSize]
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="Teams State #">
|
||||
<CellTemplate>
|
||||
@context.Item.MaxTeamCountState
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn>
|
||||
<CellTemplate>
|
||||
<MudStack Row>
|
||||
<MudButtonGroup Size="Size.Small">
|
||||
<MudTooltip Text="Details">
|
||||
<MudIconButton Href="@($"/events/details?id={context.Item.Id}")" Icon="@Icons.Material.Filled.Description">Details</MudIconButton>
|
||||
</MudTooltip>
|
||||
<MudTooltip Text="Edit">
|
||||
<MudIconButton Href="@($"/events/edit?id={context.Item.Id}")" Icon="@Icons.Material.Filled.Edit">Edit</MudIconButton>
|
||||
</MudTooltip>
|
||||
<MudTooltip Text="Delete">
|
||||
<MudIconButton Href="@($"/events/delete?id={context.Item.Id}")" Icon="@Icons.Material.Filled.Delete" Color="@Color.Warning">Delete</MudIconButton>
|
||||
</MudTooltip>
|
||||
</MudButtonGroup>
|
||||
</MudStack>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
</Columns>
|
||||
<PagerContent>
|
||||
<MudDataGridPager T="EventDefinition"></MudDataGridPager>
|
||||
</PagerContent>
|
||||
</MudDataGrid>
|
||||
|
||||
@*
|
||||
<QuickGrid Class="table" Items="context.Events">
|
||||
<PropertyColumn Property="eventdefinition => eventdefinition.Name" />
|
||||
<PropertyColumn Property="eventdefinition => eventdefinition.EventFormat" />
|
||||
@* <PropertyColumn Property="eventdefinition => eventdefinition.MinTeamSize" />
|
||||
<PropertyColumn Property="eventdefinition => eventdefinition.MaxTeamSize" />
|
||||
<PropertyColumn Property="eventdefinition => eventdefinition.SemifinalistActivity" />
|
||||
<PropertyColumn Property="eventdefinition => eventdefinition.Notes" />
|
||||
<PropertyColumn Property="eventdefinition => eventdefinition.MaxTeamCountState" />
|
||||
<PropertyColumn Property="eventdefinition => eventdefinition.RegionalEvent" />
|
||||
<PropertyColumn Property="eventdefinition => eventdefinition.RegionalPresubmit" />
|
||||
<PropertyColumn Property="eventdefinition => eventdefinition.StatePresubmission" />
|
||||
<PropertyColumn Property="eventdefinition => eventdefinition.StatePretesting" />
|
||||
<PropertyColumn Property="eventdefinition => eventdefinition.StatePreliminaryRound" />
|
||||
<PropertyColumn Property="eventdefinition => eventdefinition.Documentation" />
|
||||
<PropertyColumn Property="eventdefinition => eventdefinition.Eligibility" />
|
||||
<PropertyColumn Property="eventdefinition => eventdefinition.Theme" />
|
||||
<PropertyColumn Property="eventdefinition => eventdefinition.Description" />
|
||||
<PropertyColumn Property="eventdefinition => eventdefinition.LevelOfEffort" />
|
||||
*@
|
||||
|
||||
@code {
|
||||
MudDataGrid<EventDefinition> _dataGrid = null!;
|
||||
|
||||
private async Task<GridData<EventDefinition>> ServerReload(GridState<EventDefinition> state)
|
||||
{
|
||||
|
||||
var query = Context.Events.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
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
@page "/"
|
||||
@inject IConfiguration Configuration
|
||||
|
||||
<PageTitle>Home</PageTitle>
|
||||
|
||||
<MudText Typo="Typo.h2">TSA Chapter Organizer</MudText>
|
||||
<MudText Typo="Typo.h3">@Configuration["ChapterSettings:Name"]</MudText>
|
||||
|
||||
<MudLink Href="events">
|
||||
<MudCard>
|
||||
<MudCardHeader>Events</MudCardHeader>
|
||||
</MudCard>
|
||||
</MudLink>
|
||||
@@ -0,0 +1,113 @@
|
||||
@page "/import"
|
||||
@using Core.Parsers
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@inject AppDbContext Context
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
<PageTitle>Import Data</PageTitle>
|
||||
|
||||
<h1>Import Data</h1>
|
||||
|
||||
<h3>Events</h3>
|
||||
<InputFile OnChange="UploadEvents"></InputFile>
|
||||
<text>@_events?.Length Events</text>
|
||||
<button class="btn btn-primary" @onclick="SaveEvents">Save to Database</button>
|
||||
<br/>
|
||||
|
||||
<h3>Students</h3>
|
||||
<InputFile OnChange="UploadStudents"></InputFile>
|
||||
<text>@_students?.Length Students</text>
|
||||
<button class="btn btn-primary" @onclick="SaveStudents">Save to Database</button>
|
||||
|
||||
@code {
|
||||
private EventDefinition[]? _events;
|
||||
private Student[]? _students;
|
||||
|
||||
async Task UploadEvents(InputFileChangeEventArgs arg)
|
||||
{
|
||||
await GetStreamReaderFromInputFile(arg, reader =>
|
||||
{
|
||||
var eventDefinitionParser = new EventDefinitionParser(reader);
|
||||
_events = eventDefinitionParser.Parse();
|
||||
});
|
||||
}
|
||||
|
||||
async Task SaveEvents()
|
||||
{
|
||||
if (_events == null)
|
||||
return;
|
||||
|
||||
foreach (var evt in _events)
|
||||
{
|
||||
// check if it already exists
|
||||
var exists
|
||||
= await Context.Events
|
||||
.FirstOrDefaultAsync(e => e.Name == evt.Name);
|
||||
if (exists != null)
|
||||
continue;
|
||||
await Context.Events.AddAsync(evt);
|
||||
}
|
||||
|
||||
await Context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
async Task UploadStudents(InputFileChangeEventArgs arg)
|
||||
{
|
||||
await GetStreamReaderFromInputFile(arg, reader =>
|
||||
{
|
||||
var studentParser = new StudentParser(reader);
|
||||
_students = studentParser.Parse();
|
||||
});
|
||||
}
|
||||
|
||||
async Task SaveStudents()
|
||||
{
|
||||
if (_students == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var student in _students)
|
||||
{
|
||||
// check if it already exists
|
||||
var exists
|
||||
= await Context.Students
|
||||
.FirstOrDefaultAsync(e
|
||||
=> e.FirstName == student.FirstName
|
||||
&& e.LastName == student.LastName);
|
||||
if (exists != null)
|
||||
continue;
|
||||
await Context.Students.AddAsync(student);
|
||||
}
|
||||
await Context.SaveChangesAsync();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
static async Task GetStreamReaderFromInputFile(InputFileChangeEventArgs arg, Action<StreamReader> f)
|
||||
{
|
||||
StreamReader? streamReader = null;
|
||||
try
|
||||
{
|
||||
var browserFile = arg.File;
|
||||
|
||||
await using var fs = browserFile.OpenReadStream();
|
||||
await using var ms = new MemoryStream();
|
||||
|
||||
await fs.CopyToAsync(ms);
|
||||
ms.Seek(0,0);
|
||||
streamReader = new StreamReader(ms);
|
||||
f(streamReader);
|
||||
}
|
||||
catch
|
||||
{
|
||||
streamReader?.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
@page "/students/create"
|
||||
@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 />
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<EditForm method="post" Model="Student" OnValidSubmit="AddStudent" FormName="create" Enhance>
|
||||
<DataAnnotationsValidator />
|
||||
<ValidationSummary class="text-danger" role="alert"/>
|
||||
<div class="mb-3">
|
||||
<label for="firstname" class="form-label">First Name:</label>
|
||||
<InputText id="firstname" @bind-Value="Student.FirstName" class="form-control" />
|
||||
<ValidationMessage For="() => Student.FirstName" class="text-danger" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="lastname" class="form-label">Last Name:</label>
|
||||
<InputText id="lastname" @bind-Value="Student.LastName" class="form-control" />
|
||||
<ValidationMessage For="() => Student.LastName" class="text-danger" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="grade" class="form-label">Grade:</label>
|
||||
<InputNumber id="grade" @bind-Value="Student.Grade" class="form-control" />
|
||||
<ValidationMessage For="() => Student.Grade" class="text-danger" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="tsayear" class="form-label">TSA Year:</label>
|
||||
<InputNumber id="tsayear" @bind-Value="Student.TsaYear" class="form-control" />
|
||||
<ValidationMessage For="() => Student.TsaYear" class="text-danger" />
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">Create</button>
|
||||
</EditForm>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<a href="/students">Back to List</a>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[SupplyParameterFromForm]
|
||||
private Student Student { get; set; } = new() { TsaYear = 1 };
|
||||
|
||||
private async Task AddStudent()
|
||||
{
|
||||
|
||||
Context.Students.Add(Student);
|
||||
await Context.SaveChangesAsync();
|
||||
NavigationManager.NavigateTo("/students");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
@page "/students/delete"
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@inject AppDbContext context
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<PageTitle>Delete Student - TSA Chapter Organizer</PageTitle>
|
||||
|
||||
<h1>Delete</h1>
|
||||
|
||||
<p>Are you sure you want to delete this?</p>
|
||||
<div>
|
||||
<h2>Student</h2>
|
||||
<hr />
|
||||
@if (student is null)
|
||||
{
|
||||
<p><em>Loading...</em></p>
|
||||
}
|
||||
else {
|
||||
<dl class="row">
|
||||
<dt class="col-sm-2">FirstName</dt>
|
||||
<dd class="col-sm-10">@student.FirstName</dd>
|
||||
</dl>
|
||||
<dl class="row">
|
||||
<dt class="col-sm-2">LastName</dt>
|
||||
<dd class="col-sm-10">@student.LastName</dd>
|
||||
</dl>
|
||||
<dl class="row">
|
||||
<dt class="col-sm-2">Grade</dt>
|
||||
<dd class="col-sm-10">@student.Grade</dd>
|
||||
</dl>
|
||||
<dl class="row">
|
||||
<dt class="col-sm-2">StateId</dt>
|
||||
<dd class="col-sm-10">@student.StateId</dd>
|
||||
</dl>
|
||||
<dl class="row">
|
||||
<dt class="col-sm-2">RegionalId</dt>
|
||||
<dd class="col-sm-10">@student.RegionalId</dd>
|
||||
</dl>
|
||||
<dl class="row">
|
||||
<dt class="col-sm-2">NationalId</dt>
|
||||
<dd class="col-sm-10">@student.NationalId</dd>
|
||||
</dl>
|
||||
<dl class="row">
|
||||
<dt class="col-sm-2">TsaYear</dt>
|
||||
<dd class="col-sm-10">@student.TsaYear</dd>
|
||||
</dl>
|
||||
<dl class="row">
|
||||
<dt class="col-sm-2">OfficerRole</dt>
|
||||
<dd class="col-sm-10">@student.OfficerRole</dd>
|
||||
</dl>
|
||||
<EditForm method="post" Model="student" OnValidSubmit="DeleteStudent" FormName="delete" Enhance>
|
||||
<button type="submit" class="btn btn-danger" disabled="@(student is null)">Delete</button> |
|
||||
<a href="/students">Back to List</a>
|
||||
</EditForm>
|
||||
}
|
||||
</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");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteStudent()
|
||||
{
|
||||
context.Students.Remove(student!);
|
||||
await context.SaveChangesAsync();
|
||||
NavigationManager.NavigateTo("/students");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
@page "/students/details"
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using Core.Entities
|
||||
@using Data
|
||||
@inject AppDbContext context
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<PageTitle>Student Details - TSA Chapter Organizer</PageTitle>
|
||||
|
||||
<h1>Details</h1>
|
||||
|
||||
<div>
|
||||
<h2>Student</h2>
|
||||
<hr />
|
||||
@if (student is null)
|
||||
{
|
||||
<p><em>Loading...</em></p>
|
||||
}
|
||||
else {
|
||||
<dl class="row">
|
||||
<dt class="col-sm-2">FirstName</dt>
|
||||
<dd class="col-sm-10">@student.FirstName</dd>
|
||||
<dt class="col-sm-2">LastName</dt>
|
||||
<dd class="col-sm-10">@student.LastName</dd>
|
||||
<dt class="col-sm-2">Grade</dt>
|
||||
<dd class="col-sm-10">@student.Grade</dd>
|
||||
<dt class="col-sm-2">Email</dt>
|
||||
<dd class="col-sm-10">@student.Email</dd>
|
||||
<dt class="col-sm-2">PhoneNumber</dt>
|
||||
<dd class="col-sm-10">@student.PhoneNumber</dd>
|
||||
<dt class="col-sm-2">TsaYear</dt>
|
||||
<dd class="col-sm-10">@student.TsaYear</dd>
|
||||
<dt class="col-sm-2">StateId</dt>
|
||||
<dd class="col-sm-10">@student.StateId</dd>
|
||||
<dt class="col-sm-2">RegionalId</dt>
|
||||
<dd class="col-sm-10">@student.RegionalId</dd>
|
||||
<dt class="col-sm-2">NationalId</dt>
|
||||
<dd class="col-sm-10">@student.NationalId</dd>
|
||||
<dt class="col-sm-2">OfficerRole</dt>
|
||||
<dd class="col-sm-10">@student.OfficerRole</dd>
|
||||
</dl>
|
||||
<div>
|
||||
<a href="@($"/students/edit?id={student.Id}")">Edit</a> |
|
||||
<a href="@($"/students")">Back to List</a>
|
||||
</div>
|
||||
}
|
||||
</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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
@page "/students/edit"
|
||||
@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>
|
||||
<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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@page "/students/event-ranking"
|
||||
@inject AppDbContext Context
|
||||
@rendermode InteractiveServer
|
||||
|
||||
<PageTitle>Student Event Ranks - TSA Chapter Organizer</PageTitle>
|
||||
|
||||
@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>1st</MudTh>
|
||||
<MudTh>2nd</MudTh>
|
||||
<MudTh>3rd</MudTh>
|
||||
<MudTh>4th</MudTh>
|
||||
<MudTh>5th</MudTh>
|
||||
<MudTh>6th</MudTh>
|
||||
<MudTh></MudTh>
|
||||
<MudTh>Warnings</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
|
||||
<MudTd>@context.FirstName</MudTd>
|
||||
<MudTh>@context.Grade</MudTh>
|
||||
<MudTh>@context.TsaYear</MudTh>
|
||||
|
||||
@for (var i = 1; i <= 6; i++)
|
||||
{
|
||||
var st = context.EventRankings.FirstOrDefault(e => e.Rank == i);
|
||||
<MudTd Class="@($"event-rank-{i})")">
|
||||
@if (st != null)
|
||||
{
|
||||
<span>@st.EventDefinition.ShortName
|
||||
@if(st.EventDefinition.EventFormat == EventFormat.Individual) { <span>ⓘ</span>}
|
||||
@if(st.EventDefinition.RegionalEvent) { <span>ⓡ</span>}
|
||||
@if(st.EventDefinition.OnSiteActivity) { <span>ⓐ</span>}
|
||||
</span>
|
||||
}
|
||||
</MudTd>
|
||||
}
|
||||
<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">
|
||||
<MudIcon Color="Color.Warning" Icon="@Icons.Material.Filled.LocalActivity"></MudIcon>
|
||||
</MudTooltip>
|
||||
}
|
||||
@if (!context.RankedEvents.Any(re => re.RegionalEvent))
|
||||
{
|
||||
<MudTooltip Text="No Regional Event">
|
||||
<MudIcon Color="Color.Warning" Icon="@Icons.Material.Filled.PinDrop"></MudIcon>
|
||||
</MudTooltip>
|
||||
}
|
||||
|
||||
@if (context.RankedEvents.All(re => re.EventFormat != EventFormat.Individual))
|
||||
{
|
||||
<MudTooltip Text="No Individual Event">
|
||||
<MudIcon Color="Color.Warning" Icon="@Icons.Material.Filled.Person"></MudIcon>
|
||||
</MudTooltip>
|
||||
}
|
||||
</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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using BlazorSortableList
|
||||
@using WebApp.Models
|
||||
@page "/students/event-ranking-edit/{StudentId:int}"
|
||||
@inject AppDbContext Context
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<PageTitle>Student Event Ranks - TSA Chapter Organizer</PageTitle>
|
||||
|
||||
<MudText Typo="Typo.h3">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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
@page "/students"
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using WebApp.Models
|
||||
@inject AppDbContext Context
|
||||
|
||||
<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="@Icons.Material.Filled.AddChart" 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.FirstName" Sortable="true">
|
||||
<CellTemplate>
|
||||
@context.Item.Name
|
||||
@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>
|
||||
<MudStack Row>
|
||||
<MudButtonGroup Size="Size.Small">
|
||||
<MudTooltip Text="Details">
|
||||
<MudIconButton Href="@($"/students/details?id={context.Item.Id}")" Icon="@Icons.Material.Filled.Description">Details</MudIconButton>
|
||||
</MudTooltip>
|
||||
<MudTooltip Text="Edit">
|
||||
<MudIconButton Href="@($"/students/edit?id={context.Item.Id}")" Icon="@Icons.Material.Filled.Edit">Edit</MudIconButton>
|
||||
</MudTooltip>
|
||||
<MudTooltip Text="Delete">
|
||||
<MudIconButton Href="@($"/students/delete?id={context.Item.Id}")" Icon="@Icons.Material.Filled.Delete" Color="@Color.Warning">Delete</MudIconButton>
|
||||
</MudTooltip>
|
||||
</MudButtonGroup>
|
||||
</MudStack>
|
||||
</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.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
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,365 @@
|
||||
@page "/teams/assignment"
|
||||
@using Core.Calculation
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using WebApp.Models
|
||||
@using EventAssignment = Core.Calculation.EventAssignment
|
||||
@inject AppDbContext Context
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<PageTitle>Event Assignment - TSA Chapter Organizer</PageTitle>
|
||||
|
||||
<MudText Typo="Typo.h3">Assignment</MudText>
|
||||
|
||||
|
||||
<MudText>Optimized team assignments based on the student event rankings</MudText>
|
||||
|
||||
<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">
|
||||
<MudIcon Color="Color.Warning" Icon="@Icons.Material.Filled.LocalActivity"></MudIcon>
|
||||
</MudTooltip>
|
||||
}
|
||||
@if (!context.HasRegionalEvent)
|
||||
{
|
||||
<MudTooltip Text="No Regional Event">
|
||||
<MudIcon Color="Color.Warning" Icon="@Icons.Material.Filled.PinDrop"></MudIcon>
|
||||
</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.RankedEvent(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
|
||||
@{
|
||||
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 Title="@($"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 Title="@($"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 Title="@($"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 Title="@($"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 Teams [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></MudTd>
|
||||
<MudTd Style="white-space:nowrap">@thresholds.StudentRankingCount, @thresholds.TeamCount × [@thresholds.LowerBound-@thresholds.UpperBound]</MudTd>
|
||||
|
||||
</RowTemplate>
|
||||
<ChildRowContent>
|
||||
<MudTr>
|
||||
<td colspan="2">
|
||||
@foreach (var student in
|
||||
context.Students
|
||||
.OrderBy(e =>
|
||||
e.EventRankings
|
||||
.Find(er => er.EventDefinition == context.Event)?.Rank ?? 10)
|
||||
.ThenBy(s => s.Grade + s.TsaYear))
|
||||
{
|
||||
var eventRank =
|
||||
student.EventRankings
|
||||
.Find(e => e.EventDefinition == context.Event)?.Rank;
|
||||
var color = AppIcons.RankedEvent(eventRank ?? 0);
|
||||
|
||||
<MudPaper Class="d-inline-flex pa-2 mx-3 my-1" Style="@($"background:{color};")">
|
||||
@student.FirstName
|
||||
</MudPaper>
|
||||
}
|
||||
<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>
|
||||
<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">
|
||||
<HeaderContent>
|
||||
<MudTh></MudTh>
|
||||
<MudTh>Student</MudTh>
|
||||
<MudTh>Event</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate Context="item">
|
||||
<MudTd Class="align-center">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.RemoveCircle"
|
||||
OnClick="() => RemoveRequireEvent(item)"></MudIconButton>
|
||||
</MudTd>
|
||||
<MudTd Class="align-center">@item.Student.FirstName</MudTd>
|
||||
<MudTd Class="align-center">
|
||||
@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>
|
||||
</MudGrid>
|
||||
<MudButton OnClick="Solve" Variant="Variant.Filled" Disabled="@_isSolving">Solve</MudButton>
|
||||
</MudPaper>
|
||||
|
||||
<MudButton StartIcon="@Icons.Material.Filled.Edit" Href="students/event-ranking">Edit Student Event Rankings</MudButton>
|
||||
|
||||
@code {
|
||||
public bool TestSwitch { get; set; } = false;
|
||||
|
||||
private readonly AssignmentParameters _parameters = new () {LimitTeamsToOne = false};
|
||||
|
||||
private List<EventDefinition>? _events;
|
||||
private List<Student>? _students;
|
||||
private List<EventAssignmentThresholds> _eventAssignmentThresholds = [];
|
||||
|
||||
MudTable<Team> _teamData;
|
||||
MudTable<StudentEventStatistics> _statisticData;
|
||||
private List<StudentEventStatistics> _statistics = [];
|
||||
MudTable<AssignmentRequirement> _assignmentRequirementData;
|
||||
private List<AssignmentRequirement> _assignmentRequirements = [];
|
||||
|
||||
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.Teams.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);
|
||||
}
|
||||
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
|
||||
return new TableData<Team> { Items = solution.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 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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
@page "/teams/create"
|
||||
@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>
|
||||
<DataAnnotationsValidator />
|
||||
<MudGrid>
|
||||
<MudItem xs="12" sm="7">
|
||||
<MudPaper Class="pa-4">
|
||||
<MudSelect T="EventDefinition" @bind-Value="@Team.Event" Label="Event">
|
||||
|
||||
@foreach (var evt in _events)
|
||||
{
|
||||
<MudSelectItem T="EventDefinition" Value="@(evt)"></MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
<MudTextField T="string" Label="Name" @bind-Value="Team.Name" For="@(() => Team.Name)"></MudTextField>
|
||||
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
<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;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_events =
|
||||
await Context.Events
|
||||
.OrderBy(e => e.Name)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
private async Task AddTeam()
|
||||
{
|
||||
Context.Teams.Add(Team);
|
||||
await Context.SaveChangesAsync();
|
||||
NavigationManager.NavigateTo("/teams");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
@page "/teams"
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using WebApp.Models
|
||||
@inject AppDbContext Context
|
||||
|
||||
<PageTitle>Teams</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>
|
||||
|
||||
<MudDataGrid T="Team" ServerData="ServerReload" @ref="_dataGrid" Filterable="true" RowsPerPage="25">
|
||||
<Columns>
|
||||
<PropertyColumn Property="@(e => e.Name)" Title="Name" />
|
||||
<PropertyColumn Property="@(e => e.Event.Name)" Title="Event" />
|
||||
@* <TemplateColumn Title="Name" SortBy="e => e.FirstName" Sortable="true">
|
||||
<CellTemplate>
|
||||
@context.Item.Name
|
||||
@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>
|
||||
<MudStack Row>
|
||||
<MudButtonGroup Size="Size.Small">
|
||||
<MudTooltip Text="Details">
|
||||
<MudIconButton Href="@($"/teams/details?id={context.Item.Id}")" Icon="@Icons.Material.Filled.Description">Details</MudIconButton>
|
||||
</MudTooltip>
|
||||
<MudTooltip Text="Edit">
|
||||
<MudIconButton Href="@($"/teams/edit?id={context.Item.Id}")" Icon="@Icons.Material.Filled.Edit">Edit</MudIconButton>
|
||||
</MudTooltip>
|
||||
<MudTooltip Text="Delete">
|
||||
<MudIconButton Href="@($"/teams/delete?id={context.Item.Id}")" Icon="@Icons.Material.Filled.Delete" Color="@Color.Warning">Delete</MudIconButton>
|
||||
</MudTooltip>
|
||||
</MudButtonGroup>
|
||||
</MudStack>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
</Columns>
|
||||
<PagerContent>
|
||||
<MudDataGridPager T="Student"></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)
|
||||
.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
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user