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 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>
@if (_isLoading) {
@if (_isLoading)
{
<span class="spinner-border spinner-border-sm" style="width: 14px; height: 14px;"></span>
}
</button>
<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>
} else {
}
else
{
<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="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 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="text-center">
<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>
</div>
</div>
} else if (_errorMessage != null) {
}
else if (_errorMessage != null)
{
<div class="error-container">
<div class="alert alert-danger shadow-lg">
<div class="d-flex align-items-start">
@@ -129,18 +136,22 @@
</div>
</div>
</div>
} else {
}
else
{
<div class="sender-grid-container">
<div class="sender-tabs">
<button class="sender-tab @(_activeTab == "active" ? "sender-tab--active" : "")" @onclick='() => _activeTab = "active"'>
<span>Aktive Umschläge</span>
@if (_activeEnvelopes != null) {
@if (_activeEnvelopes != null)
{
<span style="opacity: 0.6; margin-left: 0.5rem;">(@_activeEnvelopes.Count())</span>
}
</button>
<button class="sender-tab @(_activeTab == "completed" ? "sender-tab--active" : "")" @onclick='() => _activeTab = "completed"'>
<span>Abgeschlossene Umschläge</span>
@if (_completedEnvelopes != null) {
@if (_completedEnvelopes != null)
{
<span style="opacity: 0.6; margin-left: 0.5rem;">(@_completedEnvelopes.Count())</span>
}
</button>
@@ -156,7 +167,8 @@
</svg>
Die Größe und Position der Spalten wird automatisch für Sie gespeichert.
</div>
@if (_activeTab == "active") {
@if (_activeTab == "active")
{
<DxGrid Data="@_activeEnvelopes"
@ref="_gridActive"
ShowFilterRow="true"
@@ -189,7 +201,8 @@
<CellDisplayTemplate Context="cellContext">
@{
var envelope = cellContext.DataItem as EnvelopeDto;
if (envelope != null) {
if (envelope != null)
{
var statusInfo = GetStatusInfo(envelope.Status);
<div class="status-badge status-badge--@statusInfo.CssClass">
<span class="status-dot status-dot--@statusInfo.DotColor"></span>
@@ -203,7 +216,8 @@
<CellDisplayTemplate Context="cellContext">
@{
var envelope = cellContext.DataItem as EnvelopeDto;
if (envelope != null) {
if (envelope != null)
{
var receivers = envelope.EnvelopeReceivers?.ToList() ?? [];
var signed = receivers.Count(r => r.Signed);
var total = receivers.Count;
@@ -211,7 +225,8 @@
<span style="font-size: 0.875rem; color: #6b7280;">
@signed / @total unterschrieben
</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="height: 100%; background: linear-gradient(90deg, #81c784 0%, #66bb6a 100%); width: @((signed * 100.0 / total).ToString("F0"))%;"></div>
</div>
@@ -233,17 +248,22 @@
<h6 style="font-weight: 600; color: #374151; margin-bottom: 0.75rem;">Empfänger</h6>
@{
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;">
@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;">
<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">
<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>
<span>Unterschrieben</span>
} else {
}
else
{
<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="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>
} else {
}
else
{
<p style="color: #9ca3af; font-size: 0.875rem; margin: 0;">Keine Empfänger</p>
}
}
</div>
</DetailRowTemplate>
</DxGrid>
} else {
}
else
{
<DxGrid Data="@_completedEnvelopes"
@ref="_gridCompleted"
ShowFilterRow="true"
@@ -295,7 +319,8 @@
<CellDisplayTemplate Context="cellContext">
@{
var envelope = cellContext.DataItem as EnvelopeDto;
if (envelope != null) {
if (envelope != null)
{
var statusInfo = GetStatusInfo(envelope.Status);
<div class="status-badge status-badge--@statusInfo.CssClass">
<span class="status-dot status-dot--@statusInfo.DotColor"></span>
@@ -309,7 +334,8 @@
<CellDisplayTemplate Context="cellContext">
@{
var envelope = cellContext.DataItem as EnvelopeDto;
if (envelope != null) {
if (envelope != null)
{
var receivers = envelope.EnvelopeReceivers?.ToList() ?? [];
var signed = receivers.Count(r => r.Signed);
var total = receivers.Count;
@@ -317,7 +343,8 @@
<span style="font-size: 0.875rem; color: #6b7280;">
@signed / @total unterschrieben
</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="height: 100%; background: linear-gradient(90deg, #81c784 0%, #66bb6a 100%); width: @((signed * 100.0 / total).ToString("F0"))%;"></div>
</div>
@@ -339,17 +366,22 @@
<h6 style="font-weight: 600; color: #374151; margin-bottom: 0.75rem;">Empfänger</h6>
@{
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;">
@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;">
<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">
<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>
<span>Unterschrieben</span>
} else {
}
else
{
<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="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>
} else {
}
else
{
<p style="color: #9ca3af; font-size: 0.875rem; margin: 0;">Keine Empfänger</p>
}
}

View File

@@ -30,31 +30,13 @@
</head>
<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 />
<script src="_content/DevExpress.Blazor.Resources/js/preload-script.js"></script>
<script src="js/typed.umd.js"></script>
<script src="js/receiver-signature.js?v=9"></script>
<script src="js/signflow-theme.js"></script>
<script src="js/envelope-editor.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>
</html>

View File

@@ -56,42 +56,6 @@ article {
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 {
background: lightyellow;
bottom: 0;