From 7eec1337da80b647c560d1d876dff56ed83b77f8 Mon Sep 17 00:00:00 2001
From: Luca <102960244+Luca-Timo@users.noreply.github.com>
Date: Wed, 3 Jun 2026 13:10:06 +0200
Subject: [PATCH] feat(contracts): preview PDF before sending
Add a 'Preview PDF' button on draft contracts that renders a fresh PDF
via the existing no-write /preview endpoint, so the admin can check
layout + signature blocks before sending (no audit trail created).
---
frontend/src/i18n/locales/de.json | 1 +
frontend/src/i18n/locales/en.json | 1 +
.../admin/contracts/ContractDetailPage.tsx | 23 +++++++++++++++++++
3 files changed, 25 insertions(+)
diff --git a/frontend/src/i18n/locales/de.json b/frontend/src/i18n/locales/de.json
index 4f7506b1..b1866d74 100644
--- a/frontend/src/i18n/locales/de.json
+++ b/frontend/src/i18n/locales/de.json
@@ -4153,6 +4153,7 @@
"new": "Neuer Vertrag"
},
"detail": {
+ "previewPdf": "PDF-Vorschau",
"integrity": {
"title": "PDF-Integritätsprüfung",
"help": "Berechnet die SHA-256-Hashes der unsignierten und signierten PDFs neu und vergleicht sie mit den beim Ausstellen gespeicherten Werten. Deckt Backup-Beschädigungen oder nachträgliche Änderungen an der Kundenkopie auf.",
diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json
index 2bf1fd1b..64a55130 100644
--- a/frontend/src/i18n/locales/en.json
+++ b/frontend/src/i18n/locales/en.json
@@ -4151,6 +4151,7 @@
"new": "New contract"
},
"detail": {
+ "previewPdf": "Preview PDF",
"integrity": {
"title": "PDF integrity check",
"help": "Re-hashes the unsigned + signed PDFs on disk and compares them to the SHA-256 stored when the document was issued. Catches backup corruption or manual edits since the customer received their copy.",
diff --git a/frontend/src/pages/admin/contracts/ContractDetailPage.tsx b/frontend/src/pages/admin/contracts/ContractDetailPage.tsx
index c322b1a6..fc4325fc 100644
--- a/frontend/src/pages/admin/contracts/ContractDetailPage.tsx
+++ b/frontend/src/pages/admin/contracts/ContractDetailPage.tsx
@@ -197,6 +197,25 @@ export const ContractDetailPage: React.FC = () => {
}
}
+ // Pre-send preview: renders a fresh PDF from the current draft without
+ // writing/sending anything, so the admin can sanity-check layout +
+ // signature blocks before committing to send (no audit trail created).
+ async function handlePdfPreview() {
+ if (!numericId) return;
+ const previewWindow = window.open('about:blank', '_blank');
+ if (!previewWindow) {
+ toast.error(t('contracts.detail.popupBlocked', 'Allow pop-ups for this site to preview the PDF.') as string);
+ return;
+ }
+ try {
+ const url = await contractsService.previewPdfUrl(numericId);
+ previewWindow.location.href = url;
+ } catch (err: any) {
+ previewWindow.close();
+ toast.error(err?.response?.data?.error || 'Preview failed');
+ }
+ }
+
async function handleSignedPdfDownload() {
if (!numericId) return;
const previewWindow = window.open('about:blank', '_blank');
@@ -241,6 +260,10 @@ export const ContractDetailPage: React.FC = () => {
{t('contracts.detail.edit', 'Edit')}
+