"use client"; import * as React from "react"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Label } from "@/components/ui/label"; import { Badge } from "@/components/ui/badge"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { Checkbox } from "@/components/ui/checkbox"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Loader2, X, FileText, Shield, Globe, Settings, Link, CheckCircle, Info, AlertCircle, Building2 } from "lucide-react"; import { cn } from "@/lib/utils"; import { toast } from "sonner"; import { getAvlVendorsForRfq, addAvlVendorsToRfq } from "../service"; interface AvlVendor { id: number; vendorId: number | null; vendorName: string; vendorCode: string | null; avlVendorName: string; tier: string | null; headquarterLocation: string | null; manufacturingLocation: string | null; materialGroupCode: string; materialGroupName: string | null; packageName: string | null; isAgent: boolean; hasAvl: boolean; isBlacklist: boolean; isBcc: boolean; remark: string | null; } interface VendorContract { vendorId: number; agreementYn: boolean; ndaYn: boolean; gtcType: "general" | "project" | "none"; } interface AvlVendorDialogProps { open: boolean; onOpenChange: (open: boolean) => void; rfqId: number; rfqCode?: string; onSuccess: () => void; } export function AvlVendorDialog({ open, onOpenChange, rfqId, rfqCode, onSuccess, }: AvlVendorDialogProps) { const [isLoading, setIsLoading] = React.useState(false); const [isLoadingAvl, setIsLoadingAvl] = React.useState(false); const [avlVendors, setAvlVendors] = React.useState([]); const [selectedVendorIds, setSelectedVendorIds] = React.useState>(new Set()); const [activeTab, setActiveTab] = React.useState<"vendors" | "contracts">("vendors"); const [vendorContracts, setVendorContracts] = React.useState([]); const [existingVendorIds, setExistingVendorIds] = React.useState>(new Set()); // 일괄 적용용 기본값 const [defaultContract, setDefaultContract] = React.useState({ agreementYn: true, ndaYn: true, gtcType: "none" as "general" | "project" | "none" }); // AVL 벤더 로드 const loadAvlVendors = React.useCallback(async () => { setIsLoadingAvl(true); try { const result = await getAvlVendorsForRfq(rfqId); if (result.success && result.vendors) { setAvlVendors(result.vendors); // 이미 RFQ에 추가된 벤더 ID 설정 const existingIds = new Set(result.existingVendorIds || []); setExistingVendorIds(existingIds); // AVL에서 가져온 모든 벤더를 기본 선택 (이미 추가된 것 제외) const defaultSelected = new Set( result.vendors .filter(v => v.vendorId && !existingIds.has(v.vendorId)) .map(v => v.vendorId!) ); setSelectedVendorIds(defaultSelected); // 초기 계약 설정 const initialContracts = result.vendors .filter(v => v.vendorId && defaultSelected.has(v.vendorId)) .map(v => { const isInternational = v.headquarterLocation && v.headquarterLocation !== "KR" && v.headquarterLocation !== "한국"; return { vendorId: v.vendorId!, agreementYn: true, ndaYn: true, gtcType: isInternational ? "general" : "none" as const }; }); setVendorContracts(initialContracts); if (result.vendors.length === 0) { toast.info("해당 프로젝트와 자재그룹에 대한 AVL 벤더가 없습니다."); } } else { toast.error(result.error || "AVL 데이터를 불러오는데 실패했습니다."); } } catch (error) { console.error("Failed to load AVL vendors:", error); toast.error("AVL 데이터를 불러오는데 실패했습니다."); } finally { setIsLoadingAvl(false); } }, [rfqId]); // 다이얼로그 열릴 때 데이터 로드 React.useEffect(() => { if (open) { loadAvlVendors(); } }, [open, loadAvlVendors]); // 초기화 React.useEffect(() => { if (!open) { setAvlVendors([]); setSelectedVendorIds(new Set()); setVendorContracts([]); setActiveTab("vendors"); setDefaultContract({ agreementYn: true, ndaYn: true, gtcType: "none" }); } }, [open]); // 벤더 선택 토글 const toggleVendorSelection = (vendorId: number) => { const newSelection = new Set(selectedVendorIds); if (newSelection.has(vendorId)) { newSelection.delete(vendorId); setVendorContracts(contracts => contracts.filter(c => c.vendorId !== vendorId) ); } else { newSelection.add(vendorId); const vendor = avlVendors.find(v => v.vendorId === vendorId); if (vendor) { const isInternational = vendor.headquarterLocation && vendor.headquarterLocation !== "KR" && vendor.headquarterLocation !== "한국"; setVendorContracts(contracts => [ ...contracts, { vendorId, agreementYn: defaultContract.agreementYn, ndaYn: defaultContract.ndaYn, gtcType: isInternational ? defaultContract.gtcType : "none" } ]); } } setSelectedVendorIds(newSelection); }; // 개별 벤더의 계약 설정 업데이트 const updateVendorContract = (vendorId: number, field: string, value: any) => { setVendorContracts(contracts => contracts.map(c => c.vendorId === vendorId ? { ...c, [field]: value } : c ) ); }; // 모든 벤더에 일괄 적용 const applyToAll = () => { setVendorContracts(contracts => contracts.map(c => { const vendor = avlVendors.find(v => v.vendorId === c.vendorId); const isInternational = vendor?.headquarterLocation && vendor.headquarterLocation !== "KR" && vendor.headquarterLocation !== "한국"; return { ...c, agreementYn: defaultContract.agreementYn, ndaYn: defaultContract.ndaYn, gtcType: isInternational ? defaultContract.gtcType : "none" }; }) ); toast.success("모든 벤더에 기본계약 설정이 적용되었습니다."); }; // 제출 처리 const handleSubmit = async () => { if (selectedVendorIds.size === 0) { toast.error("최소 1개 이상의 벤더를 선택해주세요."); return; } setIsLoading(true); try { const selectedVendors = avlVendors.filter(v => v.vendorId && selectedVendorIds.has(v.vendorId) ); const result = await addAvlVendorsToRfq({ rfqId, vendors: selectedVendors.map(v => ({ vendorId: v.vendorId!, vendorName: v.vendorName, vendorCode: v.vendorCode, contractRequirements: vendorContracts.find(c => c.vendorId === v.vendorId) || { agreementYn: true, ndaYn: true, gtcType: "none" as const } })) }); if (result.success) { toast.success(

{result.addedCount}개의 AVL 벤더가 추가되었습니다.

{result.skippedCount && result.skippedCount > 0 && (

{result.skippedCount}개는 이미 추가되어 있어 건너뛰었습니다.

)}
); onSuccess(); onOpenChange(false); } else { toast.error(result.error || "벤더 추가에 실패했습니다."); } } catch (error) { console.error("Submit error:", error); toast.error("오류가 발생했습니다."); } finally { setIsLoading(false); } }; // 선택 가능한 벤더 필터링 const selectableVendors = avlVendors.filter(v => v.vendorId); const selectedVendors = selectableVendors.filter(v => selectedVendorIds.has(v.vendorId!)); return ( AVL 벤더 연동 프로젝트 AVL에 등록된 벤더를 RFQ에 추가합니다. 선택된 벤더에게 견적 요청을 발송할 수 있습니다. {isLoadingAvl ? (
) : ( setActiveTab(v as any)} className="flex-1 flex flex-col min-h-0"> 1. AVL 벤더 선택 {selectedVendorIds.size > 0 && ( {selectedVendorIds.size} )} 2. 기본계약 설정 {avlVendors.length === 0 ? ( 해당 프로젝트와 자재그룹에 대한 AVL 벤더가 없습니다. ) : ( AVL 벤더 목록 총 {avlVendors.length}개 업체 AVL에서 자동으로 가져온 벤더입니다. 필요한 벤더를 선택하세요.
{avlVendors.map((vendor) => { const isDisabled = !vendor.vendorId || existingVendorIds.has(vendor.vendorId); const isSelected = vendor.vendorId && selectedVendorIds.has(vendor.vendorId); const isInternational = vendor.headquarterLocation && vendor.headquarterLocation !== "KR" && vendor.headquarterLocation !== "한국"; return (
vendor.vendorId && toggleVendorSelection(vendor.vendorId)} disabled={isDisabled} />
{vendor.vendorName} {vendor.vendorCode && ( {vendor.vendorCode} )} {existingVendorIds.has(vendor.vendorId!) && ( 추가됨 )}
{vendor.tier && ( 등급: {vendor.tier} )} {isInternational ? ( {vendor.headquarterLocation} ) : ( 국내 )} {vendor.materialGroupName && ( {vendor.materialGroupName} )} {vendor.isAgent && ( Agent )}
{vendor.hasAvl && ( AVL )} {vendor.isBcc && ( BCC )} {vendor.isBlacklist && ( Blacklist )}
); })}
)}
일괄 적용 설정 모든 벤더에 동일한 설정을 적용할 수 있습니다.
setDefaultContract({ ...defaultContract, agreementYn: !!checked }) } />
setDefaultContract({ ...defaultContract, ndaYn: !!checked }) } />
setDefaultContract({ ...defaultContract, gtcType: value }) } >
개별 벤더 기본계약 설정 각 벤더별로 다른 기본계약을 요구할 수 있습니다.
{selectedVendors.map((vendor) => { const contract = vendorContracts.find(c => c.vendorId === vendor.vendorId); const isInternational = vendor.headquarterLocation && vendor.headquarterLocation !== "KR" && vendor.headquarterLocation !== "한국"; return (
{vendor.vendorCode && ( {vendor.vendorCode} )} {vendor.vendorName} {vendor.headquarterLocation || "미지정"}
vendor.vendorId && updateVendorContract(vendor.vendorId, "agreementYn", !!checked) } />
vendor.vendorId && updateVendorContract(vendor.vendorId, "ndaYn", !!checked) } />
{isInternational && vendor.vendorId && (
updateVendorContract(vendor.vendorId!, "gtcType", value) } >
)} {!isInternational && (
국내 업체 - GTC 불필요
)}
); })}
)} {activeTab === "vendors" && selectedVendorIds.size > 0 && ( )} {activeTab === "contracts" && ( )}
); }