@page "/meeting-schedule/history" @attribute [Authorize] @using Core.Entities @using Microsoft.EntityFrameworkCore @using WebApp.Components.Shared.Components @using WebApp.Services @inject ITeamMeetingHistoryService TeamMeetingHistoryService @inject AppDbContext Context @inject IDialogService DialogService @inject ISnackbar Snackbar @inject IConfiguration Configuration @inject IMeetingScheduleDataService DataService @inject IMeetingScheduleStateService StateService @inject NavigationManager NavigationManager @implements IAsyncDisposable Back to Schedule @if (_isLoading) { } else { @if (_meetingHistories.Any()) { @for (var c = 0; c < _allTeams.Count; c++) { } @{ var teamIndex = 0; } @foreach (var team in _allTeams) { var isEven = teamIndex % 2 == 0; var colClass = isEven ? "history-cell history-cell-col-even" : "history-cell history-cell-col-odd"; teamIndex++; } @{ var summaryTeamIndex = 0; } @foreach (var team in _allTeams) { var timesMet = GetTimesMetForTeam(team); var isEven = summaryTeamIndex % 2 == 0; var colClass = isEven ? "history-cell history-cell-col-even history-cell-times-met" : "history-cell history-cell-col-odd history-cell-times-met"; summaryTeamIndex++; } @{ var rowIndex = 0; } @foreach (var history in _meetingHistories) { var rowTeamIndex = 0; var rowClass = rowIndex % 2 == 0 ? "history-row-even" : "history-row-odd"; @foreach (var team in _allTeams) { var met = TeamMetOnDate(history, team); var isEven = rowTeamIndex % 2 == 0; var colClass = isEven ? "history-cell history-cell-col-even" : "history-cell history-cell-col-odd"; rowTeamIndex++; } rowIndex++; }
Date Actions @team.ToString()
Times Met @timesMet
@history.MeetingDate.ToString("MM/dd/yy") @if (met) { × }
} else { No meeting history found. Save a meeting schedule to get started. }
}
@code { private List _meetingHistories = []; private List _allTeams = []; private Dictionary _timesMetDict = new(); private bool _isLoading = true; private CancellationTokenSource? _cancellationTokenSource; private bool _isDisposed = false; protected override void OnInitialized() { _cancellationTokenSource = new CancellationTokenSource(); } protected override async Task OnInitializedAsync() { await LoadData(); } private async Task LoadData() { if (_isDisposed) return; try { _isLoading = true; StateHasChanged(); // Load all teams for columns _allTeams = await Context.Teams .AsNoTracking() .Include(t => t.Event) .OrderBy(t => t.Event.Name) .ThenBy(t => t.Identifier) .ToListAsync(); // Load meeting histories await RefreshMeetingHistories(); // Calculate times met CalculateTimesMet(); } catch (TaskCanceledException) { // Component was disposed, ignore } catch (JSDisconnectedException) { // JS connection lost, ignore } catch (Exception ex) { if (!_isDisposed) { Snackbar.Add($"Error loading meeting history: {ex.Message}", Severity.Error); } } finally { if (!_isDisposed) { _isLoading = false; StateHasChanged(); } } } private async Task RefreshMeetingHistories() { _meetingHistories = (await TeamMeetingHistoryService.GetMeetingHistoriesAsync()).ToList(); // Extract all unique teams from meeting histories and merge with all teams var teamsInHistory = _meetingHistories .SelectMany(mh => mh.Teams) .DistinctBy(t => t.Id) .ToList(); // Merge with all teams, ensuring all teams are included var allTeamIds = _allTeams.Select(t => t.Id).ToHashSet(); var newTeams = teamsInHistory.Where(t => !allTeamIds.Contains(t.Id)).ToList(); _allTeams = _allTeams.Concat(newTeams).OrderBy(t => t.Event?.Name ?? "").ThenBy(t => t.Identifier).ToList(); } private void CalculateTimesMet() { _timesMetDict.Clear(); foreach (var team in _allTeams) { _timesMetDict[team.Id] = 0; } foreach (var history in _meetingHistories) { var teamIds = history.Teams.Select(t => t.Id).ToHashSet(); foreach (var teamId in teamIds) { if (_timesMetDict.ContainsKey(teamId)) { _timesMetDict[teamId]++; } } } } private int GetTimesMetForTeam(Team team) { return _timesMetDict.GetValueOrDefault(team.Id, 0); } private bool TeamMetOnDate(TeamMeetingHistory history, Team team) { return history.Teams.Any(t => t.Id == team.Id); } private async Task ViewMeetingDetails(TeamMeetingHistory history) { var parameters = new DialogParameters { ["MeetingHistoryId"] = history.Id }; var options = new DialogOptions { MaxWidth = MaxWidth.Large, FullWidth = true, CloseButton = true }; var dialog = await DialogService.ShowAsync("Meeting Details", parameters, options); var result = await dialog.Result; if (!result.Canceled) { // Refresh data if meeting was updated or deleted await RefreshMeetingHistories(); CalculateTimesMet(); if (!_isDisposed) { StateHasChanged(); } } } private async Task LoadMeetingIntoPlanner(TeamMeetingHistory history) { if (_isDisposed) return; try { // Get all teams and students from database var allTeams = await DataService.LoadTeamsAsync(); var allStudents = await DataService.LoadStudentsAsync(); // Match teams from history to all teams by ID for reference equality var historyTeamIds = history.Teams.Select(t => t.Id).ToHashSet(); var scheduledTeams = allTeams.Where(t => historyTeamIds.Contains(t.Id)); // Calculate absent students (all students not in the meeting history's student list) var presentStudentIds = history.Students.Select(s => s.Id).ToHashSet(); var absentStudents = allStudents.Where(s => !presentStudentIds.Contains(s.Id)); // Save state to localStorage await StateService.SaveScheduledTeamsAsync(scheduledTeams); await StateService.SaveAbsentStudentsAsync(absentStudents); // Clear extended teams and excluded students when loading from history await StateService.SaveExtendedTeamsAsync([]); await StateService.SaveExcludedStudentsAsync(new Dictionary<(int teamId, int timeSlotIndex, int studentId), bool>()); // Navigate to planner NavigationManager.NavigateTo("/meeting-schedule"); if (!_isDisposed) { Snackbar.Add($"Loaded meeting from {history.MeetingDate:MM/dd/yyyy} into planner", Severity.Success); } } catch (TaskCanceledException) { // Component was disposed, ignore } catch (JSDisconnectedException) { // JS connection lost, ignore } catch (Exception ex) { if (!_isDisposed) { Snackbar.Add($"Error loading meeting into planner: {ex.Message}", Severity.Error); } } } public async ValueTask DisposeAsync() { if (!_isDisposed) { _isDisposed = true; _cancellationTokenSource?.Cancel(); _cancellationTokenSource?.Dispose(); _cancellationTokenSource = null; } await ValueTask.CompletedTask; } }