# 벤더 선택기 (Vendor Selector) 벤더 선택을 위한 공용 컴포넌트입니다. 다양한 형태로 벤더를 검색하고 선택할 수 있는 기능을 제공합니다. ## 기능 - PostgreSQL DB에서 벤더 실시간 검색 및 선택 - 벤더명, 벤더코드로 필터링 가능 - 상태별 필터링 지원 (ACTIVE, PENDING_REVIEW, APPROVED, INACTIVE 등) - 페이지네이션 지원 - 단일/다중 선택 모드 - 최대 선택 개수 제한 - 제외 벤더 설정 가능 - 반응형 다이얼로그 UI ## 컴포넌트 구조 ### 1. VendorSelector (기본 선택기) Popover 기반의 기본 벤더 선택기 컴포넌트 ### 2. VendorSelectorDialogSingle (단일 선택 다이얼로그) Dialog 형태의 단일 벤더 선택 컴포넌트 ### 3. VendorSelectorDialogMulti (다중 선택 다이얼로그) Dialog 형태의 다중 벤더 선택 컴포넌트 ## 사용법 ### 기본 선택기 사용법 ```tsx import { VendorSelector, VendorSearchItem } from '@/components/common/vendor' function MyComponent() { const [selectedVendors, setSelectedVendors] = useState([]) return ( { console.log('선택된 벤더들:', vendors) setSelectedVendors(vendors) }} placeholder="벤더를 검색하세요..." statusFilter="ACTIVE" // ACTIVE 상태의 벤더만 표시 /> ) } ``` ### 단일 선택 다이얼로그 사용법 ```tsx import { VendorSelectorDialogSingle, VendorSearchItem } from '@/components/common/vendor' function MyComponent() { const [selectedVendor, setSelectedVendor] = useState(null) return ( { console.log('선택된 벤더:', vendor) setSelectedVendor(vendor) }} title="협력업체 선택" description="프로젝트에 참여할 협력업체를 선택해주세요." /> ) } ``` ### 다중 선택 다이얼로그 사용법 ```tsx import { VendorSelectorDialogMulti, VendorSearchItem } from '@/components/common/vendor' function MyComponent() { const [selectedVendors, setSelectedVendors] = useState([]) return ( { console.log('선택된 벤더들:', vendors) setSelectedVendors(vendors) }} maxSelections={5} title="협력업체 선택" description="프로젝트에 참여할 협력업체들을 선택해주세요. (최대 5개)" showSelectedInTrigger={true} /> ) } ``` ## Props ### VendorSelector Props | Prop | 타입 | 필수 | 기본값 | 설명 | |------|------|------|--------|------| | `selectedVendors` | `VendorSearchItem[]` | ❌ | `[]` | 선택된 벤더들 | | `onVendorsChange` | `(vendors: VendorSearchItem[]) => void` | ❌ | - | 벤더 선택 변경 시 호출되는 콜백 | | `singleSelect` | `boolean` | ❌ | `false` | 단일 선택 모드 여부 | | `placeholder` | `string` | ❌ | `"벤더를 검색하세요..."` | 검색 입력창 placeholder | | `noValuePlaceHolder` | `string` | ❌ | `"벤더를 검색해주세요"` | 선택된 벤더가 없을 때 표시되는 텍스트 | | `disabled` | `boolean` | ❌ | `false` | 비활성화 여부 | | `className` | `string` | ❌ | - | 추가 CSS 클래스 | | `closeOnSelect` | `boolean` | ❌ | `true` | 선택 후 자동 닫기 여부 | | `excludeVendorIds` | `Set` | ❌ | - | 제외할 벤더 ID들 | | `showInitialData` | `boolean` | ❌ | `true` | 초기 데이터 표시 여부 | | `maxSelections` | `number` | ❌ | - | 최대 선택 가능한 벤더 개수 | | `statusFilter` | `string` | ❌ | - | 벤더 상태 필터 | ### VendorSelectorDialogSingle Props | Prop | 타입 | 필수 | 기본값 | 설명 | |------|------|------|--------|------| | `triggerLabel` | `string` | ❌ | `"벤더 선택"` | 트리거 버튼 텍스트 | | `selectedVendor` | `VendorSearchItem \| null` | ❌ | `null` | 선택된 벤더 | | `onVendorSelect` | `(vendor: VendorSearchItem \| null) => void` | ❌ | - | 벤더 선택 완료 시 호출되는 콜백 | | `placeholder` | `string` | ❌ | `"벤더를 검색하세요..."` | 검색 입력창 placeholder | | `title` | `string` | ❌ | `"벤더 선택"` | Dialog 제목 | | `description` | `string` | ❌ | `"원하는 벤더를 검색하고 선택해주세요."` | Dialog 설명 | | `disabled` | `boolean` | ❌ | `false` | 트리거 버튼 비활성화 여부 | | `triggerVariant` | `ButtonVariant` | ❌ | `"outline"` | 트리거 버튼 variant | | `excludeVendorIds` | `Set` | ❌ | - | 제외할 벤더 ID들 | | `showInitialData` | `boolean` | ❌ | `true` | 초기 데이터 표시 여부 | | `statusFilter` | `string` | ❌ | - | 벤더 상태 필터 | ### VendorSelectorDialogMulti Props VendorSelectorDialogSingle과 유사하지만 다음이 추가됩니다: | Prop | 타입 | 필수 | 기본값 | 설명 | |------|------|------|--------|------| | `selectedVendors` | `VendorSearchItem[]` | ❌ | `[]` | 선택된 벤더들 | | `onVendorsSelect` | `(vendors: VendorSearchItem[]) => void` | ❌ | - | 벤더 선택 완료 시 호출되는 콜백 | | `maxSelections` | `number` | ❌ | - | 최대 선택 가능한 벤더 개수 | | `showSelectedInTrigger` | `boolean` | ❌ | `true` | 트리거 버튼에서 선택된 벤더들을 표시할지 여부 | ## 타입 ### VendorSearchItem ```tsx interface VendorSearchItem { id: number // 벤더 ID vendorName: string // 벤더명 vendorCode: string | null // 벤더코드 (없을 수 있음) status: string // 벤더 상태 displayText: string // 표시용 텍스트 (vendorName + vendorCode) } ``` ### VendorSearchOptions ```tsx interface VendorSearchOptions { searchTerm?: string // 검색어 statusFilter?: string // 상태 필터 limit?: number // 조회 제한 수 offset?: number // 조회 시작 위치 sortBy?: 'vendorName' | 'vendorCode' | 'status' // 정렬 기준 sortOrder?: 'asc' | 'desc' // 정렬 순서 } ``` ### VendorPagination ```tsx interface VendorPagination { page: number // 현재 페이지 perPage: number // 페이지당 항목 수 total: number // 전체 항목 수 pageCount: number // 전체 페이지 수 hasNextPage: boolean // 다음 페이지 존재 여부 hasPrevPage: boolean // 이전 페이지 존재 여부 } ``` ## 데이터 소스 **PostgreSQL DB의 `vendors` 테이블** - **필드**: - id (벤더 ID) - vendorName (벤더명) - vendorCode (벤더코드) - status (벤더 상태) ## 동작 방식 1. **서버 액션 호출**: Next.js 서버 액션을 통한 안전한 데이터 페칭 2. **PostgreSQL 조회**: Drizzle ORM을 사용하여 vendors 테이블에서 조회 3. **검색 최적화**: ILIKE를 사용한 대소문자 무관 검색 4. **페이지네이션**: 대량 데이터 처리를 위한 페이지네이션 지원 5. **상태 필터링**: 벤더 상태별 필터링 지원 ## 주의사항 - PostgreSQL DB 연결이 필요합니다 - vendors 스키마의 vendors 테이블에 데이터가 있어야 합니다 - 검색은 벤더명과 벤더코드에 대해서만 수행됩니다 - 상태 값은 테이블에 정의된 값을 사용해야 합니다