Files
sam-design/src/TABLE_DESIGN_SYSTEM.md
정재웅 060b9ce2ef 프로젝트 초기 설정 및 구조 추가
- Vite + React 프로젝트 구조 설정
- 불필요한 PDF 파일 삭제

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-02 13:01:43 +09:00

14 KiB

SAM MES 테이블 디자인 시스템

작성일: 2025년 10월 24일
버전: 1.0.0


📋 목차

  1. 개요
  2. 테이블 시스템 구조
  3. 셀 타입
  4. 컴포넌트 가이드
  5. 사용 예시
  6. Best Practices

개요

SAM MES의 모든 테이블은 통일된 디자인 시스템을 사용합니다. 컬럼과 데이터만 정의하면 자동으로 적절한 형태로 렌더링됩니다.

핵심 원칙

  1. 일관성: 모든 테이블이 동일한 디자인 패턴
  2. 타입 안정성: 셀 타입별 자동 렌더링
  3. 재사용성: 컬럼 정의만으로 테이블 완성
  4. 반응형: 데스크톱/모바일 자동 대응

테이블 시스템 구조

DataTable (Organism)
├── Column Definition (셀 타입 정의)
├── Cell Renderer (타입별 자동 렌더링)
│   ├── StatusBadge (Molecule)
│   ├── IconWithBadge (Molecule)
│   ├── TableActions (Molecule)
│   └── Badge (Atom)
└── Pagination (Optional)

셀 타입

지원하는 셀 타입

타입 설명 정렬 예시
text 일반 텍스트 좌측 "홍길동"
number 숫자 (자동 천 단위 구분) 우측 1,234
currency 통화 (₩ 표시) 우측 ₩1,234,567
date 날짜 좌측 2025-10-24
datetime 날짜 + 시간 좌측 2025-10-24 14:30
status 상태 배지 중앙 🟢 활성
badge 일반 배지 중앙 v1.0
icon 아이콘 중앙 📦
iconBadge 아이콘 + 배지 좌측 📦 완제품
actions 액션 버튼들 우측 👁️ ✏️ 🗑️
custom 커스텀 렌더링 설정 가능 (사용자 정의)

컴포넌트 가이드

1. DataTable (Organism)

통일된 테이블 컴포넌트

위치: /components/organisms/DataTable.tsx

Props:

interface DataTableProps<T> {
  columns: Column<T>[];      // 컬럼 정의
  data: T[];                 // 데이터
  keyField: keyof T;         // 고유 키 필드
  onRowClick?: (row: T) => void;  // 행 클릭 핸들러
  loading?: boolean;         // 로딩 상태
  emptyMessage?: string;     // 빈 메시지
  pagination?: {             // 페이지네이션
    currentPage: number;
    totalPages: number;
    onPageChange: (page: number) => void;
  };
  striped?: boolean;         // 줄무늬 (기본: false)
  hoverable?: boolean;       // 호버 효과 (기본: true)
  compact?: boolean;         // 컴팩트 모드 (기본: false)
}

Column 정의:

interface Column<T> {
  key: keyof T | string;     // 데이터 키
  label: string;             // 컬럼 헤더
  type?: CellType;           // 셀 타입
  width?: string;            // 너비
  align?: "left" | "center" | "right";  // 정렬
  
  // 타입별 설정
  statusConfig?: { showDot?: boolean };
  badgeConfig?: { variant?: string; className?: string };
  iconConfig?: { iconColor?: string; iconBgColor?: string; size?: "sm" | "md" | "lg" };
  currencyConfig?: { locale?: string; currency?: string };
  
  // 커스텀
  render?: (value: any, row: T) => ReactNode;
  format?: (value: any) => string | number;
}

2. StatusBadge (Molecule)

상태 표시 배지

위치: /components/molecules/StatusBadge.tsx

지원 상태:

// 프로세스 상태
"대기" | "진행중" | "완료" | "취소" | "반려"

// 문서 상태
"작성중" | "검토중" | "승인" | "보류"

// 품질 상태
"합격" | "부적합" | "조건부합격" | "검사중"

// 설비 상태
"정상" | "고장" | "점검중"

// 활성화 상태
"활성" | "비활성" | "사용" | "미사용" | "폐기"

// 생산 상태
"생산중" | "배송중" | "생산대기"

// 재고 상태
"입고" | "출고" | "재고부족"

사용법:

<StatusBadge status="활성" />
<StatusBadge status="진행중" showDot />

3. IconWithBadge (Molecule)

아이콘 + 라벨 + 배지 조합

위치: /components/molecules/IconWithBadge.tsx

사용법:

<IconWithBadge
  icon={Package}
  label="완제품"
  iconColor="text-blue-600"
  iconBgColor="bg-blue-100"
  badge={{ label: "신규", variant: "default" }}
  size="md"
/>

4. TableActions (Molecule)

테이블 액션 버튼 그룹

위치: /components/molecules/TableActions.tsx

