# 공통 컴포넌트 추출 계획서

> MVP 완료 후 리팩토링 계획 (2025-12-23)

## 개요

| 항목 | 수치 |
|-----|------|
| 예상 코드 절감 | ~1,900줄 |
| 영향 파일 | 50+ 개 |
| 유지보수 비용 감소 | 30-40% |
| 예상 작업 기간 | 3-4일 |

---

## 현재 공통화 현황

### ✅ 잘 되어있는 것
- `SearchFilter` - 검색 입력 + 필터
- `TabFilter` - 탭 형태 필터
- `DateRangeSelector` - 날짜 범위 선택
- `TableActions` - 테이블 행 액션 버튼
- `FormActions` - 폼 저장/취소 버튼
- `FormField` - 개별 폼 필드
- `StatCards` - 통계 카드
- `StandardDialog` - 기본 다이얼로그 (but 사용률 저조)

### ❌ 공통화 필요한 것
- 삭제 확인 다이얼로그 (40+ 파일 중복)
- 금액 포맷 유틸 (30+ 파일 중복)
- 상태 배지 + 색상 상수 (10+ 파일 중복)
- 상세정보 카드 (15+ 파일 각자 구현)
- 폼 레이아웃 템플릿 (20+ 파일 중복)

---

## Phase 1: 핵심 컴포넌트 (1일)

### 1.1 DeleteDialog 컴포넌트

**위치**: `src/components/molecules/DeleteDialog.tsx`

**Props 설계**:
```typescript
interface DeleteDialogProps {
  open: boolean;
  onOpenChange: (open: boolean) => void;
  itemName?: string;        // "거래처", "품목" 등
  itemLabel?: string;       // 삭제 대상 이름 (예: "삼성전자")
  title?: string;           // 커스텀 타이틀
  description?: string;     // 커스텀 설명
  onConfirm: () => void;
  isLoading?: boolean;
  confirmText?: string;     // 기본값: "삭제"
  cancelText?: string;      // 기본값: "취소"
}
```

**사용 예시**:
```typescript
<DeleteDialog
  open={showDeleteDialog}
  onOpenChange={setShowDeleteDialog}
  itemName="거래처"
  itemLabel={selectedVendor?.name}
  onConfirm={handleDelete}
  isLoading={isDeleting}
/>
```

**체크리스트**:
- [ ] DeleteDialog 컴포넌트 생성
- [ ] 기본 스타일 (빨간색 삭제 버튼)
- [ ] isLoading 상태 처리
- [ ] 접근성 (포커스 트랩, ESC 닫기)
- [ ] molecules/index.ts export 추가

**적용 대상 파일** (40+ 파일):
- [ ] `accounting/VendorManagement/index.tsx`
- [ ] `accounting/BillManagement/index.tsx`
- [ ] `accounting/SalesManagement/index.tsx`
- [ ] `accounting/PurchaseManagement/index.tsx`
- [ ] `accounting/DepositManagement/index.tsx`
- [ ] `accounting/WithdrawalManagement/index.tsx`
- [ ] `hr/EmployeeManagement/index.tsx`
- [ ] `hr/DepartmentManagement/index.tsx`
- [ ] `hr/VacationManagement/index.tsx`
- [ ] `settings/RankManagement/index.tsx`
- [ ] `settings/TitleManagement/index.tsx`
- [ ] `settings/PermissionManagement/index.tsx`
- [ ] `settings/AccountManagement/index.tsx`
- [ ] `board/BoardManagement/index.tsx`
- [ ] `items/ItemListClient.tsx`
- [ ] (나머지 25+ 파일은 grep으로 검색)

---

### 1.2 포맷 유틸 함수

**위치**: `src/lib/formatters.ts`

**함수 설계**:
```typescript
// 금액 포맷
export function formatCurrency(amount: number): string;
export function formatCurrencyWithSign(amount: number): string; // +/- 표시

// 금액 셀 (조건부 표시)
export function formatCurrencyOrDash(amount: number, dash?: string): string;

// 날짜 포맷
export function formatDate(date: string | Date, format?: string): string;
export function formatDateTime(date: string | Date): string;

// 숫자 포맷
export function formatNumber(num: number): string;
export function formatPercent(num: number, decimals?: number): string;

// 전화번호 포맷
export function formatPhone(phone: string): string;

// 사업자번호 포맷
export function formatBizNo(bizNo: string): string;
```

**사용 예시**:
```typescript
formatCurrency(10000)           // "10,000원"
formatCurrencyOrDash(0)         // "-"
formatCurrencyWithSign(-5000)   // "-5,000원"
formatDate('2024-01-15')        // "2024-01-15"
formatPhone('01012345678')      // "010-1234-5678"
formatBizNo('1234567890')       // "123-45-67890"
```

