Refactor SenderEditorPage for modernized UI design

Replaced inline styles with reusable CSS classes to improve maintainability, readability, and scalability. Updated the layout and styling of key components, including the action bar, title and message inputs, PDF metadata display, and buttons. Introduced a `DxAccordion` for envelope settings and redesigned the empty state and save result dialog. Added dynamic styling using CSS variables and centralized styles in `envelope-viewer.css`. Removed redundant inline styles and improved overall UI consistency.
This commit is contained in:
2026-10-02 14:59:43 +02:00
parent ae3264253d
commit 54d16eb3b8
2 changed files with 511 additions and 173 deletions

View File

@@ -34,64 +34,52 @@
@* ── Action Bar ── *@ @* ── Action Bar ── *@
<div class="envelope-action-bar"> <div class="envelope-action-bar">
<div class="envelope-action-bar__inner" <div class="envelope-action-bar__inner sender-editor-action-inner">
style="flex-direction: row; align-items: center; padding: 0.35rem 1.5rem; gap: 0.75rem;">
@* Left: Title + meta inputs + receivers *@ @* Left: Title + meta inputs + receivers *@
<div style="flex: 1; min-width: 0; display: flex; flex-direction: column; align-items: stretch; gap: 0.5rem;"> <div class="sender-editor-main-column">
@* ── Title & Message inputs ── *@ @* ── Title & Message inputs ── *@
<div style="display: flex; align-items: flex-start; gap: 0.5rem; flex-wrap: wrap;"> <div class="sender-editor-meta-row">
@* Titel *@ @* Titel *@
<div style="display: flex; flex-direction: column; gap: 0.2rem; flex: 1 1 180px; min-width: 140px;"> <div class="sender-editor-input-field sender-editor-input-field--title">
<label style="font-size: 0.65rem; font-weight: 600; color: #6b7280; <label class="sender-editor-label">
text-transform: uppercase; letter-spacing: 0.05em;"> @L("Title", "Titel") <span class="sender-editor-required-asterisk">*</span>
@L("Title", "Titel") <span style="color:#dc2626;">*</span>
</label> </label>
<input type="text" <input type="text"
value="@_envelopeTitle" value="@_envelopeTitle"
@oninput="e => _envelopeTitle = e.Value?.ToString() ?? string.Empty" @oninput="e => _envelopeTitle = e.Value?.ToString() ?? string.Empty"
placeholder='@L("SenderEditor_Title_Placeholder", "z.B. Arbeitsvertrag 2025")' placeholder='@L("SenderEditor_Title_Placeholder", "z.B. Arbeitsvertrag 2025")'
maxlength="120" maxlength="120"
style="font-size: 0.78rem; font-weight: 500; color: #1f2937; class="sender-editor-text-input @(_titleTouched && string.IsNullOrWhiteSpace(_envelopeTitle) ? "sender-editor-text-input--invalid" : string.Empty)" />
border: 1px solid #d1d5db; border-radius: 5px;
padding: 0.3rem 0.55rem; outline: none; width: 100%;
background: #fff;
@(_titleTouched && string.IsNullOrWhiteSpace(_envelopeTitle) ? "border-color:#dc2626;" : "")" />
@if (_titleTouched && string.IsNullOrWhiteSpace(_envelopeTitle)) @if (_titleTouched && string.IsNullOrWhiteSpace(_envelopeTitle))
{ {
<span style="font-size:0.6rem;color:#dc2626;">@L("RequiredField", "Pflichtfeld")</span> <span class="sender-editor-required-text">@L("RequiredField", "Pflichtfeld")</span>
} }
</div> </div>
@* Nachricht *@ @* Nachricht *@
<div style="display: flex; flex-direction: column; gap: 0.2rem; flex: 2 1 260px; min-width: 180px;"> <div class="sender-editor-input-field sender-editor-input-field--message">
<label style="font-size: 0.65rem; font-weight: 600; color: #6b7280; <label class="sender-editor-label">
text-transform: uppercase; letter-spacing: 0.05em;"> @L("Message", "Nachricht") <span class="sender-editor-optional">(@L("Optional", "optional"))</span>
@L("Message", "Nachricht") <span style="color:#6b7280;font-weight:400;">(@L("Optional", "optional"))</span>
</label> </label>
<input type="text" <input type="text"
value="@_envelopeMessage" value="@_envelopeMessage"
@oninput="e => _envelopeMessage = e.Value?.ToString() ?? string.Empty" @oninput="e => _envelopeMessage = e.Value?.ToString() ?? string.Empty"
placeholder='@L("SenderEditor_Message_Placeholder", "Bitte unterzeichnen Sie das beigefügte Dokument.")' placeholder='@L("SenderEditor_Message_Placeholder", "Bitte unterzeichnen Sie das beigefügte Dokument.")'
maxlength="500" maxlength="500"
style="font-size: 0.78rem; color: #1f2937; class="sender-editor-text-input" />
border: 1px solid #d1d5db; border-radius: 5px;
padding: 0.3rem 0.55rem; outline: none; width: 100%;
background: #fff;" />
</div> </div>
@* PDF filename badge *@ @* PDF filename badge *@
@if (_pdfLoaded) @if (_pdfLoaded)
{ {
<div style="display:flex;align-items:flex-end;gap:0.4rem;padding-bottom:0.05rem;flex-shrink:0;"> <div class="sender-editor-file-meta">
<span style="font-size: 0.7rem; color: #6b7280;">@_fileName</span> <span class="sender-editor-file-name">@_fileName</span>
@if (_signatureFields.Count > 0) @if (_signatureFields.Count > 0)
{ {
<span style="display: inline-flex; align-items: center; padding: 0.125rem 0.4rem; <span class="sender-editor-signature-count">
background: #ede9fe; border-radius: 0.25rem; color: #6d28d9;
font-weight: 500; font-size: 0.7rem; white-space: nowrap;">
@_signatureFields.Count @L("SenderEditor_SignatureField", "Signaturfeld")@(_signatureFields.Count != 1 ? "er" : "") @_signatureFields.Count @L("SenderEditor_SignatureField", "Signaturfeld")@(_signatureFields.Count != 1 ? "er" : "")
</span> </span>
} }
@@ -99,106 +87,109 @@
} }
</div> </div>
<details style="border:1px solid #dbe5ef;border-radius:8px;background:#f9fbfd;"> <div class="sender-settings-shell">
<summary style="cursor:pointer;list-style:none;display:flex;align-items:center;justify-content:space-between;padding:0.55rem 0.7rem;font-size:0.74rem;font-weight:700;color:#1f2937;text-transform:uppercase;letter-spacing:0.04em;"> <DxAccordion ExpandMode="AccordionExpandMode.SingleOrNone" CssClass="sender-settings-accordion">
<span>@L("SenderEditor_EnvelopeSettings", "Envelope-Einstellungen")</span> <Items>
<span style="font-size:0.66rem;color:#64748b;font-weight:600;">@L("SenderEditor_AdvancedOptional", "Erweitert")</span> <DxAccordionItem Text='@L("SenderEditor_EnvelopeSettings", "Envelope-Einstellungen")' Expanded="true">
</summary> <ContentTemplate>
<div class="sender-settings-body">
<div class="sender-settings-chip">@L("SenderEditor_AdvancedOptional", "Erweitert")</div>
<div style="padding:0 0.7rem 0.7rem;display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:0.45rem;"> <div class="sender-settings-grid">
<div style="display:flex;flex-direction:column;gap:0.2rem;"> <div class="sender-settings-field">
<label style="font-size:0.65rem;font-weight:600;color:#64748b;">@L("EnvelopeType", "Umschlagtyp")</label> <label class="sender-settings-label">@L("EnvelopeType", "Umschlagtyp")</label>
<select value="@(_selectedEnvelopeTypeId?.ToString() ?? string.Empty)" <select value="@(_selectedEnvelopeTypeId?.ToString() ?? string.Empty)"
@onchange="OnEnvelopeTypeChanged" @onchange="OnEnvelopeTypeChanged"
style="font-size:0.78rem;border:1px solid #d1d5db;border-radius:5px;padding:0.3rem 0.45rem;background:#fff;"> class="sender-settings-select">
<option value="">@L("SenderEditor_SelectTemplate", "Vorlage auswählen")</option> <option value="">@L("SenderEditor_SelectTemplate", "Vorlage auswählen")</option>
@foreach (var type in _envelopeTypes) @foreach (var type in _envelopeTypes)
{ {
<option value="@type.Id">@type.Title</option> <option value="@type.Id">@type.Title</option>
} }
</select> </select>
</div> </div>
<div style="display:flex;flex-direction:column;gap:0.2rem;"> <div class="sender-settings-field">
<label style="font-size:0.65rem;font-weight:600;color:#64748b;">@L("Language", "Sprache")</label> <label class="sender-settings-label">@L("Language", "Sprache")</label>
<select @bind="_language" <select @bind="_language" class="sender-settings-select">
style="font-size:0.78rem;border:1px solid #d1d5db;border-radius:5px;padding:0.3rem 0.45rem;background:#fff;"> <option value="de">Deutsch (de)</option>
<option value="de">Deutsch (de)</option> <option value="en">English (en)</option>
<option value="en">English (en)</option> <option value="de-DE">Deutsch (de-DE)</option>
<option value="de-DE">Deutsch (de-DE)</option> <option value="en-US">English (en-US)</option>
<option value="en-US">English (en-US)</option> </select>
</select> </div>
</div>
<div style="display:flex;flex-direction:column;gap:0.2rem;"> <div class="sender-settings-field">
<label style="font-size:0.65rem;font-weight:600;color:#64748b;">@L("CertificationType", "Zertifizierung")</label> <label class="sender-settings-label">@L("CertificationType", "Zertifizierung")</label>
<select @bind="_certificationType" <select @bind="_certificationType" class="sender-settings-select">
style="font-size:0.78rem;border:1px solid #d1d5db;border-radius:5px;padding:0.3rem 0.45rem;background:#fff;"> <option value="1">@L("AdvancedElectronicSignature", "Advanced Electronic Signature")</option>
<option value="1">@L("AdvancedElectronicSignature", "Advanced Electronic Signature")</option> <option value="2">@L("ElectronicSignature", "Electronic Signature")</option>
<option value="2">@L("ElectronicSignature", "Electronic Signature")</option> <option value="3">@L("QualifiedSignature", "Qualified Signature")</option>
<option value="3">@L("QualifiedSignature", "Qualified Signature")</option> </select>
</select> </div>
</div>
<div style="display:flex;flex-direction:column;gap:0.3rem;justify-content:flex-end;padding-bottom:0.25rem;"> <div class="sender-settings-toggles">
<label style="display:flex;align-items:center;gap:0.45rem;font-size:0.74rem;color:#1f2937;"> <label class="sender-settings-toggle">
<input type="checkbox" checked="@_tfaEnabled" @onchange="OnTfaChanged" /> <input type="checkbox" checked="@_tfaEnabled" @onchange="OnTfaChanged" />
@L("TFAEnabled", "2FA aktiviert") @L("TFAEnabled", "2FA aktiviert")
</label> </label>
<label style="display:flex;align-items:center;gap:0.45rem;font-size:0.74rem;color:#1f2937;"> <label class="sender-settings-toggle">
<input type="checkbox" checked="@_useAccessCode" @onchange="OnUseAccessCodeChanged" disabled="@_tfaEnabled" /> <input type="checkbox" checked="@_useAccessCode" @onchange="OnUseAccessCodeChanged" disabled="@_tfaEnabled" />
@L("UseAccessCode", "Zugriffscode verwenden") @L("UseAccessCode", "Zugriffscode verwenden")
</label> </label>
</div> </div>
<div style="display:flex;flex-direction:column;gap:0.2rem;"> <div class="sender-settings-field">
<label style="font-size:0.65rem;font-weight:600;color:#64748b;">@L("FinalEmailToCreator", "Finale E-Mail an Ersteller")</label> <label class="sender-settings-label">@L("FinalEmailToCreator", "Finale E-Mail an Ersteller")</label>
<select @bind="_finalEmailToCreator" <select @bind="_finalEmailToCreator" class="sender-settings-select">
style="font-size:0.78rem;border:1px solid #d1d5db;border-radius:5px;padding:0.3rem 0.45rem;background:#fff;"> <option value="0">@L("No", "Nein")</option>
<option value="0">@L("No", "Nein")</option> <option value="1">@L("Yes", "Ja")</option>
<option value="1">@L("Yes", "Ja")</option> <option value="2">@L("YesWithAttachment", "Ja, mit Anhang")</option>
<option value="2">@L("YesWithAttachment", "Ja, mit Anhang")</option> </select>
</select> </div>
</div>
<div style="display:flex;flex-direction:column;gap:0.2rem;"> <div class="sender-settings-field">
<label style="font-size:0.65rem;font-weight:600;color:#64748b;">@L("FinalEmailToReceivers", "Finale E-Mail an Empfänger")</label> <label class="sender-settings-label">@L("FinalEmailToReceivers", "Finale E-Mail an Empfänger")</label>
<select @bind="_finalEmailToReceivers" <select @bind="_finalEmailToReceivers" class="sender-settings-select">
style="font-size:0.78rem;border:1px solid #d1d5db;border-radius:5px;padding:0.3rem 0.45rem;background:#fff;"> <option value="0">@L("No", "Nein")</option>
<option value="0">@L("No", "Nein")</option> <option value="1">@L("Yes", "Ja")</option>
<option value="1">@L("Yes", "Ja")</option> <option value="2">@L("YesWithAttachment", "Ja, mit Anhang")</option>
<option value="2">@L("YesWithAttachment", "Ja, mit Anhang")</option> </select>
</select> </div>
</div>
<div style="display:flex;flex-direction:column;gap:0.3rem;justify-content:flex-end;padding-bottom:0.25rem;"> <div class="sender-settings-toggles sender-settings-toggles--single">
<label style="display:flex;align-items:center;gap:0.45rem;font-size:0.74rem;color:#1f2937;"> <label class="sender-settings-toggle">
<input type="checkbox" checked="@_sendReminderEmails" @onchange="OnSendReminderEmailsChanged" /> <input type="checkbox" checked="@_sendReminderEmails" @onchange="OnSendReminderEmailsChanged" />
@L("SendReminderEmails", "Erinnerungs-E-Mails senden") @L("SendReminderEmails", "Erinnerungs-E-Mails senden")
</label> </label>
</div> </div>
<div style="display:flex;flex-direction:column;gap:0.2rem;"> <div class="sender-settings-field">
<label style="font-size:0.65rem;font-weight:600;color:#64748b;">@L("FirstReminderDays", "Erste Erinnerung (Tage)")</label> <label class="sender-settings-label">@L("FirstReminderDays", "Erste Erinnerung (Tage)")</label>
<InputNumber @bind-Value="_firstReminderDays" class="form-control form-control-sm" disabled="@(!_sendReminderEmails)" /> <InputNumber @bind-Value="_firstReminderDays" class="form-control form-control-sm sender-settings-number" disabled="@(!_sendReminderEmails)" />
</div> </div>
<div style="display:flex;flex-direction:column;gap:0.2rem;"> <div class="sender-settings-field">
<label style="font-size:0.65rem;font-weight:600;color:#64748b;">@L("ReminderIntervalDays", "Erinnerungsintervall (Tage)")</label> <label class="sender-settings-label">@L("ReminderIntervalDays", "Erinnerungsintervall (Tage)")</label>
<InputNumber @bind-Value="_reminderIntervalDays" class="form-control form-control-sm" disabled="@(!_sendReminderEmails)" /> <InputNumber @bind-Value="_reminderIntervalDays" class="form-control form-control-sm sender-settings-number" disabled="@(!_sendReminderEmails)" />
</div> </div>
<div style="display:flex;flex-direction:column;gap:0.2rem;"> <div class="sender-settings-field">
<label style="font-size:0.65rem;font-weight:600;color:#64748b;">@L("ExpiresWhenDays", "Läuft ab nach (Tage)")</label> <label class="sender-settings-label">@L("ExpiresWhenDays", "Läuft ab nach (Tage)")</label>
<InputNumber @bind-Value="_expiresWhenDays" class="form-control form-control-sm" /> <InputNumber @bind-Value="_expiresWhenDays" class="form-control form-control-sm sender-settings-number" />
</div> </div>
<div style="display:flex;flex-direction:column;gap:0.2rem;"> <div class="sender-settings-field">
<label style="font-size:0.65rem;font-weight:600;color:#64748b;">@L("ExpiresWarningWhenDays", "Ablaufwarnung (Tage)")</label> <label class="sender-settings-label">@L("ExpiresWarningWhenDays", "Ablaufwarnung (Tage)")</label>
<InputNumber @bind-Value="_expiresWarningWhenDays" class="form-control form-control-sm" /> <InputNumber @bind-Value="_expiresWarningWhenDays" class="form-control form-control-sm sender-settings-number" />
</div> </div>
</div> </div>
</details> </div>
</ContentTemplate>
</DxAccordionItem>
</Items>
</DxAccordion>
</div>
<div class="sender-receivers-panel"> <div class="sender-receivers-panel">
<div class="sender-receivers-panel__header"> <div class="sender-receivers-panel__header">
@@ -228,8 +219,7 @@
<div class="sender-receivers-list"> <div class="sender-receivers-list">
@foreach (var receiver in _receivers) @foreach (var receiver in _receivers)
{ {
<div class="sender-receiver-chip" <div class="sender-receiver-chip" style="--receiver-color:@receiver.Color;">
style="border-left: 3px solid @receiver.Color;">
<div class="sender-receiver-chip__body"> <div class="sender-receiver-chip__body">
<div class="sender-receiver-chip__name">@receiver.FullName</div> <div class="sender-receiver-chip__name">@receiver.FullName</div>
<div class="sender-receiver-chip__email">@receiver.Email</div> <div class="sender-receiver-chip__email">@receiver.Email</div>
@@ -239,11 +229,11 @@
} }
</div> </div>
<button class="pdf-toolbar__btn pdf-toolbar__btn--signature-change sender-toolbar-action-btn sender-toolbar-action-btn--compact <button class="pdf-toolbar__btn pdf-toolbar__btn--signature-change sender-toolbar-action-btn sender-toolbar-action-btn--compact sender-receiver-chip__place-btn
@(_pendingReceiverForPlacement?.Id == receiver.Id ? "pdf-toolbar__btn--signature-change-active" : "")" @(_pendingReceiverForPlacement?.Id == receiver.Id ? "sender-receiver-chip__place-btn--active" : "sender-receiver-chip__place-btn--idle")"
@onclick="() => ActivatePlacementForReceiver(receiver)" @onclick="() => ActivatePlacementForReceiver(receiver)"
style="@(_pendingReceiverForPlacement?.Id == receiver.Id ? $"background:{receiver.Color};color:#fff;border-color:{receiver.Color};" : $"color:{receiver.Color};border-color:{receiver.Color};")" style="--receiver-color:@receiver.Color;"
title='@L("SenderEditor_PlaceSignatureField", "Signaturfeld platzieren")'> title='@L("SenderEditor_PlaceSignatureField", "Signaturfeld platzieren")'>
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" viewBox="0 0 16 16"> <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" viewBox="0 0 16 16">
<path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10z" /> <path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10z" />
</svg> </svg>
@@ -257,7 +247,7 @@
</div> </div>
@* Right: Buttons *@ @* Right: Buttons *@
<div style="display: flex; align-items: center; gap: 0.5rem; flex-shrink: 0;"> <div class="sender-editor-actions">
<button class="pdf-toolbar__btn pdf-toolbar__btn--reset" <button class="pdf-toolbar__btn pdf-toolbar__btn--reset"
@onclick="Cancel" @onclick="Cancel"
@@ -266,14 +256,12 @@
</button> </button>
@* PDF Upload *@ @* PDF Upload *@
<label class="pdf-toolbar__btn" <label class="pdf-toolbar__btn sender-editor-upload-btn @((!_isPageReady || _isPdfUploadInProgress) ? "sender-editor-upload-btn--disabled" : string.Empty)"
title='@L("UploadPdf", "PDF hochladen")' title='@L("UploadPdf", "PDF hochladen")'
style="cursor: @((_isPageReady && !_isPdfUploadInProgress) ? "pointer" : "not-allowed"); >
opacity:@((_isPageReady && !_isPdfUploadInProgress) ? "1" : "0.6");
display: inline-flex; align-items: center; gap: 0.3rem; padding: 0.3rem 0.75rem; font-size: 0.75rem; font-weight: 500;">
@if (_isPdfUploadInProgress) @if (_isPdfUploadInProgress)
{ {
<span class="spinner-border spinner-border-sm" role="status" style="width:0.8rem;height:0.8rem;border-width:2px;"></span> <span class="spinner-border spinner-border-sm sender-editor-spinner" role="status"></span>
} }
else else
{ {
@@ -301,7 +289,7 @@
<InputFile OnChange="OnPdfFileSelectedAsync" <InputFile OnChange="OnPdfFileSelectedAsync"
accept=".pdf" accept=".pdf"
disabled="@(!_isPageReady || _isPdfUploadInProgress)" disabled="@(!_isPageReady || _isPdfUploadInProgress)"
style="display: none;" /> class="sender-editor-hidden-input" />
</label> </label>
@if (_pdfLoaded && _signatureFields.Count > 0) @if (_pdfLoaded && _signatureFields.Count > 0)
@@ -322,8 +310,7 @@
title='@L("Save", "Speichern")'> title='@L("Save", "Speichern")'>
@if (_isSaving) @if (_isSaving)
{ {
<span class="spinner-border spinner-border-sm me-1" role="status" <span class="spinner-border spinner-border-sm me-1 sender-editor-spinner" role="status"></span>
style="width:0.8rem;height:0.8rem;border-width:2px;"></span>
} }
else else
{ {
@@ -338,12 +325,10 @@
<button class="pdf-toolbar__btn pdf-toolbar__btn--success" <button class="pdf-toolbar__btn pdf-toolbar__btn--success"
@onclick="SendAsync" @onclick="SendAsync"
disabled="@_isSaving" disabled="@_isSaving"
title='@L("SendEnvelope", "Umschlag senden")' title='@L("SendEnvelope", "Umschlag senden")'>
style="background: linear-gradient(135deg,#059669,#047857); color:#fff; border-radius:6px; padding:0.3rem 0.75rem; font-size:0.75rem; font-weight:600; border:none;">
@if (_isSaving) @if (_isSaving)
{ {
<span class="spinner-border spinner-border-sm me-1" role="status" <span class="spinner-border spinner-border-sm me-1 sender-editor-spinner" role="status"></span>
style="width:0.8rem;height:0.8rem;border-width:2px;"></span>
} }
else else
{ {
@@ -359,11 +344,9 @@
@* Placement mode hint bar *@ @* Placement mode hint bar *@
@if (_pendingReceiverForPlacement is not null) @if (_pendingReceiverForPlacement is not null)
{ {
<div style="background: @_pendingReceiverForPlacement.Color; color: white; font-size: 0.75rem; font-weight: 500; <div class="sender-editor-placement-hint" style="--placement-color:@_pendingReceiverForPlacement.Color;">
padding: 0.3rem 1.5rem; text-align: center; letter-spacing: 0.01em;">
📌 @L("SenderEditor_ClickDocumentFor", "Klicken Sie auf die Stelle im Dokument für") <strong>@_pendingReceiverForPlacement.FullName</strong>. 📌 @L("SenderEditor_ClickDocumentFor", "Klicken Sie auf die Stelle im Dokument für") <strong>@_pendingReceiverForPlacement.FullName</strong>.
&nbsp;<button @onclick="CancelPlacement" &nbsp;<button @onclick="CancelPlacement" class="sender-editor-placement-cancel">
style="background:none;border:none;color:white;cursor:pointer;font-size:0.75rem;text-decoration:underline;padding:0;">
@L("Cancel", "Abbrechen") @L("Cancel", "Abbrechen")
</button> </button>
</div> </div>
@@ -371,30 +354,25 @@
</div> </div>
@* ── Content ── *@ @* ── Content ── *@
<div class="envelope-content" style="padding: 0; overflow: hidden;"> <div class="envelope-content sender-editor-content">
@if (!_pdfLoaded) @if (!_pdfLoaded)
{ {
@* Empty state *@ @* Empty state *@
<div class="d-flex justify-content-center align-items-center h-100"> <div class="d-flex justify-content-center align-items-center h-100">
<div class="text-center" style="max-width: 420px;"> <div class="text-center sender-editor-empty-state">
<div style="width: 72px; height: 72px; background: rgba(255,255,255,0.15); <div class="sender-editor-empty-state__icon-wrap">
border-radius: 50%; display: flex; align-items: center;
justify-content: center; margin: 0 auto 1.25rem;">
<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" fill="white" viewBox="0 0 16 16"> <svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" fill="white" viewBox="0 0 16 16">
<path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5L14 4.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5h-2z" /> <path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5L14 4.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5h-2z" />
</svg> </svg>
</div> </div>
<h5 class="text-white mb-2">@L("SenderEditor_NoDocumentLoaded", "Kein Dokument geladen")</h5> <h5 class="text-white mb-2">@L("SenderEditor_NoDocumentLoaded", "Kein Dokument geladen")</h5>
<p class="text-white mb-4" style="opacity: 0.75; font-size: 0.85rem;"> <p class="text-white mb-4 sender-editor-empty-state__description">
@L("SenderEditor_UploadPdfToPlaceFields", "Laden Sie eine PDF-Datei hoch, um Signaturfelder zu platzieren.") @L("SenderEditor_UploadPdfToPlaceFields", "Laden Sie eine PDF-Datei hoch, um Signaturfelder zu platzieren.")
</p> </p>
<label class="btn btn-light btn-sm" <label class="btn btn-light btn-sm sender-editor-empty-state__upload @((!_isPageReady || _isPdfUploadInProgress) ? "sender-editor-empty-state__upload--disabled" : string.Empty)">
style="cursor: @((_isPageReady && !_isPdfUploadInProgress) ? "pointer" : "not-allowed");
opacity:@((_isPageReady && !_isPdfUploadInProgress) ? "1" : "0.8");
font-weight: 500;">
@if (_isPdfUploadInProgress) @if (_isPdfUploadInProgress)
{ {
<span class="spinner-border spinner-border-sm me-1" role="status" style="width:0.8rem;height:0.8rem;border-width:2px;"></span> <span class="spinner-border spinner-border-sm me-1 sender-editor-spinner" role="status"></span>
} }
else else
{ {
@@ -422,7 +400,7 @@
<InputFile OnChange="OnPdfFileSelectedAsync" <InputFile OnChange="OnPdfFileSelectedAsync"
accept=".pdf" accept=".pdf"
disabled="@(!_isPageReady || _isPdfUploadInProgress)" disabled="@(!_isPageReady || _isPdfUploadInProgress)"
style="display: none;" /> class="sender-editor-hidden-input" />
</label> </label>
</div> </div>
</div> </div>
@@ -546,18 +524,17 @@
<BodyContentTemplate> <BodyContentTemplate>
@if (_saveErrorMessage is null) @if (_saveErrorMessage is null)
{ {
<div style="display:flex;align-items:flex-start;gap:1rem;padding:0.5rem 0;"> <div class="sender-editor-save-result">
<div style="flex-shrink:0;width:40px;height:40px;background:#d1fae5;border-radius:50%; <div class="sender-editor-save-result__icon sender-editor-save-result__icon--success">
display:flex;align-items:center;justify-content:center;">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="#065f46" viewBox="0 0 16 16"> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="#065f46" viewBox="0 0 16 16">
<path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z" /> <path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z" />
</svg> </svg>
</div> </div>
<div> <div>
<p style="margin:0 0 0.4rem;font-weight:600;color:#1f2937;font-size:0.95rem;"> <p class="sender-editor-save-result__title">
@_saveSuccessTitle @_saveSuccessTitle
</p> </p>
<p style="margin:0;color:#6b7280;font-size:0.85rem;line-height:1.5;"> <p class="sender-editor-save-result__message">
@_saveSuccessBody @_saveSuccessBody
</p> </p>
</div> </div>
@@ -565,19 +542,18 @@
} }
else else
{ {
<div style="display:flex;align-items:flex-start;gap:1rem;padding:0.5rem 0;"> <div class="sender-editor-save-result">
<div style="flex-shrink:0;width:40px;height:40px;background:#fee2e2;border-radius:50%; <div class="sender-editor-save-result__icon sender-editor-save-result__icon--error">
display:flex;align-items:center;justify-content:center;">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="#991b1b" viewBox="0 0 16 16"> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="#991b1b" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" /> <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" />
<path d="M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995z" /> <path d="M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995z" />
</svg> </svg>
</div> </div>
<div> <div>
<p style="margin:0 0 0.4rem;font-weight:600;color:#1f2937;font-size:0.95rem;"> <p class="sender-editor-save-result__title">
@L("SenderEditor_ErrorCreatingEnvelope", "Fehler beim Erstellen des Umschlags") @L("SenderEditor_ErrorCreatingEnvelope", "Fehler beim Erstellen des Umschlags")
</p> </p>
<p style="margin:0;color:#6b7280;font-size:0.85rem;line-height:1.5;word-break:break-word;"> <p class="sender-editor-save-result__message sender-editor-save-result__message--break">
@_saveErrorMessage @_saveErrorMessage
</p> </p>
</div> </div>
@@ -585,30 +561,25 @@
} }
</BodyContentTemplate> </BodyContentTemplate>
<FooterContentTemplate> <FooterContentTemplate>
<div style="display:flex;gap:0.5rem;justify-content:flex-end;width:100%;padding:0.5rem 0;"> <div class="sender-editor-save-actions">
@if (_saveErrorMessage is null) @if (_saveErrorMessage is null)
{ {
<button class="btn btn-outline-secondary" <button class="btn btn-outline-secondary sender-editor-save-actions__secondary"
@onclick="() => _savePopupVisible = false" @onclick="() => _savePopupVisible = false">
style="border-radius:6px;padding:0.5rem 1.25rem;font-weight:500;">
@L("Close", "Schließen") @L("Close", "Schließen")
</button> </button>
@if (_saveNavigateToDashboard) @if (_saveNavigateToDashboard)
{ {
<button class="btn btn-primary" <button class="btn btn-primary sender-editor-save-actions__primary" @onclick="GoToDashboard">
@onclick="GoToDashboard"
style="background:linear-gradient(135deg,#059669,#047857);border:none;border-radius:6px;
padding:0.5rem 1.5rem;font-weight:600;color:#fff;">
@L("SenderEditor_ToOverview", "Zur Übersicht") @L("SenderEditor_ToOverview", "Zur Übersicht")
</button> </button>
} }
} }
else else
{ {
<button class="btn btn-outline-secondary" <button class="btn btn-outline-secondary sender-editor-save-actions__secondary"
@onclick="() => _savePopupVisible = false" @onclick="() => _savePopupVisible = false">
style="border-radius:6px;padding:0.5rem 1.25rem;font-weight:500;">
@L("Close", "Schließen") @L("Close", "Schließen")
</button> </button>
} }

View File

@@ -1002,6 +1002,107 @@ body.resizing {
border: 1px solid rgba(126, 34, 206, 0.12); border: 1px solid rgba(126, 34, 206, 0.12);
} }
.sender-settings-shell {
border-radius: 12px;
overflow: hidden;
}
.sender-settings-accordion {
border: 1px solid #dbe5ef;
border-radius: 12px;
background: linear-gradient(135deg, #ffffff 0%, #f8fbff 100%);
}
.sender-settings-accordion .dxbl-accordion-item {
border: none;
}
.sender-settings-accordion .dxbl-accordion-item-header {
text-transform: uppercase;
letter-spacing: 0.04em;
font-size: 0.74rem;
font-weight: 700;
color: #1f2937;
padding: 0.55rem 0.7rem;
background: rgba(124, 58, 237, 0.04);
}
.sender-settings-body {
display: flex;
flex-direction: column;
gap: 0.65rem;
padding: 0.25rem 0.7rem 0.7rem;
}
.sender-settings-chip {
align-self: flex-start;
font-size: 0.66rem;
font-weight: 700;
color: #5b21b6;
background: rgba(126, 34, 206, 0.1);
border: 1px solid rgba(126, 34, 206, 0.15);
border-radius: 999px;
padding: 0.12rem 0.5rem;
}
.sender-settings-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 0.5rem;
}
.sender-settings-field {
display: flex;
flex-direction: column;
gap: 0.2rem;
}
.sender-settings-label {
font-size: 0.65rem;
font-weight: 700;
color: #64748b;
letter-spacing: 0.01em;
}
.sender-settings-select,
.sender-settings-number {
font-size: 0.78rem;
border: 1px solid #d1d5db;
border-radius: 6px;
padding: 0.3rem 0.45rem;
background: #fff;
min-height: 32px;
color: #1f2937;
}
.sender-settings-number:focus,
.sender-settings-select:focus {
border-color: rgba(126, 34, 206, 0.45);
box-shadow: 0 0 0 2px rgba(126, 34, 206, 0.12);
outline: none;
}
.sender-settings-toggles {
display: flex;
flex-direction: column;
justify-content: flex-end;
gap: 0.3rem;
padding-bottom: 0.1rem;
}
.sender-settings-toggle {
display: inline-flex;
align-items: center;
gap: 0.45rem;
font-size: 0.74rem;
color: #1f2937;
min-height: 32px;
}
.sender-settings-toggles--single {
justify-content: center;
}
.sender-receivers-panel__header { .sender-receivers-panel__header {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -1119,9 +1220,275 @@ body.resizing {
border-radius: 12px; border-radius: 12px;
background: rgba(255, 255, 255, 0.88); background: rgba(255, 255, 255, 0.88);
border: 1px solid rgba(126, 34, 206, 0.12); border: 1px solid rgba(126, 34, 206, 0.12);
border-left: 3px solid var(--receiver-color, #7c3aed);
box-shadow: 0 6px 16px rgba(15, 23, 42, 0.06); box-shadow: 0 6px 16px rgba(15, 23, 42, 0.06);
} }
.sender-receiver-chip__place-btn--idle {
color: var(--receiver-color, #7c3aed);
border-color: var(--receiver-color, #7c3aed);
}
.sender-receiver-chip__place-btn--active {
background: var(--receiver-color, #7c3aed);
color: #ffffff;
border-color: var(--receiver-color, #7c3aed);
}
.sender-editor-action-inner {
flex-direction: row;
align-items: center;
padding: 0.35rem 1.5rem;
gap: 0.75rem;
}
.sender-editor-main-column {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
align-items: stretch;
gap: 0.5rem;
}
.sender-editor-meta-row {
display: flex;
align-items: flex-start;
gap: 0.5rem;
flex-wrap: wrap;
}
.sender-editor-input-field {
display: flex;
flex-direction: column;
gap: 0.2rem;
}
.sender-editor-input-field--title {
flex: 1 1 180px;
min-width: 140px;
}
.sender-editor-input-field--message {
flex: 2 1 260px;
min-width: 180px;
}
.sender-editor-label {
font-size: 0.65rem;
font-weight: 600;
color: #6b7280;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.sender-editor-required-asterisk {
color: #dc2626;
}
.sender-editor-optional {
color: #6b7280;
font-weight: 400;
}
.sender-editor-text-input {
font-size: 0.78rem;
color: #1f2937;
border: 1px solid #d1d5db;
border-radius: 5px;
padding: 0.3rem 0.55rem;
outline: none;
width: 100%;
background: #fff;
}
.sender-editor-text-input--invalid {
border-color: #dc2626;
}
.sender-editor-required-text {
font-size: 0.6rem;
color: #dc2626;
}
.sender-editor-file-meta {
display: flex;
align-items: flex-end;
gap: 0.4rem;
padding-bottom: 0.05rem;
flex-shrink: 0;
}
.sender-editor-file-name {
font-size: 0.7rem;
color: #6b7280;
}
.sender-editor-signature-count {
display: inline-flex;
align-items: center;
padding: 0.125rem 0.4rem;
background: #ede9fe;
border-radius: 0.25rem;
color: #6d28d9;
font-weight: 500;
font-size: 0.7rem;
white-space: nowrap;
}
.sender-editor-actions {
display: flex;
align-items: center;
gap: 0.5rem;
flex-shrink: 0;
}
.sender-editor-upload-btn {
display: inline-flex;
align-items: center;
gap: 0.3rem;
padding: 0.3rem 0.75rem;
font-size: 0.75rem;
font-weight: 500;
cursor: pointer;
}
.sender-editor-upload-btn--disabled {
cursor: not-allowed;
opacity: 0.6;
}
.sender-editor-hidden-input {
display: none;
}
.sender-editor-spinner {
width: 0.8rem;
height: 0.8rem;
border-width: 2px;
}
.sender-editor-placement-hint {
background: var(--placement-color, #7c3aed);
color: #ffffff;
font-size: 0.75rem;
font-weight: 500;
padding: 0.3rem 1.5rem;
text-align: center;
letter-spacing: 0.01em;
}
.sender-editor-placement-cancel {
background: none;
border: none;
color: #ffffff;
cursor: pointer;
font-size: 0.75rem;
text-decoration: underline;
padding: 0;
}
.sender-editor-content {
padding: 0;
overflow: hidden;
}
.sender-editor-empty-state {
max-width: 420px;
}
.sender-editor-empty-state__icon-wrap {
width: 72px;
height: 72px;
background: rgba(255, 255, 255, 0.15);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 1.25rem;
}
.sender-editor-empty-state__description {
opacity: 0.75;
font-size: 0.85rem;
}
.sender-editor-empty-state__upload {
font-weight: 500;
cursor: pointer;
}
.sender-editor-empty-state__upload--disabled {
cursor: not-allowed;
opacity: 0.8;
}
.sender-editor-save-result {
display: flex;
align-items: flex-start;
gap: 1rem;
padding: 0.5rem 0;
}
.sender-editor-save-result__icon {
flex-shrink: 0;
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.sender-editor-save-result__icon--success {
background: #d1fae5;
}
.sender-editor-save-result__icon--error {
background: #fee2e2;
}
.sender-editor-save-result__title {
margin: 0 0 0.4rem;
font-weight: 600;
color: #1f2937;
font-size: 0.95rem;
}
.sender-editor-save-result__message {
margin: 0;
color: #6b7280;
font-size: 0.85rem;
line-height: 1.5;
}
.sender-editor-save-result__message--break {
word-break: break-word;
}
.sender-editor-save-actions {
display: flex;
gap: 0.5rem;
justify-content: flex-end;
width: 100%;
padding: 0.5rem 0;
}
.sender-editor-save-actions__secondary {
border-radius: 6px;
padding: 0.5rem 1.25rem;
font-weight: 500;
}
.sender-editor-save-actions__primary {
background: linear-gradient(135deg, #059669, #047857);
border: none;
border-radius: 6px;
padding: 0.5rem 1.5rem;
font-weight: 600;
color: #fff;
}
.sender-receiver-chip__body { .sender-receiver-chip__body {
min-width: 0; min-width: 0;
flex: 1; flex: 1;