2025-10-01 12:00:13 +09:00
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
import {
|
|
|
|
|
Dialog,
|
|
|
|
|
DialogContent,
|
|
|
|
|
DialogDescription,
|
|
|
|
|
DialogFooter,
|
|
|
|
|
DialogHeader,
|
|
|
|
|
DialogTitle,
|
|
|
|
|
} from "@/components/ui/dialog";
|
|
|
|
|
import { Button } from "@/components/ui/button";
|
|
|
|
|
import { Printer, FileDown } from "lucide-react";
|
|
|
|
|
import { useReportDesigner } from "@/contexts/ReportDesignerContext";
|
|
|
|
|
|
|
|
|
|
interface ReportPreviewModalProps {
|
|
|
|
|
isOpen: boolean;
|
|
|
|
|
onClose: () => void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function ReportPreviewModal({ isOpen, onClose }: ReportPreviewModalProps) {
|
2025-10-01 13:58:55 +09:00
|
|
|
const { components, canvasWidth, canvasHeight, getQueryResult } = useReportDesigner();
|
|
|
|
|
|
|
|
|
|
// 컴포넌트의 실제 표시 값 가져오기
|
|
|
|
|
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 || "텍스트";
|
|
|
|
|
};
|
2025-10-01 12:00:13 +09:00
|
|
|
|
|
|
|
|
const handlePrint = () => {
|
2025-10-01 13:58:55 +09:00
|
|
|
// 현재 미리보기 영역만 인쇄
|
|
|
|
|
const printContent = document.getElementById("preview-content");
|
|
|
|
|
if (!printContent) return;
|
|
|
|
|
|
|
|
|
|
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.close();
|
|
|
|
|
printWindow.print();
|
2025-10-01 12:00:13 +09:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleDownloadPDF = () => {
|
|
|
|
|
alert("PDF 다운로드 기능은 추후 구현 예정입니다.");
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleDownloadWord = () => {
|
|
|
|
|
alert("WORD 다운로드 기능은 추후 구현 예정입니다.");
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Dialog open={isOpen} onOpenChange={onClose}>
|
|
|
|
|
<DialogContent className="max-w-4xl">
|
|
|
|
|
<DialogHeader>
|
|
|
|
|
<DialogTitle>미리보기</DialogTitle>
|
|
|
|
|
<DialogDescription>
|
|
|
|
|
현재 레이아웃의 미리보기입니다. 인쇄하거나 파일로 다운로드할 수 있습니다.
|
|
|
|
|
</DialogDescription>
|
|
|
|
|
</DialogHeader>
|
|
|
|
|
|
|
|
|
|
{/* 미리보기 영역 */}
|
|
|
|
|
<div className="max-h-[500px] overflow-auto rounded border bg-gray-100 p-4">
|
|
|
|
|
<div
|
2025-10-01 13:58:55 +09:00
|
|
|
id="preview-content"
|
2025-10-01 12:00:13 +09:00
|
|
|
className="relative mx-auto bg-white shadow-lg"
|
|
|
|
|
style={{
|
|
|
|
|
width: `${canvasWidth}mm`,
|
|
|
|
|
minHeight: `${canvasHeight}mm`,
|
|
|
|
|
}}
|
|
|
|
|
>
|
2025-10-01 13:58:55 +09:00
|
|
|
{components.map((component) => {
|
|
|
|
|
const displayValue = getComponentValue(component);
|
|
|
|
|
const queryResult = component.queryId ? getQueryResult(component.queryId) : null;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
key={component.id}
|
|
|
|
|
className="absolute"
|
|
|
|
|
style={{
|
|
|
|
|
left: `${component.x}px`,
|
|
|
|
|
top: `${component.y}px`,
|
|
|
|
|
width: `${component.width}px`,
|
|
|
|
|
height: `${component.height}px`,
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{component.type === "text" && (
|
|
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
fontSize: `${component.fontSize}px`,
|
|
|
|
|
color: component.fontColor,
|
|
|
|
|
backgroundColor: component.backgroundColor,
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{displayValue}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{component.type === "label" && (
|
|
|
|
|
<div
|
|
|
|
|
className="font-semibold"
|
|
|
|
|
style={{
|
|
|
|
|
fontSize: `${component.fontSize}px`,
|
|
|
|
|
color: component.fontColor,
|
|
|
|
|
backgroundColor: component.backgroundColor,
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{displayValue}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{component.type === "table" && queryResult && queryResult.rows.length > 0 ? (
|
2025-10-01 12:00:13 +09:00
|
|
|
<table className="w-full border-collapse text-xs">
|
|
|
|
|
<thead>
|
|
|
|
|
<tr className="bg-gray-100">
|
2025-10-01 13:58:55 +09:00
|
|
|
{queryResult.fields.map((field) => (
|
|
|
|
|
<th key={field} className="border border-gray-300 p-1">
|
|
|
|
|
{field}
|
|
|
|
|
</th>
|
|
|
|
|
))}
|
2025-10-01 12:00:13 +09:00
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody>
|
2025-10-01 13:58:55 +09:00
|
|
|
{queryResult.rows.map((row, idx) => (
|
|
|
|
|
<tr key={idx}>
|
|
|
|
|
{queryResult.fields.map((field) => (
|
|
|
|
|
<td key={field} className="border border-gray-300 p-1">
|
|
|
|
|
{String(row[field] ?? "")}
|
|
|
|
|
</td>
|
|
|
|
|
))}
|
|
|
|
|
</tr>
|
|
|
|
|
))}
|
2025-10-01 12:00:13 +09:00
|
|
|
</tbody>
|
|
|
|
|
</table>
|
2025-10-01 13:58:55 +09:00
|
|
|
) : component.type === "table" ? (
|
|
|
|
|
<div className="text-xs text-gray-400">쿼리를 실행해주세요</div>
|
|
|
|
|
) : null}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
})}
|
2025-10-01 12:00:13 +09:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<DialogFooter>
|
|
|
|
|
<Button variant="outline" onClick={onClose}>
|
|
|
|
|
닫기
|
|
|
|
|
</Button>
|
|
|
|
|
<Button variant="outline" onClick={handlePrint} className="gap-2">
|
|
|
|
|
<Printer className="h-4 w-4" />
|
|
|
|
|
인쇄
|
|
|
|
|
</Button>
|
|
|
|
|
<Button onClick={handleDownloadPDF} className="gap-2">
|
|
|
|
|
<FileDown className="h-4 w-4" />
|
|
|
|
|
PDF
|
|
|
|
|
</Button>
|
|
|
|
|
<Button onClick={handleDownloadWord} variant="secondary" className="gap-2">
|
|
|
|
|
<FileDown className="h-4 w-4" />
|
|
|
|
|
WORD
|
|
|
|
|
</Button>
|
|
|
|
|
</DialogFooter>
|
|
|
|
|
</DialogContent>
|
|
|
|
|
</Dialog>
|
|
|
|
|
);
|
|
|
|
|
}
|