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

@@ -85,15 +85,19 @@
<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" />
@@ -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,7 +119,9 @@
<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">
@@ -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>
@@ -156,7 +167,8 @@
</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>
@@ -233,17 +248,22 @@
<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" />
@@ -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,17 +366,22 @@
<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" />
@@ -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>
} }
} }

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

@@ -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;