Compare commits
25 Commits
refactor/w
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 89a4e789f1 | |||
| 94658b0d9b | |||
| 6ad76fa4b0 | |||
| 31933f5568 | |||
| 47c4f33dfc | |||
| 26d41d9178 | |||
| 238c938ce7 | |||
| 5fb7b5a79e | |||
| 2c8b65d576 | |||
| e1f24249a9 | |||
| f6dc454193 | |||
| b65098fafb | |||
| c6fe7722b4 | |||
| 2065102fb3 | |||
| 858b748e65 | |||
| 25c03b350b | |||
| ed9ea0ac54 | |||
| 18c1adb68a | |||
| ec8b9adb9c | |||
| 4a10db0865 | |||
| ceb2860883 | |||
| 11e72bac53 | |||
| 638b734648 | |||
| eec1b4926b | |||
| d7611a6b59 |
@@ -287,6 +287,7 @@ Manual testing workflow:
|
||||
- Audit `EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/` for unnecessary request/payload remapping and remove redundant mappings where direct request DTO usage is safe; review client HTTP methods end-to-end for similar avoidable transformation layers.
|
||||
- Centralize dependency injection registrations into dedicated extension methods/modules (especially server + client service registrations in `Program.cs`) to reduce startup composition sprawl and improve maintainability.
|
||||
- Refactor `EnvelopeGenerator.Server/EnvelopeGenerator.Server/Controllers/PdfRenderController.cs` + matching client service contract to remove JSON/base64 `byte[]` transport for PDF rendering; prefer binary streaming (`application/octet-stream` or multipart), avoid `data:` URL bloat where possible, and validate end-to-end memory/latency behavior with large PDFs.
|
||||
- Move sender PDF image editor initial zoom from hardcoded value to Options pattern configuration (bind from `appsettings.json` and inject via `IOptions`/`IOptionsMonitor`) so default zoom (for example `%180`) is environment-configurable.
|
||||
- Establish a DevExpress toast-based notification architecture using `DxToastProvider` + `IToastNotificationService` (tab/page-scoped provider strategy, standardized `ToastOptions` factory, and severity-to-style mapping) so sender/receiver flows show consistent, non-duplicated, actionable feedback.
|
||||
- Eliminate manual property-by-property request→domain mapping drift (example: `CreateEnvelopeCommandHandler`) by introducing a standardized mapping policy (AutoMapper/profile conventions or reflection/source-generator based mapper) and enforcing it across envelope workflows; this needs to be solved in the shared company Core library level so all services get the same guardrails by default.
|
||||
- P1 security workstream: remove dynamic SQL string composition/interpolation from active save/create/update paths (`string.Format`, `$"...{input}..."`, `ToSqlParam`) and migrate to parameterized queries only; treat this as a potential SQL injection vulnerability (stability + security risk), execute repo-wide audit, and add CI guardrails to block reintroduction.
|
||||
|
||||
@@ -622,10 +622,10 @@
|
||||
<value>Zertifizierungstyp</value>
|
||||
</data>
|
||||
<data name="FinalEmailToCreator" xml:space="preserve">
|
||||
<value>Finale E-Mail an Ersteller</value>
|
||||
<value>Abgeschlossenes Dokument an Ersteller</value>
|
||||
</data>
|
||||
<data name="FinalEmailToReceivers" xml:space="preserve">
|
||||
<value>Finale E-Mail an Empfänger</value>
|
||||
<value>Abgeschlossenes Dokument an Empfänger</value>
|
||||
</data>
|
||||
<data name="SendReminderEmails" xml:space="preserve">
|
||||
<value>Erinnerungs-E-Mails senden</value>
|
||||
@@ -749,7 +749,14 @@
|
||||
<data name="SenderEditor_PageLabel" xml:space="preserve"><value>Seite</value></data>
|
||||
<data name="SenderEditor_PlaceSignatureField" xml:space="preserve"><value>Signaturfeld platzieren</value></data>
|
||||
<data name="SenderEditor_RemoveAllSignatureFields" xml:space="preserve"><value>Alle Signaturfelder entfernen</value></data>
|
||||
<data name="SenderEditor_RemoveAllSignatureFieldsConfirm" xml:space="preserve"><value>Diese Aktion entfernt alle platzierten Signaturfelder unwiderruflich. Fortfahren?</value></data>
|
||||
<data name="SenderEditor_SelectPdf" xml:space="preserve"><value>Bitte wählen Sie eine PDF-Datei aus.</value></data>
|
||||
<data name="SenderEditor_SendConfirm_Message" xml:space="preserve"><value>Wollen Sie den Umschlag jetzt versenden? Hinweis: Nach der Übergabe kann der Vorgang nicht mehr bearbeitet werden.</value></data>
|
||||
<data name="SenderEditor_SendConfirm_Title" xml:space="preserve"><value>Umschlag jetzt versenden?</value></data>
|
||||
<data name="SenderEditor_SendConfirm_Note" xml:space="preserve"><value>Bitte prüfen Sie Empfänger, Signaturfelder und Einstellungen vor dem Versenden.</value></data>
|
||||
<data name="SenderEditor_SendConfirm_Action" xml:space="preserve"><value>Jetzt versenden</value></data>
|
||||
<data name="SenderEditor_SendConfirm_InProgress" xml:space="preserve"><value>Umschlag wird versendet...</value></data>
|
||||
<data name="SenderEditor_SendConfirm_InProgressHint" xml:space="preserve"><value>Bitte warten Sie einen Moment. Der Vorgang wird abgeschlossen.</value></data>
|
||||
<data name="SenderEditor_Sent_Body" xml:space="preserve"><value>Der bestehende Entwurf wurde versendet.</value></data>
|
||||
<data name="SenderEditor_Sent_Header" xml:space="preserve"><value>Umschlag gesendet</value></data>
|
||||
<data name="SenderEditor_Sent_Title" xml:space="preserve"><value>Umschlag wurde erfolgreich gesendet.</value></data>
|
||||
@@ -777,6 +784,10 @@
|
||||
<data name="Signed" xml:space="preserve"><value>Unterschrieben</value></data>
|
||||
<data name="SignedDocument" xml:space="preserve"><value>Signiertes Dokument</value></data>
|
||||
<data name="System" xml:space="preserve"><value>System</value></data>
|
||||
<data name="SenderWizard_Step_Start" xml:space="preserve"><value>Start</value></data>
|
||||
<data name="SenderWizard_DocumentUploadSimpleHint" xml:space="preserve"><value>Dokument hinzufuegen: klicken oder per Drag-and-Drop ablegen</value></data>
|
||||
<data name="SenderWizard_DocumentReplaceHint" xml:space="preserve"><value>Zum Ersetzen klicken oder eine Datei auf diese Flaeche ziehen.</value></data>
|
||||
<data name="SenderWizard_FinalUploadHint" xml:space="preserve"><value>Letzte Moeglichkeit im Wizard: Bitte laden Sie jetzt Ihr PDF ueber den rechten Upload-Bereich hoch. Im Editor koennen Sie spaeter ebenfalls ein Dokument hochladen oder ersetzen.</value></data>
|
||||
<data name="UnexpectedErrorTryAgainLater" xml:space="preserve"><value>Ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es später erneut.</value></data>
|
||||
<data name="UploadPdf" xml:space="preserve"><value>PDF hochladen</value></data>
|
||||
<data name="UserName" xml:space="preserve"><value>Benutzername</value></data>
|
||||
|
||||
@@ -622,10 +622,10 @@
|
||||
<value>Certification Type</value>
|
||||
</data>
|
||||
<data name="FinalEmailToCreator" xml:space="preserve">
|
||||
<value>Final Email to Creator</value>
|
||||
<value>Completed Document to Creator</value>
|
||||
</data>
|
||||
<data name="FinalEmailToReceivers" xml:space="preserve">
|
||||
<value>Final Email to Receivers</value>
|
||||
<value>Completed Document to Receivers</value>
|
||||
</data>
|
||||
<data name="SendReminderEmails" xml:space="preserve">
|
||||
<value>Send Reminder Emails</value>
|
||||
@@ -749,7 +749,14 @@
|
||||
<data name="SenderEditor_PageLabel" xml:space="preserve"><value>Page</value></data>
|
||||
<data name="SenderEditor_PlaceSignatureField" xml:space="preserve"><value>Place signature field</value></data>
|
||||
<data name="SenderEditor_RemoveAllSignatureFields" xml:space="preserve"><value>Remove all signature fields</value></data>
|
||||
<data name="SenderEditor_RemoveAllSignatureFieldsConfirm" xml:space="preserve"><value>This action will permanently remove all placed signature fields. Continue?</value></data>
|
||||
<data name="SenderEditor_SelectPdf" xml:space="preserve"><value>Please select a PDF file.</value></data>
|
||||
<data name="SenderEditor_SendConfirm_Message" xml:space="preserve"><value>Do you want to send the envelope now? Note: After handover, the process can no longer be edited.</value></data>
|
||||
<data name="SenderEditor_SendConfirm_Title" xml:space="preserve"><value>Send envelope now?</value></data>
|
||||
<data name="SenderEditor_SendConfirm_Note" xml:space="preserve"><value>Please review recipients, signature fields, and settings before sending.</value></data>
|
||||
<data name="SenderEditor_SendConfirm_Action" xml:space="preserve"><value>Send now</value></data>
|
||||
<data name="SenderEditor_SendConfirm_InProgress" xml:space="preserve"><value>Sending envelope...</value></data>
|
||||
<data name="SenderEditor_SendConfirm_InProgressHint" xml:space="preserve"><value>Please wait a moment while we complete the process.</value></data>
|
||||
<data name="SenderEditor_Sent_Body" xml:space="preserve"><value>The existing draft has been sent.</value></data>
|
||||
<data name="SenderEditor_Sent_Header" xml:space="preserve"><value>Envelope sent</value></data>
|
||||
<data name="SenderEditor_Sent_Title" xml:space="preserve"><value>Envelope was sent successfully.</value></data>
|
||||
@@ -777,6 +784,10 @@
|
||||
<data name="Signed" xml:space="preserve"><value>Signed</value></data>
|
||||
<data name="SignedDocument" xml:space="preserve"><value>Signed document</value></data>
|
||||
<data name="System" xml:space="preserve"><value>System</value></data>
|
||||
<data name="SenderWizard_Step_Start" xml:space="preserve"><value>Start</value></data>
|
||||
<data name="SenderWizard_DocumentUploadSimpleHint" xml:space="preserve"><value>Add document: click or drag and drop</value></data>
|
||||
<data name="SenderWizard_DocumentReplaceHint" xml:space="preserve"><value>Click to replace or drag a file onto this area.</value></data>
|
||||
<data name="SenderWizard_FinalUploadHint" xml:space="preserve"><value>Last chance in the wizard: please upload your PDF now using the upload area on the right. In the editor, you can still upload or replace a document later.</value></data>
|
||||
<data name="UnexpectedErrorTryAgainLater" xml:space="preserve"><value>An unexpected error occurred. Please try again later.</value></data>
|
||||
<data name="UploadPdf" xml:space="preserve"><value>Upload PDF</value></data>
|
||||
<data name="UserName" xml:space="preserve"><value>Username</value></data>
|
||||
|
||||
@@ -622,10 +622,10 @@
|
||||
<value>Type de certification</value>
|
||||
</data>
|
||||
<data name="FinalEmailToCreator" xml:space="preserve">
|
||||
<value>E-mail final au créateur</value>
|
||||
<value>Document terminé au créateur</value>
|
||||
</data>
|
||||
<data name="FinalEmailToReceivers" xml:space="preserve">
|
||||
<value>E-mail final aux destinataires</value>
|
||||
<value>Document terminé aux destinataires</value>
|
||||
</data>
|
||||
<data name="SendReminderEmails" xml:space="preserve">
|
||||
<value>Envoyer des e-mails de rappel</value>
|
||||
@@ -749,7 +749,14 @@
|
||||
<data name="SenderEditor_PageLabel" xml:space="preserve"><value>Page</value></data>
|
||||
<data name="SenderEditor_PlaceSignatureField" xml:space="preserve"><value>Placer un champ de signature</value></data>
|
||||
<data name="SenderEditor_RemoveAllSignatureFields" xml:space="preserve"><value>Supprimer tous les champs de signature</value></data>
|
||||
<data name="SenderEditor_RemoveAllSignatureFieldsConfirm" xml:space="preserve"><value>Cette action supprimera définitivement tous les champs de signature placés. Continuer?</value></data>
|
||||
<data name="SenderEditor_SelectPdf" xml:space="preserve"><value>Veuillez sélectionner un fichier PDF.</value></data>
|
||||
<data name="SenderEditor_SendConfirm_Message" xml:space="preserve"><value>Voulez-vous envoyer l'enveloppe maintenant ? Remarque : après la transmission, le processus ne peut plus être modifié.</value></data>
|
||||
<data name="SenderEditor_SendConfirm_Title" xml:space="preserve"><value>Envoyer l'enveloppe maintenant ?</value></data>
|
||||
<data name="SenderEditor_SendConfirm_Note" xml:space="preserve"><value>Veuillez vérifier les destinataires, les champs de signature et les paramètres avant l'envoi.</value></data>
|
||||
<data name="SenderEditor_SendConfirm_Action" xml:space="preserve"><value>Envoyer maintenant</value></data>
|
||||
<data name="SenderEditor_SendConfirm_InProgress" xml:space="preserve"><value>Envoi de l'enveloppe...</value></data>
|
||||
<data name="SenderEditor_SendConfirm_InProgressHint" xml:space="preserve"><value>Veuillez patienter un instant pendant la finalisation du traitement.</value></data>
|
||||
<data name="SenderEditor_Sent_Body" xml:space="preserve"><value>Le brouillon existant a été envoyé.</value></data>
|
||||
<data name="SenderEditor_Sent_Header" xml:space="preserve"><value>Enveloppe envoyée</value></data>
|
||||
<data name="SenderEditor_Sent_Title" xml:space="preserve"><value>L'enveloppe a été envoyée avec succès.</value></data>
|
||||
@@ -777,6 +784,10 @@
|
||||
<data name="Signed" xml:space="preserve"><value>Signé</value></data>
|
||||
<data name="SignedDocument" xml:space="preserve"><value>Document signé</value></data>
|
||||
<data name="System" xml:space="preserve"><value>Système</value></data>
|
||||
<data name="SenderWizard_Step_Start" xml:space="preserve"><value>Démarrer</value></data>
|
||||
<data name="SenderWizard_DocumentUploadSimpleHint" xml:space="preserve"><value>Ajouter un document : cliquer ou glisser-deposer</value></data>
|
||||
<data name="SenderWizard_DocumentReplaceHint" xml:space="preserve"><value>Cliquez pour remplacer ou glissez un fichier sur cette zone.</value></data>
|
||||
<data name="SenderWizard_FinalUploadHint" xml:space="preserve"><value>Derniere possibilite dans l'assistant : veuillez televerser votre PDF maintenant via la zone d'upload a droite. Dans l'editeur, vous pourrez encore televerser ou remplacer un document plus tard.</value></data>
|
||||
<data name="UnexpectedErrorTryAgainLater" xml:space="preserve"><value>Une erreur inattendue s'est produite. Veuillez réessayer plus tard.</value></data>
|
||||
<data name="UploadPdf" xml:space="preserve"><value>Téléverser un PDF</value></data>
|
||||
<data name="UserName" xml:space="preserve"><value>Nom d'utilisateur</value></data>
|
||||
|
||||
@@ -295,10 +295,10 @@
|
||||
<value>Use Access Code</value>
|
||||
</data>
|
||||
<data name="FinalEmailToCreator" xml:space="preserve">
|
||||
<value>Final Email to Creator</value>
|
||||
<value>Completed Document to Creator</value>
|
||||
</data>
|
||||
<data name="FinalEmailToReceivers" xml:space="preserve">
|
||||
<value>Final Email to Receivers</value>
|
||||
<value>Completed Document to Receivers</value>
|
||||
</data>
|
||||
<data name="SendReminderEmails" xml:space="preserve">
|
||||
<value>Send Reminder Emails</value>
|
||||
|
||||
@@ -132,10 +132,10 @@
|
||||
<value>Utiliser un code d'acces</value>
|
||||
</data>
|
||||
<data name="FinalEmailToCreator" xml:space="preserve">
|
||||
<value>E-mail final au createur</value>
|
||||
<value>Document termine au createur</value>
|
||||
</data>
|
||||
<data name="FinalEmailToReceivers" xml:space="preserve">
|
||||
<value>E-mail final aux destinataires</value>
|
||||
<value>Document termine aux destinataires</value>
|
||||
</data>
|
||||
<data name="SendReminderEmails" xml:space="preserve">
|
||||
<value>Envoyer des e-mails de rappel</value>
|
||||
|
||||
@@ -298,10 +298,10 @@
|
||||
<value>Zugriffscode verwenden</value>
|
||||
</data>
|
||||
<data name="FinalEmailToCreator" xml:space="preserve">
|
||||
<value>Finale E-Mail an Ersteller</value>
|
||||
<value>Abgeschlossenes Dokument an Ersteller</value>
|
||||
</data>
|
||||
<data name="FinalEmailToReceivers" xml:space="preserve">
|
||||
<value>Finale E-Mail an Empfänger</value>
|
||||
<value>Abgeschlossenes Dokument an Empfänger</value>
|
||||
</data>
|
||||
<data name="SendReminderEmails" xml:space="preserve">
|
||||
<value>Erinnerungs-E-Mails senden</value>
|
||||
|
||||
@@ -146,7 +146,7 @@
|
||||
private double _grabOffsetXPt;
|
||||
private double _grabOffsetYPt;
|
||||
private int _currentPage = 1;
|
||||
private int _zoomPercent = 112;
|
||||
private int _zoomPercent = 180;
|
||||
private bool _zoomOverrideInitialized;
|
||||
private bool _isDownloading;
|
||||
private bool _isPrinting;
|
||||
|
||||
@@ -11,13 +11,16 @@
|
||||
@inject IStringLocalizer<Model> ModelLocalizer
|
||||
@inject IStringLocalizer<Resource> UiLocalizer
|
||||
@inject ReceiverService ReceiverService
|
||||
@inject PdfRenderService PdfRenderService
|
||||
|
||||
<DxPopup Visible="@_isVisible"
|
||||
VisibleChanged="OnPopupVisibleChangedAsync"
|
||||
HeaderText="@L("SenderWizard_Header", "Neuen Umschlag erstellen")"
|
||||
Width="860px"
|
||||
MaxWidth="96vw"
|
||||
Width="90vw"
|
||||
MaxWidth="90vw"
|
||||
Height="85vh"
|
||||
MinHeight="85vh"
|
||||
MaxHeight="85vh"
|
||||
Scrollable="false"
|
||||
ShowFooter="true"
|
||||
CloseOnOutsideClick="false"
|
||||
ShowCloseButton="true"
|
||||
@@ -28,10 +31,15 @@
|
||||
<div class="sender-create-wizard"
|
||||
tabindex="0"
|
||||
@onkeydown="OnWizardKeyDownAsync">
|
||||
<div class="sender-create-wizard__body-layout">
|
||||
<MudPaper Class="sender-create-wizard__stepper-shell" Elevation="0">
|
||||
<MudStepper Vertical="true"
|
||||
NonLinear="false"
|
||||
ShowResetButton="false"
|
||||
CurrentStepColor="Color.Info"
|
||||
CompletedStepColor="Color.Success"
|
||||
ErrorStepColor="Color.Error"
|
||||
StepCompleteIcon="@Icons.Material.Filled.CheckCircle"
|
||||
ActiveIndex="@_currentStep"
|
||||
Class="sender-create-wizard__stepper">
|
||||
<ChildContent>
|
||||
@@ -39,18 +47,25 @@
|
||||
{
|
||||
var stepIndex = step;
|
||||
|
||||
<MudStep Title="@GetStepTitle(stepIndex)">
|
||||
<MudStep Title="@GetStepTitle(stepIndex)"
|
||||
Completed="@_completedSteps.Contains(stepIndex)"
|
||||
Skippable="false">
|
||||
<div class="sender-create-wizard__stepper-description">@GetStepDescription(stepIndex)</div>
|
||||
|
||||
@if (stepIndex == _currentStep)
|
||||
{
|
||||
<div class="sender-create-wizard__step-content">
|
||||
<div class="sender-create-wizard__step-layout">
|
||||
<div class="sender-create-wizard__step-main">
|
||||
@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 class="sender-create-wizard__label sender-create-wizard__label-row">
|
||||
<span>@L("Title", "Titel") <span class="sender-create-wizard__required">*</span></span>
|
||||
<MudTooltip Text='@L("SenderWizard_Help_Title", "Ein kurzer, eindeutiger Titel hilft beim schnellen Wiederfinden des Umschlags.")'>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Info" Color="Color.Tertiary" Size="Size.Small" Class="sender-create-wizard__help-icon" aria-label='@L("SenderWizard_HelpAria_Title", "Hilfe zum Feld Titel")' />
|
||||
</MudTooltip>
|
||||
</label>
|
||||
<DxTextBox Text="@_title"
|
||||
TextChanged="OnTitleChanged"
|
||||
@@ -60,8 +75,11 @@
|
||||
</div>
|
||||
|
||||
<div class="sender-create-wizard__field">
|
||||
<label class="sender-create-wizard__label">
|
||||
@L("EnvelopeType", "Umschlagtyp") <span class="sender-create-wizard__required">*</span>
|
||||
<label class="sender-create-wizard__label sender-create-wizard__label-row">
|
||||
<span>@L("EnvelopeType", "Umschlagtyp") <span class="sender-create-wizard__required">*</span></span>
|
||||
<MudTooltip Text='@L("SenderWizard_Help_EnvelopeType", "Der Umschlagtyp kann Standardwerte wie Sprache, Erinnerungen und Sicherheit vorbelegen.")'>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Info" Color="Color.Tertiary" Size="Size.Small" Class="sender-create-wizard__help-icon" aria-label='@L("SenderWizard_HelpAria_EnvelopeType", "Hilfe zum Feld Umschlagtyp")' />
|
||||
</MudTooltip>
|
||||
</label>
|
||||
<select value="@(_selectedEnvelopeTypeId?.ToString() ?? string.Empty)"
|
||||
@onchange="OnEnvelopeTypeChanged"
|
||||
@@ -78,8 +96,11 @@
|
||||
{
|
||||
<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 class="sender-create-wizard__label sender-create-wizard__label-row">
|
||||
<span>@L("Message", "Nachricht") <span class="sender-create-wizard__optional">(@L("Optional", "optional"))</span></span>
|
||||
<MudTooltip Text='@L("SenderWizard_Help_Message", "Diese Nachricht wird den Empfaengern in der E-Mail und im Signaturprozess angezeigt.")'>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Info" Color="Color.Tertiary" Size="Size.Small" Class="sender-create-wizard__help-icon" aria-label='@L("SenderWizard_HelpAria_Message", "Hilfe zum Feld Nachricht")' />
|
||||
</MudTooltip>
|
||||
</label>
|
||||
<DxMemo Text="@_message"
|
||||
TextChanged="OnMessageChanged"
|
||||
@@ -90,8 +111,11 @@
|
||||
</div>
|
||||
|
||||
<div class="sender-create-wizard__field">
|
||||
<label class="sender-create-wizard__label">
|
||||
@L("Language", "Sprache")
|
||||
<label class="sender-create-wizard__label sender-create-wizard__label-row">
|
||||
<span>@L("Language", "Sprache")</span>
|
||||
<MudTooltip Text='@L("SenderWizard_Help_Language", "Legt die Sprache fuer Hinweise, E-Mails und Oberflaechentexte fest.")'>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Info" Color="Color.Tertiary" Size="Size.Small" Class="sender-create-wizard__help-icon" aria-label='@L("SenderWizard_HelpAria_Language", "Hilfe zum Feld Sprache")' />
|
||||
</MudTooltip>
|
||||
</label>
|
||||
<select @bind="_language" class="sender-create-wizard__select">
|
||||
<option value="de-DE">Deutsch (de-DE)</option>
|
||||
@@ -100,6 +124,12 @@
|
||||
<option value="en">English (en)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="sender-create-wizard__field sender-create-wizard__field--full">
|
||||
<div class="sender-create-wizard__stage">
|
||||
<p>@L("SenderWizard_FinalUploadHint", "Letzte Moeglichkeit im Wizard: Bitte laden Sie jetzt Ihr PDF ueber den rechten Upload-Bereich hoch. Im Editor koennen Sie spaeter ebenfalls ein Dokument hochladen oder ersetzen.")</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else if (_currentStep == 2)
|
||||
@@ -112,15 +142,25 @@
|
||||
|
||||
<div class="sender-create-wizard__form-grid">
|
||||
<div class="sender-create-wizard__field">
|
||||
<label class="sender-create-wizard__label">@L("CertificationType", "Zertifizierung")</label>
|
||||
<select @bind="_certificationType" class="sender-create-wizard__select">
|
||||
<label class="sender-create-wizard__label sender-create-wizard__label-row">
|
||||
<span>@L("CertificationType", "Zertifizierung")</span>
|
||||
<MudTooltip Text='@L("SenderWizard_Help_CertificationType", "Bestimmt das Signaturniveau fuer diesen Umschlag.")'>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Info" Color="Color.Tertiary" Size="Size.Small" Class="sender-create-wizard__help-icon" aria-label='@L("SenderWizard_HelpAria_CertificationType", "Hilfe zum Feld Zertifizierung")' />
|
||||
</MudTooltip>
|
||||
</label>
|
||||
<select @bind="_certificationType" @bind:after="OnCertificationTypeChanged" class="sender-create-wizard__select">
|
||||
<option value="@CertificationType.AdvancedElectronicSignature">@L("AdvancedElectronicSignature", "Advanced Electronic Signature")</option>
|
||||
<option value="@CertificationType.ElectronicSignature">@L("ElectronicSignature", "Electronic Signature")</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="sender-create-wizard__field">
|
||||
<label class="sender-create-wizard__label">@L("FinalEmailToCreator", "Finale E-Mail an Ersteller")</label>
|
||||
<label class="sender-create-wizard__label sender-create-wizard__label-row">
|
||||
<span>@L("FinalEmailToCreator", "Abgeschlossenes Dokument an Ersteller")</span>
|
||||
<MudTooltip Text='@L("SenderWizard_Help_FinalEmailToCreator", "Steuert, ob der Ersteller nach Abschluss eine finale E-Mail erhaelt und ob das Dokument angehaengt wird.")'>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Info" Color="Color.Tertiary" Size="Size.Small" Class="sender-create-wizard__help-icon" aria-label='@L("SenderWizard_HelpAria_FinalEmailToCreator", "Hilfe zum Feld Abgeschlossenes Dokument an Ersteller")' />
|
||||
</MudTooltip>
|
||||
</label>
|
||||
<select @bind="_finalEmailToCreator" class="sender-create-wizard__select">
|
||||
<option value="0">@L("No", "Nein")</option>
|
||||
<option value="1">@L("Yes", "Ja")</option>
|
||||
@@ -129,7 +169,12 @@
|
||||
</div>
|
||||
|
||||
<div class="sender-create-wizard__field">
|
||||
<label class="sender-create-wizard__label">@L("FinalEmailToReceivers", "Finale E-Mail an Empfänger")</label>
|
||||
<label class="sender-create-wizard__label sender-create-wizard__label-row">
|
||||
<span>@L("FinalEmailToReceivers", "Abgeschlossenes Dokument an Empfänger")</span>
|
||||
<MudTooltip Text='@L("SenderWizard_Help_FinalEmailToReceivers", "Steuert, ob Empfaenger nach Abschluss eine finale E-Mail erhalten und ob das Dokument angehaengt wird.")'>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Info" Color="Color.Tertiary" Size="Size.Small" Class="sender-create-wizard__help-icon" aria-label='@L("SenderWizard_HelpAria_FinalEmailToReceivers", "Hilfe zum Feld Abgeschlossenes Dokument an Empfaenger")' />
|
||||
</MudTooltip>
|
||||
</label>
|
||||
<select @bind="_finalEmailToReceivers" class="sender-create-wizard__select">
|
||||
<option value="0">@L("No", "Nein")</option>
|
||||
<option value="1">@L("Yes", "Ja")</option>
|
||||
@@ -140,12 +185,18 @@
|
||||
<div class="sender-create-wizard__field sender-create-wizard__field--full">
|
||||
<div class="sender-create-wizard__settings-toggles">
|
||||
<label class="sender-create-wizard__settings-toggle">
|
||||
<input type="checkbox" checked="@_tfaEnabled" @onchange="OnTfaChanged" />
|
||||
@L("TFAEnabled", "2FA aktiviert")
|
||||
<input type="checkbox" checked="@_tfaEnabled" @onchange="OnTfaChanged" disabled="@IsFesSelected" />
|
||||
<span>@L("TFAEnabled", "2FA aktiviert")</span>
|
||||
<MudTooltip Text='@L("SenderWizard_Help_TfaEnabled", "Wenn aktiviert, ist die Signaturfreigabe nur mit zusaetzlicher Verifizierung moeglich.")'>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Info" Color="Color.Tertiary" Size="Size.Small" Class="sender-create-wizard__help-icon" aria-label='@L("SenderWizard_HelpAria_TfaEnabled", "Hilfe zur Option 2FA")' />
|
||||
</MudTooltip>
|
||||
</label>
|
||||
<label class="sender-create-wizard__settings-toggle">
|
||||
<input type="checkbox" checked="@_useAccessCode" @onchange="OnUseAccessCodeChanged" disabled="@_tfaEnabled" />
|
||||
@L("UseAccessCode", "Zugriffscode verwenden")
|
||||
<span>@L("UseAccessCode", "Zugriffscode verwenden")</span>
|
||||
<MudTooltip Text='@L("SenderWizard_Help_UseAccessCode", "Empfaenger muessen einen Zugriffscode eingeben. Bei aktivem 2FA bleibt diese Option immer aktiv.")'>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Info" Color="Color.Tertiary" Size="Size.Small" Class="sender-create-wizard__help-icon" aria-label='@L("SenderWizard_HelpAria_UseAccessCode", "Hilfe zur Option Zugriffscode verwenden")' />
|
||||
</MudTooltip>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -161,27 +212,50 @@
|
||||
<div class="sender-create-wizard__field sender-create-wizard__field--full">
|
||||
<label class="sender-create-wizard__settings-toggle">
|
||||
<input type="checkbox" checked="@_sendReminderEmails" @onchange="OnSendReminderEmailsChanged" />
|
||||
@L("SendReminderEmails", "Erinnerungs-E-Mails senden")
|
||||
<span>@L("SendReminderEmails", "Erinnerungs-E-Mails senden")</span>
|
||||
<MudTooltip Text='@L("SenderWizard_Help_SendReminderEmails", "Aktiviert automatische Erinnerungsmails fuer noch offene Signaturen.")'>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Info" Color="Color.Tertiary" Size="Size.Small" Class="sender-create-wizard__help-icon" aria-label='@L("SenderWizard_HelpAria_SendReminderEmails", "Hilfe zur Option Erinnerungs-E-Mails senden")' />
|
||||
</MudTooltip>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="sender-create-wizard__field">
|
||||
<label class="sender-create-wizard__label">@L("FirstReminderDays", "Erste Erinnerung (Tage)")</label>
|
||||
<label class="sender-create-wizard__label sender-create-wizard__label-row">
|
||||
<span>@L("FirstReminderDays", "Erste Erinnerung (Tage)")</span>
|
||||
<MudTooltip Text='@L("SenderWizard_Help_FirstReminderDays", "Anzahl Tage nach Versand bis zur ersten Erinnerung.")'>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Info" Color="Color.Tertiary" Size="Size.Small" Class="sender-create-wizard__help-icon" aria-label='@L("SenderWizard_HelpAria_FirstReminderDays", "Hilfe zum Feld Erste Erinnerung")' />
|
||||
</MudTooltip>
|
||||
</label>
|
||||
<InputNumber @bind-Value="_firstReminderDays" class="form-control form-control-sm" disabled="@(!_sendReminderEmails)" />
|
||||
</div>
|
||||
|
||||
<div class="sender-create-wizard__field">
|
||||
<label class="sender-create-wizard__label">@L("ReminderIntervalDays", "Erinnerungsintervall (Tage)")</label>
|
||||
<label class="sender-create-wizard__label sender-create-wizard__label-row">
|
||||
<span>@L("ReminderIntervalDays", "Erinnerungsintervall (Tage)")</span>
|
||||
<MudTooltip Text='@L("SenderWizard_Help_ReminderIntervalDays", "Abstand in Tagen zwischen den weiteren Erinnerungen.")'>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Info" Color="Color.Tertiary" Size="Size.Small" Class="sender-create-wizard__help-icon" aria-label='@L("SenderWizard_HelpAria_ReminderIntervalDays", "Hilfe zum Feld Erinnerungsintervall")' />
|
||||
</MudTooltip>
|
||||
</label>
|
||||
<InputNumber @bind-Value="_reminderIntervalDays" class="form-control form-control-sm" disabled="@(!_sendReminderEmails)" />
|
||||
</div>
|
||||
|
||||
<div class="sender-create-wizard__field">
|
||||
<label class="sender-create-wizard__label">@L("ExpiresWhenDays", "Läuft ab nach (Tage)")</label>
|
||||
<label class="sender-create-wizard__label sender-create-wizard__label-row">
|
||||
<span>@L("ExpiresWhenDays", "Laeuft ab nach (Tage)")</span>
|
||||
<MudTooltip Text='@L("SenderWizard_Help_ExpiresWhenDays", "Legt fest, nach wie vielen Tagen der Umschlag automatisch ablaeuft.")'>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Info" Color="Color.Tertiary" Size="Size.Small" Class="sender-create-wizard__help-icon" aria-label='@L("SenderWizard_HelpAria_ExpiresWhenDays", "Hilfe zum Feld Laeuft ab nach")' />
|
||||
</MudTooltip>
|
||||
</label>
|
||||
<InputNumber @bind-Value="_expiresWhenDays" class="form-control form-control-sm" />
|
||||
</div>
|
||||
|
||||
<div class="sender-create-wizard__field">
|
||||
<label class="sender-create-wizard__label">@L("ExpiresWarningWhenDays", "Ablaufwarnung (Tage)")</label>
|
||||
<label class="sender-create-wizard__label sender-create-wizard__label-row">
|
||||
<span>@L("ExpiresWarningWhenDays", "Ablaufwarnung (Tage)")</span>
|
||||
<MudTooltip Text='@L("SenderWizard_Help_ExpiresWarningWhenDays", "Tage vor Ablauf, an denen eine Warnung versendet wird.")'>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Info" Color="Color.Tertiary" Size="Size.Small" Class="sender-create-wizard__help-icon" aria-label='@L("SenderWizard_HelpAria_ExpiresWarningWhenDays", "Hilfe zum Feld Ablaufwarnung")' />
|
||||
</MudTooltip>
|
||||
</label>
|
||||
<InputNumber @bind-Value="_expiresWarningWhenDays" class="form-control form-control-sm" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -191,7 +265,11 @@
|
||||
else if (_currentStep == 1)
|
||||
{
|
||||
<div class="sender-create-wizard__suggestions">
|
||||
<div class="sender-create-wizard__suggestions-title">@L("SenderWizard_RecentReceivers", "Zuletzt verwendete Empfänger")</div>
|
||||
<DxTextBox Text="@_receiverSuggestionFilterText"
|
||||
TextChanged="OnReceiverSuggestionFilterChanged"
|
||||
BindValueMode="BindValueMode.OnInput"
|
||||
NullText='@L("SenderWizard_FilterRecentReceivers_Placeholder", "Empfänger in Vorschlägen filtern...")'
|
||||
CssClass="w-100" />
|
||||
|
||||
@if (_isLoadingRecentSuggestions)
|
||||
{
|
||||
@@ -204,7 +282,7 @@
|
||||
{
|
||||
<button type="button"
|
||||
class="sender-create-wizard__suggestion-item"
|
||||
@onclick="() => ApplyRecentSuggestion(suggestion)"
|
||||
@onclick="() => ApplyRecentSuggestionAndAddAsync(suggestion)"
|
||||
disabled="@IsActionLocked">
|
||||
<span class="sender-create-wizard__suggestion-name">@suggestion.FullName</span>
|
||||
<span class="sender-create-wizard__suggestion-email">@suggestion.EmailAddress</span>
|
||||
@@ -214,142 +292,82 @@
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="sender-create-wizard__form-grid">
|
||||
<div class="sender-create-wizard__receiver-tagbox-wrap">
|
||||
<div class="sender-create-wizard__receiver-list" role="table" aria-label="Added receivers">
|
||||
<div class="sender-create-wizard__receiver-existing-list">
|
||||
@for (var i = 0; i < _receivers.Count; i++)
|
||||
{
|
||||
var rowIndex = i;
|
||||
var receiver = _receivers[rowIndex];
|
||||
|
||||
<div class="sender-create-wizard__receiver-row" role="row">
|
||||
<div class="sender-create-wizard__receiver-fields-stack sender-create-wizard__field--full">
|
||||
<div class="sender-create-wizard__field">
|
||||
<DxTextBox Text="@receiver.FullName"
|
||||
TextChanged="@(value => OnReceiverListNameChanged(rowIndex, value))"
|
||||
BindValueMode="BindValueMode.OnInput"
|
||||
NullText='@L("FullName", "Vor- und Nachname")'
|
||||
CssClass="w-100"
|
||||
Enabled="@(!IsActionLocked)" />
|
||||
</div>
|
||||
<div class="sender-create-wizard__field">
|
||||
<DxTextBox Text="@receiver.EmailAddress"
|
||||
TextChanged="@(value => OnReceiverListEmailChanged(rowIndex, value))"
|
||||
BindValueMode="BindValueMode.OnInput"
|
||||
NullText='@L("EmailAddress", "E-Mail-Adresse")'
|
||||
CssClass="w-100"
|
||||
Enabled="@(!IsActionLocked)" />
|
||||
</div>
|
||||
</div>
|
||||
<button type="button"
|
||||
class="sender-create-wizard__receiver-remove-btn"
|
||||
@onclick="() => RemoveReceiver(receiver)"
|
||||
disabled="@IsActionLocked"
|
||||
title='@L("Remove", "Entfernen")'
|
||||
aria-label='@L("Remove", "Entfernen")'>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true">
|
||||
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z" />
|
||||
<path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
<div class="sender-create-wizard__receiver-row sender-create-wizard__receiver-row--new" role="row">
|
||||
<div class="sender-create-wizard__receiver-fields-stack sender-create-wizard__field--full">
|
||||
<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" />
|
||||
CssClass="w-100"
|
||||
Enabled="@(!IsActionLocked)" />
|
||||
</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" />
|
||||
CssClass="w-100"
|
||||
Enabled="@(!IsActionLocked)" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sender-create-wizard__receiver-actions">
|
||||
<button type="button"
|
||||
class="sender-create-wizard__add-receiver-btn"
|
||||
class="sender-create-wizard__receiver-add-inline-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" />
|
||||
disabled="@IsActionLocked"
|
||||
title='@L("AddReceiver", "Empfänger hinzufügen")'
|
||||
aria-label='@L("AddReceiver", "Empfänger hinzufügen")'>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true">
|
||||
<path d="M8 1a.5.5 0 0 1 .5.5V7.5h6a.5.5 0 0 1 0 1h-6v6a.5.5 0 0 1-1 0v-6h-6a.5.5 0 0 1 0-1h6v-6A.5.5 0 0 1 8 1z" />
|
||||
</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-preview-card">
|
||||
<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>
|
||||
<div class="sender-create-wizard__document-preview-wrap">
|
||||
<SenderPdfImageEditor IsPlacementMode="false"
|
||||
PageCount="@_documentPreviewPageCount"
|
||||
Pages="@_documentPreviewPages"
|
||||
PdfDataUrl="@DocumentPreviewDataUrl"
|
||||
PdfFileName="@_documentFileName"
|
||||
Fields="@_documentPreviewFieldOverlays"
|
||||
EditorClick="OnDocumentPreviewEditorClickAsync"
|
||||
RemoveField="OnDocumentPreviewRemoveFieldAsync"
|
||||
MoveField="OnDocumentPreviewMoveFieldAsync" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<PdfFileDropZone Disabled="@IsActionLocked"
|
||||
HasDocument="false"
|
||||
UploadText='@L("SenderWizard_DocumentRequiredHint", "Bitte laden Sie ein PDF-Dokument hoch, um fortzufahren.")'
|
||||
DragDropHintText='@L("SenderWizard_DragDropPdfHint", "Oder ziehen Sie die PDF-Datei hierher.")'
|
||||
FileSelected="OnDocumentSelectedAsync" />
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</MudStep>
|
||||
@@ -358,6 +376,18 @@
|
||||
</MudStepper>
|
||||
</MudPaper>
|
||||
|
||||
<aside class="sender-create-wizard__wizard-side">
|
||||
<PdfFileDropZone Disabled="@IsActionLocked"
|
||||
HasDocument="@(_documentBytes is { Length: > 0 })"
|
||||
DocumentName="@_documentFileName"
|
||||
DocumentSizeText="@_documentSizeText"
|
||||
UploadText='@L("SenderWizard_DocumentUploadSimpleHint", "Dokument hinzufuegen: klicken oder per Drag-and-Drop ablegen")'
|
||||
ReplaceText='@L("SenderWizard_DocumentUploadSimpleHint", "Dokument hinzufuegen: klicken oder per Drag-and-Drop ablegen")'
|
||||
UploadedHintText='@L("SenderWizard_DocumentReplaceHint", "Zum Ersetzen klicken oder eine Datei auf diese Fläche ziehen.")'
|
||||
FileSelected="OnDocumentSelectedAsync" />
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(_validationMessage))
|
||||
{
|
||||
<div class="sender-create-wizard__validation">@_validationMessage</div>
|
||||
@@ -372,14 +402,6 @@
|
||||
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"
|
||||
@@ -416,12 +438,13 @@
|
||||
[Parameter]
|
||||
public IReadOnlyList<EnvelopeTypeDto> EnvelopeTypes { get; set; } = [];
|
||||
|
||||
private const int TotalSteps = 5;
|
||||
private const int TotalSteps = 4;
|
||||
private static readonly System.ComponentModel.DataAnnotations.EmailAddressAttribute EmailValidator = new();
|
||||
|
||||
private bool _isVisible;
|
||||
private bool _previousVisible;
|
||||
private int _currentStep;
|
||||
private readonly HashSet<int> _completedSteps = [];
|
||||
|
||||
private string _title = string.Empty;
|
||||
private int? _selectedEnvelopeTypeId;
|
||||
@@ -440,8 +463,8 @@
|
||||
|
||||
private string _receiverName = string.Empty;
|
||||
private string _receiverEmail = string.Empty;
|
||||
private string _receiverSuggestionFilterText = string.Empty;
|
||||
private readonly List<CreateEnvelopeWizardReceiver> _receivers = [];
|
||||
private IEnumerable<string> _receiverTags = [];
|
||||
private readonly List<RecentReceiverSuggestionDto> _recentReceiverSuggestions = [];
|
||||
private bool _isLoadingRecentSuggestions;
|
||||
private bool _recentSuggestionsLoaded;
|
||||
@@ -451,41 +474,27 @@
|
||||
private byte[]? _documentBytes;
|
||||
private string? _documentFileName;
|
||||
private string _documentSizeText = string.Empty;
|
||||
private int _documentPreviewPageCount;
|
||||
private List<PdfPageImageDto> _documentPreviewPages = [];
|
||||
private bool _isDocumentUploading;
|
||||
private bool _isStepActionRunning;
|
||||
private bool _isFinishing;
|
||||
|
||||
private readonly IReadOnlyList<SenderPdfFieldOverlay> _documentPreviewFieldOverlays = [];
|
||||
|
||||
private string? _validationMessage;
|
||||
|
||||
private bool IsActionLocked => _isDocumentUploading || _isStepActionRunning || _isFinishing;
|
||||
private bool IsFesSelected => _certificationType == CertificationType.AdvancedElectronicSignature;
|
||||
|
||||
private bool IsFirstStep => _currentStep <= 0;
|
||||
private bool IsLastStep => _currentStep >= TotalSteps - 1;
|
||||
private bool CanSkipCurrentStep => _currentStep == 3;
|
||||
|
||||
private IReadOnlyList<RecentReceiverSuggestionDto> FilteredRecentSuggestions =>
|
||||
BuildFilteredRecentSuggestions();
|
||||
|
||||
private string? DocumentPreviewDataUrl => _documentBytes is { Length: > 0 }
|
||||
? $"data:application/pdf;base64,{Convert.ToBase64String(_documentBytes)}"
|
||||
: null;
|
||||
|
||||
private string CurrentStepTitle => GetStepTitle(_currentStep);
|
||||
|
||||
private string CurrentStepDescription => GetStepDescription(_currentStep);
|
||||
|
||||
private string GetStepTitle(int step) => step switch
|
||||
{
|
||||
0 => L("SenderWizard_Step_Basics", "Grunddaten"),
|
||||
0 => L("SenderWizard_Step_Start", "Start"),
|
||||
1 => L("SenderWizard_Step_Receivers", "Empfänger"),
|
||||
2 => L("SenderEditor_EnvelopeSettings", "Envelope-Einstellungen"),
|
||||
3 => L("SenderWizard_Step_Message", "Nachricht und Optionen"),
|
||||
4 => L("SenderWizard_Step_Document", "Dokument"),
|
||||
_ => L("SenderWizard_Step_Basics", "Grunddaten")
|
||||
3 => L("SenderWizard_Step_Message", "Nachricht und letzter Dokument-Upload"),
|
||||
_ => L("SenderWizard_Step_Start", "Start")
|
||||
};
|
||||
|
||||
private string GetStepDescription(int step) => step switch
|
||||
@@ -493,8 +502,7 @@
|
||||
0 => L("SenderWizard_Step_Basics_Desc", "Starten Sie mit den Pflichtangaben für den Umschlag."),
|
||||
1 => L("SenderWizard_Step_Receivers_Desc", "Definieren Sie die Empfänger für den Signaturprozess."),
|
||||
2 => L("SenderWizard_Step_Settings_Desc", "Konfigurieren Sie Sicherheits-, Erinnerungs- und Ablaufoptionen."),
|
||||
3 => L("SenderWizard_Step_Message_Desc", "Fügen Sie optionale Nachricht und Einstellungen hinzu."),
|
||||
4 => L("SenderWizard_Step_Document_Desc", "Laden Sie das Dokument hoch und platzieren Sie Signaturfelder."),
|
||||
3 => L("SenderWizard_Step_Message_Desc", "Fuegen Sie optionale Nachricht hinzu und nutzen Sie rechts die letzte Upload-Moeglichkeit im Wizard."),
|
||||
_ => L("SenderWizard_Step_Basics_Desc", "Starten Sie mit den Pflichtangaben für den Umschlag.")
|
||||
};
|
||||
|
||||
@@ -539,6 +547,13 @@
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task OnReceiverSuggestionFilterChanged(string? value)
|
||||
{
|
||||
_receiverSuggestionFilterText = value ?? string.Empty;
|
||||
_validationMessage = null;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task OnEnvelopeTypeChanged(ChangeEventArgs args)
|
||||
{
|
||||
_validationMessage = null;
|
||||
@@ -590,7 +605,6 @@
|
||||
}
|
||||
|
||||
_receivers.Add(new CreateEnvelopeWizardReceiver(fullName, email));
|
||||
_receiverTags = _receivers.Select(x => x.EmailAddress).ToList();
|
||||
_receiverName = string.Empty;
|
||||
_receiverEmail = string.Empty;
|
||||
_validationMessage = null;
|
||||
@@ -603,17 +617,28 @@
|
||||
return Task.CompletedTask;
|
||||
|
||||
_receivers.Remove(receiver);
|
||||
_receiverTags = _receivers.Select(x => x.EmailAddress).ToList();
|
||||
_validationMessage = null;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task OnReceiverTagsChanged()
|
||||
private Task OnReceiverListNameChanged(int index, string? value)
|
||||
{
|
||||
var selectedEmails = _receiverTags.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
if (IsActionLocked || index < 0 || index >= _receivers.Count)
|
||||
return Task.CompletedTask;
|
||||
|
||||
_receivers.RemoveAll(x => !selectedEmails.Contains(x.EmailAddress));
|
||||
_receiverTags = _receivers.Select(x => x.EmailAddress).ToList();
|
||||
var current = _receivers[index];
|
||||
_receivers[index] = current with { FullName = value ?? string.Empty };
|
||||
_validationMessage = null;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task OnReceiverListEmailChanged(int index, string? value)
|
||||
{
|
||||
if (IsActionLocked || index < 0 || index >= _receivers.Count)
|
||||
return Task.CompletedTask;
|
||||
|
||||
var current = _receivers[index];
|
||||
_receivers[index] = current with { EmailAddress = value ?? string.Empty };
|
||||
_validationMessage = null;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
@@ -624,7 +649,10 @@
|
||||
return [];
|
||||
|
||||
var handler = new ReceiverSuggestionFilterHandler(_recentReceiverSuggestions);
|
||||
var filtered = handler.Filter(_receiverName, _receiverEmail);
|
||||
var filterText = _receiverSuggestionFilterText.Trim();
|
||||
var filtered = string.IsNullOrWhiteSpace(filterText)
|
||||
? _recentReceiverSuggestions
|
||||
: handler.Filter(filterText);
|
||||
|
||||
return filtered
|
||||
.GroupBy(x => x.EmailAddress, StringComparer.OrdinalIgnoreCase)
|
||||
@@ -664,21 +692,6 @@
|
||||
_documentFileName = file.Name;
|
||||
_documentSizeText = FormatSize(_documentBytes.Length);
|
||||
|
||||
_documentPreviewPages = [];
|
||||
try
|
||||
{
|
||||
_documentPreviewPages = (await PdfRenderService.RenderPagesAsync(_documentBytes))
|
||||
.OrderBy(x => x.PageNumber)
|
||||
.ToList();
|
||||
}
|
||||
catch
|
||||
{
|
||||
_documentPreviewPages = [];
|
||||
}
|
||||
|
||||
_documentPreviewPageCount = _documentPreviewPages.Count > 0
|
||||
? _documentPreviewPages.Count
|
||||
: EstimatePdfPageCount(_documentBytes) ?? 1;
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -714,10 +727,12 @@
|
||||
|
||||
if (!IsLastStep)
|
||||
{
|
||||
_completedSteps.Add(_currentStep);
|
||||
_currentStep++;
|
||||
|
||||
if (_currentStep == 1)
|
||||
await EnsureRecentSuggestionsLoadedAsync();
|
||||
|
||||
}
|
||||
}
|
||||
finally
|
||||
@@ -728,19 +743,6 @@
|
||||
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)
|
||||
@@ -864,14 +866,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyRecentSuggestion(RecentReceiverSuggestionDto suggestion)
|
||||
private async Task ApplyRecentSuggestionAndAddAsync(RecentReceiverSuggestionDto suggestion)
|
||||
{
|
||||
if (IsActionLocked)
|
||||
return;
|
||||
|
||||
_receiverName = suggestion.FullName;
|
||||
_receiverEmail = suggestion.EmailAddress;
|
||||
_validationMessage = null;
|
||||
await AddReceiver();
|
||||
}
|
||||
|
||||
private bool ValidateCurrentStep()
|
||||
@@ -915,14 +917,38 @@
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
for (var i = 0; i < _receivers.Count; i++)
|
||||
{
|
||||
var receiver = _receivers[i];
|
||||
var fullName = receiver.FullName.Trim();
|
||||
var email = receiver.EmailAddress.Trim();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(fullName))
|
||||
{
|
||||
_validationMessage = L("SenderEditor_Validation_FullNameRequired", "Bitte geben Sie einen Vor- und Nachnamen ein.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_currentStep == 4)
|
||||
if (string.IsNullOrWhiteSpace(email))
|
||||
{
|
||||
if (_documentBytes is null || _documentBytes.Length == 0)
|
||||
_validationMessage = L("SenderEditor_Validation_EmailRequired", "Bitte geben Sie eine E-Mail-Adresse ein.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!EmailValidator.IsValid(email))
|
||||
{
|
||||
_validationMessage = L("SenderEditor_Validation_UploadPdf", "Bitte laden Sie zuerst ein PDF-Dokument hoch.");
|
||||
_validationMessage = L("SenderEditor_Validation_EmailInvalid", "Bitte geben Sie eine gültige E-Mail-Adresse ein.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var duplicateEmailExists = _receivers
|
||||
.GroupBy(x => x.EmailAddress.Trim(), StringComparer.OrdinalIgnoreCase)
|
||||
.Any(group => group.Count() > 1);
|
||||
|
||||
if (duplicateEmailExists)
|
||||
{
|
||||
_validationMessage = L("SenderEditor_Validation_EmailDuplicate", "Diese E-Mail-Adresse wurde bereits hinzugefügt.");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -946,59 +972,29 @@
|
||||
return $"{bytes} B";
|
||||
}
|
||||
|
||||
private static int? EstimatePdfPageCount(byte[]? documentBytes)
|
||||
{
|
||||
if (documentBytes is null || documentBytes.Length == 0)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
var text = System.Text.Encoding.ASCII.GetString(documentBytes);
|
||||
const string marker = "/Type /Page";
|
||||
var count = 0;
|
||||
var index = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
index = text.IndexOf(marker, index, StringComparison.Ordinal);
|
||||
if (index < 0)
|
||||
break;
|
||||
|
||||
count++;
|
||||
index += marker.Length;
|
||||
}
|
||||
|
||||
return count > 0 ? count : null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Task OnDocumentPreviewEditorClickAsync(MouseEventArgs _)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task OnDocumentPreviewRemoveFieldAsync(int _)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task OnDocumentPreviewMoveFieldAsync(SenderPdfFieldMoveRequest _)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void OnTfaChanged(ChangeEventArgs args)
|
||||
{
|
||||
_validationMessage = null;
|
||||
|
||||
if (IsFesSelected)
|
||||
{
|
||||
_tfaEnabled = true;
|
||||
_useAccessCode = true;
|
||||
return;
|
||||
}
|
||||
|
||||
_tfaEnabled = ParseCheckboxValue(args);
|
||||
if (_tfaEnabled)
|
||||
_useAccessCode = true;
|
||||
}
|
||||
|
||||
private Task OnCertificationTypeChanged()
|
||||
{
|
||||
_validationMessage = null;
|
||||
NormalizeSettings();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void OnUseAccessCodeChanged(ChangeEventArgs args)
|
||||
{
|
||||
_validationMessage = null;
|
||||
@@ -1068,6 +1064,9 @@
|
||||
{
|
||||
_language = string.IsNullOrWhiteSpace(_language) ? "de-DE" : _language.Trim();
|
||||
|
||||
if (IsFesSelected)
|
||||
_tfaEnabled = true;
|
||||
|
||||
if (_tfaEnabled)
|
||||
_useAccessCode = true;
|
||||
|
||||
@@ -1120,6 +1119,7 @@
|
||||
private void ResetWizardState()
|
||||
{
|
||||
_currentStep = 0;
|
||||
_completedSteps.Clear();
|
||||
_validationMessage = null;
|
||||
|
||||
_title = string.Empty;
|
||||
@@ -1139,8 +1139,8 @@
|
||||
|
||||
_receiverName = string.Empty;
|
||||
_receiverEmail = string.Empty;
|
||||
_receiverSuggestionFilterText = string.Empty;
|
||||
_receivers.Clear();
|
||||
_receiverTags = [];
|
||||
_recentReceiverSuggestions.Clear();
|
||||
_isLoadingRecentSuggestions = false;
|
||||
_recentSuggestionsLoaded = false;
|
||||
@@ -1148,10 +1148,9 @@
|
||||
_documentBytes = null;
|
||||
_documentFileName = null;
|
||||
_documentSizeText = string.Empty;
|
||||
_documentPreviewPageCount = 0;
|
||||
_documentPreviewPages = [];
|
||||
|
||||
ApplyDefaultEnvelopeType();
|
||||
NormalizeSettings();
|
||||
}
|
||||
|
||||
private int? GetDefaultEnvelopeTypeId()
|
||||
|
||||
@@ -2,10 +2,24 @@
|
||||
|
||||
<div class="sender-pdf-dropzone @DropZoneClass">
|
||||
<div class="sender-pdf-dropzone__content">
|
||||
<div class="sender-pdf-dropzone__title">@CurrentActionText</div>
|
||||
@if (!string.IsNullOrWhiteSpace(DragDropHintText))
|
||||
@if (HasDocument)
|
||||
{
|
||||
<div class="sender-pdf-dropzone__hint">@DragDropHintText</div>
|
||||
@if (!string.IsNullOrWhiteSpace(DocumentName))
|
||||
{
|
||||
<div class="sender-pdf-dropzone__doc-name">@DocumentName</div>
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(DocumentSizeText))
|
||||
{
|
||||
<div class="sender-pdf-dropzone__doc-size">@DocumentSizeText</div>
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(UploadedHintText))
|
||||
{
|
||||
<div class="sender-pdf-dropzone__hint">@UploadedHintText</div>
|
||||
}
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(CurrentActionText))
|
||||
{
|
||||
<div class="sender-pdf-dropzone__hint">@CurrentActionText</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -38,6 +52,15 @@
|
||||
[Parameter]
|
||||
public string DragDropHintText { get; set; } = "Datei hierhin ziehen oder klicken";
|
||||
|
||||
[Parameter]
|
||||
public string? DocumentName { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string? DocumentSizeText { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string UploadedHintText { get; set; } = "Zum Ersetzen klicken oder eine Datei hierher ziehen.";
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<InputFileChangeEventArgs> FileSelected { get; set; }
|
||||
|
||||
@@ -53,6 +76,9 @@
|
||||
if (Disabled)
|
||||
return "sender-pdf-dropzone--disabled";
|
||||
|
||||
if (HasDocument)
|
||||
return "sender-pdf-dropzone--has-document";
|
||||
|
||||
return _isDragActive ? "sender-pdf-dropzone--drag-active" : string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,23 +4,19 @@ namespace EnvelopeGenerator.Server.Client.Components.Sender.ReceiverSuggestionFi
|
||||
|
||||
public class ReceiverSuggestionFilterHandler(IEnumerable<RecentReceiverSuggestionDto> suggestions)
|
||||
{
|
||||
private readonly IReadOnlyList<Entry> _entries = suggestions
|
||||
private readonly IReadOnlyList<Entry> _entries = [.. suggestions
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x.FullName) && !string.IsNullOrWhiteSpace(x.EmailAddress))
|
||||
.Select(x => new Entry(new ReceiverFilterCriteria(x.FullName, x.EmailAddress), x))
|
||||
.ToList();
|
||||
.Select(x => new Entry(new ReceiverFilterCriteria(x.FullName, x.EmailAddress), x))];
|
||||
|
||||
private readonly record struct Entry(ReceiverFilterCriteria Criteria, RecentReceiverSuggestionDto Suggestion);
|
||||
|
||||
public IEnumerable<RecentReceiverSuggestionDto> Filter(ReceiverFilterCriteria criteria)
|
||||
public IEnumerable<RecentReceiverSuggestionDto> Filter(string query)
|
||||
{
|
||||
var normalizedQuery = new ReceiverFilterCriteria(query, query);
|
||||
return _entries
|
||||
.Where(x => x.Criteria.Matches(criteria))
|
||||
.Where(x =>
|
||||
x.Criteria.FullName.Contains(normalizedQuery.FullName, StringComparison.Ordinal)
|
||||
|| x.Criteria.EmailAddress.Contains(normalizedQuery.EmailAddress, StringComparison.Ordinal))
|
||||
.Select(x => x.Suggestion);
|
||||
}
|
||||
|
||||
public IEnumerable<RecentReceiverSuggestionDto> Filter(string fullName, string emailAddress)
|
||||
{
|
||||
var criteria = new ReceiverFilterCriteria(fullName, emailAddress);
|
||||
return Filter(criteria);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,12 +289,13 @@
|
||||
@if (_pdfLoaded && _signatureFields.Count > 0)
|
||||
{
|
||||
<button class="pdf-toolbar__btn pdf-toolbar__btn--reset"
|
||||
@onclick="ClearAllFieldsAsync"
|
||||
@onclick='() => _clearFieldsConfirmVisible = true'
|
||||
title='@L("SenderEditor_RemoveAllSignatureFields", "Alle Signaturfelder entfernen")'>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z" />
|
||||
<path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1 0-2h3a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1h3a1 1 0 0 1 1 1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3h11V2h-11v1z" />
|
||||
</svg>
|
||||
<span class="pdf-toolbar__btn-text"> @L("SenderEditor_SignatureField", "Signaturfeld")</span>
|
||||
</button>
|
||||
}
|
||||
|
||||
@@ -317,7 +318,7 @@
|
||||
</button>
|
||||
|
||||
<button class="pdf-toolbar__btn pdf-toolbar__btn--success"
|
||||
@onclick="SendAsync"
|
||||
@onclick="RequestSendAsync"
|
||||
disabled="@_isSaving"
|
||||
title='@L("SendEnvelope", "Umschlag senden")'>
|
||||
@if (_isSaving)
|
||||
@@ -506,6 +507,69 @@
|
||||
</FooterContentTemplate>
|
||||
</DxPopup>
|
||||
|
||||
@* ── Clear all fields confirmation popup ── *@
|
||||
<DxPopup @bind-Visible="_clearFieldsConfirmVisible"
|
||||
Width="400px"
|
||||
MaxWidth="95vw"
|
||||
ShowHeader="false"
|
||||
ShowFooter="false"
|
||||
CloseOnEscape="true"
|
||||
CloseOnOutsideClick="true">
|
||||
<BodyContentTemplate>
|
||||
<div class="sender-confirm-popup">
|
||||
<p class="sender-confirm-popup__message">@L("SenderEditor_RemoveAllSignatureFieldsConfirm", "Diese Aktion entfernt alle platzierten Signaturfelder unwiderruflich. Fortfahren?")</p>
|
||||
<div class="sender-confirm-popup__actions">
|
||||
<DxButton Text='@L("Cancel", "Abbrechen")'
|
||||
RenderStyle="ButtonRenderStyle.Secondary"
|
||||
Click='() => _clearFieldsConfirmVisible = false' />
|
||||
<DxButton Text='@L("Delete", "Löschen")'
|
||||
RenderStyle="ButtonRenderStyle.Danger"
|
||||
Click="ClearAllFieldsAsync" />
|
||||
</div>
|
||||
</div>
|
||||
</BodyContentTemplate>
|
||||
</DxPopup>
|
||||
|
||||
@* ── Send confirmation popup ── *@
|
||||
<DxPopup @bind-Visible="_sendConfirmVisible"
|
||||
HeaderText='@L("SenderEditor_SendConfirm_Title", "Umschlag jetzt versenden?")'
|
||||
SizeMode="SizeMode.Medium"
|
||||
Width="460px"
|
||||
MaxWidth="95vw"
|
||||
ShowFooter="@(!_isSendConfirmSubmitting)"
|
||||
ShowCloseButton="@(!_isSendConfirmSubmitting)"
|
||||
CloseOnEscape="@(!_isSendConfirmSubmitting)"
|
||||
CloseOnOutsideClick="false">
|
||||
<BodyContentTemplate>
|
||||
@if (_isSendConfirmSubmitting)
|
||||
{
|
||||
<div class="sender-send-progress">
|
||||
<DxWaitIndicator Visible="true" />
|
||||
<div>
|
||||
<p class="sender-send-progress__title">@L("SenderEditor_SendConfirm_InProgress", "Umschlag wird versendet...")</p>
|
||||
<p class="sender-send-progress__hint">@L("SenderEditor_SendConfirm_InProgressHint", "Bitte warten Sie einen Moment. Der Vorgang wird abgeschlossen.")</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="sender-confirm-popup sender-confirm-popup--send">
|
||||
<p class="sender-confirm-popup__note">
|
||||
@L("SenderEditor_SendConfirm_Message", "Wollen Sie den Umschlag jetzt versenden? Hinweis: Nach der Übergabe kann der Vorgang nicht mehr bearbeitet werden.")
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
</BodyContentTemplate>
|
||||
<FooterContentTemplate>
|
||||
<DxButton Text='@L("No", "Nein")'
|
||||
RenderStyle="ButtonRenderStyle.Secondary"
|
||||
Click='() => _sendConfirmVisible = false' />
|
||||
<DxButton Text='@L("SenderEditor_SendConfirm_Action", "Ja, versenden")'
|
||||
RenderStyle="ButtonRenderStyle.Primary"
|
||||
Click="ConfirmSendAsync" />
|
||||
</FooterContentTemplate>
|
||||
</DxPopup>
|
||||
|
||||
@* ── Save result popup (success + error) ── *@
|
||||
<DxPopup @bind-Visible="_savePopupVisible"
|
||||
HeaderText='@(_saveErrorMessage is null ? _saveSuccessHeader : L("Error", "Fehler"))'
|
||||
@@ -620,6 +684,9 @@
|
||||
// ── Save state ──
|
||||
bool _isSaving = false;
|
||||
bool _savePopupVisible = false;
|
||||
bool _clearFieldsConfirmVisible = false;
|
||||
bool _sendConfirmVisible = false;
|
||||
bool _isSendConfirmSubmitting = false;
|
||||
string? _saveErrorMessage = null;
|
||||
string _saveSuccessHeader = "Erfolg";
|
||||
string _saveSuccessTitle = "Vorgang erfolgreich abgeschlossen.";
|
||||
@@ -1060,6 +1127,7 @@
|
||||
// ── Clear all fields ──
|
||||
async Task ClearAllFieldsAsync()
|
||||
{
|
||||
_clearFieldsConfirmVisible = false;
|
||||
_signatureFields.Clear();
|
||||
_pendingReceiverForPlacement = null;
|
||||
await Task.CompletedTask;
|
||||
@@ -1215,13 +1283,7 @@
|
||||
|
||||
Logger.LogInformation("[SenderEditor] Envelope sent from draft. Id={Id} Status={Status}", sendResult.Id, sendResult.Status);
|
||||
|
||||
// Success — show popup; GoToDashboard clears cache and navigates
|
||||
_saveErrorMessage = null;
|
||||
_saveSuccessHeader = L("SenderEditor_Sent_Header", "Umschlag gesendet");
|
||||
_saveSuccessTitle = L("SenderEditor_Sent_Title", "Umschlag wurde erfolgreich gesendet.");
|
||||
_saveSuccessBody = L("SenderEditor_Sent_Body", "Der bestehende Entwurf wurde versendet.");
|
||||
_saveNavigateToDashboard = true;
|
||||
_savePopupVisible = true;
|
||||
NavigationManager.NavigateTo("/sender", forceLoad: false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -1235,6 +1297,38 @@
|
||||
}
|
||||
}
|
||||
|
||||
Task RequestSendAsync()
|
||||
{
|
||||
if (_isSaving)
|
||||
return Task.CompletedTask;
|
||||
|
||||
_sendConfirmVisible = true;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
async Task ConfirmSendAsync()
|
||||
{
|
||||
if (_isSendConfirmSubmitting)
|
||||
return;
|
||||
|
||||
_isSendConfirmSubmitting = true;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
try
|
||||
{
|
||||
await SendAsync();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isSendConfirmSubmitting = false;
|
||||
|
||||
if (_savePopupVisible)
|
||||
_sendConfirmVisible = false;
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
|
||||
CreateEnvelopeCommand BuildCreateEnvelopeCommand(bool send)
|
||||
{
|
||||
NormalizeSettings();
|
||||
|
||||
@@ -32,6 +32,11 @@
|
||||
<link href="@AppVersion.GetVersionedUrl("css/envelope-viewer.css")" rel="stylesheet" />
|
||||
<link href="@AppVersion.GetVersionedUrl("css/sender-page.css")" rel="stylesheet" />
|
||||
|
||||
<MudThemeProvider />
|
||||
<MudPopoverProvider />
|
||||
<MudDialogProvider />
|
||||
<MudSnackbarProvider />
|
||||
|
||||
<div class="sender-dashboard-layout">
|
||||
<div class="sender-action-bar">
|
||||
<div class="sender-action-bar__inner">
|
||||
@@ -108,12 +113,12 @@
|
||||
}
|
||||
</button>
|
||||
|
||||
<button class="sender-btn" @onclick="ShowPreview" disabled="@(_selectedEnvelope == null)" title="@L("Sender_Preview_Title", "Ausgewählten Umschlag als PDF anzeigen")">
|
||||
<button class="sender-btn" @onclick="ShowPreview" disabled="@(_selectedEnvelope == null)" title="@L("Sender_ShowOriginalDocument_Title", "Originaldokument anzeigen")">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M10.5 8a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0z"/>
|
||||
<path d="M0 8s3-5.5 8-5.5S16 8 16 8s-3 5.5-8 5.5S0 8 0 8zm8 3.5a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7z"/>
|
||||
</svg>
|
||||
@L("Preview", "Vorschau")
|
||||
@L("Sender_ShowOriginalDocument", "Original Dokument anzeigen")
|
||||
</button>
|
||||
|
||||
<div class="sender-export-group" title="@L("Sender_ExportView_Title", "Export der aktuellen Tabellenansicht")">
|
||||
@@ -237,7 +242,10 @@
|
||||
RowDoubleClick="OnRowDoubleClick"
|
||||
CustomizeElement="OnCustomizeElement"
|
||||
LayoutAutoLoading="OnGridLayoutAutoLoading"
|
||||
LayoutAutoSaving="OnGridLayoutAutoSaving">
|
||||
LayoutAutoSaving="OnGridLayoutAutoSaving"
|
||||
HighlightRowOnHover="true"
|
||||
AutoCollapseDetailRow=true
|
||||
DetailExpandButtonDisplayMode="GridDetailExpandButtonDisplayMode.Auto">
|
||||
<Columns>
|
||||
<DxGridDataColumn FieldName="Id" Caption="ID"
|
||||
DisplayFormat="0"
|
||||
@@ -310,6 +318,12 @@
|
||||
<h6 style="font-weight: 600; color: #374151; margin-bottom: 0.75rem;">@L("Receiver", "Empfänger")</h6>
|
||||
@{
|
||||
var envelope = detailContext.DataItem as EnvelopeDto;
|
||||
if(envelope is not null && _grid is not null)
|
||||
{
|
||||
_grid.SelectDataItem(envelope);
|
||||
// _grid!.SelectedDataItem = envelope!;
|
||||
}
|
||||
|
||||
if (envelope?.EnvelopeReceivers?.Any() == true)
|
||||
{
|
||||
<div style="display: flex; flex-direction: column; gap: 0.5rem;">
|
||||
@@ -440,7 +454,7 @@
|
||||
|
||||
@* ── PDF Vorschau Popup ────────────────────────────────────────────────── *@
|
||||
<DxPopup @bind-Visible="@_previewVisible"
|
||||
HeaderText="@($"{(_activeTab == "completed" && _selectedEnvelope?.Status == EnvelopeStatus.EnvelopeCompletelySigned ? L("SignedPdf", "Signiertes PDF") : L("Preview", "Vorschau"))} — {_selectedEnvelope?.Title} (#{_selectedEnvelope?.Id})")"
|
||||
HeaderText="@($"{(_activeTab == "completed" && _selectedEnvelope?.Status == EnvelopeStatus.EnvelopeCompletelySigned ? L("SignedPdf", "Signiertes PDF") : L("Sender_OriginalDocument", "Originaldokument"))} — {_selectedEnvelope?.Title} (#{_selectedEnvelope?.Id})")"
|
||||
Width="80vw"
|
||||
MaxWidth="80vw"
|
||||
Height="90vh"
|
||||
@@ -489,6 +503,22 @@
|
||||
else if (_previewPdfBytes is not null && _previewViewerMounted)
|
||||
{
|
||||
<div class="sender-preview-viewer-wrap">
|
||||
@if (_showPreviewHint)
|
||||
{
|
||||
<div class="sender-preview-info" style="display:flex;align-items:center;justify-content:space-between;gap:0.5rem;padding:0.625rem 0.75rem;margin:0 0 0.5rem 0;background:#eff6ff;border:1px solid #bfdbfe;border-radius:8px;color:#1e3a8a;font-size:0.85rem;">
|
||||
<span>@L("Sender_OriginalDocument_NoSignatureFieldsHint", "Hinweis: In dieser Ansicht werden keine Unterschriftsfelder angezeigt.")</span>
|
||||
<button type="button"
|
||||
class="sender-preview-info__close"
|
||||
@onclick="DismissPreviewHint"
|
||||
title="@L("Close", "Schliessen")"
|
||||
aria-label="@L("Close", "Schliessen")"
|
||||
style="display:inline-flex;align-items:center;justify-content:center;padding:0.125rem;border:none;background:transparent;box-shadow:none;appearance:none;cursor:pointer;color:#1e3a8a;line-height:1;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true" focusable="false">
|
||||
<path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.75.75 0 1 1 1.06 1.06L9.06 8l3.22 3.22a.75.75 0 1 1-1.06 1.06L8 9.06l-3.22 3.22a.75.75 0 1 1-1.06-1.06L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
<SenderPdfImageEditor IsPlacementMode="false"
|
||||
PageCount="@_previewPageCount"
|
||||
Pages="@_previewPagePreviews"
|
||||
@@ -577,6 +607,9 @@
|
||||
private string _previewFileName = "document.pdf";
|
||||
private string? _previewErrorMessage = null;
|
||||
private readonly IReadOnlyList<SenderPdfFieldOverlay> _previewFieldOverlays = [];
|
||||
private bool _showPreviewHint = false;
|
||||
private int _previewHintVersion = 0;
|
||||
private const int PreviewHintAutoHideMs = 4000;
|
||||
|
||||
private string? PreviewPdfDataUrl => _previewPdfBytes is { Length: > 0 }
|
||||
? $"data:application/pdf;base64,{Convert.ToBase64String(_previewPdfBytes)}"
|
||||
@@ -613,22 +646,29 @@
|
||||
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.
|
||||
// Fan out all independent post-auth calls in parallel to reduce initial load time.
|
||||
var themeTask = Task.Run(async () =>
|
||||
{
|
||||
try { await ThemeService.LoadFromServerAsync(); }
|
||||
catch { /* swallow — default theme remains active */ }
|
||||
});
|
||||
|
||||
_canUseReceiverEnvelopeUrl = await EnvelopeReceiverService.GetReceiverEnvelopeUrlFeatureEnabledAsync();
|
||||
var featureTask = EnvelopeReceiverService.GetReceiverEnvelopeUrlFeatureEnabledAsync();
|
||||
var logosTask = CustomImagesService.GetAsync();
|
||||
var typesTask = EnvelopeTypeService.GetAsync();
|
||||
var envelopesTask = EnvelopeService.GetAsync(includeDocAndDocRes: false);
|
||||
|
||||
// Load logo config — authenticated, so this should succeed
|
||||
var logos = await CustomImagesService.GetAsync();
|
||||
await Task.WhenAll(themeTask, featureTask, logosTask, typesTask, envelopesTask);
|
||||
|
||||
_canUseReceiverEnvelopeUrl = await featureTask;
|
||||
|
||||
var logos = await logosTask;
|
||||
_appLogo = logos.App;
|
||||
_companyLogo = logos.Company;
|
||||
|
||||
try
|
||||
{
|
||||
_envelopeTypes = (await EnvelopeTypeService.GetAsync()).ToList();
|
||||
_envelopeTypes = (await typesTask).ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -636,7 +676,8 @@
|
||||
_envelopeTypes = [];
|
||||
}
|
||||
|
||||
await LoadEnvelopesAsync();
|
||||
// Use already-fetched envelope data instead of a second HTTP call.
|
||||
await ApplyEnvelopeResultAsync(envelopesTask);
|
||||
}
|
||||
|
||||
async Task LoadEnvelopesAsync()
|
||||
@@ -644,10 +685,15 @@
|
||||
_isLoading = true;
|
||||
_errorMessage = null;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
await ApplyEnvelopeResultAsync(EnvelopeService.GetAsync(includeDocAndDocRes: false));
|
||||
}
|
||||
|
||||
async Task ApplyEnvelopeResultAsync(Task<IEnumerable<EnvelopeDto>?> envelopesTask)
|
||||
{
|
||||
_isLoading = true;
|
||||
try
|
||||
{
|
||||
_allEnvelopes = await EnvelopeService.GetAsync(includeDocAndDocRes: false) ?? [];
|
||||
_allEnvelopes = (await envelopesTask) ?? [];
|
||||
|
||||
// Split into active and completed based on effective status
|
||||
// (history-aware: rejected envelopes should not stay in active tab).
|
||||
@@ -1061,13 +1107,19 @@
|
||||
var clicked = e.Grid.GetDataItem(e.VisibleIndex) as EnvelopeDto;
|
||||
if (clicked == null) return;
|
||||
|
||||
if (_selectedEnvelope?.Id == clicked.Id)
|
||||
if (e.Grid.IsDetailRowExpanded(e.VisibleIndex))
|
||||
{
|
||||
// Same row clicked again → deselect
|
||||
// Already expanded → collapse and deselect
|
||||
e.Grid.CollapseDetailRow(e.VisibleIndex);
|
||||
_selectedEnvelope = null;
|
||||
e.Grid.DeselectDataItem(clicked);
|
||||
}
|
||||
// Different row: @bind-SelectedDataItem already updated _selectedEnvelope
|
||||
else
|
||||
{
|
||||
// Collapsed → expand and select
|
||||
e.Grid.SelectDataItem(clicked);
|
||||
e.Grid.ExpandDetailRow(e.VisibleIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// Double-click: select the row and open preview popup
|
||||
@@ -1132,6 +1184,7 @@
|
||||
|
||||
// PDF ready — mount the viewer now
|
||||
_previewViewerMounted = true;
|
||||
ShowPreviewHint();
|
||||
}
|
||||
catch (HttpRequestException ex) when ((int?)ex.StatusCode == 404)
|
||||
{
|
||||
@@ -1156,6 +1209,9 @@
|
||||
|
||||
void OnPreviewClosed()
|
||||
{
|
||||
_previewHintVersion++;
|
||||
_showPreviewHint = false;
|
||||
|
||||
// Unmount the viewer first — prevent DevExpress async render from
|
||||
// accessing the already-disposed byte array
|
||||
_previewViewerMounted = false;
|
||||
@@ -1187,6 +1243,28 @@
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
void DismissPreviewHint()
|
||||
{
|
||||
_previewHintVersion++;
|
||||
_showPreviewHint = false;
|
||||
}
|
||||
|
||||
void ShowPreviewHint()
|
||||
{
|
||||
_showPreviewHint = true;
|
||||
var currentVersion = ++_previewHintVersion;
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(PreviewHintAutoHideMs);
|
||||
if (currentVersion != _previewHintVersion)
|
||||
return;
|
||||
|
||||
_showPreviewHint = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
});
|
||||
}
|
||||
|
||||
static int? EstimatePdfPageCount(byte[]? documentBytes)
|
||||
{
|
||||
if (documentBytes is null || documentBytes.Length == 0)
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
@using EnvelopeGenerator.Server.Client.Models
|
||||
@using EnvelopeGenerator.Server.Client.Options
|
||||
@using EnvelopeGenerator.Server.Client.Components.PdfEditor
|
||||
@using MudBlazor
|
||||
@using DevExpress.Blazor
|
||||
@using DevExpress.Blazor.PdfViewer
|
||||
@using DevExpress.Blazor.Reporting
|
||||
|
||||
@@ -2085,3 +2085,79 @@ body.resizing {
|
||||
[data-sf-theme="blazing-dark"] .pdf-text-layer ::-moz-selection {
|
||||
background: rgba(99, 102, 241, 0.35);
|
||||
}
|
||||
|
||||
/* ── Confirm popup (headerless, footerless) ── */
|
||||
.sender-confirm-popup {
|
||||
padding: 0.25rem 0.25rem 0.5rem;
|
||||
}
|
||||
|
||||
.sender-confirm-popup__message {
|
||||
margin: 0 0 1.25rem;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.5;
|
||||
color: var(--bs-body-color, #e2e8f0);
|
||||
}
|
||||
|
||||
.sender-confirm-popup__actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.sender-confirm-popup--send {
|
||||
padding: 0.5rem 0.25rem;
|
||||
}
|
||||
|
||||
.sender-confirm-popup--send .sender-confirm-popup__note {
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sender-confirm-popup__title {
|
||||
margin: 0 0 0.4rem;
|
||||
font-size: 1.02rem;
|
||||
font-weight: 600;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.sender-confirm-popup__note {
|
||||
margin: 0 0 1rem;
|
||||
font-size: 0.84rem;
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
.sender-send-progress {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 0.5rem 0.25rem;
|
||||
}
|
||||
|
||||
.sender-send-progress__spinner {
|
||||
width: 1.15rem;
|
||||
height: 1.15rem;
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.sender-send-progress__title {
|
||||
margin: 0 0 0.2rem;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.sender-send-progress__hint {
|
||||
margin: 0;
|
||||
font-size: 0.83rem;
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
[data-sf-theme="blazing-dark"] .sender-confirm-popup__title,
|
||||
[data-sf-theme="blazing-dark"] .sender-send-progress__title {
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
[data-sf-theme="blazing-dark"] .sender-confirm-popup__note,
|
||||
[data-sf-theme="blazing-dark"] .sender-send-progress__hint {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
@@ -1103,9 +1103,90 @@
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.sender-create-wizard__body-layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(240px, 280px);
|
||||
gap: 0.85rem;
|
||||
align-items: stretch;
|
||||
transition: grid-template-columns 220ms ease 0.2s, gap 220ms ease 0.2s;
|
||||
}
|
||||
|
||||
.sender-create-wizard-popup.dxbl-modal-root,
|
||||
.sender-create-wizard-popup .dxbl-modal-root,
|
||||
.sender-create-wizard-popup .dxbl-modal,
|
||||
.sender-create-wizard-popup .dxbl-modal-dialog,
|
||||
.sender-create-wizard-popup .dxbl-modal-content,
|
||||
.sender-create-wizard-popup .dxbl-popup,
|
||||
.sender-create-wizard-popup.dxbl-popup {
|
||||
height: 85vh !important;
|
||||
max-height: 85vh !important;
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
}
|
||||
|
||||
.sender-create-wizard-popup .dxbl-modal-body,
|
||||
.sender-create-wizard-popup .dxbl-popup-content {
|
||||
max-height: min(78vh, 760px);
|
||||
overflow: auto;
|
||||
flex: 1 1 auto !important;
|
||||
height: auto !important;
|
||||
min-height: 0 !important;
|
||||
max-height: none !important;
|
||||
overflow: hidden !important;
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
}
|
||||
|
||||
.sender-create-wizard-popup .dxbl-popup-content > .sender-create-wizard,
|
||||
.sender-create-wizard-popup .sender-create-wizard,
|
||||
.sender-create-wizard-popup .sender-create-wizard__body-layout {
|
||||
flex: 1 1 auto !important;
|
||||
height: auto !important;
|
||||
min-height: 0 !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
/* Scroll only inside the stepper content, so the body never resizes. */
|
||||
.sender-create-wizard__stepper-shell {
|
||||
min-height: 0 !important;
|
||||
overflow: hidden !important;
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
}
|
||||
|
||||
.sender-create-wizard__stepper {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
min-height: 0 !important;
|
||||
height: 100% !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
/* MudStepper internal wrappers must pass height down without scrolling. */
|
||||
.sender-create-wizard__stepper .mud-stepper-content,
|
||||
.sender-create-wizard__stepper .mud-stepper-nav-content,
|
||||
.sender-create-wizard__stepper .mud-stepper-content-wrapper {
|
||||
flex: 1 1 auto !important;
|
||||
min-height: 0 !important;
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
.sender-create-wizard__step-content,
|
||||
.sender-create-wizard__step-layout {
|
||||
flex: 1 1 auto !important;
|
||||
min-height: 0 !important;
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
}
|
||||
|
||||
/* The actual scroll area for every step, mirroring the receiver list behavior. */
|
||||
.sender-create-wizard__step-main {
|
||||
flex: 1 1 auto !important;
|
||||
min-height: 0 !important;
|
||||
overflow-y: auto !important;
|
||||
overflow-x: hidden !important;
|
||||
scrollbar-gutter: stable;
|
||||
padding-right: 0.2rem;
|
||||
}
|
||||
|
||||
.sender-create-wizard-popup .dxbl-popup-footer {
|
||||
@@ -1121,6 +1202,7 @@
|
||||
border-radius: 10px;
|
||||
background: rgba(126, 34, 206, 0.04);
|
||||
padding: 0.75rem;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.sender-create-wizard__stepper {
|
||||
@@ -1137,7 +1219,7 @@
|
||||
}
|
||||
|
||||
.sender-create-wizard__stepper .mud-step-label {
|
||||
color: #1f2937;
|
||||
color: #334155;
|
||||
font-size: 0.86rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
@@ -1153,22 +1235,53 @@
|
||||
.sender-create-wizard__stepper .mud-step-icon {
|
||||
color: #64748b;
|
||||
background: #f8fafc;
|
||||
border: 1px solid rgba(126, 34, 206, 0.25);
|
||||
border: 1px solid #cbd5e1;
|
||||
}
|
||||
|
||||
.sender-create-wizard__stepper .mud-step-completed .mud-step-icon,
|
||||
.sender-create-wizard__stepper .mud-step-current .mud-step-icon {
|
||||
color: #ffffff;
|
||||
border-color: transparent;
|
||||
.sender-create-wizard__stepper .mud-step:not(.mud-step-completed) .mud-step-icon {
|
||||
position: relative;
|
||||
color: transparent;
|
||||
background: #e2e8f0;
|
||||
border-color: #94a3b8;
|
||||
}
|
||||
|
||||
.sender-create-wizard__stepper .mud-step:not(.mud-step-completed) .mud-step-icon::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 0.55rem;
|
||||
height: 0.55rem;
|
||||
border-radius: 50%;
|
||||
border: 2px solid #64748b;
|
||||
background: #64748b;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.sender-create-wizard__stepper .mud-step-current:not(.mud-step-completed) .mud-step-icon::before {
|
||||
border-color: #0e7490;
|
||||
background: #22d3ee;
|
||||
}
|
||||
|
||||
.sender-create-wizard__stepper .mud-step-current .mud-step-label {
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.sender-create-wizard__stepper .mud-step-current .mud-step-icon {
|
||||
background: linear-gradient(135deg, #7e22ce 0%, #2a5298 100%);
|
||||
box-shadow: 0 0 0 3px rgba(126, 34, 206, 0.15);
|
||||
box-shadow: 0 0 0 3px rgba(14, 116, 144, 0.2);
|
||||
}
|
||||
|
||||
.sender-create-wizard__stepper .mud-step-completed .mud-step-icon {
|
||||
background: #2a5298;
|
||||
box-shadow: 0 0 0 2px rgba(22, 163, 74, 0.2);
|
||||
}
|
||||
|
||||
.sender-create-wizard__stepper .mud-step-completed .mud-step-label {
|
||||
color: #14532d;
|
||||
}
|
||||
|
||||
.sender-create-wizard__stepper .mud-step-completed .mud-step-content,
|
||||
.sender-create-wizard__stepper .mud-step-current .mud-step-content {
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
.sender-create-wizard__stepper-description {
|
||||
@@ -1183,6 +1296,33 @@
|
||||
margin-top: 0.7rem;
|
||||
}
|
||||
|
||||
.sender-create-wizard__step-layout {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.sender-create-wizard__step-main {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.sender-create-wizard__wizard-side {
|
||||
min-width: 0;
|
||||
max-width: 280px;
|
||||
border: 1px solid rgba(126, 34, 206, 0.12);
|
||||
border-radius: 10px;
|
||||
background: rgba(126, 34, 206, 0.04);
|
||||
padding: 0.75rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: stretch;
|
||||
opacity: 1;
|
||||
transition: opacity 220ms ease 0.2s, transform 220ms ease 0.2s;
|
||||
}
|
||||
|
||||
.sender-create-wizard__wizard-side .sender-pdf-dropzone {
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.sender-create-wizard__meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -1296,6 +1436,25 @@
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
.sender-create-wizard__label-row {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.3rem;
|
||||
}
|
||||
|
||||
.sender-create-wizard__help-icon {
|
||||
width: 1.2rem;
|
||||
height: 1.2rem;
|
||||
padding: 0;
|
||||
min-width: 1.2rem;
|
||||
min-height: 1.2rem;
|
||||
}
|
||||
|
||||
.sender-create-wizard__help-icon .mud-icon-root {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.sender-create-wizard__required {
|
||||
color: #dc2626;
|
||||
}
|
||||
@@ -1452,6 +1611,31 @@
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.sender-create-wizard__receiver-existing-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
padding-right: 0.2rem;
|
||||
}
|
||||
|
||||
.sender-create-wizard__receiver-existing-list > .sender-create-wizard__receiver-row--new {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.sender-create-wizard__receiver-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.55rem;
|
||||
}
|
||||
|
||||
.sender-create-wizard__receiver-fields-stack {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
.sender-create-wizard__receiver-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -1463,6 +1647,11 @@
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.sender-create-wizard__receiver-item-fields {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.sender-create-wizard__receiver-name {
|
||||
font-size: 0.86rem;
|
||||
font-weight: 700;
|
||||
@@ -1484,6 +1673,41 @@
|
||||
padding: 0.25rem 0.55rem;
|
||||
}
|
||||
|
||||
.sender-create-wizard__receiver-remove-btn {
|
||||
flex: 0 0 auto;
|
||||
width: auto;
|
||||
height: auto;
|
||||
align-self: stretch;
|
||||
aspect-ratio: 1 / 1;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: transparent;
|
||||
color: #b91c1c;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.sender-create-wizard__receiver-remove-btn:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.sender-create-wizard__receiver-add-inline-btn {
|
||||
flex: 0 0 auto;
|
||||
width: auto;
|
||||
height: auto;
|
||||
align-self: stretch;
|
||||
aspect-ratio: 1 / 1;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: transparent;
|
||||
color: #0f766e;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.sender-create-wizard__upload-stage {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -1539,6 +1763,11 @@
|
||||
box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.16);
|
||||
}
|
||||
|
||||
.sender-pdf-dropzone--has-document {
|
||||
border-color: rgba(16, 185, 129, 0.55);
|
||||
background: linear-gradient(180deg, rgba(16, 185, 129, 0.16), rgba(16, 185, 129, 0.07));
|
||||
}
|
||||
|
||||
.sender-pdf-dropzone--disabled {
|
||||
opacity: 0.55;
|
||||
cursor: not-allowed;
|
||||
@@ -1561,6 +1790,18 @@
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.sender-pdf-dropzone__doc-name {
|
||||
font-size: 0.82rem;
|
||||
font-weight: 700;
|
||||
color: #065f46;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.sender-pdf-dropzone__doc-size {
|
||||
font-size: 0.76rem;
|
||||
color: #047857;
|
||||
}
|
||||
|
||||
.sender-pdf-dropzone__input {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
@@ -1625,8 +1866,8 @@
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.sender-create-wizard-popup .dxbl-popup {
|
||||
width: 96vw !important;
|
||||
max-width: 96vw !important;
|
||||
width: 90vw !important;
|
||||
max-width: 90vw !important;
|
||||
}
|
||||
|
||||
.sender-create-wizard__progress {
|
||||
@@ -1650,10 +1891,41 @@
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.sender-create-wizard__receiver-row {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.sender-create-wizard__receiver-existing-list {
|
||||
max-height: min(20vh, 180px);
|
||||
}
|
||||
|
||||
.sender-create-wizard__receiver-fields-stack {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.sender-create-wizard__receiver-remove {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sender-create-wizard__body-layout {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 0.7rem;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.sender-create-wizard__wizard-side {
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.sender-create-wizard__receiver-remove-btn {
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.sender-create-wizard__receiver-add-inline-btn {
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.sender-pdf-dropzone {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user