"use client" import * as React from "react" import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Upload, Save, Download, Copy, Check, Loader2 } from "lucide-react" import { WebViewerInstance } from "@pdftron/webviewer" import { Project } from "@/db/schema" import { toast } from "sonner" import { quickDownload } from "@/lib/file-download" import { BasicContractTemplateViewer } from "@/lib/basic-contract/template/basic-contract-template-viewer" import { useRouter, usePathname } from "next/navigation" interface CoverTemplateDialogProps { open: boolean onOpenChange: (open: boolean) => void project: Project | null } export function CoverTemplateDialog({ open, onOpenChange, project }: CoverTemplateDialogProps) { const [instance, setInstance] = React.useState(null) const [filePath, setFilePath] = React.useState("") const [uploadedFile, setUploadedFile] = React.useState(null) const [isSaving, setIsSaving] = React.useState(false) const [isUploading, setIsUploading] = React.useState(false) const [copiedVar, setCopiedVar] = React.useState(null) const router = useRouter() // 필수 템플릿 변수 const templateVariables = [ { key: "docNumber", value: "docNumber", label: "문서 번호" }, { key: "projectNumber", value: "projectNumber", label: "프로젝트 번호" }, { key: "projectName", value: "projectName", label: "프로젝트명" } ] // instance 상태 모니터링 React.useEffect(() => { console.log("🔍 Instance 상태:", instance ? "있음" : "없음"); }, [instance]); // 다이얼로그가 열릴 때마다 상태 초기화 및 템플릿 로드 React.useEffect(() => { if (open) { // instance는 초기화하지 않음 - 뷰어가 알아서 설정함 setUploadedFile(null) setIsSaving(false) setIsUploading(false) setCopiedVar(null) // 프로젝트에 저장된 템플릿이 있으면 로드 if (project?.coverTemplatePath) { setFilePath(project.coverTemplatePath) } else { setFilePath("") } } else { // 다이얼로그가 닫힐 때만 완전히 초기화 setFilePath("") setInstance(null) setUploadedFile(null) } }, [open, project]) // 파일 업로드 핸들러 const handleFileUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0] if (!file) return if (!file.name.endsWith('.docx')) { toast.error("DOCX 파일만 업로드 가능합니다") return } setIsUploading(true) setUploadedFile(file) const formData = new FormData() formData.append("file", file) formData.append("projectId", String(project?.id)) try { const response = await fetch("/api/projects/cover-template/upload", { method: "POST", body: formData, }) if (!response.ok) { const error = await response.json() throw new Error(error.message || "업로드 실패") } const data = await response.json() setFilePath(data.filePath) router.refresh() toast.success("템플릿 파일이 업로드되었습니다") } catch (error) { console.error("파일 업로드 오류:", error) toast.error(error instanceof Error ? error.message : "파일 업로드 중 오류가 발생했습니다") } finally { setIsUploading(false) } } // 복사 함수 - 더 강력한 버전 const copyToClipboard = async (text: string, key: string) => { let copySuccess = false; // 방법 1: 최신 Clipboard API (가장 확실함) try { await navigator.clipboard.writeText(text); copySuccess = true; setCopiedVar(key); setTimeout(() => setCopiedVar(null), 2000); toast.success(`복사됨: ${text}`, { description: "문서에 붙여넣으세요 (Ctrl+V)" }); return; } catch (err) { console.error("Clipboard API 실패:", err); } // 방법 2: 이벤트 기반 복사 (사용자 상호작용 컨텍스트 유지) try { const listener = (e: ClipboardEvent) => { e.clipboardData?.setData('text/plain', text); e.preventDefault(); copySuccess = true; }; document.addEventListener('copy', listener); const result = document.execCommand('copy'); document.removeEventListener('copy', listener); if (result && copySuccess) { setCopiedVar(key); setTimeout(() => setCopiedVar(null), 2000); toast.success(`복사됨: ${text}`, { description: "문서에 붙여넣으세요 (Ctrl+V)" }); return; } } catch (err) { console.error("이벤트 기반 복사 실패:", err); } // 방법 3: textarea 방식 (강화 버전) try { const textArea = document.createElement("textarea"); textArea.value = text; // 스타일 설정으로 화면에 보이지 않게 textArea.style.position = "fixed"; textArea.style.top = "0"; textArea.style.left = "0"; textArea.style.width = "2em"; textArea.style.height = "2em"; textArea.style.padding = "0"; textArea.style.border = "none"; textArea.style.outline = "none"; textArea.style.boxShadow = "none"; textArea.style.background = "transparent"; textArea.style.opacity = "0"; document.body.appendChild(textArea); // iOS 대응 if (navigator.userAgent.match(/ipad|ipod|iphone/i)) { textArea.contentEditable = "true"; textArea.readOnly = false; const range = document.createRange(); range.selectNodeContents(textArea); const selection = window.getSelection(); selection?.removeAllRanges(); selection?.addRange(range); textArea.setSelectionRange(0, 999999); } else { textArea.select(); textArea.setSelectionRange(0, 99999); } const successful = document.execCommand('copy'); document.body.removeChild(textArea); if (successful) { setCopiedVar(key); setTimeout(() => setCopiedVar(null), 2000); toast.success(`복사됨: ${text}`, { description: "문서에 붙여넣으세요 (Ctrl+V)" }); copySuccess = true; return; } } catch (err) { console.error("textarea 복사 실패:", err); } // 모든 방법 실패 if (!copySuccess) { toast.error("자동 복사 실패", { description: `수동으로 복사하세요: ${text}`, duration: 5000, }); } }; // 템플릿 저장 const handleSaveTemplate = async () => { console.log("💾 저장 시도 - instance:", instance); console.log("💾 저장 시도 - project:", project); if (!instance) { toast.error("뷰어가 아직 준비되지 않았습니다", { description: "문서가 완전히 로드될 때까지 기다려주세요" }) return } if (!project) { toast.error("프로젝트 정보가 없습니다") return } setIsSaving(true) try { const { documentViewer } = instance.Core const doc = documentViewer.getDocument() if (!doc) { throw new Error("문서가 로드되지 않았습니다") } console.log("📄 문서 export 시작..."); // DOCX로 export const data = await doc.getFileData({ downloadType: 'office', includeAnnotations: true }) console.log("✅ 문서 export 완료, 크기:", data.byteLength); const blob = new Blob([data], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' }) // FormData 생성 const formData = new FormData() formData.append("file", blob, `${project.code}_cover_template.docx`) formData.append("projectId", String(project.id)) formData.append("templateName", `${project.name} 커버 템플릿`) console.log("📤 서버 전송 시작..."); const response = await fetch("/api/projects/cover-template/save", { method: "POST", body: formData, }) if (!response.ok) { const error = await response.json() throw new Error(error.message || "저장 실패") } const result = await response.json() console.log("✅ 서버 저장 완료:", result); router.refresh() toast.success("커버 페이지가 생성되었습니다") // 저장된 파일 경로 업데이트 if (result.filePath) { setFilePath(result.filePath) } onOpenChange(false) } catch (error) { console.error("❌ 템플릿 저장 오류:", error) toast.error(error instanceof Error ? error.message : "템플릿 저장 중 오류가 발생했습니다") } finally { setIsSaving(false) } } const handleDownloadTemplate = () => { if (!filePath || !project) return quickDownload(filePath, `${project.code}_cover_template.docx`) } return ( 커버 페이지 템플릿 관리 - {project?.name} ({project?.code})
{filePath && ( )}
{isUploading && (

업로드 중...

)}
복사 버튼을 클릭하여 변수를 복사한 후 문서에 붙여넣으세요
{templateVariables.map(({ key, value, label }) => (
{label}
))}
💡 사용 방법
1. 복사 버튼을 클릭하여 변수를 복사
2. 문서에서 원하는 위치에 Ctrl+V로 붙여넣기
3. 문서 생성 시 변수는 실제 값으로 자동 치환됩니다

📌 커스텀 변수
필요한 경우 {`{{customField}}`} 형식으로 직접 입력 가능
{/* 상태 표시 */}
파일: {filePath ? '준비됨' : '없음'}
{filePath &&
뷰어: {instance ? '준비됨' : '로딩 중...'}
}
{filePath ? (
) : (
DOCX 파일을 업로드하세요
)}
) }