From cf8dac0c6490469dab88a560004b0c07dbd48612 Mon Sep 17 00:00:00 2001 From: dujinkim 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; 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>({}); 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>(new Map()); - + // 재전송 시 기본계약 스킵 옵션 - 업체별 관리 const [skipContractsForVendor, setSkipContractsForVendor] = React.useState>({}); - + 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 = {}; 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(); - - // 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(); + + // 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({
  • 업체는 새로운 버전의 견적서를 작성해야 합니다.
  • 이전에 제출한 견적서는 더 이상 유효하지 않습니다.
  • - + {/* 기본계약 재발송 정보 */}
    @@ -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) ? "전체 유지" : "전체 유지"} @@ -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" /> {skipContractsForVendor[vendor.vendorId] ? "유지" : "재생성"} @@ -1002,8 +1002,8 @@ export function SendRfqDialog({

    - {skipContractsForVendor[vendor.vendorId] - ? "기존 계약서를 그대로 유지합니다" + {skipContractsForVendor[vendor.vendorId] + ? "기존 계약서를 그대로 유지합니다" : "기존 계약서를 삭제하고 새로 생성합니다"}

    @@ -1306,9 +1306,9 @@ export function SendRfqDialog({ onChange={(e) => setAdditionalMessage(e.target.value)} />
    - - {/* PDF 생성 진행 상황 표시 */} - {isGeneratingPdfs && ( + + {/* PDF 생성 진행 상황 표시 */} + {isGeneratingPdfs && (
    @@ -1327,8 +1327,8 @@ export function SendRfqDialog({
    )} - - + +
    @@ -1371,7 +1371,7 @@ export function SendRfqDialog({ - + {/* 재발송 확인 다이얼로그 */} @@ -1385,7 +1385,7 @@ export function SendRfqDialog({

    {resendVendorsInfo.count}개 업체가 재발송 대상입니다.

    - + {/* 재발송 대상 업체 목록 및 계약서 설정 */}

    재발송 대상 업체 및 계약서 설정:

    @@ -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 (
    @@ -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" /> {skipContractsForVendor[vendor.vendorId] ? "계약서 유지" : "계약서 재생성"} @@ -1433,43 +1433,43 @@ export function SendRfqDialog({ ); })}
    - + {/* 전체 선택 버튼 */} - {vendorsWithRecipients.some(v => v.sendVersion && v.sendVersion > 0 && + {vendorsWithRecipients.some(v => v.sendVersion && v.sendVersion > 0 && (v.ndaYn || v.generalGtcYn || v.projectGtcYn || v.agreementYn)) && ( -
    - - -
    - )} +
    + + +
    + )}
    - + {/* 경고 메시지 */} @@ -1479,17 +1479,17 @@ export function SendRfqDialog({
  • 기존에 작성된 견적 데이터가 모두 초기화됩니다.
  • 업체는 처음부터 새로 견적서를 작성해야 합니다.
  • 이전에 제출한 견적서는 더 이상 유효하지 않습니다.
  • - {Object.entries(skipContractsForVendor).some(([vendorId, skip]) => !skip && + {Object.entries(skipContractsForVendor).some(([vendorId, skip]) => !skip && vendorsWithRecipients.find(v => v.vendorId === Number(vendorId))) && ( -
  • - ⚠️ 선택한 업체의 기존 기본계약서가 삭제되고 새로운 계약서가 발송됩니다. -
  • - )} +
  • + ⚠️ 선택한 업체의 기존 기본계약서가 삭제되고 새로운 계약서가 발송됩니다. +
  • + )}
  • 이 작업은 취소할 수 없습니다.
  • - +

    재발송을 진행하시겠습니까?

    @@ -1503,7 +1503,7 @@ export function SendRfqDialog({ }}> 취소 - { setShowResendConfirmDialog(false); proceedWithSend(); -- cgit v1.2.3