feat(sender): support draft editing flow in sender pages
This commit is contained in:
@@ -3,9 +3,12 @@
|
||||
@using DevExpress.Blazor.PdfViewer
|
||||
@using DevExpress.Blazor.Reporting.Models
|
||||
@using DevExpress.Blazor
|
||||
@using EnvelopeGenerator.Application.Common.Dto
|
||||
@using EnvelopeGenerator.Application.Envelopes.Commands
|
||||
@using EnvelopeGenerator.Application.EnvelopeReceivers.Commands
|
||||
@using EnvelopeGenerator.Server.Client.Services
|
||||
@using EnvelopeGenerator.Server.Services
|
||||
@using Microsoft.AspNetCore.WebUtilities
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Microsoft.Extensions.Caching.Memory
|
||||
@inject IJSRuntime JSRuntime
|
||||
@@ -14,6 +17,8 @@
|
||||
@inject ILogger<EnvelopeSenderEditorPage> Logger
|
||||
@inject EnvelopeReceiverPageDataService ReceiverPageDataService
|
||||
@inject EnvelopeReceiverService EnvelopeReceiverService
|
||||
@inject EnvelopeService EnvelopeService
|
||||
@inject IEnvelopeAuthService EnvelopeAuthService
|
||||
@inject IMemoryCache MemoryCache
|
||||
|
||||
<link href="_content/DevExpress.Blazor.Themes/blazing-berry.bs5.min.css" rel="stylesheet" />
|
||||
@@ -169,41 +174,55 @@
|
||||
style="display: none;" />
|
||||
</label>
|
||||
|
||||
@if (_pdfLoaded)
|
||||
@if (_pdfLoaded && _signatureFields.Count > 0)
|
||||
{
|
||||
@* Clear all fields *@
|
||||
@if (_signatureFields.Count > 0)
|
||||
{
|
||||
<button class="pdf-toolbar__btn pdf-toolbar__btn--reset"
|
||||
@onclick="ClearAllFieldsAsync"
|
||||
title="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>
|
||||
</button>
|
||||
}
|
||||
|
||||
@* Save *@
|
||||
<button class="pdf-toolbar__btn pdf-toolbar__btn--success"
|
||||
@onclick="SaveAsync"
|
||||
disabled="@_isSaving"
|
||||
title="Speichern"
|
||||
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)
|
||||
{
|
||||
<span class="spinner-border spinner-border-sm me-1" role="status"
|
||||
style="width:0.8rem;height:0.8rem;border-width:2px;"></span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" class="me-1" 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" />
|
||||
</svg>
|
||||
}
|
||||
Speichern
|
||||
<button class="pdf-toolbar__btn pdf-toolbar__btn--reset"
|
||||
@onclick="ClearAllFieldsAsync"
|
||||
title="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>
|
||||
</button>
|
||||
}
|
||||
|
||||
<button class="pdf-toolbar__btn pdf-toolbar__btn--draft"
|
||||
@onclick="SaveDraftAsync"
|
||||
disabled="@_isSaving"
|
||||
title="Speichern">
|
||||
@if (_isSaving)
|
||||
{
|
||||
<span class="spinner-border spinner-border-sm me-1" role="status"
|
||||
style="width:0.8rem;height:0.8rem;border-width:2px;"></span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" class="me-1" viewBox="0 0 16 16">
|
||||
<path d="M2 2a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V5.414A2 2 0 0 0 15.414 4L12 0.586A2 2 0 0 0 10.586 0H2zm9 1.5v2A1.5 1.5 0 0 1 9.5 7h-3A1.5 1.5 0 0 1 5 5.5v-4h5.586a1 1 0 0 1 .707.293L14.207 4.707A1 1 0 0 1 14.5 5.414V12a.5.5 0 0 1-.5.5H2a.5.5 0 0 1-.5-.5V4A.5.5 0 0 1 2 3.5h9z"/>
|
||||
<path d="M5.5 1.5v4a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-4h-4zM4 9a1 1 0 0 0-1 1v3h10v-3a1 1 0 0 0-1-1H4z"/>
|
||||
</svg>
|
||||
}
|
||||
<span class="pdf-toolbar__btn-text">Speichern</span>
|
||||
</button>
|
||||
|
||||
<button class="pdf-toolbar__btn pdf-toolbar__btn--success"
|
||||
@onclick="SendAsync"
|
||||
disabled="@_isSaving"
|
||||
title="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)
|
||||
{
|
||||
<span class="spinner-border spinner-border-sm me-1" role="status"
|
||||
style="width:0.8rem;height:0.8rem;border-width:2px;"></span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" class="me-1" 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" />
|
||||
</svg>
|
||||
}
|
||||
Senden
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -358,7 +377,7 @@
|
||||
|
||||
@* ── Save result popup (success + error) ── *@
|
||||
<DxPopup @bind-Visible="_savePopupVisible"
|
||||
HeaderText="@(_saveErrorMessage is null ? "Umschlag erstellt" : "Fehler")"
|
||||
HeaderText="@(_saveErrorMessage is null ? _saveSuccessHeader : "Fehler")"
|
||||
Width="460px"
|
||||
MaxWidth="95vw"
|
||||
ShowFooter="true"
|
||||
@@ -377,10 +396,10 @@
|
||||
</div>
|
||||
<div>
|
||||
<p style="margin:0 0 0.4rem;font-weight:600;color:#1f2937;font-size:0.95rem;">
|
||||
Umschlag wurde erfolgreich erstellt.
|
||||
@_saveSuccessTitle
|
||||
</p>
|
||||
<p style="margin:0;color:#6b7280;font-size:0.85rem;line-height:1.5;">
|
||||
Der Umschlag wurde gespeichert und die Empfänger wurden benachrichtigt.
|
||||
@_saveSuccessBody
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -410,12 +429,21 @@
|
||||
<div style="display:flex;gap:0.5rem;justify-content:flex-end;width:100%;padding:0.5rem 0;">
|
||||
@if (_saveErrorMessage is null)
|
||||
{
|
||||
<button class="btn btn-primary"
|
||||
@onclick="GoToDashboard"
|
||||
style="background:linear-gradient(135deg,#059669,#047857);border:none;border-radius:6px;
|
||||
padding:0.5rem 1.5rem;font-weight:600;color:#fff;">
|
||||
Zur Übersicht
|
||||
<button class="btn btn-outline-secondary"
|
||||
@onclick="() => _savePopupVisible = false"
|
||||
style="border-radius:6px;padding:0.5rem 1.25rem;font-weight:500;">
|
||||
Schließen
|
||||
</button>
|
||||
|
||||
@if (_saveNavigateToDashboard)
|
||||
{
|
||||
<button class="btn btn-primary"
|
||||
@onclick="GoToDashboard"
|
||||
style="background:linear-gradient(135deg,#059669,#047857);border:none;border-radius:6px;
|
||||
padding:0.5rem 1.5rem;font-weight:600;color:#fff;">
|
||||
Zur Übersicht
|
||||
</button>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -434,6 +462,9 @@
|
||||
[SupplyParameterFromQuery(Name = "esid")]
|
||||
public string? Esid { get; set; }
|
||||
|
||||
[SupplyParameterFromQuery(Name = "envelopeId")]
|
||||
public int? EnvelopeId { get; set; }
|
||||
|
||||
// ── Constants ──
|
||||
// Signature field size in PDF points (fixed): 1.77" × 1.96"
|
||||
const double SigWidthPt = 1.77 * 72; // 127.44 pt
|
||||
@@ -452,6 +483,9 @@
|
||||
string? _errorMessage;
|
||||
byte[]? _pdfBytes; // Current rendered PDF (original + placeholders burned in)
|
||||
byte[]? _originalPdfBytes; // Pristine upload — never modified, used as base for redraw
|
||||
int? _loadedEnvelopeId;
|
||||
int? _editingEnvelopeId;
|
||||
bool _isLoadingEnvelopeForEdit;
|
||||
|
||||
List<SignatureFieldDraft> _signatureFields = [];
|
||||
ReceiverDraft? _pendingReceiverForPlacement; // Set when user clicks "Signatur hinzufügen"
|
||||
@@ -460,6 +494,10 @@
|
||||
bool _isSaving = false;
|
||||
bool _savePopupVisible = false;
|
||||
string? _saveErrorMessage = null;
|
||||
string _saveSuccessHeader = "Erfolg";
|
||||
string _saveSuccessTitle = "Vorgang erfolgreich abgeschlossen.";
|
||||
string _saveSuccessBody = string.Empty;
|
||||
bool _saveNavigateToDashboard = false;
|
||||
|
||||
// ── Envelope metadata ──
|
||||
string _envelopeTitle = string.Empty;
|
||||
@@ -481,6 +519,7 @@
|
||||
|
||||
// ── Cache key helper ──
|
||||
string SessionKey => $"sender-editor:{Esid}";
|
||||
bool IsEditMode => EnvelopeId.HasValue && EnvelopeId.Value > 0;
|
||||
|
||||
// ── Lifecycle ──
|
||||
|
||||
@@ -500,12 +539,27 @@
|
||||
if (string.IsNullOrWhiteSpace(Esid))
|
||||
{
|
||||
var sid = Guid.NewGuid().ToString("N");
|
||||
NavigationManager.NavigateTo($"/sender/editor?esid={sid}", forceLoad: false);
|
||||
var editQuery = EnvelopeId.HasValue && EnvelopeId.Value > 0
|
||||
? $"&envelopeId={EnvelopeId.Value}"
|
||||
: string.Empty;
|
||||
NavigationManager.NavigateTo($"/sender/editor?esid={sid}{editQuery}", forceLoad: false);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnParametersSet()
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (EnvelopeId.HasValue && EnvelopeId.Value > 0)
|
||||
_editingEnvelopeId = EnvelopeId.Value;
|
||||
|
||||
if (IsEditMode && _loadedEnvelopeId != EnvelopeId)
|
||||
{
|
||||
await LoadEnvelopeForEditAsync(EnvelopeId!.Value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsEditMode)
|
||||
return;
|
||||
|
||||
// After the esid is set via query param, try to restore from cache.
|
||||
if (!string.IsNullOrWhiteSpace(Esid)
|
||||
&& MemoryCache.TryGetValue(SessionKey, out EditorSessionData? cached)
|
||||
@@ -525,6 +579,90 @@
|
||||
}
|
||||
}
|
||||
|
||||
async Task LoadEnvelopeForEditAsync(int envelopeId)
|
||||
{
|
||||
if (_isLoadingEnvelopeForEdit)
|
||||
return;
|
||||
|
||||
_isLoadingEnvelopeForEdit = true;
|
||||
_errorMessage = null;
|
||||
|
||||
try
|
||||
{
|
||||
var user = EnvelopeAuthService.GetCurrentUser();
|
||||
if (user?.Identity is null || !user.Identity.IsAuthenticated)
|
||||
throw new InvalidOperationException("Sender is not authenticated.");
|
||||
|
||||
var envelopes = await EnvelopeService.GetAsync(id: envelopeId);
|
||||
var envelope = envelopes?.FirstOrDefault()
|
||||
?? throw new InvalidOperationException($"Envelope {envelopeId} not found.");
|
||||
|
||||
var document = await ReceiverPageDataService.GetDocumentDetailsOfSenderAsync(user, envelopeId);
|
||||
if (document.ByteData is null || document.ByteData.Length == 0)
|
||||
throw new InvalidOperationException($"Document for envelope {envelopeId} is missing.");
|
||||
|
||||
_envelopeTitle = envelope.Title ?? string.Empty;
|
||||
_envelopeMessage = envelope.Message ?? string.Empty;
|
||||
|
||||
var envelopeReceivers = envelope.EnvelopeReceivers?.ToList() ?? [];
|
||||
_receivers = envelopeReceivers
|
||||
.Select((er, index) => new ReceiverDraft(
|
||||
Guid.NewGuid(),
|
||||
er.Name ?? er.Receiver?.EmailAddress ?? $"Receiver {er.ReceiverId}",
|
||||
er.Receiver?.EmailAddress ?? string.Empty,
|
||||
string.Empty,
|
||||
ReceiverPalette[index % ReceiverPalette.Length]))
|
||||
.ToList();
|
||||
|
||||
var receiverById = envelopeReceivers.ToDictionary(er => er.ReceiverId, er => er);
|
||||
var colorByEmail = _receivers
|
||||
.Where(r => !string.IsNullOrWhiteSpace(r.Email))
|
||||
.GroupBy(r => r.Email, StringComparer.OrdinalIgnoreCase)
|
||||
.ToDictionary(g => g.Key, g => g.First().Color, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
_signatureFields = (document.Elements ?? Enumerable.Empty<DocReceiverElementDto>())
|
||||
.Select(element =>
|
||||
{
|
||||
receiverById.TryGetValue(element.ReceiverId, out var envelopeReceiver);
|
||||
|
||||
var receiverName = envelopeReceiver?.Name
|
||||
?? envelopeReceiver?.Receiver?.EmailAddress
|
||||
?? element.FullName
|
||||
?? $"Receiver {element.ReceiverId}";
|
||||
|
||||
var receiverEmail = envelopeReceiver?.Receiver?.EmailAddress;
|
||||
var color = (!string.IsNullOrWhiteSpace(receiverEmail) && colorByEmail.TryGetValue(receiverEmail, out var receiverColor))
|
||||
? receiverColor
|
||||
: ReceiverPalette[Math.Abs(element.ReceiverId) % ReceiverPalette.Length];
|
||||
|
||||
return new SignatureFieldDraft(
|
||||
XPt: element.X * 72.0,
|
||||
YPt: element.Y * 72.0,
|
||||
Page: element.Page,
|
||||
ReceiverName: receiverName,
|
||||
Color: color);
|
||||
})
|
||||
.ToList();
|
||||
|
||||
_originalPdfBytes = document.ByteData;
|
||||
_pdfBytes = DrawPlaceholders(_originalPdfBytes, _signatureFields);
|
||||
_pdfLoaded = true;
|
||||
_fileName = $"Envelope-{envelopeId}.pdf";
|
||||
_pendingReceiverForPlacement = null;
|
||||
_loadedEnvelopeId = envelopeId;
|
||||
_editingEnvelopeId = envelopeId;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Failed to load envelope {EnvelopeId} for edit mode", envelopeId);
|
||||
_errorMessage = $"Fehler beim Laden des Umschlags #{envelopeId}: {ex.Message}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isLoadingEnvelopeForEdit = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ── PDF upload ──
|
||||
async Task OnPdfFileSelectedAsync(InputFileChangeEventArgs e)
|
||||
{
|
||||
@@ -670,8 +808,48 @@
|
||||
NavigationManager.NavigateTo("/sender");
|
||||
}
|
||||
|
||||
// ── Save — POST /api/EnvelopeReceiver ──
|
||||
async Task SaveAsync()
|
||||
async Task SaveDraftAsync()
|
||||
{
|
||||
_isSaving = true;
|
||||
_saveErrorMessage = null;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
try
|
||||
{
|
||||
var previousEnvelopeId = EnvelopeId;
|
||||
var command = BuildSaveDraftCommand();
|
||||
Logger.LogInformation("[SenderEditor] Saving draft. EnvelopeId={EnvelopeId} IsEditMode={IsEditMode}", command.EnvelopeId, IsEditMode);
|
||||
var result = await EnvelopeService.SaveDraftAsync(command);
|
||||
|
||||
if (result is null)
|
||||
throw new InvalidOperationException("Draft save did not return a result.");
|
||||
|
||||
EnvelopeId = result.EnvelopeId;
|
||||
_loadedEnvelopeId = result.EnvelopeId;
|
||||
_editingEnvelopeId = result.EnvelopeId;
|
||||
|
||||
if (previousEnvelopeId != result.EnvelopeId)
|
||||
{
|
||||
var sid = string.IsNullOrWhiteSpace(Esid) ? Guid.NewGuid().ToString("N") : Esid;
|
||||
NavigationManager.NavigateTo($"/sender/editor?esid={sid}&envelopeId={result.EnvelopeId}", forceLoad: false);
|
||||
}
|
||||
|
||||
NavigationManager.NavigateTo("/sender", forceLoad: false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "[SenderEditor] Failed to save draft");
|
||||
_saveErrorMessage = ex.Message;
|
||||
_savePopupVisible = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isSaving = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Send — POST /api/EnvelopeReceiver ──
|
||||
async Task SendAsync()
|
||||
{
|
||||
// ── Validation ──
|
||||
_titleTouched = true;
|
||||
@@ -763,6 +941,10 @@
|
||||
|
||||
// Success — show popup; GoToDashboard clears cache and navigates
|
||||
_saveErrorMessage = null;
|
||||
_saveSuccessHeader = "Umschlag erstellt";
|
||||
_saveSuccessTitle = "Umschlag wurde erfolgreich erstellt.";
|
||||
_saveSuccessBody = "Der Umschlag wurde gespeichert und die Empfänger wurden benachrichtigt.";
|
||||
_saveNavigateToDashboard = true;
|
||||
_savePopupVisible = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -777,6 +959,70 @@
|
||||
}
|
||||
}
|
||||
|
||||
SaveEnvelopeDraftCommand BuildSaveDraftCommand()
|
||||
{
|
||||
var envelopeId = _editingEnvelopeId
|
||||
?? EnvelopeId
|
||||
?? _loadedEnvelopeId
|
||||
?? GetEnvelopeIdFromCurrentUri();
|
||||
|
||||
if (IsEditMode && envelopeId is null)
|
||||
throw new InvalidOperationException("Edit mode envelope id is missing.");
|
||||
|
||||
var docBase64 = _originalPdfBytes is { Length: > 0 }
|
||||
? Convert.ToBase64String(_originalPdfBytes)
|
||||
: null;
|
||||
|
||||
var receiverCommands = _receivers.Select(receiver =>
|
||||
{
|
||||
var fields = _signatureFields
|
||||
.Where(f => f.ReceiverName == receiver.FullName)
|
||||
.Select(f => new SaveEnvelopeDraftFieldDto(
|
||||
X: f.XPt / 72.0,
|
||||
Y: f.YPt / 72.0,
|
||||
Page: f.Page))
|
||||
.ToList();
|
||||
|
||||
return new SaveEnvelopeDraftReceiverDto(
|
||||
EmailAddress: receiver.Email,
|
||||
Name: receiver.FullName,
|
||||
PhoneNumber: string.IsNullOrWhiteSpace(receiver.PhoneNumber) ? null : receiver.PhoneNumber,
|
||||
Fields: fields);
|
||||
}).ToList();
|
||||
|
||||
return new SaveEnvelopeDraftCommand
|
||||
{
|
||||
EnvelopeId = envelopeId,
|
||||
Title = _envelopeTitle,
|
||||
Message = _envelopeMessage,
|
||||
TFAEnabled = false,
|
||||
Document = string.IsNullOrWhiteSpace(docBase64) ? null : new SaveEnvelopeDraftDocumentDto(docBase64),
|
||||
Receivers = receiverCommands
|
||||
};
|
||||
}
|
||||
|
||||
int? GetEnvelopeIdFromCurrentUri()
|
||||
{
|
||||
try
|
||||
{
|
||||
var uri = NavigationManager.ToAbsoluteUri(NavigationManager.Uri);
|
||||
if (string.IsNullOrWhiteSpace(uri.Query))
|
||||
return null;
|
||||
|
||||
var query = QueryHelpers.ParseQuery(uri.Query);
|
||||
if (!query.TryGetValue("envelopeId", out var raw))
|
||||
return null;
|
||||
|
||||
return int.TryParse(raw.ToString(), out var id) && id > 0
|
||||
? id
|
||||
: null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Cache persistence ──
|
||||
void PersistSession()
|
||||
{
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
Neuer Umschlag
|
||||
</button>
|
||||
|
||||
<button class="sender-btn" @onclick="EditEnvelope" disabled="@(_selectedEnvelope == null || IsEnvelopeSent(_selectedEnvelope))" title="Ausgewählten Umschlag bearbeiten">
|
||||
<button class="sender-btn" @onclick="EditEnvelope" disabled="@(_selectedEnvelope == null || !IsEnvelopeEditable(_selectedEnvelope))" title="Ausgewählten Umschlag bearbeiten">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" 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-10zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z" />
|
||||
</svg>
|
||||
@@ -728,7 +728,9 @@
|
||||
void EditEnvelope()
|
||||
{
|
||||
if (_selectedEnvelope == null) return;
|
||||
// TODO: Navigate to envelope editor
|
||||
if (!IsEnvelopeEditable(_selectedEnvelope)) return;
|
||||
|
||||
Navigation.NavigateTo($"/sender/editor?envelopeId={_selectedEnvelope.Id}");
|
||||
}
|
||||
|
||||
void DeleteEnvelope()
|
||||
@@ -796,6 +798,12 @@
|
||||
return status >= EnvelopeStatus.EnvelopeQueued;
|
||||
}
|
||||
|
||||
bool IsEnvelopeEditable(EnvelopeDto envelope)
|
||||
{
|
||||
var status = (EnvelopeStatus)envelope.Status;
|
||||
return status is EnvelopeStatus.EnvelopeCreated or EnvelopeStatus.EnvelopeSaved;
|
||||
}
|
||||
|
||||
(string Label, string CssClass, string DotColor) GetStatusInfo(EnvelopeStatus status)
|
||||
{
|
||||
return status switch
|
||||
|
||||
@@ -49,6 +49,14 @@ public class EnvelopeReceiverPageDataService(
|
||||
: throw new NotFoundException($"Document for envelope {envelopeId} not found.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the full document DTO (including receiver elements) for the authenticated sender.
|
||||
/// </summary>
|
||||
public async Task<DocumentDto> GetDocumentDetailsOfSenderAsync(ClaimsPrincipal user, int envelopeId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await mediator.Send(new ReadDocumentQuery(EnvelopeId: envelopeId, UserId: user.SenderId()), cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the current receiver's signature placeholders.
|
||||
/// </summary>
|
||||
|
||||
@@ -542,6 +542,40 @@ body.resizing {
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Draft save button for sender editor */
|
||||
.pdf-toolbar__btn--draft {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
min-width: auto;
|
||||
padding: 0.3rem 0.75rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
color: #0f4c81;
|
||||
border: 1px solid rgba(14, 165, 233, 0.34);
|
||||
background: linear-gradient(135deg, rgba(14, 165, 233, 0.12) 0%, rgba(2, 132, 199, 0.1) 100%);
|
||||
}
|
||||
|
||||
.pdf-toolbar__btn--draft:hover:not(:disabled) {
|
||||
color: #ffffff;
|
||||
border-color: transparent;
|
||||
background: linear-gradient(135deg, #0ea5e9 0%, #0284c7 100%);
|
||||
box-shadow: 0 4px 12px rgba(2, 132, 199, 0.28);
|
||||
}
|
||||
|
||||
.pdf-toolbar__btn--draft:disabled {
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.pdf-toolbar__btn--draft svg {
|
||||
flex-shrink: 0;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.pdf-toolbar__btn--draft .pdf-toolbar__btn-text {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
/* Signature Change Button */
|
||||
.pdf-toolbar__btn--signature-change {
|
||||
display: flex;
|
||||
|
||||
Reference in New Issue
Block a user