**체크리스트**:
- [ ] formatters.ts 파일 생성
- [ ] formatCurrency 함수
- [ ] formatCurrencyOrDash 함수
- [ ] formatCurrencyWithSign 함수
- [ ] formatDate 함수
- [ ] formatDateTime 함수
- [ ] formatNumber 함수
- [ ] formatPercent 함수
- [ ] formatPhone 함수
- [ ] formatBizNo 함수
- [ ] 단위 테스트 (선택)

**적용 대상 파일** (30+ 파일):
- [ ] 모든 accounting/* 컴포넌트
- [ ] 모든 테이블에서 금액 표시하는 곳

---

## Phase 2: 상태 표시 컴포넌트 (1일)

### 2.1 상태 색상 중앙화

**위치**: `src/lib/status-colors.ts`

**설계**:
```typescript
// 공통 상태 색상
export const STATUS_COLORS = {
  // 일반 상태
  active: 'bg-green-100 text-green-800',
  inactive: 'bg-gray-100 text-gray-800',
  pending: 'bg-yellow-100 text-yellow-800',
  completed: 'bg-blue-100 text-blue-800',
  cancelled: 'bg-red-100 text-red-800',

  // 결제/금융 상태
  paid: 'bg-green-100 text-green-800',
  unpaid: 'bg-red-100 text-red-800',
  partial: 'bg-orange-100 text-orange-800',
  overdue: 'bg-red-100 text-red-800',

  // 기본값
  default: 'bg-gray-100 text-gray-800',
} as const;

// 도메인별 상태 색상
export const BILL_STATUS_COLORS = { ... };
export const VENDOR_CATEGORY_COLORS = { ... };
export const ORDER_STATUS_COLORS = { ... };
export const INSPECTION_STATUS_COLORS = { ... };
```

**체크리스트**:
- [ ] status-colors.ts 파일 생성
- [ ] 공통 STATUS_COLORS 정의
- [ ] BILL_STATUS_COLORS 이동
- [ ] VENDOR_CATEGORY_COLORS 이동
- [ ] ORDER_STATUS_COLORS 정의
- [ ] INSPECTION_STATUS_COLORS 정의
- [ ] PRODUCTION_STATUS_COLORS 정의

---

### 2.2 StatusBadge 컴포넌트

**위치**: `src/components/ui/status-badge.tsx`

**Props 설계**:
```typescript
interface StatusBadgeProps {
  status: string;
  label?: string;
  colorMap?: Record<string, string>;
  size?: 'sm' | 'md' | 'lg';
  className?: string;
}
```

**사용 예시**:
```typescript
// 색상맵 지정
<StatusBadge
  status="paymentComplete"
  label="결제완료"
  colorMap={BILL_STATUS_COLORS}
/>

// 공통 색상 사용
<StatusBadge status="active" label="활성" />
```

**체크리스트**:
- [ ] StatusBadge 컴포넌트 생성
- [ ] 기본 색상 (STATUS_COLORS) 적용
- [ ] colorMap prop으로 커스텀 색상 지원
- [ ] size 변형 (sm, md, lg)
- [ ] ui/index.ts export 추가

**적용 대상 파일** (10+ 파일):
- [ ] `accounting/BillManagement/index.tsx`
- [ ] `accounting/SalesManagement/index.tsx`
- [ ] `accounting/VendorManagement/index.tsx`
- [ ] `production/WorkOrders/WorkOrderList.tsx`
- [ ] `quality/InspectionManagement/InspectionList.tsx`
- [ ] (나머지 파일)

---

## Phase 3: 카드/레이아웃 컴포넌트 (1일)

### 3.1 DetailInfoCard 컴포넌트

**위치**: `src/components/molecules/DetailInfoCard.tsx`

**Props 설계**:
```typescript
interface InfoItem {
  label: string;
  value: React.ReactNode;
  className?: string;
  span?: number;  // grid span
}

interface DetailInfoCardProps {
  title?: string;
  description?: string;
  items: InfoItem[];
  columns?: 1 | 2 | 3 | 4;
  className?: string;
  headerAction?: React.ReactNode;
}
```

**사용 예시**:
```typescript
<DetailInfoCard
  title="거래처 정보"
  columns={2}
  headerAction={<Button size="sm">수정</Button>}
  items={[
    { label: '상호명', value: data.name },
    { label: '사업자번호', value: formatBizNo(data.bizNo) },
    { label: '대표자', value: data.ceo },
    { label: '연락처', value: formatPhone(data.phone) },
    { label: '주소', value: data.address, span: 2 },
  ]}
/>
```

**체크리스트**:
- [ ] DetailInfoCard 컴포넌트 생성
- [ ] columns 1/2/3/4 지원
- [ ] span으로 컬럼 병합 지원
- [ ] headerAction 슬롯
- [ ] 반응형 (모바일에서 1컬럼)
- [ ] molecules/index.ts export 추가

**적용 대상 파일** (15+ 파일):
- [ ] `accounting/VendorManagement/VendorDetail.tsx`
- [ ] `accounting/BillManagement/BillDetail.tsx`
- [ ] `accounting/SalesManagement/SalesDetail.tsx`
- [ ] `accounting/PurchaseManagement/PurchaseDetail.tsx`
- [ ] `hr/EmployeeManagement/EmployeeDetail.tsx`
- [ ] (나머지 Detail 페이지들)

---

### 3.2 FormGridLayout 컴포넌트

**위치**: `src/components/molecules/FormGridLayout.tsx`

**Props 설계**:
```typescript
interface FormGridLayoutProps {
  children: React.ReactNode;
  columns?: 1 | 2 | 3 | 4;
  gap?: 'sm' | 'md' | 'lg';
  className?: string;
}

interface FormSectionProps {
  title?: string;
  description?: string;
  children: React.ReactNode;
  columns?: 1 | 2 | 3 | 4;
}
```

**사용 예시**:
```typescript
<FormSection title="기본 정보" columns={2}>
  <FormField label="이름" required value={name} onChange={setName} />
  <FormField label="이메일" type="email" value={email} onChange={setEmail} />
  <FormField label="주소" className="col-span-2" value={address} onChange={setAddress} />
</FormSection>
```

**체크리스트**:
- [ ] FormGridLayout 컴포넌트 생성
- [ ] FormSection 컴포넌트 생성
- [ ] columns 1/2/3/4 지원
- [ ] gap 크기 (sm/md/lg)
- [ ] col-span 클래스 지원
- [ ] 반응형

---

## Phase 4: 마이그레이션 및 검증 (1일)

### 4.1 기존 코드 마이그레이션

**체크리스트**:
- [ ] DeleteDialog 마이그레이션 (40+ 파일)
- [ ] formatCurrency 마이그레이션 (30+ 파일)
- [ ] StatusBadge 마이그레이션 (10+ 파일)
- [ ] DetailInfoCard 마이그레이션 (15+ 파일)
- [ ] 불필요한 import 제거
- [ ] 미사용 코드 삭제

### 4.2 검증

**체크리스트**:
- [ ] 빌드 에러 없음 확인 (`npm run build`)
- [ ] 타입 에러 없음 확인 (`npm run type-check`)
- [ ] 주요 페이지 동작 테스트
  - [ ] 거래처 삭제
  - [ ] 품목 삭제
  - [ ] 금액 표시 확인
  - [ ] 상태 배지 표시 확인
- [ ] 반응형 테스트 (모바일)

### 4.3 문서화

**체크리스트**:
- [ ] 컴포넌트 JSDoc 주석
- [ ] 사용 예시 코드
- [ ] claudedocs 업데이트

---

## 파일 구조 (최종)

```
src/
├── components/
│   ├── ui/
│   │   ├── status-badge.tsx        # 🆕 Phase 2
│   │   └── ...
│   ├── molecules/
│   │   ├── StandardDialog.tsx      # 기존
│   │   ├── DeleteDialog.tsx        # 🆕 Phase 1
│   │   ├── DetailInfoCard.tsx      # 🆕 Phase 3
│   │   ├── FormGridLayout.tsx      # 🆕 Phase 3
│   │   └── index.ts
│   └── ...
├── lib/
│   ├── formatters.ts               # 🆕 Phase 1
│   ├── status-colors.ts            # 🆕 Phase 2
│   └── ...
└── ...
```

---

## 예상 효과

| Phase | 컴포넌트 | 절감 라인 | 영향 파일 |
|-------|---------|----------|----------|
| 1 | DeleteDialog | ~800줄 | 40+ |
| 1 | formatters | ~150줄 | 30+ |
| 2 | status-colors | ~200줄 | 10+ |
| 2 | StatusBadge | ~100줄 | 10+ |
| 3 | DetailInfoCard | ~400줄 | 15+ |
| 3 | FormGridLayout | ~250줄 | 20+ |
| **합계** | | **~1,900줄** | **50+** |

---

## 우선순위 정리

### 🔴 필수 (Phase 1)
1. **DeleteDialog** - 가장 많은 중복, 즉시 효과
2. **formatters** - 유틸 함수, 간단히 적용

### 🟡 권장 (Phase 2)
3. **status-colors** - 색상 상수 중앙화
4. **StatusBadge** - 일관된 상태 표시

### 🟢 선택 (Phase 3)
5. **DetailInfoCard** - 상세 페이지 통일
6. **FormGridLayout** - 폼 레이아웃 통일

---

## 변경 이력

| 날짜 | 변경 내용 |
|-----|----------|
| 2025-12-23 | 최초 작성 - 공통 컴포넌트 추출 계획 |