"use client";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Printer, FileDown, FileText } from "lucide-react";
import { useReportDesigner } from "@/contexts/ReportDesignerContext";
import { useState } from "react";
import { useToast } from "@/hooks/use-toast";
import { Document, Packer, Paragraph, TextRun, Table, TableCell, TableRow, WidthType } from "docx";
import { getFullImageUrl } from "@/lib/api/client";
interface ReportPreviewModalProps {
isOpen: boolean;
onClose: () => void;
}
export function ReportPreviewModal({ isOpen, onClose }: ReportPreviewModalProps) {
const { layoutConfig, getQueryResult, reportDetail } = useReportDesigner();
const [isExporting, setIsExporting] = useState(false);
const { toast } = useToast();
// 컴포넌트의 실제 표시 값 가져오기
const getComponentValue = (component: any): string => {
if (component.queryId && component.fieldName) {
const queryResult = getQueryResult(component.queryId);
if (queryResult && queryResult.rows.length > 0) {
const value = queryResult.rows[0][component.fieldName];
if (value !== null && value !== undefined) {
return String(value);
}
}
return `{${component.fieldName}}`;
}
return component.defaultValue || "텍스트";
};
const handlePrint = () => {
// HTML 생성하여 인쇄
const printHtml = generatePrintHTML();
const printWindow = window.open("", "_blank");
if (!printWindow) return;
printWindow.document.write(printHtml);
printWindow.document.close();
printWindow.print();
};
// 페이지별 컴포넌트 HTML 생성
const generatePageHTML = (
pageComponents: any[],
pageWidth: number,
pageHeight: number,
backgroundColor: string,
): string => {
const componentsHTML = pageComponents
.map((component) => {
const queryResult = component.queryId ? getQueryResult(component.queryId) : null;
let content = "";
// Text/Label 컴포넌트
if (component.type === "text" || component.type === "label") {
const displayValue = getComponentValue(component);
content = `
${displayValue}
`;
}
// Image 컴포넌트
else if (component.type === "image" && component.imageUrl) {
const imageUrl = component.imageUrl.startsWith("data:")
? component.imageUrl
: getFullImageUrl(component.imageUrl);
content = `
`;
}
// 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 = ``;
}
// 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 = `
${showLabel ? `
${labelText}
` : ""}
${imageUrl ? `

` : ""}
${component.showUnderline ? '
' : ""}
`;
} else {
content = `
${showLabel && labelPosition === "top" ? `
${labelText}
` : ""}
${imageUrl ? `

` : ""}
${component.showUnderline ? '
' : ""}
${showLabel && labelPosition === "bottom" ? `
${labelText}
` : ""}
`;
}
}
// 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 = `
${personName ? `
${personName}
` : ""}
${imageUrl ? `

` : ""}
${showLabel ? `
${labelText}
` : ""}
`;
}
// 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) => `
${columns.map((col: { field: string; align?: string }) => `| ${String(row[col.field] ?? "")} | `).join("")}
`,
)
.join("");
content = `
${columns.map((col: { header: string; align?: string; width?: number }) => `| ${col.header} | `).join("")}
${tableRows}
`;
}
return `
${content}
`;
})
.join("");
return `
${componentsHTML}
`;
};
// 모든 페이지 HTML 생성 (인쇄/PDF용)
const generatePrintHTML = (): string => {
const pagesHTML = layoutConfig.pages
.sort((a, b) => a.page_order - b.page_order)
.map((page) => generatePageHTML(page.components, page.width, page.height, page.background_color))
.join('');
return `
리포트 인쇄
${pagesHTML}
`;
};
// PDF 다운로드 (브라우저 인쇄 기능 이용)
const handleDownloadPDF = () => {
const printHtml = generatePrintHTML();
const printWindow = window.open("", "_blank");
if (!printWindow) return;
printWindow.document.write(printHtml);
printWindow.document.close();
toast({
title: "안내",
description: "인쇄 대화상자에서 'PDF로 저장'을 선택하세요.",
});
};
// WORD 다운로드
const handleDownloadWord = async () => {
setIsExporting(true);
try {
// 컴포넌트를 Paragraph로 변환
const paragraphs: (Paragraph | Table)[] = [];
// 모든 페이지의 컴포넌트 수집
const allComponents = layoutConfig.pages
.sort((a, b) => a.page_order - b.page_order)
.flatMap((page) => page.components);
// Y 좌표로 정렬
const sortedComponents = [...allComponents].sort((a, b) => a.y - b.y);
for (const component of sortedComponents) {
if (component.type === "text" || component.type === "label") {
const value = getComponentValue(component);
paragraphs.push(
new Paragraph({
children: [
new TextRun({
text: value,
size: (component.fontSize || 13) * 2, // pt to half-pt
color: component.fontColor?.replace("#", "") || "000000",
bold: component.fontWeight === "bold",
}),
],
spacing: {
after: 200,
},
}),
);
} else if (component.type === "table" && component.queryId) {
const queryResult = getQueryResult(component.queryId);
if (queryResult && queryResult.rows.length > 0) {
// 테이블 헤더
const headerCells = queryResult.fields.map(
(field) =>
new TableCell({
children: [new Paragraph({ text: field })],
width: { size: 100 / queryResult.fields.length, type: WidthType.PERCENTAGE },
}),
);
// 테이블 행
const dataRows = queryResult.rows.map(
(row) =>
new TableRow({
children: queryResult.fields.map(
(field) =>
new TableCell({
children: [new Paragraph({ text: String(row[field] ?? "") })],
}),
),
}),
);
const table = new Table({
rows: [new TableRow({ children: headerCells }), ...dataRows],
width: { size: 100, type: WidthType.PERCENTAGE },
});
paragraphs.push(table);
}
}
}
// 문서 생성
const doc = new Document({
sections: [
{
properties: {},
children: paragraphs,
},
],
});
// Blob 생성 및 다운로드
const blob = await Packer.toBlob(doc);
const fileName = reportDetail?.report?.report_name_kor || "리포트";
const timestamp = new Date().toISOString().slice(0, 10);
const url = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = `${fileName}_${timestamp}.docx`;
link.click();
window.URL.revokeObjectURL(url);
toast({
title: "성공",
description: "WORD 파일이 다운로드되었습니다.",
});
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "WORD 생성에 실패했습니다.";
toast({
title: "오류",
description: errorMessage,
variant: "destructive",
});
} finally {
setIsExporting(false);
}
};
return (
);
}