# 입력폼 공통 컴포넌트화 구현 계획서

**작성일**: 2026-01-21
**작성자**: Claude Code
**상태**: ✅ Phase 1-3 VendorDetail 적용 완료
**최종 수정**: 2026-01-21

---

## 1. 개요

### 1.1 목적
- 숫자 입력필드의 선행 0(leading zero) 문제 해결
- 금액/수량 입력 시 천단위 콤마 및 포맷팅 일관성 확보
- 전화번호, 사업자번호, 주민번호 등 포맷팅이 필요한 입력필드 공통화
- 소수점 입력이 필요한 필드 지원 (비율, 환율 등)

### 1.2 현재 문제점
| 문제 | 현상 | 영향 범위 |
|------|------|----------|
| 숫자 입력 leading zero | `01`, `001` 등 표시 | 전체 숫자 입력 |
| 금액 포맷팅 불일치 | 콤마 처리 제각각 | **147개 파일** |
| 전화번호 포맷팅 없음 | `01012341234` 그대로 표시 | 거래처, 직원 관리 |
| 사업자번호 포맷팅 없음 | `1234567890` 그대로 표시 | 거래처 관리 |
| Number 타입 일관성 | string/number 혼용 | 타입 에러 가능성 |

---

## 2. 구현 우선순위

### 🔴 Phase 1: 핵심 숫자 입력 (최우선)
| 순서 | 컴포넌트 | 용도 | 영향 범위 |
|------|---------|------|----------|
| 1 | **NumberInput** | 범용 숫자 입력 (leading zero 해결) | 전체 |
| 2 | **CurrencyInput** | 금액 입력 (₩, 천단위 콤마) | 147개 파일 |
| 3 | **QuantityInput** | 수량 입력 (정수, 최소값 0) | 재고/주문 |

### 🟠 Phase 2: 포맷팅 입력 (완료)
| 순서 | 컴포넌트 | 용도 | 상태 |
|------|---------|------|------|
| 4 | **PhoneInput** | 전화번호 자동 하이픈 | ✅ 완료 |
| 5 | **BusinessNumberInput** | 사업자번호 포맷팅 | ✅ 완료 |
| 6 | **PersonalNumberInput** | 주민번호 포맷팅/마스킹 | ✅ 완료 |

### 🟢 Phase 3: 통합 및 확장
| 순서 | 작업 | 설명 |
|------|------|------|
| 7 | ui/index.ts export | 새 컴포넌트 내보내기 |
| 8 | FormField 확장 | 새 타입 지원 추가 |
| 9 | 실사용 적용 테스트 | VendorDetail 등 |

---

## 3. 생성/수정 파일 목록

### 3.1 새로 생성한 파일

```
src/
├── lib/
│   └── formatters.ts              ✅ 완료
├── components/
│   └── ui/
│       ├── phone-input.tsx        ✅ 완료
│       ├── business-number-input.tsx  ✅ 완료
│       ├── personal-number-input.tsx  ✅ 완료
│       ├── number-input.tsx       ✅ 완료
│       ├── currency-input.tsx     ✅ 완료
│       └── quantity-input.tsx     ✅ 완료
```

### 3.2 수정한 파일

| 파일 | 수정 내용 | 상태 |
|------|----------|------|
| `src/components/molecules/FormField.tsx` | 새 타입 지원 추가 (phone, businessNumber, personalNumber, currency, quantity) | ✅ 완료 |

---

## 4. 컴포넌트 상세 설계

### 4.1 NumberInput (범용 숫자 입력)

```typescript
interface NumberInputProps {
  value: number | string | undefined;
  onChange: (value: number | undefined) => void;

  // 포맷 옵션
  allowDecimal?: boolean;      // 소수점 허용 (기본: false)
  decimalPlaces?: number;      // 소수점 자릿수 제한
  allowNegative?: boolean;     // 음수 허용 (기본: false)
  useComma?: boolean;          // 천단위 콤마 (기본: false)

  // 범위 제한
  min?: number;
  max?: number;

  // 표시 옵션
  suffix?: string;             // 접미사 (원, 개, % 등)
  allowEmpty?: boolean;        // 빈 값 허용 (기본: true)
}
```

