Added input fields for "Full Name," "Position," and "Place" in `ReportViewer.razor` to collect additional user details. Introduced validation for required fields ("Full Name" and "Place") in `ApplySignatureAsync`. Updated `CreateSignedReportInstance` and `AddSignature` to include these details in the signature label, which now dynamically displays full name, position (if provided), place, and date. Adjusted layout and bounds for proper alignment and spacing in the report.
332 lines
15 KiB
Plaintext
332 lines
15 KiB
Plaintext
@page "/reportviewer/"
|
|
@using System.Drawing
|
|
@using DevExpress.Drawing
|
|
@using DevExpress.Utils
|
|
@using DevExpress.XtraPrinting
|
|
@using DevExpress.XtraPrinting.Drawing
|
|
@using XtraReport = DevExpress.XtraReports.UI.XtraReport
|
|
@using BottomMarginBand = DevExpress.XtraReports.UI.BottomMarginBand
|
|
@using XRLabel = DevExpress.XtraReports.UI.XRLabel
|
|
@using XRPictureBox = DevExpress.XtraReports.UI.XRPictureBox
|
|
@using XRControl = DevExpress.XtraReports.UI.XRControl
|
|
@using ImageSizeMode = DevExpress.XtraPrinting.ImageSizeMode
|
|
@using EnvelopeGenerator.ReceiverUI.Services;
|
|
@inject IJSRuntime JSRuntime
|
|
@inject InMemoryReportStorageWebExtension ReportStorage
|
|
|
|
<link href="_content/DevExpress.Blazor.Themes/blazing-berry.bs5.min.css" rel="stylesheet" />
|
|
<link href="_content/DevExpress.Blazor.Reporting.Viewer/css/dx-blazor-reporting-components.bs5.css" rel="stylesheet" />
|
|
|
|
<div class="card m-3">
|
|
<div class="card-body">
|
|
<h5 class="card-title">Unterschrift</h5>
|
|
<p class="card-text text-muted mb-2">
|
|
@if(SignatureApplied) {
|
|
<span>Die Unterschrift wurde dem Bericht hinzugefuegt. Sie koennen die Unterschrift erneuern oder das signierte PDF exportieren.</span>
|
|
} else {
|
|
<span>Bitte fuegen Sie vor dem PDF-Export Ihre Unterschrift hinzu.</span>
|
|
}
|
|
</p>
|
|
@if(!string.IsNullOrWhiteSpace(SignatureValidationMessage)) {
|
|
<div class="text-danger mb-2">@SignatureValidationMessage</div>
|
|
}
|
|
<div class="d-flex gap-2 flex-wrap">
|
|
<button class="btn btn-primary" @onclick="OpenSignaturePopupAsync">
|
|
@(SignatureApplied ? "Unterschrift erneuern" : "Unterschrift hinzufuegen")
|
|
</button>
|
|
<button class="btn btn-success" disabled="@(!SignatureApplied)" @onclick="ExportSignedPdfAsync">Signiertes PDF exportieren</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<DxPopup @bind-Visible="SignaturePopupVisible"
|
|
HeaderText="Unterschrift erfassen"
|
|
Width="620px"
|
|
ShowFooter="true"
|
|
CloseOnEscape="true"
|
|
CloseOnOutsideClick="false">
|
|
<BodyContentTemplate>
|
|
<ul class="nav nav-tabs mb-3">
|
|
<li class="nav-item">
|
|
<button type="button" class="nav-link @(ActiveSignatureTab == SignatureTabDraw ? "active" : null)" @onclick="() => SetSignatureTabAsync(SignatureTabDraw)">Zeichnen</button>
|
|
</li>
|
|
<li class="nav-item">
|
|
<button type="button" class="nav-link @(ActiveSignatureTab == SignatureTabText ? "active" : null)" @onclick="() => SetSignatureTabAsync(SignatureTabText)">Text</button>
|
|
</li>
|
|
<li class="nav-item">
|
|
<button type="button" class="nav-link @(ActiveSignatureTab == SignatureTabImage ? "active" : null)" @onclick="() => SetSignatureTabAsync(SignatureTabImage)">Bild</button>
|
|
</li>
|
|
</ul>
|
|
|
|
@if(ActiveSignatureTab == SignatureTabDraw) {
|
|
<p class="text-muted mb-2">Bitte unterschreiben Sie im folgenden Feld.</p>
|
|
<canvas id="receiver-signature-pad" width="520" height="160" class="border rounded bg-white w-100" style="max-width: 520px; touch-action: none;"></canvas>
|
|
} else if(ActiveSignatureTab == SignatureTabText) {
|
|
<p class="text-muted mb-2">Geben Sie Ihre Unterschrift als Text ein und waehlen Sie eine Schriftart.</p>
|
|
<div class="row g-2 mb-2">
|
|
<div class="col-12 col-md-7">
|
|
<input class="form-control" placeholder="Ihre Unterschrift" value="@TypedSignatureText" @oninput="OnTypedSignatureChanged" />
|
|
</div>
|
|
<div class="col-12 col-md-5">
|
|
<select class="form-select" value="@TypedSignatureFont" @onchange="OnTypedSignatureFontChanged">
|
|
@foreach(var font in TypedSignatureFonts) {
|
|
<option value="@font.Value" style="font-family: @font.Value">@font.Text</option>
|
|
}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<canvas id="receiver-typed-signature-pad" width="520" height="160" class="border rounded bg-white w-100" style="max-width: 520px;"></canvas>
|
|
} else {
|
|
<p class="text-muted mb-2">Laden Sie ein Bild Ihrer Unterschrift hoch.</p>
|
|
<input id="receiver-signature-image-input" class="form-control mb-2" type="file" accept="image/png,image/jpeg,image/webp" />
|
|
<canvas id="receiver-image-signature-pad" width="520" height="160" class="border rounded bg-white w-100" style="max-width: 520px;"></canvas>
|
|
}
|
|
|
|
|
|
<div class="border-top mt-3 pt-3">
|
|
<p class="text-muted mb-2">Bitte geben Sie die folgenden Angaben ein. Das Datum wird automatisch hinzugefuegt.</p>
|
|
<div class="row g-2">
|
|
<div class="col-12 col-md-6">
|
|
<label class="form-label" for="receiver-signer-name">Vor- und Nachname *</label>
|
|
<input id="receiver-signer-name" class="form-control" value="@SignerFullName" @oninput="args => SignerFullName = args.Value?.ToString() ?? string.Empty" />
|
|
</div>
|
|
<div class="col-12 col-md-6">
|
|
<label class="form-label" for="receiver-signer-position">Position</label>
|
|
<input id="receiver-signer-position" class="form-control" value="@SignerPosition" @oninput="args => SignerPosition = args.Value?.ToString() ?? string.Empty" />
|
|
</div>
|
|
<div class="col-12 col-md-6">
|
|
<label class="form-label" for="receiver-signature-place">Ort *</label>
|
|
<input id="receiver-signature-place" class="form-control" value="@SignaturePlace" @oninput="args => SignaturePlace = args.Value?.ToString() ?? string.Empty" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@if(!string.IsNullOrWhiteSpace(PopupValidationMessage)) {
|
|
<div class="text-danger mt-2">@PopupValidationMessage</div>
|
|
}
|
|
</BodyContentTemplate>
|
|
<FooterContentTemplate>
|
|
<div class="d-flex gap-2 flex-wrap justify-content-end w-100">
|
|
<button class="btn btn-outline-secondary" @onclick="RenewSignatureAsync">Unterschrift erneuern</button>
|
|
<button class="btn btn-primary" @onclick="ApplySignatureAsync">Zum Bericht hinzufuegen</button>
|
|
<button class="btn btn-secondary" @onclick="CloseSignaturePopup">Schliessen</button>
|
|
</div>
|
|
</FooterContentTemplate>
|
|
</DxPopup>
|
|
|
|
@if(Report is not null) {
|
|
<DxReportViewer @key="ViewerKey" @ref="reportViewer" Report="Report" RootCssClasses="w-100 h-100" />
|
|
}
|
|
|
|
@code {
|
|
const string SignatureTabDraw = "draw";
|
|
const string SignatureTabText = "text";
|
|
const string SignatureTabImage = "image";
|
|
const string DrawCanvasId = "receiver-signature-pad";
|
|
const string TypedCanvasId = "receiver-typed-signature-pad";
|
|
const string ImageInputId = "receiver-signature-image-input";
|
|
const string ImageCanvasId = "receiver-image-signature-pad";
|
|
|
|
readonly (string Text, string Value)[] TypedSignatureFonts = {
|
|
("Brush Script", "'Brush Script MT', cursive"),
|
|
("Segoe Script", "'Segoe Script', cursive"),
|
|
("Lucida Handwriting", "'Lucida Handwriting', cursive"),
|
|
("Comic Sans", "'Comic Sans MS', cursive"),
|
|
("Cursive", "cursive")
|
|
};
|
|
|
|
DxReportViewer reportViewer;
|
|
XtraReport? Report;
|
|
bool SignatureApplied;
|
|
bool SignaturePopupVisible;
|
|
string? SignatureValidationMessage;
|
|
string? PopupValidationMessage;
|
|
string ActiveSignatureTab = SignatureTabDraw;
|
|
string TypedSignatureText = string.Empty;
|
|
string TypedSignatureFont = "'Brush Script MT', cursive";
|
|
string SignerFullName = string.Empty;
|
|
string SignerPosition = string.Empty;
|
|
string SignaturePlace = string.Empty;
|
|
int ViewerKey;
|
|
|
|
protected override async Task OnInitializedAsync() {
|
|
Report = CreateReportInstance();
|
|
|
|
await Task.CompletedTask;
|
|
}
|
|
|
|
async Task OpenSignaturePopupAsync() {
|
|
ActiveSignatureTab = SignatureTabDraw;
|
|
SignaturePopupVisible = true;
|
|
SignatureValidationMessage = null;
|
|
PopupValidationMessage = null;
|
|
await InvokeAsync(StateHasChanged);
|
|
await Task.Delay(50);
|
|
await InitializeActiveSignatureTabAsync();
|
|
}
|
|
|
|
async Task SetSignatureTabAsync(string tab) {
|
|
ActiveSignatureTab = tab;
|
|
PopupValidationMessage = null;
|
|
await InvokeAsync(StateHasChanged);
|
|
await Task.Delay(50);
|
|
await InitializeActiveSignatureTabAsync();
|
|
}
|
|
|
|
async Task InitializeActiveSignatureTabAsync() {
|
|
if(ActiveSignatureTab == SignatureTabDraw) {
|
|
await JSRuntime.InvokeVoidAsync("receiverSignature.initialize", DrawCanvasId);
|
|
} else if(ActiveSignatureTab == SignatureTabText) {
|
|
await JSRuntime.InvokeVoidAsync("receiverSignature.initializeTyped", TypedCanvasId);
|
|
await RenderTypedSignatureAsync();
|
|
} else {
|
|
await JSRuntime.InvokeVoidAsync("receiverSignature.initializeImage", ImageInputId, ImageCanvasId);
|
|
}
|
|
}
|
|
|
|
async Task RenewSignatureAsync() {
|
|
PopupValidationMessage = null;
|
|
|
|
if(ActiveSignatureTab == SignatureTabDraw) {
|
|
await JSRuntime.InvokeVoidAsync("receiverSignature.clear", DrawCanvasId);
|
|
} else if(ActiveSignatureTab == SignatureTabText) {
|
|
TypedSignatureText = string.Empty;
|
|
await JSRuntime.InvokeVoidAsync("receiverSignature.clearTyped", TypedCanvasId);
|
|
} else {
|
|
await JSRuntime.InvokeVoidAsync("receiverSignature.clearImage", ImageInputId, ImageCanvasId);
|
|
}
|
|
}
|
|
|
|
void CloseSignaturePopup() {
|
|
PopupValidationMessage = null;
|
|
SignaturePopupVisible = false;
|
|
}
|
|
|
|
async Task OnTypedSignatureChanged(Microsoft.AspNetCore.Components.ChangeEventArgs args) {
|
|
TypedSignatureText = args.Value?.ToString() ?? string.Empty;
|
|
await RenderTypedSignatureAsync();
|
|
}
|
|
|
|
async Task OnTypedSignatureFontChanged(Microsoft.AspNetCore.Components.ChangeEventArgs args) {
|
|
TypedSignatureFont = args.Value?.ToString() ?? TypedSignatureFont;
|
|
await RenderTypedSignatureAsync();
|
|
}
|
|
|
|
async Task RenderTypedSignatureAsync() {
|
|
await JSRuntime.InvokeVoidAsync("receiverSignature.renderTypedSignature", TypedCanvasId, TypedSignatureText, TypedSignatureFont);
|
|
}
|
|
|
|
async Task ApplySignatureAsync() {
|
|
if(string.IsNullOrWhiteSpace(SignerFullName)) {
|
|
PopupValidationMessage = "Bitte geben Sie Vor- und Nachname ein.";
|
|
return;
|
|
}
|
|
|
|
if(string.IsNullOrWhiteSpace(SignaturePlace)) {
|
|
PopupValidationMessage = "Bitte geben Sie den Ort ein.";
|
|
return;
|
|
}
|
|
|
|
var signatureDataUrl = await GetActiveSignatureDataUrlAsync();
|
|
|
|
if(string.IsNullOrWhiteSpace(signatureDataUrl)) {
|
|
PopupValidationMessage = "Die Unterschrift ist fuer den PDF-Export erforderlich.";
|
|
return;
|
|
}
|
|
|
|
PopupValidationMessage = null;
|
|
SignatureValidationMessage = null;
|
|
Report = CreateSignedReportInstance(signatureDataUrl, SignerFullName.Trim(), SignerPosition.Trim(), SignaturePlace.Trim());
|
|
SignatureApplied = true;
|
|
SignaturePopupVisible = false;
|
|
ViewerKey++;
|
|
}
|
|
|
|
async Task<string?> GetActiveSignatureDataUrlAsync() {
|
|
if(ActiveSignatureTab == SignatureTabDraw)
|
|
return await JSRuntime.InvokeAsync<string?>("receiverSignature.getDataUrl", DrawCanvasId);
|
|
|
|
if(ActiveSignatureTab == SignatureTabText) {
|
|
await RenderTypedSignatureAsync();
|
|
return await JSRuntime.InvokeAsync<string?>("receiverSignature.getTypedDataUrl", TypedCanvasId);
|
|
}
|
|
|
|
return await JSRuntime.InvokeAsync<string?>("receiverSignature.getImageDataUrl", ImageCanvasId);
|
|
}
|
|
|
|
async Task ExportSignedPdfAsync() {
|
|
if(!SignatureApplied || Report is null) {
|
|
SignatureValidationMessage = "Bitte fuegen Sie die Unterschrift zuerst zum Bericht hinzu.";
|
|
return;
|
|
}
|
|
|
|
try {
|
|
SignatureValidationMessage = null;
|
|
await reportViewer.ExportToAsync(ExportFormat.Pdf);
|
|
} catch(Exception) {
|
|
SignatureValidationMessage = "Das signierte PDF konnte nicht exportiert werden. Bitte laden Sie die Seite neu und versuchen Sie es erneut.";
|
|
}
|
|
}
|
|
|
|
XtraReport CreateReportInstance() {
|
|
return ReportStorage.TryGetReport("LargeDatasetReport", out var savedReport)
|
|
? savedReport
|
|
: PredefinedReports.ReportsFactory.GetReport("LargeDatasetReport");
|
|
}
|
|
|
|
XtraReport CreateSignedReportInstance(string signatureDataUrl, string signerFullName, string signerPosition, string signaturePlace) {
|
|
var report = CreateReportInstance();
|
|
AddSignature(report, signatureDataUrl, signerFullName, signerPosition, signaturePlace);
|
|
return report;
|
|
}
|
|
|
|
static void AddSignature(XtraReport report, string signatureDataUrl, string signerFullName, string signerPosition, string signaturePlace) {
|
|
var imageBytes = Convert.FromBase64String(signatureDataUrl[(signatureDataUrl.IndexOf(',') + 1)..]);
|
|
using var imageStream = new MemoryStream(imageBytes);
|
|
var imageSource = new ImageSource(DXImage.FromStream(imageStream));
|
|
var bottomMargin = report.Bands.OfType<BottomMarginBand>().FirstOrDefault();
|
|
|
|
if(bottomMargin is null) {
|
|
bottomMargin = new BottomMarginBand();
|
|
report.Bands.Add(bottomMargin);
|
|
}
|
|
|
|
bottomMargin.HeightF = Math.Max(bottomMargin.HeightF, 140F);
|
|
RemoveExistingSignature(bottomMargin);
|
|
|
|
var signatureInformation = string.IsNullOrWhiteSpace(signerPosition)
|
|
? $"Empfaengerunterschrift\nName: {signerFullName}\nOrt: {signaturePlace}\nDatum: {DateTime.Now:g}"
|
|
: $"Empfaengerunterschrift\nName: {signerFullName}\nPosition: {signerPosition}\nOrt: {signaturePlace}\nDatum: {DateTime.Now:g}";
|
|
|
|
var signatureLabel = new XRLabel {
|
|
Name = "receiverSignatureLabel",
|
|
Text = signatureInformation,
|
|
BoundsF = new RectangleF(390F, 6F, 230F, 48F),
|
|
Font = new DXFont("Open Sans", 8F, DXFontStyle.Bold),
|
|
ForeColor = System.Drawing.Color.FromArgb(73, 80, 87),
|
|
TextAlignment = TextAlignment.MiddleLeft
|
|
};
|
|
|
|
var signature = new XRPictureBox {
|
|
Name = "receiverSignatureImage",
|
|
ImageSource = imageSource,
|
|
BoundsF = new RectangleF(390F, 58F, 230F, 70F),
|
|
Sizing = ImageSizeMode.ZoomImage,
|
|
Borders = BorderSide.Bottom,
|
|
BorderColor = System.Drawing.Color.FromArgb(73, 80, 87)
|
|
};
|
|
|
|
bottomMargin.Controls.AddRange(new XRControl[] { signatureLabel, signature });
|
|
}
|
|
|
|
static void RemoveExistingSignature(BottomMarginBand bottomMargin) {
|
|
var controls = bottomMargin.Controls
|
|
.Cast<XRControl>()
|
|
.Where(control => control.Name is "receiverSignatureLabel" or "receiverSignatureImage")
|
|
.ToArray();
|
|
|
|
foreach(var control in controls)
|
|
bottomMargin.Controls.Remove(control);
|
|
}
|
|
}
|