pdf 저장 수정

This commit is contained in:
dohyeons 2025-10-02 10:52:13 +09:00
parent c52937c22d
commit ae616ae611
1 changed files with 181 additions and 50 deletions

View File

@ -42,85 +42,215 @@ export function ReportPreviewModal({ isOpen, onClose }: ReportPreviewModalProps)
}; };
const handlePrint = () => { const handlePrint = () => {
// 현재 미리보기 영역만 인쇄 // HTML 생성하여 인쇄
const printContent = document.getElementById("preview-content"); const printHtml = generatePrintHTML();
if (!printContent) return;
const printWindow = window.open("", "_blank"); const printWindow = window.open("", "_blank");
if (!printWindow) return; if (!printWindow) return;
printWindow.document.write(` printWindow.document.write(printHtml);
<html>
<head>
<title> </title>
<style>
body { margin: 0; padding: 20px; }
@media print {
body { margin: 0; padding: 0; }
}
</style>
</head>
<body>
${printContent.innerHTML}
</body>
</html>
`);
printWindow.document.close(); printWindow.document.close();
printWindow.print(); printWindow.print();
}; };
// PDF 다운로드 (브라우저 인쇄 기능 이용) // HTML 생성 (인쇄/PDF용)
const handleDownloadPDF = () => { const generatePrintHTML = (): string => {
const printContent = document.getElementById("preview-content"); // 컴포넌트별 HTML 생성
if (!printContent) return; const componentsHTML = components
.map((component) => {
const queryResult = component.queryId ? getQueryResult(component.queryId) : null;
let content = "";
const printWindow = window.open("", "_blank"); // Text/Label 컴포넌트
if (!printWindow) return; if (component.type === "text" || component.type === "label") {
const displayValue = getComponentValue(component);
content = `<div style="font-size: ${component.fontSize || 13}px; color: ${component.fontColor || "#000000"}; font-weight: ${component.fontWeight || "normal"}; text-align: ${component.textAlign || "left"};">${displayValue}</div>`;
}
printWindow.document.write(` // Image 컴포넌트
else if (component.type === "image" && component.imageUrl) {
const imageUrl = component.imageUrl.startsWith("data:")
? component.imageUrl
: getFullImageUrl(component.imageUrl);
content = `<img src="${imageUrl}" style="width: 100%; height: 100%; object-fit: ${component.objectFit || "contain"};" />`;
}
// Divider 컴포넌트
else if (component.type === "divider") {
const width = component.orientation === "horizontal" ? "100%" : `${component.lineWidth || 1}px`;
const height = component.orientation === "vertical" ? "100%" : `${component.lineWidth || 1}px`;
content = `<div style="width: ${width}; height: ${height}; background-color: ${component.lineColor || "#000000"};"></div>`;
}
// Signature 컴포넌트
else if (component.type === "signature") {
const labelPosition = component.labelPosition || "left";
const showLabel = component.showLabel !== false;
const labelText = component.labelText || "서명:";
const imageUrl = component.imageUrl
? component.imageUrl.startsWith("data:")
? component.imageUrl
: getFullImageUrl(component.imageUrl)
: "";
if (labelPosition === "left" || labelPosition === "right") {
content = `
<div style="display: flex; align-items: center; flex-direction: ${labelPosition === "right" ? "row-reverse" : "row"}; gap: 8px; height: 100%;">
${showLabel ? `<div style="font-size: 12px; white-space: nowrap;">${labelText}</div>` : ""}
<div style="flex: 1; position: relative;">
${imageUrl ? `<img src="${imageUrl}" style="width: 100%; height: 100%; object-fit: ${component.objectFit || "contain"};" />` : ""}
${component.showUnderline ? '<div style="position: absolute; bottom: 0; left: 0; right: 0; height: 1px; background-color: #000000;"></div>' : ""}
</div>
</div>`;
} else {
content = `
<div style="display: flex; flex-direction: column; align-items: center; height: 100%;">
${showLabel && labelPosition === "top" ? `<div style="font-size: 12px;">${labelText}</div>` : ""}
<div style="flex: 1; width: 100%; position: relative;">
${imageUrl ? `<img src="${imageUrl}" style="width: 100%; height: 100%; object-fit: ${component.objectFit || "contain"};" />` : ""}
${component.showUnderline ? '<div style="position: absolute; bottom: 0; left: 0; right: 0; height: 1px; background-color: #000000;"></div>' : ""}
</div>
${showLabel && labelPosition === "bottom" ? `<div style="font-size: 12px;">${labelText}</div>` : ""}
</div>`;
}
}
// Stamp 컴포넌트
else if (component.type === "stamp") {
const showLabel = component.showLabel !== false;
const labelText = component.labelText || "(인)";
const personName = component.personName || "";
const imageUrl = component.imageUrl
? component.imageUrl.startsWith("data:")
? component.imageUrl
: getFullImageUrl(component.imageUrl)
: "";
content = `
<div style="display: flex; align-items: center; gap: 8px; height: 100%;">
${personName ? `<div style="font-size: 12px;">${personName}</div>` : ""}
<div style="position: relative; width: ${component.width}px; height: ${component.height}px;">
${imageUrl ? `<img src="${imageUrl}" style="width: 100%; height: 100%; object-fit: ${component.objectFit || "contain"}; border-radius: 50%;" />` : ""}
${showLabel ? `<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 12px; font-weight: bold; color: #dc2626;">${labelText}</div>` : ""}
</div>
</div>`;
}
// Table 컴포넌트
else if (component.type === "table" && queryResult && queryResult.rows.length > 0) {
const columns =
component.tableColumns && component.tableColumns.length > 0
? component.tableColumns
: queryResult.fields.map((field) => ({
field,
header: field,
align: "left" as const,
width: undefined,
}));
const tableRows = queryResult.rows
.map(
(row) => `
<tr>
${columns.map((col) => `<td style="border: ${component.showBorder !== false ? "1px solid #d1d5db" : "none"}; padding: 6px 8px; text-align: ${col.align || "left"}; height: ${component.rowHeight || "auto"}px;">${String(row[col.field] ?? "")}</td>`).join("")}
</tr>
`,
)
.join("");
content = `
<table style="width: 100%; border-collapse: ${component.showBorder !== false ? "collapse" : "separate"}; font-size: 12px;">
<thead>
<tr style="background-color: ${component.headerBackgroundColor || "#f3f4f6"}; color: ${component.headerTextColor || "#111827"};">
${columns.map((col) => `<th style="border: ${component.showBorder !== false ? "1px solid #d1d5db" : "none"}; padding: 6px 8px; text-align: ${col.align || "left"}; width: ${col.width ? `${col.width}px` : "auto"}; font-weight: 600;">${col.header}</th>`).join("")}
</tr>
</thead>
<tbody>
${tableRows}
</tbody>
</table>`;
}
return `
<div style="position: absolute; left: ${component.x}px; top: ${component.y}px; width: ${component.width}px; height: ${component.height}px; background-color: ${component.backgroundColor || "transparent"}; border: ${component.borderWidth ? `${component.borderWidth}px solid ${component.borderColor}` : "none"}; padding: 8px; box-sizing: border-box;">
${content}
</div>`;
})
.join("");
return `
<html> <html>
<head> <head>
<meta charset="UTF-8">
<title> </title> <title> </title>
<style> <style>
* { box-sizing: border-box; }
@page {
size: A4;
margin: 10mm;
}
@media print { @media print {
@page { body { margin: 0; padding: 0; }
size: A4; .print-container { page-break-inside: avoid; }
margin: 10mm;
}
body {
margin: 0;
padding: 0;
}
} }
body { body {
font-family: 'Malgun Gothic', sans-serif; font-family: 'Malgun Gothic', 'Apple SD Gothic Neo', sans-serif;
margin: 0; margin: 0;
padding: 20px; padding: 20px;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
} }
table { .print-container {
border-collapse: collapse; position: relative;
width: 100%; width: ${canvasWidth}mm;
} min-height: ${canvasHeight}mm;
th, td { background-color: white;
border: 1px solid #ddd; margin: 0 auto;
padding: 8px;
text-align: left;
}
th {
background-color: #f2f2f2;
} }
</style> </style>
</head> </head>
<body> <body>
${printContent.innerHTML} <div class="print-container">
${componentsHTML}
</div>
<script> <script>
window.onload = function() { window.onload = function() {
window.print(); // 이미지 로드 대기 후 인쇄
const images = document.getElementsByTagName('img');
if (images.length === 0) {
setTimeout(() => window.print(), 100);
} else {
let loadedCount = 0;
Array.from(images).forEach(img => {
if (img.complete) {
loadedCount++;
} else {
img.onload = () => {
loadedCount++;
if (loadedCount === images.length) {
setTimeout(() => window.print(), 100);
}
};
}
});
if (loadedCount === images.length) {
setTimeout(() => window.print(), 100);
}
}
} }
</script> </script>
</body> </body>
</html> </html>`;
`); };
// PDF 다운로드 (브라우저 인쇄 기능 이용)
const handleDownloadPDF = () => {
const printHtml = generatePrintHTML();
const printWindow = window.open("", "_blank");
if (!printWindow) return;
printWindow.document.write(printHtml);
printWindow.document.close(); printWindow.document.close();
toast({ toast({
@ -306,6 +436,7 @@ export function ReportPreviewModal({ isOpen, onClose }: ReportPreviewModalProps)
field, field,
header: field, header: field,
align: "left" as const, align: "left" as const,
width: undefined,
})); }));
return ( return (