# 공통 컴포넌트 가이드

## 컴포넌트 계층 요약

```
Templates   → 페이지 전체 (IntegratedListTemplateV2)
Organisms   → 페이지 블록 (PageHeader, DataTable, SearchFilter ...)
Molecules   → 조합 단위 (FormField, StatusBadge, StandardDialog ...)
UI          → 원자 단위 (Button, Input, Select ...)
```

---

## Templates

### IntegratedListTemplateV2

리스트 페이지를 위한 **올인원 템플릿**. 새 리스트 페이지 생성 시 이 템플릿 사용을 우선 검토합니다.

**경로**: `src/components/templates/IntegratedListTemplateV2.tsx`

**포함 기능**:
- PageLayout + PageHeader (아이콘/제목/설명)
- 검색 + 필터 + 날짜 선택 헤더
- 통계 카드 (StatCards)
- 테이블 + 컬럼 설정 + 페이지네이션
- 모바일 카드 자동 전환 (반응형)
- 체크박스 선택 (`Set<string>`)

**필수 적용 항목**:
1. 컬럼 설정 (`useColumnSettings` + `ColumnSettingsPopover`)
2. 모바일 카드 (`renderMobileCard`)
3. 체크박스 (`selectedItems: Set<string>`)
4. 테이블 내 필터 (`tableHeaderActions`)

**기본 사용법**:

```tsx
import IntegratedListTemplateV2 from '@/components/templates/IntegratedListTemplateV2';
import { useColumnSettings } from '@/hooks/useColumnSettings';
import { ColumnSettingsPopover } from '@/components/molecules/ColumnSettingsPopover';
import { MobileCard, InfoField } from '@/components/organisms';

const columns = [
  { key: 'itemName', label: '품목명', width: '200px' },
  { key: 'itemCode', label: '품목코드', width: '150px' },
  { key: 'status', label: '상태', width: '100px' },
];

export default function ItemListPage() {
  const [selectedItems, setSelectedItems] = useState<Set<string>>(new Set());
  const { visibleColumns, allColumnsWithVisibility, columnWidths, setColumnWidth,
          toggleColumnVisibility, resetSettings, hasHiddenColumns } =
    useColumnSettings({ pageId: 'item-list', columns });

  return (
    <IntegratedListTemplateV2
      title="품목 관리"
      icon={Package}
      description="품목 목록을 관리합니다"
      // 검색
      searchValue={search}
      onSearchChange={setSearch}
      searchPlaceholder="품목명 또는 코드로 검색"
      // 테이블
      tableColumns={visibleColumns}
      columnSettings={{
        columnWidths,
        onColumnResize: setColumnWidth,
        settingsPopover: (
          <ColumnSettingsPopover
            columns={allColumnsWithVisibility}
            onToggle={toggleColumnVisibility}
            onReset={resetSettings}
            hasHiddenColumns={hasHiddenColumns}
          />
        ),
      }}
      data={items}
      // 체크박스
      selectedItems={selectedItems}
      onToggleSelection={(id) => {
        setSelectedItems(prev => {
          const next = new Set(prev);
          next.has(id) ? next.delete(id) : next.add(id);
          return next;
        });
      }}
      onToggleSelectAll={() => { /* 전체 선택/해제 */ }}
      getItemId={(item) => item.id}
      // 테이블 행
      renderTableRow={(item, index, globalIndex, isSelected, onToggle) => (
        <tr key={item.id} className={isSelected ? 'bg-blue-50' : ''}>
          <td><Checkbox checked={isSelected} onCheckedChange={onToggle} /></td>
          <td>{globalIndex}</td>
          <td>{item.itemName}</td>
          <td>{item.itemCode}</td>
        </tr>
      )}
      // 모바일 카드 (반응형)
      renderMobileCard={(item, index, globalIndex, isSelected, onToggle) => (
        <MobileCard
          title={item.itemName}
          subtitle={item.itemCode}
          isSelected={isSelected}
          onToggleSelection={onToggle}
          details={[
            { label: '상태', value: item.status },
          ]}
        />
      )}
      // 페이지네이션
      pagination={{
        currentPage: pagination.currentPage,
        totalPages: pagination.lastPage,
        totalItems: pagination.total,
        itemsPerPage: pagination.perPage,
        onPageChange: (page) => fetchData({ page }),
      }}
      isLoading={isLoading}
      // 등록 버튼
      createButton={{ label: '품목 등록', onClick: () => router.push('?mode=new') }}
    />
  );
}
```

---

## Organisms

**경로**: `src/components/organisms/`
**import**: `import { PageHeader, DataTable, ... } from '@/components/organisms'`

### PageHeader

```tsx
<PageHeader
  title="품목 관리"
  description="품목 목록을 관리합니다"
  icon={Package}
  actions={<Button onClick={handleCreate}>등록</Button>}
/>
```

| Prop | 타입 | 설명 |
|------|------|------|
| `title` | string \| ReactNode | 페이지 제목 (필수) |
| `description?` | string | 부제목 |
| `icon?` | LucideIcon | 좌측 아이콘 |
| `actions?` | ReactNode | 우측 액션 버튼 |

### PageLayout

```tsx
<PageLayout maxWidth="full">
  {children}
</PageLayout>
```

| Prop | 타입 | 기본값 | 설명 |
|------|------|--------|------|
| `maxWidth?` | "sm"\|"md"\|"lg"\|"xl"\|"2xl"\|"full" | "full" | 최대 너비 |

### StatCards

