From 4c07f977c951cd99dd50d3bdaad0437e3dd55e6d Mon Sep 17 00:00:00 2001 From: joonhoekim <26rote@gmail.com> Date: Thu, 6 Nov 2025 20:01:16 +0900 Subject: (김준회) ITB/RFQ/일반견적: 발송시 첨부파일 있는 경우 '암호화해제 결재' 프로세스 타도록 변경 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/rfq-last/vendor/avl-vendor-dialog.tsx | 628 ++++++------------------------ 1 file changed, 125 insertions(+), 503 deletions(-) (limited to 'lib/rfq-last/vendor/avl-vendor-dialog.tsx') diff --git a/lib/rfq-last/vendor/avl-vendor-dialog.tsx b/lib/rfq-last/vendor/avl-vendor-dialog.tsx index 2efd96b9..67a71cc5 100644 --- a/lib/rfq-last/vendor/avl-vendor-dialog.tsx +++ b/lib/rfq-last/vendor/avl-vendor-dialog.tsx @@ -10,37 +10,28 @@ import { 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, +import { + Loader2, + Eye, + Globe, CheckCircle, - Info, - AlertCircle, - Building2 + Building2, + Info } from "lucide-react"; import { cn } from "@/lib/utils"; import { toast } from "sonner"; -import { getAvlVendorsForRfq, addAvlVendorsToRfq } from "../service"; +import { getAvlVendorsForRfq } from "../service"; interface AvlVendor { id: number; vendorId: number | null; - vendorName: string; + vendorName: string | null; vendorCode: string | null; - avlVendorName: string; + avlVendorName: string | null; tier: string | null; headquarterLocation: string | null; manufacturingLocation: string | null; @@ -54,79 +45,35 @@ interface AvlVendor { 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 벤더 로드 + // AVL 벤더 로드 (참조용) const loadAvlVendors = React.useCallback(async () => { setIsLoadingAvl(true); try { const result = await getAvlVendorsForRfq(rfqId); if (result.success && result.vendors) { - setAvlVendors(result.vendors); - - // 이미 RFQ에 추가된 벤더 ID 설정 + setAvlVendors(result.vendors as AvlVendor[]); + + // 이미 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 벤더가 없습니다."); } @@ -152,139 +99,22 @@ export function AvlVendorDialog({ React.useEffect(() => { if (!open) { setAvlVendors([]); - setSelectedVendorIds(new Set()); - setVendorContracts([]); - setActiveTab("vendors"); - setDefaultContract({ - agreementYn: true, - ndaYn: true, - gtcType: "none" - }); + setExistingVendorIds(new Set()); } }, [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 벤더 목록 조회 - 프로젝트 AVL에 등록된 벤더를 RFQ에 추가합니다. 선택된 벤더에게 견적 요청을 발송할 수 있습니다. + 동일한 자재그룹코드를 다루는 AVL 등록 벤더들의 목록을 확인할 수 있습니다. @@ -293,340 +123,132 @@ export function AvlVendorDialog({ ) : ( - 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 - - )} -
-
-
+
+ {avlVendors.length === 0 ? ( + + + + 해당 프로젝트와 자재그룹에 대한 AVL 벤더가 없습니다. + + + ) : ( + + + + AVL 등록 벤더 목록 + + 총 {avlVendors.length}개 업체 + + + + 동일한 자재그룹코드를 다루는 AVL 등록 벤더들의 정보입니다. + + + + +
+ {avlVendors.map((vendor) => { + const isInCurrentRfq = vendor.vendorId && existingVendorIds.has(vendor.vendorId); + const isInternational = vendor.headquarterLocation && + vendor.headquarterLocation !== "KR" && + vendor.headquarterLocation !== "한국"; -
- {vendor.hasAvl && ( - - AVL + return ( +
+
+ +
+
+ {vendor.vendorName} + {vendor.vendorCode && ( + + {vendor.vendorCode} + + )} + {isInCurrentRfq && ( + + + RFQ 참여중 )} - {vendor.isBcc && ( +
+
+ {vendor.tier && ( - BCC + 등급: {vendor.tier} )} - {vendor.isBlacklist && ( - - Blacklist + {isInternational ? ( + + + {vendor.headquarterLocation} + + ) : ( + + 국내 )} -
-
-
- ); - })} -
- - - - )} - - - -
- - - - - 일괄 적용 설정 - - - 모든 벤더에 동일한 설정을 적용할 수 있습니다. - - - -
-
-
- - 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.materialGroupName && ( + + {vendor.materialGroupName} + + )} + {vendor.isAgent && ( + + Agent + )} - {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 불필요 + {vendor.remark && ( +
+ {vendor.remark}
)}
- ); - })} -
- - - -
- - + +
+ {vendor.hasAvl && ( + + + AVL + + )} + {vendor.isBcc && ( + + BCC + + )} + {vendor.isBlacklist && ( + + Blacklist + + )} +
+
+ ); + })} +
+ + + + )} +
)} - {activeTab === "vendors" && selectedVendorIds.size > 0 && ( - - )} - {activeTab === "contracts" && ( - - )}
-- cgit v1.2.3