pdf 저장 수정
This commit is contained in:
parent
c52937c22d
commit
ae616ae611
|
|
@ -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 (
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue