Improve PDF preview UX in SenderPage.razor

Updated button labels and popup headers to reflect a change in functionality from "Preview" to "Show Original Document." Added an informational hint in the PDF preview viewer to notify users about the absence of signature fields, with auto-hide and dismiss functionality. Introduced state management for the hint display, including methods to show and dismiss it, and ensured proper reset on preview close. Enhanced localization and styling for better user clarity.
This commit is contained in:
2026-11-11 16:58:47 +01:00
parent 31933f5568
commit 6ad76fa4b0

View File

@@ -113,12 +113,12 @@
}
</button>
<button class="sender-btn" @onclick="ShowPreview" disabled="@(_selectedEnvelope == null)" title="@L("Sender_Preview_Title", "Ausgewählten Umschlag als PDF anzeigen")">
<button class="sender-btn" @onclick="ShowPreview" disabled="@(_selectedEnvelope == null)" title="@L("Sender_ShowOriginalDocument_Title", "Originaldokument anzeigen")">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
<path d="M10.5 8a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0z"/>
<path d="M0 8s3-5.5 8-5.5S16 8 16 8s-3 5.5-8 5.5S0 8 0 8zm8 3.5a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7z"/>
</svg>
@L("Preview", "Vorschau")
@L("Sender_ShowOriginalDocument", "Original Dokument anzeigen")
</button>
<div class="sender-export-group" title="@L("Sender_ExportView_Title", "Export der aktuellen Tabellenansicht")">
@@ -454,7 +454,7 @@
@* ── PDF Vorschau Popup ────────────────────────────────────────────────── *@
<DxPopup @bind-Visible="@_previewVisible"
HeaderText="@($"{(_activeTab == "completed" && _selectedEnvelope?.Status == EnvelopeStatus.EnvelopeCompletelySigned ? L("SignedPdf", "Signiertes PDF") : L("Preview", "Vorschau"))} — {_selectedEnvelope?.Title} (#{_selectedEnvelope?.Id})")"
HeaderText="@($"{(_activeTab == "completed" && _selectedEnvelope?.Status == EnvelopeStatus.EnvelopeCompletelySigned ? L("SignedPdf", "Signiertes PDF") : L("Sender_OriginalDocument", "Originaldokument"))} — {_selectedEnvelope?.Title} (#{_selectedEnvelope?.Id})")"
Width="80vw"
MaxWidth="80vw"
Height="90vh"
@@ -503,6 +503,22 @@
else if (_previewPdfBytes is not null && _previewViewerMounted)
{
<div class="sender-preview-viewer-wrap">
@if (_showPreviewHint)
{
<div class="sender-preview-info" style="display:flex;align-items:center;justify-content:space-between;gap:0.5rem;padding:0.625rem 0.75rem;margin:0 0 0.5rem 0;background:#eff6ff;border:1px solid #bfdbfe;border-radius:8px;color:#1e3a8a;font-size:0.85rem;">
<span>@L("Sender_OriginalDocument_NoSignatureFieldsHint", "Hinweis: In dieser Ansicht werden keine Unterschriftsfelder angezeigt.")</span>
<button type="button"
class="sender-preview-info__close"
@onclick="DismissPreviewHint"
title="@L("Close", "Schliessen")"
aria-label="@L("Close", "Schliessen")"
style="display:inline-flex;align-items:center;justify-content:center;padding:0.125rem;border:none;background:transparent;box-shadow:none;appearance:none;cursor:pointer;color:#1e3a8a;line-height:1;">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true" focusable="false">
<path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.75.75 0 1 1 1.06 1.06L9.06 8l3.22 3.22a.75.75 0 1 1-1.06 1.06L8 9.06l-3.22 3.22a.75.75 0 1 1-1.06-1.06L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06z"/>
</svg>
</button>
</div>
}
<SenderPdfImageEditor IsPlacementMode="false"
PageCount="@_previewPageCount"
Pages="@_previewPagePreviews"
@@ -591,6 +607,9 @@
private string _previewFileName = "document.pdf";
private string? _previewErrorMessage = null;
private readonly IReadOnlyList<SenderPdfFieldOverlay> _previewFieldOverlays = [];
private bool _showPreviewHint = false;
private int _previewHintVersion = 0;
private const int PreviewHintAutoHideMs = 4000;
private string? PreviewPdfDataUrl => _previewPdfBytes is { Length: > 0 }
? $"data:application/pdf;base64,{Convert.ToBase64String(_previewPdfBytes)}"
@@ -1165,6 +1184,7 @@
// PDF ready — mount the viewer now
_previewViewerMounted = true;
ShowPreviewHint();
}
catch (HttpRequestException ex) when ((int?)ex.StatusCode == 404)
{
@@ -1189,6 +1209,9 @@
void OnPreviewClosed()
{
_previewHintVersion++;
_showPreviewHint = false;
// Unmount the viewer first — prevent DevExpress async render from
// accessing the already-disposed byte array
_previewViewerMounted = false;
@@ -1220,6 +1243,28 @@
return Task.CompletedTask;
}
void DismissPreviewHint()
{
_previewHintVersion++;
_showPreviewHint = false;
}
void ShowPreviewHint()
{
_showPreviewHint = true;
var currentVersion = ++_previewHintVersion;
_ = Task.Run(async () =>
{
await Task.Delay(PreviewHintAutoHideMs);
if (currentVersion != _previewHintVersion)
return;
_showPreviewHint = false;
await InvokeAsync(StateHasChanged);
});
}
static int? EstimatePdfPageCount(byte[]? documentBytes)
{
if (documentBytes is null || documentBytes.Length == 0)