Files
chapter-organizer/WebApp/Components/Shared/Components/PageNoteButton.razor
T
poprhythmandCursor 3f50d6e635 feat: add a Tools page printer for note merge and print presets
Chapter officers can merge a markdown note onto filtered students, teams, or events and save the recipe. Extra student-note columns are additional fields (any ## … fields heading) so they work as print tokens whether imported or typed by hand.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-30 15:38:43 -04:00

168 lines
5.0 KiB
Plaintext

@namespace WebApp.Components.Shared.Components
@inject INotesService NotesService
@inject IDialogService DialogService
@implements IAsyncDisposable
<div @onmouseenter="@(() => { if (_hasContent) _popoverOpen = true; })"
@onmouseleave="@(() => _popoverOpen = false)"
style="display: inline-block; position: relative;">
<MudTooltip Text="@TooltipText">
<MudButton StartIcon="@IconValue"
OnClick="OpenDialog"
Variant="@Variant"
Size="@Size"
Color="@ButtonColor"
Class="@MarkdownHelper.GetNoteColorClass(_noteId)">
@if (!string.IsNullOrEmpty(ButtonText))
{
@ButtonText
}
</MudButton>
</MudTooltip>
<MudPopover Open="_popoverOpen"
AnchorOrigin="Origin.BottomCenter"
TransformOrigin="Origin.TopCenter"
Elevation="8">
<ChildContent>
@if (_hasContent && !string.IsNullOrWhiteSpace(_noteContent))
{
<MudPaper Elevation="0"
Class="pa-3"
Style="max-width: 500px; max-height: 400px; overflow-y: auto;"
@onmouseenter="@(() => _popoverOpen = true)"
@onmouseleave="@(() => _popoverOpen = false)">
<MudText Typo="Typo.subtitle1" Class="mb-2">@PageIdentifier</MudText>
<MudDivider Class="my-2" />
<div class="markdown-content" style="max-height: 350px; overflow-y: auto;">
@((MarkupString)MarkdownHelper.ToHtml(_noteContent))
</div>
</MudPaper>
}
</ChildContent>
</MudPopover>
</div>
@code {
[Parameter]
public string PageIdentifier { get; set; } = null!;
[Parameter]
public string? Icon { get; set; }
[Parameter]
public Variant Variant { get; set; } = Variant.Outlined;
[Parameter]
public Size Size { get; set; } = Size.Medium;
[Parameter]
public string? ButtonText { get; set; }
[Parameter]
public string? Tooltip { get; set; }
private string IconValue => Icon ?? Icons.Material.Filled.Note;
private string TooltipText => Tooltip ?? $"Page notes for {PageIdentifier}";
private bool _hasContent = false;
private int _noteId = 0;
private string _noteContent = string.Empty;
private bool _popoverOpen = false;
private Color ButtonColor => _hasContent ? Color.Success : (Variant == Variant.Filled ? Color.Primary : Color.Default);
private CancellationTokenSource? _cancellationTokenSource;
private bool _isDisposed = false;
protected override void OnInitialized()
{
_cancellationTokenSource = new CancellationTokenSource();
}
protected override async Task OnInitializedAsync()
{
await CheckNoteContent();
}
private async Task CheckNoteContent()
{
if (_isDisposed) return;
try
{
var note = await NotesService.GetPageNoteAsync(PageIdentifier);
_hasContent = note != null && !string.IsNullOrWhiteSpace(note.Content);
_noteId = note?.Id ?? 0;
_noteContent = note?.Content ?? string.Empty;
if (!_isDisposed)
{
StateHasChanged();
}
}
catch (TaskCanceledException)
{
// Component was disposed, ignore
}
catch (JSDisconnectedException)
{
// JS connection lost, ignore
}
catch (Exception)
{
// Error checking note - ignore
}
}
private async Task OpenDialog()
{
if (_isDisposed) return;
try
{
var parameters = new DialogParameters
{
["PageIdentifier"] = PageIdentifier
};
var options = new DialogOptions
{
MaxWidth = MaxWidth.Medium,
FullWidth = true,
CloseButton = true
};
var dialog = await DialogService.ShowAsync<PageNoteDialog>($"Page Notes: {PageIdentifier}", parameters, options);
var result = await dialog.Result;
// Always refresh content indicator after dialog closes
// (content may have been saved even if dialog was closed with Cancel)
if (!_isDisposed)
{
await CheckNoteContent();
}
}
catch (TaskCanceledException)
{
// Component was disposed, ignore
}
catch (JSDisconnectedException)
{
// JS connection lost, ignore
}
catch (Exception)
{
// Error opening dialog - could show snackbar if we had access
}
}
public async ValueTask DisposeAsync()
{
if (!_isDisposed)
{
_isDisposed = true;
_cancellationTokenSource?.Cancel();
_cancellationTokenSource?.Dispose();
_cancellationTokenSource = null;
}
await ValueTask.CompletedTask;
}
}