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).
This commit is contained in:
@@ -4153,6 +4153,7 @@
|
|||||||
"new": "Neuer Vertrag"
|
"new": "Neuer Vertrag"
|
||||||
},
|
},
|
||||||
"detail": {
|
"detail": {
|
||||||
|
"previewPdf": "PDF-Vorschau",
|
||||||
"integrity": {
|
"integrity": {
|
||||||
"title": "PDF-Integritätsprüfung",
|
"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.",
|
"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.",
|
||||||
|
|||||||
@@ -4151,6 +4151,7 @@
|
|||||||
"new": "New contract"
|
"new": "New contract"
|
||||||
},
|
},
|
||||||
"detail": {
|
"detail": {
|
||||||
|
"previewPdf": "Preview PDF",
|
||||||
"integrity": {
|
"integrity": {
|
||||||
"title": "PDF integrity check",
|
"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.",
|
"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.",
|
||||||
|
|||||||
@@ -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() {
|
async function handleSignedPdfDownload() {
|
||||||
if (!numericId) return;
|
if (!numericId) return;
|
||||||
const previewWindow = window.open('about:blank', '_blank');
|
const previewWindow = window.open('about:blank', '_blank');
|
||||||
@@ -241,6 +260,10 @@ export const ContractDetailPage: React.FC = () => {
|
|||||||
<Edit2 className="w-4 h-4 mr-1" />
|
<Edit2 className="w-4 h-4 mr-1" />
|
||||||
{t('contracts.detail.edit', 'Edit')}
|
{t('contracts.detail.edit', 'Edit')}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button variant="outline" onClick={handlePdfPreview}>
|
||||||
|
<FileDown className="w-4 h-4 mr-1" />
|
||||||
|
{t('contracts.detail.previewPdf', 'Preview PDF')}
|
||||||
|
</Button>
|
||||||
<Button onClick={() => sendMutation.mutate()} disabled={sendMutation.isPending}>
|
<Button onClick={() => sendMutation.mutate()} disabled={sendMutation.isPending}>
|
||||||
<Send className="w-4 h-4 mr-1" />
|
<Send className="w-4 h-4 mr-1" />
|
||||||
{t('contracts.detail.send', 'Send to customer')}
|
{t('contracts.detail.send', 'Send to customer')}
|
||||||
|
|||||||
Reference in New Issue
Block a user