지원 액션 타입:

  • view - 상세보기 (👁️)
  • edit - 수정 (✏️)
  • delete - 삭제 (🗑️)
  • copy - 복사 (📋)
  • download - 다운로드 (⬇️)
  • custom - 커스텀

레이아웃:

  • buttons - 버튼으로 표시
  • dropdown - 드롭다운 메뉴
  • auto - 자동 (3개 이하: 버튼, 4개 이상: 드롭다운)

사용법:

<TableActions
  actions={[
    { type: "view", onClick: handleView },
    { type: "edit", onClick: handleEdit },
    { type: "delete", onClick: handleDelete, variant: "destructive" }
  ]}
  layout="auto"
/>

사용 예시

예시 1: 기본 테이블

import { DataTable, Column } from "./organisms/DataTable";

interface Product {
  id: string;
  code: string;
  name: string;
  price: number;
  status: string;
  updatedAt: string;
}

function ProductList() {
  const columns: Column<Product>[] = [
    { key: "code", label: "제품코드", type: "text" },
    { key: "name", label: "제품명", type: "text" },
    { key: "price", label: "가격", type: "currency" },
    { key: "status", label: "상태", type: "status" },
    { key: "updatedAt", label: "수정일", type: "date" }
  ];

  return (
    <DataTable
      columns={columns}
      data={products}
      keyField="id"
    />
  );
}

예시 2: 액션 버튼이 있는 테이블

const columns: Column<Product>[] = [
  { key: "code", label: "제품코드", type: "text" },
  { key: "name", label: "제품명", type: "text" },
  { key: "price", label: "가격", type: "currency" },
  { key: "status", label: "상태", type: "status" },
  {
    key: "id",
    label: "관리",
    type: "actions",
    render: (_, row) => [
      { type: "view", onClick: () => handleView(row) },
      { type: "edit", onClick: () => handleEdit(row) },
      { type: "delete", onClick: () => handleDelete(row), variant: "destructive" }
    ]
  }
];

예시 3: 아이콘 + 배지

const columns: Column<Item>[] = [
  {
    key: "type",
    label: "품목유형",
    type: "iconBadge",
    render: (value) => ({
      icon: getItemIcon(value),
      label: getItemLabel(value),
      badge: { label: "신규", variant: "default" }
    })
  }
];

예시 4: 커스텀 렌더링

const columns: Column<Order>[] = [
  {
    key: "customer",
    label: "고객사",
    type: "custom",
    render: (value, row) => (
      <div>
        <p className="font-medium">{value}</p>
        <p className="text-xs text-muted-foreground">{row.customerCode}</p>
      </div>
    )
  }
];

예시 5: 페이지네이션

const { currentPage, totalPages, paginatedData, goToPage } = usePagination(data, 10);

<DataTable
  columns={columns}
  data={paginatedData}
  keyField="id"
  pagination={{
    currentPage,
    totalPages,
    onPageChange: goToPage
  }}
/>

예시 6: 다양한 옵션

<DataTable
  columns={columns}
  data={data}
  keyField="id"
  striped              // 줄무늬
  compact              // 컴팩트 모드
  hoverable={false}    // 호버 효과 비활성화
  loading={isLoading}  // 로딩 상태
  emptyMessage="검색 결과가 없습니다."
  onRowClick={handleRowClick}  // 행 클릭
/>

Best Practices

1. 올바른 타입 선택

// ✅ 좋은 예
{ key: "price", label: "가격", type: "currency" }
{ key: "quantity", label: "수량", type: "number" }
{ key: "status", label: "상태", type: "status" }
{ key: "createdAt", label: "생성일", type: "date" }

// ❌ 나쁜 예
{ key: "price", label: "가격", type: "text" }  // 통화 형식 미적용
{ key: "status", label: "상태", type: "text" }  // 배지 미적용

2. 액션 버튼 정리

// ✅ 좋은 예 - 타입 시스템 활용
{
  key: "id",
  label: "관리",
  type: "actions",
  render: (_, row) => [
    { type: "view", onClick: () => handleView(row) },
    { type: "edit", onClick: () => handleEdit(row) },
    { type: "delete", onClick: () => handleDelete(row), variant: "destructive" }
  ]
}

// ❌ 나쁜 예 - 직접 구현
{
  key: "id",
  label: "관리",
  render: (_, row) => (
    <div className="flex gap-2">
      <Button onClick={() => handleView(row)}>보기</Button>
      <Button onClick={() => handleEdit(row)}>수정</Button>
      <Button onClick={() => handleDelete(row)}>삭제</Button>
    </div>
  )
}

3. 상태 통일

// ✅ 좋은 예 - StatusBadge 활용
{ key: "status", label: "상태", type: "status" }

// ❌ 나쁜 예 - 직접 구현
{
  key: "status",
  render: (value) => (
    <Badge className={value === "활성" ? "bg-green-100" : "bg-gray-100"}>
      {value}
    </Badge>
  )
}

