"use client"; import * as React from "react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Trophy, TrendingUp, TrendingDown, AlertCircle, CheckCircle, XCircle, ChevronDown, ChevronUp, Info, DollarSign, Calendar, Package, Globe, FileText, Truck, AlertTriangle, } from "lucide-react"; import { cn } from "@/lib/utils"; import { format } from "date-fns"; import { ko } from "date-fns/locale"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "@/components/ui/collapsible"; import type { ComparisonData, VendorComparison, PrItemComparison } from "../actions"; interface QuotationCompareViewProps { data: ComparisonData; } export function QuotationCompareView({ data }: QuotationCompareViewProps) { const [expandedItems, setExpandedItems] = React.useState>(new Set()); const [selectedMetric, setSelectedMetric] = React.useState<"price" | "delivery" | "compliance">("price"); // 아이템 확장/축소 토글 const toggleItemExpansion = (itemId: number) => { setExpandedItems((prev) => { const newSet = new Set(prev); if (newSet.has(itemId)) { newSet.delete(itemId); } else { newSet.add(itemId); } return newSet; }); }; // 순위에 따른 색상 const getRankColor = (rank: number) => { switch (rank) { case 1: return "text-green-600 bg-green-50"; case 2: return "text-blue-600 bg-blue-50"; case 3: return "text-orange-600 bg-orange-50"; default: return "text-gray-600 bg-gray-50"; } }; // 가격 차이 색상 const getVarianceColor = (variance: number) => { if (variance < -5) return "text-green-600"; if (variance > 5) return "text-red-600"; return "text-gray-600"; }; // 조건 일치 여부 아이콘 const getComplianceIcon = (matches: boolean) => { return matches ? ( ) : ( ); }; // 금액 포맷 const formatAmount = (amount: number, currency: string = "USD") => { return new Intl.NumberFormat("ko-KR", { style: "currency", currency: currency, minimumFractionDigits: 0, maximumFractionDigits: 2, }).format(amount); }; return (
{/* 요약 카드 */}
{/* 최저가 벤더 */} 최저가 벤더

{data.summary.lowestBidder}

{formatAmount(data.summary.priceRange.min, data.summary.currency)}

{/* 평균 가격 */} 평균 가격

{formatAmount(data.summary.priceRange.average, data.summary.currency)}

{data.vendors.length}개 업체 평균

{/* 가격 범위 */} 가격 범위

{((data.summary.priceRange.max - data.summary.priceRange.min) / data.summary.priceRange.min * 100).toFixed(1)}%

최저가 대비 최고가 차이

{/* 조건 불일치 */} 조건 불일치

{data.vendors.filter(v => v.conditionDifferences.hasDifferences).length}개

제시 조건과 차이 있음

