From 98e86ada15b2a867374188c79f78f5578018a911 Mon Sep 17 00:00:00 2001 From: joonhoekim <26rote@gmail.com> Date: Fri, 7 Nov 2025 09:40:41 +0900 Subject: (김준회) 공통 컴포넌트 이해를 위한 문서 추가 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/common/discipline/README.md | 96 ++++++++ components/common/selectors/nation/README.md | 183 ++++++++++++++ .../common/selectors/purchase-group-code/README.md | 274 +++++++++++++++++++++ components/common/ship-type/README.md | 123 +++++++++ components/common/vendor/README.md | 211 ++++++++++++++++ 5 files changed, 887 insertions(+) create mode 100644 components/common/discipline/README.md create mode 100644 components/common/selectors/nation/README.md create mode 100644 components/common/selectors/purchase-group-code/README.md create mode 100644 components/common/ship-type/README.md create mode 100644 components/common/vendor/README.md (limited to 'components') diff --git a/components/common/discipline/README.md b/components/common/discipline/README.md new file mode 100644 index 00000000..7e95dd4e --- /dev/null +++ b/components/common/discipline/README.md @@ -0,0 +1,96 @@ +# 설계공종코드 선택기 (Engineering Discipline Selector) + +내부 PostgreSQL DB에서 설계공종코드(CD_CLF='PLJP43')를 조회하는 공용 컴포넌트입니다. + +## 기능 + +- 내부 PostgreSQL DB에서 설계공종코드 실시간 검색 +- 코드와 이름으로 필터링 가능 +- 페이지네이션 지원 +- 반응형 다이얼로그 UI +- 서버 액션을 활용한 최적화된 성능 + +## 사용법 + +### 기본 사용법 + +```tsx +import { EngineeringDisciplineSelector, DisciplineCode } from '@/components/common/discipline' + +function MyComponent() { + const [selectedDiscipline, setSelectedDiscipline] = useState() + + return ( + { + console.log('선택된 설계공종:', discipline) + setSelectedDiscipline(discipline) + }} + /> + ) +} +``` + +### 커스터마이징 + +```tsx + +``` + +## Props + +| Prop | 타입 | 필수 | 기본값 | 설명 | +|------|------|------|--------|------| +| `selectedDiscipline` | `DisciplineCode` | ❌ | - | 선택된 설계공종 | +| `onDisciplineSelect` | `(discipline: DisciplineCode) => void` | ✅ | - | 설계공종 선택 시 호출되는 콜백 | +| `disabled` | `boolean` | ❌ | `false` | 비활성화 여부 | +| `placeholder` | `string` | ❌ | `"설계공종을 선택하세요"` | 플레이스홀더 텍스트 | +| `className` | `string` | ❌ | - | 추가 CSS 클래스 | +| `searchOptions` | `Partial` | ❌ | `{ limit: 100 }` | 검색 옵션 | + +## 타입 + +### DisciplineCode +```tsx +interface DisciplineCode { + CD: string // 설계공종코드 + USR_DF_CHAR_18: string // 설계공종명 +} +``` + +### DisciplineSearchOptions +```tsx +interface DisciplineSearchOptions { + searchTerm?: string // 검색어 + limit?: number // 조회 제한 수 +} +``` + +## 데이터 소스 + +**내부 PostgreSQL DB** +- **테이블**: cmctbCd (NONSAP 스키마) +- **조건**: CD_CLF = 'PLJP43' +- **필드**: + - CD (설계공종코드) + - USR_DF_CHAR_18 (설계공종명) + +## 동작 방식 + +1. **서버 액션 호출**: Next.js 서버 액션을 통한 안전한 데이터 페칭 +2. **PostgreSQL 조회**: Drizzle ORM을 사용하여 cmctbCd 테이블에서 조회 +3. **검색 최적화**: ILIKE를 사용한 대소문자 무관 검색 +4. **성능 최적화**: useTransition을 활용한 논블로킹 UI 업데이트 + +## 주의사항 + +- 내부 PostgreSQL DB 연결이 필요합니다 +- NONSAP 스키마의 cmctbCd 테이블에 데이터가 있어야 합니다 diff --git a/components/common/selectors/nation/README.md b/components/common/selectors/nation/README.md new file mode 100644 index 00000000..f16c3a2f --- /dev/null +++ b/components/common/selectors/nation/README.md @@ -0,0 +1,183 @@ +# 국가 선택기 (Nation Selector) + +국가 코드를 선택할 수 있는 컴포넌트들입니다. CMCTB_CD 테이블에서 CD_CLF='LE0010' 조건으로 국가 정보를 조회합니다. + +## 컴포넌트 구조 + +- `nation-service.ts`: 국가 데이터 조회 서버 액션 +- `nation-selector.tsx`: 기본 국가 선택기 (트리거 버튼 포함) +- `nation-single-selector.tsx`: 단일 선택 다이얼로그 +- `nation-multi-selector.tsx`: 다중 선택 다이얼로그 + +## 데이터 구조 + +```typescript +interface NationCode { + CD: string // 2글자 국가코드 (예: KR) + CD2: string // 3글자 국가코드 (예: KOR) + CD3: string // 3글자 숫자 국가코드 (예: 410) + CDNM: string // 한국어 국가명 (예: 대한민국) + GRP_DSC: string // 영문 국가명 (예: Korea, Republic of) +} +``` + +## 사용 예제 + +### 1. 기본 국가 선택기 + +```tsx +import { useState } from 'react' +import { NationSelector, NationCode } from '@/components/common/selectors/nation' + +function MyComponent() { + const [selectedNation, setSelectedNation] = useState() + + return ( + + ) +} +``` + +### 2. 단일 선택 다이얼로그 + +```tsx +import { useState } from 'react' +import { Button } from '@/components/ui/button' +import { NationSingleSelector, NationCode } from '@/components/common/selectors/nation' + +function MyComponent() { + const [open, setOpen] = useState(false) + const [selectedNation, setSelectedNation] = useState() + + return ( + <> + + + { + console.log('선택된 국가:', nation) + }} + onCancel={() => { + console.log('선택 취소됨') + }} + /> + + ) +} +``` + +### 3. 다중 선택 다이얼로그 + +```tsx +import { useState } from 'react' +import { Button } from '@/components/ui/button' +import { NationMultiSelector, NationCode } from '@/components/common/selectors/nation' + +function MyComponent() { + const [open, setOpen] = useState(false) + const [selectedNations, setSelectedNations] = useState([]) + + return ( + <> + + + { + console.log('선택된 국가들:', nations) + }} + onCancel={() => { + console.log('선택 취소됨') + }} + /> + + ) +} +``` + +### 4. 서버 액션 직접 사용 + +```tsx +import { getNationCodes, getNationCodeByCode } from '@/components/common/selectors/nation' + +// 모든 국가 조회 +const result = await getNationCodes() +if (result.success) { + console.log('국가 목록:', result.data) +} + +// 검색 조건으로 조회 +const searchResult = await getNationCodes({ + searchTerm: '한국', + limit: 50 +}) + +// 특정 국가 코드로 조회 +const korea = await getNationCodeByCode('KR') +console.log('대한민국:', korea) +``` + +## Props 옵션 + +### NationSelector + +- `selectedNation?: NationCode` - 선택된 국가 +- `onNationSelect: (nation: NationCode) => void` - 국가 선택 콜백 +- `disabled?: boolean` - 비활성화 여부 +- `placeholder?: string` - 플레이스홀더 텍스트 +- `className?: string` - 추가 CSS 클래스 +- `searchOptions?: Partial` - 검색 옵션 + +### NationSingleSelector + +- `open: boolean` - 다이얼로그 열림 상태 +- `onOpenChange: (open: boolean) => void` - 다이얼로그 상태 변경 콜백 +- `selectedNation?: NationCode` - 선택된 국가 +- `onNationSelect: (nation: NationCode) => void` - 국가 선택 콜백 +- `onConfirm?: (nation: NationCode | undefined) => void` - 확인 버튼 콜백 +- `onCancel?: () => void` - 취소 버튼 콜백 +- `title?: string` - 다이얼로그 제목 +- `description?: string` - 다이얼로그 설명 +- `showConfirmButtons?: boolean` - 확인/취소 버튼 표시 여부 + +### NationMultiSelector + +- `open: boolean` - 다이얼로그 열림 상태 +- `onOpenChange: (open: boolean) => void` - 다이얼로그 상태 변경 콜백 +- `selectedNations?: NationCode[]` - 선택된 국가들 +- `onNationsSelect: (nations: NationCode[]) => void` - 국가들 선택 콜백 +- `onConfirm?: (nations: NationCode[]) => void` - 확인 버튼 콜백 +- `onCancel?: () => void` - 취소 버튼 콜백 +- `title?: string` - 다이얼로그 제목 +- `description?: string` - 다이얼로그 설명 +- `maxSelection?: number` - 최대 선택 가능 개수 + +## 특징 + +- **검색 기능**: 국가코드, 국가명으로 실시간 검색 +- **페이지네이션**: 대량의 데이터를 페이지별로 표시 +- **디바운스**: 검색 성능 최적화 +- **다국어 지원**: 한국어명과 영문명 모두 표시 +- **접근성**: 키보드 네비게이션 및 스크린 리더 지원 +- **반응형**: 다양한 화면 크기에 대응 diff --git a/components/common/selectors/purchase-group-code/README.md b/components/common/selectors/purchase-group-code/README.md new file mode 100644 index 00000000..b956a296 --- /dev/null +++ b/components/common/selectors/purchase-group-code/README.md @@ -0,0 +1,274 @@ +# 구매그룹코드 선택기 (Purchase Group Code Selector) + +구매그룹코드를 선택할 수 있는 컴포넌트들입니다. Oracle DB의 CMCTB_CDNM, CMCTB_CD 테이블에서 CD_CLF='MMA070' 조건으로 구매그룹 정보를 조회하며, 선택 시 해당 사번의 사용자 정보도 함께 반환합니다. + +## 컴포넌트 구조 + +- `purchase-group-code-service.ts`: 구매그룹코드 데이터 조회 서버 액션 (Oracle DB 조회 + 사용자 정보 포함) +- `purchase-group-code-selector.tsx`: 기본 구매그룹코드 선택기 (트리거 버튼 포함) +- `purchase-group-code-single-selector.tsx`: 단일 선택 다이얼로그 +- `purchase-group-code-multi-selector.tsx`: 다중 선택 다이얼로그 + +## 데이터 구조 + +```typescript +// 기본 구매그룹코드 정보 +interface PurchaseGroupCode { + PURCHASE_GROUP_CODE: string // 구매그룹코드 + DISPLAY_NAME: string // 표시명 (이름_부서_퇴직/전배정보) + EMPLOYEE_NUMBER: string // 사번 +} + +// 사용자 정보를 포함한 구매그룹코드 +interface PurchaseGroupCodeWithUser extends PurchaseGroupCode { + user?: { + id: number + name: string + email: string + employeeNumber: string | null + userCode: string | null + } | null +} +``` + +## 사용 예제 + +### 1. 기본 구매그룹코드 선택기 + +```tsx +import { useState } from 'react' +import { PurchaseGroupCodeSelector, PurchaseGroupCodeWithUser } from '@/components/common/selectors/purchase-group-code' + +function MyComponent() { + const [selectedCode, setSelectedCode] = useState() + + const handleSelect = (code: PurchaseGroupCodeWithUser) => { + console.log('선택된 구매그룹코드:', code.PURCHASE_GROUP_CODE) + console.log('표시명:', code.DISPLAY_NAME) + console.log('사번:', code.EMPLOYEE_NUMBER) + console.log('사용자 정보:', code.user) + setSelectedCode(code) + } + + return ( + + ) +} +``` + +### 2. 단일 선택 다이얼로그 + +```tsx +import { useState } from 'react' +import { Button } from '@/components/ui/button' +import { + PurchaseGroupCodeSingleSelector, + PurchaseGroupCodeWithUser +} from '@/components/common/selectors/purchase-group-code' + +function MyComponent() { + const [open, setOpen] = useState(false) + const [selectedCode, setSelectedCode] = useState() + + return ( + <> + + + { + console.log('선택 완료:', code) + if (code?.user) { + console.log('연결된 사용자:', code.user.name) + } + }} + onCancel={() => { + console.log('선택 취소됨') + }} + /> + + ) +} +``` + +### 3. 다중 선택 다이얼로그 + +```tsx +import { useState } from 'react' +import { Button } from '@/components/ui/button' +import { + PurchaseGroupCodeMultiSelector, + PurchaseGroupCodeWithUser +} from '@/components/common/selectors/purchase-group-code' + +function MyComponent() { + const [open, setOpen] = useState(false) + const [selectedCodes, setSelectedCodes] = useState([]) + + return ( + <> + + + { + console.log('선택된 구매그룹코드들:', codes) + codes.forEach(code => { + console.log(`${code.PURCHASE_GROUP_CODE}: ${code.user?.name}`) + }) + }} + onCancel={() => { + console.log('선택 취소됨') + }} + /> + + ) +} +``` + +### 4. 서버 액션 직접 사용 + +```tsx +import { + getPurchaseGroupCodes, + getPurchaseGroupCodeWithUser, + getPurchaseGroupCodeByEmployeeNumber +} from '@/components/common/selectors/purchase-group-code' + +// 모든 구매그룹코드 조회 +const result = await getPurchaseGroupCodes() +if (result.success) { + console.log('구매그룹코드 목록:', result.data) +} + +// 검색 조건으로 조회 +const searchResult = await getPurchaseGroupCodes({ + searchTerm: '홍길동', + limit: 50 +}) + +// 특정 구매그룹코드로 조회 (사용자 정보 포함) +const codeWithUser = await getPurchaseGroupCodeWithUser('PG001') +if (codeWithUser) { + console.log('구매그룹코드:', codeWithUser.PURCHASE_GROUP_CODE) + console.log('사용자:', codeWithUser.user?.name) +} + +// 사번으로 구매그룹코드 조회 (사용자 정보 포함) +const byEmployeeNumber = await getPurchaseGroupCodeByEmployeeNumber('1234567') +if (byEmployeeNumber) { + console.log('해당 사번의 구매그룹코드:', byEmployeeNumber.PURCHASE_GROUP_CODE) +} +``` + +## Props 옵션 + +### PurchaseGroupCodeSelector + +- `selectedCode?: PurchaseGroupCodeWithUser` - 선택된 구매그룹코드 +- `onCodeSelect: (code: PurchaseGroupCodeWithUser) => void` - 구매그룹코드 선택 콜백 +- `disabled?: boolean` - 비활성화 여부 +- `placeholder?: string` - 플레이스홀더 텍스트 +- `className?: string` - 추가 CSS 클래스 +- `searchOptions?: Partial` - 검색 옵션 + +### PurchaseGroupCodeSingleSelector + +- `open: boolean` - 다이얼로그 열림 상태 +- `onOpenChange: (open: boolean) => void` - 다이얼로그 상태 변경 콜백 +- `selectedCode?: PurchaseGroupCodeWithUser` - 선택된 구매그룹코드 +- `onCodeSelect: (code: PurchaseGroupCodeWithUser) => void` - 구매그룹코드 선택 콜백 +- `onConfirm?: (code: PurchaseGroupCodeWithUser | undefined) => void` - 확인 버튼 콜백 +- `onCancel?: () => void` - 취소 버튼 콜백 +- `title?: string` - 다이얼로그 제목 +- `description?: string` - 다이얼로그 설명 +- `showConfirmButtons?: boolean` - 확인/취소 버튼 표시 여부 + +### PurchaseGroupCodeMultiSelector + +- `open: boolean` - 다이얼로그 열림 상태 +- `onOpenChange: (open: boolean) => void` - 다이얼로그 상태 변경 콜백 +- `selectedCodes?: PurchaseGroupCodeWithUser[]` - 선택된 구매그룹코드들 +- `onCodesSelect: (codes: PurchaseGroupCodeWithUser[]) => void` - 구매그룹코드들 선택 콜백 +- `onConfirm?: (codes: PurchaseGroupCodeWithUser[]) => void` - 확인 버튼 콜백 +- `onCancel?: () => void` - 취소 버튼 콜백 +- `title?: string` - 다이얼로그 제목 +- `description?: string` - 다이얼로그 설명 +- `maxSelection?: number` - 최대 선택 가능 개수 + +## 특징 + +- **Oracle DB 연동**: Oracle NonSAP 데이터베이스에서 실시간 조회 +- **폴백 테스트 데이터**: Oracle DB 연결 실패 시 자동으로 테스트 데이터 사용 +- **사용자 정보 통합**: 선택 시 사번으로 연결된 사용자 정보 자동 조회 +- **검색 기능**: 구매그룹코드, 이름, 사번으로 실시간 검색 +- **페이지네이션**: 대량의 데이터를 페이지별로 표시 +- **디바운스**: 검색 성능 최적화 +- **다중 선택 지원**: 여러 구매그룹코드 동시 선택 가능 +- **접근성**: 키보드 네비게이션 및 스크린 리더 지원 +- **반응형**: 다양한 화면 크기에 대응 + +## 데이터 소스 + +구매그룹코드는 Oracle DB의 다음 테이블에서 조회됩니다: + +```sql +SELECT CD.CD AS PURCHASE_GROUP_CODE, + NM.CDNM AS DISPLAY_NAME, + CD.USR_DF_CHAR_9 AS EMPLOYEE_NUMBER +FROM CMCTB_CDNM NM +JOIN CMCTB_CD CD ON NM.CD_CLF = CD.CD_CLF + AND NM.CD = CD.CD + AND NM.CD2 = CD.CD3 +WHERE NM.CD_CLF = 'MMA070' + AND CD.USR_DF_CHAR_9 IS NOT NULL +``` + +사용자 정보는 PostgreSQL DB의 `users` 테이블에서 `employeeNumber`로 조회됩니다. + +## 주의사항 + +1. **Oracle DB 연결 필요**: 이 컴포넌트는 Oracle DB 연결이 필요합니다. 환경 변수가 올바르게 설정되어 있는지 확인하세요. +2. **폴백 테스트 데이터**: Oracle DB 연결 실패 시 하드코딩된 10개의 테스트 데이터가 자동으로 사용됩니다. 테스트 환경에서 유용합니다. +3. **사용자 정보 매칭**: 사번으로 사용자 정보를 조회하므로, 사용자 테이블에 해당 사번이 존재해야 합니다. +4. **성능**: 검색 시 Oracle DB를 직접 조회하므로, 대량 데이터 환경에서는 인덱스 설정이 중요합니다. + +## 폴백 테스트 데이터 + +Oracle DB 연결이 불가능한 테스트 환경에서는 다음 10개의 구매그룹코드가 자동으로 제공됩니다: + +- `12L` - 김철수_구매팀_재직 +- `32F` - 이영희_자재팀_재직 +- `45A` - 박민수_조달팀_재직 +- `67K` - 정수진_구매1팀_재직 +- `89D` - 최동욱_구매2팀_재직 +- `11B` - 강미라_자재관리팀_재직 +- `23G` - 윤성호_구매기획팀_재직 +- `56H` - 임지훈_조달지원팀_재직 +- `78M` - 한소희_구매운영팀_재직 +- `90C` - 오준석_전략구매팀_재직 + +## 관련 서비스 + +- 구매그룹코드 동기화: `lib/nonsap-sync/purchase-group-code/purchase-group-code-sync.ts` +- 사용자 서비스: `lib/users/service.ts` diff --git a/components/common/ship-type/README.md b/components/common/ship-type/README.md new file mode 100644 index 00000000..4b50773d --- /dev/null +++ b/components/common/ship-type/README.md @@ -0,0 +1,123 @@ +# 선종 선택기 (Ship Type Selector) + +선종 정보를 검색하고 선택할 수 있는 컴포넌트입니다. + +## 컴포넌트 구조 + +```text +components/common/ship-type/ +├── ship-type-service.ts # 서버 액션 (선종 데이터 조회) +├── ship-type-selector.tsx # 선종 선택기 컴포넌트 +├── index.ts # 모듈 export +└── README.md # 문서 +``` + +## 데이터 소스 + +**내부 PostgreSQL DB - `cmctb_cdnm` 테이블** + +- 조건: `CD_CLF = 'PSA330' AND DEL_YN = 'N'` +- 선종코드: `CD` 컬럼 +- 선종명: `CDNM` 컬럼 + +## 기본 사용법 + +```tsx +import { ShipTypeSelector, ShipTypeItem } from '@/components/common/ship-type' + +function MyComponent() { + const [selectedShipType, setSelectedShipType] = useState() + + const handleShipTypeSelect = (shipType: ShipTypeItem) => { + setSelectedShipType(shipType) + console.log('선택된 선종:', shipType.CD, shipType.CDNM) + } + + return ( + + ) +} +``` + +## Props + +### ShipTypeSelector + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `selectedShipType` | `ShipTypeItem \| undefined` | - | 현재 선택된 선종 | +| `onShipTypeSelect` | `(shipType: ShipTypeItem) => void` | - | 선종 선택 시 호출되는 콜백 함수 | +| `disabled` | `boolean` | `false` | 선택기 비활성화 여부 | +| `placeholder` | `string` | `"선종을 선택하세요"` | 선택되지 않았을 때 표시할 텍스트 | +| `className` | `string` | - | 추가 CSS 클래스 | + +## 타입 정의 + +### ShipTypeItem + +```tsx +interface ShipTypeItem { + CD: string // 선종코드 + CDNM: string // 선종명 + displayText: string // 표시용 텍스트 (CD + " - " + CDNM) +} +``` + +### ShipTypeSearchOptions + +```tsx +interface ShipTypeSearchOptions { + searchTerm?: string // 검색어 (선종코드 또는 선종명) + limit?: number // 조회 결과 제한 (기본값: 100) +} +``` + +## 서버 액션 + +### getShipTypes(options) + +선종 목록을 조회합니다. + +```tsx +const result = await getShipTypes({ + searchTerm: "CONT", + limit: 50 +}) + +if (result.success) { + console.log('선종 목록:', result.data) +} else { + console.error('오류:', result.error) +} +``` + +### getShipTypeByCode(code) + +특정 선종코드로 선종 정보를 조회합니다. + +```tsx +const shipType = await getShipTypeByCode("CONT") +if (shipType) { + console.log('선종 정보:', shipType.CD, shipType.CDNM) +} +``` + +## 특징 + +- ✅ **즉시 검색**: 검색어 입력 시 실시간으로 결과 필터링 +- ✅ **디바운싱**: 300ms 디바운스로 API 호출 최적화 +- ✅ **서버 액션**: `useTransition`을 사용한 논블로킹 서버 호출 +- ✅ **페이지네이션**: 대량 데이터 지원 (기본 페이지당 10개) +- ✅ **검색 최적화**: 선종코드와 선종명 모두 검색 가능 +- ✅ **사용자 친화적**: Dialog 기반 선택 UI + +## 주의사항 + +- 선종 데이터는 약 50개 정도로 페이지네이션 없이도 충분히 처리 가능 +- 검색은 선종코드(`CD`)와 선종명(`CDNM`) 모두에서 수행됩니다 +- 대소문자 구분 없이 검색 가능 (ILIKE 사용) +- `DEL_YN = 'N'` 조건으로 삭제되지 않은 선종만 조회 diff --git a/components/common/vendor/README.md b/components/common/vendor/README.md new file mode 100644 index 00000000..7c9e54d9 --- /dev/null +++ b/components/common/vendor/README.md @@ -0,0 +1,211 @@ +# 벤더 선택기 (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 테이블에 데이터가 있어야 합니다 +- 검색은 벤더명과 벤더코드에 대해서만 수행됩니다 +- 상태 값은 테이블에 정의된 값을 사용해야 합니다 -- cgit v1.2.3