pdf 저장 수정
This commit is contained in:
parent
c52937c22d
commit
ae616ae611
|
|
@ -42,85 +42,215 @@ export function ReportPreviewModal({ isOpen, onClose }: ReportPreviewModalProps)
|
|||
};
|
||||
|
||||
const handlePrint = () => {
|
||||
// 현재 미리보기 영역만 인쇄
|
||||
const printContent = document.getElementById("preview-content");
|
||||
if (!printContent) return;
|
||||
// HTML 생성하여 인쇄
|
||||
const printHtml = generatePrintHTML();
|
||||
|
||||
const printWindow = window.open("", "_blank");
|
||||
if (!printWindow) return;
|
||||
|
||||
printWindow.document.write(`
|
||||
<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.write(printHtml);
|
||||
printWindow.document.close();
|
||||
printWindow.print();
|
||||
};
|
||||
|
||||
// PDF 다운로드 (브라우저 인쇄 기능 이용)
|
||||
const handleDownloadPDF = () => {
|
||||
const printContent = document.getElementById("preview-content");
|
||||
if (!printContent) return;
|
||||
// HTML 생성 (인쇄/PDF용)
|
||||
const generatePrintHTML = (): string => {
|
||||
// 컴포넌트별 HTML 생성
|
||||
const componentsHTML = components
|
||||
.map((component) => {
|
||||
const queryResult = component.queryId ? getQueryResult(component.queryId) : null;
|
||||
let content = "";
|
||||
|
||||
const printWindow = window.open("", "_blank");
|
||||
if (!printWindow) return;
|
||||
// Text/Label 컴포넌트
|
||||
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>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>리포트 인쇄</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; }
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 10mm;
|
||||
}
|
||||
@media print {
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 10mm;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
body { margin: 0; padding: 0; }
|
||||
.print-container { page-break-inside: avoid; }
|
||||
}
|
||||
body {
|
||||
font-family: 'Malgun Gothic', sans-serif;
|
||||
font-family: 'Malgun Gothic', 'Apple SD Gothic Neo', sans-serif;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
-webkit-print-color-adjust: exact;
|
||||
print-color-adjust: exact;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
th, td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
th {
|
||||
background-color: #f2f2f2;
|
||||
.print-container {
|
||||
position: relative;
|
||||
width: ${canvasWidth}mm;
|
||||
min-height: ${canvasHeight}mm;
|
||||
background-color: white;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
${printContent.innerHTML}
|
||||
<div class="print-container">
|
||||
${componentsHTML}
|
||||
</div>
|
||||
<script>
|
||||
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>
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
</html>`;
|
||||
};
|
||||
|
||||
// PDF 다운로드 (브라우저 인쇄 기능 이용)
|
||||
const handleDownloadPDF = () => {
|
||||
const printHtml = generatePrintHTML();
|
||||
|
||||
const printWindow = window.open("", "_blank");
|
||||
if (!printWindow) return;
|
||||
|
||||
printWindow.document.write(printHtml);
|
||||
printWindow.document.close();
|
||||
|
||||
toast({
|
||||
|
|
@ -306,6 +436,7 @@ export function ReportPreviewModal({ isOpen, onClose }: ReportPreviewModalProps)
|
|||
field,
|
||||
header: field,
|
||||
align: "left" as const,
|
||||
width: undefined,
|
||||
}));
|
||||
|
||||
return (
|
||||
|
|
|
|||
Loading…
Reference in New Issue