Merge branch 'feat/migr-DxReportViewer' of http://git.dd:3000/AppStd/EnvelopeGenerator into feat/migr-DxReportViewer

This commit is contained in:
2026-07-01 13:59:29 +02:00
2 changed files with 126 additions and 19 deletions

View File

@@ -1,5 +1,6 @@
@page "/"
@inject IJSRuntime JS
@rendermode InteractiveWebAssembly
<link href="_content/DevExpress.Blazor.Themes/blazing-berry.bs5.min.css" rel="stylesheet" />

View File

@@ -9,6 +9,7 @@
@using System.Security.Claims
@inject NavigationManager Navigation
@inject IJSRuntime JSRuntime
@inject EnvelopeGenerator.Server.Client.Services.AuthService AuthService
@inject EnvelopeGenerator.Server.Services.EnvelopeReceiverAuthorizationService ReceiverAuthorizationService
@inject EnvelopeGenerator.Server.Services.EnvelopeReceiverPageDataService PageDataService
@inject AppVersionService AppVersion
@@ -42,6 +43,20 @@
<div style="font-size: 0.9rem; font-weight: 600; color: #1f2937;">Signiertes Dokument</div>
}
</div>
@* Right: Submit button *@
<div style="flex: 0 0 auto;">
<button class="pdf-toolbar__btn pdf-toolbar__btn--signature-change pdf-toolbar__btn--signature-change-active"
@onclick="OpenSubmitConfirmPopup"
disabled="@_isLoggingOut"
title="Abschließen"
style="flex-shrink: 0;">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" 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>
<span class="pdf-toolbar__btn-text">Abschließen</span>
</button>
</div>
</div>
</div>
</div>
@@ -82,6 +97,53 @@
</div>
</div>
@* Submit confirmation popup *@
<DxPopup @bind-Visible="_submitConfirmVisible"
HeaderText="Unterschrift bestätigen"
Width="440px"
MaxWidth="95vw"
ShowFooter="true"
CloseOnOutsideClick="false"
ShowCloseButton="false"
CloseOnEscape="false">
<BodyContentTemplate>
<div style="display: flex; align-items: flex-start; gap: 1rem; padding: 0.5rem 0;">
<div style="flex-shrink: 0; width: 40px; height: 40px; background: #d1fae5; border-radius: 50%; 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">
<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>
</div>
<div>
<p style="margin: 0 0 0.4rem; font-weight: 600; color: #1f2937; font-size: 0.95rem;">
Möchten Sie das Dokument verbindlich unterschreiben?
</p>
<p style="margin: 0; color: #6b7280; font-size: 0.85rem; line-height: 1.5;">
Diese Aktion kann nicht rückgängig gemacht werden. Mit der Bestätigung erklären Sie, das oben angezeigte Dokument elektronisch unterzeichnet zu haben. Das unterzeichnete Dokument wird anschließend an alle beteiligten Parteien übermittelt.
</p>
</div>
</div>
</BodyContentTemplate>
<FooterContentTemplate>
<div class="d-flex gap-2 justify-content-end w-100" style="padding: 0.5rem 0;">
<button class="btn btn-outline-secondary"
@onclick="() => _submitConfirmVisible = false"
style="border-radius: 6px; padding: 0.5rem 1.25rem; font-weight: 500;">
Abbrechen
</button>
<button class="btn btn-primary"
@onclick="SubmitAndLogoutAsync"
disabled="@_isLoggingOut"
style="background: linear-gradient(135deg, #059669 0%, #047857 100%); border: none; border-radius: 6px; padding: 0.5rem 1.5rem; font-weight: 600; box-shadow: 0 2px 4px rgba(5, 150, 105, 0.3);">
@if (_isLoggingOut)
{
<span class="spinner-border spinner-border-sm me-1" role="status"></span>
}
Abschließen
</button>
</div>
</FooterContentTemplate>
</DxPopup>
@code {
[Parameter] public string? EnvelopeKey { get; set; }
@@ -96,6 +158,22 @@
XtraReport? _report;
SignatureCaptureDto? _sig;
// ----- Submit / logout state -----
bool _isLoggingOut = false;
bool _submitConfirmVisible = false;
void OpenSubmitConfirmPopup() => _submitConfirmVisible = true;
async Task SubmitAndLogoutAsync()
{
if (_isLoggingOut) return;
_isLoggingOut = true;
_submitConfirmVisible = false;
await InvokeAsync(StateHasChanged);
await AuthService.LogoutEnvelopeReceiverAsync(EnvelopeKey!);
Navigation.NavigateTo("/", forceLoad: true);
}
protected override async Task OnInitializedAsync()
{
if (string.IsNullOrWhiteSpace(EnvelopeKey))
@@ -120,6 +198,18 @@
_sig = cached;
}
// Cache miss or missing sid — redirect back to report page
if (_sig is null)
{
Logger.LogWarning(
"[SignedPage] Cache miss or no sid={Sid} for {EnvelopeKey}, redirecting to report page.",
Sid, EnvelopeKey);
Navigation.NavigateTo(
$"/envelope/{Uri.EscapeDataString(EnvelopeKey)}/report",
forceLoad: true);
return;
}
try
{
var pdfBytes = await PageDataService.GetDocumentAsync(_receiverUser);
@@ -208,13 +298,17 @@
const double sigW = 1.77 * 72; // 127.44 pt
const double sigH = 1.96 * 72; // 141.12 pt
const double imgRatio = 0.60; // top 60% = image
const double textRatio = 0.38; // bottom 38% = text (2% padding)
const double imgRatio = 0.52; // top 52% = image
const double lineH = 11.5; // fixed row height matching font size (bold 7.5pt + normal 6.5pt)
const double bgPad = 3.0; // background box padding around content (pt)
var black = PdfSharp.Drawing.XColor.FromArgb(255, 20, 20, 20);
var darkGray = PdfSharp.Drawing.XColor.FromArgb(255, 80, 80, 80);
var lineColor = PdfSharp.Drawing.XColor.FromArgb(180, 100, 100, 120);
var bgColor = PdfSharp.Drawing.XColor.FromArgb(255, 255, 253, 240);
var bgBrush = new PdfSharp.Drawing.XSolidBrush(bgColor);
var fontBold = new PdfSharp.Drawing.XFont("Arial", 7.5, PdfSharp.Drawing.XFontStyleEx.Bold);
var fontNormal = new PdfSharp.Drawing.XFont("Arial", 6.5, PdfSharp.Drawing.XFontStyleEx.Regular);
var linePen = new PdfSharp.Drawing.XPen(lineColor, 0.5);
@@ -239,40 +333,52 @@
double x = field.X;
double y = field.Y;
// --- Image area ---
// --- Calculate layout positions first (needed for bg rect) ---
double imgH = sigH * imgRatio;
var imgRect = new PdfSharp.Drawing.XRect(x, y, sigW, imgH);
double lineY = y + imgH + 1.0; // 1pt gap between image and separator
double textY = lineY + 1.5; // 1.5pt gap below separator line
double padding = 3;
// Row 1: FullName
double row1Y = textY;
// Row 2: Position (optional)
double row2Y = row1Y + lineH;
// Row 3: Place, Date — immediately after row2 regardless of position
double row3Y = !string.IsNullOrWhiteSpace(sig.Position) ? row2Y + lineH : row2Y;
double contentBottom = row3Y + lineH;
// --- Background rectangle sized to actual content (not full sigH) ---
var bgRect = new PdfSharp.Drawing.XRect(
x - bgPad,
y - bgPad,
sigW + bgPad * 2,
(contentBottom - y) + bgPad * 2);
gfx.DrawRectangle(bgBrush, bgRect);
// --- Image area ---
var imgRect = new PdfSharp.Drawing.XRect(x, y, sigW, imgH);
using var imgStream = new System.IO.MemoryStream(imgBytes);
var xImg = PdfSharp.Drawing.XImage.FromStream(imgStream);
gfx.DrawImage(xImg, imgRect);
// --- Separator line ---
double lineY = y + imgH + sigH * 0.01;
gfx.DrawLine(linePen, x + 2, lineY, x + sigW - 2, lineY);
// --- Text area ---
double textY = lineY + 2;
double textH = sigH * textRatio;
double lineH = textH / 3.5; // max 3 text rows
double padding = 3;
// --- Text rows ---
// Row 1: FullName (bold)
var nameRect = new PdfSharp.Drawing.XRect(x + padding, textY, sigW - padding * 2, lineH);
var nameRect = new PdfSharp.Drawing.XRect(x + padding, row1Y, sigW - padding * 2, lineH);
gfx.DrawString(sig.FullName, fontBold, new PdfSharp.Drawing.XSolidBrush(black), nameRect, fmtLeft);
// Row 2: Position (optional)
double row2Y = textY + lineH;
if (!string.IsNullOrWhiteSpace(sig.Position))
{
var posRect = new PdfSharp.Drawing.XRect(x + padding, row2Y, sigW - padding * 2, lineH);
gfx.DrawString(sig.Position, fontNormal, new PdfSharp.Drawing.XSolidBrush(darkGray), posRect, fmtLeft);
row2Y += lineH;
}
// Row 3 (or 2 if no position): Place, Date
// Row 3: Place, Date
var placeDate = $"{sig.Place}, {date}";
var dateRect = new PdfSharp.Drawing.XRect(x + padding, row2Y, sigW - padding * 2, lineH);
var dateRect = new PdfSharp.Drawing.XRect(x + padding, row3Y, sigW - padding * 2, lineH);
gfx.DrawString(placeDate, fontNormal, new PdfSharp.Drawing.XSolidBrush(darkGray), dateRect, fmtLeft);
}