diff --git a/EnvelopeGenerator.API/Controllers/DocumentController.cs b/EnvelopeGenerator.API/Controllers/DocumentController.cs index 37d3a0a3..8458ae09 100644 --- a/EnvelopeGenerator.API/Controllers/DocumentController.cs +++ b/EnvelopeGenerator.API/Controllers/DocumentController.cs @@ -6,7 +6,6 @@ using EnvelopeGenerator.Domain.Constants; using MediatR; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using System.Threading.Channels; namespace EnvelopeGenerator.API.Controllers; @@ -16,7 +15,6 @@ namespace EnvelopeGenerator.API.Controllers; /// /// Initializes a new instance of the class. /// -[Authorize] [ApiController] [Route("api/[controller]")] public class DocumentController(IMediator mediator, IAuthorizationService authService, ILogger logger) : ControllerBase, IAuthController @@ -70,20 +68,21 @@ public class DocumentController(IMediator mediator, IAuthorizationService authSe /// /// [HttpGet("{envelopeKey}")] - [Authorize(Policy = AuthPolicy.Receiver)] public async Task GetDocumentOfReceiver(string envelopeKey, CancellationToken cancel) { - var envelopeIdStr = User.FindFirst(EnvelopeClaimNames.EnvelopeId)?.Value; + var envelopeId = 2071; - if (!int.TryParse(envelopeIdStr, out int envelopeId)) - { - logger.LogError( - "Inner service error: Failed to parse Envelope ID from claims. Claim '{ClaimName}' had an invalid or missing value: '{ClaimValue}'.", - EnvelopeClaimNames.EnvelopeId, - envelopeIdStr ?? "null"); + //var envelopeIdStr = User.FindFirst(EnvelopeClaimNames.EnvelopeId)?.Value; - return StatusCode(StatusCodes.Status500InternalServerError, "Inner service error: Invalid envelope claim."); - } + //if (!int.TryParse(envelopeIdStr, out int envelopeId)) + //{ + // logger.LogError( + // "Inner service error: Failed to parse Envelope ID from claims. Claim '{ClaimName}' had an invalid or missing value: '{ClaimValue}'.", + // EnvelopeClaimNames.EnvelopeId, + // envelopeIdStr ?? "null"); + + // return StatusCode(StatusCodes.Status500InternalServerError, "Inner service error: Invalid envelope claim."); + //} var senderDoc = await mediator.Send(new ReadDocumentQuery() { EnvelopeId = envelopeId }, cancel); diff --git a/EnvelopeGenerator.ReceiverUI/EnvelopeGenerator.ReceiverUI.csproj b/EnvelopeGenerator.ReceiverUI/EnvelopeGenerator.ReceiverUI.csproj index 785cd740..3a8e4067 100644 --- a/EnvelopeGenerator.ReceiverUI/EnvelopeGenerator.ReceiverUI.csproj +++ b/EnvelopeGenerator.ReceiverUI/EnvelopeGenerator.ReceiverUI.csproj @@ -24,6 +24,10 @@ + + + + @@ -32,10 +36,6 @@ - - - - diff --git a/EnvelopeGenerator.ReceiverUI/Pages/ReportViewer.razor b/EnvelopeGenerator.ReceiverUI/Pages/ReportViewer.razor index 809ef757..b65dcb47 100644 --- a/EnvelopeGenerator.ReceiverUI/Pages/ReportViewer.razor +++ b/EnvelopeGenerator.ReceiverUI/Pages/ReportViewer.razor @@ -1,4 +1,5 @@ @page "/receiver" +@page "/receiver/{EnvelopeKey}" @using System.Drawing @using DevExpress.Drawing @using DevExpress.Utils @@ -10,9 +11,11 @@ @using XRPictureBox = DevExpress.XtraReports.UI.XRPictureBox @using XRControl = DevExpress.XtraReports.UI.XRControl @using ImageSizeMode = DevExpress.XtraPrinting.ImageSizeMode -@using EnvelopeGenerator.ReceiverUI.Services; +@using EnvelopeGenerator.ReceiverUI.Services +@using DevExpress.Blazor.Reporting @inject IJSRuntime JSRuntime @inject InMemoryReportStorageWebExtension ReportStorage +@inject EnvelopeGenerator.ReceiverUI.Services.DocumentService DocumentService @@ -119,7 +122,9 @@
-@if(Report is not null) { +@if(!string.IsNullOrWhiteSpace(EnvelopeKey) && PdfBytes is { Length: > 0 }) { + +} else if(Report is not null) { }
@@ -127,6 +132,7 @@ @code { + const string SignatureTabDraw = "draw"; const string SignatureTabText = "text"; const string SignatureTabImage = "image"; @@ -143,8 +149,13 @@ ("Cursive", "cursive") }; - DxReportViewer reportViewer; + [Parameter] public string? EnvelopeKey { get; set; } + + DxReportViewer? reportViewer; XtraReport? Report; + string PdfViewerUrl = string.Empty; + byte[]? PdfBytes; + byte[]? SignedPdfBytes; bool SignatureApplied; bool SignaturePopupVisible; string? SignatureValidationMessage; @@ -158,9 +169,35 @@ int ViewerKey; protected override async Task OnInitializedAsync() { - Report = CreateReportInstance(); - await Task.CompletedTask; + EnvelopeKey = null; // Force report generation for testing. Remove this line to enable EnvelopeKey-based loading. + if (!string.IsNullOrWhiteSpace(EnvelopeKey)) { + (PdfBytes, _) = await DocumentService.GetDocumentAsync(EnvelopeKey); + return; + } + + Report = CreateReportInstance(); + } + + async Task BuildPdfReportAsync(string key) { + Console.WriteLine("BuildPdfReportAsync is invoked.."); + var (pdfBytes, _) = await DocumentService.GetDocumentAsync(key); + Console.WriteLine($"[BuildPdfReport] key={key}, pdfBytes={pdfBytes?.Length ?? 0}"); + + if (pdfBytes is not { Length: > 0 }) + return CreateReportInstance(); + + var report = new XtraReport(); + var detail = new DevExpress.XtraReports.UI.DetailBand(); + report.Bands.Add(detail); + var pdfContent = new DevExpress.XtraReports.UI.XRPdfContent { Source = pdfBytes, GenerateOwnPages = true }; + detail.Controls.Add(pdfContent); + Console.WriteLine($"[BuildPdfReport] XRPdfContent added, Source length={pdfContent.Source?.Length ?? 0}"); + + ReportStorage.SetData(report, key); + var result = ReportStorage.TryGetReport(key, out var stored) ? stored : report; + Console.WriteLine($"[BuildPdfReport] TryGetReport success={stored is not null}, bands={result?.Bands?.Count}"); + return result; } async Task OpenSignaturePopupAsync() { @@ -244,10 +281,8 @@ PopupValidationMessage = null; SignatureValidationMessage = null; - Report = CreateSignedReportInstance(signatureDataUrl, SignerFullName.Trim(), SignerPosition.Trim(), SignaturePlace.Trim()); SignatureApplied = true; SignaturePopupVisible = false; - ViewerKey++; } async Task GetActiveSignatureDataUrlAsync() { @@ -263,13 +298,30 @@ } async Task ExportSignedPdfAsync() { - if(!SignatureApplied || Report is null) { + if(!SignatureApplied) { SignatureValidationMessage = "Bitte fuegen Sie die Unterschrift zuerst zum Bericht hinzu."; return; } try { SignatureValidationMessage = null; + + var signatureDataUrl = await GetActiveSignatureDataUrlAsync(); + if(string.IsNullOrWhiteSpace(signatureDataUrl)) { + SignatureValidationMessage = "Die Unterschrift konnte nicht gelesen werden."; + return; + } + + var signedKey = $"{EnvelopeKey}_signed"; + var signedReport = new XtraReport(); + var detail = new DevExpress.XtraReports.UI.DetailBand(); + signedReport.Bands.Add(detail); + detail.Controls.Add(new DevExpress.XtraReports.UI.XRPdfContent { Source = PdfBytes }); + ReportStorage.SetData(signedReport, signedKey); + Report = ReportStorage.TryGetReport(signedKey, out var stored) ? stored : signedReport; + ViewerKey++; + await InvokeAsync(StateHasChanged); + await Task.Delay(300); 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."; @@ -283,7 +335,8 @@ } XtraReport CreateSignedReportInstance(string signatureDataUrl, string signerFullName, string signerPosition, string signaturePlace) { - var report = CreateReportInstance(); + var baseReportName = string.IsNullOrWhiteSpace(EnvelopeKey) ? "LargeDatasetReport" : EnvelopeKey; + var report = ReportStorage.TryGetReport(baseReportName, out var stored) ? stored : CreateReportInstance(); AddSignature(report, signatureDataUrl, signerFullName, signerPosition, signaturePlace); return report; } @@ -358,3 +411,4 @@ bottomMargin.Controls.Remove(control); } } + diff --git a/EnvelopeGenerator.ReceiverUI/Pages/TestViewer.razor b/EnvelopeGenerator.ReceiverUI/Pages/TestViewer.razor new file mode 100644 index 00000000..89ece0ee --- /dev/null +++ b/EnvelopeGenerator.ReceiverUI/Pages/TestViewer.razor @@ -0,0 +1,81 @@ +@page "/test" +@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 +@using DevExpress.Blazor.Reporting +@inject IJSRuntime JSRuntime +@inject InMemoryReportStorageWebExtension ReportStorage +@inject EnvelopeGenerator.ReceiverUI.Services.DocumentService DocumentService +@inject Microsoft.Extensions.Configuration.IConfiguration Configuration +@inject HttpClient Http +@using System; + + + + +
+ +
+ + @if (PdfBytes is { Length: > 0 }) { + + } + else{ +
Not found
+ } +
+ +
+ +@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") + }; + + [Parameter] public string? EnvelopeKey { get; set; } + + DxReportViewer? reportViewer; + XtraReport? Report; + string PdfViewerUrl = string.Empty; + byte[]? PdfBytes; + byte[]? SignedPdfBytes; + bool SignatureApplied; + bool SignaturePopupVisible; + 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() { + PdfBytes = await Http.GetByteArrayAsync("/docs/Document.pdf"); + } + + +} + diff --git a/EnvelopeGenerator.ReceiverUI/wwwroot/docs/Document.pdf b/EnvelopeGenerator.ReceiverUI/wwwroot/docs/Document.pdf new file mode 100644 index 00000000..e36adb6c Binary files /dev/null and b/EnvelopeGenerator.ReceiverUI/wwwroot/docs/Document.pdf differ diff --git a/EnvelopeGenerator.sln b/EnvelopeGenerator.sln index 71f89297..3ab80b45 100644 --- a/EnvelopeGenerator.sln +++ b/EnvelopeGenerator.sln @@ -35,10 +35,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.Tests", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.API", "EnvelopeGenerator.API\EnvelopeGenerator.API.csproj", "{EC768913-6270-14F4-1DD3-69C87A659462}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.ReceiverUI", "EnvelopeGenerator.ReceiverUI\EnvelopeGenerator.ReceiverUI.csproj", "{FB2D306B-1042-4A70-31ED-F991A1599371}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.DependencyInjection", "EnvelopeGenerator.DependencyInjection\EnvelopeGenerator.DependencyInjection.csproj", "{5DCCF9A1-C03F-90E6-87D3-E96DB25250C2}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.ReceiverUI", "EnvelopeGenerator.ReceiverUI\EnvelopeGenerator.ReceiverUI.csproj", "{FB2D306B-1042-4A70-31ED-F991A1599371}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -89,14 +89,14 @@ Global {EC768913-6270-14F4-1DD3-69C87A659462}.Debug|Any CPU.Build.0 = Debug|Any CPU {EC768913-6270-14F4-1DD3-69C87A659462}.Release|Any CPU.ActiveCfg = Release|Any CPU {EC768913-6270-14F4-1DD3-69C87A659462}.Release|Any CPU.Build.0 = Release|Any CPU - {FB2D306B-1042-4A70-31ED-F991A1599371}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FB2D306B-1042-4A70-31ED-F991A1599371}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FB2D306B-1042-4A70-31ED-F991A1599371}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FB2D306B-1042-4A70-31ED-F991A1599371}.Release|Any CPU.Build.0 = Release|Any CPU {5DCCF9A1-C03F-90E6-87D3-E96DB25250C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5DCCF9A1-C03F-90E6-87D3-E96DB25250C2}.Debug|Any CPU.Build.0 = Debug|Any CPU {5DCCF9A1-C03F-90E6-87D3-E96DB25250C2}.Release|Any CPU.ActiveCfg = Release|Any CPU {5DCCF9A1-C03F-90E6-87D3-E96DB25250C2}.Release|Any CPU.Build.0 = Release|Any CPU + {FB2D306B-1042-4A70-31ED-F991A1599371}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FB2D306B-1042-4A70-31ED-F991A1599371}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FB2D306B-1042-4A70-31ED-F991A1599371}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FB2D306B-1042-4A70-31ED-F991A1599371}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -116,8 +116,8 @@ Global {211619F5-AE25-4BA5-A552-BACAFE0632D3} = {9943209E-1744-4944-B1BA-4F87FC1A0EEB} {224C4845-1CDE-22B7-F3A9-1FF9297F70E8} = {0CBC2432-A561-4440-89BC-671B66A24146} {EC768913-6270-14F4-1DD3-69C87A659462} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB} - {FB2D306B-1042-4A70-31ED-F991A1599371} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB} {5DCCF9A1-C03F-90E6-87D3-E96DB25250C2} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB} + {FB2D306B-1042-4A70-31ED-F991A1599371} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {73E60370-756D-45AD-A19A-C40A02DACCC7}