summaryrefslogtreecommitdiff
path: root/lib/rfq-last/vendor/send-rfq-dialog.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rfq-last/vendor/send-rfq-dialog.tsx')
-rw-r--r--lib/rfq-last/vendor/send-rfq-dialog.tsx378
1 files changed, 189 insertions, 189 deletions
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();