@page "/sender" @rendermode @(new InteractiveWebAssemblyRenderMode(prerender: false)) @using System.Text.Json @using EnvelopeGenerator.Domain.Constants @using EnvelopeGenerator.Server.Client.Models @using DevExpress.Blazor @using EnvelopeGenerator.Server.Client.Services @using EnvelopeGenerator.Application.Common.Dto @inject EnvelopeGenerator.Server.Client.Services.EnvelopeService EnvelopeService @inject EnvelopeGenerator.Server.Client.Services.AuthService AuthService @inject EnvelopeGenerator.Server.Client.Services.ThemeService ThemeService @inject EnvelopeGenerator.Server.Client.Services.UserPreferencesService UserPreferencesService @inject EnvelopeGenerator.Server.Client.Services.CustomImagesService CustomImagesService @inject NavigationManager Navigation @inject IJSRuntime JSRuntime @inject AppVersionService AppVersion
Umschlag-Übersicht
@* ── Kunden-Logo (Mitte) ───────────────────────────────────────── *@
@if (_companyLogo is not null && !string.IsNullOrEmpty(_companyLogo.Src)) { Kunden-Logo } else {
Kunden-Logo
}
@* ── Toolbar (rechts) ─────────────────────────────────────────── *@
@if (_isLoading && _allEnvelopes == null) {
Lädt...

Umschläge werden geladen...

} else if (_errorMessage != null) {
Fehler beim Laden der Umschläge

@_errorMessage

} else {
@* Grid persistence hint — informs users their layout is auto-saved *@
Die Größe und Position der Spalten wird automatisch für Sie gespeichert.
@if (_activeTab == "active") { @((cellContext.DataItem as EnvelopeDto)?.Id) @((cellContext.DataItem as EnvelopeDto)?.Title) @{ var envelope = cellContext.DataItem as EnvelopeDto; if (envelope != null) { var statusInfo = GetStatusInfo(envelope.Status);
@statusInfo.Label
} }
@{ var envelope = cellContext.DataItem as EnvelopeDto; if (envelope != null) { var receivers = envelope.EnvelopeReceivers?.ToList() ?? []; var signed = receivers.Count(r => r.Signed); var total = receivers.Count;
@signed / @total unterschrieben @if (total > 0) {
}
} }
Empfänger
@{ var envelope = detailContext.DataItem as EnvelopeDto; if (envelope?.EnvelopeReceivers?.Any() == true) {
@foreach (var receiver in envelope.EnvelopeReceivers) {
@if (receiver.Signed) { Unterschrieben } else { Ausstehend }
@receiver.Name @receiver.Receiver?.EmailAddress
}
} else {

Keine Empfänger

} }
} else { @((cellContext.DataItem as EnvelopeDto)?.Id) @((cellContext.DataItem as EnvelopeDto)?.Title) @{ var envelope = cellContext.DataItem as EnvelopeDto; if (envelope != null) { var statusInfo = GetStatusInfo(envelope.Status);
@statusInfo.Label
} }
@{ var envelope = cellContext.DataItem as EnvelopeDto; if (envelope != null) { var receivers = envelope.EnvelopeReceivers?.ToList() ?? []; var signed = receivers.Count(r => r.Signed); var total = receivers.Count;
@signed / @total unterschrieben @if (total > 0) {
}
} }
Empfänger
@{ var envelope = detailContext.DataItem as EnvelopeDto; if (envelope?.EnvelopeReceivers?.Any() == true) {
@foreach (var receiver in envelope.EnvelopeReceivers) {
@if (receiver.Signed) { Unterschrieben } else { Ausstehend }
@receiver.Name @receiver.Receiver?.EmailAddress
}
} else {

Keine Empfänger

} }
}
}
@code { private IEnumerable? _allEnvelopes; private IEnumerable? _activeEnvelopes; private IEnumerable? _completedEnvelopes; private EnvelopeDto? _selectedEnvelope; private string _activeTab = "active"; private bool _isLoading = true; private bool _isLoggingOut = false; private string? _errorMessage; private DxGrid? _gridActive; private DxGrid? _gridCompleted; // Logo config loaded from /api/CustomImages after auth private EnvelopeGenerator.Server.Client.Services.CustomImageDto? _appLogo; private EnvelopeGenerator.Server.Client.Services.CustomImageDto? _companyLogo; // Stable keys used to store/restore each grid's layout in UserPreferences.GridLayouts private const string GridKeyActive = "sender.active-envelopes"; private const string GridKeyCompleted = "sender.completed-envelopes"; protected override async Task OnInitializedAsync() { var hasAccess = await AuthService.CheckSenderAccessAsync(); if (!hasAccess) { Navigation.NavigateTo($"/sender/login"); return; } // Load theme + DisplayName from server after authentication. // Non-fatal: if this fails the page still loads with default theme. // ThemeService.ScheduleSaveAsync() already logs errors internally. try { await ThemeService.LoadFromServerAsync(); } catch { /* swallow — default theme remains active */ } // Load logo config — authenticated, so this should succeed var logos = await CustomImagesService.GetAsync(); _appLogo = logos.App; _companyLogo = logos.Company; await LoadEnvelopesAsync(); } async Task LoadEnvelopesAsync() { _isLoading = true; _errorMessage = null; await InvokeAsync(StateHasChanged); try { _allEnvelopes = await EnvelopeService.GetAsync() ?? []; // Split into active and completed based on status var envelopes = _allEnvelopes.ToList(); _activeEnvelopes = envelopes.Where(e => ((EnvelopeStatus)e.Status).IsActive()).ToList(); _completedEnvelopes = envelopes.Where(e => ((EnvelopeStatus)e.Status).IsCompleted()).ToList(); } catch (Exception ex) { _errorMessage = ex.Message; } finally { _isLoading = false; await InvokeAsync(StateHasChanged); } } async Task RefreshEnvelopes() { await LoadEnvelopesAsync(); } void CreateEnvelope() { Navigation.NavigateTo("/sender/editor"); } void EditEnvelope() { if (_selectedEnvelope == null) return; // TODO: Navigate to envelope editor } void DeleteEnvelope() { if (_selectedEnvelope == null) return; // TODO: Show delete confirmation dialog } async Task LogoutAsync() { _isLoggingOut = true; await InvokeAsync(StateHasChanged); UserPreferencesService.InvalidateCache(); await AuthService.LogoutSenderAsync(); Navigation.NavigateTo("/sender/login", forceLoad: true); } bool IsEnvelopeSent(EnvelopeDto envelope) { var status = (EnvelopeStatus)envelope.Status; return status >= EnvelopeStatus.EnvelopeQueued; } (string Label, string CssClass, string DotColor) GetStatusInfo(EnvelopeStatus status) { return status switch { EnvelopeStatus.EnvelopePartlySigned => ("Teilweise unterschrieben", "partly-signed", "green"), EnvelopeStatus.EnvelopeQueued => ("In Warteschlange", "queued", "orange"), EnvelopeStatus.EnvelopeSent => ("Gesendet", "sent", "orange"), EnvelopeStatus.EnvelopeCompletelySigned => ("Vollständig unterschrieben", "completed", "green"), EnvelopeStatus.EnvelopeDeleted => ("Gelöscht", "deleted", "red"), EnvelopeStatus.EnvelopeRejected => ("Abgelehnt", "rejected", "red"), EnvelopeStatus.EnvelopeWithdrawn => ("Zurückgezogen", "withdrawn", "red"), EnvelopeStatus.EnvelopeCreated => ("Erstellt", "created", "blue"), EnvelopeStatus.EnvelopeSaved => ("Gespeichert", "saved", "blue"), _ => ("Unbekannt", "unknown", "blue") }; } void OnCustomizeElement(GridCustomizeElementEventArgs e) { // Future: Add custom row coloring based on status if needed } void OnSelectedEnvelopeChanged(object envelope) { _selectedEnvelope = envelope as EnvelopeDto; } // ── Grid Layout Persistence ─────────────────────────────────────────────── // LayoutAutoLoading fires when the grid initializes — restore saved layout. // LayoutAutoSaving fires when the user changes column order/width/sort — persist it. // UserPreferencesService.GetAsync() uses the 1h in-memory cache → 0 HTTP on restore. // UserPreferencesService.SaveGridLayoutAsync() merges the single key → 1 PUT. async Task OnActiveGridLayoutAutoLoading(GridPersistentLayoutEventArgs e) { // Use cached preferences only — avoid HTTP during grid initialization. // If cache is empty (LoadFromServerAsync failed), skip layout restore silently. var prefs = UserPreferencesService.GetCached(); if (prefs is not null && prefs.GridLayouts.TryGetValue(GridKeyActive, out var json)) e.Layout = JsonSerializer.Deserialize(json); } async Task OnActiveGridLayoutAutoSaving(GridPersistentLayoutEventArgs e) { try { var json = JsonSerializer.Serialize(e.Layout); await UserPreferencesService.SaveGridLayoutAsync(GridKeyActive, json); } catch (Exception ex) { await JSRuntime.InvokeVoidAsync("console.warn", $"[Sender] Grid layout save failed: {ex.Message}"); } } async Task OnCompletedGridLayoutAutoLoading(GridPersistentLayoutEventArgs e) { var prefs = UserPreferencesService.GetCached(); if (prefs is not null && prefs.GridLayouts.TryGetValue(GridKeyCompleted, out var json)) e.Layout = JsonSerializer.Deserialize(json); } async Task OnCompletedGridLayoutAutoSaving(GridPersistentLayoutEventArgs e) { var json = JsonSerializer.Serialize(e.Layout); await UserPreferencesService.SaveGridLayoutAsync(GridKeyCompleted, json); } }