4. 정렬 기본값

타입별 자동 정렬을 활용하세요:

  • text: 좌측 정렬
  • number, currency, actions: 우측 정렬
  • status, badge, icon: 중앙 정렬
// ✅ 자동 정렬 활용
{ key: "price", label: "가격", type: "currency" }  // 자동 우측 정렬

// ⚠️ 필요시에만 명시
{ key: "price", label: "가격", type: "currency", align: "left" }  // 수동 좌측 정렬

5. 반응형 대응

데스크톱과 모바일을 함께 고려하세요:

// 데스크톱: DataTable
<DataTable
  columns={columns}
  data={data}
  keyField="id"
/>

// 모바일: MobileCard
<div className="md:hidden space-y-3">
  {data.map((item) => (
    <MobileCard
      key={item.id}
      title={item.name}
      subtitle={item.code}
      fields={[...]}
    />
  ))}
</div>

마이그레이션 가이드

기존 테이블 → 새 테이블 시스템

Before:

<Table>
  <TableHeader>
    <TableRow>
      <TableHead>제품코드</TableHead>
      <TableHead>제품명</TableHead>
      <TableHead>가격</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    {data.map((row) => (
      <TableRow key={row.id}>
        <TableCell>{row.code}</TableCell>
        <TableCell>{row.name}</TableCell>
        <TableCell>{row.price.toLocaleString()}</TableCell>
      </TableRow>
    ))}
  </TableBody>
</Table>

After:

const columns: Column<Product>[] = [
  { key: "code", label: "제품코드", type: "text" },
  { key: "name", label: "제품명", type: "text" },
  { key: "price", label: "가격", type: "currency" }
];

<DataTable columns={columns} data={data} keyField="id" />

효과:

  • 코드 라인 수: 20+ 라인 → 5 라인 (75% 감소)
  • 일관성: 자동으로 통일된 디자인 적용
  • 유지보수: 컬럼 정의만 수정

컴포넌트 계층 구조

Atoms (UI 기본 컴포넌트)
├── Badge
├── Button
└── Table Components

Molecules (작은 조합)
├── StatusBadge          ⭐ 상태 배지
├── IconWithBadge        ⭐ 아이콘 + 배지
└── TableActions         ⭐ 액션 버튼 그룹

Organisms (복잡한 조합)
└── DataTable            ⭐ 통합 테이블

Templates (페이지 레이아웃)
└── ListPageTemplate     ⭐ 목록 페이지 (DataTable 포함)

적용 대상 페이지

즉시 적용 가능 (40개 이상)

관리 페이지:

  • CustomerManagement
  • SupplierManagement
  • OrderManagement
  • PurchaseOrderManagement
  • ItemManagement
  • EquipmentManagement
  • VehicleManagement
  • EmployeeManagement
  • DepartmentManagement

재고 관리:

  • InventoryManagement
  • ReceivingManagement
  • ShippingManagement
  • StockStatus

생산 관리:

  • ProductionManagement
  • WorkOrderManagement
  • ProcessManagement

품질 관리:

  • IncomingInspectionManagement
  • ProcessInspectionManagement
  • FinalInspectionManagement
  • NonconformingManagement

회계/영업:

  • SalesAccountingManagement
  • PurchaseAccountingManagement
  • QuoteManagement
  • PricingManagement

FAQ

Q1. 커스텀 렌더링과 타입 시스템 중 어떤 것을 사용해야 하나요?

A: 가능하면 타입 시스템을 우선 사용하세요. 커스텀 렌더링은 정말 특수한 경우에만 사용합니다.

// ✅ 타입 시스템 활용 (권장)
{ key: "status", label: "상태", type: "status" }

// ⚠️ 커스텀 렌더링 (특수한 경우만)
{
  key: "complex",
  render: (value, row) => (
    <div>
      <p>{value}</p>
      <p className="text-xs">{row.detail}</p>
    </div>
  )
}

Q2. 액션 버튼이 4개 이상일 때 어떻게 표시되나요?

A: 자동으로 드롭다운 메뉴로 변환됩니다.

// 3개 이하: 버튼으로 표시
// 4개 이상: 자동으로 드롭다운 메뉴로 변환
layout="auto"  // 기본값

Q3. 테이블 행을 클릭 가능하게 만들려면?

A: onRowClick prop을 사용하세요.

<DataTable
  columns={columns}
  data={data}
  keyField="id"
  onRowClick={(row) => navigate(`/detail/${row.id}`)}
/>

Q4. 로딩 상태는 어떻게 표시하나요?

A: loading prop을 사용하세요.

<DataTable
  columns={columns}
  data={data}
  keyField="id"
  loading={isLoading}
/>

변경 이력

버전 날짜 변경 내용
1.0.0 2025-10-24 초기 버전 생성

문서 버전: 1.0.0
최종 업데이트: 2025년 10월 24일
작성자: SAM MES 개발팀