Introduced `ReceiverSuggestionFilterHandler` to refine the filtering of recent receiver suggestions based on name and email. Added `SuggestionLimit` constant to replace hardcoded limits and ensure consistency. Updated UI to use the new `FilteredRecentSuggestions` property, which leverages the `BuildFilteredRecentSuggestions` method for grouping, ordering, and limiting suggestions. Adjusted `OnDocumentSelectedAsync` to use the new constant. Added necessary `@using` directive for the filter handler.
818 lines
30 KiB
Plaintext
818 lines
30 KiB
Plaintext
@using EnvelopeGenerator.Application.Common.Dto
|
|
@using EnvelopeGenerator.Application.Common.Dto.Receiver
|
|
@using EnvelopeGenerator.Application.Resources
|
|
@using EnvelopeGenerator.Domain.Resources
|
|
@using EnvelopeGenerator.Server.Client.Components.Sender.ReceiverSuggestionFilter
|
|
@using EnvelopeGenerator.Server.Client.Services
|
|
@using Microsoft.AspNetCore.Components.Forms
|
|
@using Microsoft.Extensions.Localization
|
|
@inject IStringLocalizer<Model> ModelLocalizer
|
|
@inject IStringLocalizer<Resource> UiLocalizer
|
|
@inject ReceiverService ReceiverService
|
|
|
|
<DxPopup Visible="@_isVisible"
|
|
VisibleChanged="OnPopupVisibleChangedAsync"
|
|
HeaderText="@L("SenderWizard_Header", "Neuen Umschlag erstellen")"
|
|
Width="860px"
|
|
MaxWidth="96vw"
|
|
ShowFooter="true"
|
|
CloseOnOutsideClick="false"
|
|
ShowCloseButton="true"
|
|
CloseOnEscape="true"
|
|
ShowHeader="true"
|
|
CssClass="sender-create-wizard-popup">
|
|
<BodyContentTemplate>
|
|
<div class="sender-create-wizard"
|
|
tabindex="0"
|
|
@onkeydown="OnWizardKeyDownAsync">
|
|
<div class="sender-create-wizard__progress">
|
|
<div class="sender-create-wizard__progress-track"></div>
|
|
@for (var step = 0; step < TotalSteps; step++)
|
|
{
|
|
var isActive = step == _currentStep;
|
|
var isDone = step < _currentStep;
|
|
<div class="sender-create-wizard__step @(isActive ? "sender-create-wizard__step--active" : string.Empty) @(isDone ? "sender-create-wizard__step--done" : string.Empty)">
|
|
<span class="sender-create-wizard__step-index">@(step + 1)</span>
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
<div class="sender-create-wizard__meta">
|
|
<div class="sender-create-wizard__title">@CurrentStepTitle</div>
|
|
<div class="sender-create-wizard__description">@CurrentStepDescription</div>
|
|
</div>
|
|
|
|
@if (!string.IsNullOrWhiteSpace(_validationMessage))
|
|
{
|
|
<div class="sender-create-wizard__validation">@_validationMessage</div>
|
|
}
|
|
|
|
@if (_currentStep == 0)
|
|
{
|
|
<div class="sender-create-wizard__form-grid">
|
|
<div class="sender-create-wizard__field">
|
|
<label class="sender-create-wizard__label">
|
|
@L("Title", "Titel") <span class="sender-create-wizard__required">*</span>
|
|
</label>
|
|
<DxTextBox Text="@_title"
|
|
TextChanged="OnTitleChanged"
|
|
BindValueMode="BindValueMode.OnInput"
|
|
NullText='@L("SenderEditor_Title_Placeholder", "z.B. Arbeitsvertrag 2025")'
|
|
CssClass="w-100" />
|
|
</div>
|
|
|
|
<div class="sender-create-wizard__field">
|
|
<label class="sender-create-wizard__label">
|
|
@L("EnvelopeType", "Umschlagtyp") <span class="sender-create-wizard__required">*</span>
|
|
</label>
|
|
<select value="@(_selectedEnvelopeTypeId?.ToString() ?? string.Empty)"
|
|
@onchange="OnEnvelopeTypeChanged"
|
|
class="sender-create-wizard__select">
|
|
@foreach (var type in EnvelopeTypes)
|
|
{
|
|
<option value="@type.Id">@type.Title</option>
|
|
}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
}
|
|
else if (_currentStep == 1)
|
|
{
|
|
<div class="sender-create-wizard__form-grid">
|
|
<div class="sender-create-wizard__field sender-create-wizard__field--full">
|
|
<label class="sender-create-wizard__label">
|
|
@L("Message", "Nachricht") <span class="sender-create-wizard__optional">(@L("Optional", "optional"))</span>
|
|
</label>
|
|
<DxMemo Text="@_message"
|
|
TextChanged="OnMessageChanged"
|
|
Rows="5"
|
|
MaxLength="500"
|
|
NullText='@L("SenderEditor_Message_Placeholder", "Bitte unterzeichnen Sie das beigefügte Dokument.")'
|
|
CssClass="w-100" />
|
|
</div>
|
|
|
|
<div class="sender-create-wizard__field">
|
|
<label class="sender-create-wizard__label">
|
|
@L("Language", "Sprache")
|
|
</label>
|
|
<select @bind="_language" class="sender-create-wizard__select">
|
|
<option value="de-DE">Deutsch (de-DE)</option>
|
|
<option value="en-US">English (en-US)</option>
|
|
<option value="de">Deutsch (de)</option>
|
|
<option value="en">English (en)</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
}
|
|
else if (_currentStep == 2)
|
|
{
|
|
<div class="sender-create-wizard__suggestions">
|
|
<div class="sender-create-wizard__suggestions-title">@L("SenderWizard_RecentReceivers", "Zuletzt verwendete Empfänger")</div>
|
|
|
|
@if (_isLoadingRecentSuggestions)
|
|
{
|
|
<div class="sender-create-wizard__empty-list">@L("Loading", "Lädt...")</div>
|
|
}
|
|
else if (_recentReceiverSuggestions.Count > 0)
|
|
{
|
|
<div class="sender-create-wizard__suggestion-list" role="listbox" aria-label="Recent receivers">
|
|
@foreach (var suggestion in FilteredRecentSuggestions)
|
|
{
|
|
<button type="button"
|
|
class="sender-create-wizard__suggestion-item"
|
|
@onclick="() => ApplyRecentSuggestion(suggestion)"
|
|
disabled="@IsActionLocked">
|
|
<span class="sender-create-wizard__suggestion-name">@suggestion.FullName</span>
|
|
<span class="sender-create-wizard__suggestion-email">@suggestion.EmailAddress</span>
|
|
</button>
|
|
}
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
<div class="sender-create-wizard__form-grid">
|
|
<div class="sender-create-wizard__field">
|
|
<label class="sender-create-wizard__label">
|
|
@L("FullName", "Vor- und Nachname") <span class="sender-create-wizard__required">*</span>
|
|
</label>
|
|
<DxTextBox Text="@_receiverName"
|
|
TextChanged="OnReceiverNameChanged"
|
|
BindValueMode="BindValueMode.OnInput"
|
|
NullText="Max Mustermann"
|
|
CssClass="w-100" />
|
|
</div>
|
|
|
|
<div class="sender-create-wizard__field">
|
|
<label class="sender-create-wizard__label">
|
|
@L("EmailAddress", "E-Mail-Adresse") <span class="sender-create-wizard__required">*</span>
|
|
</label>
|
|
<DxTextBox Text="@_receiverEmail"
|
|
TextChanged="OnReceiverEmailChanged"
|
|
BindValueMode="BindValueMode.OnInput"
|
|
NullText="name@beispiel.de"
|
|
CssClass="w-100" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="sender-create-wizard__receiver-actions">
|
|
<button type="button"
|
|
class="sender-create-wizard__add-receiver-btn"
|
|
@onclick="AddReceiver"
|
|
disabled="@IsActionLocked">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true">
|
|
<path d="M8 4a.5.5 0 0 1 .5.5V7.5H11.5a.5.5 0 0 1 0 1H8.5v3a.5.5 0 0 1-1 0v-3H4.5a.5.5 0 0 1 0-1H7.5v-3A.5.5 0 0 1 8 4z" />
|
|
</svg>
|
|
<span>@L("AddReceiver", "Empfänger hinzufügen")</span>
|
|
</button>
|
|
</div>
|
|
|
|
@if (_receivers.Count == 0)
|
|
{
|
|
<div class="sender-create-wizard__empty-list">
|
|
@L("SenderEditor_NoReceiversAdded", "Noch keine Empfänger hinzugefügt.")
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<div class="sender-create-wizard__receiver-tagbox-wrap">
|
|
<label class="sender-create-wizard__label" for="wizardReceiversTagBox">
|
|
@L("Receiver", "Empfänger")
|
|
</label>
|
|
<DxTagBox Data="@_receivers"
|
|
TData="CreateEnvelopeWizardReceiver"
|
|
TValue="CreateEnvelopeWizardReceiver"
|
|
AllowCustomTags="true"
|
|
@bind-Tags="@_receiverTags"
|
|
@bind-Tags:after="OnReceiverTagsChanged"
|
|
HideSelectedItems="false"
|
|
TextFieldName="@nameof(CreateEnvelopeWizardReceiver.EmailAddress)"
|
|
ListRenderMode="ListRenderMode.Virtual"
|
|
SearchMode="ListSearchMode.AutoSearch"
|
|
SearchFilterCondition="ListSearchFilterCondition.Contains"
|
|
SearchTextParseMode="ListSearchTextParseMode.GroupWordsByAnd"
|
|
CssClass="sender-create-wizard__receiver-tagbox"
|
|
InputId="wizardReceiversTagBox"
|
|
Enabled="@(!IsActionLocked)">
|
|
<TagDisplayTemplate Context="tagContext">
|
|
<div class="sender-create-wizard__receiver-tag">
|
|
@if (tagContext.DataItem is CreateEnvelopeWizardReceiver receiver)
|
|
{
|
|
<span class="sender-create-wizard__receiver-tag-name">@receiver.FullName</span>
|
|
<span class="sender-create-wizard__receiver-tag-email">@receiver.EmailAddress</span>
|
|
}
|
|
else
|
|
{
|
|
<span class="sender-create-wizard__receiver-tag-email">@tagContext.DisplayText</span>
|
|
}
|
|
<button type="button"
|
|
class="sender-create-wizard__receiver-tag-remove"
|
|
@onclick="tagContext.RemoveTagAction"
|
|
aria-label="Remove receiver"
|
|
disabled="@IsActionLocked">x</button>
|
|
</div>
|
|
</TagDisplayTemplate>
|
|
<EmptyDataAreaTemplate Context="emptyContext">
|
|
<div class="sender-create-wizard__tagbox-empty"></div>
|
|
</EmptyDataAreaTemplate>
|
|
</DxTagBox>
|
|
</div>
|
|
}
|
|
}
|
|
else
|
|
{
|
|
<div class="sender-create-wizard__upload-stage">
|
|
<label class="sender-create-wizard__upload-btn @(IsActionLocked ? "sender-create-wizard__upload-btn--disabled" : string.Empty)">
|
|
@if (_documentBytes is null)
|
|
{
|
|
<span>@L("UploadPdf", "PDF hochladen")</span>
|
|
}
|
|
else
|
|
{
|
|
<span>@L("SenderWizard_ReplacePdf", "PDF ersetzen")</span>
|
|
}
|
|
<InputFile OnChange="OnDocumentSelectedAsync"
|
|
accept=".pdf"
|
|
disabled="@IsActionLocked"
|
|
class="sender-create-wizard__hidden-input" />
|
|
</label>
|
|
|
|
@if (_documentBytes is not null)
|
|
{
|
|
<div class="sender-create-wizard__document-meta">
|
|
<div class="sender-create-wizard__document-name">@_documentFileName</div>
|
|
<div class="sender-create-wizard__document-size">@_documentSizeText</div>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<div class="sender-create-wizard__empty-list">
|
|
@L("SenderWizard_DocumentRequiredHint", "Bitte laden Sie ein PDF-Dokument hoch, um fortzufahren.")
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
</div>
|
|
</BodyContentTemplate>
|
|
<FooterContentTemplate>
|
|
<div class="sender-create-wizard__footer">
|
|
<DxButton Text="@L("Cancel", "Abbrechen")"
|
|
RenderStyle="ButtonRenderStyle.Secondary"
|
|
Click="CloseAsync"
|
|
Enabled="@(!IsActionLocked)" />
|
|
|
|
@if (CanSkipCurrentStep)
|
|
{
|
|
<DxButton Text="@L("Skip", "Überspringen")"
|
|
RenderStyle="ButtonRenderStyle.Secondary"
|
|
Click="SkipOptionalStep"
|
|
Enabled="@(!IsActionLocked)" />
|
|
}
|
|
|
|
<DxButton Text="@L("Back", "Zurück")"
|
|
RenderStyle="ButtonRenderStyle.Secondary"
|
|
Click="MoveBack"
|
|
Enabled="@(!IsFirstStep && !IsActionLocked)" />
|
|
|
|
@if (!IsLastStep)
|
|
{
|
|
<DxButton Text="@L("Next", "Weiter")"
|
|
RenderStyle="ButtonRenderStyle.Primary"
|
|
Click="MoveNextAsync"
|
|
Enabled="@(!IsActionLocked)" />
|
|
}
|
|
else
|
|
{
|
|
<DxButton Text="@L("Start", "Editor öffnen")"
|
|
RenderStyle="ButtonRenderStyle.Primary"
|
|
Click="FinishAsync"
|
|
Enabled="@(!IsActionLocked)" />
|
|
}
|
|
</div>
|
|
</FooterContentTemplate>
|
|
</DxPopup>
|
|
|
|
@code {
|
|
[Parameter]
|
|
public bool Visible { get; set; }
|
|
|
|
[Parameter]
|
|
public EventCallback<bool> VisibleChanged { get; set; }
|
|
|
|
[Parameter]
|
|
public EventCallback<CreateEnvelopeWizardResult> StartEditorRequested { get; set; }
|
|
|
|
[Parameter]
|
|
public IReadOnlyList<EnvelopeTypeDto> EnvelopeTypes { get; set; } = [];
|
|
|
|
private const int TotalSteps = 4;
|
|
private static readonly System.ComponentModel.DataAnnotations.EmailAddressAttribute EmailValidator = new();
|
|
|
|
private bool _isVisible;
|
|
private bool _previousVisible;
|
|
private int _currentStep;
|
|
|
|
private string _title = string.Empty;
|
|
private int? _selectedEnvelopeTypeId;
|
|
private string _message = string.Empty;
|
|
private string _language = "de-DE";
|
|
|
|
private string _receiverName = string.Empty;
|
|
private string _receiverEmail = string.Empty;
|
|
private readonly List<CreateEnvelopeWizardReceiver> _receivers = [];
|
|
private IEnumerable<string> _receiverTags = [];
|
|
private readonly List<RecentReceiverSuggestionDto> _recentReceiverSuggestions = [];
|
|
private bool _isLoadingRecentSuggestions;
|
|
private bool _recentSuggestionsLoaded;
|
|
|
|
private const int SuggestionLimit = 20;
|
|
|
|
private byte[]? _documentBytes;
|
|
private string? _documentFileName;
|
|
private string _documentSizeText = string.Empty;
|
|
private bool _isDocumentUploading;
|
|
private bool _isStepActionRunning;
|
|
private bool _isFinishing;
|
|
|
|
private string? _validationMessage;
|
|
|
|
private bool IsActionLocked => _isDocumentUploading || _isStepActionRunning || _isFinishing;
|
|
|
|
private bool IsFirstStep => _currentStep <= 0;
|
|
private bool IsLastStep => _currentStep >= TotalSteps - 1;
|
|
private bool CanSkipCurrentStep => _currentStep == 1;
|
|
|
|
private IReadOnlyList<RecentReceiverSuggestionDto> FilteredRecentSuggestions =>
|
|
BuildFilteredRecentSuggestions();
|
|
|
|
private string CurrentStepTitle => _currentStep switch
|
|
{
|
|
0 => L("SenderWizard_Step_Basics", "Grunddaten"),
|
|
1 => L("SenderWizard_Step_Message", "Nachricht und Optionen"),
|
|
2 => L("SenderWizard_Step_Receivers", "Empfänger"),
|
|
3 => L("SenderWizard_Step_Document", "Dokument"),
|
|
_ => L("SenderWizard_Step_Basics", "Grunddaten")
|
|
};
|
|
|
|
private string CurrentStepDescription => _currentStep switch
|
|
{
|
|
0 => L("SenderWizard_Step_Basics_Desc", "Starten Sie mit den Pflichtangaben für den Umschlag."),
|
|
1 => L("SenderWizard_Step_Message_Desc", "Fügen Sie optionale Nachricht und Einstellungen hinzu."),
|
|
2 => L("SenderWizard_Step_Receivers_Desc", "Definieren Sie die Empfänger für den Signaturprozess."),
|
|
3 => L("SenderWizard_Step_Document_Desc", "Laden Sie das Dokument hoch und platzieren Sie Signaturfelder."),
|
|
_ => L("SenderWizard_Step_Basics_Desc", "Starten Sie mit den Pflichtangaben für den Umschlag.")
|
|
};
|
|
|
|
protected override void OnParametersSet()
|
|
{
|
|
_isVisible = Visible;
|
|
|
|
if (_selectedEnvelopeTypeId is null && EnvelopeTypes.Count > 0)
|
|
_selectedEnvelopeTypeId = GetDefaultEnvelopeTypeId();
|
|
|
|
if (Visible && !_previousVisible)
|
|
ResetWizardState();
|
|
|
|
_previousVisible = Visible;
|
|
}
|
|
|
|
private Task OnTitleChanged(string? value)
|
|
{
|
|
_title = value ?? string.Empty;
|
|
_validationMessage = null;
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
private Task OnMessageChanged(string? value)
|
|
{
|
|
_message = value ?? string.Empty;
|
|
_validationMessage = null;
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
private Task OnReceiverNameChanged(string? value)
|
|
{
|
|
_receiverName = value ?? string.Empty;
|
|
_validationMessage = null;
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
private Task OnReceiverEmailChanged(string? value)
|
|
{
|
|
_receiverEmail = value ?? string.Empty;
|
|
_validationMessage = null;
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
private Task OnEnvelopeTypeChanged(ChangeEventArgs args)
|
|
{
|
|
_validationMessage = null;
|
|
var raw = args.Value?.ToString();
|
|
if (int.TryParse(raw, out var id) && id > 0)
|
|
{
|
|
_selectedEnvelopeTypeId = id;
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
_selectedEnvelopeTypeId = null;
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
private Task AddReceiver()
|
|
{
|
|
if (IsActionLocked)
|
|
return Task.CompletedTask;
|
|
|
|
var fullName = _receiverName.Trim();
|
|
var email = _receiverEmail.Trim();
|
|
|
|
if (string.IsNullOrWhiteSpace(fullName))
|
|
{
|
|
_validationMessage = L("SenderEditor_Validation_FullNameRequired", "Bitte geben Sie einen Vor- und Nachnamen ein.");
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(email))
|
|
{
|
|
_validationMessage = L("SenderEditor_Validation_EmailRequired", "Bitte geben Sie eine E-Mail-Adresse ein.");
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
if (!EmailValidator.IsValid(email))
|
|
{
|
|
_validationMessage = L("SenderEditor_Validation_EmailInvalid", "Bitte geben Sie eine gültige E-Mail-Adresse ein.");
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
if (_receivers.Any(x => string.Equals(x.EmailAddress, email, StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
_validationMessage = L("SenderEditor_Validation_EmailDuplicate", "Diese E-Mail-Adresse wurde bereits hinzugefügt.");
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
_receivers.Add(new CreateEnvelopeWizardReceiver(fullName, email));
|
|
_receiverTags = _receivers.Select(x => x.EmailAddress).ToList();
|
|
_receiverName = string.Empty;
|
|
_receiverEmail = string.Empty;
|
|
_validationMessage = null;
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
private Task RemoveReceiver(CreateEnvelopeWizardReceiver receiver)
|
|
{
|
|
if (IsActionLocked)
|
|
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;
|
|
}
|
|
|
|
private IReadOnlyList<RecentReceiverSuggestionDto> BuildFilteredRecentSuggestions()
|
|
{
|
|
if (_recentReceiverSuggestions.Count == 0)
|
|
return [];
|
|
|
|
var handler = new ReceiverSuggestionFilterHandler(_recentReceiverSuggestions);
|
|
var filtered = handler.Filter(_receiverName, _receiverEmail);
|
|
|
|
return filtered
|
|
.GroupBy(x => x.EmailAddress, StringComparer.OrdinalIgnoreCase)
|
|
.Select(g => g.OrderByDescending(x => x.LastUsedAt).First())
|
|
.OrderByDescending(x => x.LastUsedAt)
|
|
.ThenBy(x => x.EmailAddress, StringComparer.OrdinalIgnoreCase)
|
|
.Take(SuggestionLimit)
|
|
.ToList();
|
|
}
|
|
|
|
private async Task OnDocumentSelectedAsync(InputFileChangeEventArgs args)
|
|
{
|
|
if (IsActionLocked)
|
|
return;
|
|
|
|
_validationMessage = null;
|
|
var file = args.File;
|
|
if (file is null)
|
|
return;
|
|
|
|
if (!file.Name.EndsWith(".pdf", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
_validationMessage = L("SenderEditor_SelectPdf", "Bitte wählen Sie eine PDF-Datei aus.");
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
_isDocumentUploading = true;
|
|
|
|
const long maxBytes = 50 * 1024 * 1024;
|
|
await using var stream = file.OpenReadStream(maxBytes);
|
|
using var ms = new System.IO.MemoryStream();
|
|
await stream.CopyToAsync(ms);
|
|
|
|
_documentBytes = ms.ToArray();
|
|
_documentFileName = file.Name;
|
|
_documentSizeText = FormatSize(_documentBytes.Length);
|
|
}
|
|
finally
|
|
{
|
|
_isDocumentUploading = false;
|
|
}
|
|
}
|
|
|
|
private Task MoveBack()
|
|
{
|
|
if (IsActionLocked)
|
|
return Task.CompletedTask;
|
|
|
|
_validationMessage = null;
|
|
|
|
if (!IsFirstStep)
|
|
_currentStep--;
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
private async Task MoveNextAsync()
|
|
{
|
|
if (IsActionLocked)
|
|
return;
|
|
|
|
_isStepActionRunning = true;
|
|
try
|
|
{
|
|
if (!ValidateCurrentStep())
|
|
return;
|
|
|
|
_validationMessage = null;
|
|
|
|
if (!IsLastStep)
|
|
{
|
|
_currentStep++;
|
|
|
|
if (_currentStep == 2)
|
|
await EnsureRecentSuggestionsLoadedAsync();
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
_isStepActionRunning = false;
|
|
}
|
|
|
|
await Task.CompletedTask;
|
|
}
|
|
|
|
private async Task SkipOptionalStep()
|
|
{
|
|
if (IsActionLocked)
|
|
return;
|
|
|
|
if (!CanSkipCurrentStep)
|
|
return;
|
|
|
|
_message = string.Empty;
|
|
_validationMessage = null;
|
|
await MoveNextAsync();
|
|
}
|
|
|
|
private async Task CloseAsync()
|
|
{
|
|
if (IsActionLocked)
|
|
return;
|
|
|
|
await CloseCoreAsync();
|
|
}
|
|
|
|
private async Task CloseCoreAsync()
|
|
{
|
|
ResetWizardState();
|
|
await OnPopupVisibleChangedAsync(false);
|
|
}
|
|
|
|
private async Task FinishAsync()
|
|
{
|
|
if (IsActionLocked)
|
|
return;
|
|
|
|
_isFinishing = true;
|
|
try
|
|
{
|
|
if (!ValidateCurrentStep())
|
|
return;
|
|
|
|
var result = new CreateEnvelopeWizardResult(
|
|
Title: _title.Trim(),
|
|
Message: _message.Trim(),
|
|
EnvelopeTypeId: _selectedEnvelopeTypeId,
|
|
Language: string.IsNullOrWhiteSpace(_language) ? "de-DE" : _language,
|
|
Receivers: _receivers.ToList(),
|
|
DocumentBytes: _documentBytes,
|
|
DocumentFileName: _documentFileName);
|
|
|
|
await CloseCoreAsync();
|
|
|
|
if (StartEditorRequested.HasDelegate)
|
|
await StartEditorRequested.InvokeAsync(result);
|
|
}
|
|
finally
|
|
{
|
|
_isFinishing = false;
|
|
}
|
|
}
|
|
|
|
private async Task OnWizardKeyDownAsync(KeyboardEventArgs args)
|
|
{
|
|
if (!_isVisible)
|
|
return;
|
|
|
|
if (string.Equals(args.Key, "Escape", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
await CloseAsync();
|
|
return;
|
|
}
|
|
|
|
if (!string.Equals(args.Key, "Enter", StringComparison.OrdinalIgnoreCase))
|
|
return;
|
|
|
|
if (_currentStep == 1)
|
|
return;
|
|
|
|
if (_currentStep == 2 && (!string.IsNullOrWhiteSpace(_receiverName) || !string.IsNullOrWhiteSpace(_receiverEmail)))
|
|
{
|
|
await AddReceiver();
|
|
return;
|
|
}
|
|
|
|
if (IsLastStep)
|
|
{
|
|
await FinishAsync();
|
|
return;
|
|
}
|
|
|
|
await MoveNextAsync();
|
|
}
|
|
|
|
private async Task OnPopupVisibleChangedAsync(bool value)
|
|
{
|
|
_isVisible = value;
|
|
|
|
if (!value)
|
|
ResetWizardState();
|
|
|
|
if (VisibleChanged.HasDelegate)
|
|
await VisibleChanged.InvokeAsync(value);
|
|
}
|
|
|
|
private async Task EnsureRecentSuggestionsLoadedAsync()
|
|
{
|
|
if (_recentSuggestionsLoaded || _isLoadingRecentSuggestions)
|
|
return;
|
|
|
|
_isLoadingRecentSuggestions = true;
|
|
try
|
|
{
|
|
var suggestions = await ReceiverService.GetRecentReceiverSuggestionsAsync(SuggestionLimit);
|
|
_recentReceiverSuggestions.Clear();
|
|
_recentReceiverSuggestions.AddRange(suggestions);
|
|
_recentSuggestionsLoaded = true;
|
|
}
|
|
catch
|
|
{
|
|
_recentReceiverSuggestions.Clear();
|
|
_recentSuggestionsLoaded = true;
|
|
}
|
|
finally
|
|
{
|
|
_isLoadingRecentSuggestions = false;
|
|
await InvokeAsync(StateHasChanged);
|
|
}
|
|
}
|
|
|
|
private void ApplyRecentSuggestion(RecentReceiverSuggestionDto suggestion)
|
|
{
|
|
if (IsActionLocked)
|
|
return;
|
|
|
|
_receiverName = suggestion.FullName;
|
|
_receiverEmail = suggestion.EmailAddress;
|
|
_validationMessage = null;
|
|
}
|
|
|
|
private bool ValidateCurrentStep()
|
|
{
|
|
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.");
|
|
return false;
|
|
}
|
|
|
|
if (!_selectedEnvelopeTypeId.HasValue || _selectedEnvelopeTypeId.Value <= 0)
|
|
{
|
|
_validationMessage = L("SenderWizard_Validation_EnvelopeTypeRequired", "Bitte wählen Sie einen Umschlagtyp aus.");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
if (_currentStep == 2)
|
|
{
|
|
if (_receivers.Count == 0)
|
|
{
|
|
_validationMessage = L("SenderEditor_Validation_ReceiverRequired", "Bitte fügen Sie mindestens einen Empfänger hinzu.");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
if (_currentStep == 3)
|
|
{
|
|
if (_documentBytes is null || _documentBytes.Length == 0)
|
|
{
|
|
_validationMessage = L("SenderEditor_Validation_UploadPdf", "Bitte laden Sie zuerst ein PDF-Dokument hoch.");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private static string FormatSize(long bytes)
|
|
{
|
|
const double kb = 1024;
|
|
const double mb = kb * 1024;
|
|
|
|
if (bytes >= mb)
|
|
return $"{bytes / mb:0.##} MB";
|
|
|
|
if (bytes >= kb)
|
|
return $"{bytes / kb:0.##} KB";
|
|
|
|
return $"{bytes} B";
|
|
}
|
|
|
|
private void ResetWizardState()
|
|
{
|
|
_currentStep = 0;
|
|
_validationMessage = null;
|
|
|
|
_title = string.Empty;
|
|
_selectedEnvelopeTypeId = GetDefaultEnvelopeTypeId();
|
|
_message = string.Empty;
|
|
_language = "de-DE";
|
|
|
|
_receiverName = string.Empty;
|
|
_receiverEmail = string.Empty;
|
|
_receivers.Clear();
|
|
_receiverTags = [];
|
|
_recentReceiverSuggestions.Clear();
|
|
_isLoadingRecentSuggestions = false;
|
|
_recentSuggestionsLoaded = false;
|
|
|
|
_documentBytes = null;
|
|
_documentFileName = null;
|
|
_documentSizeText = string.Empty;
|
|
}
|
|
|
|
private int? GetDefaultEnvelopeTypeId()
|
|
{
|
|
if (EnvelopeTypes.Count == 0)
|
|
return null;
|
|
|
|
var handwrittenType = EnvelopeTypes.FirstOrDefault(x =>
|
|
!string.IsNullOrWhiteSpace(x.Title)
|
|
&& x.Title.Contains("Handschriftliche Unterschrift", StringComparison.OrdinalIgnoreCase));
|
|
|
|
return handwrittenType?.Id ?? EnvelopeTypes[0].Id;
|
|
}
|
|
|
|
private string L(string key, string fallback)
|
|
{
|
|
var modelValue = ModelLocalizer[key];
|
|
if (!modelValue.ResourceNotFound)
|
|
return modelValue.Value;
|
|
|
|
var uiValue = UiLocalizer[key];
|
|
return uiValue.ResourceNotFound ? fallback : uiValue.Value;
|
|
}
|
|
|
|
}
|