From 74e55e92065a8be2ccd8c5858d94c9cb076132cc Mon Sep 17 00:00:00 2001 From: TekH Date: Tue, 27 Oct 2026 21:56:39 +0100 Subject: [PATCH] Refactor and optimize envelope handling Refactored `ReadDocumentDetailsQueryHandler` to remove AutoMapper, improving performance by manually selecting necessary data. Simplified `SenderEditorPage.razor` by removing bootstrap logic and updating the UI for a more compact design. Streamlined envelope creation in `SenderPage.razor` by eliminating the bootstrap token. Updated styles in `envelope-viewer.css` for better alignment and readability. --- .../Queries/ReadDocumentDetailsQuery.cs | 72 ++++- .../Pages/SenderEditorPage.razor | 276 +++--------------- .../Pages/SenderPage.razor | 61 ++-- .../wwwroot/css/envelope-viewer.css | 62 +++- 4 files changed, 198 insertions(+), 273 deletions(-) diff --git a/EnvelopeGenerator.Application/Documents/Queries/ReadDocumentDetailsQuery.cs b/EnvelopeGenerator.Application/Documents/Queries/ReadDocumentDetailsQuery.cs index cc00019c..5c5ac1f8 100644 --- a/EnvelopeGenerator.Application/Documents/Queries/ReadDocumentDetailsQuery.cs +++ b/EnvelopeGenerator.Application/Documents/Queries/ReadDocumentDetailsQuery.cs @@ -1,5 +1,3 @@ -using AutoMapper; -using AutoMapper.QueryableExtensions; using DigitalData.Core.Abstraction.Application.Repository; using EnvelopeGenerator.Application.Common.Dto; using EnvelopeGenerator.Domain.Entities; @@ -21,12 +19,10 @@ public record ReadDocumentDetailsQuery(int EnvelopeId, int? UserId = null) : IRe public class ReadDocumentDetailsQueryHandler : IRequestHandler { private readonly IRepository _repo; - private readonly IMapper _mapper; - public ReadDocumentDetailsQueryHandler(IRepository repo, IMapper mapper) + public ReadDocumentDetailsQueryHandler(IRepository repo) { _repo = repo; - _mapper = mapper; } public async Task Handle(ReadDocumentDetailsQuery query, CancellationToken cancel) @@ -36,9 +32,71 @@ public class ReadDocumentDetailsQueryHandler : IRequestHandler doc.Envelope.UserId == userId); - return await docQuery + // shortcut: manual select avoids AutoMapper ProjectTo pulling BYTE_DATA and the full navigation + // graph (Envelope → History → User etc.) which caused an extremely slow query (~87s). + // Annotations navigation is IEnumerable on the entity (not ICollection), so EF Core + // cannot translate it in a Select projection — we skip it entirely here; sender editor only + // needs element coordinates, not annotation payloads. + var raw = await docQuery .Where(d => d.EnvelopeId == query.EnvelopeId) - .ProjectTo(_mapper.ConfigurationProvider) + .Select(d => new + { + d.Id, + d.EnvelopeId, + d.AddedWhen, + Elements = d.Elements == null ? null : d.Elements.Select(e => new + { + e.Id, + e.DocumentId, + e.ReceiverId, + e.ElementType, + e.X, + e.Y, + e.Width, + e.Height, + e.Page, + e.Required, + e.Tooltip, + e.ReadOnly, + e.AnnotationIndex, + e.AddedWhen, + e.ChangedWhen, + e.FullName, + e.Position, + e.Place, + }).ToList() + }) .FirstOrDefaultAsync(cancel); + + if (raw is null) + return null; + + return new DocumentDetailsDto + { + Id = raw.Id, + EnvelopeId = raw.EnvelopeId, + AddedWhen = raw.AddedWhen, + Elements = raw.Elements?.Select(e => new DocReceiverElementDto + { + Id = e.Id, + DocumentId = e.DocumentId, + ReceiverId = e.ReceiverId, + ElementType = e.ElementType, + X = e.X, + Y = e.Y, + Width = e.Width, + Height = e.Height, + Page = e.Page, + Required = e.Required, + Tooltip = e.Tooltip, + ReadOnly = e.ReadOnly, + AnnotationIndex = e.AnnotationIndex, + AddedWhen = e.AddedWhen, + ChangedWhen = e.ChangedWhen, + FullName = e.FullName, + Position = e.Position, + Place = e.Place, + }) + }; } } diff --git a/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Pages/SenderEditorPage.razor b/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Pages/SenderEditorPage.razor index 5761256d..41b48566 100644 --- a/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Pages/SenderEditorPage.razor +++ b/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Pages/SenderEditorPage.razor @@ -10,7 +10,6 @@ @using EnvelopeGenerator.Application.Common.Configurations @using EnvelopeGenerator.Server.Client.Components.PdfEditor @using EnvelopeGenerator.Server.Client.Services -@using Microsoft.AspNetCore.WebUtilities @using Microsoft.AspNetCore.Components.Forms @using Microsoft.Extensions.Localization @using Microsoft.Extensions.Options @@ -24,7 +23,6 @@ @inject EnvelopeReceiverService EnvelopeReceiverService @inject EnvelopeService EnvelopeService @inject EnvelopeTypeService EnvelopeTypeService -@inject CreateEnvelopeBootstrapStorageService CreateEnvelopeBootstrapStorageService @inject AuthService AuthService @inject IStringLocalizer UiLocalizer @inject IStringLocalizer ModelLocalizer @@ -35,11 +33,6 @@ -
@* ── Action Bar ── *@ @@ -199,20 +192,43 @@
-
-
- @L("Receiver", "Empfänger") - @_receivers.Count -
- - + + @foreach (var receiver in _receivers) + { +
+
+ +
+ + + + +
+ }
@if (_receivers.Count == 0) @@ -221,43 +237,6 @@ @L("SenderEditor_NoReceiversAdded", "Noch keine Empfänger hinzugefügt.")
} - else - { -
- @foreach (var receiver in _receivers) - { -
-
-
@receiver.FullName
- - @if (!string.IsNullOrWhiteSpace(receiver.PhoneNumber)) - { -
@receiver.PhoneNumber
- } -
- - - - -
- } -
- } @@ -442,7 +421,6 @@ } -
SignatureFieldOptions.CurrentValue.Width; double SigHeightInches => SignatureFieldOptions.CurrentValue.Height; @@ -629,10 +604,7 @@ int _pageCount; List _pagePreviews = []; int? _loadedEnvelopeId; - int? _editingEnvelopeId; bool _isLoadingEnvelopeForEdit; - bool _bootstrapApplied; - string? _pendingBootstrapToken; List _signatureFields = []; ReceiverDraft? _pendingReceiverForPlacement; // Set when user clicks "Signatur hinzufügen" @@ -653,7 +625,6 @@ string _saveSuccessTitle = "Vorgang erfolgreich abgeschlossen."; string _saveSuccessBody = string.Empty; bool _saveNavigateToDashboard = false; - bool _saveDraftAfterBootstrap; // ── Envelope metadata ── string _envelopeTitle = string.Empty; @@ -687,9 +658,6 @@ static readonly System.ComponentModel.DataAnnotations.EmailAddressAttribute ReceiverEmailValidator = new(); - bool IsEditMode => EnvelopeId.HasValue && EnvelopeId.Value > 0; - bool IsBootstrapLoading => !IsEditMode && !_bootstrapApplied; - // ── Lifecycle ── protected override async Task OnInitializedAsync() @@ -704,12 +672,6 @@ try { _envelopeTypes = (await EnvelopeTypeService.GetAsync()).ToList(); - - if (!IsEditMode && _selectedEnvelopeTypeId is null && _envelopeTypes.Count > 0) - { - _selectedEnvelopeTypeId = _envelopeTypes[0].Id; - ApplyEnvelopeTypeDefaults(_envelopeTypes[0]); - } } catch (Exception ex) { @@ -720,96 +682,14 @@ protected override async Task OnParametersSetAsync() { - if (EnvelopeId.HasValue && EnvelopeId.Value > 0) - _editingEnvelopeId = EnvelopeId.Value; - - if (IsEditMode && _loadedEnvelopeId != EnvelopeId) - { - await LoadEnvelopeForEditAsync(EnvelopeId!.Value); - return; - } - - if (IsEditMode || _bootstrapApplied) - return; - - if (string.IsNullOrWhiteSpace(BootstrapToken)) + if (!EnvelopeId.HasValue || EnvelopeId.Value <= 0) { NavigationManager.NavigateTo("/sender", replace: true); return; } - _pendingBootstrapToken = BootstrapToken; - } - - async Task TryApplyBootstrapAsync(string token) - { - var payload = await CreateEnvelopeBootstrapStorageService.ConsumeAsync(token); - _bootstrapApplied = true; - - if (payload is null) - { - NavigationManager.NavigateTo("/sender", replace: true); - return; - } - - _envelopeTitle = payload.Title ?? string.Empty; - _envelopeMessage = payload.Message ?? string.Empty; - _selectedEnvelopeTypeId = payload.EnvelopeTypeId; - _language = string.IsNullOrWhiteSpace(payload.Language) ? "de-DE" : payload.Language; - _useAccessCode = payload.UseAccessCode; - _tfaEnabled = payload.TFAEnabled; - _certificationType = payload.CertificationType; - _finalEmailToCreator = payload.FinalEmailToCreator; - _finalEmailToReceivers = payload.FinalEmailToReceivers; - _sendReminderEmails = payload.SendReminderEmails; - _firstReminderDays = payload.FirstReminderDays; - _reminderIntervalDays = payload.ReminderIntervalDays; - _expiresWhenDays = payload.ExpiresWhenDays; - _expiresWarningWhenDays = payload.ExpiresWarningWhenDays; - NormalizeSettings(); - - _receivers = payload.Receivers - .Select((receiver, index) => new ReceiverDraft( - Guid.NewGuid(), - null, - receiver.FullName, - receiver.EmailAddress, - string.Empty, - ReceiverPalette[index % ReceiverPalette.Length])) - .ToList(); - - _originalPdfBytes = payload.DocumentBytes; - _fileName = string.IsNullOrWhiteSpace(payload.DocumentFileName) ? "document.pdf" : payload.DocumentFileName; - _pdfLoaded = _originalPdfBytes is { Length: > 0 }; - - _signatureFields.Clear(); - _pendingReceiverForPlacement = null; - - _saveDraftAfterBootstrap = true; - - if (_pdfLoaded && _originalPdfBytes is not null) - { - try - { - _pagePreviews = (await PdfRenderService.RenderPagesAsync(_originalPdfBytes)) - .OrderBy(p => p.PageNumber) - .ToList(); - } - catch (Exception ex) - { - Logger.LogWarning(ex, "Failed to render bootstrap PDF previews."); - _pagePreviews = []; - } - - _pageCount = _pagePreviews.Count > 0 - ? _pagePreviews.Count - : EstimatePdfPageCount(_originalPdfBytes) ?? 1; - } - else - { - _pagePreviews = []; - _pageCount = 0; - } + if (_loadedEnvelopeId != EnvelopeId && !_isLoadingEnvelopeForEdit) + await LoadEnvelopeForEditAsync(EnvelopeId.Value); } protected override async Task OnAfterRenderAsync(bool firstRender) @@ -819,26 +699,6 @@ _isPageReady = true; await InvokeAsync(StateHasChanged); } - - if (!OperatingSystem.IsBrowser()) - return; - - if (!IsEditMode && !_bootstrapApplied && !string.IsNullOrWhiteSpace(_pendingBootstrapToken)) - { - var token = _pendingBootstrapToken; - _pendingBootstrapToken = null; - await TryApplyBootstrapAsync(token); - await InvokeAsync(StateHasChanged); - return; - } - - if (_saveDraftAfterBootstrap && !_isSaving) - { - _saveDraftAfterBootstrap = false; - await SaveDraftAndStayOnEditorAsync(); - return; - } - } async Task LoadEnvelopeForEditAsync(int envelopeId) @@ -960,7 +820,6 @@ _fileName = $"Envelope-{envelopeId}.pdf"; _pendingReceiverForPlacement = null; _loadedEnvelopeId = envelopeId; - _editingEnvelopeId = envelopeId; } catch (Exception ex) { @@ -1263,9 +1122,8 @@ try { - var previousEnvelopeId = EnvelopeId; var command = BuildCreateEnvelopeCommand(send: false); - Logger.LogInformation("[SenderEditor] Saving draft. EnvelopeId={EnvelopeId} IsEditMode={IsEditMode}", command.EnvelopeId, IsEditMode); + Logger.LogInformation("[SenderEditor] Saving draft. EnvelopeId={EnvelopeId}", command.EnvelopeId); var result = await EnvelopeService.UpsertAsync(command); if (result is null) @@ -1273,16 +1131,9 @@ EnvelopeId = result.Id; _loadedEnvelopeId = result.Id; - _editingEnvelopeId = result.Id; if (navigateToDashboard) - { NavigationManager.NavigateTo("/sender", forceLoad: false); - } - else if (previousEnvelopeId != result.Id) - { - NavigationManager.NavigateTo($"/sender/editor?envelopeId={result.Id}", forceLoad: false); - } return result.Id; } @@ -1300,12 +1151,6 @@ } } - async Task SaveDraftAndStayOnEditorAsync() - { - Logger.LogInformation("[SenderEditor] Trigger bootstrap draft save."); - await SaveDraftCoreAsync(navigateToDashboard: false); - } - // ── Send envelope ── async Task SendAsync() { @@ -1360,21 +1205,10 @@ try { - var envelopeId = _editingEnvelopeId - ?? EnvelopeId - ?? _loadedEnvelopeId - ?? GetEnvelopeIdFromCurrentUri(); + var envelopeId = EnvelopeId ?? _loadedEnvelopeId; if (!envelopeId.HasValue || envelopeId.Value <= 0) - { - var saveResult = await EnvelopeService.UpsertAsync(BuildCreateEnvelopeCommand(send: false)) - ?? throw new InvalidOperationException("Draft save did not return a result."); - - envelopeId = saveResult.Id; - EnvelopeId = saveResult.Id; - _loadedEnvelopeId = saveResult.Id; - _editingEnvelopeId = saveResult.Id; - } + throw new InvalidOperationException("Envelope id is missing for send."); var sendResult = await EnvelopeService.UpsertAsync(BuildCreateEnvelopeCommand(send: true) with { EnvelopeId = envelopeId }) ?? throw new InvalidOperationException("Send did not return a result."); @@ -1405,13 +1239,10 @@ { NormalizeSettings(); - var envelopeId = _editingEnvelopeId - ?? EnvelopeId - ?? _loadedEnvelopeId - ?? GetEnvelopeIdFromCurrentUri(); + var envelopeId = EnvelopeId ?? _loadedEnvelopeId; - if (IsEditMode && envelopeId is null) - throw new InvalidOperationException("Edit mode envelope id is missing."); + if (envelopeId is null) + throw new InvalidOperationException("Envelope id is missing."); var docBase64 = _originalPdfBytes is { Length: > 0 } ? Convert.ToBase64String(_originalPdfBytes) @@ -1499,28 +1330,6 @@ return true; } - int? GetEnvelopeIdFromCurrentUri() - { - try - { - var uri = NavigationManager.ToAbsoluteUri(NavigationManager.Uri); - if (string.IsNullOrWhiteSpace(uri.Query)) - return null; - - var query = QueryHelpers.ParseQuery(uri.Query); - if (!query.TryGetValue("envelopeId", out var raw)) - return null; - - return int.TryParse(raw.ToString(), out var id) && id > 0 - ? id - : null; - } - catch - { - return null; - } - } - // ── Receiver popup ── void OpenAddReceiverPopup() { @@ -1693,10 +1502,7 @@ if (_isSaving) return; - var envelopeId = _editingEnvelopeId - ?? EnvelopeId - ?? _loadedEnvelopeId - ?? GetEnvelopeIdFromCurrentUri(); + var envelopeId = EnvelopeId ?? _loadedEnvelopeId; if (envelopeId is int persistedEnvelopeId && persistedEnvelopeId > 0 && receiver.ReceiverId is int persistedReceiverId && persistedReceiverId > 0) { diff --git a/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Pages/SenderPage.razor b/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Pages/SenderPage.razor index 7882b482..ca52de05 100644 --- a/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Pages/SenderPage.razor +++ b/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Pages/SenderPage.razor @@ -7,6 +7,7 @@ @using EnvelopeGenerator.Server.Client.Services @using EnvelopeGenerator.Application.Common.Dto @using EnvelopeGenerator.Application.Common.Dto.History +@using EnvelopeGenerator.Application.Envelopes.Commands @using EnvelopeGenerator.Application.Resources @using EnvelopeGenerator.Domain.Resources @using EnvelopeGenerator.Server.Client.Components.Sender @@ -21,7 +22,6 @@ @inject EnvelopeGenerator.Server.Client.Services.CustomImagesService CustomImagesService @inject EnvelopeGenerator.Server.Client.Services.PdfSignatureService PdfSignatureService @inject EnvelopeGenerator.Server.Client.Services.EnvelopeReceiverService EnvelopeReceiverService -@inject EnvelopeGenerator.Server.Client.Services.CreateEnvelopeBootstrapStorageService CreateEnvelopeBootstrapStorageService @inject ILogger Logger @inject NavigationManager Navigation @inject IJSRuntime JSRuntime @@ -744,29 +744,44 @@ async Task StartEditorFromWizardAsync(CreateEnvelopeWizardResult result) { - var payload = new CreateEnvelopeBootstrapPayload( - Title: result.Title, - Message: result.Message, - EnvelopeTypeId: result.EnvelopeTypeId, - Language: result.Language, - UseAccessCode: result.UseAccessCode, - TFAEnabled: result.TFAEnabled, - CertificationType: result.CertificationType, - FinalEmailToCreator: result.FinalEmailToCreator, - FinalEmailToReceivers: result.FinalEmailToReceivers, - SendReminderEmails: result.SendReminderEmails, - FirstReminderDays: result.FirstReminderDays, - ReminderIntervalDays: result.ReminderIntervalDays, - ExpiresWhenDays: result.ExpiresWhenDays, - ExpiresWarningWhenDays: result.ExpiresWarningWhenDays, - Receivers: result.Receivers - .Select(x => new CreateEnvelopeBootstrapReceiver(x.FullName, x.EmailAddress)) - .ToList(), - DocumentBytes: result.DocumentBytes, - DocumentFileName: result.DocumentFileName); + var docBase64 = result.DocumentBytes is { Length: > 0 } + ? Convert.ToBase64String(result.DocumentBytes) + : null; - var token = await CreateEnvelopeBootstrapStorageService.StoreAsync(payload); - Navigation.NavigateTo($"/sender/editor?bootstrapToken={token}"); + var command = new CreateEnvelopeCommand + { + EnvelopeTypeId = result.EnvelopeTypeId, + Title = result.Title, + Message = result.Message, + Language = string.IsNullOrWhiteSpace(result.Language) ? "de-DE" : result.Language, + UseAccessCode = result.UseAccessCode, + TFAEnabled = result.TFAEnabled, + CertificationType = result.CertificationType, + FinalEmailToCreator = result.FinalEmailToCreator, + FinalEmailToReceivers = result.FinalEmailToReceivers, + SendReminderEmails = result.SendReminderEmails, + FirstReminderDays = result.FirstReminderDays, + ReminderIntervalDays = result.ReminderIntervalDays, + ExpiresWhenDays = result.ExpiresWhenDays, + ExpiresWarningWhenDays = result.ExpiresWarningWhenDays, + Send = false, + Document = string.IsNullOrWhiteSpace(docBase64) ? null : new CreateEnvelopeDocumentDto(docBase64), + Receivers = result.Receivers + .Select(x => new CreateEnvelopeReceiverDto(x.EmailAddress, x.FullName, null, [])) + .ToList() + }; + + try + { + var envelope = await EnvelopeService.UpsertAsync(command) + ?? throw new InvalidOperationException("Envelope creation returned no result."); + Navigation.NavigateTo($"/sender/editor?envelopeId={envelope.Id}"); + } + catch (Exception ex) + { + Logger.LogError(ex, "Failed to create envelope from wizard."); + _errorMessage = ex.Message; + } } void EditEnvelope() diff --git a/EnvelopeGenerator.Server/EnvelopeGenerator.Server/wwwroot/css/envelope-viewer.css b/EnvelopeGenerator.Server/EnvelopeGenerator.Server/wwwroot/css/envelope-viewer.css index 3804d914..941281a5 100644 --- a/EnvelopeGenerator.Server/EnvelopeGenerator.Server/wwwroot/css/envelope-viewer.css +++ b/EnvelopeGenerator.Server/EnvelopeGenerator.Server/wwwroot/css/envelope-viewer.css @@ -989,7 +989,7 @@ body.resizing { } .sender-toolbar-action-btn--compact { - padding: 0.45rem 0.7rem; + padding: 0.28rem 0.42rem; } .sender-receivers-panel { @@ -1208,23 +1208,68 @@ body.resizing { .sender-receivers-list { display: flex; flex-wrap: wrap; - gap: 0.625rem; + gap: 0.4rem; + align-items: center; +} + +.sender-receivers-list__add-btn { + position: relative; + display: inline-flex; + align-items: center; + gap: 0.38rem; + min-height: 30px; + padding: 0.35rem 0.6rem; + margin-right: 0.28rem; +} + +.sender-receivers-list__add-text { + font-size: 0.75rem; + font-weight: 600; + line-height: 1; +} + +.sender-receivers-list__count { + position: absolute; + top: -0.45rem; + right: -0.45rem; + min-width: 1.15rem; + height: 1.15rem; + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 0.65rem; + font-weight: 700; + padding: 0 0.3rem; } .sender-receiver-chip { display: inline-flex; align-items: center; - gap: 0.75rem; - min-width: 220px; + gap: 0.35rem; + min-width: 190px; max-width: 100%; - padding: 0.625rem 0.75rem; - border-radius: 12px; + padding: 0.28rem 0.4rem; + border-radius: 9px; background: rgba(255, 255, 255, 0.88); border: 1px solid rgba(126, 34, 206, 0.12); border-left: 3px solid var(--receiver-color, #7c3aed); box-shadow: 0 6px 16px rgba(15, 23, 42, 0.06); } +.sender-receiver-chip__action { + min-width: auto; + min-height: 26px; + width: 26px; + height: 26px; + padding: 0; + border-radius: 6px; +} + +.sender-receiver-chip__action svg { + width: 11px; + height: 11px; +} + .sender-receiver-chip__place-btn--idle { color: var(--receiver-color, #7c3aed); border-color: var(--receiver-color, #7c3aed); @@ -1516,8 +1561,9 @@ body.resizing { } .sender-receiver-chip__email { - margin-top: 0.15rem; - font-size: 0.72rem; + margin-top: 0; + font-size: 0.7rem; + line-height: 1.1; color: #64748b; white-space: nowrap; overflow: hidden;