"use client" import * as React from "react" import { Search, Loader2, CheckSquare, Square, ChevronDown, ChevronRight } from "lucide-react" import { Button } from "@/components/ui/button" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group" import { ScrollArea } from "@/components/ui/scroll-area" import { Separator } from "@/components/ui/separator" import { toast } from "sonner" import { searchShipbuildingItems, searchOffshoreTopItems, searchOffshoreHullItems, searchTechVendors, getTechVendorsContactsForMapping, addContactItemMapping, } from "../service-add-mapping" interface AddContactItemMappingDialogProps { trigger?: React.ReactNode } interface Item { id: number itemCode: string itemList: string workType: string | null shipTypes?: string | null subItemList?: string | null } interface Vendor { id: number vendorCode: string | null vendorName: string email: string | null techVendorType: string status: string } interface Contact { id: number contactName: string contactPosition: string | null contactTitle: string | null contactEmail: string contactPhone: string | null isPrimary: boolean } export function AddContactItemMappingDialog({ trigger }: AddContactItemMappingDialogProps) { const [open, setOpen] = React.useState(false) const [isSubmitting, setIsSubmitting] = React.useState(false) const [selectedItemType, setSelectedItemType] = React.useState<'ship' | 'top' | 'hull'>('ship') const [itemSearch, setItemSearch] = React.useState("") const [items, setItems] = React.useState([]) const [selectedItems, setSelectedItems] = React.useState>(new Set()) const [isLoadingItems, setIsLoadingItems] = React.useState(false) // Section 2: 벤더 선택 const [vendorSearch, setVendorSearch] = React.useState("") const [vendors, setVendors] = React.useState([]) const [selectedVendors, setSelectedVendors] = React.useState>(new Set()) const [isLoadingVendors, setIsLoadingVendors] = React.useState(false) // Section 3: 벤더별 담당자 선택 const [vendorContacts, setVendorContacts] = React.useState>({}) const [selectedContacts, setSelectedContacts] = React.useState>>({}) const [expandedVendors, setExpandedVendors] = React.useState>(new Set()) const [isLoadingContacts, setIsLoadingContacts] = React.useState(false) // 아이템 타입 변경 시 아이템 로드 React.useEffect(() => { loadItems() }, [selectedItemType, itemSearch]) // 벤더 검색 디바운싱 React.useEffect(() => { const timer = setTimeout(() => { loadVendors() }, 300) return () => clearTimeout(timer) }, [vendorSearch]) // 선택된 벤더 변경 시 담당자 로드 React.useEffect(() => { if (selectedVendors.size > 0) { loadContacts() } }, [selectedVendors]) const loadItems = async () => { setIsLoadingItems(true) try { let result if (selectedItemType === 'ship') { result = await searchShipbuildingItems(itemSearch) } else if (selectedItemType === 'top') { result = await searchOffshoreTopItems(itemSearch) } else { result = await searchOffshoreHullItems(itemSearch) } setItems(result.data || []) } catch (err) { toast.error("아이템 로드 실패") } finally { setIsLoadingItems(false) } } const loadVendors = async () => { setIsLoadingVendors(true) try { const result = await searchTechVendors(vendorSearch) setVendors(result.data || []) } catch (err) { toast.error("벤더 로드 실패") } finally { setIsLoadingVendors(false) } } const loadContacts = async () => { setIsLoadingContacts(true) try { const vendorIds = Array.from(selectedVendors) const result = await getTechVendorsContactsForMapping(vendorIds) if (result.data) { const contacts: Record = {} Object.entries(result.data).forEach(([vendorIdStr, vendorData]) => { const vendorId = parseInt(vendorIdStr) contacts[vendorId] = vendorData.contacts }) setVendorContacts(contacts) } } catch (err) { toast.error("담당자 로드 실패") } finally { setIsLoadingContacts(false) } } const handleItemTypeChange = (type: 'ship' | 'top' | 'hull') => { setSelectedItemType(type) setSelectedItems(new Set()) // 타입 변경 시 선택 초기화 setItemSearch("") // 검색어도 초기화 } const handleItemSelect = (itemId: number) => { setSelectedItems(prev => { const newSet = new Set(prev) if (newSet.has(itemId)) { newSet.delete(itemId) } else { newSet.add(itemId) } return newSet }) } const handleVendorSelect = (vendorId: number) => { setSelectedVendors(prev => { const newSet = new Set(prev) if (newSet.has(vendorId)) { newSet.delete(vendorId) // 벤더 선택 해제 시 해당 벤더의 담당자 선택도 초기화 setSelectedContacts(prev => { const newContacts = { ...prev } delete newContacts[vendorId] return newContacts }) } else { newSet.add(vendorId) } return newSet }) } const handleContactSelect = (vendorId: number, contactId: number) => { setSelectedContacts(prev => { const vendorContacts = prev[vendorId] || new Set() const newSet = new Set(vendorContacts) if (newSet.has(contactId)) { newSet.delete(contactId) } else { newSet.add(contactId) } return { ...prev, [vendorId]: newSet } }) } const toggleVendorExpand = (vendorId: number) => { setExpandedVendors(prev => { const newSet = new Set(prev) if (newSet.has(vendorId)) { newSet.delete(vendorId) } else { newSet.add(vendorId) } return newSet }) } const handleSubmit = async () => { // 검증 if (selectedItems.size === 0) { toast.error("최소 1개 이상의 아이템을 선택해주세요.") return } if (selectedVendors.size === 0) { toast.error("최소 1개 이상의 벤더를 선택해주세요.") return } const hasSelectedContacts = Object.values(selectedContacts).some(contacts => contacts.size > 0) if (!hasSelectedContacts) { toast.error("최소 1개 이상의 담당자를 선택해주세요.") return } setIsSubmitting(true) try { // 선택된 아이템 변환 const itemType = selectedItemType === 'ship' ? 'SHIP' : selectedItemType === 'top' ? 'TOP' : 'HULL' const itemsToAdd: { id: number; type: 'SHIP' | 'TOP' | 'HULL' }[] = [] selectedItems.forEach(id => itemsToAdd.push({ id, type: itemType })) // 벤더별 담당자 ID를 객체로 변환 const contactsByVendor: Record = {} Object.entries(selectedContacts).forEach(([vendorIdStr, contacts]) => { contactsByVendor[parseInt(vendorIdStr)] = Array.from(contacts) }) const result = await addContactItemMapping({ items: itemsToAdd, vendors: Array.from(selectedVendors), contactsByVendor, }) if (result.error) { toast.error(result.error) } else { toast.success("담당자-아이템 매핑이 추가되었습니다.") setOpen(false) // 초기화 resetForm() } } catch (err) { toast.error("매핑 추가 중 오류가 발생했습니다.") } finally { setIsSubmitting(false) } } const resetForm = () => { setSelectedItemType('ship') setItemSearch("") setSelectedItems(new Set()) setVendorSearch("") setSelectedVendors(new Set()) setSelectedContacts({}) setExpandedVendors(new Set()) } const getTotalSelectedContacts = () => { return Object.values(selectedContacts).reduce((sum, contacts) => sum + contacts.size, 0) } // 매핑 추가 버튼 활성화 조건 const canSubmit = selectedItems.size > 0 && selectedVendors.size > 0 && getTotalSelectedContacts() > 0 return ( {trigger || ( )} 아이템별 협력업체별 담당자 설정 아이템, 협력업체, 담당자를 선택하여 매핑을 추가합니다.
{/* Section 1: 아이템 선택 */}
{/* 아이템 타입 필터 */} handleItemTypeChange(value as 'ship' | 'top' | 'hull')}>
{/* 검색 */}
setItemSearch(e.target.value)} />
{/* 아이템 리스트 */} {isLoadingItems ? (
) : (
{items.length > 0 && items.map(item => (
handleItemSelect(item.id)} > {selectedItems.has(item.id) ? ( ) : ( )}
{item.itemCode}
{item.itemList}
{item.workType} {item.shipTypes && ` • ${item.shipTypes}`} {item.subItemList && ` • ${item.subItemList}`}
))}
)}
{/* Section 2: 벤더 선택 */}
{/* 검색 */}
setVendorSearch(e.target.value)} />
{/* 벤더 리스트 */} {isLoadingVendors ? (
) : (
{vendors.map(vendor => (
handleVendorSelect(vendor.id)} > {selectedVendors.has(vendor.id) ? ( ) : ( )}
{vendor.vendorName}
{vendor.vendorCode && (
{vendor.vendorCode}
)}
{vendor.techVendorType} • {vendor.status}
))}
)}
{/* Section 3: 벤더별 담당자 선택 */}
{isLoadingContacts ? (
) : selectedVendors.size === 0 ? (
협력업체를 먼저 선택해주세요
) : (
{Array.from(selectedVendors).map(vendorId => { const contacts = vendorContacts[vendorId] || [] const vendor = vendors.find(v => v.id === vendorId) const isExpanded = expandedVendors.has(vendorId) return (
{/* 벤더 헤더 */}
toggleVendorExpand(vendorId)} > {isExpanded ? ( ) : ( )}
{vendor?.vendorName}
담당자 {contacts.length}명 • {selectedContacts[vendorId]?.size || 0}명 선택
{/* 담당자 리스트 */} {isExpanded && (
{contacts.length === 0 ? (
등록된 담당자가 없습니다
) : ( contacts.map(contact => (
handleContactSelect(vendorId, contact.id)} > {selectedContacts[vendorId]?.has(contact.id) ? ( ) : ( )}
{contact.contactName} {contact.isPrimary && ( (주담당) )}
{contact.contactPosition}
{contact.contactEmail}
)) )}
)}
) })}
)}
) }