947d95893f
This commit enhances the Home.razor and Notes.razor components by restructuring the note display logic. The Home component now correctly wraps the pinned notes section in a MudPaper component, ensuring consistent styling. The Notes component has been updated to utilize ClientSidePagination for better performance and user experience, replacing the previous pagination logic. This change simplifies the code and improves the overall maintainability of the note management interface.
300 lines
10 KiB
Plaintext
300 lines
10 KiB
Plaintext
@page "/"
|
|
@attribute [Authorize]
|
|
@using Microsoft.EntityFrameworkCore
|
|
@using WebApp.Models
|
|
@using Core.Entities
|
|
@using WebApp.Services
|
|
@using WebApp.Components.Shared.Components
|
|
@inject IConfiguration Configuration
|
|
@inject AppDbContext Context
|
|
@inject INotesService NotesService
|
|
@inject IDialogService DialogService
|
|
@implements IAsyncDisposable
|
|
|
|
<PageTitle>@Configuration["ChapterSettings:Name"] - TSA Chapter Organizer</PageTitle>
|
|
|
|
<MudPaper Elevation="0" Class="mb-6">
|
|
<div class="d-flex flex-column align-center text-center mb-4">
|
|
<MudImage Fluid="true" Src="TCO_Title.png" Alt="TSA Chapter Organizer" Class="mb-4" Style="width: 100%; max-width: 600px; height: auto;" />
|
|
<MudHidden Breakpoint="Breakpoint.SmAndDown">
|
|
<MudText Typo="Typo.h3" Class="mb-2">
|
|
<strong>@Configuration["ChapterSettings:Name"]</strong>
|
|
</MudText>
|
|
</MudHidden>
|
|
<MudHidden Breakpoint="Breakpoint.MdAndUp">
|
|
<MudText Typo="Typo.h5" Class="mb-2">
|
|
<strong>@Configuration["ChapterSettings:Name"]</strong>
|
|
</MudText>
|
|
</MudHidden>
|
|
<MudText Typo="Typo.h6" Color="Color.Info">
|
|
@Configuration["ChapterSettings:CompetitionYear"] Competition Year
|
|
</MudText>
|
|
</div>
|
|
</MudPaper>
|
|
|
|
@if (_pinnedNotes.Any())
|
|
{
|
|
<MudPaper Elevation="0" Class="mb-4">
|
|
<MudGrid>
|
|
@foreach (var note in _pinnedNotes)
|
|
{
|
|
<NoteCard Note="@note" OnNoteChanged="HandleNoteChanged" />
|
|
}
|
|
</MudGrid>
|
|
</MudPaper>
|
|
}
|
|
|
|
@if (!_hasStudents)
|
|
{
|
|
<!-- Getting Started: No students yet -->
|
|
<MudPaper Elevation="0" Class="mb-4">
|
|
<MudText Typo="Typo.h4" Color="Color.Primary">Getting Started</MudText>
|
|
<MudText Typo="Typo.body2" Color="Color.Secondary">Add your chapter's students to begin</MudText>
|
|
</MudPaper>
|
|
<MudGrid>
|
|
<DashboardCard Icon="@AppIcons.Student"
|
|
Title="Students"
|
|
Count="@_studentCount"
|
|
Subtitle="Add Students"
|
|
NavigateUrl="/students"
|
|
Emphasized="true">
|
|
<MudText Typo="Typo.caption" Class="mt-2">Import or add your chapter roster</MudText>
|
|
</DashboardCard>
|
|
|
|
<DashboardCard Icon="@AppIcons.Events"
|
|
Title="Events"
|
|
Count="@_eventCount"
|
|
Subtitle="Total Events"
|
|
Caption="@($"{_teamEventsCount} Team | {_individualEventsCount} Individual")"
|
|
NavigateUrl="/events" />
|
|
</MudGrid>
|
|
}
|
|
else if (!_hasTeams)
|
|
{
|
|
<!-- Team Building: Students exist but no teams yet -->
|
|
<MudPaper Elevation="0" Class="mb-4">
|
|
<MudText Typo="Typo.h4" Color="Color.Primary">Team Building</MudText>
|
|
<MudText Typo="Typo.body2" Color="Color.Secondary">Collect event rankings and build your teams</MudText>
|
|
</MudPaper>
|
|
<MudGrid>
|
|
<DashboardCard Icon="@AppIcons.EventRank"
|
|
Title="Event Ranking"
|
|
Caption="Collect student event preferences"
|
|
NavigateUrl="/students/event-ranking"
|
|
Emphasized="true"/>
|
|
|
|
<DashboardCard Icon="@AppIcons.TeamAssignment"
|
|
Title="Team Assignment"
|
|
Caption="Build optimal teams"
|
|
NavigateUrl="/teams/assignment"
|
|
Emphasized="true"/>
|
|
</MudGrid>
|
|
|
|
<MudPaper Elevation="0" Class="my-4">
|
|
<MudText Typo="Typo.h4">Chapter Data</MudText>
|
|
</MudPaper>
|
|
<MudGrid>
|
|
<DashboardCard Icon="@AppIcons.Student"
|
|
Title="Students"
|
|
Count="@_studentCount"
|
|
Subtitle="Active Students"
|
|
NavigateUrl="/students">
|
|
@if (!string.IsNullOrEmpty(_gradeDistribution) && _gradeDistribution != "No students yet")
|
|
{
|
|
<MudStack>
|
|
<MudText Typo="Typo.caption">@((MarkupString)_gradeDistribution)</MudText>
|
|
</MudStack>
|
|
}
|
|
</DashboardCard>
|
|
|
|
<DashboardCard Icon="@AppIcons.Events"
|
|
Title="Events"
|
|
Count="@_eventCount"
|
|
Subtitle="Total Events"
|
|
Caption="@($"{_teamEventsCount} Team | {_individualEventsCount} Individual")"
|
|
NavigateUrl="/events" />
|
|
</MudGrid>
|
|
}
|
|
else
|
|
{
|
|
<!-- Normal view: Students and teams exist -->
|
|
<MudPaper Elevation="0" Class="mb-4">
|
|
<MudText Typo="Typo.h4">Scheduling</MudText>
|
|
</MudPaper>
|
|
<MudGrid>
|
|
<DashboardCard Icon="@AppIcons.Scheduler"
|
|
Title="Meeting Schedule"
|
|
Caption="Optimize meeting times"
|
|
NavigateUrl="/meeting-schedule"/>
|
|
|
|
<DashboardCard Icon="@AppIcons.EventCalendar"
|
|
Title="Event Calendar"
|
|
Caption="Conference schedules"
|
|
NavigateUrl="/calendar"/>
|
|
</MudGrid>
|
|
|
|
<MudPaper Elevation="0" Class="my-4">
|
|
<MudText Typo="Typo.h4">Teams & Registration</MudText>
|
|
</MudPaper>
|
|
<MudGrid>
|
|
<DashboardCard Icon="@AppIcons.Teams"
|
|
Title="Teams"
|
|
Count="@_teamCount"
|
|
Subtitle="Total Teams"
|
|
Caption="@($"{_groupTeamsCount} Team | {_individualTeamsCount} Individual")"
|
|
NavigateUrl="/teams" />
|
|
|
|
<DashboardCard Icon="@AppIcons.Registration"
|
|
Title="Registration"
|
|
Caption="View student registrations"
|
|
NavigateUrl="/students/teams"/>
|
|
</MudGrid>
|
|
|
|
<MudPaper Elevation="0" Class="my-4">
|
|
<MudText Typo="Typo.h4">Team Building</MudText>
|
|
</MudPaper>
|
|
<MudGrid>
|
|
<DashboardCard Icon="@AppIcons.EventRank"
|
|
Title="Event Ranking"
|
|
Caption="Student event preferences"
|
|
NavigateUrl="/students/event-ranking"/>
|
|
|
|
<DashboardCard Icon="@AppIcons.TeamAssignment"
|
|
Title="Team Assignment"
|
|
Caption="Build optimal teams"
|
|
NavigateUrl="/teams/assignment"/>
|
|
</MudGrid>
|
|
|
|
<MudPaper Elevation="0" Class="my-4">
|
|
<MudText Typo="Typo.h4">Chapter Data</MudText>
|
|
</MudPaper>
|
|
<MudGrid>
|
|
<DashboardCard Icon="@AppIcons.Student"
|
|
Title="Students"
|
|
Count="@_studentCount"
|
|
Subtitle="Active Students"
|
|
NavigateUrl="/students">
|
|
@if (!string.IsNullOrEmpty(_gradeDistribution) && _gradeDistribution != "No students yet")
|
|
{
|
|
<MudStack>
|
|
<MudText Typo="Typo.caption">@((MarkupString)_gradeDistribution)</MudText>
|
|
</MudStack>
|
|
}
|
|
</DashboardCard>
|
|
|
|
<DashboardCard Icon="@AppIcons.Events"
|
|
Title="Events"
|
|
Count="@_eventCount"
|
|
Subtitle="Total Events"
|
|
Caption="@($"{_teamEventsCount} Team | {_individualEventsCount} Individual")"
|
|
NavigateUrl="/events" />
|
|
</MudGrid>
|
|
}
|
|
|
|
@code {
|
|
private int _eventCount;
|
|
private int _individualEventsCount;
|
|
private int _teamEventsCount;
|
|
private int _studentCount;
|
|
private string _gradeDistribution = "";
|
|
private int _teamCount;
|
|
private int _individualTeamsCount;
|
|
private int _groupTeamsCount;
|
|
private List<Note> _pinnedNotes = [];
|
|
private CancellationTokenSource? _cancellationTokenSource;
|
|
private bool _isDisposed = false;
|
|
|
|
private bool _hasStudents => _studentCount > 0;
|
|
private bool _hasTeams => _teamCount > 0;
|
|
|
|
protected override void OnInitialized()
|
|
{
|
|
_cancellationTokenSource = new CancellationTokenSource();
|
|
}
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
await LoadStatistics();
|
|
await LoadPinnedNotes();
|
|
}
|
|
|
|
private async Task HandleNoteChanged()
|
|
{
|
|
if (!_isDisposed)
|
|
{
|
|
await LoadPinnedNotes();
|
|
StateHasChanged();
|
|
}
|
|
}
|
|
|
|
private async Task LoadPinnedNotes()
|
|
{
|
|
if (_isDisposed) return;
|
|
|
|
try
|
|
{
|
|
var cancellationToken = _cancellationTokenSource?.Token ?? CancellationToken.None;
|
|
_pinnedNotes = (await NotesService.GetPinnedNotesAsync()).ToList();
|
|
}
|
|
catch (TaskCanceledException)
|
|
{
|
|
// Component was disposed, ignore
|
|
}
|
|
catch (JSDisconnectedException)
|
|
{
|
|
// JS connection lost, ignore
|
|
}
|
|
catch (Exception)
|
|
{
|
|
// Error loading pinned notes - ignore
|
|
}
|
|
}
|
|
|
|
private async Task LoadStatistics()
|
|
{
|
|
// Events statistics
|
|
var events = await Context.Events.ToListAsync();
|
|
_eventCount = events.Count();
|
|
_individualEventsCount = events.Count(e => e.EventFormat == EventFormat.Individual);
|
|
_teamEventsCount = events.Count(e => e.EventFormat == EventFormat.Team);
|
|
|
|
// Students statistics
|
|
_studentCount = await Context.Students.CountAsync();
|
|
|
|
// Grade distribution
|
|
var gradeGroups = await Context.Students
|
|
.GroupBy(s => s.Grade)
|
|
.Select(g => new { Grade = g.Key, Count = g.Count() })
|
|
.OrderBy(g => g.Grade)
|
|
.ToListAsync();
|
|
|
|
if (gradeGroups.Any())
|
|
{
|
|
_gradeDistribution = string.Join(" | ", gradeGroups.Select(g =>
|
|
$"<span style=\"white-space: nowrap\">{AppIcons.GetOrdinalSuperscript(g.Grade)}: <strong>{g.Count}</strong></span>"));
|
|
}
|
|
else
|
|
{
|
|
_gradeDistribution = "No students yet";
|
|
}
|
|
|
|
// Teams statistics
|
|
var contextTeams = await Context.Teams.ToListAsync();
|
|
_teamCount = contextTeams.Count;
|
|
_individualTeamsCount = contextTeams.Count(e => e.Event.EventFormat == EventFormat.Individual);
|
|
_groupTeamsCount = contextTeams.Count(e => e.Event.EventFormat == EventFormat.Team);
|
|
}
|
|
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
if (!_isDisposed)
|
|
{
|
|
_isDisposed = true;
|
|
_cancellationTokenSource?.Cancel();
|
|
_cancellationTokenSource?.Dispose();
|
|
_cancellationTokenSource = null;
|
|
}
|
|
await ValueTask.CompletedTask;
|
|
}
|
|
}
|