Refactor and cleanup EnvelopeSender components

Refactored code for improved readability, consistency, and maintainability. Removed the WASM boot loading overlay and its associated styles/scripts. Updated SVG paths to use self-closing tags for HTML compliance. Reformatted conditional blocks, state management, and session data logic. Enhanced error handling, spinner visibility, and empty state displays.

Removed redundant comments and unused code. Added `envelope-editor.js` for new functionality. Cleaned up CSS and JavaScript related to the removed WASM overlay. Improved grid layout persistence and signature field logic. Adjusted localization strings for better alignment with German translations.
This commit is contained in:
2026-09-21 13:05:59 +02:00
parent f517ab4cfb
commit e5402bafc9
4 changed files with 168 additions and 188 deletions

View File

@@ -34,7 +34,7 @@
else else
{ {
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" viewBox="0 0 16 16"> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" viewBox="0 0 16 16">
<path d="M0 4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V4Zm2-1a1 1 0 0 0-1 1v.217l7 4.2 7-4.2V4a1 1 0 0 0-1-1H2Zm13 2.383-4.708 2.825L15 11.105V5.383Zm-.034 6.876-5.64-3.471L8 9.583l-1.326-.795-5.64 3.47A1 1 0 0 0 2 13h12a1 1 0 0 0 .966-.741ZM1 11.105l4.708-2.897L1 5.383v5.722Z"/> <path d="M0 4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V4Zm2-1a1 1 0 0 0-1 1v.217l7 4.2 7-4.2V4a1 1 0 0 0-1-1H2Zm13 2.383-4.708 2.825L15 11.105V5.383Zm-.034 6.876-5.64-3.471L8 9.583l-1.326-.795-5.64 3.47A1 1 0 0 0 2 13h12a1 1 0 0 0 .966-.741ZM1 11.105l4.708-2.897L1 5.383v5.722Z" />
</svg> </svg>
} }
</div> </div>
@@ -60,43 +60,47 @@
<div class="sender-toolbar"> <div class="sender-toolbar">
<button class="sender-btn sender-btn--primary" @onclick="CreateEnvelope" title="Neuen Umschlag erstellen"> <button class="sender-btn sender-btn--primary" @onclick="CreateEnvelope" title="Neuen Umschlag erstellen">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"/> <path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z" />
</svg> </svg>
Neuer Umschlag Neuer Umschlag
</button> </button>
<button class="sender-btn" @onclick="EditEnvelope" disabled="@(_selectedEnvelope == null || IsEnvelopeSent(_selectedEnvelope))" title="Ausgewählten Umschlag bearbeiten"> <button class="sender-btn" @onclick="EditEnvelope" disabled="@(_selectedEnvelope == null || IsEnvelopeSent(_selectedEnvelope))" title="Ausgewählten Umschlag bearbeiten">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
<path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z"/> <path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z" />
</svg> </svg>
Bearbeiten Bearbeiten
</button> </button>
<button class="sender-btn sender-btn--danger" @onclick="DeleteEnvelope" disabled="@(_selectedEnvelope == null)" title="Ausgewählten Umschlag löschen"> <button class="sender-btn sender-btn--danger" @onclick="DeleteEnvelope" disabled="@(_selectedEnvelope == null)" title="Ausgewählten Umschlag löschen">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/> <path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z" />
<path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/> <path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z" />
</svg> </svg>
Löschen Löschen
</button> </button>
<button class="sender-btn" @onclick="RefreshEnvelopes" disabled="@_isLoading" title="Aktualisieren"> <button class="sender-btn" @onclick="RefreshEnvelopes" disabled="@_isLoading" title="Aktualisieren">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z"/> <path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z" />
<path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z"/> <path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z" />
</svg> </svg>
@if (_isLoading) { @if (_isLoading)
{
<span class="spinner-border spinner-border-sm" style="width: 14px; height: 14px;"></span> <span class="spinner-border spinner-border-sm" style="width: 14px; height: 14px;"></span>
} }
</button> </button>
<button class="sender-btn sender-btn--logout" @onclick="LogoutAsync" disabled="@_isLoggingOut" title="Abmelden"> <button class="sender-btn sender-btn--logout" @onclick="LogoutAsync" disabled="@_isLoggingOut" title="Abmelden">
@if (_isLoggingOut) { @if (_isLoggingOut)
{
<span class="spinner-border spinner-border-sm" style="width: 14px; height: 14px;"></span> <span class="spinner-border spinner-border-sm" style="width: 14px; height: 14px;"></span>
} else { }
else
{
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M10 12.5a.5.5 0 0 1-.5.5h-8a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5h8a.5.5 0 0 1 .5.5v2a.5.5 0 0 0 1 0v-2A1.5 1.5 0 0 0 9.5 2h-8A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h8a1.5 1.5 0 0 0 1.5-1.5v-2a.5.5 0 0 0-1 0v2z"/> <path fill-rule="evenodd" d="M10 12.5a.5.5 0 0 1-.5.5h-8a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5h8a.5.5 0 0 1 .5.5v2a.5.5 0 0 0 1 0v-2A1.5 1.5 0 0 0 9.5 2h-8A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h8a1.5 1.5 0 0 0 1.5-1.5v-2a.5.5 0 0 0-1 0v2z" />
<path fill-rule="evenodd" d="M15.854 8.354a.5.5 0 0 0 0-.708l-3-3a.5.5 0 0 0-.708.708L14.293 7.5H5.5a.5.5 0 0 0 0 1h8.793l-2.147 2.146a.5.5 0 0 0 .708.708l3-3z"/> <path fill-rule="evenodd" d="M15.854 8.354a.5.5 0 0 0 0-.708l-3-3a.5.5 0 0 0-.708.708L14.293 7.5H5.5a.5.5 0 0 0 0 1h8.793l-2.147 2.146a.5.5 0 0 0 .708.708l3-3z" />
</svg> </svg>
} }
</button> </button>
@@ -105,7 +109,8 @@
</div> </div>
<div class="sender-content"> <div class="sender-content">
@if (_isLoading && _allEnvelopes == null) { @if (_isLoading && _allEnvelopes == null)
{
<div class="d-flex justify-content-center align-items-center h-100"> <div class="d-flex justify-content-center align-items-center h-100">
<div class="text-center"> <div class="text-center">
<div class="spinner-border text-white mb-3" style="width: 3.5rem; height: 3.5rem;" role="status"> <div class="spinner-border text-white mb-3" style="width: 3.5rem; height: 3.5rem;" role="status">
@@ -114,13 +119,15 @@
<p class="text-white fw-semibold">Umschläge werden geladen...</p> <p class="text-white fw-semibold">Umschläge werden geladen...</p>
</div> </div>
</div> </div>
} else if (_errorMessage != null) { }
else if (_errorMessage != null)
{
<div class="error-container"> <div class="error-container">
<div class="alert alert-danger shadow-lg"> <div class="alert alert-danger shadow-lg">
<div class="d-flex align-items-start"> <div class="d-flex align-items-start">
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" fill="currentColor" class="me-3 flex-shrink-0" viewBox="0 0 16 16"> <svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" fill="currentColor" class="me-3 flex-shrink-0" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/> <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" />
<path d="M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995z"/> <path d="M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995z" />
</svg> </svg>
<div> <div>
<h5 class="mb-2">Fehler beim Laden der Umschläge</h5> <h5 class="mb-2">Fehler beim Laden der Umschläge</h5>
@@ -129,18 +136,22 @@
</div> </div>
</div> </div>
</div> </div>
} else { }
else
{
<div class="sender-grid-container"> <div class="sender-grid-container">
<div class="sender-tabs"> <div class="sender-tabs">
<button class="sender-tab @(_activeTab == "active" ? "sender-tab--active" : "")" @onclick='() => _activeTab = "active"'> <button class="sender-tab @(_activeTab == "active" ? "sender-tab--active" : "")" @onclick='() => _activeTab = "active"'>
<span>Aktive Umschläge</span> <span>Aktive Umschläge</span>
@if (_activeEnvelopes != null) { @if (_activeEnvelopes != null)
{
<span style="opacity: 0.6; margin-left: 0.5rem;">(@_activeEnvelopes.Count())</span> <span style="opacity: 0.6; margin-left: 0.5rem;">(@_activeEnvelopes.Count())</span>
} }
</button> </button>
<button class="sender-tab @(_activeTab == "completed" ? "sender-tab--active" : "")" @onclick='() => _activeTab = "completed"'> <button class="sender-tab @(_activeTab == "completed" ? "sender-tab--active" : "")" @onclick='() => _activeTab = "completed"'>
<span>Abgeschlossene Umschläge</span> <span>Abgeschlossene Umschläge</span>
@if (_completedEnvelopes != null) { @if (_completedEnvelopes != null)
{
<span style="opacity: 0.6; margin-left: 0.5rem;">(@_completedEnvelopes.Count())</span> <span style="opacity: 0.6; margin-left: 0.5rem;">(@_completedEnvelopes.Count())</span>
} }
</button> </button>
@@ -151,12 +162,13 @@
@* Grid persistence hint — informs users their layout is auto-saved *@ @* Grid persistence hint — informs users their layout is auto-saved *@
<div class="sf-grid-persistence-hint"> <div class="sf-grid-persistence-hint">
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="currentColor" viewBox="0 0 16 16"> <svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="currentColor" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/> <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" />
<path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533L8.93 6.588zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0z"/> <path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533L8.93 6.588zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0z" />
</svg> </svg>
Die Größe und Position der Spalten wird automatisch für Sie gespeichert. Die Größe und Position der Spalten wird automatisch für Sie gespeichert.
</div> </div>
@if (_activeTab == "active") { @if (_activeTab == "active")
{
<DxGrid Data="@_activeEnvelopes" <DxGrid Data="@_activeEnvelopes"
@ref="_gridActive" @ref="_gridActive"
ShowFilterRow="true" ShowFilterRow="true"
@@ -189,7 +201,8 @@
<CellDisplayTemplate Context="cellContext"> <CellDisplayTemplate Context="cellContext">
@{ @{
var envelope = cellContext.DataItem as EnvelopeDto; var envelope = cellContext.DataItem as EnvelopeDto;
if (envelope != null) { if (envelope != null)
{
var statusInfo = GetStatusInfo(envelope.Status); var statusInfo = GetStatusInfo(envelope.Status);
<div class="status-badge status-badge--@statusInfo.CssClass"> <div class="status-badge status-badge--@statusInfo.CssClass">
<span class="status-dot status-dot--@statusInfo.DotColor"></span> <span class="status-dot status-dot--@statusInfo.DotColor"></span>
@@ -203,7 +216,8 @@
<CellDisplayTemplate Context="cellContext"> <CellDisplayTemplate Context="cellContext">
@{ @{
var envelope = cellContext.DataItem as EnvelopeDto; var envelope = cellContext.DataItem as EnvelopeDto;
if (envelope != null) { if (envelope != null)
{
var receivers = envelope.EnvelopeReceivers?.ToList() ?? []; var receivers = envelope.EnvelopeReceivers?.ToList() ?? [];
var signed = receivers.Count(r => r.Signed); var signed = receivers.Count(r => r.Signed);
var total = receivers.Count; var total = receivers.Count;
@@ -211,7 +225,8 @@
<span style="font-size: 0.875rem; color: #6b7280;"> <span style="font-size: 0.875rem; color: #6b7280;">
@signed / @total unterschrieben @signed / @total unterschrieben
</span> </span>
@if (total > 0) { @if (total > 0)
{
<div style="flex: 1; min-width: 60px; max-width: 120px; height: 6px; background: #e5e7eb; border-radius: 3px; overflow: hidden;"> <div style="flex: 1; min-width: 60px; max-width: 120px; height: 6px; background: #e5e7eb; border-radius: 3px; overflow: hidden;">
<div style="height: 100%; background: linear-gradient(90deg, #81c784 0%, #66bb6a 100%); width: @((signed * 100.0 / total).ToString("F0"))%;"></div> <div style="height: 100%; background: linear-gradient(90deg, #81c784 0%, #66bb6a 100%); width: @((signed * 100.0 / total).ToString("F0"))%;"></div>
</div> </div>
@@ -219,7 +234,7 @@
</div> </div>
} }
} }
</CellDisplayTemplate> </CellDisplayTemplate>
</DxGridDataColumn> </DxGridDataColumn>
<DxGridDataColumn FieldName="EnvelopeTypeTitle" Caption="Typ" <DxGridDataColumn FieldName="EnvelopeTypeTitle" Caption="Typ"
Width="120px" AllowSort="true" /> Width="120px" AllowSort="true" />
@@ -233,20 +248,25 @@
<h6 style="font-weight: 600; color: #374151; margin-bottom: 0.75rem;">Empfänger</h6> <h6 style="font-weight: 600; color: #374151; margin-bottom: 0.75rem;">Empfänger</h6>
@{ @{
var envelope = detailContext.DataItem as EnvelopeDto; var envelope = detailContext.DataItem as EnvelopeDto;
if (envelope?.EnvelopeReceivers?.Any() == true) { if (envelope?.EnvelopeReceivers?.Any() == true)
{
<div style="display: flex; flex-direction: column; gap: 0.5rem;"> <div style="display: flex; flex-direction: column; gap: 0.5rem;">
@foreach (var receiver in envelope.EnvelopeReceivers) { @foreach (var receiver in envelope.EnvelopeReceivers)
{
<div style="display: flex; align-items: center; gap: 1rem; padding: 0.5rem; background: white; border-radius: 6px; border: 1px solid #e5e7eb;"> <div style="display: flex; align-items: center; gap: 1rem; padding: 0.5rem; background: white; border-radius: 6px; border: 1px solid #e5e7eb;">
<span class="receiver-badge receiver-badge--@(receiver.Signed ? "signed" : "unsigned")" style="min-width: 100px;"> <span class="receiver-badge receiver-badge--@(receiver.Signed ? "signed" : "unsigned")" style="min-width: 100px;">
@if (receiver.Signed) { @if (receiver.Signed)
{
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16"> <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16">
<path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z"/> <path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z" />
</svg> </svg>
<span>Unterschrieben</span> <span>Unterschrieben</span>
} else { }
else
{
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16"> <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/> <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" />
<path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"/> <path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z" />
</svg> </svg>
<span>Ausstehend</span> <span>Ausstehend</span>
} }
@@ -258,14 +278,18 @@
</div> </div>
} }
</div> </div>
} else { }
else
{
<p style="color: #9ca3af; font-size: 0.875rem; margin: 0;">Keine Empfänger</p> <p style="color: #9ca3af; font-size: 0.875rem; margin: 0;">Keine Empfänger</p>
} }
} }
</div> </div>
</DetailRowTemplate> </DetailRowTemplate>
</DxGrid> </DxGrid>
} else { }
else
{
<DxGrid Data="@_completedEnvelopes" <DxGrid Data="@_completedEnvelopes"
@ref="_gridCompleted" @ref="_gridCompleted"
ShowFilterRow="true" ShowFilterRow="true"
@@ -295,7 +319,8 @@
<CellDisplayTemplate Context="cellContext"> <CellDisplayTemplate Context="cellContext">
@{ @{
var envelope = cellContext.DataItem as EnvelopeDto; var envelope = cellContext.DataItem as EnvelopeDto;
if (envelope != null) { if (envelope != null)
{
var statusInfo = GetStatusInfo(envelope.Status); var statusInfo = GetStatusInfo(envelope.Status);
<div class="status-badge status-badge--@statusInfo.CssClass"> <div class="status-badge status-badge--@statusInfo.CssClass">
<span class="status-dot status-dot--@statusInfo.DotColor"></span> <span class="status-dot status-dot--@statusInfo.DotColor"></span>
@@ -309,7 +334,8 @@
<CellDisplayTemplate Context="cellContext"> <CellDisplayTemplate Context="cellContext">
@{ @{
var envelope = cellContext.DataItem as EnvelopeDto; var envelope = cellContext.DataItem as EnvelopeDto;
if (envelope != null) { if (envelope != null)
{
var receivers = envelope.EnvelopeReceivers?.ToList() ?? []; var receivers = envelope.EnvelopeReceivers?.ToList() ?? [];
var signed = receivers.Count(r => r.Signed); var signed = receivers.Count(r => r.Signed);
var total = receivers.Count; var total = receivers.Count;
@@ -317,7 +343,8 @@
<span style="font-size: 0.875rem; color: #6b7280;"> <span style="font-size: 0.875rem; color: #6b7280;">
@signed / @total unterschrieben @signed / @total unterschrieben
</span> </span>
@if (total > 0) { @if (total > 0)
{
<div style="flex: 1; min-width: 60px; max-width: 120px; height: 6px; background: #e5e7eb; border-radius: 3px; overflow: hidden;"> <div style="flex: 1; min-width: 60px; max-width: 120px; height: 6px; background: #e5e7eb; border-radius: 3px; overflow: hidden;">
<div style="height: 100%; background: linear-gradient(90deg, #81c784 0%, #66bb6a 100%); width: @((signed * 100.0 / total).ToString("F0"))%;"></div> <div style="height: 100%; background: linear-gradient(90deg, #81c784 0%, #66bb6a 100%); width: @((signed * 100.0 / total).ToString("F0"))%;"></div>
</div> </div>
@@ -339,20 +366,25 @@
<h6 style="font-weight: 600; color: #374151; margin-bottom: 0.75rem;">Empfänger</h6> <h6 style="font-weight: 600; color: #374151; margin-bottom: 0.75rem;">Empfänger</h6>
@{ @{
var envelope = detailContext.DataItem as EnvelopeDto; var envelope = detailContext.DataItem as EnvelopeDto;
if (envelope?.EnvelopeReceivers?.Any() == true) { if (envelope?.EnvelopeReceivers?.Any() == true)
{
<div style="display: flex; flex-direction: column; gap: 0.5rem;"> <div style="display: flex; flex-direction: column; gap: 0.5rem;">
@foreach (var receiver in envelope.EnvelopeReceivers) { @foreach (var receiver in envelope.EnvelopeReceivers)
{
<div style="display: flex; align-items: center; gap: 1rem; padding: 0.5rem; background: white; border-radius: 6px; border: 1px solid #e5e7eb;"> <div style="display: flex; align-items: center; gap: 1rem; padding: 0.5rem; background: white; border-radius: 6px; border: 1px solid #e5e7eb;">
<span class="receiver-badge receiver-badge--@(receiver.Signed ? "signed" : "unsigned")" style="min-width: 100px;"> <span class="receiver-badge receiver-badge--@(receiver.Signed ? "signed" : "unsigned")" style="min-width: 100px;">
@if (receiver.Signed) { @if (receiver.Signed)
{
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16"> <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16">
<path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z"/> <path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z" />
</svg> </svg>
<span>Unterschrieben</span> <span>Unterschrieben</span>
} else { }
else
{
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16"> <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/> <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" />
<path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"/> <path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z" />
</svg> </svg>
<span>Ausstehend</span> <span>Ausstehend</span>
} }
@@ -364,7 +396,9 @@
</div> </div>
} }
</div> </div>
} else { }
else
{
<p style="color: #9ca3af; font-size: 0.875rem; margin: 0;">Keine Empfänger</p> <p style="color: #9ca3af; font-size: 0.875rem; margin: 0;">Keine Empfänger</p>
} }
} }
@@ -395,7 +429,7 @@
private EnvelopeGenerator.Server.Client.Services.CustomImageDto? _companyLogo; private EnvelopeGenerator.Server.Client.Services.CustomImageDto? _companyLogo;
// Stable keys used to store/restore each grid's layout in UserPreferences.GridLayouts // Stable keys used to store/restore each grid's layout in UserPreferences.GridLayouts
private const string GridKeyActive = "sender.active-envelopes"; private const string GridKeyActive = "sender.active-envelopes";
private const string GridKeyCompleted = "sender.completed-envelopes"; private const string GridKeyCompleted = "sender.completed-envelopes";
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
@@ -415,7 +449,7 @@
// Load logo config — authenticated, so this should succeed // Load logo config — authenticated, so this should succeed
var logos = await CustomImagesService.GetAsync(); var logos = await CustomImagesService.GetAsync();
_appLogo = logos.App; _appLogo = logos.App;
_companyLogo = logos.Company; _companyLogo = logos.Company;
await LoadEnvelopesAsync(); await LoadEnvelopesAsync();

View File

@@ -30,31 +30,13 @@
</head> </head>
<body> <body>
<!-- WASM boot loading overlay — hidden once Blazor circuit connects -->
<div id="sf-boot-overlay">
<div class="sf-boot-spinner">
<div class="sf-boot-ring"></div>
</div>
</div>
<Routes /> <Routes />
<script src="_content/DevExpress.Blazor.Resources/js/preload-script.js"></script> <script src="_content/DevExpress.Blazor.Resources/js/preload-script.js"></script>
<script src="js/typed.umd.js"></script> <script src="js/typed.umd.js"></script>
<script src="js/receiver-signature.js?v=9"></script> <script src="js/receiver-signature.js?v=9"></script>
<script src="js/signflow-theme.js"></script> <script src="js/signflow-theme.js"></script>
<script src="js/envelope-editor.js"></script>
<script src="_framework/blazor.web.js"></script> <script src="_framework/blazor.web.js"></script>
<script>
function sfRemoveBootOverlay() {
var overlay = document.getElementById('sf-boot-overlay');
if (overlay) { overlay.style.opacity = '0'; setTimeout(function () { if (overlay) overlay.remove(); }, 250); }
}
// Remove on every enhanced navigation (covers both WASM and Server render modes).
Blazor.addEventListener('enhancedload', sfRemoveBootOverlay);
// Also remove when WASM circuit is ready (for non-enhanced pages like /sender)
document.addEventListener('blazor-circuit-connected', sfRemoveBootOverlay, { once: true });
// Fallback: remove after 4s regardless
setTimeout(sfRemoveBootOverlay, 4000);
</script>
</body> </body>
</html> </html>

View File

@@ -81,8 +81,8 @@
@if (_signatureFields.Count > 0) @if (_signatureFields.Count > 0)
{ {
<span style="display: inline-flex; align-items: center; padding: 0.125rem 0.4rem; <span style="display: inline-flex; align-items: center; padding: 0.125rem 0.4rem;
background: #ede9fe; border-radius: 0.25rem; color: #6d28d9; background: #ede9fe; border-radius: 0.25rem; color: #6d28d9;
font-weight: 500; font-size: 0.7rem; white-space: nowrap;"> font-weight: 500; font-size: 0.7rem; white-space: nowrap;">
@_signatureFields.Count Signaturfeld@(_signatureFields.Count != 1 ? "er" : "") @_signatureFields.Count Signaturfeld@(_signatureFields.Count != 1 ? "er" : "")
</span> </span>
} }
@@ -118,8 +118,8 @@
<div class="sender-receivers-list"> <div class="sender-receivers-list">
@foreach (var receiver in _receivers) @foreach (var receiver in _receivers)
{ {
<div class="sender-receiver-chip" <div class="sender-receiver-chip"
style="border-left: 3px solid @receiver.Color;"> style="border-left: 3px solid @receiver.Color;">
<div class="sender-receiver-chip__body"> <div class="sender-receiver-chip__body">
<div class="sender-receiver-chip__name">@receiver.FullName</div> <div class="sender-receiver-chip__name">@receiver.FullName</div>
<div class="sender-receiver-chip__email">@receiver.Email</div> <div class="sender-receiver-chip__email">@receiver.Email</div>
@@ -130,7 +130,7 @@
</div> </div>
<button class="pdf-toolbar__btn pdf-toolbar__btn--signature-change sender-toolbar-action-btn sender-toolbar-action-btn--compact <button class="pdf-toolbar__btn pdf-toolbar__btn--signature-change sender-toolbar-action-btn sender-toolbar-action-btn--compact
@(_pendingReceiverForPlacement?.Id == receiver.Id ? "pdf-toolbar__btn--signature-change-active" : "")" @(_pendingReceiverForPlacement?.Id == receiver.Id ? "pdf-toolbar__btn--signature-change-active" : "")"
@onclick="() => ActivatePlacementForReceiver(receiver)" @onclick="() => ActivatePlacementForReceiver(receiver)"
style="@(_pendingReceiverForPlacement?.Id == receiver.Id ? $"background:{receiver.Color};color:#fff;border-color:{receiver.Color};" : $"color:{receiver.Color};border-color:{receiver.Color};")" style="@(_pendingReceiverForPlacement?.Id == receiver.Id ? $"background:{receiver.Color};color:#fff;border-color:{receiver.Color};" : $"color:{receiver.Color};border-color:{receiver.Color};")"
title="Signaturfeld platzieren"> title="Signaturfeld platzieren">
@@ -211,7 +211,7 @@
@if (_pendingReceiverForPlacement is not null) @if (_pendingReceiverForPlacement is not null)
{ {
<div style="background: @_pendingReceiverForPlacement.Color; color: white; font-size: 0.75rem; font-weight: 500; <div style="background: @_pendingReceiverForPlacement.Color; color: white; font-size: 0.75rem; font-weight: 500;
padding: 0.3rem 1.5rem; text-align: center; letter-spacing: 0.01em;"> padding: 0.3rem 1.5rem; text-align: center; letter-spacing: 0.01em;">
📌 Klicken Sie auf die Stelle im Dokument für <strong>@_pendingReceiverForPlacement.FullName</strong>. 📌 Klicken Sie auf die Stelle im Dokument für <strong>@_pendingReceiverForPlacement.FullName</strong>.
&nbsp;<button @onclick="CancelPlacement" &nbsp;<button @onclick="CancelPlacement"
style="background:none;border:none;color:white;cursor:pointer;font-size:0.75rem;text-decoration:underline;padding:0;"> style="background:none;border:none;color:white;cursor:pointer;font-size:0.75rem;text-decoration:underline;padding:0;">
@@ -229,8 +229,8 @@
<div class="d-flex justify-content-center align-items-center h-100"> <div class="d-flex justify-content-center align-items-center h-100">
<div class="text-center" style="max-width: 420px;"> <div class="text-center" style="max-width: 420px;">
<div style="width: 72px; height: 72px; background: rgba(255,255,255,0.15); <div style="width: 72px; height: 72px; background: rgba(255,255,255,0.15);
border-radius: 50%; display: flex; align-items: center; border-radius: 50%; display: flex; align-items: center;
justify-content: center; margin: 0 auto 1.25rem;"> justify-content: center; margin: 0 auto 1.25rem;">
<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" fill="white" viewBox="0 0 16 16"> <svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" fill="white" viewBox="0 0 16 16">
<path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5L14 4.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5h-2z" /> <path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5L14 4.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5h-2z" />
</svg> </svg>
@@ -316,13 +316,13 @@
@if (_receiverEmailSuggestions.Count > 0) @if (_receiverEmailSuggestions.Count > 0)
{ {
<div class="sender-receiver-popup__suggestions"> <div class="sender-receiver-popup__suggestions">
<DxListBox TData="string" <DxListBox TData="string"
TValue="string" TValue="string"
Data="@_receiverEmailSuggestions" Data="@_receiverEmailSuggestions"
Value="@_selectedReceiverEmailSuggestion" Value="@_selectedReceiverEmailSuggestion"
ValueChanged="OnReceiverEmailSuggestionCommittedAsync" ValueChanged="OnReceiverEmailSuggestionCommittedAsync"
SelectionMode="ListBoxSelectionMode.Single" SelectionMode="ListBoxSelectionMode.Single"
CssClass="w-100" /> CssClass="w-100" />
</div> </div>
} }
</div> </div>
@@ -370,9 +370,9 @@
{ {
<div style="display:flex;align-items:flex-start;gap:1rem;padding:0.5rem 0;"> <div style="display:flex;align-items:flex-start;gap:1rem;padding:0.5rem 0;">
<div style="flex-shrink:0;width:40px;height:40px;background:#d1fae5;border-radius:50%; <div style="flex-shrink:0;width:40px;height:40px;background:#d1fae5;border-radius:50%;
display:flex;align-items:center;justify-content:center;"> display:flex;align-items:center;justify-content:center;">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="#065f46" viewBox="0 0 16 16"> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="#065f46" viewBox="0 0 16 16">
<path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z"/> <path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z" />
</svg> </svg>
</div> </div>
<div> <div>
@@ -389,10 +389,10 @@
{ {
<div style="display:flex;align-items:flex-start;gap:1rem;padding:0.5rem 0;"> <div style="display:flex;align-items:flex-start;gap:1rem;padding:0.5rem 0;">
<div style="flex-shrink:0;width:40px;height:40px;background:#fee2e2;border-radius:50%; <div style="flex-shrink:0;width:40px;height:40px;background:#fee2e2;border-radius:50%;
display:flex;align-items:center;justify-content:center;"> display:flex;align-items:center;justify-content:center;">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="#991b1b" viewBox="0 0 16 16"> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="#991b1b" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/> <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" />
<path d="M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995z"/> <path d="M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995z" />
</svg> </svg>
</div> </div>
<div> <div>
@@ -413,7 +413,7 @@
<button class="btn btn-primary" <button class="btn btn-primary"
@onclick="GoToDashboard" @onclick="GoToDashboard"
style="background:linear-gradient(135deg,#059669,#047857);border:none;border-radius:6px; style="background:linear-gradient(135deg,#059669,#047857);border:none;border-radius:6px;
padding:0.5rem 1.5rem;font-weight:600;color:#fff;"> padding:0.5rem 1.5rem;font-weight:600;color:#fff;">
Zur Übersicht Zur Übersicht
</button> </button>
} }
@@ -436,7 +436,7 @@
// ── Constants ── // ── Constants ──
// Signature field size in PDF points (fixed): 1.77" × 1.96" // Signature field size in PDF points (fixed): 1.77" × 1.96"
const double SigWidthPt = 1.77 * 72; // 127.44 pt const double SigWidthPt = 1.77 * 72; // 127.44 pt
const double SigHeightPt = 1.96 * 72; // 141.12 pt const double SigHeightPt = 1.96 * 72; // 141.12 pt
// CssClass for DxPdfViewer — used by JS to locate page elements // CssClass for DxPdfViewer — used by JS to locate page elements
@@ -447,8 +447,8 @@
// ── State ── // ── State ──
DxPdfViewer? _pdfViewer; DxPdfViewer? _pdfViewer;
bool _pdfLoaded = false; bool _pdfLoaded = false;
string _fileName = string.Empty; string _fileName = string.Empty;
string? _errorMessage; string? _errorMessage;
byte[]? _pdfBytes; // Current rendered PDF (original + placeholders burned in) byte[]? _pdfBytes; // Current rendered PDF (original + placeholders burned in)
byte[]? _originalPdfBytes; // Pristine upload — never modified, used as base for redraw byte[]? _originalPdfBytes; // Pristine upload — never modified, used as base for redraw
@@ -457,25 +457,25 @@
ReceiverDraft? _pendingReceiverForPlacement; // Set when user clicks "Signatur hinzufügen" ReceiverDraft? _pendingReceiverForPlacement; // Set when user clicks "Signatur hinzufügen"
// ── Save state ── // ── Save state ──
bool _isSaving = false; bool _isSaving = false;
bool _savePopupVisible = false; bool _savePopupVisible = false;
string? _saveErrorMessage = null; string? _saveErrorMessage = null;
// ── Envelope metadata ── // ── Envelope metadata ──
string _envelopeTitle = string.Empty; string _envelopeTitle = string.Empty;
string _envelopeMessage = string.Empty; string _envelopeMessage = string.Empty;
bool _titleTouched = false; bool _titleTouched = false;
List<ReceiverDraft> _receivers = []; List<ReceiverDraft> _receivers = [];
bool _receiverPopupVisible; bool _receiverPopupVisible;
string _receiverDraftName = string.Empty; string _receiverDraftName = string.Empty;
string _receiverDraftEmail = string.Empty; string _receiverDraftEmail = string.Empty;
string _receiverDraftPhoneNumber = string.Empty; string _receiverDraftPhoneNumber = string.Empty;
string? _selectedReceiverEmailSuggestion; string? _selectedReceiverEmailSuggestion;
string? _receiverPopupValidationMessage; string? _receiverPopupValidationMessage;
bool _isReceiverEmailSearchRunning; bool _isReceiverEmailSearchRunning;
List<string> _receiverEmailSuggestions = []; List<string> _receiverEmailSuggestions = [];
int _receiverEmailSearchVersion; int _receiverEmailSearchVersion;
static readonly System.ComponentModel.DataAnnotations.EmailAddressAttribute ReceiverEmailValidator = new(); static readonly System.ComponentModel.DataAnnotations.EmailAddressAttribute ReceiverEmailValidator = new();
@@ -512,12 +512,12 @@
&& cached is not null) && cached is not null)
{ {
_originalPdfBytes = cached.OriginalPdfBytes; _originalPdfBytes = cached.OriginalPdfBytes;
_signatureFields = cached.Fields; _signatureFields = cached.Fields;
_fileName = cached.FileName; _fileName = cached.FileName;
_pdfLoaded = _originalPdfBytes is { Length: > 0 }; _pdfLoaded = _originalPdfBytes is { Length: > 0 };
_receivers = cached.Receivers; _receivers = cached.Receivers;
_envelopeTitle = cached.Title; _envelopeTitle = cached.Title;
_envelopeMessage = cached.Message; _envelopeMessage = cached.Message;
// Redraw placeholders onto the original PDF // Redraw placeholders onto the original PDF
if (_pdfLoaded) if (_pdfLoaded)
@@ -545,8 +545,8 @@
await file.OpenReadStream(maxBytes).CopyToAsync(ms); await file.OpenReadStream(maxBytes).CopyToAsync(ms);
_originalPdfBytes = ms.ToArray(); _originalPdfBytes = ms.ToArray();
_fileName = file.Name; _fileName = file.Name;
_pdfLoaded = true; _pdfLoaded = true;
_signatureFields.Clear(); _signatureFields.Clear();
_pendingReceiverForPlacement = null; _pendingReceiverForPlacement = null;
@@ -597,12 +597,12 @@
double pageHeightPt; double pageHeightPt;
try try
{ {
using var ms = new System.IO.MemoryStream(_originalPdfBytes); using var ms = new System.IO.MemoryStream(_originalPdfBytes);
var doc = PdfSharp.Pdf.IO.PdfReader.Open(ms, PdfSharp.Pdf.IO.PdfDocumentOpenMode.Import); var doc = PdfSharp.Pdf.IO.PdfReader.Open(ms, PdfSharp.Pdf.IO.PdfDocumentOpenMode.Import);
int pageIndex = Math.Max(0, Math.Min(coords.PageIndex, doc.PageCount - 1)); int pageIndex = Math.Max(0, Math.Min(coords.PageIndex, doc.PageCount - 1));
var page = doc.Pages[pageIndex]; var page = doc.Pages[pageIndex];
pageWidthPt = page.Width.Point; pageWidthPt = page.Width.Point;
pageHeightPt = page.Height.Point; pageHeightPt = page.Height.Point;
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -614,17 +614,17 @@
double xPt = coords.NormX * pageWidthPt; double xPt = coords.NormX * pageWidthPt;
double yPt = coords.NormY * pageHeightPt; double yPt = coords.NormY * pageHeightPt;
xPt = Math.Max(0, Math.Min(xPt, pageWidthPt - SigWidthPt)); xPt = Math.Max(0, Math.Min(xPt, pageWidthPt - SigWidthPt));
yPt = Math.Max(0, Math.Min(yPt, pageHeightPt - SigHeightPt)); yPt = Math.Max(0, Math.Min(yPt, pageHeightPt - SigHeightPt));
int page1Based = coords.PageIndex + 1; int page1Based = coords.PageIndex + 1;
var field = new SignatureFieldDraft( var field = new SignatureFieldDraft(
XPt: xPt, XPt: xPt,
YPt: yPt, YPt: yPt,
Page: page1Based, Page: page1Based,
ReceiverName: _pendingReceiverForPlacement.FullName, ReceiverName: _pendingReceiverForPlacement.FullName,
Color: _pendingReceiverForPlacement.Color); Color: _pendingReceiverForPlacement.Color);
_signatureFields.Add(field); _signatureFields.Add(field);
_pendingReceiverForPlacement = null; _pendingReceiverForPlacement = null;
@@ -711,7 +711,7 @@
string.Join(", ", receiversWithoutField.Select(r => r.FullName))); string.Join(", ", receiversWithoutField.Select(r => r.FullName)));
} }
_isSaving = true; _isSaving = true;
_saveErrorMessage = null; _saveErrorMessage = null;
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);
@@ -728,16 +728,16 @@
var fields = _signatureFields var fields = _signatureFields
.Where(f => f.ReceiverName == receiver.FullName) .Where(f => f.ReceiverName == receiver.FullName)
.Select(f => new DocReceiverElementCreateDto( .Select(f => new DocReceiverElementCreateDto(
X: f.XPt / 72.0, X: f.XPt / 72.0,
Y: f.YPt / 72.0, Y: f.YPt / 72.0,
Page: f.Page)) Page: f.Page))
.ToList(); .ToList();
return new ReceiverGetOrCreateCommand return new ReceiverGetOrCreateCommand
{ {
EmailAddress = receiver.Email, EmailAddress = receiver.Email,
Salution = receiver.FullName, Salution = receiver.FullName,
PhoneNumber = string.IsNullOrWhiteSpace(receiver.PhoneNumber) PhoneNumber = string.IsNullOrWhiteSpace(receiver.PhoneNumber)
? null ? null
: receiver.PhoneNumber, : receiver.PhoneNumber,
DocReceiverElements = fields, DocReceiverElements = fields,
@@ -746,13 +746,13 @@
var command = new CreateEnvelopeReceiverCommand var command = new CreateEnvelopeReceiverCommand
{ {
Title = _envelopeTitle.Trim(), Title = _envelopeTitle.Trim(),
Message = string.IsNullOrWhiteSpace(_envelopeMessage) Message = string.IsNullOrWhiteSpace(_envelopeMessage)
? "Bitte unterzeichnen Sie das beigefügte Dokument." ? "Bitte unterzeichnen Sie das beigefügte Dokument."
: _envelopeMessage.Trim(), : _envelopeMessage.Trim(),
TFAEnabled = false, TFAEnabled = false,
Document = new DocumentCreateCommand { DataAsBase64 = docBase64 }, Document = new DocumentCreateCommand { DataAsBase64 = docBase64 },
Receivers = receiversCmd, Receivers = receiversCmd,
}; };
var result = await EnvelopeReceiverService.CreateAsync(command); var result = await EnvelopeReceiverService.CreateAsync(command);
@@ -784,11 +784,11 @@
var data = new EditorSessionData( var data = new EditorSessionData(
OriginalPdfBytes: _originalPdfBytes ?? [], OriginalPdfBytes: _originalPdfBytes ?? [],
Fields: [.. _signatureFields], Fields: [.. _signatureFields],
FileName: _fileName, FileName: _fileName,
Receivers: [.. _receivers], Receivers: [.. _receivers],
Title: _envelopeTitle, Title: _envelopeTitle,
Message: _envelopeMessage); Message: _envelopeMessage);
MemoryCache.Set(SessionKey, data, SessionTtl); MemoryCache.Set(SessionKey, data, SessionTtl);
} }
@@ -798,7 +798,7 @@
{ {
if (fields.Count == 0) return originalPdf; if (fields.Count == 0) return originalPdf;
using var inputMs = new System.IO.MemoryStream(originalPdf); using var inputMs = new System.IO.MemoryStream(originalPdf);
using var outputMs = new System.IO.MemoryStream(); using var outputMs = new System.IO.MemoryStream();
var document = PdfSharp.Pdf.IO.PdfReader.Open( var document = PdfSharp.Pdf.IO.PdfReader.Open(
@@ -819,11 +819,11 @@
var nameBrush = new PdfSharp.Drawing.XSolidBrush(HexToXColor(field.Color, alpha: 230)); var nameBrush = new PdfSharp.Drawing.XSolidBrush(HexToXColor(field.Color, alpha: 230));
var fontLabel = new PdfSharp.Drawing.XFont("Arial", 9, PdfSharp.Drawing.XFontStyleEx.Bold); var fontLabel = new PdfSharp.Drawing.XFont("Arial", 9, PdfSharp.Drawing.XFontStyleEx.Bold);
var fontName = new PdfSharp.Drawing.XFont("Arial", 7, PdfSharp.Drawing.XFontStyleEx.Regular); var fontName = new PdfSharp.Drawing.XFont("Arial", 7, PdfSharp.Drawing.XFontStyleEx.Regular);
var fmtCenter = new PdfSharp.Drawing.XStringFormat var fmtCenter = new PdfSharp.Drawing.XStringFormat
{ {
Alignment = PdfSharp.Drawing.XStringAlignment.Center, Alignment = PdfSharp.Drawing.XStringAlignment.Center,
LineAlignment = PdfSharp.Drawing.XLineAlignment.Center, LineAlignment = PdfSharp.Drawing.XLineAlignment.Center,
}; };
@@ -861,11 +861,11 @@
// ── Receiver popup ── // ── Receiver popup ──
void OpenAddReceiverPopup() void OpenAddReceiverPopup()
{ {
_receiverDraftName = string.Empty; _receiverDraftName = string.Empty;
_receiverDraftEmail = string.Empty; _receiverDraftEmail = string.Empty;
_receiverDraftPhoneNumber = string.Empty; _receiverDraftPhoneNumber = string.Empty;
_selectedReceiverEmailSuggestion = null; _selectedReceiverEmailSuggestion = null;
_receiverPopupValidationMessage = null; _receiverPopupValidationMessage = null;
_receiverEmailSuggestions.Clear(); _receiverEmailSuggestions.Clear();
_receiverPopupVisible = true; _receiverPopupVisible = true;
} }
@@ -873,9 +873,9 @@
void CloseAddReceiverPopup() void CloseAddReceiverPopup()
{ {
_receiverPopupVisible = false; _receiverPopupVisible = false;
_receiverPopupValidationMessage = null; _receiverPopupValidationMessage = null;
_selectedReceiverEmailSuggestion = null; _selectedReceiverEmailSuggestion = null;
_isReceiverEmailSearchRunning = false; _isReceiverEmailSearchRunning = false;
} }
void OnReceiverNameChanged(string? value) void OnReceiverNameChanged(string? value)
@@ -947,7 +947,7 @@
{ {
_receiverDraftEmail = value?.Trim() ?? string.Empty; _receiverDraftEmail = value?.Trim() ?? string.Empty;
_selectedReceiverEmailSuggestion = _receiverDraftEmail; _selectedReceiverEmailSuggestion = _receiverDraftEmail;
_receiverPopupValidationMessage = null; _receiverPopupValidationMessage = null;
var searchVersion = ++_receiverEmailSearchVersion; var searchVersion = ++_receiverEmailSearchVersion;
@@ -955,7 +955,7 @@
{ {
_receiverEmailSuggestions.Clear(); _receiverEmailSuggestions.Clear();
_selectedReceiverEmailSuggestion = null; _selectedReceiverEmailSuggestion = null;
_isReceiverEmailSearchRunning = false; _isReceiverEmailSearchRunning = false;
return; return;
} }
@@ -994,8 +994,8 @@
Task SaveReceiverAsync() Task SaveReceiverAsync()
{ {
var fullName = _receiverDraftName.Trim(); var fullName = _receiverDraftName.Trim();
var email = _receiverDraftEmail.Trim(); var email = _receiverDraftEmail.Trim();
var phoneNumber = _receiverDraftPhoneNumber.Trim(); var phoneNumber = _receiverDraftPhoneNumber.Trim();
if (string.IsNullOrWhiteSpace(fullName)) if (string.IsNullOrWhiteSpace(fullName))
@@ -1034,12 +1034,12 @@
record ReceiverDraft(Guid Id, string FullName, string Email, string PhoneNumber, string Color); record ReceiverDraft(Guid Id, string FullName, string Email, string PhoneNumber, string Color);
record EditorSessionData( record EditorSessionData(
byte[] OriginalPdfBytes, byte[] OriginalPdfBytes,
List<SignatureFieldDraft> Fields, List<SignatureFieldDraft> Fields,
string FileName, string FileName,
List<ReceiverDraft> Receivers, List<ReceiverDraft> Receivers,
string Title, string Title,
string Message); string Message);
// ── Receiver colour palette (cycles when > 8 receivers) ── // ── Receiver colour palette (cycles when > 8 receivers) ──
static readonly string[] ReceiverPalette = static readonly string[] ReceiverPalette =

View File

@@ -56,42 +56,6 @@ article {
color: red; color: red;
} }
/* ── WASM Boot Loading Overlay ───────────────────────────────────────────── */
/* Shown immediately on page load, removed once Blazor circuit connects. */
/* Prevents white/unstyled flash while WASM boots (~1-2s on first load). */
#sf-boot-overlay {
position: fixed;
inset: 0;
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 50%, #7e22ce 100%);
transition: opacity 0.25s ease;
pointer-events: none; /* don't block clicks once fading out */
}
.sf-boot-spinner {
display: flex;
flex-direction: column;
align-items: center;
gap: 1.25rem;
}
.sf-boot-ring {
width: 44px;
height: 44px;
border: 3px solid rgba(255, 255, 255, 0.2);
border-top-color: rgba(255, 255, 255, 0.85);
border-radius: 50%;
animation: sf-boot-spin 0.8s linear infinite;
}
@keyframes sf-boot-spin {
to { transform: rotate(360deg); }
}
#blazor-error-ui { #blazor-error-ui {
background: lightyellow; background: lightyellow;
bottom: 0; bottom: 0;