feat(ui): notification report row double-click opens detail modal

This commit is contained in:
Joseph Doherty
2026-05-21 02:39:41 -04:00
parent 80076a3951
commit ef5cf76026
2 changed files with 287 additions and 2 deletions

View File

@@ -139,7 +139,9 @@
<tbody>
@foreach (var n in _notifications)
{
<tr @key="n.NotificationId" class="@(n.IsStuck ? "table-warning" : "")">
<tr @key="n.NotificationId" class="@(n.IsStuck ? "table-warning" : "")"
style="cursor: pointer;" @ondblclick="() => ShowDetail(n)"
title="Double-click for full detail">
<td><code class="small" title="@n.NotificationId">@ShortId(n.NotificationId)</code></td>
<td>@n.Type</td>
<td>@n.ListName</td>
@@ -162,7 +164,7 @@
<td><span class="small">@SiteName(n.SourceSiteId)</span></td>
<td><TimestampDisplay Value="@n.CreatedAt" Format="yyyy-MM-dd HH:mm" /></td>
<td><TimestampDisplay Value="@n.DeliveredAt" Format="yyyy-MM-dd HH:mm" NullText="—" /></td>
<td class="text-end">
<td class="text-end" @ondblclick:stopPropagation="true">
@* Bundle D (#23 M7-T10) drill-in: NotificationId is the audit
CorrelationId, so the link deep-links into the central Audit
Log pre-filtered to this notification's lifecycle events. *@
@@ -206,6 +208,86 @@
}
</div>
@* ── Row detail modal ── *@
@if (_detailNotification != null)
{
var d = _detailNotification;
<div class="modal show d-block" tabindex="-1" style="background: rgba(0,0,0,0.4);"
@onclick="CloseDetail">
<div class="modal-dialog modal-dialog-scrollable modal-lg" @onclick:stopPropagation="true">
<div class="modal-content">
<div class="modal-header">
<h6 class="modal-title">Notification Detail — @ShortId(d.NotificationId)</h6>
<button type="button" class="btn-close" aria-label="Close"
@onclick="CloseDetail"></button>
</div>
<div class="modal-body">
<dl class="row mb-0">
<dt class="col-sm-3">Notification ID</dt>
<dd class="col-sm-9"><code>@d.NotificationId</code></dd>
<dt class="col-sm-3">Type</dt>
<dd class="col-sm-9">@d.Type</dd>
<dt class="col-sm-3">List</dt>
<dd class="col-sm-9">@d.ListName</dd>
<dt class="col-sm-3">Subject</dt>
<dd class="col-sm-9">@d.Subject</dd>
<dt class="col-sm-3">Status</dt>
<dd class="col-sm-9">
<span class="badge @StatusBadgeClass(d.Status)">@d.Status</span>
@if (d.IsStuck)
{
<span class="badge bg-warning text-dark ms-1">Stuck</span>
}
</dd>
<dt class="col-sm-3">Stuck</dt>
<dd class="col-sm-9">@(d.IsStuck ? "Yes" : "No")</dd>
<dt class="col-sm-3">Retry count</dt>
<dd class="col-sm-9 font-monospace">@d.RetryCount</dd>
<dt class="col-sm-3">Source site</dt>
<dd class="col-sm-9">@SiteName(d.SourceSiteId)</dd>
<dt class="col-sm-3">Source instance</dt>
<dd class="col-sm-9">@(string.IsNullOrEmpty(d.SourceInstanceId) ? "—" : d.SourceInstanceId)</dd>
<dt class="col-sm-3">Created</dt>
<dd class="col-sm-9"><TimestampDisplay Value="@d.CreatedAt" Format="yyyy-MM-dd HH:mm:ss" /></dd>
<dt class="col-sm-3">Delivered</dt>
<dd class="col-sm-9"><TimestampDisplay Value="@d.DeliveredAt" Format="yyyy-MM-dd HH:mm:ss" NullText="—" /></dd>
@if (!string.IsNullOrEmpty(d.LastError))
{
<dt class="col-sm-3">Last error</dt>
<dd class="col-sm-9 text-danger">@d.LastError</dd>
}
</dl>
</div>
<div class="modal-footer">
@if (d.Status == "Parked")
{
<button class="btn btn-outline-success btn-sm"
@onclick="() => RetryFromDetail(d)" disabled="@_actionInProgress">
Retry
</button>
<button class="btn btn-outline-danger btn-sm"
@onclick="() => DiscardFromDetail(d)" disabled="@_actionInProgress">
Discard
</button>
}
<button class="btn btn-outline-secondary btn-sm" @onclick="CloseDetail">Close</button>
</div>
</div>
</div>
</div>
}
@code {
private const int _pageSize = 50;
@@ -220,6 +302,9 @@
private string? _listError;
private bool _actionInProgress;
// Row detail modal
private NotificationSummary? _detailNotification;
// Filters
private string _statusFilter = string.Empty;
private string _typeFilter = string.Empty;
@@ -355,6 +440,24 @@
_actionInProgress = false;
}
private void ShowDetail(NotificationSummary n) => _detailNotification = n;
private void CloseDetail() => _detailNotification = null;
private async Task RetryFromDetail(NotificationSummary n)
{
await RetryNotification(n);
// RefreshAll replaces the row list; close the modal so the user sees the
// refreshed grid rather than a now-stale detail snapshot.
CloseDetail();
}
private async Task DiscardFromDetail(NotificationSummary n)
{
await DiscardNotification(n);
CloseDetail();
}
private void ClearFilters()
{
_statusFilter = string.Empty;