@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 @implements IAsyncDisposable Back to Schedule @if (_isLoading) { } else { @if (_meetingHistories.Any()) { @foreach (var team in _allTeams) { } Date @{ var teamIndex = 0; } @foreach (var team in _allTeams) { var isEven = teamIndex % 2 == 0; var bgColor = isEven ? "background-color: var(--mud-palette-background-grey);" : ""; var headerStyle = $"padding: 4px 2px; border-right: 1px solid var(--mud-palette-divider); {bgColor}"; @team.ToString() teamIndex++; } @context.MeetingDate.ToString("MM/dd/yy") @{ var rowTeamIndex = 0; } @foreach (var team in _allTeams) { var met = TeamMetOnDate(context, team); var isEven = rowTeamIndex % 2 == 0; var bgColor = isEven ? "background-color: var(--mud-palette-background-grey);" : ""; var cellStyle = $"padding: 4px 2px; border-right: 1px solid var(--mud-palette-divider); {bgColor}"; @if (met) { × } rowTeamIndex++; } Times Met @{ var footerTeamIndex = 0; } @foreach (var team in _allTeams) { var timesMet = GetTimesMetForTeam(team); var isEven = footerTeamIndex % 2 == 0; var bgColor = isEven ? "background-color: var(--mud-palette-background-grey);" : ""; var footerCellStyle = $"padding: 4px 2px; border-right: 1px solid var(--mud-palette-divider); {bgColor}"; @timesMet footerTeamIndex++; } } 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(); } } } public async ValueTask DisposeAsync() { if (!_isDisposed) { _isDisposed = true; _cancellationTokenSource?.Cancel(); _cancellationTokenSource?.Dispose(); _cancellationTokenSource = null; } await ValueTask.CompletedTask; } }