{/* 탭 뷰 */} 종합 비교 조건 비교 아이템별 비교 상세 분석 {/* 종합 비교 */} 가격 순위
{data.vendors.map((vendor) => (
{vendor.rank}

{vendor.vendorName}

{vendor.vendorCode} • {vendor.vendorCountry}

{/* 조건 차이 표시 */} {vendor.conditionDifferences.criticalDifferences.length > 0 && ( 중요 차이 {vendor.conditionDifferences.criticalDifferences.length}
{vendor.conditionDifferences.criticalDifferences.map((diff, idx) => (

{diff}

))}
)} {/* 가격 정보 */}

{formatAmount(vendor.totalAmount, vendor.currency)}

{vendor.priceVariance && vendor.priceVariance > 0 ? "+" : ""} {vendor.priceVariance?.toFixed(1)}% vs 평균

))}
{/* 조건 비교 */} 거래 조건 비교 {data.vendors.map((vendor) => ( ))} {/* 통화 */} {data.vendors.map((vendor) => ( ))} {/* 지급조건 */} {data.vendors.map((vendor) => ( ))} {/* 인코텀즈 */} {data.vendors.map((vendor) => ( ))} {/* 납기 */} {data.vendors.map((vendor) => { const vendorDate = vendor.vendorConditions.deliveryDate || vendor.buyerConditions.deliveryDate; const isDelayed = vendorDate && vendor.buyerConditions.deliveryDate && new Date(vendorDate) > new Date(vendor.buyerConditions.deliveryDate); return ( ); })} {/* 초도품 */} {data.vendors.map((vendor) => ( ))} {/* 스페어파트 */} {data.vendors.map((vendor) => ( ))} {/* 연동제 */} {data.vendors.map((vendor) => ( ))}
항목 구매자 제시 {vendor.vendorName}
통화 {data.vendors[0]?.buyerConditions.currency}
{vendor.vendorConditions.currency || vendor.buyerConditions.currency} {vendor.vendorConditions.currency !== vendor.buyerConditions.currency && ( 변경 )}
지급조건 {data.vendors[0]?.buyerConditions.paymentTermsCode} {data.vendors[0]?.buyerConditions.paymentTermsDesc}
{vendor.vendorConditions.paymentTermsCode || vendor.buyerConditions.paymentTermsCode} {vendor.vendorConditions.paymentTermsDesc || vendor.buyerConditions.paymentTermsDesc} {vendor.vendorConditions.paymentTermsCode && vendor.vendorConditions.paymentTermsCode !== vendor.buyerConditions.paymentTermsCode && ( 변경 )}
인코텀즈 {data.vendors[0]?.buyerConditions.incotermsCode}
{vendor.vendorConditions.incotermsCode || vendor.buyerConditions.incotermsCode} {vendor.vendorConditions.incotermsCode !== vendor.buyerConditions.incotermsCode && ( 변경 )}
납기 {data.vendors[0]?.buyerConditions.deliveryDate ? format(new Date(data.vendors[0].buyerConditions.deliveryDate), "yyyy-MM-dd") : "-"}
{vendorDate ? format(new Date(vendorDate), "yyyy-MM-dd") : "-"} {isDelayed && ( 지연 )}
초도품 {data.vendors[0]?.buyerConditions.firstYn ? "요구" : "해당없음"} {vendor.buyerConditions.firstYn && ( {vendor.vendorConditions.firstAcceptance || "미응답"} )} {!vendor.buyerConditions.firstYn && "-"}
스페어파트 {data.vendors[0]?.buyerConditions.sparepartYn ? "요구" : "해당없음"} {vendor.buyerConditions.sparepartYn && ( {vendor.vendorConditions.sparepartAcceptance || "미응답"} )} {!vendor.buyerConditions.sparepartYn && "-"}
연동제 {data.vendors[0]?.buyerConditions.materialPriceRelatedYn ? "적용" : "미적용"}
{vendor.vendorConditions.materialPriceRelatedYn !== undefined ? vendor.vendorConditions.materialPriceRelatedYn ? "적용" : "미적용" : vendor.buyerConditions.materialPriceRelatedYn ? "적용" : "미적용"} {vendor.vendorConditions.materialPriceRelatedReason && (

{vendor.vendorConditions.materialPriceRelatedReason}

)}
{/* 아이템별 비교 */} PR 아이템별 가격 비교
{data.prItems.map((item) => ( toggleItemExpansion(item.prItemId)} >
{expandedItems.has(item.prItemId) ? ( ) : ( )}

{item.materialDescription}

{item.materialCode} • {item.prNo} • {item.requestedQuantity} {item.uom}

단가 범위

{formatAmount(item.priceAnalysis.lowestPrice)} ~ {formatAmount(item.priceAnalysis.highestPrice)}

{item.vendorQuotes.map((quote) => ( ))}
벤더 단가 총액 수량 납기 제조사 순위
{quote.vendorName} {formatAmount(quote.unitPrice, quote.currency)} {formatAmount(quote.totalPrice, quote.currency)} {quote.quotedQuantity} {quote.deliveryDate ? format(new Date(quote.deliveryDate), "yyyy-MM-dd") : quote.leadTime ? `${quote.leadTime}일` : "-"} {quote.manufacturer && (

{quote.manufacturer}

{quote.modelNo && (

{quote.modelNo}

)}
)}
#{quote.priceRank}
{/* 가격 분석 요약 */}

평균 단가

{formatAmount(item.priceAnalysis.averagePrice)}

가격 편차

±{formatAmount(item.priceAnalysis.priceVariance)}

최저가 업체

{item.vendorQuotes.find(q => q.priceRank === 1)?.vendorName}

가격 차이

{((item.priceAnalysis.highestPrice - item.priceAnalysis.lowestPrice) / item.priceAnalysis.lowestPrice * 100).toFixed(1)}%

))}
{/* 상세 분석 */}
{/* 위험 요소 분석 */} 위험 요소 분석
{data.vendors.map((vendor) => { if (!vendor.conditionDifferences.hasDifferences) return null; return (

{vendor.vendorName}

{vendor.conditionDifferences.criticalDifferences.length > 0 && (

중요 차이점:

{vendor.conditionDifferences.criticalDifferences.map((diff, idx) => (

• {diff}

))}
)} {vendor.conditionDifferences.differences.length > 0 && (

일반 차이점:

{vendor.conditionDifferences.differences.map((diff, idx) => (

• {diff}

))}
)}
); })}
{/* 추천 사항 */} 선정 추천
{/* 가격 기준 추천 */}

가격 우선 선정

{data.vendors[0]?.vendorName} - {formatAmount(data.vendors[0]?.totalAmount || 0)}

{data.vendors[0]?.conditionDifferences.hasDifferences && (

⚠️ 조건 차이 검토 필요

)}
{/* 조건 준수 기준 추천 */}

조건 준수 우선 선정

{(() => { const compliantVendor = data.vendors.find(v => !v.conditionDifferences.hasDifferences); if (compliantVendor) { return (

{compliantVendor.vendorName} - {formatAmount(compliantVendor.totalAmount)}

모든 조건 충족 (가격 순위: #{compliantVendor.rank})

); } return (

모든 조건을 충족하는 벤더 없음

); })()}
{/* 균형 추천 */}

균형 선정 (추천)

{(() => { // 가격 순위와 조건 차이를 고려한 점수 계산 const scoredVendors = data.vendors.map(v => ({ ...v, score: (v.rank || 10) + v.conditionDifferences.criticalDifferences.length * 3 + v.conditionDifferences.differences.length })); scoredVendors.sort((a, b) => a.score - b.score); const recommended = scoredVendors[0]; return (

{recommended.vendorName} - {formatAmount(recommended.totalAmount)}

가격 순위 #{recommended.rank}, 조건 차이 최소화

); })()}
{/* 벤더별 비고사항 */} {data.vendors.some(v => v.generalRemark || v.technicalProposal) && ( 벤더 제안사항 및 비고
{data.vendors.map((vendor) => { if (!vendor.generalRemark && !vendor.technicalProposal) return null; return (

{vendor.vendorName}

{vendor.generalRemark && (

일반 비고:

{vendor.generalRemark}

)} {vendor.technicalProposal && (

기술 제안:

{vendor.technicalProposal}

)}
); })}
)}
); }