"use client" import React, { useState, useCallback } from "react" import { Button } from "@/components/ui/button" import { Badge } from "@/components/ui/badge" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog" import { VendorSelector } from "./vendor-selector" import { VendorSearchItem } from "./vendor-service" import { X } from "lucide-react" /** * 벤더 다중 선택 Dialog 컴포넌트 * * @description * - VendorSelector를 Dialog로 래핑한 다중 선택 컴포넌트 * - 버튼 클릭 시 Dialog가 열리고, 여러 벤더를 선택한 후 확인 버튼으로 완료 * - 최대 선택 개수 제한 가능 * * @VendorSearchItem_Structure * 상태에서 관리되는 벤더 객체의 형태: * ```typescript * interface VendorSearchItem { * id: number; // 벤더 ID * vendorName: string; // 벤더명 * vendorCode: string | null; // 벤더코드 (없을 수 있음) * status: string; // 벤더 상태 (ACTIVE, PENDING_REVIEW 등) * displayText: string; // 표시용 텍스트 (vendorName + vendorCode) * } * ``` * * @state * - open: Dialog 열림/닫힘 상태 * - selectedVendors: 현재 선택된 벤더들 (배열) * - tempSelectedVendors: Dialog 내에서 임시로 선택된 벤더들 (확인 버튼 클릭 전까지) * * @callback * - onVendorsSelect: 벤더 선택 완료 시 호출되는 콜백 * - 매개변수: VendorSearchItem[] * - 선택된 벤더들의 배열 (빈 배열일 수도 있음) * * @usage * ```tsx * { * setSelectedVendors(vendors); * console.log('선택된 벤더들:', vendors); * }} * maxSelections={5} * placeholder="벤더를 검색하세요..." * /> * ``` */ interface VendorSelectorDialogMultiProps { /** Dialog를 여는 트리거 버튼 텍스트 */ triggerLabel?: string /** 현재 선택된 벤더들 */ selectedVendors?: VendorSearchItem[] /** 벤더 선택 완료 시 호출되는 콜백 */ onVendorsSelect?: (vendors: VendorSearchItem[]) => void /** 최대 선택 가능한 벤더 개수 */ maxSelections?: number /** 검색 입력창 placeholder */ placeholder?: string /** Dialog 제목 */ title?: string /** Dialog 설명 */ description?: string /** 트리거 버튼 비활성화 여부 */ disabled?: boolean /** 트리거 버튼 variant */ triggerVariant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link" /** 제외할 벤더 ID들 */ excludeVendorIds?: Set /** 초기 데이터 표시 여부 */ showInitialData?: boolean /** 트리거 버튼에서 선택된 벤더들을 표시할지 여부 */ showSelectedInTrigger?: boolean /** 벤더 상태 필터 */ statusFilter?: string } export function VendorSelectorDialogMulti({ triggerLabel = "벤더 선택", selectedVendors = [], onVendorsSelect, maxSelections, placeholder = "벤더를 검색하세요...", title = "벤더 선택 (다중)", description, disabled = false, triggerVariant = "outline", excludeVendorIds, showInitialData = true, showSelectedInTrigger = true, statusFilter }: VendorSelectorDialogMultiProps) { // Dialog 열림/닫힘 상태 const [open, setOpen] = useState(false) // Dialog 내에서 임시로 선택된 벤더들 (확인 버튼 클릭 전까지) const [tempSelectedVendors, setTempSelectedVendors] = useState([]) // Dialog 설명 동적 생성 const dialogDescription = description || (maxSelections ? `원하는 벤더를 검색하고 선택해주세요. (최대 ${maxSelections}개)` : "원하는 벤더를 검색하고 선택해주세요." ) // Dialog 열림 시 현재 선택된 벤더들로 임시 선택 초기화 const handleOpenChange = useCallback((newOpen: boolean) => { setOpen(newOpen) if (newOpen) { setTempSelectedVendors([...selectedVendors]) } }, [selectedVendors]) // 벤더 선택 처리 (Dialog 내에서) const handleVendorsChange = useCallback((vendors: VendorSearchItem[]) => { setTempSelectedVendors(vendors) }, []) // 확인 버튼 클릭 시 선택 완료 const handleConfirm = useCallback(() => { onVendorsSelect?.(tempSelectedVendors) setOpen(false) }, [tempSelectedVendors, onVendorsSelect]) // 취소 버튼 클릭 시 const handleCancel = useCallback(() => { setTempSelectedVendors([...selectedVendors]) setOpen(false) }, [selectedVendors]) // 전체 선택 해제 const handleClearAll = useCallback(() => { setTempSelectedVendors([]) }, []) // 개별 벤더 제거 (트리거 버튼에서) const handleRemoveVendor = useCallback((vendorToRemove: VendorSearchItem, e: React.MouseEvent) => { e.preventDefault() e.stopPropagation() const newVendors = selectedVendors.filter( (vendor) => vendor.id !== vendorToRemove.id ) onVendorsSelect?.(newVendors) }, [selectedVendors, onVendorsSelect]) // 벤더 상태별 색상 const getStatusColor = (status: string) => { switch (status) { case 'ACTIVE': return 'bg-green-100 text-green-800' case 'APPROVED': return 'bg-blue-100 text-blue-800' case 'PENDING_REVIEW': return 'bg-yellow-100 text-yellow-800' case 'INACTIVE': return 'bg-gray-100 text-gray-800' default: return 'bg-gray-100 text-gray-800' } } // 트리거 버튼 렌더링 const renderTriggerContent = () => { if (selectedVendors.length === 0) { return triggerLabel } if (!showSelectedInTrigger) { return `${triggerLabel} (${selectedVendors.length}개)` } return (
{triggerLabel}: {selectedVendors.slice(0, 2).map((vendor) => ( {vendor.vendorName} {vendor.status} {!disabled && ( )} ))} {selectedVendors.length > 2 && ( +{selectedVendors.length - 2}개 )}
) } return ( {title} {dialogDescription}
{/* 선택된 벤더 개수 표시 */}
{maxSelections ? ( `선택됨: ${tempSelectedVendors.length}/${maxSelections}개` ) : ( `선택됨: ${tempSelectedVendors.length}개` )}
{tempSelectedVendors.length > 0 && ( )}
) } /** * 사용 예시: * * ```tsx * const [selectedVendors, setSelectedVendors] = useState([]); * * return ( * { * setSelectedVendors(vendors); * console.log('선택된 벤더들:', vendors.map(v => ({ * id: v.id, * name: v.vendorName, * code: v.vendorCode, * status: v.status * }))); * }} * maxSelections={5} * title="협력업체 선택" * description="프로젝트에 참여할 협력업체들을 선택해주세요." * showSelectedInTrigger={true} * statusFilter="ACTIVE" * /> * ); * ``` * * @advanced_usage * ```tsx * // 제외할 벤더가 있는 경우 * const excludedVendorIds = new Set([1, 2, 3]); * * * ``` */