Add Blazor WebApp and rework data handling to utilize Entity Framework
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace WebApp
|
||||
{
|
||||
public class ChapterSettings
|
||||
{
|
||||
public required string Name { get; set; }
|
||||
public required string ShortName { get; set; }
|
||||
public required string RegionalId { get; set; }
|
||||
public required string StateId { get; set; }
|
||||
public required string NationalId { get; set; }
|
||||
public required string CompetitionYear { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<base href="/" />
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
|
||||
<link href="@Assets["_content/MudBlazor/MudBlazor.min.css"]" rel="stylesheet" />
|
||||
<link rel="stylesheet" href="app.css" />
|
||||
<link rel="stylesheet" href="WebApp.styles.css" />
|
||||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
<HeadOutlet />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<Routes @rendermode="InteractiveServer" />
|
||||
|
||||
<script src="_framework/blazor.web.js"></script>
|
||||
<script src="@Assets["_content/MudBlazor/MudBlazor.min.js"]"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,27 @@
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
<MudThemeProvider />
|
||||
<MudPopoverProvider />
|
||||
|
||||
<div class="page">
|
||||
|
||||
<div class="sidebar">
|
||||
<NavMenu/>
|
||||
</div>
|
||||
|
||||
<main>
|
||||
@* <div class="top-row px-4">
|
||||
<a href="https://learn.microsoft.com/aspnet/core/" target="_blank">About</a>
|
||||
</div> *@
|
||||
|
||||
<article class="content px-4">
|
||||
@Body
|
||||
</article>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<div id="blazor-error-ui">
|
||||
An unhandled error has occurred.
|
||||
<a href="" class="reload">Reload</a>
|
||||
<a class="dismiss">🗙</a>
|
||||
</div>
|
||||
@@ -0,0 +1,96 @@
|
||||
.page {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
|
||||
}
|
||||
|
||||
.top-row {
|
||||
background-color: #f7f7f7;
|
||||
border-bottom: 1px solid #d6d5d5;
|
||||
justify-content: flex-end;
|
||||
height: 3.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.top-row ::deep a, .top-row ::deep .btn-link {
|
||||
white-space: nowrap;
|
||||
margin-left: 1.5rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.top-row ::deep a:first-child {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
@media (max-width: 640.98px) {
|
||||
.top-row {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.top-row ::deep a, .top-row ::deep .btn-link {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 641px) {
|
||||
.page {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 250px;
|
||||
height: 100vh;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.top-row {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.top-row.auth ::deep a:first-child {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.top-row, article {
|
||||
padding-left: 2rem !important;
|
||||
padding-right: 1.5rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
#blazor-error-ui {
|
||||
background: lightyellow;
|
||||
bottom: 0;
|
||||
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
|
||||
display: none;
|
||||
left: 0;
|
||||
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#blazor-error-ui .dismiss {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 0.75rem;
|
||||
top: 0.5rem;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<div class="top-row ps-3 navbar navbar-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="">WebApp</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="checkbox" title="Navigation menu" class="navbar-toggler" />
|
||||
|
||||
<div class="nav-scrollable" onclick="document.querySelector('.navbar-toggler').click()">
|
||||
<nav class="flex-column">
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
|
||||
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
|
||||
</NavLink>
|
||||
</div>
|
||||
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="events">
|
||||
<span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Events
|
||||
</NavLink>
|
||||
</div>
|
||||
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="students">
|
||||
<span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Students
|
||||
</NavLink>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="teams">
|
||||
<span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Teams
|
||||
</NavLink>
|
||||
</div>
|
||||
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="import">
|
||||
<span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Import
|
||||
</NavLink>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
.navbar-toggler {
|
||||
appearance: none;
|
||||
cursor: pointer;
|
||||
width: 3.5rem;
|
||||
height: 2.5rem;
|
||||
color: white;
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
right: 1rem;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") no-repeat center/1.75rem rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.navbar-toggler:checked {
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.top-row {
|
||||
height: 3.5rem;
|
||||
background-color: rgba(0,0,0,0.4);
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.bi {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
margin-right: 0.75rem;
|
||||
top: -1px;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.bi-house-door-fill-nav-menu {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.bi-plus-square-fill-nav-menu {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.bi-list-nested-nav-menu {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
font-size: 0.9rem;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.nav-item:first-of-type {
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.nav-item:last-of-type {
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.nav-item ::deep .nav-link {
|
||||
color: #d7d7d7;
|
||||
background: none;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
height: 3rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 3rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nav-item ::deep a.active {
|
||||
background-color: rgba(255,255,255,0.37);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.nav-item ::deep .nav-link:hover {
|
||||
background-color: rgba(255,255,255,0.1);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.nav-scrollable {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.navbar-toggler:checked ~ .nav-scrollable {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media (min-width: 641px) {
|
||||
.navbar-toggler {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nav-scrollable {
|
||||
/* Never collapse the sidebar for wide screens */
|
||||
display: block;
|
||||
|
||||
/* Allow sidebar to scroll for tall menus */
|
||||
height: calc(100vh - 3.5rem);
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<Router AppAssembly="typeof(Program).Assembly">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
|
||||
<FocusOnNavigate RouteData="routeData" Selector="h1" />
|
||||
</Found>
|
||||
</Router>
|
||||
@@ -0,0 +1,13 @@
|
||||
@using System.Net.Http
|
||||
@using System.Net.Http.Json
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@using Microsoft.JSInterop
|
||||
@using WebApp
|
||||
@using WebApp.Components
|
||||
@using MudBlazor
|
||||
@using Core.Entities
|
||||
@using Data
|
||||
@@ -0,0 +1,57 @@
|
||||
using Core.Entities;
|
||||
using MudBlazor;
|
||||
|
||||
namespace WebApp.Models
|
||||
{
|
||||
public static class AppIcons
|
||||
{
|
||||
private const string Prefix = "@Icons.Material.Filled.";
|
||||
|
||||
public static string LevelOfEffortIcon(int loe)
|
||||
{
|
||||
|
||||
return loe switch
|
||||
{
|
||||
1 => MudBlazor.Icons.Material.Filled.StarBorder,
|
||||
2 => MudBlazor.Icons.Material.Filled.StarHalf,
|
||||
3 => MudBlazor.Icons.Material.Filled.Star,
|
||||
_ => MudBlazor.Icons.Material.Filled.QuestionMark
|
||||
};
|
||||
}
|
||||
public static string OfficerRoleIcon(OfficerRole officerRole)
|
||||
{
|
||||
return officerRole switch
|
||||
{
|
||||
OfficerRole.President => MudBlazor.Icons.Material.Filled.Gavel,
|
||||
OfficerRole.VicePresident => MudBlazor.Icons.Material.Filled.StarBorderPurple500,
|
||||
OfficerRole.Secretary => MudBlazor.Icons.Material.Filled.Draw,
|
||||
OfficerRole.Treasurer => MudBlazor.Icons.Material.Filled.Key,
|
||||
OfficerRole.Reporter => MudBlazor.Icons.Material.Filled.Mic,
|
||||
OfficerRole.SergeantAtArms => MudBlazor.Icons.Material.Filled.Handshake,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(officerRole), officerRole, null)
|
||||
};
|
||||
}
|
||||
|
||||
public static string RankedEvent(int rank)
|
||||
{
|
||||
return rank switch
|
||||
{
|
||||
1 => "#dd7e6b",
|
||||
2 => "#ea9999",
|
||||
3 => "#f9cb9c",
|
||||
4 => "#ffe599",
|
||||
5 => "#fff2cc",
|
||||
6 => "#fffaea",
|
||||
_ => "#ddd"
|
||||
};
|
||||
}
|
||||
/*
|
||||
* #dd7e6b;
|
||||
#ea9999;
|
||||
#f9cb9c;
|
||||
#ffe599;
|
||||
#fff2cc;
|
||||
#fffaea;
|
||||
*/
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using BlazorSortableList;
|
||||
using Core.Entities;
|
||||
|
||||
namespace WebApp.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Class SharedSortableListGroup.
|
||||
/// Used for BlazorSortableList
|
||||
/// </summary>
|
||||
internal class SharedSortableListGroup : MultiSortableListGroup<EventDefinition>
|
||||
{
|
||||
public SharedSortableListGroup(Action refreshComponent)
|
||||
: base(refreshComponent)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MudBlazor.Services;
|
||||
using WebApp.Components;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container.
|
||||
builder.Services.AddRazorComponents()
|
||||
.AddInteractiveServerComponents();
|
||||
|
||||
builder.Services.AddMudServices();
|
||||
|
||||
// Configure SQLite
|
||||
var connectionString = builder.Configuration.GetConnectionString("SQLiteDefault");
|
||||
builder.Services.AddDbContext<AppDbContext>(options => options.UseSqlite(connectionString));
|
||||
|
||||
builder.Services.AddQuickGridEntityFrameworkAdapter();
|
||||
|
||||
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (!app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseExceptionHandler("/Error", createScopeForErrors: true);
|
||||
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
||||
app.UseHsts();
|
||||
app.UseMigrationsEndPoint();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.UseStaticFiles();
|
||||
app.UseAntiforgery();
|
||||
|
||||
app.MapRazorComponents<App>()
|
||||
.AddInteractiveServerRenderMode();
|
||||
|
||||
app.Run();
|
||||
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"dotnetRunMessages": true,
|
||||
"applicationUrl": "http://localhost:5013"
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"dotnetRunMessages": true,
|
||||
"applicationUrl": "https://localhost:7235;http://localhost:5013"
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"Rank": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "students/event-ranking-edit/15",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"dotnetRunMessages": true,
|
||||
"applicationUrl": "https://localhost:7235;http://localhost:5013"
|
||||
},
|
||||
"Assignment": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "teams/assignment",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"dotnetRunMessages": true,
|
||||
"applicationUrl": "https://localhost:7235;http://localhost:5013"
|
||||
}
|
||||
},
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:31382",
|
||||
"sslPort": 44365
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BlazorSortableList" Version="2.1.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.QuickGrid.EntityFrameworkAdapter" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.8">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.8">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="9.0.0" />
|
||||
<PackageReference Include="MudBlazor" Version="8.12.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Core\Core.csproj" />
|
||||
<ProjectReference Include="..\Data\Data.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Components\Temp\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"ConnectionStrings": {
|
||||
"SQLiteDefault": "Data Source=ChapterOrganizer.db"
|
||||
},
|
||||
"Name" : "Test" ,
|
||||
"ChapterSettings": {
|
||||
"Name": "Robertsville Middle School",
|
||||
"ShortName": "RMS",
|
||||
"NationalId": "2227",
|
||||
"StateId": "12227",
|
||||
"RegionalId": "12227",
|
||||
"CompetitionYear": "2026"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
html, body {
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
a, .btn-link {
|
||||
color: #006bb7;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
color: #fff;
|
||||
background-color: #1b6ec2;
|
||||
border-color: #1861ac;
|
||||
}
|
||||
|
||||
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
|
||||
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding-top: 1.1rem;
|
||||
}
|
||||
|
||||
h1:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.valid.modified:not([type=checkbox]) {
|
||||
outline: 1px solid #26b050;
|
||||
}
|
||||
|
||||
.invalid {
|
||||
outline: 1px solid #e50000;
|
||||
}
|
||||
|
||||
.validation-message {
|
||||
color: #e50000;
|
||||
}
|
||||
|
||||
.blazor-error-boundary {
|
||||
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
|
||||
padding: 1rem 1rem 1rem 3.7rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.blazor-error-boundary::after {
|
||||
content: "An error has occurred."
|
||||
}
|
||||
|
||||
.darker-border-checkbox.form-check-input {
|
||||
border-color: #929292;
|
||||
}
|
||||
|
||||
|
||||
@media print {
|
||||
/* body .container {
|
||||
max-width: 1200px;
|
||||
}*/
|
||||
|
||||
main {
|
||||
font-size: 11px;
|
||||
margin: 30pt;
|
||||
color: #000;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
body .sidebar, main > div.top-row {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nobrk {
|
||||
break-inside: avoid;
|
||||
}
|
||||
}
|
||||
|
||||
.ranked-event-column > div:only-child{
|
||||
height:100%;
|
||||
}
|
||||
|
||||
.ranked-event-column > div > div:nth-child(1) > .mud-card-content { background-color: #dd7e6b; }
|
||||
.ranked-event-column > div > div:nth-child(2) > .mud-card-content { background-color: #ea9999; }
|
||||
.ranked-event-column > div > div:nth-child(3) > .mud-card-content { background-color: #f9cb9c; }
|
||||
.ranked-event-column > div > div:nth-child(4) > .mud-card-content { background-color: #ffe599; }
|
||||
.ranked-event-column > div > div:nth-child(5) > .mud-card-content { background-color: #fff2cc; }
|
||||
.ranked-event-column > div > div:nth-child(6) > .mud-card-content { background-color: #fffaea; }
|
||||
|
||||
.event-rank-1 { background-color: #dd7e6b; }
|
||||
.event-rank-2 { background-color: #ea9999; }
|
||||
.event-rank-3 { background-color: #f9cb9c; }
|
||||
.event-rank-4 { background-color: #ffe599; }
|
||||
.event-rank-5 { background-color: #fff2cc; }
|
||||
.event-rank-6 { background-color: #fffaea; }
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
Reference in New Issue
Block a user