**사용 예시**:
```tsx
// 기본 정수 입력
<NumberInput value={qty} onChange={setQty} />

// 소수점 2자리 (비율, 환율)
<NumberInput value={rate} onChange={setRate} allowDecimal decimalPlaces={2} />

// 퍼센트 입력 (0-100 제한)
<NumberInput value={percent} onChange={setPercent} min={0} max={100} suffix="%" />

// 음수 허용 (재고 조정)
<NumberInput value={adjust} onChange={setAdjust} allowNegative />
```

### 4.2 CurrencyInput (금액 입력)

```typescript
interface CurrencyInputProps {
  value: number | undefined;
  onChange: (value: number | undefined) => void;

  currency?: '₩' | '$' | '¥';  // 통화 기호 (기본: ₩)
  showCurrency?: boolean;       // 통화 기호 표시 (기본: true)
  allowNegative?: boolean;      // 음수 허용 (기본: false)
}
```

**특징**:
- 항상 천단위 콤마 표시
- 정수만 허용 (원 단위)
- 포커스 해제 시 통화 기호 표시

### 4.3 QuantityInput (수량 입력)

```typescript
interface QuantityInputProps {
  value: number | undefined;
  onChange: (value: number | undefined) => void;

  min?: number;                 // 최소값 (기본: 0)
  max?: number;                 // 최대값
  step?: number;                // 증감 단위 (기본: 1)
  showButtons?: boolean;        // +/- 버튼 표시
  suffix?: string;              // 단위 (개, EA, 박스 등)
}
```

**특징**:
- 정수만 허용
- 기본 최소값 0
- 선택적 +/- 버튼

### 4.4 PhoneInput ✅ 완료

```typescript
interface PhoneInputProps {
  value: string;
  onChange: (value: string) => void;  // 숫자만 반환
  error?: boolean;
}
```

### 4.5 BusinessNumberInput ✅ 완료

```typescript
interface BusinessNumberInputProps {
  value: string;
  onChange: (value: string) => void;
  showValidation?: boolean;  // 유효성 검사 아이콘
  error?: boolean;
}
```

### 4.6 PersonalNumberInput ✅ 완료

```typescript
interface PersonalNumberInputProps {
  value: string;
  onChange: (value: string) => void;
  maskBack?: boolean;  // 뒷자리 마스킹
  error?: boolean;
}
```

---

## 5. 검수 계획서

### 5.1 NumberInput 테스트

| 테스트 항목 | 입력 | 기대 결과 |
|------------|------|----------|
| Leading zero 제거 | `01` | 표시: `1`, 값: `1` |
| Leading zero 제거 | `007` | 표시: `7`, 값: `7` |
| 소수점 (허용시) | `3.14` | 표시: `3.14`, 값: `3.14` |
| 소수점 자릿수 제한 | `3.14159` (2자리) | 표시: `3.14`, 값: `3.14` |
| 음수 (허용시) | `-100` | 표시: `-100`, 값: `-100` |
| 콤마 표시 | `1000000` | 표시: `1,000,000`, 값: `1000000` |
| 범위 제한 (max:100) | `150` | 값: `100` (제한) |
| 빈 값 | `` | 값: `undefined` |
| 문자 입력 차단 | `abc` | 입력 안됨 |

### 5.2 CurrencyInput 테스트

| 테스트 항목 | 입력 | 기대 결과 |
|------------|------|----------|
| 기본 입력 | `50000` | 표시: `50,000`, 값: `50000` |
| 통화 기호 | `50000` (blur) | 표시: `₩50,000` |
| 소수점 차단 | `100.5` | 표시: `100`, 값: `100` |
| 대용량 | `1000000000` | 표시: `1,000,000,000` |

### 5.3 QuantityInput 테스트

