Phase 1: Workflows UI, Dedup/Merge UI, Import/Export UI, Print/PDF
- Workflows UI: full page with definitions/instances tabs, step editor, instance detail with approve/reject - Dedup/Merge UI: duplicate detection, side-by-side comparison, field-level merge dialog, merge history - Import/Export UI: import wizard (dry-run preview), export panel (CSV/XLSX), backend export route added - Print/PDF: PrintButton component, print.css, integrated in Contacts/Calendar/Reports/ContactDetail - Backend: GET /api/v1/export endpoint, export_companies_csv() service function - Routes: /workflows, /contacts/dedup, /import-export registered - Menu items: Workflows, Import/Export, Duplikate added to automation plugin manifest - IMPLEMENTATION_PLAN.md: audit-corrected plan for all 14 remaining features
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* Print / PDF utility functions.
|
||||
* Uses the browser's native print-to-PDF capability.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Print a specific DOM element by cloning it into a new window.
|
||||
* Copies all stylesheets from the current document so the clone looks identical.
|
||||
*/
|
||||
export function printElement(elementId: string): void {
|
||||
const element = document.getElementById(elementId);
|
||||
if (!element) {
|
||||
console.warn(`printElement: element with id "${elementId}" not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
const printWindow = window.open('', '_blank', 'width=900,height=700');
|
||||
if (!printWindow) {
|
||||
alert('Bitte erlauben Sie Pop-up-Fenster zum Drucken.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Clone the target element so we don't disturb the live DOM
|
||||
const clone = element.cloneNode(true) as HTMLElement;
|
||||
|
||||
// Write the HTML document
|
||||
printWindow.document.open();
|
||||
printWindow.document.write('<!DOCTYPE html>');
|
||||
printWindow.document.write('<html lang="de"><head><meta charset="UTF-8">');
|
||||
printWindow.document.write('<title>Druckansicht</title>');
|
||||
|
||||
// Copy all stylesheets from the current document
|
||||
const styleSheets = document.querySelectorAll('style, link[rel="stylesheet"]');
|
||||
styleSheets.forEach((sheet) => {
|
||||
if (sheet.tagName === 'STYLE') {
|
||||
const styleEl = sheet.cloneNode(true) as HTMLElement;
|
||||
printWindow.document.head.appendChild(styleEl);
|
||||
} else if (sheet.tagName === 'LINK') {
|
||||
const linkEl = sheet.cloneNode(true) as HTMLElement;
|
||||
printWindow.document.head.appendChild(linkEl);
|
||||
}
|
||||
});
|
||||
|
||||
// Add print-specific CSS
|
||||
printWindow.document.write(
|
||||
'<style>' +
|
||||
'@media print {' +
|
||||
'body { margin: 1cm; font-size: 12pt; color: #000; background: #fff; }' +
|
||||
'img { max-width: 100%; }' +
|
||||
'table { width: 100%; border-collapse: collapse; }' +
|
||||
'th, td { border: 1px solid #ccc; padding: 4px 8px; font-size: 11pt; }' +
|
||||
'th { background: #f5f5f5; font-weight: 600; }' +
|
||||
'}' +
|
||||
'@media screen {' +
|
||||
'body { padding: 20px; font-family: sans-serif; }' +
|
||||
'}' +
|
||||
'</style>',
|
||||
);
|
||||
|
||||
printWindow.document.write('</head><body>');
|
||||
printWindow.document.write(clone.outerHTML);
|
||||
printWindow.document.write('</body></html>');
|
||||
printWindow.document.close();
|
||||
|
||||
// Wait for stylesheets to load before printing
|
||||
printWindow.onload = () => {
|
||||
printWindow.focus();
|
||||
printWindow.print();
|
||||
// Close the window after print dialog (most browsers do this automatically)
|
||||
setTimeout(() => {
|
||||
printWindow.close();
|
||||
}, 500);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the current page using window.print().
|
||||
* Relies on @media print CSS (print.css) to hide non-printable elements.
|
||||
*/
|
||||
export function printCurrentPage(): void {
|
||||
window.print();
|
||||
}
|
||||
|
||||
/**
|
||||
* Export an element to PDF using the browser's native print-to-PDF.
|
||||
* Opens the print dialog where the user can choose "Save as PDF".
|
||||
*
|
||||
* @param elementId - The id of the DOM element to export
|
||||
* @param filename - Suggested filename (used in the print window title)
|
||||
*/
|
||||
export function exportToPDF(elementId: string, filename: string): void {
|
||||
const element = document.getElementById(elementId);
|
||||
if (!element) {
|
||||
console.warn(`exportToPDF: element with id "${elementId}" not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
const printWindow = window.open('', '_blank', 'width=900,height=700');
|
||||
if (!printWindow) {
|
||||
alert('Bitte erlauben Sie Pop-up-Fenster zum Exportieren als PDF.');
|
||||
return;
|
||||
}
|
||||
|
||||
const clone = element.cloneNode(true) as HTMLElement;
|
||||
|
||||
printWindow.document.open();
|
||||
printWindow.document.write('<!DOCTYPE html>');
|
||||
printWindow.document.write('<html lang="de"><head><meta charset="UTF-8">');
|
||||
printWindow.document.write(`<title>${filename}</title>`);
|
||||
|
||||
// Copy all stylesheets
|
||||
const styleSheets = document.querySelectorAll('style, link[rel="stylesheet"]');
|
||||
styleSheets.forEach((sheet) => {
|
||||
if (sheet.tagName === 'STYLE') {
|
||||
const styleEl = sheet.cloneNode(true) as HTMLElement;
|
||||
printWindow.document.head.appendChild(styleEl);
|
||||
} else if (sheet.tagName === 'LINK') {
|
||||
const linkEl = sheet.cloneNode(true) as HTMLElement;
|
||||
printWindow.document.head.appendChild(linkEl);
|
||||
}
|
||||
});
|
||||
|
||||
// Print-specific styles for PDF export
|
||||
printWindow.document.write(
|
||||
'<style>' +
|
||||
'@media print {' +
|
||||
`@page { margin: 1.5cm; }` +
|
||||
'body { margin: 0; font-size: 12pt; color: #000; background: #fff; line-height: 1.4; }' +
|
||||
'img { max-width: 100%; height: auto; }' +
|
||||
'table { width: 100%; border-collapse: collapse; page-break-inside: auto; }' +
|
||||
'tr { page-break-inside: avoid; page-break-after: auto; }' +
|
||||
'thead { display: table-header-group; }' +
|
||||
'th, td { border: 1px solid #ccc; padding: 4px 8px; font-size: 11pt; text-align: left; }' +
|
||||
'th { background: #f5f5f5; font-weight: 600; }' +
|
||||
'h1, h2, h3 { page-break-after: avoid; }' +
|
||||
'.card, [class*="rounded"] { border: 1px solid #ddd !important; box-shadow: none !important; border-radius: 4px; }' +
|
||||
'}' +
|
||||
'@media screen {' +
|
||||
'body { padding: 20px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }' +
|
||||
'}' +
|
||||
'</style>',
|
||||
);
|
||||
|
||||
printWindow.document.write('</head><body>');
|
||||
printWindow.document.write(clone.outerHTML);
|
||||
printWindow.document.write('</body></html>');
|
||||
printWindow.document.close();
|
||||
|
||||
printWindow.onload = () => {
|
||||
printWindow.focus();
|
||||
printWindow.print();
|
||||
setTimeout(() => {
|
||||
printWindow.close();
|
||||
}, 500);
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user