```tsx
<StatCards stats={[
  { label: '전체', value: 100, icon: Package },
  { label: '활성', value: 80, icon: CheckCircle, iconColor: 'text-green-500' },
  { label: '비활성', value: 20, icon: XCircle, iconColor: 'text-red-500' },
]} />
```

### SearchFilter

```tsx
<SearchFilter
  searchValue={search}
  onSearchChange={setSearch}
  searchPlaceholder="검색어 입력"
  extraActions={<DatePicker value={date} onChange={setDate} />}
/>
```

### DataTable

```tsx
<DataTable
  columns={[
    { key: 'name', label: '이름', sortable: true },
    { key: 'status', label: '상태', type: 'badge' },
    { key: 'amount', label: '금액', type: 'currency', align: 'right' },
    { key: 'actions', label: '', type: 'custom',
      render: (_, row) => <Button size="sm">수정</Button> },
  ]}
  data={items}
  keyField="id"
  onRowClick={(row) => router.push(`/items/${row.id}`)}
  pagination={{ currentPage, totalPages, onPageChange }}
/>
```

**Column type 종류**: `text`, `number`, `currency`, `date`, `datetime`, `status`, `badge`, `icon`, `actions`, `custom`

### SearchableSelectionModal

검색+선택 팝업이 필요할 때 사용. **직접 Dialog 조합 금지**.

```tsx
<SearchableSelectionModal<Vendor>
  open={isOpen}
  onOpenChange={setIsOpen}
  title="거래처 검색"
  fetchData={async (query) => {
    const result = await searchVendors({ search: query });
    return result.success ? result.data : [];
  }}
  keyExtractor={(vendor) => vendor.id}
  mode="single"
  onSelect={(vendor) => handleVendorSelect(vendor)}
  searchPlaceholder="거래처명으로 검색"
  renderItem={(vendor, isSelected) => (
    <div className={cn('p-3', isSelected && 'bg-blue-50')}>
      <div className="font-medium">{vendor.name}</div>
      <div className="text-sm text-muted-foreground">{vendor.code}</div>
    </div>
  )}
/>
```

| Prop | 필수 | 설명 |
|------|:---:|------|
| `open` | O | 모달 열기 상태 |
| `onOpenChange` | O | 상태 변경 |
| `title` | O | 모달 제목 |
| `fetchData` | O | `(query: string) => Promise<T[]>` |
| `keyExtractor` | O | `(item: T) => string` |
| `mode` | O | `'single'` \| `'multiple'` |
| `onSelect` | O | 선택 콜백 |
| `renderItem` | O | 아이템 렌더링 |
| `searchMode?` | | `'debounce'`(기본) \| `'enter'` |
| `loadOnOpen?` | | 열릴 때 자동 로드 |
| `listWrapper?` | | 리스트 래퍼 (테이블 구조 등) |

### MobileCard / InfoField

```tsx
<MobileCard
  title="품목A"
  subtitle="P-001"
  isSelected={isSelected}
  onToggleSelection={onToggle}
  details={[
    { label: '규격', value: '100x200' },
    { label: '단가', value: '10,000원' },
  ]}
  onClick={() => router.push(`/items/${item.id}`)}
/>
```

### EmptyState / TableEmptyState

```tsx
<EmptyState message="데이터가 없습니다" />
<TableEmptyState colSpan={5} message="검색 결과가 없습니다" />
```

---

## Molecules

**경로**: `src/components/molecules/`

### FormField (신규 폼 필수)

`Label + Input + Error` 수동 조합 대신 사용.

```tsx
import { FormField } from '@/components/molecules/FormField';

<FormField
  label="회사명"
  required
  type="text"
  value={formData.companyName}
  onChange={(value) => handleChange('companyName', value)}
  placeholder="회사명을 입력하세요"
  disabled={mode === 'view'}
  error={errors.companyName}
/>
```

**지원 type**: `text`, `number`, `date`, `select`, `textarea`, `custom`, `password`, `phone`, `businessNumber`, `personalNumber`, `currency`, `quantity`

**FormField로 대체하지 않는 경우**:
- Select, DatePicker, ImageUpload 등 특수 컴포넌트
- 주소 검색(버튼+입력) 등 복합 레이아웃
- 편집/읽기 모드가 다른 커스텀 인터랙션

### StatusBadge

```tsx
import { StatusBadge } from '@/components/molecules/StatusBadge';

<StatusBadge label="승인" variant="success" />
<StatusBadge label="대기" variant="warning" showDot />
<StatusBadge label="반려" variant="danger" />
```

**variant**: `default`, `success`, `warning`, `danger`, `info`, `secondary`, `outline`

### ColumnSettingsPopover

`useColumnSettings` hook과 함께 사용:

```tsx
<ColumnSettingsPopover
  columns={allColumnsWithVisibility}
  onToggle={toggleColumnVisibility}
  onReset={resetSettings}
  hasHiddenColumns={hasHiddenColumns}
/>
```

### StandardDialog

```tsx
<StandardDialog
  open={isOpen}
  onOpenChange={setIsOpen}
  title="확인"
  description="정말 삭제하시겠습니까?"
  size="md"
  footer={
    <>
      <Button variant="outline" onClick={() => setIsOpen(false)}>취소</Button>
      <Button variant="destructive" onClick={handleDelete}>삭제</Button>
    </>
  }
>
  <p>이 작업은 되돌릴 수 없습니다.</p>
</StandardDialog>
```

**size**: `sm`, `md`, `lg`, `xl`, `full`