| 테스트 항목 | 입력 | 기대 결과 |
|------------|------|----------|
| 기본 입력 | `10` | 표시: `10`, 값: `10` |
| 음수 차단 | `-5` | 값: `0` (최소값) |
| 소수점 차단 | `10.5` | 표시: `10`, 값: `10` |
| +/- 버튼 | 클릭 | 1씩 증감 |

### 5.4 실사용 테스트 페이지

| 페이지 | 경로 | 테스트 항목 |
|--------|------|------------|
| 거래처 관리 | `/accounting/vendor-management` | 전화번호, 사업자번호 |
| 직원 관리 | `/hr/employee-management` | 전화번호, 주민번호 |
| 견적 등록 | `/quotes` | 수량, 금액 |
| 주문 관리 | `/sales/order-management-sales` | 수량, 금액 |
| 재고 관리 | `/material/stock-status` | 수량 |

---

## 6. 완료 체크리스트

### Phase 1: 유틸리티 및 기본 컴포넌트
- [x] formatters.ts 유틸리티 함수 생성
- [x] PhoneInput 컴포넌트 생성
- [x] BusinessNumberInput 컴포넌트 생성
- [x] PersonalNumberInput 컴포넌트 생성
- [x] NumberInput 컴포넌트 생성
- [x] CurrencyInput 컴포넌트 생성
- [x] QuantityInput 컴포넌트 생성

### Phase 2: 통합
- [x] ui/index.ts export 추가 (개별 import 방식 사용)
- [x] FormField 타입 확장

### Phase 3: 테스트 및 적용
- [ ] 개별 컴포넌트 동작 테스트
- [x] VendorDetail 적용 완료
  - [x] PhoneInput: phone, mobile, fax, managerPhone
  - [x] BusinessNumberInput: businessNumber (유효성 검사 포함)
  - [x] CurrencyInput: outstandingAmount, unpaidAmount
  - [x] NumberInput: overdueDays
- [ ] 문서 최종 업데이트

---

## 7. 롤백 계획

문제 발생 시:
1. 새 컴포넌트 import 제거
2. 기존 `<Input type="number">` 컴포넌트로 복원
3. FormField 타입 변경 롤백

---

## 8. 참고사항

### 기존 컴포넌트 위치
- Input: `src/components/ui/input.tsx`
- FormField: `src/components/molecules/FormField.tsx`

### 생성된 파일
| 파일 | 경로 |
|------|------|
| formatters | `src/lib/formatters.ts` |
| PhoneInput | `src/components/ui/phone-input.tsx` |
| BusinessNumberInput | `src/components/ui/business-number-input.tsx` |
| PersonalNumberInput | `src/components/ui/personal-number-input.tsx` |
| NumberInput | `src/components/ui/number-input.tsx` |
| CurrencyInput | `src/components/ui/currency-input.tsx` |
| QuantityInput | `src/components/ui/quantity-input.tsx` |

---

## 9. 사용 예시

### 직접 import 방식
```tsx
import { PhoneInput } from '@/components/ui/phone-input';
import { CurrencyInput } from '@/components/ui/currency-input';
import { NumberInput } from '@/components/ui/number-input';

// 전화번호
<PhoneInput value={phone} onChange={setPhone} />

// 금액
<CurrencyInput value={price} onChange={setPrice} />

// 소수점 허용 숫자
<NumberInput value={rate} onChange={setRate} allowDecimal decimalPlaces={2} />
```

### FormField 통합 방식
```tsx
import { FormField } from '@/components/molecules/FormField';

// 전화번호
<FormField
  label="전화번호"
  type="phone"
  value={phone}
  onChange={setPhone}
/>

// 사업자번호 (유효성 검사 표시)
<FormField
  label="사업자번호"
  type="businessNumber"
  value={bizNo}
  onChange={setBizNo}
  showValidation
/>

// 금액
<FormField
  label="금액"
  type="currency"
  value={price}
  onChangeNumber={setPrice}
/>

// 수량 (+/- 버튼)
<FormField
  label="수량"
  type="quantity"
  value={qty}
  onChangeNumber={setQty}
  showButtons
  min={1}
  max={100}
/>
```