6bc4c2e7f2
This commit introduces the TeamMeetingHistory entity, including its configuration and database migrations. A new ITeamMeetingHistoryService interface and its implementation, TeamMeetingHistoryService, are added to handle CRUD operations for meeting histories. Additionally, UI components such as History.razor, MeetingHistoryDetailDialog, and SaveMeetingHistoryDialog are created to facilitate viewing and saving meeting histories. The integration of INoteNamingService enhances note management for meeting records, improving overall functionality and user experience in the application.
299 lines
9.5 KiB
Plaintext
299 lines
9.5 KiB
Plaintext
@namespace WebApp.Components.Features.MeetingSchedule
|
|
@using Core.Entities
|
|
@using Core.Services
|
|
@using Core.Utility
|
|
@using WebApp.Services
|
|
@using WebApp.Components.Shared.Components
|
|
@using WebApp.Models
|
|
@inject ITeamMeetingHistoryService TeamMeetingHistoryService
|
|
@inject INotesService NotesService
|
|
@inject INoteNamingService NoteNamingService
|
|
@inject ISnackbar Snackbar
|
|
@inject IDialogService DialogService
|
|
@implements IAsyncDisposable
|
|
|
|
<MudDialog>
|
|
<DialogContent>
|
|
@if (_isLoading)
|
|
{
|
|
<MudProgressLinear Color="Color.Primary" Indeterminate="true" Class="my-4" />
|
|
}
|
|
else if (_meetingHistory == null)
|
|
{
|
|
<MudAlert Severity="Severity.Error">Meeting history not found.</MudAlert>
|
|
}
|
|
else
|
|
{
|
|
<MudStack Spacing="3">
|
|
<MudText Typo="Typo.h6">Meeting Details</MudText>
|
|
|
|
<MudPaper Elevation="1" Class="pa-3">
|
|
<MudStack Spacing="2">
|
|
<MudText Typo="Typo.subtitle2">
|
|
<strong>Date:</strong> @_meetingHistory.MeetingDate.ToString("MM/dd/yyyy")
|
|
</MudText>
|
|
</MudStack>
|
|
</MudPaper>
|
|
|
|
<MudDivider />
|
|
|
|
<MudText Typo="Typo.subtitle1">Teams That Met (@_meetingHistory.Teams.Count)</MudText>
|
|
<MudPaper Elevation="1" Class="pa-2">
|
|
<MudStack Row="true" Spacing="1" Wrap="Wrap.Wrap">
|
|
@foreach (var team in _meetingHistory.Teams.OrderByEventFormatFirst().ThenBy(e => e.ToString()))
|
|
{
|
|
<MudChip T="string" Size="Size.Small" Color="Color.Default" Variant="@AppIcons.TeamChipVariant()" Class="mx-1 my-1">@team.ToString()</MudChip>
|
|
}
|
|
</MudStack>
|
|
</MudPaper>
|
|
|
|
<MudDivider />
|
|
|
|
<MudText Typo="Typo.subtitle1">Students (@GetAllStudentsFromTeams().Count)</MudText>
|
|
<MudPaper Elevation="1" Class="pa-2">
|
|
<MudStack Row="true" Spacing="1" Wrap="Wrap.Wrap">
|
|
@{
|
|
var presentStudentIds = _meetingHistory.Students.Select(s => s.Id).ToHashSet();
|
|
var allStudents = GetAllStudentsFromTeams().OrderBy(s => s.FirstName);
|
|
}
|
|
@foreach (var student in allStudents)
|
|
{
|
|
var isPresent = presentStudentIds.Contains(student.Id);
|
|
<MudChip T="string"
|
|
Size="Size.Small"
|
|
Color="Color.Default"
|
|
Variant="@AppIcons.StudentChipVariant()"
|
|
Class="mx-1 my-1"
|
|
Style="@(!isPresent ? "opacity: 0.5;" : "")">
|
|
@student.FirstNameLastName
|
|
</MudChip>
|
|
}
|
|
</MudStack>
|
|
</MudPaper>
|
|
|
|
@if (_meetingNote != null)
|
|
{
|
|
<MudDivider />
|
|
<MudText Typo="Typo.subtitle1">Meeting Notes</MudText>
|
|
<MudPaper Elevation="1" Class="pa-3">
|
|
<MudText Typo="Typo.body2"><strong>@_meetingNote.Title</strong></MudText>
|
|
@if (!string.IsNullOrWhiteSpace(_meetingNote.Content))
|
|
{
|
|
<MudText Typo="Typo.body2" Class="mt-2">
|
|
@((MarkupString)MarkdownHelper.ToHtml(_meetingNote.Content))
|
|
</MudText>
|
|
}
|
|
<MudButton Variant="Variant.Text"
|
|
Size="Size.Small"
|
|
Color="Color.Primary"
|
|
StartIcon="@Icons.Material.Filled.Edit"
|
|
OnClick="ViewNote"
|
|
Class="mt-2">
|
|
Edit Note
|
|
</MudButton>
|
|
</MudPaper>
|
|
}
|
|
</MudStack>
|
|
}
|
|
</DialogContent>
|
|
<DialogActions>
|
|
@if (_meetingHistory != null)
|
|
{
|
|
<MudButton Variant="Variant.Text"
|
|
Color="Color.Error"
|
|
OnClick="ConfirmDelete"
|
|
Disabled="@IsActionDisabled">
|
|
Delete
|
|
</MudButton>
|
|
<MudSpacer />
|
|
<MudButton OnClick="Close" Disabled="@IsActionDisabled">Close</MudButton>
|
|
}
|
|
else
|
|
{
|
|
<MudSpacer />
|
|
<MudButton OnClick="Close">Close</MudButton>
|
|
}
|
|
</DialogActions>
|
|
</MudDialog>
|
|
|
|
@code {
|
|
[CascadingParameter]
|
|
IMudDialogInstance MudDialog { get; set; } = null!;
|
|
|
|
[Parameter]
|
|
public int MeetingHistoryId { get; set; }
|
|
|
|
private TeamMeetingHistory? _meetingHistory;
|
|
private Note? _meetingNote;
|
|
private bool _isLoading = true;
|
|
private bool _isDeleting = false;
|
|
private CancellationTokenSource? _cancellationTokenSource;
|
|
private bool _isDisposed = false;
|
|
private bool IsActionDisabled => _isLoading || _isDeleting;
|
|
|
|
protected override void OnInitialized()
|
|
{
|
|
_cancellationTokenSource = new CancellationTokenSource();
|
|
}
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
await LoadMeetingHistory();
|
|
}
|
|
|
|
private async Task LoadMeetingHistory()
|
|
{
|
|
if (_isDisposed) return;
|
|
|
|
try
|
|
{
|
|
_meetingHistory = await TeamMeetingHistoryService.GetMeetingHistoryAsync(MeetingHistoryId);
|
|
|
|
// Load note by title if meeting history exists
|
|
if (_meetingHistory != null)
|
|
{
|
|
_meetingNote = await TeamMeetingHistoryService.GetMeetingNoteAsync(_meetingHistory.MeetingDate);
|
|
}
|
|
}
|
|
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 ConfirmDelete()
|
|
{
|
|
if (_isDisposed || _isDeleting || _meetingHistory == null) return;
|
|
|
|
var result = await DialogService.ShowMessageBox(
|
|
"Confirm Delete",
|
|
$"Are you sure you want to delete the meeting history for {_meetingHistory.MeetingDate:MM/dd/yyyy}? This action cannot be undone.",
|
|
yesText: "Delete",
|
|
cancelText: "Cancel");
|
|
|
|
if (result == true)
|
|
{
|
|
await DeleteMeetingHistory();
|
|
}
|
|
}
|
|
|
|
private async Task DeleteMeetingHistory()
|
|
{
|
|
if (_isDisposed || _isDeleting || _meetingHistory == null) return;
|
|
|
|
try
|
|
{
|
|
_isDeleting = true;
|
|
StateHasChanged();
|
|
|
|
await TeamMeetingHistoryService.DeleteMeetingHistoryAsync(MeetingHistoryId);
|
|
|
|
if (!_isDisposed)
|
|
{
|
|
Snackbar.Add("Meeting history deleted successfully", 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 deleting meeting history: {ex.Message}", Severity.Error);
|
|
_isDeleting = false;
|
|
StateHasChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
private async Task ViewNote()
|
|
{
|
|
if (_meetingNote == null) return;
|
|
|
|
var parameters = new DialogParameters
|
|
{
|
|
["NoteId"] = _meetingNote.Id
|
|
};
|
|
|
|
var options = new DialogOptions
|
|
{
|
|
MaxWidth = MaxWidth.Medium,
|
|
FullWidth = true,
|
|
CloseButton = true
|
|
};
|
|
|
|
var dialog = await DialogService.ShowAsync<NoteViewDialog>("Meeting Notes", parameters, options);
|
|
await dialog.Result;
|
|
|
|
// Refresh meeting history to get updated note
|
|
await LoadMeetingHistory();
|
|
}
|
|
|
|
private List<Student> GetAllStudentsFromTeams()
|
|
{
|
|
if (_meetingHistory == null)
|
|
return [];
|
|
|
|
var allStudents = new List<Student>();
|
|
var studentIds = new HashSet<int>();
|
|
|
|
foreach (var team in _meetingHistory.Teams)
|
|
{
|
|
foreach (var student in team.Students)
|
|
{
|
|
if (!studentIds.Contains(student.Id))
|
|
{
|
|
studentIds.Add(student.Id);
|
|
allStudents.Add(student);
|
|
}
|
|
}
|
|
}
|
|
|
|
return allStudents;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|