diff --git a/WebApp/Components/Features/Teams/Components/TeamMeetingHistoryBadge.razor b/WebApp/Components/Features/Teams/Components/TeamMeetingHistoryBadge.razor
new file mode 100644
index 0000000..16a55c2
--- /dev/null
+++ b/WebApp/Components/Features/Teams/Components/TeamMeetingHistoryBadge.razor
@@ -0,0 +1,108 @@
+@namespace WebApp.Components.Features.Teams.Components
+@using Core.Entities
+@using WebApp.Services
+@using MudBlazor
+@inject ITeamMeetingHistoryService TeamMeetingHistoryService
+@inject IDialogService DialogService
+@implements IAsyncDisposable
+
+@if (_meetingCount.HasValue && _meetingCount.Value > 0)
+{
+
+
+
+
+
+}
+
+@code {
+ [Parameter]
+ public int TeamId { get; set; }
+
+ [Parameter]
+ public string TeamName { get; set; } = string.Empty;
+
+ private int? _meetingCount;
+ private CancellationTokenSource? _cancellationTokenSource;
+ private bool _isDisposed = false;
+
+ protected override void OnInitialized()
+ {
+ _cancellationTokenSource = new CancellationTokenSource();
+ }
+
+ protected override async Task OnInitializedAsync()
+ {
+ await LoadMeetingCount();
+ }
+
+ private async Task LoadMeetingCount()
+ {
+ if (_isDisposed) return;
+
+ try
+ {
+ var cancellationToken = _cancellationTokenSource?.Token ?? CancellationToken.None;
+ _meetingCount = await TeamMeetingHistoryService.GetMeetingHistoryCountForTeamAsync(TeamId);
+ }
+ catch (TaskCanceledException)
+ {
+ // Component was disposed, ignore
+ }
+ catch (JSDisconnectedException)
+ {
+ // JS connection lost, ignore
+ }
+ catch (Exception)
+ {
+ // Ignore errors - just don't show the badge
+ _meetingCount = null;
+ }
+ finally
+ {
+ if (!_isDisposed)
+ {
+ StateHasChanged();
+ }
+ }
+ }
+
+ private async Task OpenMeetingHistoryDialog()
+ {
+ if (_isDisposed) return;
+
+ var parameters = new DialogParameters
+ {
+ ["TeamId"] = TeamId,
+ ["TeamName"] = TeamName
+ };
+
+ var options = new DialogOptions
+ {
+ CloseOnEscapeKey = true,
+ CloseButton = true,
+ MaxWidth = MaxWidth.Medium,
+ FullWidth = true
+ };
+
+ await DialogService.ShowAsync($"{TeamName} Meeting History", parameters, options);
+ }
+
+ public async ValueTask DisposeAsync()
+ {
+ if (!_isDisposed)
+ {
+ _isDisposed = true;
+ _cancellationTokenSource?.Cancel();
+ _cancellationTokenSource?.Dispose();
+ _cancellationTokenSource = null;
+ }
+ await ValueTask.CompletedTask;
+ }
+}
diff --git a/WebApp/Components/Features/Teams/Components/TeamMeetingToggleSelector.razor b/WebApp/Components/Features/Teams/Components/TeamMeetingToggleSelector.razor
index 6393b28..f28d56c 100644
--- a/WebApp/Components/Features/Teams/Components/TeamMeetingToggleSelector.razor
+++ b/WebApp/Components/Features/Teams/Components/TeamMeetingToggleSelector.razor
@@ -1,5 +1,6 @@
@using WebApp.Models
@using Core.Utility
+@using WebApp.Components.Features.Teams.Components
@if (Title != null)
{
@@ -28,14 +29,17 @@
@if (IsSelected(team))
{
var isExtended = IsExtended(team);
-
-
-
+
+
+
+
+
+
}
@if (ShowEventAttributes)
{
diff --git a/WebApp/Components/Features/Teams/Details.razor b/WebApp/Components/Features/Teams/Details.razor
index 1a996b5..cf71cd5 100644
--- a/WebApp/Components/Features/Teams/Details.razor
+++ b/WebApp/Components/Features/Teams/Details.razor
@@ -3,9 +3,11 @@
@using Microsoft.EntityFrameworkCore
@using WebApp.Components.Shared.Components
@using WebApp.Components.Features.Teams.Components
+@using MudBlazor
@inject AppDbContext Context
@inject NavigationManager NavigationManager
@inject IJSRuntime JSRuntime
+@inject IDialogService DialogService
@if (Team is null)
{
@@ -29,6 +31,11 @@
Href="@($"/teams/edit?id={Team.Id}&returnUrl={ReturnUrl ?? "/teams"}")"
Variant="Variant.Outlined">Edit
+
+ Meeting History
+
@@ -86,4 +93,26 @@
{
await JSRuntime.InvokeVoidAsync("window.print");
}
+
+ private async Task ShowMeetingHistory()
+ {
+ if (Team == null) return;
+
+ var teamName = Team.ToString();
+ var parameters = new DialogParameters
+ {
+ ["TeamId"] = Team.Id,
+ ["TeamName"] = teamName
+ };
+
+ var options = new DialogOptions
+ {
+ CloseOnEscapeKey = true,
+ CloseButton = true,
+ MaxWidth = MaxWidth.Medium,
+ FullWidth = true
+ };
+
+ await DialogService.ShowAsync($"{teamName} Meeting History", parameters, options);
+ }
}
diff --git a/WebApp/Components/Features/Teams/Index.razor b/WebApp/Components/Features/Teams/Index.razor
index 52dad5a..a4c0ae8 100644
--- a/WebApp/Components/Features/Teams/Index.razor
+++ b/WebApp/Components/Features/Teams/Index.razor
@@ -47,11 +47,12 @@
+ Underline="Underline.Hover"
+ Color="Color.Primary">
@context.Item.ToString()
+
diff --git a/WebApp/Components/Features/Teams/TeamMeetingHistoryDialog.razor b/WebApp/Components/Features/Teams/TeamMeetingHistoryDialog.razor
new file mode 100644
index 0000000..ae38848
--- /dev/null
+++ b/WebApp/Components/Features/Teams/TeamMeetingHistoryDialog.razor
@@ -0,0 +1,127 @@
+@namespace WebApp.Components.Features.Teams
+@using Core.Entities
+@using WebApp.Services
+@using Microsoft.AspNetCore.Components
+@inject ITeamMeetingHistoryService TeamMeetingHistoryService
+@implements IAsyncDisposable
+
+
+
+ @TeamName Meeting History
+
+
+ @if (_isLoading)
+ {
+
+ }
+ else if (!_meetingHistories.Any())
+ {
+ No meeting history found for this team.
+ }
+ else
+ {
+
+
+ Date
+ Status
+
+
+ @{
+ var teamWasInMeeting = context.Teams.Any(t => t.Id == TeamId);
+ }
+ @context.MeetingDate.ToString("MM/dd/yyyy")
+
+
+ @(teamWasInMeeting ? "Team Met" : "Team Not Scheduled")
+
+
+
+
+ }
+
+
+
+ Close
+
+
+
+@code {
+ [CascadingParameter]
+ IMudDialogInstance MudDialog { get; set; } = null!;
+
+ [Parameter]
+ public int TeamId { get; set; }
+
+ [Parameter]
+ public string TeamName { get; set; } = string.Empty;
+
+ private List _meetingHistories = [];
+ private bool _isLoading = true;
+ private CancellationTokenSource? _cancellationTokenSource;
+ private bool _isDisposed = false;
+
+ protected override void OnInitialized()
+ {
+ _cancellationTokenSource = new CancellationTokenSource();
+ }
+
+ protected override async Task OnInitializedAsync()
+ {
+ await LoadMeetingHistories();
+ }
+
+ private async Task LoadMeetingHistories()
+ {
+ if (_isDisposed) return;
+
+ try
+ {
+ var cancellationToken = _cancellationTokenSource?.Token ?? CancellationToken.None;
+ var histories = await TeamMeetingHistoryService.GetMeetingHistoriesForTeamAsync(TeamId);
+ _meetingHistories = histories.ToList();
+ }
+ catch (TaskCanceledException)
+ {
+ // Component was disposed, ignore
+ }
+ catch (JSDisconnectedException)
+ {
+ // JS connection lost, ignore
+ }
+ catch (Exception ex)
+ {
+ if (!_isDisposed)
+ {
+ System.Diagnostics.Debug.WriteLine($"Error loading meeting histories: {ex.Message}");
+ }
+ }
+ finally
+ {
+ if (!_isDisposed)
+ {
+ _isLoading = false;
+ StateHasChanged();
+ }
+ }
+ }
+
+ private void Close()
+ {
+ if (_isDisposed) return;
+ MudDialog.Close();
+ }
+
+ public async ValueTask DisposeAsync()
+ {
+ if (!_isDisposed)
+ {
+ _isDisposed = true;
+ _cancellationTokenSource?.Cancel();
+ _cancellationTokenSource?.Dispose();
+ _cancellationTokenSource = null;
+ }
+ await ValueTask.CompletedTask;
+ }
+}
diff --git a/WebApp/Services/ITeamMeetingHistoryService.cs b/WebApp/Services/ITeamMeetingHistoryService.cs
index 3d43044..d067e00 100644
--- a/WebApp/Services/ITeamMeetingHistoryService.cs
+++ b/WebApp/Services/ITeamMeetingHistoryService.cs
@@ -47,4 +47,19 @@ public interface ITeamMeetingHistoryService
/// The date of the meeting
/// The note if found, null otherwise
Task GetMeetingNoteAsync(DateTime meetingDate);
+
+ ///
+ /// Gets all meeting histories for a specific team, ordered by date descending.
+ ///
+ /// The ID of the team
+ /// Meeting histories for the team, ordered by date descending
+ Task> GetMeetingHistoriesForTeamAsync(int teamId);
+
+ ///
+ /// Gets the count of meeting histories for a specific team.
+ /// This is more efficient than loading all histories just to count them.
+ ///
+ /// The ID of the team
+ /// The number of meeting histories for the team
+ Task GetMeetingHistoryCountForTeamAsync(int teamId);
}
diff --git a/WebApp/Services/TeamMeetingHistoryService.cs b/WebApp/Services/TeamMeetingHistoryService.cs
index 704afe7..679e0c6 100644
--- a/WebApp/Services/TeamMeetingHistoryService.cs
+++ b/WebApp/Services/TeamMeetingHistoryService.cs
@@ -202,4 +202,25 @@ public class TeamMeetingHistoryService : ITeamMeetingHistoryService
.Distinct()
.ToListAsync();
}
+
+ public async Task> GetMeetingHistoriesForTeamAsync(int teamId)
+ {
+ return await _context.TeamMeetingHistories
+ .AsNoTracking()
+ .Include(tmh => tmh.Teams)
+ .ThenInclude(t => t.Event)
+ .Include(tmh => tmh.Students)
+ .Where(tmh => tmh.Teams.Any(t => t.Id == teamId))
+ .OrderByDescending(tmh => tmh.MeetingDate)
+ .ThenByDescending(tmh => tmh.Id)
+ .ToListAsync();
+ }
+
+ public async Task GetMeetingHistoryCountForTeamAsync(int teamId)
+ {
+ return await _context.TeamMeetingHistories
+ .AsNoTracking()
+ .Where(tmh => tmh.Teams.Any(t => t.Id == teamId))
+ .CountAsync();
+ }
}