From e89d471c6fef1404cb675d8196100d79d4e55b1e Mon Sep 17 00:00:00 2001 From: TekH Date: Mon, 5 Oct 2026 21:58:51 +0200 Subject: [PATCH] Refactor receiver input and improve UI/UX Replaced receiver list with a tag-based `DxTagBox` for better flexibility and dynamic updates. Updated receiver management logic to synchronize `_receivers` and `_receiverTags`. Enhanced accessibility with ARIA attributes for the suggestion list. Redesigned the "Add Receiver" button with custom styles, hover effects, and an inline SVG icon. Improved the suggestion list with horizontal scrolling and nowrap styling. Styled tags and added a remove button for each tag. Increased recent receiver suggestions fetch limit to 20. Added validation to ensure default envelope type selection. Refactored empty state handling and cleaned up redundant code for better maintainability. --- .../Sender/CreateEnvelopeWizardPopup.razor | 93 ++++++++++++++----- .../Services/ReceiverService.cs | 2 +- .../wwwroot/css/sender-page.css | 84 ++++++++++++++++- 3 files changed, 151 insertions(+), 28 deletions(-) diff --git a/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Components/Sender/CreateEnvelopeWizardPopup.razor b/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Components/Sender/CreateEnvelopeWizardPopup.razor index 89a0bd10..847673af 100644 --- a/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Components/Sender/CreateEnvelopeWizardPopup.razor +++ b/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Components/Sender/CreateEnvelopeWizardPopup.razor @@ -114,7 +114,7 @@ } else if (_recentReceiverSuggestions.Count > 0) { -
+
@foreach (var suggestion in _recentReceiverSuggestions) {
@if (_receivers.Count == 0) @@ -169,22 +173,47 @@ } else { -
- @foreach (var receiver in _receivers) - { -
-
-
@receiver.FullName
-
@receiver.EmailAddress
+
+ + + +
+ @if (tagContext.DataItem is CreateEnvelopeWizardReceiver receiver) + { + @receiver.FullName + @receiver.EmailAddress + } + else + { + @tagContext.DisplayText + } +
- -
- } + + +
+
+
} } @@ -289,6 +318,7 @@ private string _receiverName = string.Empty; private string _receiverEmail = string.Empty; private readonly List _receivers = []; + private IEnumerable _receiverTags = []; private readonly List _recentReceiverSuggestions = []; private bool _isLoadingRecentSuggestions; private bool _recentSuggestionsLoaded; @@ -330,6 +360,9 @@ { _isVisible = Visible; + if (_selectedEnvelopeTypeId is null && EnvelopeTypes.Count > 0) + _selectedEnvelopeTypeId = GetDefaultEnvelopeTypeId(); + if (Visible && !_previousVisible) ResetWizardState(); @@ -411,6 +444,7 @@ } _receivers.Add(new CreateEnvelopeWizardReceiver(fullName, email)); + _receiverTags = _receivers.Select(x => x.EmailAddress).ToList(); _receiverName = string.Empty; _receiverEmail = string.Empty; _validationMessage = null; @@ -423,6 +457,17 @@ return Task.CompletedTask; _receivers.Remove(receiver); + _receiverTags = _receivers.Select(x => x.EmailAddress).ToList(); + _validationMessage = null; + return Task.CompletedTask; + } + + private Task OnReceiverTagsChanged() + { + var selectedEmails = _receiverTags.ToHashSet(StringComparer.OrdinalIgnoreCase); + + _receivers.RemoveAll(x => !selectedEmails.Contains(x.EmailAddress)); + _receiverTags = _receivers.Select(x => x.EmailAddress).ToList(); _validationMessage = null; return Task.CompletedTask; } @@ -613,7 +658,7 @@ _isLoadingRecentSuggestions = true; try { - var suggestions = await ReceiverService.GetRecentReceiverSuggestionsAsync(); + var suggestions = await ReceiverService.GetRecentReceiverSuggestionsAsync(20); _recentReceiverSuggestions.Clear(); _recentReceiverSuggestions.AddRange(suggestions); _recentSuggestionsLoaded = true; @@ -644,6 +689,9 @@ { if (_currentStep == 0) { + if (_selectedEnvelopeTypeId is null && EnvelopeTypes.Count > 0) + _selectedEnvelopeTypeId = GetDefaultEnvelopeTypeId(); + if (string.IsNullOrWhiteSpace(_title)) { _validationMessage = L("SenderEditor_Validation_TitleRequired", "Bitte geben Sie einen Titel ein."); @@ -711,6 +759,7 @@ _receiverName = string.Empty; _receiverEmail = string.Empty; _receivers.Clear(); + _receiverTags = []; _recentReceiverSuggestions.Clear(); _isLoadingRecentSuggestions = false; _recentSuggestionsLoaded = false; diff --git a/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/ReceiverService.cs b/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/ReceiverService.cs index e11a058f..5ee5f20a 100644 --- a/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/ReceiverService.cs +++ b/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/ReceiverService.cs @@ -32,7 +32,7 @@ public class ReceiverService(IHttpClientFactory httpClientFactory) /// Fetches recent receiver suggestions for the authenticated sender. /// Calls GET /api/EnvelopeReceiver/recent-suggestions?take={take}. /// - public async Task> GetRecentReceiverSuggestionsAsync(int take = 5, CancellationToken cancel = default) + public async Task> GetRecentReceiverSuggestionsAsync(int take = 20, CancellationToken cancel = default) { using var http = httpClientFactory.CreateClient("EnvelopeGenerator.Server"); var url = $"/api/EnvelopeReceiver/recent-suggestions?take={take}"; diff --git a/EnvelopeGenerator.Server/EnvelopeGenerator.Server/wwwroot/css/sender-page.css b/EnvelopeGenerator.Server/EnvelopeGenerator.Server/wwwroot/css/sender-page.css index 5aac5f3f..3c78e7ee 100644 --- a/EnvelopeGenerator.Server/EnvelopeGenerator.Server/wwwroot/css/sender-page.css +++ b/EnvelopeGenerator.Server/EnvelopeGenerator.Server/wwwroot/css/sender-page.css @@ -1271,10 +1271,31 @@ justify-content: flex-end; } -.sender-create-wizard__add-receiver-btn.dxbl-btn { - min-width: 210px; +.sender-create-wizard__add-receiver-btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.4rem; + min-height: 36px; + min-width: 220px; font-weight: 700; - box-shadow: 0 8px 18px rgba(14, 116, 144, 0.22); + font-size: 0.84rem; + border: 1px solid rgba(14, 116, 144, 0.28); + border-radius: 8px; + background: rgba(236, 254, 255, 0.95); + color: #0f766e; + box-shadow: 0 6px 14px rgba(14, 116, 144, 0.15); + padding: 0.45rem 0.9rem; +} + +.sender-create-wizard__add-receiver-btn:hover:not(:disabled) { + background: rgba(207, 250, 254, 1); + color: #0f766e; +} + +.sender-create-wizard__add-receiver-btn:disabled { + opacity: 0.58; + cursor: not-allowed; } .sender-create-wizard__suggestions { @@ -1291,12 +1312,17 @@ .sender-create-wizard__suggestion-list { display: flex; - flex-wrap: wrap; + flex-wrap: nowrap; + overflow-x: auto; + overflow-y: hidden; + white-space: nowrap; gap: 0.45rem; + padding-bottom: 0.25rem; } .sender-create-wizard__suggestion-item { - display: inline-flex; + display: flex; + flex: 0 0 auto; flex-direction: column; align-items: flex-start; gap: 0.12rem; @@ -1308,6 +1334,54 @@ min-width: 0; } +.sender-create-wizard__receiver-tagbox-wrap { + display: flex; + flex-direction: column; + gap: 0.35rem; +} + +.sender-create-wizard__receiver-tagbox { + width: 100%; +} + +.sender-create-wizard__receiver-tag { + display: inline-flex; + align-items: center; + gap: 0.4rem; + padding: 0.18rem 0.45rem; + border-radius: 999px; + background: rgba(236, 254, 255, 0.95); + border: 1px solid rgba(14, 116, 144, 0.25); +} + +.sender-create-wizard__receiver-tag-name { + font-weight: 700; +} + +.sender-create-wizard__receiver-tag-email { + color: #64748b; + font-size: 0.78rem; +} + +.sender-create-wizard__receiver-tag-remove { + border: 0; + background: transparent; + color: #0f766e; + font-size: 0.76rem; + font-weight: 700; + line-height: 1; + padding: 0; + width: 1rem; + height: 1rem; + cursor: pointer; +} + +.sender-create-wizard__tagbox-empty { + height: 0; + padding: 0; + margin: 0; +} + .sender-create-wizard__suggestion-item:disabled { opacity: 0.58; }