From ba8cd44a0ed2c613a5f2cee06bfc9bd0f61f21c7 Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Fri, 7 Nov 2025 08:39:04 +0000
Subject: (최겸) 입찰/견적 수정사항
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../general-contract-review-request-dialog.tsx     | 891 +++++++++++++++++++++
 1 file changed, 891 insertions(+)
 create mode 100644 lib/general-contracts/detail/general-contract-review-request-dialog.tsx

(limited to 'lib/general-contracts/detail/general-contract-review-request-dialog.tsx')

diff --git a/lib/general-contracts/detail/general-contract-review-request-dialog.tsx b/lib/general-contracts/detail/general-contract-review-request-dialog.tsx
new file mode 100644
index 00000000..b487ae25
--- /dev/null
+++ b/lib/general-contracts/detail/general-contract-review-request-dialog.tsx
@@ -0,0 +1,891 @@
+'use client'
+
+import React, { useState, useEffect } from 'react'
+import { useSession } from 'next-auth/react'
+import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
+import { Button } from '@/components/ui/button'
+import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
+import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
+import { Badge } from '@/components/ui/badge'
+import { Checkbox } from '@/components/ui/checkbox'
+import { Label } from '@/components/ui/label'
+import { Input } from '@/components/ui/input'
+import { toast } from 'sonner'
+import { 
+  FileText, 
+  Upload, 
+  Eye, 
+  Send, 
+  CheckCircle, 
+  Download,
+} from 'lucide-react'
+import { 
+  getBasicInfo,
+  getContractItems,
+  getSubcontractChecklist,
+  uploadContractReviewFile,
+  sendContractReviewRequest,
+  getContractById
+} from '../service'
+
+interface ContractReviewRequestDialogProps {
+  contract: Record<string, unknown>
+  open: boolean
+  onOpenChange: (open: boolean) => void
+}
+
+interface ContractSummary {
+  basicInfo: Record<string, unknown>
+  items: Record<string, unknown>[]
+  subcontractChecklist: Record<string, unknown> | null
+}
+
+export function ContractReviewRequestDialog({ 
+  contract, 
+  open, 
+  onOpenChange 
+}: ContractReviewRequestDialogProps) {
+  const { data: session } = useSession()
+  const [currentStep, setCurrentStep] = useState(1)
+  const [contractSummary, setContractSummary] = useState<ContractSummary | null>(null)
+  const [uploadedFile, setUploadedFile] = useState<File | null>(null)
+  const [generatedPdfUrl, setGeneratedPdfUrl] = useState<string | null>(null)
+  const [generatedPdfBuffer, setGeneratedPdfBuffer] = useState<Uint8Array | null>(null)
+  const [isLoading, setIsLoading] = useState(false)
+  const [pdfViewerInstance, setPdfViewerInstance] = useState<unknown>(null)
+  const [isPdfPreviewVisible, setIsPdfPreviewVisible] = useState(false)
+
+  const contractId = contract.id as number
+  const userId = session?.user?.id || ''
+
+  // LOI 템플릿용 변수 매핑 함수
+  const mapContractSummaryToLOITemplate = (contractSummary: ContractSummary) => {
+    const { basicInfo, items } = contractSummary
+    const firstItem = items && items.length > 0 ? items[0] : {}
+
+    // 날짜 포맷팅 헬퍼 함수
+    const formatDate = (date: unknown) => {
+      if (!date) return ''
+      try {
+        const d = new Date(date)
+        return d.toLocaleDateString('ko-KR')
+      } catch {
+        return ''
+      }
+    }
+
+    return {
+      // 날짜 관련 (템플릿에서 {{todayDate}} 형식으로 사용)
+      todayDate: new Date().toLocaleDateString('ko-KR'),
+
+      // 벤더 정보
+      vendorName: basicInfo?.vendorName || '',
+      representativeName: '', // 벤더 대표자 이름 - 현재 데이터에 없음, 향후 확장 가능
+
+      // 계약 기본 정보
+      contractNumber: basicInfo?.contractNumber || '',
+
+      // 프로젝트 정보
+      projectNumber: '', // 프로젝트 코드 - 현재 데이터에 없음, 향후 확장 가능
+      projectName: basicInfo?.projectName || '',
+      project: basicInfo?.projectName || '',
+
+      // 아이템 정보
+      item: firstItem?.itemInfo || '',
+
+      // 무역 조건
+      incoterms: basicInfo?.deliveryTerm || '', // Incoterms 대신 deliveryTerm 사용
+      shippingLocation: basicInfo?.shippingLocation || '',
+
+      // 금액 및 통화
+      contractCurrency: basicInfo?.currency || '',
+      contractAmount: basicInfo?.contractAmount || '',
+      totalAmount: basicInfo?.contractAmount || '', // totalAmount가 없으면 contractAmount 사용
+
+      // 수량
+      quantity: firstItem?.quantity || '',
+
+      // 납기일
+      contractDeliveryDate: formatDate(basicInfo?.contractDeliveryDate),
+
+      // 지급 조건
+      paymentTerm: basicInfo?.paymentTerm || '',
+
+      // 유효기간
+      validityEndDate: formatDate(basicInfo?.endDate), // validityEndDate 대신 endDate 사용
+    }
+  }
+
+  // 1단계: 계약 현황 수집
+  const collectContractSummary = React.useCallback(async () => {
+    setIsLoading(true)
+    try {
+      // 각 컴포넌트에서 활성화된 데이터만 수집
+      const summary: ContractSummary = {
+        basicInfo: {},
+        items: [],
+        subcontractChecklist: null
+      }
+
+      // Basic Info 확인 (항상 활성화)
+      try {
+        const basicInfoData = await getBasicInfo(contractId)
+        if (basicInfoData && basicInfoData.success) {
+          summary.basicInfo = basicInfoData.data || {}
+        }
+        // externalYardEntry 정보도 추가로 가져오기
+        const contractData = await getContractById(contractId)
+        if (contractData) {
+          summary.basicInfo = {
+            ...summary.basicInfo,
+            externalYardEntry: contractData.externalYardEntry || 'N'
+          }
+        }
+      } catch {
+        console.log('Basic Info 데이터 없음')
+      }
+
+      // 품목 정보 확인
+      try {
+        const itemsData = await getContractItems(contractId)
+        if (itemsData && itemsData.length > 0) {
+          summary.items = itemsData
+        }
+      } catch {
+        console.log('품목 정보 데이터 없음')
+      }
+
+      try {
+        // Subcontract Checklist 확인
+        const subcontractData = await getSubcontractChecklist(contractId)
+        if (subcontractData && subcontractData.success && subcontractData.enabled) {
+          summary.subcontractChecklist = subcontractData.data
+        }
+      } catch {
+        console.log('Subcontract Checklist 데이터 없음')
+      }
+      
+      console.log('contractSummary 구조:', summary)
+      console.log('basicInfo 내용:', summary.basicInfo)
+      setContractSummary(summary)
+    } catch (error) {
+      console.error('Error collecting contract summary:', error)
+      toast.error('계약 정보를 수집하는 중 오류가 발생했습니다.')
+    } finally {
+      setIsLoading(false)
+    }
+  }, [contractId])
+
+  // 3단계: 파일 업로드 처리
+  const handleFileUpload = async (file: File) => {
+    // 파일 확장자 검증
+    const allowedExtensions = ['.doc', '.docx']
+    const fileExtension = file.name.toLowerCase().substring(file.name.lastIndexOf('.'))
+    
+    if (!allowedExtensions.includes(fileExtension)) {
+      toast.error('Word 문서(.doc, .docx) 파일만 업로드 가능합니다.')
+      return
+    }
+
+    if (!userId) {
+      toast.error('로그인이 필요합니다.')
+      return
+    }
+
+    setIsLoading(true)
+    try {
+      // 서버액션을 사용하여 파일 저장 (조건검토용)
+      const result = await uploadContractReviewFile(
+        contractId,
+        file,
+        userId
+      )
+
+      if (result.success) {
+        setUploadedFile(file)
+        toast.success('파일이 업로드되었습니다.')
+      } else {
+        throw new Error(result.error || '파일 업로드 실패')
+      }
+    } catch (error) {
+      console.error('Error uploading file:', error)
+      toast.error('파일 업로드 중 오류가 발생했습니다.')
+    } finally {
+      setIsLoading(false)
+    }
+  }
+
+  // 4단계: PDF 생성 및 미리보기 (PDFTron 사용)
+  const generatePdf = async () => {
+    if (!uploadedFile || !contractSummary) {
+      toast.error('업로드된 파일과 계약 정보가 필요합니다.')
+      return
+    }
+
+    setIsLoading(true)
+    try {
+      // PDFTron을 사용해서 변수 치환 및 PDF 변환
+      // @ts-expect-error - PDFTron WebViewer dynamic import
+      const WebViewer = await import("@pdftron/webviewer").then(({ default: WebViewer }) => WebViewer)
+      
+      // 임시 WebViewer 인스턴스 생성 (DOM에 추가하지 않음)
+      const tempDiv = document.createElement('div')
+      tempDiv.style.display = 'none'
+      document.body.appendChild(tempDiv)
+
+      const instance = await WebViewer(
+        {
+          path: "/pdftronWeb",
+          licenseKey: process.env.NEXT_PUBLIC_PDFTRON_WEBVIEW_KEY,
+          fullAPI: true,
+        },
+        tempDiv
+      )
+
+      try {
+        const { Core } = instance
+        const { createDocument } = Core
+
+      // 템플릿 문서 생성 및 변수 치환
+      const templateDoc = await createDocument(uploadedFile, {
+        filename: uploadedFile.name,
+        extension: 'docx',
+      })
+
+      // LOI 템플릿용 변수 매핑
+      const mappedTemplateData = mapContractSummaryToLOITemplate(contractSummary)
+
+      console.log("🔄 변수 치환 시작:", mappedTemplateData)
+      await templateDoc.applyTemplateValues(mappedTemplateData as Record<string, unknown>)
+      console.log("✅ 변수 치환 완료")
+
+      // PDF 변환
+      const fileData = await templateDoc.getFileData()
+        const pdfBuffer = await (Core as { officeToPDFBuffer: (data: unknown, options: { extension: string }) => Promise<Uint8Array> }).officeToPDFBuffer(fileData, { extension: 'docx' })
+
+      console.log(`✅ PDF 변환 완료: ${uploadedFile.name}`, `크기: ${pdfBuffer.byteLength} bytes`)
+
+      // PDF 버퍼를 Blob URL로 변환하여 미리보기
+      const pdfBlob = new Blob([pdfBuffer], { type: 'application/pdf' })
+      const pdfUrl = URL.createObjectURL(pdfBlob)
+      setGeneratedPdfUrl(pdfUrl)
+      
+      // PDF 버퍼를 상태에 저장 (최종 전송 시 사용)
+      setGeneratedPdfBuffer(new Uint8Array(pdfBuffer))
+      
+      toast.success('PDF가 생성되었습니다.')
+
+      } finally {
+        // 임시 WebViewer 정리
+        instance.UI.dispose()
+        document.body.removeChild(tempDiv)
+      }
+
+    } catch (error) {
+      console.error('❌ PDF 생성 실패:', error)
+      toast.error('PDF 생성 중 오류가 발생했습니다.')
+    } finally {
+      setIsLoading(false)
+    }
+  }
+
+  // PDF 미리보기 기능
+  const openPdfPreview = async () => {
+    if (!generatedPdfBuffer) {
+      toast.error('생성된 PDF가 없습니다.')
+      return
+    }
+
+    setIsLoading(true)
+    try {
+      // @ts-expect-error - PDFTron WebViewer dynamic import
+      const WebViewer = await import("@pdftron/webviewer").then(({ default: WebViewer }) => WebViewer)
+      
+      // 기존 인스턴스가 있다면 정리
+      if (pdfViewerInstance) {
+        console.log("🔄 기존 WebViewer 인스턴스 정리")
+        try {
+          pdfViewerInstance.UI.dispose()
+        } catch (error) {
+          console.warn('기존 WebViewer 정리 중 오류:', error)
+        }
+        setPdfViewerInstance(null)
+      }
+
+      // 미리보기용 컨테이너 확인
+      let previewDiv = document.getElementById('pdf-preview-container-review')
+      if (!previewDiv) {
+        console.log("🔄 컨테이너 생성")
+        previewDiv = document.createElement('div')
+        previewDiv.id = 'pdf-preview-container-review'
+        previewDiv.className = 'w-full h-full'
+        previewDiv.style.width = '100%'
+        previewDiv.style.height = '100%'
+        
+        // 실제 컨테이너에 추가
+        const actualContainer = document.querySelector('[data-pdf-container-review]')
+        if (actualContainer) {
+          actualContainer.appendChild(previewDiv)
+        }
+      }
+
+      console.log("🔄 WebViewer 인스턴스 생성 시작")
+
+      // WebViewer 인스턴스 생성 (문서 없이)
+      const instance = await Promise.race([
+        WebViewer(
+          {
+            path: "/pdftronWeb",
+            licenseKey: process.env.NEXT_PUBLIC_PDFTRON_WEBVIEW_KEY,
+            fullAPI: true,
+          },
+          previewDiv
+        ),
+        new Promise((_, reject) => 
+          setTimeout(() => reject(new Error('WebViewer 초기화 타임아웃')), 30000)
+        )
+      ])
+
+      console.log("🔄 WebViewer 인스턴스 생성 완료")
+      setPdfViewerInstance(instance)
+
+      // PDF 버퍼를 Blob으로 변환
+      const pdfBlob = new Blob([generatedPdfBuffer], { type: 'application/pdf' })
+      const pdfUrl = URL.createObjectURL(pdfBlob)
+      console.log("🔄 PDF Blob URL 생성:", pdfUrl)
+
+      // 문서 로드
+      console.log("🔄 문서 로드 시작")
+      const { documentViewer } = instance.Core
+      
+      // 문서 로드 이벤트 대기
+      await new Promise((resolve, reject) => {
+        const timeout = setTimeout(() => {
+          reject(new Error('문서 로드 타임아웃'))
+        }, 20000)
+
+        const onDocumentLoaded = () => {
+          clearTimeout(timeout)
+          documentViewer.removeEventListener('documentLoaded', onDocumentLoaded)
+          documentViewer.removeEventListener('documentError', onDocumentError)
+          console.log("🔄 문서 로드 완료")
+          resolve(true)
+        }
+
+        const onDocumentError = (error: Error) => {
+          clearTimeout(timeout)
+          documentViewer.removeEventListener('documentLoaded', onDocumentLoaded)
+          documentViewer.removeEventListener('documentError', onDocumentError)
+          console.error('문서 로드 오류:', error)
+          reject(error)
+        }
+
+        documentViewer.addEventListener('documentLoaded', onDocumentLoaded)
+        documentViewer.addEventListener('documentError', onDocumentError)
+        
+        // 문서 로드 시작
+        documentViewer.loadDocument(pdfUrl)
+      })
+
+      setIsPdfPreviewVisible(true)
+      toast.success('PDF 미리보기가 준비되었습니다.')
+
+    } catch (error) {
+      console.error('PDF 미리보기 실패:', error)
+      const errorMessage = error instanceof Error ? error.message : '알 수 없는 오류'
+      toast.error(`PDF 미리보기 중 오류가 발생했습니다: ${errorMessage}`)
+    } finally {
+      setIsLoading(false)
+    }
+  }
+
+  // PDF 다운로드 기능
+  const downloadPdf = () => {
+    if (!generatedPdfBuffer) {
+      toast.error('다운로드할 PDF가 없습니다.')
+      return
+    }
+
+    const pdfBlob = new Blob([generatedPdfBuffer], { type: 'application/pdf' })
+    const pdfUrl = URL.createObjectURL(pdfBlob)
+    
+    const link = document.createElement('a')
+    link.href = pdfUrl
+    link.download = `contract_review_${contractId}_${Date.now()}.pdf`
+    document.body.appendChild(link)
+    link.click()
+    document.body.removeChild(link)
+    
+    URL.revokeObjectURL(pdfUrl)
+    toast.success('PDF가 다운로드되었습니다.')
+  }
+
+  // PDF 미리보기 닫기
+  const closePdfPreview = () => {
+    console.log("🔄 PDF 미리보기 닫기 시작")
+    if (pdfViewerInstance) {
+      try {
+        console.log("🔄 WebViewer 인스턴스 정리")
+        pdfViewerInstance.UI.dispose()
+      } catch (error) {
+        console.warn('WebViewer 정리 중 오류:', error)
+      }
+      setPdfViewerInstance(null)
+    }
+    
+    // 컨테이너 정리
+    const previewDiv = document.getElementById('pdf-preview-container-review')
+    if (previewDiv) {
+      try {
+        previewDiv.innerHTML = ''
+      } catch (error) {
+        console.warn('컨테이너 정리 중 오류:', error)
+      }
+    }
+    
+    setIsPdfPreviewVisible(false)
+    console.log("🔄 PDF 미리보기 닫기 완료")
+  }
+
+  // 최종 전송
+  const handleFinalSubmit = async () => {
+    if (!generatedPdfUrl || !contractSummary || !generatedPdfBuffer) {
+      toast.error('생성된 PDF가 필요합니다.')
+      return
+    }
+
+    if (!userId) {
+      toast.error('로그인이 필요합니다.')
+      return
+    }
+
+    setIsLoading(true)
+    try {
+      // 서버액션을 사용하여 조건검토요청 전송
+      const result = await sendContractReviewRequest(
+        contractSummary,
+        generatedPdfBuffer,
+        contractId,
+        userId
+      )
+
+      if (result.success) {
+        toast.success('조건검토요청이 전송되었습니다.')
+        onOpenChange(false)
+        // 페이지 새로고침을 위해 window.location.reload() 호출
+        window.location.reload()
+      } else {
+        // 서버에서 이미 처리된 에러 메시지 표시
+        toast.error(result.error || '조건검토요청 전송 실패')
+        return
+      }
+    } catch (error) {
+      console.error('Error submitting review request:', error)
+      toast.error('조건검토요청 전송 중 오류가 발생했습니다.')
+    } finally {
+      setIsLoading(false)
+    }
+  }
+
+  // 다이얼로그가 열릴 때 1단계 데이터 수집
+  useEffect(() => {
+    if (open && currentStep === 1) {
+      collectContractSummary()
+    }
+  }, [open, currentStep, collectContractSummary])
+
+  // 다이얼로그가 닫힐 때 PDF 뷰어 정리
+  useEffect(() => {
+    if (!open) {
+      closePdfPreview()
+      // 상태 초기화
+      setCurrentStep(1)
+      setUploadedFile(null)
+      setGeneratedPdfUrl(null)
+      setGeneratedPdfBuffer(null)
+      setIsPdfPreviewVisible(false)
+    }
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [open])
+
+
+  return (
+    <Dialog open={open} onOpenChange={onOpenChange}>
+      <DialogContent className="max-w-6xl max-h-[90vh] overflow-y-auto">
+        <DialogHeader>
+          <DialogTitle className="flex items-center gap-2">
+            <FileText className="h-5 w-5" />
+            조건검토요청
+          </DialogTitle>
+        </DialogHeader>
+
+        <Tabs value={currentStep.toString()} className="w-full">
+          <TabsList className="grid w-full grid-cols-3">
+            <TabsTrigger value="1" disabled={currentStep < 1}>
+              1. 미리보기
+            </TabsTrigger>
+            <TabsTrigger value="2" disabled={currentStep < 2}>
+              2. 템플릿 업로드
+            </TabsTrigger>
+            <TabsTrigger value="3" disabled={currentStep < 3}>
+              3. PDF 미리보기
+            </TabsTrigger>
+          </TabsList>
+
+          {/* 1단계: 계약 현황 정리 */}
+          <TabsContent value="1" className="space-y-4">
+            <Card>
+              <CardHeader>
+                <CardTitle className="flex items-center gap-2">
+                  <CheckCircle className="h-5 w-5 text-green-600" />
+                  작성된 계약 현황
+                </CardTitle>
+              </CardHeader>
+              <CardContent className="space-y-4">
+                {isLoading ? (
+                  <div className="text-center py-4">
+                    <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto"></div>
+                    <p className="mt-2 text-sm text-muted-foreground">계약 정보를 수집하는 중...</p>
+                  </div>
+                ) : (
+                  <div className="space-y-4">
+                    {/* 기본 정보 (필수) */}
+                    <div className="border rounded-lg p-4">
+                      <div className="flex items-center gap-2 mb-3">
+                        <CheckCircle className="h-4 w-4 text-green-600" />
+                        <Label className="font-medium">기본 정보</Label>
+                        <Badge variant="secondary">필수</Badge>
+                      </div>
+                      <div className="grid grid-cols-2 gap-4 text-sm">
+                        <div>
+                          <span className="font-medium">계약번호:</span> {String(contractSummary?.basicInfo?.contractNumber || '')}
+                        </div>
+                        <div>
+                          <span className="font-medium">계약명:</span> {String(contractSummary?.basicInfo?.contractName || '')}
+                        </div>
+                        <div>
+                          <span className="font-medium">벤더:</span> {String(contractSummary?.basicInfo?.vendorName || '')}
+                        </div>
+                        <div>
+                          <span className="font-medium">프로젝트:</span> {String(contractSummary?.basicInfo?.projectName || '')}
+                        </div>
+                        <div>
+                          <span className="font-medium">계약유형:</span> {String(contractSummary?.basicInfo?.contractType || '')}
+                        </div>
+                        <div>
+                          <span className="font-medium">계약상태:</span> {String(contractSummary?.basicInfo?.contractStatus || '')}
+                        </div>
+                        <div>
+                          <span className="font-medium">계약금액:</span> {String(contractSummary?.basicInfo?.contractAmount || '')} {String(contractSummary?.basicInfo?.currency || '')}
+                        </div>
+                        <div>
+                          <span className="font-medium">계약기간:</span> {String(contractSummary?.basicInfo?.startDate || '')} ~ {String(contractSummary?.basicInfo?.endDate || '')}
+                        </div>
+                        <div>
+                          <span className="font-medium">사양서 유형:</span> {String(contractSummary?.basicInfo?.specificationType || '')}
+                        </div>
+                        <div>
+                          <span className="font-medium">단가 유형:</span> {String(contractSummary?.basicInfo?.unitPriceType || '')}
+                        </div>
+                        <div>
+                          <span className="font-medium">연결 PO번호:</span> {String(contractSummary?.basicInfo?.linkedPoNumber || '')}
+                        </div>
+                        <div>
+                          <span className="font-medium">연결 입찰번호:</span> {String(contractSummary?.basicInfo?.linkedBidNumber || '')}
+                        </div>
+                      </div>
+                    </div>
+
+                    {/* 지급/인도 조건 */}
+                    <div className="border rounded-lg p-4">
+                      <div className="flex items-center gap-2 mb-3">
+                        <CheckCircle className="h-4 w-4 text-green-600" />
+                        <Label className="font-medium">지급/인도 조건</Label>
+                        <Badge variant="secondary">필수</Badge>
+                      </div>
+                      <div className="grid grid-cols-2 gap-4 text-sm">
+                        <div>
+                          <span className="font-medium">지급조건:</span> {String(contractSummary?.basicInfo?.paymentTerm || '')}
+                        </div>
+                        <div>
+                          <span className="font-medium">세금 유형:</span> {String(contractSummary?.basicInfo?.taxType || '')}
+                        </div>
+                        <div>
+                          <span className="font-medium">인도조건:</span> {String(contractSummary?.basicInfo?.deliveryTerm || '')}
+                        </div>
+                        <div>
+                          <span className="font-medium">인도유형:</span> {String(contractSummary?.basicInfo?.deliveryType || '')}
+                        </div>
+                        <div>
+                          <span className="font-medium">선적지:</span> {String(contractSummary?.basicInfo?.shippingLocation || '')}
+                        </div>
+                        <div>
+                          <span className="font-medium">하역지:</span> {String(contractSummary?.basicInfo?.dischargeLocation || '')}
+                        </div>
+                        <div>
+                          <span className="font-medium">계약납기:</span> {String(contractSummary?.basicInfo?.contractDeliveryDate || '')}
+                        </div>
+                        <div>
+                          <span className="font-medium">위약금:</span> {contractSummary?.basicInfo?.liquidatedDamages ? '적용' : '미적용'}
+                        </div>
+                      </div>
+                    </div>
+
+                    {/* 추가 조건 */}
+                    <div className="border rounded-lg p-4">
+                      <div className="flex items-center gap-2 mb-3">
+                        <CheckCircle className="h-4 w-4 text-green-600" />
+                        <Label className="font-medium">추가 조건</Label>
+                        <Badge variant="secondary">필수</Badge>
+                      </div>
+                      <div className="grid grid-cols-2 gap-4 text-sm">
+                        <div>
+                          <span className="font-medium">연동제 정보:</span> {String(contractSummary?.basicInfo?.interlockingSystem || '')}
+                        </div>
+                        <div>
+                          <span className="font-medium">계약성립조건:</span> 
+                          {contractSummary?.basicInfo?.contractEstablishmentConditions && 
+                            Object.entries(contractSummary.basicInfo.contractEstablishmentConditions)
+                              .filter(([, value]) => value === true)
+                              .map(([key]) => key)
+                              .join(', ') || '없음'}
+                        </div>
+                        <div>
+                          <span className="font-medium">계약해지조건:</span>
+                          {contractSummary?.basicInfo?.contractTerminationConditions && 
+                            Object.entries(contractSummary.basicInfo.contractTerminationConditions)
+                              .filter(([, value]) => value === true)
+                              .map(([key]) => key)
+                              .join(', ') || '없음'}
+                        </div>
+                      </div>
+                    </div>
+
+                    {/* 품목 정보 */}
+                    <div className="border rounded-lg p-4">
+                      <div className="flex items-center gap-2 mb-3">
+                        <Checkbox 
+                          id="items-enabled" 
+                          checked={contractSummary?.items && contractSummary.items.length > 0}
+                          disabled
+                        />
+                        <Label htmlFor="items-enabled" className="font-medium">품목 정보</Label>
+                        <Badge variant="outline">선택</Badge>
+                      </div>
+                      {contractSummary?.items && contractSummary.items.length > 0 ? (
+                        <div className="space-y-2">
+                          <p className="text-sm text-muted-foreground">
+                            총 {contractSummary.items.length}개 품목이 입력되어 있습니다.
+                          </p>
+                          <div className="max-h-32 overflow-y-auto">
+                            {contractSummary.items.slice(0, 3).map((item: Record<string, unknown>, index: number) => (
+                              <div key={index} className="text-xs bg-gray-50 p-2 rounded">
+                                <div className="font-medium">{item.itemInfo || item.description || `품목 ${index + 1}`}</div>
+                                <div className="text-muted-foreground">
+                                  수량: {item.quantity || 0} | 단가: {item.contractUnitPrice || item.unitPrice || 0}
+                                </div>
+                              </div>
+                            ))}
+                            {contractSummary.items.length > 3 && (
+                              <div className="text-xs text-muted-foreground text-center">
+                                ... 외 {contractSummary.items.length - 3}개 품목
+                              </div>
+                            )}
+                          </div>
+                        </div>
+                      ) : (
+                        <p className="text-sm text-muted-foreground">
+                          품목 정보가 입력되지 않았습니다.
+                        </p>
+                      )}
+                    </div>
+
+                    {/* 하도급 체크리스트 */}
+                    <div className="border rounded-lg p-4">
+                      <div className="flex items-center gap-2 mb-3">
+                        <Checkbox 
+                          id="subcontract-enabled" 
+                          checked={!!contractSummary?.subcontractChecklist}
+                          disabled
+                        />
+                        <Label htmlFor="subcontract-enabled" className="font-medium">
+                          하도급 체크리스트
+                        </Label>
+                        <Badge variant="outline">선택</Badge>
+                      </div>
+                      <p className="text-sm text-muted-foreground">
+                        {contractSummary?.subcontractChecklist 
+                          ? '정보가 입력되어 있습니다.' 
+                          : '정보가 입력되지 않았습니다.'}
+                      </p>
+                    </div>
+                  </div>
+                )}
+              </CardContent>
+            </Card>
+
+            <div className="flex justify-end">
+              <Button 
+                onClick={() => setCurrentStep(2)}
+                disabled={isLoading}
+              >
+                다음 단계
+              </Button>
+            </div>
+          </TabsContent>
+
+          {/* 2단계: 문서 업로드 */}
+          <TabsContent value="2" className="space-y-4">
+            <Card>
+              <CardHeader>
+                <CardTitle className="flex items-center gap-2">
+                  <Upload className="h-5 w-5 text-blue-600" />
+                  계약서 템플릿 업로드
+                </CardTitle>
+              </CardHeader>
+              <CardContent className="space-y-4">
+                <div className="space-y-4">
+                <p className="text-lg text-muted-foreground">일반계약 표준문서 관리 페이지에 접속하여, 원하는 양식의 계약서를 다운받아 수정 후 업로드하세요.</p>
+                  <div>
+                    <Label htmlFor="file-upload-review">파일 업로드</Label>
+                    <Input
+                      id="file-upload-review"
+                      type="file"
+                      accept=".doc,.docx"
+                      onChange={(e) => {
+                        const file = e.target.files?.[0]
+                        if (file) handleFileUpload(file)
+                      }}
+                    />
+                    <p className="text-sm text-muted-foreground mt-1">
+                      Word 문서(.doc, .docx) 파일만 업로드 가능합니다.
+                    </p>
+                  </div>
+
+                  {uploadedFile && (
+                    <div className="border rounded-lg p-4 bg-green-50">
+                      <div className="flex items-center gap-2">
+                        <CheckCircle className="h-4 w-4 text-green-600" />
+                        <span className="font-medium text-green-900">업로드 완료</span>
+                      </div>
+                      <p className="text-sm text-green-800 mt-1">{uploadedFile.name}</p>
+                    </div>
+                  )}
+                </div>
+              </CardContent>
+            </Card>
+
+            <div className="flex justify-between">
+              <Button variant="outline" onClick={() => setCurrentStep(1)}>
+                이전 단계
+              </Button>
+              <Button
+                onClick={() => setCurrentStep(3)}
+                disabled={!uploadedFile}
+              >
+                다음 단계
+              </Button>
+            </div>
+          </TabsContent>
+
+          {/* 3단계: PDF 미리보기 */}
+          <TabsContent value="3" className="space-y-4">
+            <Card>
+              <CardHeader>
+                <CardTitle className="flex items-center gap-2">
+                  <Eye className="h-5 w-5 text-purple-600" />
+                  PDF 미리보기
+                </CardTitle>
+              </CardHeader>
+              <CardContent className="space-y-4">
+                {!generatedPdfUrl ? (
+                  <div className="text-center py-8">
+                    <Button onClick={generatePdf} disabled={isLoading}>
+                      {isLoading ? 'PDF 생성 중...' : 'PDF 생성하기'}
+                    </Button>
+                  </div>
+                ) : (
+                  <div className="space-y-4">
+                    <div className="border rounded-lg p-4 bg-green-50">
+                      <div className="flex items-center gap-2">
+                        <CheckCircle className="h-4 w-4 text-green-600" />
+                        <span className="font-medium text-green-900">PDF 생성 완료</span>
+                      </div>
+                    </div>
+
+                    <div className="border rounded-lg p-4">
+                      <div className="flex items-center justify-between mb-4">
+                        <h4 className="font-medium">생성된 PDF</h4>
+                        <div className="flex gap-2">
+                          <Button 
+                            variant="outline" 
+                            size="sm"
+                            onClick={downloadPdf}
+                            disabled={isLoading}
+                          >
+                            <Download className="h-4 w-4 mr-2" />
+                            다운로드
+                          </Button>
+                          <Button 
+                            variant="outline" 
+                            size="sm"
+                            onClick={openPdfPreview}
+                            disabled={isLoading}
+                          >
+                            <Eye className="h-4 w-4 mr-2" />
+                            미리보기
+                          </Button>
+                        </div>
+                      </div>
+                      
+                      {/* PDF 미리보기 영역 */}
+                      <div className="border rounded-lg h-96 bg-gray-50 relative" data-pdf-container-review>
+                        {isPdfPreviewVisible ? (
+                          <>
+                            <div className="absolute top-2 right-2 z-10">
+                              <Button 
+                                variant="outline" 
+                                size="sm"
+                                onClick={closePdfPreview}
+                                className="bg-white/90 hover:bg-white"
+                              >
+                                ✕ 닫기
+                              </Button>
+                            </div>
+                            <div id="pdf-preview-container-review" className="w-full h-full" />
+                          </>
+                        ) : (
+                          <div className="flex items-center justify-center h-full">
+                            <div className="text-center text-muted-foreground">
+                              <FileText className="h-12 w-12 mx-auto mb-2" />
+                              <p>미리보기 버튼을 클릭하여 PDF를 확인하세요</p>
+                            </div>
+                          </div>
+                        )}
+                      </div>
+                    </div>
+                  </div>
+                )}
+              </CardContent>
+            </Card>
+
+            <div className="flex justify-between">
+              <Button variant="outline" onClick={() => setCurrentStep(2)}>
+                이전 단계
+              </Button>
+              <Button
+                onClick={handleFinalSubmit}
+                disabled={!generatedPdfUrl || isLoading}
+                className="bg-green-600 hover:bg-green-700"
+              >
+                <Send className="h-4 w-4 mr-2" />
+                {isLoading ? '전송 중...' : '조건검토요청 전송'}
+              </Button>
+            </div>
+          </TabsContent>
+        </Tabs>
+      </DialogContent>
+    </Dialog>
+  )
+}
+
-- 
cgit v1.2.3

