Improve UI, fix signature placement, add documentation
Enhanced `ReportViewer.razor` with new UI elements, including a detailed `receiver-info-header` and `receiver-action-bar` for better signature workflows. Refactored signature logic to ensure accurate placement using `report.AfterPrint` and `PrintingSystem.Pages`. Removed legacy methods and iText7-based workflows. Added Turkish documentation (`COPILOT_CONTEXT_TR.md`) detailing project structure, workflows, and pending tasks. Updated `MainLayout.razor` to simplify layout. Improved styling in `app.css` for better visual hierarchy and readability. Documented pending tasks such as adding signature backgrounds, improving checkbox styles, and automating signature workflows.
This commit is contained in:
199
COPILOT_CONTEXT_TR.md
Normal file
199
COPILOT_CONTEXT_TR.md
Normal file
@@ -0,0 +1,199 @@
|
||||
# EnvelopeGenerator — Copilot Ba?lam Notlar? (Türkçe)
|
||||
|
||||
## Projenin Amac?
|
||||
Dijital belge imzalama sistemi. Göndericiler PDF yükleyip PSPDFKit üzerinden imza alan? (annotation) yerle?tirir. Al?c?lar Blazor WASM viewer'da belgeyi görür, annotation konumlar?nda checkbox overlay ile imza alanlar?n? onaylar, imzalar?n? olu?turur ve imzal? PDF'i export eder.
|
||||
|
||||
---
|
||||
|
||||
## Çözüm Yap?s?
|
||||
|
||||
| Proje | Hedef | Aç?klama |
|
||||
|---|---|---|
|
||||
| `EnvelopeGenerator.API` | net8.0 | Web API. Receiver auth (cookie), annotation okuma, PDF sunma. |
|
||||
| `EnvelopeGenerator.ReceiverUI` | net8.0 WASM | Blazor WebAssembly. Al?c? arayüzü. YARP proxy ile API'ye ba?lan?r. |
|
||||
| `EnvelopeGenerator.Web` | net7/8/9 | Razor Pages. Gönderen UI + PSPDFKit ile annotation yerle?tirme. |
|
||||
| `EnvelopeGenerator.Application` | multi | MediatR CQRS handler'lar?. |
|
||||
| `EnvelopeGenerator.Domain` | multi | Domain modelleri, sabitler, arayüzler. |
|
||||
| `EnvelopeGenerator.Infrastructure` | multi | EF Core repo'lar?, DB context. |
|
||||
| `EnvelopeGenerator.PdfEditor` | multi | iText7 PDF yard?mc?lar?. ReceiverUI ak???nda KULLANILMIYOR. |
|
||||
| `EnvelopeGenerator.DependencyInjection` | multi | DI kay?t yard?mc?lar?. |
|
||||
| VB.NET projeleri (Service/Form/BBTests) | net462 | Eski legacy. DOKUNMA. |
|
||||
|
||||
---
|
||||
|
||||
## Önemli Dosyalar
|
||||
|
||||
| Dosya | Amaç |
|
||||
|---|---|
|
||||
| `ReceiverUI/Pages/ReportViewer.razor` | Ana al?c? sayfas?. Tüm imzalama mant??? burada. |
|
||||
| `ReceiverUI/wwwroot/js/receiver-signature.js` | JS: checkbox overlay, imza pad (çizim/yaz?/resim). |
|
||||
| `ReceiverUI/wwwroot/fake-data/annotations.json` | Dev modda sahte annotation konumlar?. YARP proxy bu dosyaya yönlendirir. |
|
||||
| `ReceiverUI/Models/AnnotationDto.cs` | Annotation pozisyon modeli. Tüm property'ler non-nullable. |
|
||||
| `ReceiverUI/Services/AnnotationService.cs` | `List<AnnotationDto>` döner; gerçek modda API'den, dev modda fake-data'dan. |
|
||||
| `ReceiverUI/Services/DocumentService.cs` | PDF byte'lar?n? API'den al?r. |
|
||||
| `ReceiverUI/Services/AuthService.cs` | Al?c? session cookie'sini yönetir. |
|
||||
| `ReceiverUI/wwwroot/appsettings.json` | `ForceToUseFakeDocument: true` ? gerçek PDF yüklenmez, ?ablon rapor kullan?l?r. |
|
||||
| `API/Controllers/AnnotationController.cs` | GET `api/Annotation/{key}` ? annotation listesi. |
|
||||
| `API/Controllers/DocumentController.cs` | GET `api/Document/{key}` ? PDF byte'lar?. |
|
||||
|
||||
---
|
||||
|
||||
## AnnotationDto Koordinat Sistemi
|
||||
|
||||
```
|
||||
Birim : 1/100 inch (DX units) — DevExpress XtraReports'un yerel koordinat sistemi
|
||||
Köken : Sol-üst kö?e
|
||||
X artar : sa?a do?ru
|
||||
Y artar : a?a??ya do?ru
|
||||
|
||||
A4 boyutlar? DX units cinsinden: Geni?lik = 827, Yükseklik = 1169
|
||||
|
||||
Dönü?ümler:
|
||||
PSPDFKit (pt, sol-üst): xDX = xPsPdf * (100/72)
|
||||
GDPicture (pt, sol-alt): yDX = (pageHeightPt - yGD - elemHeightPt) * (100/72)
|
||||
DX ? PDF points: pt = dx * (72/100)
|
||||
PDF Y ekseni çevirme: imgBottomY = sayfaYüksekli?iPt - ann.Y*(72/100) - elemanYüksekli?iPt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ReceiverUI ?mzalama Ak??? (ReportViewer.razor)
|
||||
|
||||
### Sayfa Yüklenince (`OnInitializedAsync`)
|
||||
1. `AuthService.CheckEnvelopeAccessAsync` ? yetkisizse login sayfas?na yönlendir
|
||||
2. `AnnotationService.GetAnnotationsAsync` ? `_annotations` listesi dolar
|
||||
3. `DocumentService.GetDocumentAsync` ? `_basePdfBytes` dolar (gerçek mod)
|
||||
4. `BuildFreshBaseReport()` ? `XtraReport` olu?turulur, `DxReportViewer`'a verilir
|
||||
|
||||
### `BuildFreshBaseReport()` Mant???
|
||||
```
|
||||
_basePdfBytes dolu ? XtraReport + DetailBand + XRPdfContent { GenerateOwnPages = true }
|
||||
_basePdfBytes bo? (ForceToUseFakeDocument=true) ? ReportStorage'dan LargeDatasetReport ?ablonu (XtraReport, designer'dan geldi?i gibi)
|
||||
```
|
||||
|
||||
> NOT: `_basePdfBytes` dal? korunur (gerçek PDF modu). Dev ve test sunucusunda `PredefinedReport` (LargeDatasetReport) `XtraReport` olarak do?rudan kullan?l?r — PDF'e export ED?LMEZ.
|
||||
|
||||
### ?mza Popup'? ("Unterschrift erstellen")
|
||||
- Sekmeler: Çizim / Yaz? / Resim
|
||||
- Alanlar: Ad soyad (zorunlu), pozisyon (opsiyonel), yer (zorunlu)
|
||||
- `_capturedSignature` record'una kaydedilir
|
||||
- Annotation varsa popup kapan?r ? JS checkbox overlay kurulur
|
||||
|
||||
### JS Checkbox Overlay (`receiver-signature.js`)
|
||||
- `receiverSignature.installAnnotationCheckboxes(annotations, checkedIds, dotNetRef)` C#'tan ça?r?l?r
|
||||
- Her annotation için `.annot-sig-cb-wrapper` div'i, viewer scroll container'?na absolute olarak yerle?tirilir
|
||||
- **Koordinat hesab?:** `left = pageRect.left + ann.x * scaleX`, `top = pageRect.top + ann.y * scaleY`
|
||||
- `scaleX = sayfaPixelGeni?li?i / 827`, `scaleY = sayfaPixelYüksekli?i / 1169`
|
||||
- Bu koordinatlar sayfa-relatif ve do?ru çal???yor
|
||||
- T?klan?nca `dotNetRef.invokeMethodAsync('OnAnnotationToggled', id, checked)` ça?r?l?r
|
||||
|
||||
### ?mza Uygulama ("Unterschriften anwenden" — `SubmitSignaturesAsync`)
|
||||
|
||||
**Tek ortak yol (her iki mod):**
|
||||
- `SubmitSignaturesAsync` ? `BuildFreshBaseReport()` + `WireAnnotationSignatures(report, _capturedSignature)`
|
||||
- `WireAnnotationSignatures` ? `report.AfterPrint` olay?na abone olur
|
||||
- Belge olu?unca `report.PrintingSystem.Pages` dolu olur; her annotation için `Pages[ann.Page-1]` sayfas?na:
|
||||
- `ImageBrick { Image = imza görseli, Rect = (ann.X, ann.Y, 230, 70), SizeMode = ZoomImage }`
|
||||
- `TextBrick { Text = bilgi metni, Rect = (ann.X, ann.Y+75, 230, 65) }`
|
||||
- `Page.AddBrick(brick)` ile bas?l?r
|
||||
- Brick `Rect`'leri annotation `X`/`Y` (1/100 inch) ? checkbox overlay ile birebir ayn? koordinat, do?ru sayfa+konum
|
||||
- `ViewerKey++` ile viewer yenilenir
|
||||
|
||||
**Gerçek PDF modu (`_basePdfBytes` dolu):** Yukar?daki ile ayn?; rapor `XRPdfContent`'ten olu?ur, brick'ler yine `AfterPrint` ile `PrintingSystem.Pages` üzerine bas?l?r.
|
||||
|
||||
---
|
||||
|
||||
## ReceiverUI'daki NuGet Paketleri
|
||||
|
||||
| Paket | Versiyon | Amaç |
|
||||
|---|---|---|
|
||||
| `DevExpress.Blazor.Reporting.Viewer` | 25.2.3 | DxReportViewer bile?eni |
|
||||
| `DevExpress.Blazor.PdfViewer` | 25.2.3 | PDF görüntüleyici |
|
||||
| `DevExpress.Drawing.Skia` | 25.2.3 | Çizim backend'i |
|
||||
| `SkiaSharp.*` | 3.119.1 | WASM native render |
|
||||
|
||||
> Not: iText7, ReceiverUI imzalama ak???nda KULLANILMIYOR. ?mza yerle?tirme tamamen DevExpress XtraReports brick mekanizmas?yla (`PrintingSystem.Pages[i].AddBrick`) yap?l?r.
|
||||
|
||||
---
|
||||
|
||||
## GÖREV 1: ?mza Konum Hatas? (BUG) — ÇÖZÜLDÜ
|
||||
|
||||
### Kullan?c?n?n ?ste?i
|
||||
Annotation'lardan okunan sayfa ve X/Y koordinatlar?na göre, t?pk? checkbox overlay'ler gibi, imzalar do?ru sayfa ve konumda görünsün. `_basePdfBytes` dal? korunsun; dev/test'te designer ile olu?turulan `PredefinedReport` `XtraReport` olarak do?rudan kullan?lmaya devam etsin (PDF'e export yok).
|
||||
|
||||
### ÇÖZÜM (Oturum 12) ?
|
||||
`WireAnnotationSignatures` metodu, `report.AfterPrint` olay?nda `report.PrintingSystem.Pages[ann.Page-1].AddBrick(...)` ça??rarak imza görselini (`ImageBrick`) ve bilgi metnini (`TextBrick`) do?rudan hedef sayfaya, annotation `X`/`Y` (1/100 inch) konumunda basar.
|
||||
|
||||
**Neden çal???r:**
|
||||
- `AfterPrint`, belge tamamen olu?tuktan sonra tetiklenir; `PrintingSystem.Pages` art?k gerçek/nihai sayfalar? içerir.
|
||||
- Sayfa indeksleme (`Pages[ann.Page-1]`) band veya veri-sat?r? tekrar?ndan **ba??ms?zd?r** ? `LargeDatasetReport`'un veri-ba?l? `detailBand1` sorununu tamamen atlar.
|
||||
- Brick `Rect` koordinatlar? raporun yerel 1/100 inch sistemindedir ? checkbox overlay ile birebir ayn?, do?ru konum.
|
||||
- Yeni sayfa eklenmedi?i için sayfa say?s? katlanmaz (35 sayfa ? 35 sayfa).
|
||||
|
||||
**Derleme s?ras?nda ö?renilen API gerçekleri:**
|
||||
- `PrintOnPage` olay? `e.Page` VERMEZ (yaln?zca `PageIndex`/`PageCount`) ? brick eklenemez. Do?ru olay `AfterPrint` + `PrintingSystem.Pages`.
|
||||
- `Page` tipinde `InsertBrick` YOK; do?ru metot `Page.AddBrick(brick)` (brick'in `Rect`'i konumu belirler).
|
||||
- `ImageBrick.BorderStyle` tipi `BrickBorderStyle`'dir (`BorderDashStyle` de?il). Border için `Sides` + `BorderColor` kullan?ld?.
|
||||
|
||||
### Denenen Eski Çözümler (ba?ar?s?z — referans)
|
||||
|
||||
| Deneme | Yakla??m | Sonuç | Ba?ar?s?zl?k Sebebi |
|
||||
|---|---|---|---|
|
||||
| 1 | `BottomMarginBand` + `XRPictureBox`/`XRLabel` | Her sayfan?n alt?na ç?kt? | Band her sayfada tekrarlan?r, sayfa filtresi yok |
|
||||
| 2 | `BeforePrint` + `e.Graph?.PrintingSystem` | Derleme hatas? | `CancelEventArgs`'ta `Graph` yok |
|
||||
| 3–6 | `DetailBand` + `BeforePrint` counter | Yanl?? sayfa/konum | ?ablonun `detailBand1`'i veri sat?r? ba??na tetiklenir, sayfa ba??na de?il |
|
||||
| 7 | iText7 export/reload döngüsü | 35 ? 70 sayfa | Margin uyu?mazl???, `GenerateOwnPages` sayfalar? böldü |
|
||||
| 8 | Fake modda `BottomMarginBand` fallback | Her sayfan?n alt?nda | Koordinat yanl?? |
|
||||
|
||||
---
|
||||
|
||||
## YAPILMAMASI GEREKENLER
|
||||
|
||||
| Hata | Neden Yanl?? |
|
||||
|---|---|
|
||||
| `BottomMarginBand`/`DetailBand` ile sayfa-spesifik imza | Band veri-sat?r?/sayfa ba??na tekrarlan?r, koordinat kayar |
|
||||
| `BeforePrint` counter ile sayfa filtresi | Veri-ba?l? raporda sat?r ba??na tetiklenir, güvenilmez |
|
||||
| `PrintOnPage` ile brick ekleme | `e.Page` yok; brick eklenemez |
|
||||
| `Page.InsertBrick(...)` | Yok; do?ru metot `Page.AddBrick(...)` |
|
||||
| iText7 export+reload döngüsü | Margin uyu?mazl???ndan sayfa say?s? katlan?r |
|
||||
| ?ablonu PDF'e export edip `XRPdfContent`'e yükleme | ?stenmiyor; designer raporu do?rudan kullan?lmal? |
|
||||
| Stamplama için API endpoint ekleme | Gereksiz; brick'ler client'ta bas?l?r |
|
||||
|
||||
---
|
||||
|
||||
## BEKLEYEN D??ER GÖREVLER (Sonraki Chat'te Yap?lacak)
|
||||
|
||||
### 2. ?mza Arka Plan? Özelli?i
|
||||
?mza görselinin ve bilgilerinin kaplad??? alan kadar, yar? saydam hafif gri opak dikdörtgen arka plan ekle. Böylece imza ve bilgiler arka plandaki metinlerden etkilenmez ve okunur kal?r. (Art?k brick tabanl?: `ImageBrick`/`TextBrick`'in arkas?na bir arka plan `Brick` dikdörtgeni eklenebilir.)
|
||||
|
||||
### 3. Checkbox Renk ve Stil ?yile?tirmesi
|
||||
Mevcut checkbox'lar?n rengi ve kenarl?klar? çok dikkat çekici. Koyu füme tonlar?nda, desenli, sade ve profesyonel görünümlü bir stil olsun. (`receiver-signature.js` ve ilgili CSS.)
|
||||
|
||||
### 4. Sayfa Aç?l???nda Otomatik ?mza Popup'?
|
||||
Sayfa aç?l?r aç?lmaz imza popup'? ç?ks?n. "Kay?tl? hiç bir imzan?z yok, tan?mlay?n?z" mesaj? gösterilsin. Kullan?c? imzas?n? tan?mlamadan ilerleyemesin. Mevcut "Unterschrift erstellen" butonu "?mzay? de?i?tir" olarak güncellensin.
|
||||
|
||||
### 5. Otomatik ?mza Uygulama
|
||||
Kullan?c? tüm checkbox'lar? onaylad??? anda imzalar otomatik olarak uygulanmaya ba?las?n (butona t?klamaya gerek kalmas?n). Sayfan?n üstünde imza say?s? ve imzalanmas? gereken sayfalar hakk?nda bilgi gösterilsin.
|
||||
|
||||
### 6. Checkbox - DevExpress Toolbar Pozisyon Uyumsuzlu?u (BUG)
|
||||
- Checkbox'lar browser'?n boyut/konumuna neredeyse anl?k tepki veriyor
|
||||
- DevExpress toolbar de?i?ikliklerine geç tepki gösteriyor
|
||||
- Zoom de?i?ince bazen 2-3 PDF yan yana gelebiliyor; checkbox o konumda do?ru görünüyor ama iki PDF'in yan yana gelmesi kald?r?lmal?
|
||||
- `DocumentViewer.razor`'daki `DxDocumentViewer` + `DxDocumentViewerTabPanelSettings` bile?enleri daha uygun olabilir mi? De?erlendirmesi yap?lacak.
|
||||
|
||||
---
|
||||
|
||||
## De?i?iklik Günlü?ü
|
||||
|
||||
| Oturum | De?i?iklik |
|
||||
|---|---|
|
||||
| 1–3 | Temel altyap?: servisler, YARP proxy, JS overlay, imza pad |
|
||||
| 4 | `AddSignatureAtAnnotation` + BottomMarginBand ? ? her sayfada tekrar |
|
||||
| 5 | `BeforePrint` + `e.Graph?.PrintingSystem` ? ? derleme hatas? |
|
||||
| 6 | BeforePrint counter ? ? do?ru yakla??m, yanl?? band |
|
||||
| 7 | DetailBand'e geçi? ? ? do?ru band, koordinat hâlâ yanl?? |
|
||||
| 8 | `(page-1)*1169+Y` ? ? sayfa ?i?mesi (35?140) |
|
||||
| 9 | `BoundsF.Y = ann.Y` + counter ? (?ablon raporda çal??m?yor) |
|
||||
| 10 | iText7 `StampSignaturesOnPdf` gerçek modda ?, fake modda export+reload ? (35?70), BottomMarginBand fallback ? |
|
||||
| 11 | COPILOT_CONTEXT_TR.md ve COPILOT_CONTEXT_EN.md ayr? dosyalar olarak yeniden olu?turuldu |
|
||||
| **12** | **GÖREV 1 ÇÖZÜLDÜ ?** — `WireAnnotationSignatures`: `report.AfterPrint` + `PrintingSystem.Pages[ann.Page-1].AddBrick(ImageBrick/TextBrick)`. Sayfa hedefleme band'dan ba??ms?z, sayfa say?s? katlanm?yor. iText7 ReceiverUI ak???ndan ç?kar?ld?. `AddSignatureAtAnnotation`/`RemoveExistingSignatureById` kald?r?ld?. Derleme ba?ar?l?. |
|
||||
@@ -26,6 +26,7 @@
|
||||
@inject InMemoryReportStorageWebExtension ReportStorage
|
||||
@inject EnvelopeGenerator.ReceiverUI.Services.DocumentService DocumentService
|
||||
@inject EnvelopeGenerator.ReceiverUI.Services.AuthService AuthService
|
||||
@inject EnvelopeGenerator.ReceiverUI.Services.EnvelopeReceiverService EnvelopeReceiverService
|
||||
|
||||
<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" />
|
||||
@@ -33,75 +34,151 @@
|
||||
<div class="receiver-page-layout">
|
||||
|
||||
<div class="receiver-signature-panel">
|
||||
<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 align-items-center">
|
||||
<button class="btn @(_capturedSignature is not null ? "btn-outline-success" : "btn-primary")" @onclick="OpenSignaturePopupAsync">
|
||||
@if (_capturedSignature is not null) {
|
||||
<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>
|
||||
<span>Unterschrift gespeichert</span>
|
||||
} else {
|
||||
<span>Unterschrift ändern</span>
|
||||
}
|
||||
</button>
|
||||
@if (_annotations.Count > 0 && !SignatureApplied) {
|
||||
<div class="d-flex flex-column gap-1 align-self-center">
|
||||
<span class="text-muted small">
|
||||
<strong>@_annotations.Count</strong> @(_annotations.Count == 1 ? "Unterschriftsfeld" : "Unterschriftsfelder")
|
||||
·
|
||||
Seite@(AnnotationPages.Count() == 1 ? "" : "n"): <strong>@string.Join(", ", AnnotationPages)</strong>
|
||||
</span>
|
||||
@if (_capturedSignature is not null) {
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<div class="progress flex-grow-1" style="height:6px; min-width:80px;">
|
||||
<div class="progress-bar @(_checkedAnnotations.Count == _annotations.Count ? "bg-success" : "bg-primary")"
|
||||
style="width:@(_annotations.Count > 0 ? (_checkedAnnotations.Count * 100 / _annotations.Count) : 0)%"
|
||||
role="progressbar"></div>
|
||||
</div>
|
||||
<span class="text-muted small fst-italic">
|
||||
@_checkedAnnotations.Count / @_annotations.Count @(_annotations.Count == 1 ? "Feld" : "Felder") markiert
|
||||
</span>
|
||||
</div>
|
||||
@if (_checkedAnnotations.Count == _annotations.Count) {
|
||||
<span class="text-success small fw-semibold">✓ Alle Felder markiert – Unterschriften werden angewendet…</span>
|
||||
|
||||
@* ?? Envelope info header ???????????????????????????????????????????????? *@
|
||||
@if (_envelopeReceiver is not null) {
|
||||
<div class="receiver-info-header">
|
||||
<div class="receiver-info-header__gradient">
|
||||
<div class="receiver-info-header__left">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="currentColor" class="receiver-info-header__icon" viewBox="0 0 16 16">
|
||||
<path d="M0 4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V4Zm2-1a1 1 0 0 0-1 1v.217l7 4.2 7-4.2V4a1 1 0 0 0-1-1H2Zm13 2.383-4.708 2.825L15 11.105V5.383Zm-.034 6.876-5.64-3.471L8 9.583l-1.326-.795-5.64 3.47A1 1 0 0 0 2 13h12a1 1 0 0 0 .966-.741ZM1 11.105l4.708-2.897L1 5.383v5.722Z"/>
|
||||
</svg>
|
||||
<div>
|
||||
<div class="receiver-info-header__title">@(_envelopeReceiver.Envelope?.Title ?? "Dokument")</div>
|
||||
@if (!string.IsNullOrWhiteSpace(_envelopeReceiver.Envelope?.User?.FullName) || !string.IsNullOrWhiteSpace(_envelopeReceiver.Envelope?.User?.Email)) {
|
||||
<div class="receiver-info-header__sender">
|
||||
Von
|
||||
@if (!string.IsNullOrWhiteSpace(_envelopeReceiver.Envelope?.User?.FullName)) {
|
||||
<strong>@_envelopeReceiver.Envelope!.User!.FullName</strong>
|
||||
}
|
||||
} else {
|
||||
<span class="text-muted small fst-italic">
|
||||
Bitte zuerst eine Unterschrift erstellen, dann die Felder im Dokument markieren.
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
@if (!string.IsNullOrWhiteSpace(_envelopeReceiver.Envelope?.User?.Email)) {
|
||||
<span class="opacity-75"><@_envelopeReceiver.Envelope!.User!.Email></span>
|
||||
}
|
||||
· @_envelopeReceiver.Envelope?.AddedWhen.ToString("dd.MM.yyyy")
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="receiver-info-header__badges">
|
||||
@if (!string.IsNullOrWhiteSpace(_envelopeReceiver.Name)) {
|
||||
<span class="receiver-info-badge">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="me-1" viewBox="0 0 16 16">
|
||||
<path d="M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6Zm2-3a2 2 0 1 1-4 0 2 2 0 0 1 4 0Zm4 8c0 1-1 1-1 1H3s-1 0-1-1 1-4 6-4 6 3 6 4Z"/>
|
||||
</svg>
|
||||
@_envelopeReceiver.Name
|
||||
</span>
|
||||
}
|
||||
<button class="btn btn-success" disabled="@(!SignatureApplied)" @onclick="ExportSignedPdfAsync">Signiertes PDF exportieren</button>
|
||||
@if (!string.IsNullOrWhiteSpace(_envelopeReceiver.CompanyName)) {
|
||||
<span class="receiver-info-badge">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="me-1" viewBox="0 0 16 16">
|
||||
<path d="M14.763.075A.5.5 0 0 1 15 .5v15a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5V14h-1v1.5a.5.5 0 0 1-.5.5h-9a.5.5 0 0 1-.5-.5V10a.5.5 0 0 1 .342-.474L6 7.64V4.5a.5.5 0 0 1 .276-.447l8-4a.5.5 0 0 1 .487.022Z"/>
|
||||
</svg>
|
||||
@_envelopeReceiver.CompanyName
|
||||
</span>
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(_envelopeReceiver.JobTitle)) {
|
||||
<span class="receiver-info-badge receiver-info-badge--muted">@_envelopeReceiver.JobTitle</span>
|
||||
}
|
||||
@{
|
||||
var docElements = _envelopeReceiver.Envelope?.Documents?.FirstOrDefault()?.Elements;
|
||||
int sigCount = docElements?.Count() ?? _annotations.Count;
|
||||
}
|
||||
@if (sigCount > 0) {
|
||||
<span class="receiver-info-badge receiver-info-badge--accent">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="me-1" 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>
|
||||
@sigCount @(sigCount == 1 ? "Unterschriftsfeld" : "Unterschriftsfelder")
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@if (!string.IsNullOrWhiteSpace(_envelopeReceiver.Envelope?.Message)) {
|
||||
<div class="receiver-info-message">@_envelopeReceiver.Envelope!.Message</div>
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(_envelopeReceiver.PrivateMessage)) {
|
||||
<div class="receiver-info-private-message">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="currentColor" class="me-1 flex-shrink-0" viewBox="0 0 16 16">
|
||||
<path d="M8 1a2 2 0 0 1 2 2v4H6V3a2 2 0 0 1 2-2zm3 6V3a3 3 0 0 0-6 0v4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2z"/>
|
||||
</svg>
|
||||
@_envelopeReceiver.PrivateMessage
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@* ?? Signature action bar ???????????????????????????????????????????????? *@
|
||||
<div class="receiver-action-bar">
|
||||
<div class="receiver-action-bar__inner">
|
||||
<button class="btn btn-sm @(_capturedSignature is not null ? "btn-outline-success" : "btn-primary")" @onclick="OpenSignaturePopupAsync">
|
||||
@if (_capturedSignature is not null) {
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" 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>
|
||||
<span>Unterschrift gespeichert</span>
|
||||
} else {
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="currentColor" class="me-1" 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>
|
||||
<span>Unterschrift erstellen</span>
|
||||
}
|
||||
</button>
|
||||
|
||||
@if (_annotations.Count > 0 && !SignatureApplied) {
|
||||
<div class="receiver-action-bar__progress">
|
||||
<div class="progress" style="height:5px; min-width:70px; width:90px;">
|
||||
<div class="progress-bar @(_checkedAnnotations.Count == _annotations.Count ? "bg-success" : "bg-primary")"
|
||||
style="width:@(_annotations.Count > 0 ? (_checkedAnnotations.Count * 100 / _annotations.Count) : 0)%"
|
||||
role="progressbar"></div>
|
||||
</div>
|
||||
<span class="text-muted" style="font-size:0.75rem;">
|
||||
@_checkedAnnotations.Count / @_annotations.Count
|
||||
Seite@(AnnotationPages.Count() == 1 ? "" : "n") @string.Join(",", AnnotationPages)
|
||||
</span>
|
||||
@if (_capturedSignature is null) {
|
||||
<span class="text-muted fst-italic" style="font-size:0.72rem;">Zuerst Unterschrift erstellen</span>
|
||||
} else if (_checkedAnnotations.Count == _annotations.Count) {
|
||||
<span class="text-success fw-semibold" style="font-size:0.72rem;">✓ Wird angewendet…</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(SignatureValidationMessage)) {
|
||||
<span class="text-danger" style="font-size:0.78rem;">@SignatureValidationMessage</span>
|
||||
}
|
||||
@if (SignatureApplied) {
|
||||
<span class="text-success" style="font-size:0.78rem;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="currentColor" class="me-1" viewBox="0 0 16 16">
|
||||
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z"/>
|
||||
</svg>
|
||||
Unterschrift angewendet
|
||||
</span>
|
||||
}
|
||||
|
||||
<div class="ms-auto d-flex align-items-center gap-2">
|
||||
<button class="btn btn-sm btn-success" disabled="@(!SignatureApplied)" @onclick="ExportSignedPdfAsync">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="currentColor" class="me-1" viewBox="0 0 16 16">
|
||||
<path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/>
|
||||
<path d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z"/>
|
||||
</svg>
|
||||
PDF exportieren
|
||||
</button>
|
||||
@if (!string.IsNullOrWhiteSpace(EnvelopeKey)) {
|
||||
<button class="btn btn-outline-danger ms-auto" @onclick="LogoutAsync" disabled="@IsLoggingOut" title="Abmelden">
|
||||
<button class="btn btn-sm btn-outline-danger" @onclick="LogoutAsync" disabled="@IsLoggingOut" title="Abmelden">
|
||||
@if (IsLoggingOut) {
|
||||
<span class="spinner-border spinner-border-sm me-1" role="status" aria-hidden="true"></span>
|
||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
||||
} else {
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="me-1" 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 fill-rule="evenodd" d="M10 12.5a.5.5 0 0 1-.5.5h-8a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5h8a.5.5 0 0 1 .5.5v2a.5.5 0 0 0 1 0v-2A1.5 1.5 0 0 0 9.5 2h-8A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h8a1.5 1.5 0 0 0 1.5-1.5v-2a.5.5 0 0 0-1 0v2z"/>
|
||||
<path fill-rule="evenodd" d="M15.854 8.354a.5.5 0 0 0 0-.708l-3-3a.5.5 0 0 0-.708.708L14.293 7.5H5.5a.5.5 0 0 0 0 1h8.793l-2.147 2.146a.5.5 0 0 0 .708.708l3-3z"/>
|
||||
</svg>
|
||||
}
|
||||
Abmelden
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<DxPopup @bind-Visible="SignaturePopupVisible"
|
||||
@@ -225,6 +302,7 @@ Shown="OnPopupShownAsync">
|
||||
|
||||
IReadOnlyList<AnnotationDto> _annotations = [];
|
||||
IEnumerable<int> AnnotationPages => _annotations.Select(a => a.Page).Distinct().OrderBy(p => p);
|
||||
EnvelopeReceiverDto? _envelopeReceiver;
|
||||
record SignatureCapture(string DataUrl, string FullName, string Position, string Place);
|
||||
SignatureCapture? _capturedSignature;
|
||||
byte[]? _basePdfBytes;
|
||||
@@ -259,6 +337,7 @@ Shown="OnPopupShownAsync">
|
||||
}
|
||||
|
||||
_annotations = await AnnotationService.GetAnnotationsAsync(EnvelopeKey ?? "fake");
|
||||
_envelopeReceiver = await EnvelopeReceiverService.GetAsync(EnvelopeKey ?? "fake");
|
||||
|
||||
if (!AppOptions.Value.ForceToUseFakeDocument && !string.IsNullOrWhiteSpace(EnvelopeKey)) {
|
||||
var (pdfBytes, _) = await DocumentService.GetDocumentAsync(EnvelopeKey);
|
||||
|
||||
@@ -3,10 +3,6 @@
|
||||
|
||||
<div class="page">
|
||||
<main>
|
||||
<div class="top-row px-4">
|
||||
<a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
|
||||
</div>
|
||||
|
||||
<article class="content">
|
||||
@Body
|
||||
</article>
|
||||
|
||||
@@ -125,4 +125,121 @@ article {
|
||||
text-overflow: ellipsis;
|
||||
pointer-events: none;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
|
||||
/* ── Envelope info header ────────────────────────────────────────────────── */
|
||||
.receiver-info-header {
|
||||
border-bottom: 1px solid rgba(0,0,0,.08);
|
||||
}
|
||||
|
||||
.receiver-info-header__gradient {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
padding: 10px 16px 8px;
|
||||
background: linear-gradient(135deg, #2c3e50 0%, #3498db 100%);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.receiver-info-header__left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.receiver-info-header__icon {
|
||||
flex-shrink: 0;
|
||||
opacity: .85;
|
||||
}
|
||||
|
||||
.receiver-info-header__title {
|
||||
font-size: 0.92rem;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 340px;
|
||||
}
|
||||
|
||||
.receiver-info-header__sender {
|
||||
font-size: 0.72rem;
|
||||
opacity: .8;
|
||||
margin-top: 1px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.receiver-info-header__badges {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 5px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.receiver-info-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
background: rgba(255,255,255,.18);
|
||||
color: #fff;
|
||||
border-radius: 20px;
|
||||
padding: 2px 9px;
|
||||
font-size: 0.70rem;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.receiver-info-badge--muted {
|
||||
background: rgba(255,255,255,.10);
|
||||
opacity: .8;
|
||||
}
|
||||
|
||||
.receiver-info-badge--accent {
|
||||
background: rgba(39,174,96,.35);
|
||||
border: 1px solid rgba(39,174,96,.5);
|
||||
}
|
||||
|
||||
.receiver-info-message {
|
||||
padding: 7px 16px;
|
||||
font-size: 0.78rem;
|
||||
color: #444;
|
||||
border-bottom: 1px solid rgba(0,0,0,.05);
|
||||
white-space: pre-wrap;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.receiver-info-private-message {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 4px;
|
||||
padding: 5px 16px 6px;
|
||||
font-size: 0.75rem;
|
||||
color: #5a5a72;
|
||||
background: #f8f7ff;
|
||||
border-top: 1px solid rgba(90,80,180,.12);
|
||||
}
|
||||
|
||||
/* ── Signature action bar ────────────────────────────────────────────────── */
|
||||
.receiver-action-bar {
|
||||
border-bottom: 1px solid rgba(0,0,0,.08);
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.receiver-action-bar__inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
padding: 7px 14px;
|
||||
}
|
||||
|
||||
.receiver-action-bar__progress {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
Reference in New Issue
Block a user