@namespace WebApp.Components.Features.MeetingSchedule @using Core.Entities @using Core.Calculation @using Core.Services @using WebApp.Services @using WebApp.Components.Shared.Components @using WebApp.Models @inject ITeamMeetingHistoryService TeamMeetingHistoryService @inject INotesService NotesService @inject INoteNamingService NoteNamingService @inject ISnackbar Snackbar @implements IAsyncDisposable @if (_isLoading) { } else { Save Meeting History Teams That Met @foreach (var team in AllTeams.OrderByEventFormatFirst().ThenBy(e => e.ToString())) { @team.ToString() } Students Present @foreach (var student in AllStudents.OrderBy(s => s.FirstName)) { @student.FirstNameLastName } } Cancel @if (_isSaving) { Saving... } else { Save } @code { [CascadingParameter] IMudDialogInstance MudDialog { get; set; } = null!; [Parameter] public IEnumerable ScheduledTeams { get; set; } = []; [Parameter] public IEnumerable AbsentStudents { get; set; } = []; [Parameter] public IEnumerable AllTeams { get; set; } = []; [Parameter] public IEnumerable AllStudents { get; set; } = []; private DateTime? _meetingDate = DateTime.Today; private List _selectedTeams = []; private List _selectedStudents = []; private string _noteTitle = ""; private string _noteContent = ""; private bool _isLoading = true; private bool _isSaving = false; private CancellationTokenSource? _cancellationTokenSource; private bool _isDisposed = false; protected override void OnInitialized() { _cancellationTokenSource = new CancellationTokenSource(); } protected override async Task OnInitializedAsync() { await LoadInitialData(); } private async Task LoadInitialData() { if (_isDisposed) return; try { // Initialize selected teams from scheduled teams _selectedTeams = ScheduledTeams.ToList(); // Initialize selected students (all students except absent ones) var absentStudentIds = AbsentStudents.Select(s => s.Id).ToHashSet(); _selectedStudents = AllStudents.Where(s => !absentStudentIds.Contains(s.Id)).ToList(); // Generate default note title if meeting date is set if (_meetingDate.HasValue) { _noteTitle = NoteNamingService.GetMeetingNoteTitle(_meetingDate.Value); } } catch (TaskCanceledException) { // Component was disposed, ignore } catch (JSDisconnectedException) { // JS connection lost, ignore } catch (Exception ex) { if (!_isDisposed) { Snackbar.Add($"Error loading data: {ex.Message}", Severity.Error); } } finally { if (!_isDisposed) { _isLoading = false; StateHasChanged(); } } } private bool IsSaveDisabled => _isSaving || _isLoading; protected override async Task OnParametersSetAsync() { if (_meetingDate.HasValue && string.IsNullOrEmpty(_noteTitle)) { _noteTitle = NoteNamingService.GetMeetingNoteTitle(_meetingDate.Value); } await base.OnParametersSetAsync(); } private async Task OnMeetingDateChanged(DateTime? date) { _meetingDate = date; if (date.HasValue) { _noteTitle = NoteNamingService.GetMeetingNoteTitle(date.Value); } await InvokeAsync(StateHasChanged); } private void OnTeamsChanged(IEnumerable teams) { _selectedTeams = teams.ToList(); StateHasChanged(); } private void OnStudentsChanged(IEnumerable students) { _selectedStudents = students.ToList(); StateHasChanged(); } private async Task Save() { if (_isDisposed || _isSaving) return; if (!_meetingDate.HasValue) { Snackbar.Add("Please select a meeting date", Severity.Warning); return; } try { _isSaving = true; StateHasChanged(); // Create or update note if note content is provided Note? note = null; if (!string.IsNullOrWhiteSpace(_noteContent)) { // Use the naming service to get the meeting note title var noteTitle = NoteNamingService.GetMeetingNoteTitle(_meetingDate.Value); // Check if note already exists var existingNote = await NotesService.GetNotesAsync(includeDeleted: false); note = existingNote.FirstOrDefault(n => n.Title == noteTitle); if (note != null) { // Update existing note note.Content = _noteContent; note = await NotesService.UpdateNoteAsync(note); } else { // Create new note note = new Note { Title = noteTitle, Content = _noteContent }; note = await NotesService.CreateNoteAsync(note); } } // Create meeting history var meetingHistory = new TeamMeetingHistory { MeetingDate = _meetingDate.Value, Teams = _selectedTeams, Students = _selectedStudents }; await TeamMeetingHistoryService.CreateMeetingHistoryAsync(meetingHistory); if (!_isDisposed) { Snackbar.Add($"Meeting history saved for {_meetingDate.Value:MM/dd/yyyy}", Severity.Success); MudDialog.Close(DialogResult.Ok(true)); } } catch (TaskCanceledException) { // Component was disposed, ignore } catch (JSDisconnectedException) { // JS connection lost, ignore } catch (Exception ex) { if (!_isDisposed) { Snackbar.Add($"Error saving meeting history: {ex.Message}", Severity.Error); _isSaving = false; StateHasChanged(); } } } private void Cancel() { if (_isDisposed) return; MudDialog.Cancel(); } public async ValueTask DisposeAsync() { if (!_isDisposed) { _isDisposed = true; _cancellationTokenSource?.Cancel(); _cancellationTokenSource?.Dispose(); _cancellationTokenSource = null; } await ValueTask.CompletedTask; } }