feat(ui): notification report row double-click opens detail modal
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user