@namespace WebApp.Components.Features.MeetingSchedule
@using Core.Entities
@using Core.Calculation
@using Core.Services
@using WebApp.Services
@using WebApp.Components.Shared.Components
@using WebApp.Components.Features.Teams.Components
@using WebApp.Components.Features.Students.Components
@using WebApp.Models
@inject ITeamMeetingHistoryService TeamMeetingHistoryService
@inject INotesService NotesService
@inject INoteNamingService NoteNamingService
@inject ISnackbar Snackbar
@inject IDialogService DialogService
@implements IAsyncDisposable
@if (_isLoading)
{
}
else
{
@(_isEditMode ? "Edit Meeting History" : "Save Meeting History")
}
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; } = [];
[Parameter]
public int? MeetingHistoryId { get; set; }
private DateTime? _meetingDate = DateTime.Today;
private IEnumerable _selectedTeams = [];
private IEnumerable _selectedStudents = [];
private string _noteTitle = "";
private string _noteContent = "";
private bool _isLoading = true;
private bool _isSaving = false;
private bool _isEditMode = 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
{
// If editing, load existing meeting history
if (MeetingHistoryId.HasValue)
{
_isEditMode = true;
var existingHistory = await TeamMeetingHistoryService.GetMeetingHistoryAsync(MeetingHistoryId.Value);
if (existingHistory != null)
{
_meetingDate = existingHistory.MeetingDate;
// Match teams by ID to ensure reference equality with MudToggleGroup
var selectedTeamIds = existingHistory.Teams.Select(t => t.Id).ToHashSet();
var matchedTeams = AllTeams.Where(t => selectedTeamIds.Contains(t.Id)).ToList();
// If we couldn't match all teams from AllTeams, use the teams from existing history
// This can happen if AllTeams is empty or doesn't contain all the teams
if (matchedTeams.Count != existingHistory.Teams.Count && AllTeams.Any())
{
// Try to match what we can, but log a warning
System.Diagnostics.Debug.WriteLine($"Warning: Could not match all teams. Expected {existingHistory.Teams.Count}, matched {matchedTeams.Count}");
}
_selectedTeams = matchedTeams.Any() ? matchedTeams : existingHistory.Teams;
// Match students by ID to ensure reference equality with MudToggleGroup
var selectedStudentIds = existingHistory.Students.Select(s => s.Id).ToHashSet();
var matchedStudents = AllStudents.Where(s => selectedStudentIds.Contains(s.Id)).ToList();
// If we couldn't match all students from AllStudents, use the students from existing history
if (matchedStudents.Count != existingHistory.Students.Count && AllStudents.Any())
{
// Try to match what we can, but log a warning
System.Diagnostics.Debug.WriteLine($"Warning: Could not match all students. Expected {existingHistory.Students.Count}, matched {matchedStudents.Count}");
}
_selectedStudents = matchedStudents.Any() ? matchedStudents : existingHistory.Students;
// Load existing note if available
var existingNote = await TeamMeetingHistoryService.GetMeetingNoteAsync(existingHistory.MeetingDate);
if (existingNote != null)
{
_noteContent = existingNote.Content ?? "";
}
}
}
else
{
// Initialize selected teams from scheduled teams
_selectedTeams = ScheduledTeams;
// Initialize selected students (all students except absent ones)
var absentStudentIds = AbsentStudents.Select(s => s.Id).ToHashSet();
_selectedStudents = AllStudents.Where(s => !absentStudentIds.Contains(s.Id));
}
// 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;
StateHasChanged();
}
private void OnStudentsChanged(IEnumerable students)
{
_selectedStudents = students;
StateHasChanged();
}
private async Task Save()
{
if (_isDisposed || _isSaving) return;
if (!_meetingDate.HasValue)
{
Snackbar.Add("Please select a meeting date", Severity.Warning);
return;
}
// Validate that we have at least one team selected
if (!_selectedTeams.Any())
{
Snackbar.Add("Please select at least one team", Severity.Warning);
return;
}
// Check if a meeting history already exists for this date (only when creating new, not editing)
if (!_isEditMode)
{
var dateOnly = _meetingDate.Value.Date;
// GetMeetingHistoriesAsync: startDate >= date, endDate < (endDate.Date + 1 day)
// To get meetings for a single day, pass dateOnly as startDate and dateOnly as endDate
// This becomes: MeetingDate >= dateOnly AND MeetingDate < (dateOnly + 1 day) = just that day
var existingMeetings = await TeamMeetingHistoryService.GetMeetingHistoriesAsync(dateOnly, dateOnly);
if (existingMeetings.Any())
{
// Show confirmation dialog
var confirmResult = await DialogService.ShowMessageBox(
"Overwrite Meeting?",
"A meeting already exists for this date. Overwrite it?",
yesText: "Overwrite",
cancelText: "Cancel");
if (confirmResult != true)
{
return; // User cancelled
}
// User confirmed, switch to edit mode
var existingMeeting = existingMeetings.First();
_isEditMode = true;
MeetingHistoryId = existingMeeting.Id;
// Load existing meeting data
var existingHistory = await TeamMeetingHistoryService.GetMeetingHistoryAsync(existingMeeting.Id);
if (existingHistory != null)
{
// Match teams by ID to ensure reference equality with MudToggleGroup
var selectedTeamIds = existingHistory.Teams.Select(t => t.Id).ToHashSet();
_selectedTeams = AllTeams.Where(t => selectedTeamIds.Contains(t.Id));
// Match students by ID to ensure reference equality with MudToggleGroup
var selectedStudentIds = existingHistory.Students.Select(s => s.Id).ToHashSet();
_selectedStudents = AllStudents.Where(s => selectedStudentIds.Contains(s.Id));
// Load existing note if available
var existingNote = await TeamMeetingHistoryService.GetMeetingNoteAsync(existingHistory.MeetingDate);
if (existingNote != null)
{
_noteContent = existingNote.Content ?? "";
}
}
}
}
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 or update meeting history
TeamMeetingHistory meetingHistory;
if (_isEditMode && MeetingHistoryId.HasValue)
{
// Update existing meeting history
var existingHistory = await TeamMeetingHistoryService.GetMeetingHistoryAsync(MeetingHistoryId.Value);
if (existingHistory == null)
{
Snackbar.Add("Meeting history not found", Severity.Error);
_isSaving = false;
StateHasChanged();
return;
}
// Ensure we have teams and students - use existing if selected lists are empty (fallback)
var teamsToSave = _selectedTeams.Any() ? _selectedTeams.ToList() : existingHistory.Teams.ToList();
var studentsToSave = _selectedStudents.Any() ? _selectedStudents.ToList() : existingHistory.Students.ToList();
// Create a new meeting history object with the updated data
// Use IDs to ensure we're working with the correct entities
meetingHistory = new TeamMeetingHistory
{
Id = existingHistory.Id,
MeetingDate = _meetingDate.Value,
Teams = teamsToSave,
Students = studentsToSave
};
await TeamMeetingHistoryService.UpdateMeetingHistoryAsync(meetingHistory);
if (!_isDisposed)
{
Snackbar.Add($"Meeting history updated for {_meetingDate.Value:MM/dd/yyyy}", Severity.Success);
}
}
else
{
// Create new meeting history
meetingHistory = new TeamMeetingHistory
{
MeetingDate = _meetingDate.Value,
Teams = _selectedTeams.ToList(),
Students = _selectedStudents.ToList()
};
await TeamMeetingHistoryService.CreateMeetingHistoryAsync(meetingHistory);
if (!_isDisposed)
{
Snackbar.Add($"Meeting history saved for {_meetingDate.Value:MM/dd/yyyy}", Severity.Success);
}
}
if (!_isDisposed)
{
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;
}
}