9ab241ed77
This commit modifies the MeetingSchedule component to change the page header title to "Meeting Scheduler Planner" for better clarity. Additionally, it refines the MeetingHistoryDetailDialog by removing unnecessary UI elements to streamline the display of meeting history details. In the Printout component, null checks are added to ensure safe access to LevelOfEffort properties, enhancing data integrity. The Home component's title is also updated to "Meeting Schedule Planner" for consistency across the application. These changes collectively enhance the user experience and maintain data accuracy in the meeting scheduling feature.
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 Planner"
|
|
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;
|
|
}
|
|
}
|