# SAM MES 아토믹 디자인 시스템

> **작성일**: 2025년 10월 24일  
> **버전**: 2.0.0  
> **목적**: 전체 시스템을 아토믹 디자인 시스템으로 재구성하여 재사용성과 유지보수성 향상

---

## 📋 목차

1. [아토믹 디자인 시스템 개요](#아토믹-디자인-시스템-개요)
2. [디렉토리 구조](#디렉토리-구조)
3. [계층별 컴포넌트](#계층별-컴포넌트)
4. [공통 템플릿](#공통-템플릿)
5. [컴포넌트 통합 계획](#컴포넌트-통합-계획)
6. [마이그레이션 가이드](#마이그레이션-가이드)

---

## 아토믹 디자인 시스템 개요

### 계층 구조

```
Atoms (원자)
  ↓
Molecules (분자)
  ↓
Organisms (유기체)
  ↓
Templates (템플릿)
  ↓
Pages (페이지)
```

### 각 계층의 역할

**1. Atoms (원자)**
- 가장 작은 UI 단위
- 더 이상 분해할 수 없는 기본 요소
- 예: Button, Input, Label, Icon, Badge

**2. Molecules (분자)**
- 2개 이상의 Atoms 조합
- 하나의 역할을 수행하는 단순한 조합
- 예: SearchBar, FormField, LabeledInput

**3. Organisms (유기체)**
- Molecules와 Atoms의 복잡한 조합
- 독립적인 기능을 가진 컴포넌트
- 예: Header, DataTable, StatCards, Sidebar

**4. Templates (템플릿)**
- 페이지 레이아웃 구조
- 컨텐츠가 없는 와이어프레임
- 예: ListPageTemplate, FormPageTemplate, DashboardTemplate

**5. Pages (페이지)**
- 실제 데이터와 비즈니스 로직이 포함된 완성된 페이지
- 예: CustomerManagement, OrderManagement

---

## 디렉토리 구조

### 신규 구조 (제안)

```
components/
├── atoms/                    # Atoms (기본 UI 요소)
│   ├── Button/
│   ├── Input/
│   ├── Badge/
│   ├── Icon/
│   └── ... (기존 ui/ 폴더 내용)
│
├── molecules/                # Molecules (작은 조합)
│   ├── SearchBar/
│   │   ├── SearchBar.tsx
│   │   └── SearchBar.stories.tsx
│   ├── FormField/
│   │   ├── FormField.tsx
│   │   └── FormField.stories.tsx
│   ├── StatCard/
│   │   ├── StatCard.tsx
│   │   └── StatCard.stories.tsx
│   ├── FilterButton/
│   ├── DateRangePicker/
│   └── StatusBadge/
│
├── organisms/                # Organisms (복잡한 조합)
│   ├── PageHeader/
│   │   ├── PageHeader.tsx
│   │   └── index.ts
│   ├── StatCards/
│   │   ├── StatCards.tsx
│   │   └── index.ts
│   ├── SearchFilter/
│   │   ├── SearchFilter.tsx
│   │   └── index.ts
│   ├── DataTable/
│   │   ├── DataTable.tsx
│   │   ├── DataTableHeader.tsx
│   │   ├── DataTableBody.tsx
│   │   ├── DataTableRow.tsx
│   │   ├── DataTablePagination.tsx
│   │   └── index.ts
│   ├── MobileCardList/
│   │   ├── MobileCardList.tsx
│   │   ├── MobileCard.tsx
│   │   └── index.ts
│   ├── FormSection/
│   │   ├── FormSection.tsx
│   │   ├── CollapsibleFormSection.tsx
│   │   └── index.ts
│   ├── EmptyState/
│   ├── Sidebar/
│   └── TopNavigation/
│
├── templates/                # Templates (페이지 템플릿)
│   ├── ListPageTemplate/
│   │   ├── ListPageTemplate.tsx
│   │   └── index.ts
│   ├── FormPageTemplate/
│   │   ├── FormPageTemplate.tsx
│   │   └── index.ts
│   ├── DashboardTemplate/
│   │   ├── DashboardTemplate.tsx
│   │   └── index.ts
│   ├── DetailPageTemplate/
│   ├── InspectionTemplate/
│   └── TabbedPageTemplate/
│
├── pages/                    # Pages (실제 페이지)
│   ├── sales/
│   │   ├── CustomerManagement.tsx
│   │   ├── OrderManagement.tsx
│   │   └── QuoteManagement.tsx
│   ├── production/
│   │   ├── ItemManagement.tsx
│   │   ├── BOMManagement.tsx
│   │   └── ProductionManagement.tsx
│   ├── quality/
│   │   ├── IncomingInspection.tsx
│   │   ├── ProcessInspection.tsx
│   │   └── FinalInspection.tsx
│   ├── inventory/
│   │   ├── StockStatus.tsx
│   │   ├── ReceivingManagement.tsx
│   │   └── ShippingManagement.tsx
│   ├── hr/
│   │   ├── EmployeeManagement.tsx
│   │   ├── DepartmentManagement.tsx
│   │   └── AttendanceManagement.tsx
│   ├── accounting/
│   │   ├── SalesAccounting.tsx
│   │   └── PurchaseAccounting.tsx
│   ├── approval/
│   │   ├── DraftBox.tsx
│   │   ├── ApprovalBox.tsx
│   │   └── DocumentWrite.tsx
│   ├── master/
│   │   ├── ModelManagement.tsx
│   │   ├── ProcessManagement.tsx
│   │   └── CodeManagement.tsx
│   └── dashboards/
│       ├── SalesDashboard.tsx
│       ├── ProductionDashboard.tsx
│       └── QualityDashboard.tsx
│
├── hooks/                    # Custom Hooks
│   ├── useTableData.ts
│   ├── useFormValidation.ts
│   ├── usePagination.ts
│   ├── useSearch.ts
│   └── useModal.ts
│
├── utils/                    # Utility Functions
│   ├── formatters.ts
│   ├── validators.ts
│   ├── dateUtils.ts
│   └── codeGenerator.ts
│
└── types/                    # TypeScript Types
    ├── common.types.ts
    ├── sales.types.ts
    ├── production.types.ts
    └── quality.types.ts
```

### 마이그레이션 맵핑

**기존 → 신규**

```
components/ui/              → components/atoms/
components/common/          → components/organisms/
components/*.tsx (페이지)   → components/pages/[모듈명]/
```

---

## 계층별 컴포넌트

### 1. Atoms (원자)

**기존 위치**: `components/ui/`

**유지할 컴포넌트**:
- button.tsx
- input.tsx
- label.tsx
- badge.tsx
- card.tsx
- checkbox.tsx
- select.tsx
- textarea.tsx
- dialog.tsx
- table.tsx
- tabs.tsx
- 등 shadcn/ui 컴포넌트 전체

**추가 권장 Atoms**:
```typescript
// Icon.tsx
export const Icon = ({ name, size, color, ...props }) => {
  const IconComponent = lucideIcons[name];
  return <IconComponent className={cn(sizeClasses[size], color)} {...props} />;
};

// Loading.tsx
export const Loading = ({ size, variant }) => {
  // 로딩 스피너
};

// Avatar.tsx (이미 있음)
// Badge.tsx (이미 있음)
```

---

### 2. Molecules (분자)

**새로 생성할 Molecules**:

#### 2.1 SearchBar
```typescript
// components/molecules/SearchBar/SearchBar.tsx
import { Input } from "@/components/atoms/input";
import { Search } from "lucide-react";

interface SearchBarProps {
  value: string;
  onChange: (value: string) => void;
  placeholder?: string;
}

export const SearchBar = ({ value, onChange, placeholder }: SearchBarProps) => {
  return (
    <div className="relative">
      <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground w-4 h-4" />
      <Input
        value={value}
        onChange={(e) => onChange(e.target.value)}
        placeholder={placeholder}
        className="pl-10"
      />
    </div>
  );
};
```

#### 2.2 StatCard (단일 통계 카드)
```typescript
// components/molecules/StatCard/StatCard.tsx
import { Card, CardContent } from "@/components/atoms/card";
import { Badge } from "@/components/atoms/badge";
import { LucideIcon } from "lucide-react";

interface StatCardProps {
  label: string;
  value: string | number;
  icon: LucideIcon;
  iconColor?: string;
  trend?: {
    value: string;
    isPositive: boolean;
  };
}

export const StatCard = ({ label, value, icon: Icon, iconColor, trend }: StatCardProps) => {
  return (
    <Card>
      <CardContent className="p-6">
        <div className="flex items-center justify-between mb-4">
          <div className={cn("w-10 h-10 rounded-lg flex items-center justify-center", iconColor)}>
            <Icon className="h-5 w-5 text-white" />
          </div>
          {trend && (
            <Badge variant={trend.isPositive ? "default" : "destructive"}>
              {trend.value}
            </Badge>
          )}
        </div>
        <p className="text-sm text-muted-foreground">{label}</p>
        <p className="text-2xl font-semibold mt-1">{value}</p>
      </CardContent>
    </Card>
  );
};
```

#### 2.3 FormField (라벨 + 입력)
```typescript
// components/molecules/FormField/FormField.tsx
import { Label } from "@/components/atoms/label";
import { Input } from "@/components/atoms/input";

interface FormFieldProps {
  label: string;
  value: string;
  onChange: (value: string) => void;
  type?: string;
  required?: boolean;
  error?: string;
}

export const FormField = ({ label, value, onChange, type, required, error }: FormFieldProps) => {
  return (
    <div className="space-y-2">
      <Label>
        {label}
        {required && <span className="text-destructive ml-1">*</span>}
      </Label>
      <Input
        type={type}
        value={value}
        onChange={(e) => onChange(e.target.value)}
        className={error ? "border-destructive" : ""}
      />
      {error && <p className="text-sm text-destructive">{error}</p>}
    </div>
  );
};
```

#### 2.4 FilterButton
```typescript
// components/molecules/FilterButton/FilterButton.tsx
import { Button } from "@/components/atoms/button";
import { Filter } from "lucide-react";

interface FilterButtonProps {
  onClick: () => void;
  active?: boolean;
}

export const FilterButton = ({ onClick, active }: FilterButtonProps) => {
  return (
    <Button variant={active ? "default" : "outline"} onClick={onClick}>
      <Filter className="w-4 h-4 mr-2" />
      필터
    </Button>
  );
};
```

#### 2.5 StatusBadge
```typescript
// components/molecules/StatusBadge/StatusBadge.tsx
import { Badge } from "@/components/atoms/badge";

type StatusType = "대기" | "진행중" | "완료" | "취소" | "반려";

const statusConfig: Record<StatusType, { variant: string; className: string }> = {
  "대기": { variant: "secondary", className: "bg-gray-100 text-gray-700" },
  "진행중": { variant: "default", className: "bg-blue-100 text-blue-700" },
  "완료": { variant: "default", className: "bg-green-100 text-green-700" },
  "취소": { variant: "destructive", className: "bg-red-100 text-red-700" },
  "반려": { variant: "destructive", className: "bg-red-100 text-red-700" }
};

export const StatusBadge = ({ status }: { status: StatusType }) => {
  const config = statusConfig[status];
  return <Badge className={config.className}>{status}</Badge>;
};
```

---

### 3. Organisms (유기체)

**기존**: `components/common/`

**유지 및 개선**:

#### 3.1 PageHeader (개선)
```typescript
// components/organisms/PageHeader/PageHeader.tsx
import { ReactNode } from "react";
import { LucideIcon } from "lucide-react";

interface PageHeaderProps {
  title: string;
  description?: string;
  icon?: LucideIcon;
  actions?: ReactNode;
  breadcrumbs?: Array<{ label: string; href?: string }>;
}

export const PageHeader = ({ 
  title, 
  description, 
  icon: Icon, 
  actions,
  breadcrumbs 
}: PageHeaderProps) => {
  return (
    <div className="space-y-4">
      {breadcrumbs && (
        <nav className="flex items-center space-x-2 text-sm text-muted-foreground">
          {breadcrumbs.map((item, index) => (
            <div key={index} className="flex items-center">
              {index > 0 && <span className="mx-2">/</span>}
              <span>{item.label}</span>
            </div>
          ))}
        </nav>
      )}
      
      <div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
        <div className="flex items-center gap-3">
          {Icon && (
            <div className="hidden md:flex w-12 h-12 bg-primary/10 rounded-lg items-center justify-center">
              <Icon className="h-6 w-6 text-primary" />
            </div>
          )}
          <div>
            <h1 className="text-xl md:text-2xl font-semibold">{title}</h1>
            {description && (
              <p className="text-sm text-muted-foreground mt-1">{description}</p>
            )}
          </div>
        </div>
        
        {actions && (
          <div className="flex items-center gap-2 flex-wrap">
            {actions}
          </div>
        )}
      </div>
    </div>
  );
};
```

#### 3.2 DataTable (신규 - 공통 테이블)
```typescript
// components/organisms/DataTable/DataTable.tsx
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/atoms/table";
import { Card, CardContent } from "@/components/atoms/card";
import { Button } from "@/components/atoms/button";
import { ChevronLeft, ChevronRight } from "lucide-react";

interface Column<T> {
  key: keyof T | string;
  label: string;
  render?: (value: any, row: T) => ReactNode;
  sortable?: boolean;
  className?: string;
}

interface DataTableProps<T> {
  columns: Column<T>[];
  data: T[];
  keyField: keyof T;
  onRowClick?: (row: T) => void;
  loading?: boolean;
  pagination?: {
    currentPage: number;
    totalPages: number;
    onPageChange: (page: number) => void;
  };
}

export function DataTable<T>({ 
  columns, 
  data, 
  keyField,
  onRowClick,
  loading,
  pagination 
}: DataTableProps<T>) {
  return (
    <Card className="hidden md:block">
      <CardContent className="p-0">
        <Table>
          <TableHeader>
            <TableRow>
              {columns.map((column) => (
                <TableHead key={String(column.key)} className={column.className}>
                  {column.label}
                </TableHead>
              ))}
            </TableRow>
          </TableHeader>
          <TableBody>
            {loading ? (
              <TableRow>
                <TableCell colSpan={columns.length} className="text-center py-8">
                  로딩 중...
                </TableCell>
              </TableRow>
            ) : data.length === 0 ? (
              <TableRow>
                <TableCell colSpan={columns.length} className="text-center py-8">
                  데이터가 없습니다.
                </TableCell>
              </TableRow>
            ) : (
              data.map((row) => (
                <TableRow
                  key={String(row[keyField])}
                  onClick={() => onRowClick?.(row)}
                  className={onRowClick ? "cursor-pointer hover:bg-muted/50" : ""}
                >
                  {columns.map((column) => {
                    const value = column.key in row ? row[column.key as keyof T] : null;
                    return (
                      <TableCell key={String(column.key)} className={column.className}>
                        {column.render ? column.render(value, row) : String(value)}
                      </TableCell>
                    );
                  })}
                </TableRow>
              ))
            )}
          </TableBody>
        </Table>
        
        {pagination && (
          <div className="flex items-center justify-between p-4 border-t">
            <p className="text-sm text-muted-foreground">
              페이지 {pagination.currentPage} / {pagination.totalPages}
            </p>
            <div className="flex gap-2">
              <Button
                variant="outline"
                size="sm"
                onClick={() => pagination.onPageChange(pagination.currentPage - 1)}
                disabled={pagination.currentPage === 1}
              >
                <ChevronLeft className="w-4 h-4" />
              </Button>
              <Button
                variant="outline"
                size="sm"
                onClick={() => pagination.onPageChange(pagination.currentPage + 1)}
                disabled={pagination.currentPage === pagination.totalPages}
              >
                <ChevronRight className="w-4 h-4" />
              </Button>
            </div>
          </div>
        )}
      </CardContent>
    </Card>
  );
}
```

#### 3.3 MobileCardList (신규)
```typescript
// components/organisms/MobileCardList/MobileCardList.tsx
import { MobileCard } from "./MobileCard";

interface MobileCardListProps<T> {
  data: T[];
  keyField: keyof T;
  renderCard: (item: T) => {
    title: string;
    subtitle?: string;
    fields: Array<{ label: string; value: string }>;
    actions?: Array<{ label: string; onClick: () => void; icon?: ReactNode }>;
  };
}

export function MobileCardList<T>({ data, keyField, renderCard }: MobileCardListProps<T>) {
  return (
    <div className="md:hidden space-y-3">
      {data.map((item) => {
        const cardProps = renderCard(item);
        return <MobileCard key={String(item[keyField])} {...cardProps} />;
      })}
    </div>
  );
}
```

#### 3.4 FormSection (신규)
```typescript
// components/organisms/FormSection/FormSection.tsx
import { Card, CardContent, CardHeader, CardTitle } from "@/components/atoms/card";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/atoms/collapsible";
import { ChevronDown, ChevronUp } from "lucide-react";
import { ReactNode, useState } from "react";

interface FormSectionProps {
  title: string;
  children: ReactNode;
  collapsible?: boolean;
  defaultOpen?: boolean;
}

export const FormSection = ({ 
  title, 
  children, 
  collapsible = false, 
  defaultOpen = true 
}: FormSectionProps) => {
  const [isOpen, setIsOpen] = useState(defaultOpen);

  if (!collapsible) {
    return (
      <Card>
        <CardHeader>
          <CardTitle>{title}</CardTitle>
        </CardHeader>
        <CardContent className="space-y-4">
          {children}
        </CardContent>
      </Card>
    );
  }

  return (
    <Collapsible open={isOpen} onOpenChange={setIsOpen}>
      <Card>
        <CollapsibleTrigger className="w-full">
          <CardHeader className="flex flex-row items-center justify-between space-y-0 cursor-pointer hover:bg-muted/50">
            <CardTitle>{title}</CardTitle>
            {isOpen ? (
              <ChevronUp className="w-5 h-5 text-muted-foreground" />
            ) : (
              <ChevronDown className="w-5 h-5 text-muted-foreground" />
            )}
          </CardHeader>
        </CollapsibleTrigger>
        <CollapsibleContent>
          <CardContent className="space-y-4">
            {children}
          </CardContent>
        </CollapsibleContent>
      </Card>
    </Collapsible>
  );
};
```

---

### 4. Templates (템플릿)

**새로 생성할 Templates**:

#### 4.1 ListPageTemplate
```typescript
// components/templates/ListPageTemplate/ListPageTemplate.tsx
import { PageLayout } from "@/components/organisms/PageLayout";
import { PageHeader } from "@/components/organisms/PageHeader";
import { SearchFilter } from "@/components/organisms/SearchFilter";
import { StatCards } from "@/components/organisms/StatCards";
import { DataTable } from "@/components/organisms/DataTable";
import { MobileCardList } from "@/components/organisms/MobileCardList";
import { EmptyState } from "@/components/organisms/EmptyState";
import { ReactNode } from "react";
import { LucideIcon } from "lucide-react";

interface ListPageTemplateProps<T> {
  // Header
  title: string;
  description?: string;
  icon?: LucideIcon;
  actions?: ReactNode;
  
  // Stats
  stats?: Array<{
    label: string;
    value: string | number;
    icon: LucideIcon;
    iconColor?: string;
    trend?: { value: string; isPositive: boolean };
  }>;
  
  // Search & Filter
  searchValue: string;
  onSearchChange: (value: string) => void;
  searchPlaceholder?: string;
  filterButton?: boolean;
  onFilterClick?: () => void;
  extraActions?: ReactNode;
  
  // Data
  data: T[];
  keyField: keyof T;
  loading?: boolean;
  
  // Table
  columns: Array<{
    key: keyof T | string;
    label: string;
    render?: (value: any, row: T) => ReactNode;
    className?: string;
  }>;
  onRowClick?: (row: T) => void;
  
  // Mobile
  renderMobileCard: (item: T) => {
    title: string;
    subtitle?: string;
    fields: Array<{ label: string; value: string }>;
    actions?: Array<{ label: string; onClick: () => void }>;
  };
  
  // Empty State
  emptyIcon?: LucideIcon;
  emptyTitle?: string;
  emptyDescription?: string;
  emptyAction?: { label: string; onClick: () => void };
  
  // Pagination
  pagination?: {
    currentPage: number;
    totalPages: number;
    onPageChange: (page: number) => void;
  };
}

export function ListPageTemplate<T>({
  title,
  description,
  icon,
  actions,
  stats,
  searchValue,
  onSearchChange,
  searchPlaceholder,
  filterButton,
  onFilterClick,
  extraActions,
  data,
  keyField,
  loading,
  columns,
  onRowClick,
  renderMobileCard,
  emptyIcon,
  emptyTitle,
  emptyDescription,
  emptyAction,
  pagination
}: ListPageTemplateProps<T>) {
  const isEmpty = !loading && data.length === 0;
  
  return (
    <PageLayout>
      <PageHeader
        title={title}
        description={description}
        icon={icon}
        actions={actions}
      />
      
      {stats && <StatCards stats={stats} />}
      
      <SearchFilter
        searchValue={searchValue}
        onSearchChange={onSearchChange}
        searchPlaceholder={searchPlaceholder}
        filterButton={filterButton}
        onFilterClick={onFilterClick}
        extraActions={extraActions}
      />
      
      {isEmpty ? (
        <EmptyState
          icon={emptyIcon || icon}
          title={emptyTitle || `${title} 데이터가 없습니다`}
          description={emptyDescription}
          action={emptyAction}
        />
      ) : (
        <>
          <DataTable
            columns={columns}
            data={data}
            keyField={keyField}
            onRowClick={onRowClick}
            loading={loading}
            pagination={pagination}
          />
          
          <MobileCardList
            data={data}
            keyField={keyField}
            renderCard={renderMobileCard}
          />
        </>
      )}
    </PageLayout>
  );
}
```

#### 4.2 FormPageTemplate
```typescript
// components/templates/FormPageTemplate/FormPageTemplate.tsx
import { PageLayout } from "@/components/organisms/PageLayout";
import { PageHeader } from "@/components/organisms/PageHeader";
import { FormSection } from "@/components/organisms/FormSection";
import { Button } from "@/components/atoms/button";
import { ReactNode } from "react";
import { LucideIcon } from "lucide-react";

interface FormSection {
  title: string;
  content: ReactNode;
  collapsible?: boolean;
  defaultOpen?: boolean;
}

interface FormPageTemplateProps {
  title: string;
  description?: string;
  icon?: LucideIcon;
  sections: FormSection[];
  onSave: () => void;
  onCancel: () => void;
  saveLabel?: string;
  saving?: boolean;
}

export const FormPageTemplate = ({
  title,
  description,
  icon,
  sections,
  onSave,
  onCancel,
  saveLabel = "저장",
  saving = false
}: FormPageTemplateProps) => {
  return (
    <PageLayout>
      <PageHeader
        title={title}
        description={description}
        icon={icon}
        actions={
          <>
            <Button variant="outline" onClick={onCancel} disabled={saving}>
              취소
            </Button>
            <Button onClick={onSave} disabled={saving}>
              {saving ? "저장 중..." : saveLabel}
            </Button>
          </>
        }
      />
      
      <div className="space-y-4">
        {sections.map((section, index) => (
          <FormSection
            key={index}
            title={section.title}
            collapsible={section.collapsible}
            defaultOpen={section.defaultOpen}
          >
            {section.content}
          </FormSection>
        ))}
      </div>
    </PageLayout>
  );
};
```

#### 4.3 DashboardTemplate
```typescript
// components/templates/DashboardTemplate/DashboardTemplate.tsx
import { PageLayout } from "@/components/organisms/PageLayout";
import { PageHeader } from "@/components/organisms/PageHeader";
import { StatCards } from "@/components/organisms/StatCards";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/atoms/card";
import { ReactNode } from "react";
import { LucideIcon } from "lucide-react";

interface DashboardSection {
  title: string;
  content: ReactNode;
  span?: "full" | "half";
}

interface DashboardTemplateProps {
  title: string;
  description?: string;
  icon?: LucideIcon;
  stats: Array<{
    label: string;
    value: string | number;
    icon: LucideIcon;
    iconColor?: string;
    trend?: { value: string; isPositive: boolean };
  }>;
  sections: DashboardSection[];
  quickActions?: Array<{
    label: string;
    description: string;
    icon: LucideIcon;
    onClick: () => void;
  }>;
}

export const DashboardTemplate = ({
  title,
  description,
  icon,
  stats,
  sections,
  quickActions
}: DashboardTemplateProps) => {
  return (
    <PageLayout>
      <PageHeader
        title={title}
        description={description}
        icon={icon}
      />
      
      <StatCards stats={stats} />
      
      {quickActions && (
        <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
          {quickActions.map((action, index) => {
            const Icon = action.icon;
            return (
              <Card
                key={index}
                className="cursor-pointer hover:bg-accent transition-colors"
                onClick={action.onClick}
              >
                <CardContent className="p-6">
                  <div className="flex items-center gap-4">
                    <div className="p-2 bg-primary/10 rounded-lg">
                      <Icon className="h-5 w-5 text-primary" />
                    </div>
                    <div>
                      <p className="font-medium">{action.label}</p>
                      <p className="text-sm text-muted-foreground">{action.description}</p>
                    </div>
                  </div>
                </CardContent>
              </Card>
            );
          })}
        </div>
      )}
      
      <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
        {sections.map((section, index) => (
          <Card
            key={index}
            className={section.span === "full" ? "lg:col-span-2" : ""}
          >
            <CardHeader>
              <CardTitle>{section.title}</CardTitle>
            </CardHeader>
            <CardContent>
              {section.content}
            </CardContent>
          </Card>
        ))}
      </div>
    </PageLayout>
  );
};
```

#### 4.4 TabbedPageTemplate
```typescript
// components/templates/TabbedPageTemplate/TabbedPageTemplate.tsx
import { PageLayout } from "@/components/organisms/PageLayout";
import { PageHeader } from "@/components/organisms/PageHeader";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/atoms/tabs";
import { ReactNode } from "react";
import { LucideIcon } from "lucide-react";

interface Tab {
  id: string;
  label: string;
  icon?: LucideIcon;
  content: ReactNode;
}

interface TabbedPageTemplateProps {
  title: string;
  description?: string;
  icon?: LucideIcon;
  actions?: ReactNode;
  tabs: Tab[];
  defaultTab?: string;
}

export const TabbedPageTemplate = ({
  title,
  description,
  icon,
  actions,
  tabs,
  defaultTab
}: TabbedPageTemplateProps) => {
  return (
    <PageLayout>
      <PageHeader
        title={title}
        description={description}
        icon={icon}
        actions={actions}
      />
      
      <Tabs defaultValue={defaultTab || tabs[0]?.id}>
        <TabsList>
          {tabs.map((tab) => {
            const Icon = tab.icon;
            return (
              <TabsTrigger key={tab.id} value={tab.id}>
                {Icon && <Icon className="w-4 h-4 mr-2" />}
                {tab.label}
              </TabsTrigger>
            );
          })}
        </TabsList>
        
        {tabs.map((tab) => (
          <TabsContent key={tab.id} value={tab.id}>
            {tab.content}
          </TabsContent>
        ))}
      </Tabs>
    </PageLayout>
  );
};
```

---

## 공통 템플릿

### 템플릿 사용 가이드

#### ListPageTemplate 사용 예시
```typescript
// pages/sales/CustomerManagement.tsx
import { ListPageTemplate } from "@/components/templates/ListPageTemplate";
import { Building, Plus } from "lucide-react";
import { Button } from "@/components/atoms/button";

export const CustomerManagement = () => {
  const [customers, setCustomers] = useState([]);
  const [searchTerm, setSearchTerm] = useState("");
  
  return (
    <ListPageTemplate
      title="거래처 관리"
      description="거래처 정보를 관리합니다"
      icon={Building}
      actions={
        <Button>
          <Plus className="w-4 h-4 mr-2" />
          거래처 등록
        </Button>
      }
      stats={[
        { label: "전체 거래처", value: 150, icon: Building, iconColor: "text-blue-600" },
        { label: "활성 거래처", value: 120, icon: Building, iconColor: "text-green-600" }
      ]}
      searchValue={searchTerm}
      onSearchChange={setSearchTerm}
      searchPlaceholder="거래처명, 담당자로 검색..."
      data={customers}
      keyField="id"
      columns={[
        { key: "code", label: "거래처코드" },
        { key: "name", label: "거래처명" },
        { key: "contact", label: "담당자" },
        { key: "phone", label: "연락처" },
        { 
          key: "status", 
          label: "상태",
          render: (value) => <StatusBadge status={value} />
        }
      ]}
      renderMobileCard={(item) => ({
        title: item.name,
        subtitle: item.contact,
        fields: [
          { label: "거래처코드", value: item.code },
          { label: "연락처", value: item.phone }
        ]
      })}
      emptyTitle="등록된 거래처가 없습니다"
      emptyAction={{ label: "첫 거래처 등록", onClick: handleNew }}
    />
  );
};
```

#### FormPageTemplate 사용 예시
```typescript
// pages/sales/CustomerForm.tsx
import { FormPageTemplate } from "@/components/templates/FormPageTemplate";
import { FormField } from "@/components/molecules/FormField";
import { Building } from "lucide-react";

export const CustomerForm = () => {
  const [formData, setFormData] = useState({});
  
  return (
    <FormPageTemplate
      title="거래처 등록"
      description="거래처 정보를 입력하세요"
      icon={Building}
      sections={[
        {
          title: "기본 정보",
          content: (
            <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
              <FormField label="거래처명" value={formData.name} onChange={(v) => {}} required />
              <FormField label="사업자번호" value={formData.bizNo} onChange={(v) => {}} />
            </div>
          )
        },
        {
          title: "담당자 정보",
          collapsible: true,
          content: (
            <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
              <FormField label="담당자명" value={formData.contact} onChange={(v) => {}} />
              <FormField label="연락처" value={formData.phone} onChange={(v) => {}} />
            </div>
          )
        }
      ]}
      onSave={handleSave}
      onCancel={handleCancel}
    />
  );
};
```

---

## 컴포넌트 통합 계획

### 통합 대상 컴포넌트

#### 1. BOM 관리 (3개 → 1개)

**통합 전**:
- BOMManagement.tsx
- BOMManagementEnhanced.tsx
- BOMManagementPage.tsx

**통합 후**: `BOMManagement.tsx` (Enhanced 버전 기반)

**이유**: 동일한 기능의 여러 버전이 존재. Enhanced 버전이 가장 완성도가 높음.

#### 2. Quote 관리 (3개 → 1개)

**통합 전**:
- QuoteManagement.tsx
- QuoteManagement3.tsx (List + Write)
- QuoteSimulation.tsx

**통합 후**: `QuoteManagement.tsx` (QuoteManagement3 기반, Simulation 기능 포함)

**이유**: QuoteManagement3가 List와 Write로 분리되어 있어 가장 체계적. Simulation은 하위 기능으로 통합.

#### 3. Item 관리 (5개 → 1개)

**통합 전**:
- ItemManagement.tsx ✅ (이미 PageLayout 적용)
- ItemManagementPage.tsx
- ItemManagementForm.tsx
- ItemManagementList.tsx
- ItemRegistration.tsx

**통합 후**: `ItemManagement.tsx` (현재 버전 유지)

**이유**: ItemManagement.tsx가 이미 완성도가 높고 PageLayout 적용됨.

#### 4. Inspection (3개 통합)

**통합 전**:
- IncomingInspection.tsx
- IncomingInspectionManagement.tsx

**통합 후**: `IncomingInspectionManagement.tsx`

**이유**: Management 버전이 더 완성도가 높음.

#### 5. Dashboard (개선)

**공통화 전**:
- SalesDashboard.tsx
- ProductionDashboard.tsx
- QualityDashboard.tsx
- MaterialDashboard.tsx
- PurchaseDashboard.tsx
- AccountingDashboard.tsx

**개선 후**: `DashboardTemplate` 사용

**이유**: 모든 대시보드가 동일한 패턴 (StatCards + Sections). 템플릿으로 통일.

#### 6. Code/Lot/InspectionStandard 관리

**통합 전**:
- CodeManagement.tsx
- CodeManagementPage.tsx

**통합 후**: `CodeManagementPage.tsx`

**동일 패턴**:
- LotManagement.tsx + LotManagementPage.tsx → LotManagementPage.tsx
- InspectionStandardManagement.tsx + InspectionStandardManagementPage.tsx → InspectionStandardManagementPage.tsx

---

## 마이그레이션 가이드

### 단계별 마이그레이션

#### Phase 1: 디렉토리 구조 재구성 (Week 1)

1. **atoms/ 폴더 생성**
   - `components/ui/` → `components/atoms/`로 이동
   
2. **molecules/ 폴더 생성 및 컴포넌트 작성**
   - SearchBar
   - StatCard
   - FormField
   - FilterButton
   - StatusBadge

3. **organisms/ 폴더 생성**
   - `components/common/` → `components/organisms/`로 이동
   - DataTable 추가
   - MobileCardList 추가
   - FormSection 추가

#### Phase 2: 템플릿 생성 (Week 2)

1. **templates/ 폴더 생성**
   - ListPageTemplate
   - FormPageTemplate
   - DashboardTemplate
   - TabbedPageTemplate

2. **hooks/ 폴더 생성**
   - useTableData
   - useFormValidation
   - usePagination
   - useSearch

#### Phase 3: 중복 컴포넌트 통합 (Week 3-4)

1. **BOM 관리 통합**
   - BOMManagementEnhanced.tsx를 기반으로 통합
   - 나머지 삭제

2. **Quote 관리 통합**
   - QuoteManagement3를 기반으로 통합
   - Simulation 기능 포함

3. **Item 관리 통합**
   - ItemManagement.tsx 유지
   - 나머지 삭제

4. **기타 중복 제거**
   - Inspection 관리
   - Code/Lot/InspectionStandard 관리

#### Phase 4: 페이지 리팩토링 (Week 5-8)

1. **우선순위 1: 핵심 페이지**
   - CustomerManagement
   - OrderManagement ✅ (완료)
   - ItemManagement ✅ (완료)

2. **우선순위 2: 자주 사용하는 페이지**
   - ReceivingManagement
   - ShippingManagement
   - PurchaseOrderManagement

3. **우선순위 3: 나머지 페이지**
   - 모든 Dashboard 페이지
   - 모든 Management 페이지

### 마이그레이션 체크리스트

**페이지별 체크리스트**:

- [ ] 적절한 Template 선택 (List/Form/Dashboard/Tabbed)
- [ ] PageHeader 사용
- [ ] StatCards 사용 (통계가 있는 경우)
- [ ] DataTable/MobileCardList 사용
- [ ] 공통 molecules 사용 (SearchBar, FormField 등)
- [ ] Custom hooks 사용 (useTableData, useSearch 등)
- [ ] TypeScript 타입 정의
- [ ] 반응형 디자인 확인
- [ ] 테스트 작성

### 롤백 계획

마이그레이션 중 문제 발생 시:

1. **Git 브랜치 전략**
   - main: 현재 안정 버전
   - develop: 마이그레이션 진행
   - feature/*: 개별 기능 마이그레이션

2. **단계별 커밋**
   - 각 컴포넌트 통합 시 커밋
   - 문제 발생 시 해당 커밋으로 롤백

3. **병렬 운영**
   - 기존 컴포넌트 유지
   - 새 컴포넌트를 _v2 suffix로 생성
   - 검증 후 기존 컴포넌트 교체

---

## Custom Hooks

### useTableData
```typescript
// components/hooks/useTableData.ts
export const useTableData = <T>(initialData: T[]) => {
  const [data, setData] = useState<T[]>(initialData);
  const [loading, setLoading] = useState(false);
  const [searchTerm, setSearchTerm] = useState("");
  const [sortConfig, setSortConfig] = useState<{
    key: keyof T;
    direction: "asc" | "desc";
  } | null>(null);

  const filteredData = useMemo(() => {
    if (!searchTerm) return data;
    
    return data.filter((item) =>
      Object.values(item).some((value) =>
        String(value).toLowerCase().includes(searchTerm.toLowerCase())
      )
    );
  }, [data, searchTerm]);

  const sortedData = useMemo(() => {
    if (!sortConfig) return filteredData;

    return [...filteredData].sort((a, b) => {
      const aValue = a[sortConfig.key];
      const bValue = b[sortConfig.key];

      if (aValue < bValue) return sortConfig.direction === "asc" ? -1 : 1;
      if (aValue > bValue) return sortConfig.direction === "asc" ? 1 : -1;
      return 0;
    });
  }, [filteredData, sortConfig]);

  return {
    data: sortedData,
    loading,
    searchTerm,
    setSearchTerm,
    sortConfig,
    setSortConfig,
    refresh: () => setData([...data])
  };
};
```

### usePagination
```typescript
// components/hooks/usePagination.ts
export const usePagination = <T>(data: T[], itemsPerPage: number = 10) => {
  const [currentPage, setCurrentPage] = useState(1);

  const totalPages = Math.ceil(data.length / itemsPerPage);
  
  const paginatedData = useMemo(() => {
    const start = (currentPage - 1) * itemsPerPage;
    const end = start + itemsPerPage;
    return data.slice(start, end);
  }, [data, currentPage, itemsPerPage]);

  return {
    currentPage,
    totalPages,
    paginatedData,
    goToPage: setCurrentPage,
    nextPage: () => setCurrentPage((prev) => Math.min(prev + 1, totalPages)),
    prevPage: () => setCurrentPage((prev) => Math.max(prev - 1, 1))
  };
};
```

---

## 파일 크기 최적화

### Code Splitting

```typescript
// App.tsx
import { lazy, Suspense } from "react";

// Lazy load pages
const CustomerManagement = lazy(() => import("./components/pages/sales/CustomerManagement"));
const OrderManagement = lazy(() => import("./components/pages/sales/OrderManagement"));

// In component
<Suspense fallback={<LoadingSpinner />}>
  <CustomerManagement />
</Suspense>
```

### Tree Shaking

```typescript
// ❌ 잘못된 import (전체 import)
import * as Icons from "lucide-react";

// ✅ 올바른 import (필요한 것만)
import { User, Settings, Mail } from "lucide-react";
```

---

## 성능 최적화

### Memoization

```typescript
// 컴포넌트 memoization
export const StatCard = memo(({ label, value, icon }: StatCardProps) => {
  // ...
});

// 값 memoization
const filteredData = useMemo(() => {
  return data.filter(item => item.status === "active");
}, [data]);

// 콜백 memoization
const handleClick = useCallback(() => {
  console.log("clicked");
}, []);
```

---

## 테스트 전략

### Unit Tests

```typescript
// StatCard.test.tsx
import { render, screen } from "@testing-library/react";
import { StatCard } from "./StatCard";
import { Users } from "lucide-react";

describe("StatCard", () => {
  it("renders label and value", () => {
    render(<StatCard label="Total Users" value={100} icon={Users} />);
    
    expect(screen.getByText("Total Users")).toBeInTheDocument();
    expect(screen.getByText("100")).toBeInTheDocument();
  });
});
```

---

## 문서화

### Storybook 도입 권장

```typescript
// StatCard.stories.tsx
import type { Meta, StoryObj } from "@storybook/react";
import { StatCard } from "./StatCard";
import { Users } from "lucide-react";

const meta: Meta<typeof StatCard> = {
  title: "Molecules/StatCard",
  component: StatCard,
};

export default meta;
type Story = StoryObj<typeof StatCard>;

export const Default: Story = {
  args: {
    label: "Total Users",
    value: 150,
    icon: Users,
    iconColor: "text-blue-600"
  }
};
```

---

## 마무리

### 핵심 원칙

1. **재사용성**: 모든 컴포넌트는 재사용 가능하도록 설계
2. **일관성**: 디자인 시스템을 따라 일관된 UI 제공
3. **확장성**: 새로운 기능 추가가 쉽도록 구조화
4. **유지보수성**: 코드 중복 최소화, 명확한 책임 분리
5. **성능**: 불필요한 렌더링 방지, 코드 스플리팅

### 다음 단계

1. Phase 1: 디렉토리 구조 재구성 시작
2. 템플릿 컴포넌트 개발
3. 우선순위 높은 페이지부터 리팩토링
4. 중복 컴포넌트 통합
5. 문서화 및 테스트 작성

---

**문서 버전**: 2.0.0  
**최종 수정일**: 2025년 10월 24일  
**작성자**: SAM MES 개발팀
