summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-10-17 08:08:33 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-10-17 08:08:33 +0000
commit1540eac291761ffd8fc1947ed626e4e4a4407922 (patch)
treea7b6ae8060e164f249651cf6ef8b0c2e868019e9
parent55b6153dfce83a1cf2be72cbc3413d78084e8da1 (diff)
(최겸) 견적입찰 비교관련 수정
-rw-r--r--app/[lng]/evcp/(evcp)/(procurement)/rfq-last/[id]/compare/page.tsx6
-rw-r--r--app/api/partners/rfq-last/[id]/response/route.ts74
-rw-r--r--lib/rfq-last/compare-action.ts236
-rw-r--r--lib/rfq-last/quotation-compare-view.tsx466
-rw-r--r--lib/rfq-last/vendor-response/editor/commercial-terms-form.tsx7
-rw-r--r--lib/rfq-last/vendor/rfq-vendor-table.tsx6
6 files changed, 519 insertions, 276 deletions
diff --git a/app/[lng]/evcp/(evcp)/(procurement)/rfq-last/[id]/compare/page.tsx b/app/[lng]/evcp/(evcp)/(procurement)/rfq-last/[id]/compare/page.tsx
index 097b99eb..461a0863 100644
--- a/app/[lng]/evcp/(evcp)/(procurement)/rfq-last/[id]/compare/page.tsx
+++ b/app/[lng]/evcp/(evcp)/(procurement)/rfq-last/[id]/compare/page.tsx
@@ -28,9 +28,9 @@ export default async function ComparePage({
.map(id => parseInt(id))
.filter(id => !isNaN(id)) || [];
- if (!rfqId || vendorIds.length < 2) {
- notFound();
- }
+ // if (!rfqId || vendorIds.length < 2) {
+ // notFound();
+ // }
// 서버에서 데이터 가져오기
const data = await getComparisonData(rfqId, vendorIds);
diff --git a/app/api/partners/rfq-last/[id]/response/route.ts b/app/api/partners/rfq-last/[id]/response/route.ts
index ebcccd8f..5d05db50 100644
--- a/app/api/partners/rfq-last/[id]/response/route.ts
+++ b/app/api/partners/rfq-last/[id]/response/route.ts
@@ -264,42 +264,57 @@ export async function PUT(
// 2. 새 버전 생성 (제출 시) 또는 기존 버전 업데이트
let responseId = existingResponse.id
- if (data.status === "제출완료" && previousStatus !== "제출완료") {
- // 기존 버전을 비활성화
- await tx.update(rfqLastVendorResponses)
- .set({ isLatest: false })
- .where(eq(rfqLastVendorResponses.id, existingResponse.id))
+ // if (data.status === "제출완료" && previousStatus !== "제출완료") {
+ // // 기존 버전을 비활성화
+ // await tx.update(rfqLastVendorResponses)
+ // .set({ isLatest: false })
+ // .where(eq(rfqLastVendorResponses.id, existingResponse.id))
- // 기존 첨부파일을 새 응답으로 이동
- if (existingResponse.id) {
- await tx.update(rfqLastVendorAttachments)
- .set({ vendorResponseId: existingResponse.id }) // 임시로 기존 응답에 연결
- .where(eq(rfqLastVendorAttachments.vendorResponseId, existingResponse.id))
- }
+ // // 기존 첨부파일을 새 응답으로 이동
+ // if (existingResponse.id) {
+ // await tx.update(rfqLastVendorAttachments)
+ // .set({ vendorResponseId: existingResponse.id }) // 기존 응답에 연결
+ // .where(eq(rfqLastVendorAttachments.vendorResponseId, existingResponse.id))
+ // }
+
+ // // 새 버전 생성
+ // const [newResponse] = await tx.insert(rfqLastVendorResponses).values({
+ // ...data,
+ // vendorDeliveryDate: data.vendorDeliveryDate ? new Date(data.vendorDeliveryDate) : null,
+ // submittedAt: data.submittedAt ? new Date(data.submittedAt) : null,
+ // responseVersion: existingResponse.responseVersion + 1,
+ // status:"제출완료",
+ // participationStatus: "참여",
+ // participationRepliedAt: existingResponse.participationRepliedAt ? new Date() : null,
+ // participationRepliedBy: existingResponse.participationRepliedBy ? session.user.id : null,
+
+ // isLatest: true,
+ // createdBy: existingResponse.createdBy,
+ // updatedBy: session.user.id,
+ // }).returning()
+
+ // // 기존 첨부파일을 새 응답으로 이동
+ // if (newResponse.id) {
+ // await tx.update(rfqLastVendorAttachments)
+ // .set({ vendorResponseId: newResponse.id })
+ // .where(eq(rfqLastVendorAttachments.vendorResponseId, existingResponse.id))
+ // }
- // 새 버전 생성
- const [newResponse] = await tx.insert(rfqLastVendorResponses).values({
+ // responseId = newResponse.id
+ // } else {
+ // // 기존 버전 업데이트
+ if(data.status === "제출완료") {
+ await tx.update(rfqLastVendorResponses)
+ .set({
...data,
vendorDeliveryDate: data.vendorDeliveryDate ? new Date(data.vendorDeliveryDate) : null,
submittedAt: data.submittedAt ? new Date(data.submittedAt) : null,
- responseVersion: existingResponse.responseVersion + 1,
- status:"제출완료",
- participationStatus: "참여",
- isLatest: true,
- createdBy: existingResponse.createdBy,
+ status: data.status,
updatedBy: session.user.id,
- }).returning()
-
- // 기존 첨부파일을 새 응답으로 이동
- if (newResponse.id) {
- await tx.update(rfqLastVendorAttachments)
- .set({ vendorResponseId: newResponse.id })
- .where(eq(rfqLastVendorAttachments.vendorResponseId, existingResponse.id))
- }
-
- responseId = newResponse.id
+ updatedAt: new Date(),
+ })
+ .where(eq(rfqLastVendorResponses.id, existingResponse.id))
} else {
- // 기존 버전 업데이트
await tx.update(rfqLastVendorResponses)
.set({
...data,
@@ -311,6 +326,7 @@ export async function PUT(
.where(eq(rfqLastVendorResponses.id, existingResponse.id))
}
+
// 3. 견적 아이템 업데이트
// 기존 아이템 삭제
await tx.delete(rfqLastVendorQuotationItems)
diff --git a/lib/rfq-last/compare-action.ts b/lib/rfq-last/compare-action.ts
index 1a50a373..57f8f00f 100644
--- a/lib/rfq-last/compare-action.ts
+++ b/lib/rfq-last/compare-action.ts
@@ -1,17 +1,17 @@
"use server";
import db from "@/db/db";
-import { eq, and, inArray, ne, asc, desc } from "drizzle-orm";
+import { eq, and, inArray, ne, asc, isNotNull } from "drizzle-orm";
import {
rfqsLast,
rfqLastDetails,
rfqPrItems,
rfqLastVendorResponses,
rfqLastVendorQuotationItems,
+ rfqLastVendorAttachments,
vendors,
paymentTerms,
incoterms,
- vendorSelections,
users
} from "@/db/schema";
import { revalidatePath } from "next/cache";
@@ -62,22 +62,22 @@ export interface VendorResponseVersion {
// 벤더 제안 조건
vendorConditions: {
- currency?: string;
- paymentTermsCode?: string;
- paymentTermsDesc?: string;
- incotermsCode?: string;
- incotermsDesc?: string;
+ currency?: string | null;
+ paymentTermsCode?: string | null;
+ paymentTermsDesc?: string | null;
+ incotermsCode?: string | null;
+ incotermsDesc?: string | null;
deliveryDate?: Date | null;
- contractDuration?: string;
- taxCode?: string;
- placeOfShipping?: string;
- placeOfDestination?: string;
- firstAcceptance?: "수용" | "부분수용" | "거부";
- firstDescription?: string;
- sparepartAcceptance?: "수용" | "부분수용" | "거부";
- sparepartDescription?: string;
- materialPriceRelatedYn?: boolean;
- materialPriceRelatedReason?: string;
+ contractDuration?: string | null;
+ taxCode?: string | null;
+ placeOfShipping?: string | null;
+ placeOfDestination?: string | null;
+ firstAcceptance?: "수용" | "부분수용" | "거부" | null;
+ firstDescription?: string | null;
+ sparepartAcceptance?: "수용" | "부분수용" | "거부" | null;
+ sparepartDescription?: string | null;
+ materialPriceRelatedYn?: boolean | null;
+ materialPriceRelatedReason?: string | null;
};
// 조건 차이 분석
@@ -88,8 +88,8 @@ export interface VendorResponseVersion {
};
// 비고
- generalRemark?: string;
- technicalProposal?: string;
+ generalRemark?: string | null;
+ technicalProposal?: string | null;
// 품목별 견적 아이템 정보 추가
quotationItems?: {
@@ -98,11 +98,14 @@ export interface VendorResponseVersion {
totalPrice: number;
currency: string;
quantity: number;
- deliveryDate?: Date | null;
- leadTime?: number;
- manufacturer?: string;
- modelNo?: string;
+ deliveryDate: Date | null | undefined;
+ leadTime: number | null | undefined;
+ manufacturer: string | null | undefined;
+ modelNo: string | null | undefined;
}[];
+
+ // 첨부파일 정보
+ attachments?: VendorAttachment[];
}
export interface VendorComparison {
@@ -119,14 +122,14 @@ export interface VendorComparison {
incotermsCode: string;
incotermsDesc?: string;
deliveryDate: Date | null;
- contractDuration?: string;
- taxCode?: string;
- placeOfShipping?: string;
- placeOfDestination?: string;
+ contractDuration?: string | null;
+ taxCode?: string | null;
+ placeOfShipping?: string | null;
+ placeOfDestination?: string | null;
firstYn: boolean;
- firstDescription?: string;
+ firstDescription?: string | null;
sparepartYn: boolean;
- sparepartDescription?: string;
+ sparepartDescription?: string | null;
materialPriceRelatedYn: boolean;
};
@@ -150,22 +153,22 @@ export interface VendorComparison {
// 레거시 호환: 최신 응답의 조건 정보
vendorConditions: {
- currency?: string;
- paymentTermsCode?: string;
- paymentTermsDesc?: string;
- incotermsCode?: string;
- incotermsDesc?: string;
+ currency?: string | null;
+ paymentTermsCode?: string | null;
+ paymentTermsDesc?: string | null;
+ incotermsCode?: string | null;
+ incotermsDesc?: string | null;
deliveryDate?: Date | null;
- contractDuration?: string;
- taxCode?: string;
- placeOfShipping?: string;
- placeOfDestination?: string;
- firstAcceptance?: "수용" | "부분수용" | "거부";
- firstDescription?: string;
- sparepartAcceptance?: "수용" | "부분수용" | "거부";
- sparepartDescription?: string;
- materialPriceRelatedYn?: boolean;
- materialPriceRelatedReason?: string;
+ contractDuration?: string | null;
+ taxCode?: string | null;
+ placeOfShipping?: string | null;
+ placeOfDestination?: string | null;
+ firstAcceptance?: "수용" | "부분수용" | "거부" | null;
+ firstDescription?: string | null;
+ sparepartAcceptance?: "수용" | "부분수용" | "거부" | null;
+ sparepartDescription?: string | null;
+ materialPriceRelatedYn?: boolean | null;
+ materialPriceRelatedReason?: string | null;
};
// 레거시 호환: 최신 응답의 조건 차이 분석
@@ -176,23 +179,23 @@ export interface VendorComparison {
};
// 레거시 호환: 최신 응답의 비고
- generalRemark?: string;
- technicalProposal?: string;
+ generalRemark?: string | null;
+ technicalProposal?: string | null;
// 선정 관련 정보
isSelected?: boolean;
selectionDate?: Date | null;
- selectionReason?: string;
- selectedBy?: number;
+ selectionReason?: string | null;
+ selectedBy?: number | null;
selectedByName?: string;
selectionApprovalStatus?: "대기" | "승인" | "반려" | null;
- selectionApprovedBy?: number;
+ selectionApprovedBy?: number | null;
selectionApprovedAt?: Date | null;
- selectionApprovalComment?: string;
+ selectionApprovalComment?: string | null;
// 계약 관련 정보 추가
- contractStatus?: string;
- contractNo?: string;
+ contractStatus?: string | null;
+ contractNo?: string | null;
contractCreatedAt?: Date | null;
}
@@ -202,9 +205,14 @@ export interface PrItemComparison {
prItem: string;
materialCode: string;
materialDescription: string;
+ materialCategory?: string;
requestedQuantity: number;
uom: string;
+ size?: string;
+ grossWeight?: number;
+ gwUom?: string;
requestedDeliveryDate: Date | null;
+ remark?: string;
vendorQuotes: {
vendorId: number;
@@ -231,6 +239,23 @@ export interface PrItemComparison {
};
}
+export interface VendorAttachment {
+ id: number;
+ attachmentType: string;
+ documentNo?: string;
+ fileName: string;
+ originalFileName: string;
+ filePath: string;
+ fileSize?: number;
+ fileType?: string;
+ description?: string;
+ validFrom?: Date | null;
+ validTo?: Date | null;
+ uploadedBy: number;
+ uploadedAt: Date;
+ uploaderName?: string;
+}
+
// ===== 메인 조회 함수 =====
export async function getComparisonData(
@@ -307,7 +332,7 @@ export async function getComparisonData(
)
.where(inArray(vendors.id, vendorIds));
- // 2b. 모든 차수의 벤더 응답 조회 (isLatest 조건 제거)
+ // 2b. 벤더가 실제 제출한 응답만 조회 (submittedAt이 null이 아닌 것만)
const allVendorResponses = await db
.select({
responseId: rfqLastVendorResponses.id,
@@ -347,7 +372,8 @@ export async function getComparisonData(
.where(
and(
eq(rfqLastVendorResponses.rfqsLastId, rfqId),
- inArray(rfqLastVendorResponses.vendorId, vendorIds)
+ inArray(rfqLastVendorResponses.vendorId, vendorIds),
+ isNotNull(rfqLastVendorResponses.submittedAt) // 벤더가 실제 제출한 것만
)
)
.orderBy(asc(rfqLastVendorResponses.vendorId), asc(rfqLastVendorResponses.responseVersion));
@@ -386,7 +412,7 @@ export async function getComparisonData(
incotermsData.map(ic => [ic.code, ic.description])
);
- // 5. PR Items 조회
+ // 5. PR Items 조회 (추가 필드 포함)
const prItems = await db
.select({
id: rfqPrItems.id,
@@ -394,9 +420,14 @@ export async function getComparisonData(
prItem: rfqPrItems.prItem,
materialCode: rfqPrItems.materialCode,
materialDescription: rfqPrItems.materialDescription,
+ materialCategory: rfqPrItems.materialCategory,
quantity: rfqPrItems.quantity,
uom: rfqPrItems.uom,
+ size: rfqPrItems.size,
+ grossWeight: rfqPrItems.grossWeight,
+ gwUom: rfqPrItems.gwUom,
deliveryDate: rfqPrItems.deliveryDate,
+ remark: rfqPrItems.remark,
})
.from(rfqPrItems)
.where(eq(rfqPrItems.rfqsLastId, rfqId));
@@ -424,6 +455,31 @@ export async function getComparisonData(
.where(inArray(rfqLastVendorQuotationItems.vendorResponseId, allResponseIds))
: [];
+ // 6b. 벤더 첨부파일 조회 (모든 응답 버전 포함)
+ const vendorAttachments = allResponseIds.length > 0
+ ? await db
+ .select({
+ id: rfqLastVendorAttachments.id,
+ vendorResponseId: rfqLastVendorAttachments.vendorResponseId,
+ attachmentType: rfqLastVendorAttachments.attachmentType,
+ documentNo: rfqLastVendorAttachments.documentNo,
+ fileName: rfqLastVendorAttachments.fileName,
+ originalFileName: rfqLastVendorAttachments.originalFileName,
+ filePath: rfqLastVendorAttachments.filePath,
+ fileSize: rfqLastVendorAttachments.fileSize,
+ fileType: rfqLastVendorAttachments.fileType,
+ description: rfqLastVendorAttachments.description,
+ validFrom: rfqLastVendorAttachments.validFrom,
+ validTo: rfqLastVendorAttachments.validTo,
+ uploadedBy: rfqLastVendorAttachments.uploadedBy,
+ uploadedAt: rfqLastVendorAttachments.uploadedAt,
+ uploaderName: users.name,
+ })
+ .from(rfqLastVendorAttachments)
+ .leftJoin(users, eq(rfqLastVendorAttachments.uploadedBy, users.id))
+ .where(inArray(rfqLastVendorAttachments.vendorResponseId, allResponseIds))
+ : [];
+
// 7. 데이터 가공 및 분석 - 각 벤더별 최신 차수 기준으로 평균 계산
// 각 벤더별로 가장 높은 responseVersion을 가진 응답 찾기
const latestResponsesByVendor = new Map<number, typeof allVendorResponses[0]>();
@@ -454,7 +510,7 @@ export async function getComparisonData(
: 0;
// 8. 벤더별 비교 데이터 구성 (차수별 응답 포함)
- const vendorComparisons: VendorComparison[] = vendorData.map((v) => {
+ const vendorComparisons = vendorData.map((v) => {
// 이 벤더의 모든 응답 가져오기
const vendorResponses = allVendorResponses.filter(r => r.vendorId === v.vendorId);
@@ -501,10 +557,30 @@ export async function getComparisonData(
totalPrice: q.totalPrice || 0,
currency: q.currency || "USD",
quantity: q.quantity || 0,
- deliveryDate: q.deliveryDate,
- leadTime: q.leadTime,
- manufacturer: q.manufacturer,
- modelNo: q.modelNo,
+ deliveryDate: q.deliveryDate || undefined,
+ leadTime: q.leadTime || undefined,
+ manufacturer: q.manufacturer || undefined,
+ modelNo: q.modelNo || undefined,
+ }));
+
+ // 이 응답의 첨부파일 가져오기
+ const responseAttachments = vendorAttachments
+ .filter(a => a.vendorResponseId === resp.responseId)
+ .map(a => ({
+ id: a.id,
+ attachmentType: a.attachmentType,
+ documentNo: a.documentNo || undefined,
+ fileName: a.fileName,
+ originalFileName: a.originalFileName,
+ filePath: a.filePath,
+ fileSize: a.fileSize || undefined,
+ fileType: a.fileType || undefined,
+ description: a.description || undefined,
+ validFrom: a.validFrom || undefined,
+ validTo: a.validTo || undefined,
+ uploadedBy: a.uploadedBy,
+ uploadedAt: a.uploadedAt,
+ uploaderName: a.uploaderName || undefined,
}));
return {
@@ -544,9 +620,10 @@ export async function getComparisonData(
criticalDifferences,
},
- generalRemark: resp.generalRemark,
- technicalProposal: resp.technicalProposal,
+ generalRemark: resp.generalRemark || undefined,
+ technicalProposal: resp.technicalProposal || undefined,
quotationItems: responseQuotationItems,
+ attachments: responseAttachments,
};
});
@@ -567,14 +644,14 @@ export async function getComparisonData(
incotermsCode: v.buyerIncotermsCode || "",
incotermsDesc: incotermsMap.get(v.buyerIncotermsCode || ""),
deliveryDate: v.buyerDeliveryDate,
- contractDuration: v.buyerContractDuration,
- taxCode: v.buyerTaxCode,
- placeOfShipping: v.buyerPlaceOfShipping,
- placeOfDestination: v.buyerPlaceOfDestination,
+ contractDuration: v.buyerContractDuration || undefined,
+ taxCode: v.buyerTaxCode || undefined,
+ placeOfShipping: v.buyerPlaceOfShipping || undefined,
+ placeOfDestination: v.buyerPlaceOfDestination || undefined,
firstYn: v.buyerFirstYn || false,
- firstDescription: v.buyerFirstDescription,
+ firstDescription: v.buyerFirstDescription || undefined,
sparepartYn: v.buyerSparepartYn || false,
- sparepartDescription: v.buyerSparepartDescription,
+ sparepartDescription: v.buyerSparepartDescription || undefined,
materialPriceRelatedYn: v.buyerMaterialPriceRelatedYn || false,
},
@@ -618,23 +695,23 @@ export async function getComparisonData(
criticalDifferences: [],
},
- generalRemark: latestResp?.generalRemark,
- technicalProposal: latestResp?.technicalProposal,
+ generalRemark: latestResp?.generalRemark || undefined,
+ technicalProposal: latestResp?.technicalProposal || undefined,
// 선정 관련 정보
isSelected: v.isSelected || false,
selectionDate: v.selectionDate,
- selectionReason: v.selectionReason,
- selectedBy: v.selectedBy,
+ selectionReason: v.selectionReason || undefined,
+ selectedBy: v.selectedBy || undefined,
selectedByName: v.isSelected ? selectedByName : undefined,
- selectionApprovalStatus: v.selectionApprovalStatus,
- selectionApprovedBy: v.selectionApprovedBy,
+ selectionApprovalStatus: v.selectionApprovalStatus || undefined,
+ selectionApprovedBy: v.selectionApprovedBy || undefined,
selectionApprovedAt: v.selectionApprovedAt,
- selectionApprovalComment: v.selectionApprovalComment,
+ selectionApprovalComment: v.selectionApprovalComment || undefined,
// 계약 관련 정보
- contractStatus: v.contractStatus,
- contractNo: v.contractNo,
+ contractStatus: v.contractStatus || undefined,
+ contractNo: v.contractNo || undefined,
contractCreatedAt: v.contractCreatedAt,
};
});
@@ -690,9 +767,14 @@ export async function getComparisonData(
prItem: item.prItem || "",
materialCode: item.materialCode || "",
materialDescription: item.materialDescription || "",
+ materialCategory: item.materialCategory || undefined,
requestedQuantity: item.quantity || 0,
uom: item.uom || "",
+ size: item.size || undefined,
+ grossWeight: item.grossWeight || undefined,
+ gwUom: item.gwUom || undefined,
requestedDeliveryDate: item.deliveryDate,
+ remark: item.remark || undefined,
vendorQuotes: itemQuotes,
priceAnalysis: {
lowestPrice: Math.min(...unitPrices) || 0,
diff --git a/lib/rfq-last/quotation-compare-view.tsx b/lib/rfq-last/quotation-compare-view.tsx
index 723d1044..527fc4d8 100644
--- a/lib/rfq-last/quotation-compare-view.tsx
+++ b/lib/rfq-last/quotation-compare-view.tsx
@@ -28,6 +28,8 @@ import {
X,
RefreshCw,
Clock,
+ Download,
+ Paperclip,
} from "lucide-react";
import { cn } from "@/lib/utils";
import { format } from "date-fns";
@@ -193,13 +195,14 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) {
switch (selectedContractType) {
case "PO":
+ const poSelectionReason: string | undefined = selectedVendor.selectionReason || undefined;
result = await createPO({
rfqId: data.rfqInfo.id,
vendorId: selectedVendor.vendorId,
vendorName: selectedVendor.vendorName,
totalAmount: selectedVendor.totalAmount,
currency: selectedVendor.currency,
- selectionReason: selectedVendor.selectionReason,
+ selectionReason: poSelectionReason,
});
break;
@@ -712,18 +715,29 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) {
<table className="w-full">
<thead>
<tr className="border-b">
- <th className="text-left p-3 font-semibold">항목</th>
- <th className="text-left p-3 font-semibold">구매자 제시</th>
+ <th className="text-left p-3 font-semibold" rowSpan={2}>항목</th>
+ </tr>
+ <tr className="border-b bg-gray-50">
{data.vendors.map((vendor) => (
- <th key={vendor.vendorId} className={cn(
- "text-left p-3 font-semibold",
- vendor.isSelected && "bg-blue-50"
- )}>
- {vendor.vendorName}
+ <React.Fragment key={vendor.vendorId}>
+ <th className={cn(
+ "text-center p-2 text-xs font-medium border-r",
+ vendor.isSelected && "bg-blue-50"
+ )}>
+ SHI 제시
+ </th>
+ <th className={cn(
+ "text-center p-2 text-xs font-medium",
+ vendor.isSelected && "bg-blue-50"
+ )}>
+ <div className="font-bold">
+ {vendor.vendorName}
+ </div>
{vendor.isSelected && (
<Badge className="ml-2 bg-blue-600 text-xs">선정</Badge>
)}
- </th>
+ </th>
+ </React.Fragment>
))}
</tr>
</thead>
@@ -731,21 +745,28 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) {
{/* 통화 */}
<tr>
<td className="p-3 font-medium">통화</td>
- <td className="p-3">{data.vendors[0]?.buyerConditions.currency}</td>
{data.vendors.map((vendor) => {
const latestResponse = vendor.responses[0]; // 최신 응답 (이미 정렬되어 있음)
return (
- <td key={vendor.vendorId} className={cn(
- "p-3",
- vendor.isSelected && "bg-blue-50"
- )}>
- <div className="flex items-center gap-2">
- {latestResponse?.vendorConditions?.currency || vendor.buyerConditions.currency}
- {latestResponse?.vendorConditions?.currency && latestResponse.vendorConditions.currency !== vendor.buyerConditions.currency && (
- <Badge variant="destructive" className="text-xs">변경</Badge>
- )}
- </div>
- </td>
+ <React.Fragment key={vendor.vendorId}>
+ <td className={cn(
+ "p-3 text-center border-r",
+ vendor.isSelected && "bg-blue-50"
+ )}>
+ {vendor.buyerConditions.currency}
+ </td>
+ <td className={cn(
+ "p-3 text-center",
+ vendor.isSelected && "bg-blue-50"
+ )}>
+ <div className="flex items-center justify-center gap-2">
+ {latestResponse?.vendorConditions?.currency || vendor.buyerConditions.currency}
+ {latestResponse?.vendorConditions?.currency && latestResponse.vendorConditions.currency !== vendor.buyerConditions.currency && (
+ <Badge variant="destructive" className="text-xs">변경</Badge>
+ )}
+ </div>
+ </td>
+ </React.Fragment>
);
})}
</tr>
@@ -753,42 +774,47 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) {
{/* 지급조건 */}
<tr>
<td className="p-3 font-medium">지급조건</td>
- <td className="p-3">
- <TooltipProvider>
- <Tooltip>
- <TooltipTrigger className="cursor-help border-b border-dashed">
- {data.vendors[0]?.buyerConditions.paymentTermsCode}
- </TooltipTrigger>
- <TooltipContent>
- {data.vendors[0]?.buyerConditions.paymentTermsDesc}
- </TooltipContent>
- </Tooltip>
- </TooltipProvider>
- </td>
{data.vendors.map((vendor) => {
const latestResponse = vendor.responses[0];
return (
- <td key={vendor.vendorId} className={cn(
- "p-3",
- vendor.isSelected && "bg-blue-50"
- )}>
- <div className="flex items-center gap-2">
+ <React.Fragment key={vendor.vendorId}>
+ <td className={cn(
+ "p-3 text-center border-r",
+ vendor.isSelected && "bg-blue-50"
+ )}>
<TooltipProvider>
<Tooltip>
<TooltipTrigger className="cursor-help border-b border-dashed">
- {latestResponse?.vendorConditions?.paymentTermsCode || vendor.buyerConditions.paymentTermsCode}
+ {vendor.buyerConditions.paymentTermsCode}
</TooltipTrigger>
<TooltipContent>
- {latestResponse?.vendorConditions?.paymentTermsDesc || vendor.buyerConditions.paymentTermsDesc}
+ {vendor.buyerConditions.paymentTermsDesc}
</TooltipContent>
</Tooltip>
</TooltipProvider>
- {latestResponse?.vendorConditions?.paymentTermsCode &&
- latestResponse.vendorConditions.paymentTermsCode !== vendor.buyerConditions.paymentTermsCode && (
- <Badge variant="outline" className="text-xs">변경</Badge>
- )}
- </div>
- </td>
+ </td>
+ <td className={cn(
+ "p-3 text-center",
+ vendor.isSelected && "bg-blue-50"
+ )}>
+ <div className="flex items-center justify-center gap-2">
+ <TooltipProvider>
+ <Tooltip>
+ <TooltipTrigger className="cursor-help border-b border-dashed">
+ {latestResponse?.vendorConditions?.paymentTermsCode || vendor.buyerConditions.paymentTermsCode}
+ </TooltipTrigger>
+ <TooltipContent>
+ {latestResponse?.vendorConditions?.paymentTermsDesc || vendor.buyerConditions.paymentTermsDesc}
+ </TooltipContent>
+ </Tooltip>
+ </TooltipProvider>
+ {latestResponse?.vendorConditions?.paymentTermsCode &&
+ latestResponse.vendorConditions.paymentTermsCode !== vendor.buyerConditions.paymentTermsCode && (
+ <Badge variant="outline" className="text-xs">변경</Badge>
+ )}
+ </div>
+ </td>
+ </React.Fragment>
);
})}
</tr>
@@ -796,42 +822,47 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) {
{/* 인코텀즈 */}
<tr>
<td className="p-3 font-medium">인코텀즈</td>
- <td className="p-3">
- <TooltipProvider>
- <Tooltip>
- <TooltipTrigger className="cursor-help border-b border-dashed">
- {data.vendors[0]?.buyerConditions.incotermsCode}
- </TooltipTrigger>
- <TooltipContent>
- {data.vendors[0]?.buyerConditions.incotermsDesc}
- </TooltipContent>
- </Tooltip>
- </TooltipProvider>
- </td>
{data.vendors.map((vendor) => {
const latestResponse = vendor.responses[0];
return (
- <td key={vendor.vendorId} className={cn(
- "p-3",
- vendor.isSelected && "bg-blue-50"
- )}>
- <div className="flex items-center gap-2">
+ <React.Fragment key={vendor.vendorId}>
+ <td className={cn(
+ "p-3 text-center border-r",
+ vendor.isSelected && "bg-blue-50"
+ )}>
<TooltipProvider>
<Tooltip>
<TooltipTrigger className="cursor-help border-b border-dashed">
- {latestResponse?.vendorConditions?.incotermsCode || vendor.buyerConditions.incotermsCode}
+ {vendor.buyerConditions.incotermsCode}
</TooltipTrigger>
<TooltipContent>
- {latestResponse?.vendorConditions?.incotermsDesc || vendor.buyerConditions.incotermsDesc}
+ {vendor.buyerConditions.incotermsDesc}
</TooltipContent>
</Tooltip>
</TooltipProvider>
- {latestResponse?.vendorConditions?.incotermsCode &&
- latestResponse.vendorConditions.incotermsCode !== vendor.buyerConditions.incotermsCode && (
- <Badge variant="outline" className="text-xs">변경</Badge>
- )}
- </div>
- </td>
+ </td>
+ <td className={cn(
+ "p-3 text-center",
+ vendor.isSelected && "bg-blue-50"
+ )}>
+ <div className="flex items-center justify-center gap-2">
+ <TooltipProvider>
+ <Tooltip>
+ <TooltipTrigger className="cursor-help border-b border-dashed">
+ {latestResponse?.vendorConditions?.incotermsCode || vendor.buyerConditions.incotermsCode}
+ </TooltipTrigger>
+ <TooltipContent>
+ {latestResponse?.vendorConditions?.incotermsDesc || vendor.buyerConditions.incotermsDesc}
+ </TooltipContent>
+ </Tooltip>
+ </TooltipProvider>
+ {latestResponse?.vendorConditions?.incotermsCode &&
+ latestResponse.vendorConditions.incotermsCode !== vendor.buyerConditions.incotermsCode && (
+ <Badge variant="outline" className="text-xs">변경</Badge>
+ )}
+ </div>
+ </td>
+ </React.Fragment>
);
})}
</tr>
@@ -839,22 +870,29 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) {
{/* 선적지 */}
<tr>
<td className="p-3 font-medium">선적지</td>
- <td className="p-3">{data.vendors[0]?.buyerConditions.placeOfShipping || "-"}</td>
{data.vendors.map((vendor) => {
const latestResponse = vendor.responses[0];
return (
- <td key={vendor.vendorId} className={cn(
- "p-3",
- vendor.isSelected && "bg-blue-50"
- )}>
- <div className="flex items-center gap-2">
- {latestResponse?.vendorConditions?.placeOfShipping || vendor.buyerConditions.placeOfShipping || "-"}
- {latestResponse?.vendorConditions?.placeOfShipping &&
- latestResponse.vendorConditions.placeOfShipping !== vendor.buyerConditions.placeOfShipping && (
- <Badge variant="outline" className="text-xs">변경</Badge>
- )}
- </div>
- </td>
+ <React.Fragment key={vendor.vendorId}>
+ <td className={cn(
+ "p-3 text-center border-r",
+ vendor.isSelected && "bg-blue-50"
+ )}>
+ {vendor.buyerConditions.placeOfShipping || "-"}
+ </td>
+ <td className={cn(
+ "p-3 text-center",
+ vendor.isSelected && "bg-blue-50"
+ )}>
+ <div className="flex items-center justify-center gap-2">
+ {latestResponse?.vendorConditions?.placeOfShipping || vendor.buyerConditions.placeOfShipping || "-"}
+ {latestResponse?.vendorConditions?.placeOfShipping &&
+ latestResponse.vendorConditions.placeOfShipping !== vendor.buyerConditions.placeOfShipping && (
+ <Badge variant="outline" className="text-xs">변경</Badge>
+ )}
+ </div>
+ </td>
+ </React.Fragment>
);
})}
</tr>
@@ -862,22 +900,29 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) {
{/* 하역지 */}
<tr>
<td className="p-3 font-medium">하역지</td>
- <td className="p-3">{data.vendors[0]?.buyerConditions.placeOfDestination || "-"}</td>
{data.vendors.map((vendor) => {
const latestResponse = vendor.responses[0];
return (
- <td key={vendor.vendorId} className={cn(
- "p-3",
- vendor.isSelected && "bg-blue-50"
- )}>
- <div className="flex items-center gap-2">
- {latestResponse?.vendorConditions?.placeOfDestination || vendor.buyerConditions.placeOfDestination || "-"}
- {latestResponse?.vendorConditions?.placeOfDestination &&
- latestResponse.vendorConditions.placeOfDestination !== vendor.buyerConditions.placeOfDestination && (
- <Badge variant="outline" className="text-xs">변경</Badge>
- )}
- </div>
- </td>
+ <React.Fragment key={vendor.vendorId}>
+ <td className={cn(
+ "p-3 text-center border-r",
+ vendor.isSelected && "bg-blue-50"
+ )}>
+ {vendor.buyerConditions.placeOfDestination || "-"}
+ </td>
+ <td className={cn(
+ "p-3 text-center",
+ vendor.isSelected && "bg-blue-50"
+ )}>
+ <div className="flex items-center justify-center gap-2">
+ {latestResponse?.vendorConditions?.placeOfDestination || vendor.buyerConditions.placeOfDestination || "-"}
+ {latestResponse?.vendorConditions?.placeOfDestination &&
+ latestResponse.vendorConditions.placeOfDestination !== vendor.buyerConditions.placeOfDestination && (
+ <Badge variant="outline" className="text-xs">변경</Badge>
+ )}
+ </div>
+ </td>
+ </React.Fragment>
);
})}
</tr>
@@ -885,30 +930,35 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) {
{/* 납기일 */}
<tr>
<td className="p-3 font-medium">납기일</td>
- <td className="p-3">
- {data.vendors[0]?.buyerConditions.deliveryDate
- ? format(new Date(data.vendors[0].buyerConditions.deliveryDate), "yyyy-MM-dd")
- : "-"}
- </td>
{data.vendors.map((vendor) => {
const latestResponse = vendor.responses[0];
return (
- <td key={vendor.vendorId} className={cn(
- "p-3",
- vendor.isSelected && "bg-blue-50"
- )}>
- <div className="flex items-center gap-2">
- {latestResponse?.vendorConditions?.deliveryDate
- ? format(new Date(latestResponse.vendorConditions.deliveryDate), "yyyy-MM-dd")
- : vendor.buyerConditions.deliveryDate
- ? format(new Date(vendor.buyerConditions.deliveryDate), "yyyy-MM-dd")
- : "-"}
- {latestResponse?.vendorConditions?.deliveryDate && vendor.buyerConditions.deliveryDate &&
- new Date(latestResponse.vendorConditions.deliveryDate).getTime() !== new Date(vendor.buyerConditions.deliveryDate).getTime() && (
- <Badge variant="outline" className="text-xs">변경</Badge>
- )}
- </div>
- </td>
+ <React.Fragment key={vendor.vendorId}>
+ <td className={cn(
+ "p-3 text-center border-r",
+ vendor.isSelected && "bg-blue-50"
+ )}>
+ {vendor.buyerConditions.deliveryDate
+ ? format(new Date(vendor.buyerConditions.deliveryDate), "yyyy-MM-dd")
+ : "-"}
+ </td>
+ <td className={cn(
+ "p-3 text-center",
+ vendor.isSelected && "bg-blue-50"
+ )}>
+ <div className="flex items-center justify-center gap-2">
+ {latestResponse?.vendorConditions?.deliveryDate
+ ? format(new Date(latestResponse.vendorConditions.deliveryDate), "yyyy-MM-dd")
+ : vendor.buyerConditions.deliveryDate
+ ? format(new Date(vendor.buyerConditions.deliveryDate), "yyyy-MM-dd")
+ : "-"}
+ {/* {latestResponse?.vendorConditions?.deliveryDate && vendor.buyerConditions.deliveryDate &&
+ new Date(latestResponse.vendorConditions.deliveryDate).getTime() !== new Date(vendor.buyerConditions.deliveryDate).getTime() && (
+ <Badge variant="outline" className="text-xs">변경</Badge>
+ )} */}
+ </div>
+ </td>
+ </React.Fragment>
);
})}
</tr>
@@ -916,22 +966,29 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) {
{/* 세금조건 */}
<tr>
<td className="p-3 font-medium">세금조건</td>
- <td className="p-3">{data.vendors[0]?.buyerConditions.taxCode || "-"}</td>
{data.vendors.map((vendor) => {
const latestResponse = vendor.responses[0];
return (
- <td key={vendor.vendorId} className={cn(
- "p-3",
- vendor.isSelected && "bg-blue-50"
- )}>
- <div className="flex items-center gap-2">
- {latestResponse?.vendorConditions?.taxCode || vendor.buyerConditions.taxCode || "-"}
- {latestResponse?.vendorConditions?.taxCode &&
- latestResponse.vendorConditions.taxCode !== vendor.buyerConditions.taxCode && (
- <Badge variant="outline" className="text-xs">변경</Badge>
- )}
- </div>
- </td>
+ <React.Fragment key={vendor.vendorId}>
+ <td className={cn(
+ "p-3 text-center border-r",
+ vendor.isSelected && "bg-blue-50"
+ )}>
+ {vendor.buyerConditions.taxCode || "-"}
+ </td>
+ <td className={cn(
+ "p-3 text-center",
+ vendor.isSelected && "bg-blue-50"
+ )}>
+ <div className="flex items-center justify-center gap-2">
+ {latestResponse?.vendorConditions?.taxCode || vendor.buyerConditions.taxCode || "-"}
+ {latestResponse?.vendorConditions?.taxCode &&
+ latestResponse.vendorConditions.taxCode !== vendor.buyerConditions.taxCode && (
+ <Badge variant="outline" className="text-xs">변경</Badge>
+ )}
+ </div>
+ </td>
+ </React.Fragment>
);
})}
</tr>
@@ -939,22 +996,29 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) {
{/* 계약기간 */}
<tr>
<td className="p-3 font-medium">계약기간</td>
- <td className="p-3">{data.vendors[0]?.buyerConditions.contractDuration || "-"}</td>
{data.vendors.map((vendor) => {
const latestResponse = vendor.responses[0];
return (
- <td key={vendor.vendorId} className={cn(
- "p-3",
- vendor.isSelected && "bg-blue-50"
- )}>
- <div className="flex items-center gap-2">
- {latestResponse?.vendorConditions?.contractDuration || vendor.buyerConditions.contractDuration || "-"}
- {latestResponse?.vendorConditions?.contractDuration &&
- latestResponse.vendorConditions.contractDuration !== vendor.buyerConditions.contractDuration && (
- <Badge variant="outline" className="text-xs">변경</Badge>
- )}
- </div>
- </td>
+ <React.Fragment key={vendor.vendorId}>
+ <td className={cn(
+ "p-3 text-center border-r",
+ vendor.isSelected && "bg-blue-50"
+ )}>
+ {vendor.buyerConditions.contractDuration || "-"}
+ </td>
+ <td className={cn(
+ "p-3 text-center",
+ vendor.isSelected && "bg-blue-50"
+ )}>
+ <div className="flex items-center justify-center gap-2">
+ {latestResponse?.vendorConditions?.contractDuration || vendor.buyerConditions.contractDuration || "-"}
+ {latestResponse?.vendorConditions?.contractDuration &&
+ latestResponse.vendorConditions.contractDuration !== vendor.buyerConditions.contractDuration && (
+ <Badge variant="outline" className="text-xs">변경</Badge>
+ )}
+ </div>
+ </td>
+ </React.Fragment>
);
})}
</tr>
@@ -980,11 +1044,13 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) {
<tr className="border-b bg-gray-50">
<th className="text-left p-3 font-semibold">품목코드</th>
<th className="text-left p-3 font-semibold">품목명</th>
+ <th className="text-left p-3 font-semibold">자재분류</th>
<th className="text-right p-3 font-semibold">수량</th>
+ <th className="text-left p-3 font-semibold">규격</th>
+ <th className="text-right p-3 font-semibold">중량</th>
<th className="text-right p-3 font-semibold">단가</th>
<th className="text-right p-3 font-semibold">총액</th>
- <th className="text-left p-3 font-semibold">납기</th>
- <th className="text-left p-3 font-semibold">제조사</th>
+ <th className="text-left p-3 font-semibold">납기일자</th>
</tr>
</thead>
<tbody className="divide-y">
@@ -994,15 +1060,30 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) {
return (
<tr key={quoteItem.prItemId} className="hover:bg-gray-50">
- <td className="p-3 text-sm">{prItem.materialCode}</td>
- <td className="p-3">
- <p className="font-medium">{prItem.materialDescription}</p>
+ <td className="p-3 text-sm">
+ <p className="font-medium">{prItem.materialCode}</p>
<p className="text-xs text-muted-foreground">
{prItem.prNo} • {prItem.prItem}
</p>
</td>
+ <td className="p-3">
+ <p className="font-medium">{prItem.materialDescription}</p>
+ {prItem.remark && (
+ <p className="text-xs text-muted-foreground mt-1">{prItem.remark}</p>
+ )}
+ </td>
+ <td className="p-3 text-sm">
+ {prItem.materialCategory || "-"}
+ </td>
<td className="p-3 text-right">
- {quoteItem.quantity} {prItem.uom}
+ <p>{quoteItem.quantity} {prItem.uom}</p>
+ <p className="text-xs text-muted-foreground">요청: {prItem.requestedQuantity}</p>
+ </td>
+ <td className="p-3 text-sm">
+ {prItem.size || "-"}
+ </td>
+ <td className="p-3 text-right text-sm">
+ {prItem.grossWeight ? `${prItem.grossWeight} ${prItem.gwUom || ""}` : "-"}
</td>
<td className="p-3 text-right font-medium">
{formatAmount(quoteItem.unitPrice, quoteItem.currency)}
@@ -1016,16 +1097,11 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) {
: quoteItem.leadTime
? `${quoteItem.leadTime}일`
: "-"}
- </td>
- <td className="p-3 text-sm">
- {quoteItem.manufacturer ? (
- <div>
- <p>{quoteItem.manufacturer}</p>
- {quoteItem.modelNo && (
- <p className="text-xs text-muted-foreground">{quoteItem.modelNo}</p>
- )}
- </div>
- ) : "-"}
+ {prItem.requestedDeliveryDate && (
+ <p className="text-xs text-muted-foreground">
+ 요청: {format(new Date(prItem.requestedDeliveryDate), "yyyy-MM-dd")}
+ </p>
+ )}
</td>
</tr>
);
@@ -1054,6 +1130,76 @@ export function QuotationCompareView({ data }: QuotationCompareViewProps) {
</div>
</div>
)}
+
+ {/* 첨부파일 */}
+ {selectedResponse?.attachments && selectedResponse.attachments.length > 0 && (
+ <div className="mt-6 pt-6 border-t">
+ <h4 className="font-semibold mb-3 flex items-center gap-2">
+ <Paperclip className="h-4 w-4" />
+ 제출 문서 ({selectedResponse.attachments.length}건)
+ </h4>
+ <div className="space-y-2">
+ {selectedResponse.attachments.map((attachment) => (
+ <div
+ key={attachment.id}
+ className="flex items-center justify-between p-3 border rounded-lg hover:bg-gray-50"
+ >
+ <div className="flex items-center gap-3 flex-1">
+ <FileText className="h-5 w-5 text-blue-500" />
+ <div className="flex-1 min-w-0">
+ <p className="font-medium text-sm truncate">
+ {attachment.originalFileName}
+ </p>
+ <div className="flex items-center gap-3 text-xs text-muted-foreground mt-1">
+ <span className="px-2 py-0.5 bg-blue-100 text-blue-700 rounded">
+ {attachment.attachmentType}
+ </span>
+ {attachment.documentNo && (
+ <span>문서번호: {attachment.documentNo}</span>
+ )}
+ {attachment.fileSize && (
+ <span>{(attachment.fileSize / 1024).toFixed(1)} KB</span>
+ )}
+ {attachment.uploadedAt && (
+ <span>
+ 업로드: {format(new Date(attachment.uploadedAt), "yyyy-MM-dd HH:mm")}
+ </span>
+ )}
+ </div>
+ {attachment.description && (
+ <p className="text-xs text-muted-foreground mt-1">
+ {attachment.description}
+ </p>
+ )}
+ {(attachment.validFrom || attachment.validTo) && (
+ <p className="text-xs text-orange-600 mt-1">
+ 유효기간: {attachment.validFrom ? format(new Date(attachment.validFrom), "yyyy-MM-dd") : "-"} ~ {attachment.validTo ? format(new Date(attachment.validTo), "yyyy-MM-dd") : "-"}
+ </p>
+ )}
+ </div>
+ </div>
+ <Button
+ size="sm"
+ variant="outline"
+ onClick={() => {
+ // 다운로드 처리
+ const link = document.createElement('a');
+ link.href = attachment.filePath;
+ link.download = attachment.originalFileName;
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ }}
+ className="gap-1"
+ >
+ <Download className="h-3 w-3" />
+ 다운로드
+ </Button>
+ </div>
+ ))}
+ </div>
+ </div>
+ )}
</DialogContent>
</Dialog>
diff --git a/lib/rfq-last/vendor-response/editor/commercial-terms-form.tsx b/lib/rfq-last/vendor-response/editor/commercial-terms-form.tsx
index d896ee34..b8b3a830 100644
--- a/lib/rfq-last/vendor-response/editor/commercial-terms-form.tsx
+++ b/lib/rfq-last/vendor-response/editor/commercial-terms-form.tsx
@@ -85,11 +85,14 @@ export default function CommercialTermsForm({ rfqDetail, rfq }: CommercialTermsF
const isDifferentPaymentTerms = vendorPaymentTermsCode !== rfqDetail.paymentTermsCode
const isDifferentIncoterms = vendorIncotermsCode !== rfqDetail.incotermsCode
- // 날짜만 비교 (년월일만 체크)
+ // 날짜만 비교 (년월일만 체크) - 로컬 시간대 기준
const formatDateOnly = (date: Date | string | null) => {
if (!date) return null
const d = new Date(date)
- return d.toISOString().split('T')[0] // YYYY-MM-DD 형식으로 변환
+ const year = d.getFullYear()
+ const month = String(d.getMonth() + 1).padStart(2, '0')
+ const day = String(d.getDate()).padStart(2, '0')
+ return `${year}-${month}-${day}`
}
const isDifferentDeliveryDate = !isFrameContract &&
formatDateOnly(vendorDeliveryDate) !== formatDateOnly(rfqDetail.deliveryDate)
diff --git a/lib/rfq-last/vendor/rfq-vendor-table.tsx b/lib/rfq-last/vendor/rfq-vendor-table.tsx
index 428160d5..c0f80aca 100644
--- a/lib/rfq-last/vendor/rfq-vendor-table.tsx
+++ b/lib/rfq-last/vendor/rfq-vendor-table.tsx
@@ -344,10 +344,6 @@ export function RfqVendorTable({
row.response?.submission?.submittedAt
);
- if (vendorsWithQuotation.length < 2) {
- toast.warning("비교를 위해 최소 2개 이상의 견적서가 필요합니다.");
- return;
- }
// 견적 비교 페이지로 이동 또는 모달 열기
const vendorIds = vendorsWithQuotation
@@ -1656,7 +1652,7 @@ export function RfqVendorTable({
size="sm"
onClick={handleQuotationCompare}
disabled={quotationCount < 1}
- className={quotationCount >= 2 ? "border-blue-500 text-blue-600 hover:bg-blue-50" : ""}
+ className={quotationCount >= 1 ? "border-blue-500 text-blue-600 hover:bg-blue-50" : ""}
>
<GitCompare className="h-4 w-4 mr-2" />
견적 비교