Files
goldenChart/frontend/src/utils/printReportDocument.ts
T
2026-06-03 12:14:08 +09:00

138 lines
3.5 KiB
TypeScript

/**
* 레포트 HTML을 별도 iframe 문서로 열어 인쇄·PDF 저장 (메인 앱 visibility 인쇄 버그 회피)
*/
/** App.css 전체 로드 전에도 PDF 다페이지·하단 잘림 방지 */
const CRITICAL_PRINT_CSS = `
@media print {
html, body {
height: auto !important;
overflow: visible !important;
margin: 0 !important;
padding: 0 !important;
}
.btd-report-print-doc,
.btd-report-print-area,
.brd-dashboard,
.brd-dashboard--report {
overflow: visible !important;
max-height: none !important;
min-height: 0 !important;
height: auto !important;
font-family: 'Apple SD Gothic Neo', 'Malgun Gothic', 'Noto Sans KR', sans-serif !important;
}
.brd-dashboard {
break-inside: auto !important;
page-break-inside: auto !important;
}
.brd-row,
.brd-row--2col,
.brd-row--3col,
.brd-card,
.brd-card--full {
break-inside: auto !important;
page-break-inside: auto !important;
overflow: visible !important;
}
.brd-kpi-grid {
break-inside: auto !important;
page-break-inside: auto !important;
}
.brd-kpi-card,
.brd-dash-header {
break-inside: avoid !important;
page-break-inside: avoid !important;
}
.brd-sig-scroll {
max-height: none !important;
overflow: visible !important;
height: auto !important;
}
.brd-expand-btn {
display: none !important;
}
.btd-report-print-footer {
break-inside: avoid !important;
page-break-inside: avoid !important;
}
}
@page {
size: A4 portrait;
margin: 10mm;
}
`;
const FONT_LINKS = `
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;500;700&display=swap" rel="stylesheet">
`;
function escapeHtml(s: string): string {
return s
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}
function collectDocumentStyles(): string {
const links = Array.from(document.querySelectorAll<HTMLLinkElement>('link[rel="stylesheet"]'))
.filter(l => l.href && !l.href.includes('fonts.googleapis.com'))
.map(l => `<link rel="stylesheet" href="${l.href}">`);
const inline = Array.from(document.querySelectorAll('style'))
.map(s => s.outerHTML);
return [...links, ...inline].join('\n');
}
export function printReportDocument(contentHtml: string, documentTitle: string): void {
const iframe = document.createElement('iframe');
iframe.setAttribute('title', 'print');
iframe.style.cssText = 'position:fixed;width:0;height:0;border:0;left:-9999px;top:0;';
document.body.appendChild(iframe);
const win = iframe.contentWindow;
const doc = win?.document;
if (!doc) {
iframe.remove();
return;
}
doc.open();
doc.write(`<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>${escapeHtml(documentTitle)}</title>
${FONT_LINKS}
${collectDocumentStyles()}
<style>${CRITICAL_PRINT_CSS}</style>
</head>
<body class="btd-report-print-doc"></body>
</html>`);
doc.close();
if (doc.body) {
doc.body.insertAdjacentHTML('beforeend', contentHtml);
}
let printed = false;
const runPrint = () => {
if (printed) return;
printed = true;
try {
win?.focus();
win?.print();
} finally {
window.setTimeout(() => iframe.remove(), 2000);
}
};
iframe.addEventListener('load', () => {
window.setTimeout(runPrint, 500);
}, { once: true });
window.setTimeout(runPrint, 900);
}