Feature-based folder structure
1. Created feature-based folder structure - Components now organized by domain feature 2. Moved all components - 20+ files moved to new locations 3. Updated _Imports.razor - Added all new namespace paths for global component access 4. Updated CustomThemes.cs namespace - Changed from WebApp.Components.Layout to WebApp.Components.Shared.Layout 5. Removed old using directives - Cleaned up Login.razor and Routes.razor 6. Removed empty directories - Cleaned up old folder structure
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
@if (Title != null)
|
||||
{
|
||||
<MudText Typo="Typo.h4">@Title</MudText>
|
||||
}
|
||||
|
||||
<MudAutocomplete T="Student"
|
||||
Label="@Label"
|
||||
@bind-Value="_currentStudent"
|
||||
SearchFunc="@SearchStudents"
|
||||
ToStringFunc="@(s => ShowFullName ? s?.FirstNameLastName : s?.FirstName)"
|
||||
Immediate="true"
|
||||
ResetValueOnEmptyText="true"
|
||||
CoerceText="false"
|
||||
CoerceValue="false"
|
||||
AdornmentIcon="@Icons.Material.Filled.Search"
|
||||
Clearable="true">
|
||||
<ItemTemplate Context="student">
|
||||
@if (ShowFullName)
|
||||
{
|
||||
@student.FirstNameLastName
|
||||
}
|
||||
else
|
||||
{
|
||||
@student.FirstName
|
||||
}
|
||||
@if (ShowGrade)
|
||||
{
|
||||
<MudText Typo="Typo.caption" Color="Color.Secondary"> - Grade @student.Grade</MudText>
|
||||
}
|
||||
</ItemTemplate>
|
||||
</MudAutocomplete>
|
||||
|
||||
@if (SelectedStudents.Any())
|
||||
{
|
||||
<MudChipSet T="Student" AllClosable="true" Class="mt-2">
|
||||
@foreach (var student in SelectedStudents.OrderBy(s => s.FirstName))
|
||||
{
|
||||
<MudChip T="Student"
|
||||
Value="@student"
|
||||
Text="@(ShowFullName ? student.FirstNameLastName : student.FirstName)"
|
||||
OnClose="@(() => RemoveStudent(student))" />
|
||||
}
|
||||
</MudChipSet>
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public IEnumerable<Student> Students { get; set; } = [];
|
||||
|
||||
[Parameter]
|
||||
public IEnumerable<Student> SelectedStudents { get; set; } = [];
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<IEnumerable<Student>> SelectedStudentsChanged { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string? Title { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Label { get; set; } = "Search Students";
|
||||
|
||||
[Parameter]
|
||||
public bool ShowFullName { get; set; } = true;
|
||||
|
||||
[Parameter]
|
||||
public bool ShowGrade { get; set; } = false;
|
||||
|
||||
private Student? _currentStudent
|
||||
{
|
||||
get => _currentStudentValue;
|
||||
set
|
||||
{
|
||||
_currentStudentValue = value;
|
||||
if (value != null && !SelectedStudents.Contains(value))
|
||||
{
|
||||
var updatedList = SelectedStudents.Append(value).ToList();
|
||||
SelectedStudentsChanged.InvokeAsync(updatedList);
|
||||
_currentStudentValue = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Student? _currentStudentValue;
|
||||
|
||||
private Task<IEnumerable<Student>> SearchStudents(string? searchText, CancellationToken cancellationToken)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(searchText))
|
||||
return Task.FromResult<IEnumerable<Student>>(Students.Where(s => !SelectedStudents.Contains(s)));
|
||||
|
||||
var search = searchText.ToLower();
|
||||
return Task.FromResult<IEnumerable<Student>>(Students
|
||||
.Where(s => !SelectedStudents.Contains(s))
|
||||
.Where(s => s.FirstName.ToLower().Contains(search) ||
|
||||
s.LastName.ToLower().Contains(search))
|
||||
.OrderBy(s => s.FirstName));
|
||||
}
|
||||
|
||||
private void RemoveStudent(Student student)
|
||||
{
|
||||
var updatedList = SelectedStudents.Where(s => s.Id != student.Id).ToList();
|
||||
SelectedStudentsChanged.InvokeAsync(updatedList);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
@if (Title != null)
|
||||
{
|
||||
<MudText Typo="Typo.h4">@Title</MudText>
|
||||
}
|
||||
|
||||
<MudToggleGroup T="Student"
|
||||
SelectionMode="SelectionMode.MultiSelection"
|
||||
Values="@SelectedStudents"
|
||||
ValuesChanged="@OnSelectedStudentsChanged"
|
||||
Vertical="true"
|
||||
CheckMark>
|
||||
@foreach (var student in Students.OrderBy(e => e.FirstName))
|
||||
{
|
||||
<MudToggleItem Value="@student" Style="font-size: .75rem;">
|
||||
@if (ShowFullName)
|
||||
{
|
||||
@student.FirstNameLastName
|
||||
}
|
||||
else
|
||||
{
|
||||
@student.FirstName
|
||||
}
|
||||
</MudToggleItem>
|
||||
}
|
||||
</MudToggleGroup>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public IEnumerable<Student> Students { get; set; } = [];
|
||||
|
||||
[Parameter]
|
||||
public IEnumerable<Student> SelectedStudents { get; set; } = [];
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<IEnumerable<Student>> SelectedStudentsChanged { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string? Title { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool ShowFullName { get; set; } = true;
|
||||
|
||||
private async Task OnSelectedStudentsChanged(IEnumerable<Student> value)
|
||||
{
|
||||
SelectedStudents = value;
|
||||
await SelectedStudentsChanged.InvokeAsync(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
@page "/students/create"
|
||||
@attribute [Authorize]
|
||||
@inject AppDbContext Context
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<PageTitle>Create Student - TSA Chapter Organizer</PageTitle>
|
||||
|
||||
<MudText Typo="Typo.h3">Create</MudText>
|
||||
<MudText Typo="Typo.h4">Student</MudText>
|
||||
<MudDivider />
|
||||
|
||||
|
||||
<EditForm Model="Student" OnValidSubmit="OnValidSubmit" Enhance>
|
||||
<AntiforgeryToken />
|
||||
<DataAnnotationsValidator />
|
||||
<MudGrid>
|
||||
<MudItem xs="12" sm="7">
|
||||
<MudPaper Class="pa-4">
|
||||
<MudTextField T="string" Label="First Name" @bind-Value="Student.FirstName" For="@(() => Student.FirstName)"></MudTextField>
|
||||
<MudTextField T="string" Label="Last Name" @bind-Value="Student.LastName" For="@(() => Student.LastName)"></MudTextField>
|
||||
<MudTextField T="string" Label="Email Adress" @bind-Value="Student.Email" For="@(() => Student.Email)"></MudTextField>
|
||||
<MudTextField T="string" Label="Phone Number" @bind-Value="Student.PhoneNumber" For="@(() => Student.PhoneNumber)"></MudTextField>
|
||||
<MudTextField T="int" Label="Grade" @bind-Value="Student.Grade" For="@(() => Student.Grade)"></MudTextField>
|
||||
<MudTextField T="int" Label="TSA Year" @bind-Value="Student.TsaYear" For="@(() => Student.TsaYear)"></MudTextField>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
<MudButton StartIcon="@Icons.Material.Filled.ArrowBack" Href="students">Back</MudButton>
|
||||
<MudButton ButtonType="ButtonType.Submit" StartIcon="@Icons.Material.Filled.Save">Save</MudButton>
|
||||
</EditForm>
|
||||
|
||||
@code {
|
||||
[SupplyParameterFromForm]
|
||||
private Student Student { get; set; } = new() { TsaYear = 1 };
|
||||
|
||||
private void OnValidSubmit(EditContext context)
|
||||
{
|
||||
|
||||
Context.Students.Add(Student);
|
||||
Context.SaveChanges();
|
||||
NavigationManager.NavigateTo("/students");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
@page "/students/details"
|
||||
@attribute [Authorize]
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using Core.Entities
|
||||
@using Data
|
||||
@inject AppDbContext context
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<PageTitle>Student Details - TSA Chapter Organizer</PageTitle>
|
||||
|
||||
<MudText Typo="Typo.h3">Details</MudText>
|
||||
<MudText Typo="Typo.h4">Student</MudText>
|
||||
<MudDivider />
|
||||
|
||||
@if (student is null)
|
||||
{
|
||||
<MudText><em>Loading...</em></MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudPaper Class="pa-4 mt-4">
|
||||
<MudGrid>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudText Typo="Typo.subtitle2">First Name</MudText>
|
||||
<MudText>@student.FirstName</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudText Typo="Typo.subtitle2">Last Name</MudText>
|
||||
<MudText>@student.LastName</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudText Typo="Typo.subtitle2">Grade</MudText>
|
||||
<MudText>@student.Grade</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudText Typo="Typo.subtitle2">Email</MudText>
|
||||
<MudText>@student.Email</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudText Typo="Typo.subtitle2">Phone Number</MudText>
|
||||
<MudText>@student.PhoneNumber</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudText Typo="Typo.subtitle2">TSA Year</MudText>
|
||||
<MudText>@student.TsaYear</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudText Typo="Typo.subtitle2">State ID</MudText>
|
||||
<MudText>@student.StateId</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudText Typo="Typo.subtitle2">Regional ID</MudText>
|
||||
<MudText>@student.RegionalId</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudText Typo="Typo.subtitle2">National ID</MudText>
|
||||
<MudText>@student.NationalId</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudText Typo="Typo.subtitle2">Officer Role</MudText>
|
||||
<MudText>@student.OfficerRole</MudText>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudPaper>
|
||||
|
||||
<div class="mt-4">
|
||||
<MudButton StartIcon="@Icons.Material.Filled.Edit" Href="@($"/students/edit?id={student.Id}")" Variant="Variant.Filled" Color="Color.Primary">Edit</MudButton>
|
||||
<MudButton StartIcon="@Icons.Material.Filled.ArrowBack" Href="/students" Variant="Variant.Text">Back to List</MudButton>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
private Student? student;
|
||||
|
||||
[SupplyParameterFromQuery]
|
||||
private int Id { get; set; }
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
student = await context.Students.FirstOrDefaultAsync(m => m.Id == Id);
|
||||
|
||||
if (student is null)
|
||||
{
|
||||
NavigationManager.NavigateTo("notfound");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
@page "/students/edit"
|
||||
@attribute [Authorize]
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@inject AppDbContext Context
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<PageTitle>Edit Student - TSA Chapter Organizer</PageTitle>
|
||||
|
||||
<MudText Typo="Typo.h3">Edit</MudText>
|
||||
<MudText Typo="Typo.h4">Student@(@Student == null ? "" : $" ({Student.Name})")</MudText>
|
||||
|
||||
|
||||
|
||||
@if (Student is null)
|
||||
{
|
||||
<p><em>Loading...</em></p>
|
||||
}
|
||||
else
|
||||
{
|
||||
/* https://www.mudblazor.com/components/form */
|
||||
/* https://medium.com/@husainalbar/applying-mudblazor-for-crud-operations-in-our-blazor-project-a343037a52ef */
|
||||
<EditForm method="post" Model="Student" OnValidSubmit="UpdateStudent" FormName="edit" Enhance>
|
||||
<AntiforgeryToken />
|
||||
<DataAnnotationsValidator/>
|
||||
<MudGrid>
|
||||
<MudItem xs="12" sm="7">
|
||||
<MudPaper Class="pa-4">
|
||||
<MudTextField T="string" Label="First Name" @bind-Value="Student.FirstName" For="@(() => Student.FirstName)"></MudTextField>
|
||||
<MudTextField T="string" Label="Last Name" @bind-Value="Student.LastName" For="@(() => Student.LastName)"></MudTextField>
|
||||
<MudTextField T="string" Label="Email Adress" @bind-Value="Student.Email" For="@(() => Student.Email)"></MudTextField>
|
||||
<MudTextField T="string" Label="Phone Number" @bind-Value="Student.PhoneNumber" For="@(() => Student.PhoneNumber)"></MudTextField>
|
||||
<MudTextField T="int" Label="Grade" @bind-Value="Student.Grade" For="@(() => Student.Grade)"></MudTextField>
|
||||
<MudTextField T="int" Label="TSA Year" @bind-Value="Student.TsaYear" For="@(() => Student.TsaYear)"></MudTextField>
|
||||
<MudTextField T="string" Label="Regional Id" @bind-Value="Student.RegionalId" For="@(() => Student.RegionalId)"></MudTextField>
|
||||
<MudTextField T="string" Label="State Id" @bind-Value="Student.StateId" For="@(() => Student.StateId)"></MudTextField>
|
||||
<MudTextField T="string" Label="National Id" @bind-Value="Student.NationalId" For="@(() => Student.NationalId)"></MudTextField>
|
||||
<MudSelect T="OfficerRole?" @bind-Value="@Student.OfficerRole" Label="Officer Role">
|
||||
|
||||
<MudSelectItem T="OfficerRole?" Value="@null">-not an officer-</MudSelectItem>
|
||||
@foreach (var officerRole in Enum.GetValues(typeof(OfficerRole)).Cast<OfficerRole>())
|
||||
{
|
||||
<MudSelectItem T="OfficerRole?" Value="@(officerRole)"></MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
<MudButton StartIcon="@Icons.Material.Filled.ArrowBack" Href="students">Back</MudButton>
|
||||
<MudButton StartIcon="@Icons.Material.Filled.Save" OnClick="UpdateStudent">Save</MudButton>
|
||||
</EditForm>
|
||||
}
|
||||
|
||||
@code {
|
||||
[SupplyParameterFromQuery]
|
||||
private int Id { get; set; }
|
||||
|
||||
[SupplyParameterFromForm]
|
||||
private Student? Student { get; set; }
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
Student ??= await Context.Students.FirstOrDefaultAsync(m => m.Id == Id);
|
||||
|
||||
if (Student is null)
|
||||
{
|
||||
NavigationManager.NavigateTo("notfound");
|
||||
}
|
||||
}
|
||||
|
||||
// To protect from overposting attacks, enable the specific properties you want to bind to.
|
||||
// For more information, see https://learn.microsoft.com/aspnet/core/blazor/forms/#mitigate-overposting-attacks.
|
||||
private async Task UpdateStudent()
|
||||
{
|
||||
if (Student.OfficerRole == 0)
|
||||
Student.OfficerRole = null;
|
||||
|
||||
Context.Attach(Student!).State = EntityState.Modified;
|
||||
|
||||
try
|
||||
{
|
||||
await Context.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
if (!StudentExists(Student!.Id))
|
||||
{
|
||||
NavigationManager.NavigateTo("notfound");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
NavigationManager.NavigateTo("/students");
|
||||
}
|
||||
|
||||
private bool StudentExists(int id)
|
||||
{
|
||||
return Context.Students.Any(e => e.Id == id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
@page "/students/event-ranking"
|
||||
@attribute [Authorize]
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using WebApp.Models
|
||||
@inject AppDbContext Context
|
||||
@rendermode InteractiveServer
|
||||
|
||||
<PageTitle>Student Event Ranks - TSA Chapter Organizer</PageTitle>
|
||||
|
||||
<MudText Typo="Typo.h3"><MudIcon Icon="@AppIcons.EventRank"></MudIcon> Student Event Ranks</MudText>
|
||||
|
||||
@if (_students == null)
|
||||
{
|
||||
<p><em>Loading...</em></p>
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
<MudTable Items="_students" Hover="true" Breakpoint="Breakpoint.Sm" LoadingProgressColor="Color.Info">
|
||||
<HeaderContent>
|
||||
|
||||
<MudTh>Name</MudTh>
|
||||
<MudTh>Grade</MudTh>
|
||||
<MudTh>TSA Year</MudTh>
|
||||
<MudTh></MudTh>
|
||||
<MudTh>Warnings</MudTh>
|
||||
@for (var i = 1; i <= 10; i++)
|
||||
{
|
||||
var ii = i;
|
||||
<MudTh>@AppIcons.GetOrdinal(ii)</MudTh>
|
||||
}
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
|
||||
<MudTd>@context.FirstName</MudTd>
|
||||
<MudTh>@context.Grade</MudTh>
|
||||
<MudTh>@context.TsaYear</MudTh>
|
||||
<MudTd><MudButton StartIcon="@Icons.Material.Filled.TableChart" Href="@($"students/event-ranking-edit/{context.Id}")">Edit</MudButton></MudTd>
|
||||
<MudTd>
|
||||
@if (!context.RankedEvents.Any(re => re.OnSiteActivity))
|
||||
{
|
||||
<MudTooltip Text="No On-Site Activity">
|
||||
<MudText Style="font-weight:bolder" Color="Color.Warning">@AppIcons.OnSiteActivity</MudText>
|
||||
</MudTooltip>
|
||||
}
|
||||
@if (!context.RankedEvents.Any(re => re.RegionalEvent))
|
||||
{
|
||||
<MudTooltip Text="No Regional Event">
|
||||
<MudText Style="font-weight:bolder" Color="Color.Warning">@AppIcons.RegionalEvent</MudText>
|
||||
</MudTooltip>
|
||||
}
|
||||
|
||||
@if (context.RankedEvents.All(re => re.EventFormat != EventFormat.Individual))
|
||||
{
|
||||
<MudTooltip Text="No Individual Event">
|
||||
<MudText Style="font-weight:bolder" Color="Color.Warning">@AppIcons.IndividualEvent</MudText>
|
||||
</MudTooltip>
|
||||
}
|
||||
</MudTd>
|
||||
@for (var i = 1; i <= 10; i++)
|
||||
{
|
||||
var ii = i;
|
||||
var st = context.EventRankings.FirstOrDefault(e => e.Rank == i);
|
||||
<MudTd Class="@($"event-rank-{ii})")">
|
||||
@if (st != null)
|
||||
{
|
||||
<span>@st.EventDefinition.ShortName
|
||||
<EventAttributes EventDefinition="@st.EventDefinition"></EventAttributes>
|
||||
</span>
|
||||
}
|
||||
</MudTd>
|
||||
}
|
||||
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
<MudTable Items="_eventStudentRankings" Hover="true" Breakpoint="Breakpoint.Sm" LoadingProgressColor="Color.Info">
|
||||
<HeaderContent>
|
||||
<MudTh>Event</MudTh>
|
||||
<MudTh>Level of Effort</MudTh>
|
||||
<MudTh>Individual</MudTh>
|
||||
<MudTh>Regional</MudTh>
|
||||
<MudTh>On-site Activity</MudTh>
|
||||
<MudTh>Team Size</MudTh>
|
||||
@for (var i = 0; i < _maxEventStudentRankings; i++)
|
||||
{
|
||||
var i1 = i + 1;
|
||||
<MudTh>@i1</MudTh>
|
||||
}
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd>@context.Event.Name</MudTd>
|
||||
<MudTd>@context.Event.LevelOfEffort</MudTd>
|
||||
<MudTd>@if (context.Event.EventFormat == EventFormat.Individual) { <span>ⓘ</span> }</MudTd >
|
||||
<MudTd>@if (context.Event.RegionalEvent) { <span>ⓡ</span> }</MudTd >
|
||||
<MudTd>@if (context.Event.OnSiteActivity) { <span>ⓐ</span> }</MudTd >
|
||||
<MudTd>[@context.Event.MinTeamSize-@context.Event.MaxTeamSize]</MudTd >
|
||||
@for (var j = 0; j < _maxEventStudentRankings; j++)
|
||||
{
|
||||
var student = j < context.StudentRanking.Length ? context.StudentRanking[j] : null;
|
||||
var eventClass = student != null ? $"event-rank-{student.Item2}" : "";
|
||||
<MudTd Class="@eventClass">
|
||||
@if (student != null)
|
||||
{
|
||||
@student.Item1.FirstName
|
||||
}
|
||||
</MudTd>
|
||||
}
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
}
|
||||
@code {
|
||||
private Student[]? _students;
|
||||
|
||||
private class EventStudentRankings {
|
||||
public EventDefinition Event {get; set; }
|
||||
public Tuple<Student,int> [] StudentRanking { get; set; }
|
||||
}
|
||||
|
||||
private EventStudentRankings[] _eventStudentRankings;
|
||||
private int _maxEventStudentRankings;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_students =
|
||||
await Context.Students
|
||||
.Include(e => e.EventRankings)
|
||||
.ThenInclude(e => e.EventDefinition)
|
||||
.OrderBy(e => e.FirstName).ToArrayAsync();
|
||||
|
||||
_eventStudentRankings =
|
||||
_students.SelectMany(s =>
|
||||
s.EventRankings,
|
||||
(student, ranking) => new { e = ranking.EventDefinition, a = Tuple.Create(student, ranking.Rank) }
|
||||
)
|
||||
.GroupBy(e => e.e)
|
||||
.Select(e =>
|
||||
new EventStudentRankings
|
||||
{
|
||||
Event = e.Key,
|
||||
StudentRanking = e.Select(er => er.a).OrderBy(ser => ser.Item2).ThenByDescending(ser => ser.Item1.Grade + ser.Item1.TsaYear).ToArray()
|
||||
})
|
||||
.OrderBy(e => e.Event.Name)
|
||||
.ToArray();
|
||||
|
||||
var events = await Context.Events.ToArrayAsync();
|
||||
var remainingEvents =
|
||||
events
|
||||
.Where(e => _eventStudentRankings.All(est => est.Event.Id != e.Id))
|
||||
.Select(e => new EventStudentRankings { Event = e, StudentRanking = Array.Empty<Tuple<Student, int>>() })
|
||||
.OrderBy(e => e.Event.Name)
|
||||
.ToArray();
|
||||
|
||||
_eventStudentRankings = _eventStudentRankings.Concat(remainingEvents).ToArray();
|
||||
|
||||
_maxEventStudentRankings = _eventStudentRankings.Max(esr => esr.StudentRanking.Length);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
@page "/students/event-ranking-edit/{StudentId:int}"
|
||||
@attribute [Authorize]
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using BlazorSortableList
|
||||
@using WebApp.Models
|
||||
@inject AppDbContext Context
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<PageTitle>Student Event Ranks - TSA Chapter Organizer</PageTitle>
|
||||
|
||||
<MudText Typo="Typo.h3"><MudIcon Icon="@AppIcons.EventRank"></MudIcon> Student Event Ranks</MudText>
|
||||
|
||||
<div>
|
||||
@if (_student == null)
|
||||
{
|
||||
<p><em>Loading...</em></p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Typo="Typo.h4">@_student.Name</MudText>
|
||||
<MudText Color="Color.Warning">Warning: drag and drop is currently a bit squirrely - double check!</MudText>
|
||||
|
||||
<MudButton StartIcon="@Icons.Material.Filled.ArrowBack" Href="students/event-ranking">Back</MudButton>
|
||||
<MudButton StartIcon="@Icons.Material.Filled.Save" OnClick="Save">Save</MudButton>
|
||||
|
||||
/* https://github.com/AlexNek/BlazorSortableList */
|
||||
<MudGrid>
|
||||
<MudItem xs="6" md="4" xl="3" Class="ranked-event-column">
|
||||
<SortableList
|
||||
Group="GroupId" Id="ListId1" Context="item"
|
||||
Items="_rankedEvents" OnRemove="RankedEventsRemove" OnUpdate="Update">
|
||||
<SortableItemTemplate>
|
||||
<MudCard Outlined="true">
|
||||
<MudCardContent>@item.Name</MudCardContent>
|
||||
</MudCard>
|
||||
</SortableItemTemplate>
|
||||
</SortableList>
|
||||
</MudItem>
|
||||
<MudItem xs="6" md="4" xl="3">
|
||||
<SortableList
|
||||
Group="GroupId" Id="ListId2" Context="item"
|
||||
Items="_availableEvents" OnRemove="AvailableEventsRemove" Sort="false">
|
||||
<SortableItemTemplate>
|
||||
<MudCard Outlined="true">
|
||||
<MudCardContent>@item.Name</MudCardContent>
|
||||
</MudCard>
|
||||
</SortableItemTemplate>
|
||||
|
||||
</SortableList>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
}
|
||||
</div>
|
||||
|
||||
@code {
|
||||
|
||||
private const string ListId1 = "SharedListId1";
|
||||
private const string ListId2 = "SharedListId2";
|
||||
private const string GroupId = "CommonGroup";
|
||||
|
||||
[Parameter] public int? StudentId { get; set; }
|
||||
|
||||
private Student? _student;
|
||||
private List<EventDefinition>? _events;
|
||||
|
||||
public List<EventDefinition> _rankedEvents = [];
|
||||
public List<EventDefinition> _availableEvents = [];
|
||||
|
||||
SharedSortableListGroup _group;
|
||||
|
||||
private void RankedEventsRemove((int oldIndex, int newIndex) indices)
|
||||
{
|
||||
// get the item at the old index in list 1
|
||||
var item = _rankedEvents[indices.oldIndex];
|
||||
|
||||
// add it to the new index in list 2
|
||||
_availableEvents.Insert(indices.newIndex, item);
|
||||
|
||||
// remove the item from the old index in list 1
|
||||
_rankedEvents.Remove(_rankedEvents[indices.oldIndex]);
|
||||
}
|
||||
|
||||
private void AvailableEventsRemove((int oldIndex, int newIndex) indices)
|
||||
{
|
||||
// get the item at the old index in list 2
|
||||
var item = _availableEvents[indices.oldIndex];
|
||||
|
||||
// add it to the new index in list 1
|
||||
_rankedEvents.Insert(indices.newIndex, item);
|
||||
|
||||
// remove the item from the old index in list 2
|
||||
_availableEvents.Remove(_availableEvents[indices.oldIndex]);
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_student =
|
||||
await Context.Students
|
||||
.Include(e => e.EventRankings)
|
||||
.Where(e => e.Id == StudentId).FirstAsync();
|
||||
_events =
|
||||
await Context.Events
|
||||
.OrderBy(e => e.Name)
|
||||
.ToListAsync();
|
||||
|
||||
_rankedEvents = _student.EventRankings.OrderBy(e => e.Rank).Select(e => e.EventDefinition).ToList();
|
||||
_availableEvents = _events.Where(e => !_rankedEvents.Contains(e)).ToList();
|
||||
|
||||
_group = new SharedSortableListGroup(StateHasChanged);
|
||||
_group.AddModel(ListId1, new SortableListModel<EventDefinition>(_rankedEvents) { Group = GroupId });
|
||||
_group.AddModel(ListId2, new SortableListModel<EventDefinition>(_availableEvents) { Group = GroupId });
|
||||
}
|
||||
|
||||
private void Update((int oldIndex, int newIndex) indices)
|
||||
{
|
||||
var (oldIndex, newIndex) = indices;
|
||||
|
||||
var items = _rankedEvents;
|
||||
var itemToMove = items[oldIndex];
|
||||
items.RemoveAt(oldIndex);
|
||||
|
||||
if (newIndex < items.Count)
|
||||
{
|
||||
items.Insert(newIndex, itemToMove);
|
||||
}
|
||||
else
|
||||
{
|
||||
items.Add(itemToMove);
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
|
||||
async Task Save()
|
||||
{
|
||||
if (_student == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
_student.EventRankings.Clear();
|
||||
for (var index = 0; index < _rankedEvents.Count; index++)
|
||||
{
|
||||
var evt = _rankedEvents[index];
|
||||
_student.EventRankings.Add(new StudentEventRanking
|
||||
{
|
||||
EventDefinition = evt,
|
||||
Student = _student,
|
||||
Rank = index + 1
|
||||
});
|
||||
}
|
||||
|
||||
Context.Students.Update(_student);
|
||||
|
||||
await Context.SaveChangesAsync();
|
||||
|
||||
NavigationManager.NavigateTo("/students/event-ranking");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
@page "/students"
|
||||
@attribute [Authorize]
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using WebApp.Models
|
||||
@inject AppDbContext Context
|
||||
@inject IDialogService DialogService
|
||||
@inject ISnackbar Snackbar
|
||||
|
||||
<PageTitle>Students - TSA Chapter Organizer</PageTitle>
|
||||
|
||||
<MudText Typo="Typo.h3">Students</MudText>
|
||||
|
||||
<MudButton StartIcon="@Icons.Material.Filled.Create" Href="students/create">Create New</MudButton>
|
||||
<MudButton StartIcon="@AppIcons.EventRank" Href="students/event-ranking">Event Rankings</MudButton>
|
||||
|
||||
<MudDataGrid T="Student" ServerData="ServerReload" @ref="_dataGrid" Filterable="true" RowsPerPage="25">
|
||||
<Columns>
|
||||
@* <PropertyColumn Property="@(e => e.Name)" Title="First Name" SortBy="e => e.FirstName" /> *@
|
||||
<TemplateColumn Title="Name" SortBy="e => e.LastName" Sortable="true" >
|
||||
<CellTemplate>
|
||||
@context.Item.LastNameFirstName
|
||||
@if (context.Item.OfficerRole != null)
|
||||
{
|
||||
<MudChip T="string" Icon="@(AppIcons.OfficerRoleIcon(context.Item.OfficerRole.Value))">@context.Item.OfficerRole</MudChip>
|
||||
}
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="Grade (TSA Year)" SortBy="e => e.Grade" Sortable="true">
|
||||
<CellTemplate>
|
||||
@context.Item.Grade (@context.Item.TsaYear)
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn>
|
||||
<CellTemplate>
|
||||
<CrudActions DetailsHref="@($"/students/details?id={context.Item!.Id}")"
|
||||
EditHref="@($"/students/edit?id={context.Item!.Id}")"
|
||||
DeleteOnClick="() => DeleteStudent(context.Item!)">
|
||||
</CrudActions>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
</Columns>
|
||||
<PagerContent>
|
||||
<MudDataGridPager T="Student"></MudDataGridPager>
|
||||
</PagerContent>
|
||||
</MudDataGrid>
|
||||
|
||||
@code {
|
||||
MudDataGrid<Student> _dataGrid = null!;
|
||||
|
||||
private async Task<GridData<Student>> ServerReload(GridState<Student> state)
|
||||
{
|
||||
|
||||
var query =
|
||||
Context.Students.OrderBy(e => e.LastName)
|
||||
.Where(state.FilterDefinitions).OrderBy(state.SortDefinitions);
|
||||
|
||||
var totalItems = await query.CountAsync();
|
||||
var pagedData = await query.Skip(state.Page * state.PageSize).Take(state.PageSize).ToArrayAsync();
|
||||
|
||||
return new GridData<Student>
|
||||
{
|
||||
TotalItems = totalItems,
|
||||
Items = pagedData
|
||||
};
|
||||
}
|
||||
|
||||
private async Task DeleteStudent(Student student)
|
||||
{
|
||||
//_isRowBlocked = true;
|
||||
|
||||
var result = await DialogService
|
||||
.ShowMessageBox("Delete student",
|
||||
(MarkupString)$"Are you sure want to delete <b>{student.Name}</b>? This cannot be undone.",
|
||||
yesText:"Yes",
|
||||
noText:"Cancel");
|
||||
|
||||
if (result == true)
|
||||
{
|
||||
Context.Students.Remove(student!);
|
||||
await Context.SaveChangesAsync();
|
||||
Snackbar.Add($"Delete event: Delete of Student {student.Name}", Severity.Info);
|
||||
}
|
||||
|
||||
//_isRowBlocked = false;
|
||||
StateHasChanged();
|
||||
await _dataGrid.ReloadServerData();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user