From cf8dac0c6490469dab88a560004b0c07dbd48612 Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Thu, 18 Sep 2025 00:23:40 +0000
Subject: (대표님) rfq, 계약, 서명 등
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 lib/rfq-last/vendor/send-rfq-dialog.tsx | 378 ++++++++++++++++----------------
 1 file changed, 189 insertions(+), 189 deletions(-)

(limited to 'lib/rfq-last/vendor/send-rfq-dialog.tsx')

diff --git a/lib/rfq-last/vendor/send-rfq-dialog.tsx b/lib/rfq-last/vendor/send-rfq-dialog.tsx
index ce97dcde..34777864 100644
--- a/lib/rfq-last/vendor/send-rfq-dialog.tsx
+++ b/lib/rfq-last/vendor/send-rfq-dialog.tsx
@@ -123,13 +123,13 @@ interface Vendor {
   contactsByPosition?: Record<string, ContactDetail[]>;
   primaryEmail?: string | null;
   currency?: string | null;
-  
+
   // 기본계약 정보
   ndaYn?: boolean;
   generalGtcYn?: boolean;
   projectGtcYn?: boolean;
   agreementYn?: boolean;
-  
+
   // 발송 정보
   sendVersion?: number;
 }
@@ -243,15 +243,15 @@ export function SendRfqDialog({
   const [showCustomEmailForm, setShowCustomEmailForm] = React.useState<Record<number, boolean>>({});
   const [showResendConfirmDialog, setShowResendConfirmDialog] = React.useState(false);
   const [resendVendorsInfo, setResendVendorsInfo] = React.useState<{ count: number; names: string[] }>({ count: 0, names: [] });
-  
+
   const [isGeneratingPdfs, setIsGeneratingPdfs] = React.useState(false);
   const [pdfGenerationProgress, setPdfGenerationProgress] = React.useState(0);
   const [currentGeneratingContract, setCurrentGeneratingContract] = React.useState("");
   const [generatedPdfs, setGeneratedPdfs] = React.useState<Map<string, { buffer: number[], fileName: string }>>(new Map());
-  
+
   // 재전송 시 기본계약 스킵 옵션 - 업체별 관리
   const [skipContractsForVendor, setSkipContractsForVendor] = React.useState<Record<number, boolean>>({});
-  
+
   const generateContractPdf = async (
     vendor: VendorWithRecipients,
     contractType: string,
@@ -288,9 +288,9 @@ export function SendRfqDialog({
 
       // 3. PDFtron WebViewer로 PDF 변환
       const pdfBuffer = await convertToPdfWithWebViewer(templateFile, templateData);
-      
+
       const fileName = `${contractType}_${vendor.vendorCode || vendor.vendorId}_${Date.now()}.pdf`;
-      
+
       return {
         buffer: Array.from(pdfBuffer), // Uint8Array를 일반 배열로 변환
         fileName
@@ -374,7 +374,7 @@ export function SendRfqDialog({
       // 초기화
       setCustomEmailInputs({});
       setShowCustomEmailForm({});
-      
+
       // 재전송 업체들의 기본계약 스킵 옵션 초기화 (기본값: false - 재생성)
       const skipOptions: Record<number, boolean> = {};
       selectedVendors.forEach(v => {
@@ -511,137 +511,137 @@ export function SendRfqDialog({
   const proceedWithSend = React.useCallback(async () => {
     try {
       setIsSending(true);
-      
-   // 기본계약이 필요한 계약서 목록 수집
-   const contractsToGenerate: ContractToGenerate[] = [];
-      
-   for (const vendor of vendorsWithRecipients) {
-     // 재전송 업체이고 해당 업체의 스킵 옵션이 켜져 있으면 계약서 생성 건너뛰기
-     const isResendVendor = vendor.sendVersion && vendor.sendVersion > 0;
-     if (isResendVendor && skipContractsForVendor[vendor.vendorId]) {
-       continue; // 이 벤더의 계약서 생성을 스킵
-     }
-     
-     if (vendor.ndaYn) {
-       contractsToGenerate.push({
-         vendorId: vendor.vendorId,
-         vendorName: vendor.vendorName,
-         type: "NDA",
-         templateName: "비밀"
-       });
-     }
-     if (vendor.generalGtcYn) {
-       contractsToGenerate.push({
-         vendorId: vendor.vendorId,
-         vendorName: vendor.vendorName,
-         type: "General_GTC",
-         templateName: "General GTC"
-       });
-     }
-     if (vendor.projectGtcYn && rfqInfo?.projectCode) {
-       contractsToGenerate.push({
-         vendorId: vendor.vendorId,
-         vendorName: vendor.vendorName,
-         type: "Project_GTC",
-         templateName: rfqInfo.projectCode
-       });
-     }
-     if (vendor.agreementYn) {
-       contractsToGenerate.push({
-         vendorId: vendor.vendorId,
-         vendorName: vendor.vendorName,
-         type: "기술자료",
-         templateName: "기술"
-       });
-     }
-   }
-
-   let pdfsMap = new Map<string, { buffer: number[], fileName: string }>();
-
-   // PDF 생성이 필요한 경우
-   if (contractsToGenerate.length > 0) {
-    setIsGeneratingPdfs(true);
-    setPdfGenerationProgress(0);
-    
-    try {
-      let completed = 0;
-
-      for (const contract of contractsToGenerate) {
-        setCurrentGeneratingContract(`${contract.vendorName} - ${contract.type}`);
-        
-        const vendor = vendorsWithRecipients.find(v => v.vendorId === contract.vendorId);
-        if (!vendor) continue;
-
-        const pdf = await generateContractPdf(vendor, contract.type, contract.templateName);
-        pdfsMap.set(`${contract.vendorId}_${contract.type}_${contract.templateName}`, pdf);
-        
-        completed++;
-        setPdfGenerationProgress((completed / contractsToGenerate.length) * 100);
-        
-        await new Promise(resolve => setTimeout(resolve, 100));
+
+      // 기본계약이 필요한 계약서 목록 수집
+      const contractsToGenerate: ContractToGenerate[] = [];
+
+      for (const vendor of vendorsWithRecipients) {
+        // 재전송 업체이고 해당 업체의 스킵 옵션이 켜져 있으면 계약서 생성 건너뛰기
+        const isResendVendor = vendor.sendVersion && vendor.sendVersion > 0;
+        if (isResendVendor && skipContractsForVendor[vendor.vendorId]) {
+          continue; // 이 벤더의 계약서 생성을 스킵
+        }
+
+        if (vendor.ndaYn) {
+          contractsToGenerate.push({
+            vendorId: vendor.vendorId,
+            vendorName: vendor.vendorName,
+            type: "NDA",
+            templateName: "비밀"
+          });
+        }
+        if (vendor.generalGtcYn) {
+          contractsToGenerate.push({
+            vendorId: vendor.vendorId,
+            vendorName: vendor.vendorName,
+            type: "General_GTC",
+            templateName: "General GTC"
+          });
+        }
+        if (vendor.projectGtcYn && rfqInfo?.projectCode) {
+          contractsToGenerate.push({
+            vendorId: vendor.vendorId,
+            vendorName: vendor.vendorName,
+            type: "Project_GTC",
+            templateName: rfqInfo.projectCode
+          });
+        }
+        if (vendor.agreementYn) {
+          contractsToGenerate.push({
+            vendorId: vendor.vendorId,
+            vendorName: vendor.vendorName,
+            type: "기술자료",
+            templateName: "기술"
+          });
+        }
       }
 
-      setGeneratedPdfs(pdfsMap);  // UI 업데이트용
+      let pdfsMap = new Map<string, { buffer: number[], fileName: string }>();
+
+      // PDF 생성이 필요한 경우
+      if (contractsToGenerate.length > 0) {
+        setIsGeneratingPdfs(true);
+        setPdfGenerationProgress(0);
+
+        try {
+          let completed = 0;
+
+          for (const contract of contractsToGenerate) {
+            setCurrentGeneratingContract(`${contract.vendorName} - ${contract.type}`);
+
+            const vendor = vendorsWithRecipients.find(v => v.vendorId === contract.vendorId);
+            if (!vendor) continue;
+
+            const pdf = await generateContractPdf(vendor, contract.type, contract.templateName);
+            pdfsMap.set(`${contract.vendorId}_${contract.type}_${contract.templateName}`, pdf);
+
+            completed++;
+            setPdfGenerationProgress((completed / contractsToGenerate.length) * 100);
+
+            await new Promise(resolve => setTimeout(resolve, 100));
+          }
+
+          setGeneratedPdfs(pdfsMap);  // UI 업데이트용
+        } catch (error) {
+          console.error("PDF 생성 실패:", error);
+          toast.error("기본계약서 생성에 실패했습니다.");
+          setIsGeneratingPdfs(false);
+          setPdfGenerationProgress(0);
+          return;
+        }
+      }
+
+      // RFQ 발송 - pdfsMap을 직접 사용
+      setIsGeneratingPdfs(false);
+      setIsSending(true);
+
+      await onSend({
+        vendors: vendorsWithRecipients.map(v => ({
+          vendorId: v.vendorId,
+          vendorName: v.vendorName,
+          vendorCode: v.vendorCode,
+          vendorCountry: v.vendorCountry,
+          selectedMainEmail: v.selectedMainEmail,
+          additionalEmails: v.additionalEmails,
+          customEmails: v.customEmails.map(c => ({ email: c.email, name: c.name })),
+          currency: v.currency,
+          contractRequirements: {
+            ndaYn: v.ndaYn || false,
+            generalGtcYn: v.generalGtcYn || false,
+            projectGtcYn: v.projectGtcYn || false,
+            agreementYn: v.agreementYn || false,
+            projectCode: v.projectGtcYn ? rfqInfo?.projectCode : undefined,
+          },
+          isResend: (v.sendVersion || 0) > 0,
+          sendVersion: v.sendVersion,
+          contractsSkipped: ((v.sendVersion || 0) > 0) && skipContractsForVendor[v.vendorId],
+        })),
+        attachments: selectedAttachments,
+        message: additionalMessage,
+        // 생성된 PDF 데이터 추가
+        generatedPdfs: Array.from(pdfsMap.entries()).map(([key, data]) => ({
+          key,
+          ...data
+        })),
+      });
+
+      toast.success(
+        `${vendorsWithRecipients.length}개 업체에 RFQ를 발송했습니다.` +
+        (contractsToGenerate.length > 0 ? ` ${contractsToGenerate.length}개의 기본계약서가 포함되었습니다.` : '')
+      );
+      onOpenChange(false);
+
     } catch (error) {
-      console.error("PDF 생성 실패:", error);
-      toast.error("기본계약서 생성에 실패했습니다.");
+      console.error("RFQ 발송 실패:", error);
+      toast.error("RFQ 발송에 실패했습니다.");
+    } finally {
+      setIsSending(false);
       setIsGeneratingPdfs(false);
       setPdfGenerationProgress(0);
-      return;
+      setCurrentGeneratingContract("");
+      setSkipContractsForVendor({}); // 초기화
     }
-  }
-
-  // RFQ 발송 - pdfsMap을 직접 사용
-  setIsGeneratingPdfs(false);
-  setIsSending(true);
-
-  await onSend({
-     vendors: vendorsWithRecipients.map(v => ({
-       vendorId: v.vendorId,
-       vendorName: v.vendorName,
-       vendorCode: v.vendorCode,
-       vendorCountry: v.vendorCountry,
-       selectedMainEmail: v.selectedMainEmail,
-       additionalEmails: v.additionalEmails,
-       customEmails: v.customEmails.map(c => ({ email: c.email, name: c.name })),
-       currency: v.currency,
-       contractRequirements: {
-         ndaYn: v.ndaYn || false,
-         generalGtcYn: v.generalGtcYn || false,
-         projectGtcYn: v.projectGtcYn || false,
-         agreementYn: v.agreementYn || false,
-         projectCode: v.projectGtcYn ? rfqInfo?.projectCode : undefined,
-       },
-       isResend: (v.sendVersion || 0) > 0,
-       sendVersion: v.sendVersion,
-       contractsSkipped: ((v.sendVersion || 0) > 0) && skipContractsForVendor[v.vendorId],
-     })),
-     attachments: selectedAttachments,
-     message: additionalMessage,
-     // 생성된 PDF 데이터 추가
-     generatedPdfs: Array.from(pdfsMap.entries()).map(([key, data]) => ({
-      key,
-      ...data
-    })),
-   });
-
-   toast.success(
-     `${vendorsWithRecipients.length}개 업체에 RFQ를 발송했습니다.` +
-     (contractsToGenerate.length > 0 ? ` ${contractsToGenerate.length}개의 기본계약서가 포함되었습니다.` : '')
-   );
-   onOpenChange(false);
-
- } catch (error) {
-   console.error("RFQ 발송 실패:", error);
-   toast.error("RFQ 발송에 실패했습니다.");
- } finally {
-   setIsSending(false);
-   setIsGeneratingPdfs(false);
-   setPdfGenerationProgress(0);
-   setCurrentGeneratingContract("");
-   setSkipContractsForVendor({}); // 초기화
- }
-}, [vendorsWithRecipients, selectedAttachments, additionalMessage, onSend, onOpenChange, rfqInfo, skipContractsForVendor]);
+  }, [vendorsWithRecipients, selectedAttachments, additionalMessage, onSend, onOpenChange, rfqInfo, skipContractsForVendor]);
 
   // 전송 처리
   const handleSend = async () => {
@@ -712,7 +712,7 @@ export function SendRfqDialog({
                     <li>업체는 새로운 버전의 견적서를 작성해야 합니다.</li>
                     <li>이전에 제출한 견적서는 더 이상 유효하지 않습니다.</li>
                   </ul>
-                  
+
                   {/* 기본계약 재발송 정보 */}
                   <div className="mt-3 pt-3 border-t border-yellow-400">
                     <div className="space-y-2">
@@ -836,8 +836,8 @@ export function SendRfqDialog({
                                       setSkipContractsForVendor(newSkipOptions);
                                     }}
                                   >
-                                    {Object.values(skipContractsForVendor).every(v => v) ? "전체 재생성" : 
-                                     Object.values(skipContractsForVendor).every(v => !v) ? "전체 유지" : "전체 유지"}
+                                    {Object.values(skipContractsForVendor).every(v => v) ? "전체 재생성" :
+                                      Object.values(skipContractsForVendor).every(v => !v) ? "전체 유지" : "전체 유지"}
                                   </Button>
                                 </TooltipTrigger>
                                 <TooltipContent>
@@ -993,7 +993,7 @@ export function SendRfqDialog({
                                                 [vendor.vendorId]: !checked
                                               }));
                                             }}
-                                            // className="data-[state=checked]:bg-orange-600 data-[state=checked]:border-orange-600"
+                                          // className="data-[state=checked]:bg-orange-600 data-[state=checked]:border-orange-600"
                                           />
                                           <span className="text-xs">
                                             {skipContractsForVendor[vendor.vendorId] ? "유지" : "재생성"}
@@ -1002,8 +1002,8 @@ export function SendRfqDialog({
                                       </TooltipTrigger>
                                       <TooltipContent>
                                         <p className="text-xs">
-                                          {skipContractsForVendor[vendor.vendorId] 
-                                            ? "기존 계약서를 그대로 유지합니다" 
+                                          {skipContractsForVendor[vendor.vendorId]
+                                            ? "기존 계약서를 그대로 유지합니다"
                                             : "기존 계약서를 삭제하고 새로 생성합니다"}
                                         </p>
                                       </TooltipContent>
@@ -1306,9 +1306,9 @@ export function SendRfqDialog({
                 onChange={(e) => setAdditionalMessage(e.target.value)}
               />
             </div>
-          
-                     {/* PDF 생성 진행 상황 표시 */}
-                     {isGeneratingPdfs && (
+
+            {/* PDF 생성 진행 상황 표시 */}
+            {isGeneratingPdfs && (
               <Alert className="border-blue-500 bg-blue-50">
                 <div className="space-y-3">
                   <div className="flex items-center gap-2">
@@ -1327,8 +1327,8 @@ export function SendRfqDialog({
                 </div>
               </Alert>
             )}
-            
-          
+
+
           </div>
         </div>
 
@@ -1371,7 +1371,7 @@ export function SendRfqDialog({
           </Button>
         </DialogFooter>
       </DialogContent>
-      
+
       {/* 재발송 확인 다이얼로그 */}
       <AlertDialog open={showResendConfirmDialog} onOpenChange={setShowResendConfirmDialog}>
         <AlertDialogContent className="max-w-2xl">
@@ -1385,7 +1385,7 @@ export function SendRfqDialog({
                 <p className="text-sm">
                   <span className="font-semibold text-yellow-700">{resendVendorsInfo.count}개 업체</span>가 재발송 대상입니다.
                 </p>
-                
+
                 {/* 재발송 대상 업체 목록 및 계약서 설정 */}
                 <div className="bg-yellow-50 border border-yellow-200 rounded-lg p-3">
                   <p className="text-sm font-medium text-yellow-800 mb-3">재발송 대상 업체 및 계약서 설정:</p>
@@ -1398,7 +1398,7 @@ export function SendRfqDialog({
                         if (vendor.generalGtcYn) contracts.push("General GTC");
                         if (vendor.projectGtcYn) contracts.push("Project GTC");
                         if (vendor.agreementYn) contracts.push("기술자료");
-                        
+
                         return (
                           <div key={vendor.vendorId} className="flex items-center justify-between p-2 bg-white rounded border border-yellow-100">
                             <div className="flex items-center gap-3">
@@ -1422,7 +1422,7 @@ export function SendRfqDialog({
                                       [vendor.vendorId]: !checked
                                     }));
                                   }}
-                                  // className="data-[state=checked]:bg-orange-600 data-[state=checked]:border-orange-600"
+                                // className="data-[state=checked]:bg-orange-600 data-[state=checked]:border-orange-600"
                                 />
                                 <span className="text-xs text-yellow-800">
                                   {skipContractsForVendor[vendor.vendorId] ? "계약서 유지" : "계약서 재생성"}
@@ -1433,43 +1433,43 @@ export function SendRfqDialog({
                         );
                       })}
                   </div>
-                  
+
                   {/* 전체 선택 버튼 */}
-                  {vendorsWithRecipients.some(v => v.sendVersion && v.sendVersion > 0 && 
+                  {vendorsWithRecipients.some(v => v.sendVersion && v.sendVersion > 0 &&
                     (v.ndaYn || v.generalGtcYn || v.projectGtcYn || v.agreementYn)) && (
-                    <div className="mt-3 pt-3 border-t border-yellow-300 flex justify-end gap-2">
-                      <Button
-                        variant="outline"
-                        size="sm"
-                        onClick={() => {
-                          const resendVendors = vendorsWithRecipients.filter(v => v.sendVersion && v.sendVersion > 0);
-                          const newSkipOptions: Record<number, boolean> = {};
-                          resendVendors.forEach(v => {
-                            newSkipOptions[v.vendorId] = true; // 모두 유지
-                          });
-                          setSkipContractsForVendor(newSkipOptions);
-                        }}
-                      >
-                        전체 계약서 유지
-                      </Button>
-                      <Button
-                        variant="outline"
-                        size="sm"
-                        onClick={() => {
-                          const resendVendors = vendorsWithRecipients.filter(v => v.sendVersion && v.sendVersion > 0);
-                          const newSkipOptions: Record<number, boolean> = {};
-                          resendVendors.forEach(v => {
-                            newSkipOptions[v.vendorId] = false; // 모두 재생성
-                          });
-                          setSkipContractsForVendor(newSkipOptions);
-                        }}
-                      >
-                        전체 계약서 재생성
-                      </Button>
-                    </div>
-                  )}
+                      <div className="mt-3 pt-3 border-t border-yellow-300 flex justify-end gap-2">
+                        <Button
+                          variant="outline"
+                          size="sm"
+                          onClick={() => {
+                            const resendVendors = vendorsWithRecipients.filter(v => v.sendVersion && v.sendVersion > 0);
+                            const newSkipOptions: Record<number, boolean> = {};
+                            resendVendors.forEach(v => {
+                              newSkipOptions[v.vendorId] = true; // 모두 유지
+                            });
+                            setSkipContractsForVendor(newSkipOptions);
+                          }}
+                        >
+                          전체 계약서 유지
+                        </Button>
+                        <Button
+                          variant="outline"
+                          size="sm"
+                          onClick={() => {
+                            const resendVendors = vendorsWithRecipients.filter(v => v.sendVersion && v.sendVersion > 0);
+                            const newSkipOptions: Record<number, boolean> = {};
+                            resendVendors.forEach(v => {
+                              newSkipOptions[v.vendorId] = false; // 모두 재생성
+                            });
+                            setSkipContractsForVendor(newSkipOptions);
+                          }}
+                        >
+                          전체 계약서 재생성
+                        </Button>
+                      </div>
+                    )}
                 </div>
-                
+
                 {/* 경고 메시지 */}
                 <Alert className="border-red-200 bg-red-50">
                   <AlertCircle className="h-4 w-4 text-red-600" />
@@ -1479,17 +1479,17 @@ export function SendRfqDialog({
                       <li>기존에 작성된 견적 데이터가 <strong>모두 초기화</strong>됩니다.</li>
                       <li>업체는 처음부터 새로 견적서를 작성해야 합니다.</li>
                       <li>이전에 제출한 견적서는 더 이상 유효하지 않습니다.</li>
-                      {Object.entries(skipContractsForVendor).some(([vendorId, skip]) => !skip && 
+                      {Object.entries(skipContractsForVendor).some(([vendorId, skip]) => !skip &&
                         vendorsWithRecipients.find(v => v.vendorId === Number(vendorId))) && (
-                        <li className="text-orange-700 font-medium">
-                          ⚠️ 선택한 업체의 기존 기본계약서가 <strong>삭제</strong>되고 새로운 계약서가 발송됩니다.
-                        </li>
-                      )}
+                          <li className="text-orange-700 font-medium">
+                            ⚠️ 선택한 업체의 기존 기본계약서가 <strong>삭제</strong>되고 새로운 계약서가 발송됩니다.
+                          </li>
+                        )}
                       <li>이 작업은 <strong>취소할 수 없습니다</strong>.</li>
                     </ul>
                   </AlertDescription>
                 </Alert>
-                
+
                 <p className="text-sm text-muted-foreground">
                   재발송을 진행하시겠습니까?
                 </p>
@@ -1503,7 +1503,7 @@ export function SendRfqDialog({
             }}>
               취소
             </AlertDialogCancel>
-            <AlertDialogAction 
+            <AlertDialogAction
               onClick={() => {
                 setShowResendConfirmDialog(false);
                 proceedWithSend();
-- 
cgit v1.2.3

