# ERP 시스템 UI/UX 디자인 가이드

## 📋 문서 목적
이 문서는 ERP 시스템의 새로운 페이지나 컴포넌트를 개발할 때 참고할 수 있는 **디자인 시스템 기준안**입니다.
일관된 사용자 경험을 위해 모든 개발자는 이 가이드를 따라 개발해주세요.

---

## 🎨 디자인 시스템 개요

### 디자인 철학
- **일관성**: 모든 페이지에서 동일한 패턴 사용
- **명확성**: 직관적이고 이해하기 쉬운 UI
- **접근성**: 모든 사용자가 쉽게 사용할 수 있도록
- **반응성**: 다양한 화면 크기에 대응

### 기술 스택
- **CSS Framework**: Tailwind CSS
- **UI Library**: shadcn/ui
- **Icons**: Lucide React

---

## 📐 페이지 기본 구조

### 1. 표준 페이지 레이아웃

```tsx
export default function YourPage() {
  return (
    <div className="min-h-screen bg-gray-50">
      <div className="w-full max-w-none px-4 py-8 space-y-8">
        {/* 페이지 제목 */}
        <div className="flex items-center justify-between bg-white rounded-lg shadow-sm border p-6">
          <div>
            <h1 className="text-3xl font-bold text-gray-900">페이지 제목</h1>
            <p className="mt-2 text-gray-600">페이지 설명</p>
          </div>
          <div className="flex gap-2">
            {/* 버튼들 */}
          </div>
        </div>

        {/* 메인 컨텐츠 */}
        <Card className="shadow-sm">
          <CardContent className="p-6">
            {/* 내용 */}
          </CardContent>
        </Card>
      </div>
    </div>
  );
}
```

### 2. 구조 설명

#### 최상위 래퍼
```tsx
<div className="min-h-screen bg-gray-50">
```
- `min-h-screen`: 최소 높이를 화면 전체로
- `bg-gray-50`: 연한 회색 배경 (전체 페이지 기본 배경)

#### 컨테이너
```tsx
<div className="w-full max-w-none px-4 py-8 space-y-8">
```
- `w-full max-w-none`: 전체 너비 사용
- `px-4`: 좌우 패딩 1rem (16px)
- `py-8`: 상하 패딩 2rem (32px)
- `space-y-8`: 하위 요소 간 수직 간격 2rem

#### 헤더 카드
```tsx
<div className="flex items-center justify-between bg-white rounded-lg shadow-sm border p-6">
  <div>
    <h1 className="text-3xl font-bold text-gray-900">제목</h1>
    <p className="mt-2 text-gray-600">설명</p>
  </div>
  <div className="flex gap-2">
    {/* 버튼들 */}
  </div>
</div>
```

---

## 🎯 컴포넌트 디자인 기준

### 1. 버튼

#### 주요 버튼 (Primary)
```tsx
<Button className="bg-orange-500 hover:bg-orange-600">
  <Plus className="w-4 h-4 mr-2" />
  버튼 텍스트
</Button>
```

#### 보조 버튼 (Secondary)
```tsx
<Button variant="outline" size="sm">
  <RefreshCw className="w-4 h-4 mr-2" />
  새로고침
</Button>
```

#### 위험 버튼 (Danger)
```tsx
<Button 
  variant="ghost" 
  className="text-red-500 hover:text-red-600"
>
  <Trash2 className="w-4 h-4" />
  삭제
</Button>
```

### 2. 카드 (Card)

#### 기본 카드
```tsx
<Card className="shadow-sm">
  <CardHeader>
    <CardTitle>카드 제목</CardTitle>
  </CardHeader>
  <CardContent className="p-6">
    {/* 내용 */}
  </CardContent>
</Card>
```

#### 강조 카드
```tsx
<Card className="bg-gradient-to-r from-orange-50 to-amber-50 border-orange-200 shadow-sm">
  <CardHeader>
    <CardTitle className="flex items-center">
      <Icon className="w-5 h-5 mr-2 text-orange-500" />
      제목
    </CardTitle>
  </CardHeader>
  <CardContent>
    <p className="text-gray-700">내용</p>
  </CardContent>
</Card>
```

### 3. 테이블

#### 기본 테이블 구조
```tsx
<Card className="shadow-sm">
  <CardContent className="p-6">
    <table className="w-full">
      <thead>
        <tr className="border-b border-gray-200/40 bg-gradient-to-r from-slate-50/90 to-gray-50/70">
          <th className="h-12 px-6 py-4 text-left text-sm font-semibold text-gray-700">
            컬럼명
          </th>
        </tr>
      </thead>
      <tbody>
        <tr className="h-12 border-b border-gray-100/60 hover:bg-gradient-to-r hover:from-orange-50/80 hover:to-orange-100/60">
          <td className="px-6 py-4 text-sm text-gray-600">
            데이터
          </td>
        </tr>
      </tbody>
    </table>
  </CardContent>
</Card>
```

### 4. 폼 (Form)

#### 입력 필드
```tsx
<div className="space-y-2">
  <label className="block text-sm font-medium text-gray-700">
    라벨
  </label>
  <input
    type="text"
    className="w-full px-3 py-2 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-orange-500 focus:border-orange-500 transition-all duration-200"
  />
</div>
```

#### 셀렉트
```tsx
<Select>
  <SelectTrigger className="w-48">
    <SelectValue placeholder="선택하세요" />
  </SelectTrigger>
  <SelectContent>
    <SelectItem value="1">옵션 1</SelectItem>
    <SelectItem value="2">옵션 2</SelectItem>
  </SelectContent>
</Select>
```

### 5. 빈 상태 (Empty State)

```tsx
<Card className="text-center py-16 bg-white shadow-sm">
  <CardContent className="pt-6">
    <Icon className="w-16 h-16 mx-auto mb-4 text-gray-300" />
    <p className="text-gray-500 mb-4">데이터가 없습니다</p>
    <Button className="bg-orange-500 hover:bg-orange-600">
      <Plus className="w-4 h-4 mr-2" />
      추가하기
    </Button>
  </CardContent>
</Card>
```

### 6. 로딩 상태

```tsx
<Card className="shadow-sm">
  <CardContent className="flex justify-center items-center py-16">
    <Loader2 className="w-8 h-8 animate-spin text-orange-500" />
  </CardContent>
</Card>
```

---

## 🎨 색상 시스템

### 주 색상 (Primary)
```css
orange-50   #fff7ed    /* 매우 연한 배경 */
orange-100  #ffedd5    /* 연한 배경 */
orange-500  #f97316    /* 주요 버튼, 강조 */
orange-600  #ea580c    /* 버튼 호버 */
```

### 회색 (Gray)
```css
gray-50     #f9fafb    /* 페이지 배경 */
gray-100    #f3f4f6    /* 카드 내부 구분 */
gray-200    #e5e7eb    /* 테두리 */
gray-300    #d1d5db    /* 입력 필드 테두리 */
gray-500    #6b7280    /* 보조 텍스트 */
gray-600    #4b5563    /* 일반 텍스트 */
gray-700    #374151    /* 라벨, 헤더 */
gray-800    #1f2937    /* 제목 */
gray-900    #111827    /* 주요 제목 */
```

### 상태 색상
```css
/* 성공 */
green-100   #dcfce7
green-500   #22c55e
green-700   #15803d

/* 경고 */
red-100     #fee2e2
red-500     #ef4444
red-600     #dc2626

/* 정보 */
blue-50     #eff6ff
blue-100    #dbeafe
blue-500    #3b82f6
```

---

## 📏 간격 시스템

### Spacing Scale
```css
space-y-2   0.5rem (8px)   /* 폼 요소 간 간격 */
space-y-4   1rem (16px)    /* 섹션 내부 간격 */
space-y-6   1.5rem (24px)  /* 카드 내부 큰 간격 */
space-y-8   2rem (32px)    /* 페이지 주요 섹션 간격 */

gap-2       0.5rem (8px)   /* 버튼 그룹 간격 */
gap-4       1rem (16px)    /* 카드 그리드 간격 */
gap-6       1.5rem (24px)  /* 큰 카드 그리드 간격 */
```

### Padding
```css
p-2         0.5rem (8px)   /* 작은 요소 */
p-4         1rem (16px)    /* 일반 요소 */
p-6         1.5rem (24px)  /* 카드, 헤더 */
p-8         2rem (32px)    /* 큰 영역 */

px-3        좌우 0.75rem   /* 입력 필드 */
px-4        좌우 1rem      /* 버튼 */
px-6        좌우 1.5rem    /* 테이블 셀 */

py-2        상하 0.5rem    /* 버튼 */
py-4        상하 1rem      /* 입력 필드 */
py-8        상하 2rem      /* 페이지 컨테이너 */
```

---

## 📝 타이포그래피

### 제목 (Headings)
```css
/* 페이지 제목 */
text-3xl font-bold text-gray-900
/* 예: 30px, Bold, #111827 */

/* 섹션 제목 */
text-2xl font-bold text-gray-900
/* 예: 24px, Bold */

/* 카드 제목 */
text-lg font-semibold text-gray-800
/* 예: 18px, Semi-bold */

/* 작은 제목 */
text-base font-medium text-gray-700
/* 예: 16px, Medium */
```

### 본문 (Body Text)
```css
/* 일반 텍스트 */
text-sm text-gray-600
/* 14px, #4b5563 */

/* 보조 설명 */
text-sm text-gray-500
/* 14px, #6b7280 */

/* 라벨 */
text-sm font-medium text-gray-700
/* 14px, Medium */
```

---

## 🎭 인터랙션 패턴

### 호버 효과
```css
/* 버튼 호버 */
hover:bg-orange-600
hover:shadow-md

/* 카드 호버 */
hover:shadow-lg transition-shadow

/* 테이블 행 호버 */
hover:bg-gradient-to-r hover:from-orange-50/80 hover:to-orange-100/60
```

### 포커스 효과
```css
/* 입력 필드 포커스 */
focus:outline-none 
focus:ring-2 
focus:ring-orange-500 
focus:border-orange-500
```

### 전환 효과
```css
/* 일반 전환 */
transition-all duration-200

/* 그림자 전환 */
transition-shadow

/* 색상 전환 */
transition-colors duration-200
```

---

## 🔲 그리드 시스템

### 반응형 그리드
```tsx
{/* 1열 → 2열 → 3열 */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
  {/* 카드들 */}
</div>

{/* 1열 → 2열 */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
  {/* 항목들 */}
</div>
```

### 브레이크포인트
```css
sm:   640px  @media (min-width: 640px)
md:   768px  @media (min-width: 768px)
lg:   1024px @media (min-width: 1024px)
xl:   1280px @media (min-width: 1280px)
2xl:  1536px @media (min-width: 1536px)
```

---

## 🎯 실전 예제

### 예제 1: 관리 페이지 (데이터 있음)

```tsx
export default function ManagementPage() {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(false);

  return (
    <div className="min-h-screen bg-gray-50">
      <div className="w-full max-w-none px-4 py-8 space-y-8">
        {/* 헤더 */}
        <div className="flex items-center justify-between bg-white rounded-lg shadow-sm border p-6">
          <div>
            <h1 className="text-3xl font-bold text-gray-900">데이터 관리</h1>
            <p className="mt-2 text-gray-600">시스템 데이터를 관리합니다</p>
          </div>
          <div className="flex gap-2">
            <Button variant="outline" size="sm" onClick={loadData}>
              <RefreshCw className="w-4 h-4 mr-2" />
              새로고침
            </Button>
            <Button className="bg-orange-500 hover:bg-orange-600">
              <Plus className="w-4 h-4 mr-2" />
              새로 추가
            </Button>
          </div>
        </div>

        {/* 통계 카드 */}
        <div className="grid grid-cols-1 md:grid-cols-4 gap-6">
          <Card className="shadow-sm">
            <CardContent className="p-6">
              <div className="flex items-center justify-between">
                <div>
                  <p className="text-sm text-gray-500">총 개수</p>
                  <p className="text-2xl font-bold text-gray-900">156</p>
                </div>
                <div className="bg-blue-100 p-3 rounded-lg">
                  <Database className="w-6 h-6 text-blue-500" />
                </div>
              </div>
            </CardContent>
          </Card>
          {/* 나머지 통계 카드들... */}
        </div>

        {/* 데이터 테이블 */}
        <Card className="shadow-sm">
          <CardContent className="p-6">
            <table className="w-full">
              <thead>
                <tr className="border-b border-gray-200/40 bg-gradient-to-r from-slate-50/90 to-gray-50/70">
                  <th className="h-12 px-6 py-4 text-left text-sm font-semibold text-gray-700">
                    이름
                  </th>
                  <th className="h-12 px-6 py-4 text-left text-sm font-semibold text-gray-700">
                    상태
                  </th>
                  <th className="h-12 px-6 py-4 text-left text-sm font-semibold text-gray-700">
                    작업
                  </th>
                </tr>
              </thead>
              <tbody>
                {data.map((item) => (
                  <tr 
                    key={item.id}
                    className="h-12 border-b border-gray-100/60 hover:bg-gradient-to-r hover:from-orange-50/80 hover:to-orange-100/60"
                  >
                    <td className="px-6 py-4 text-sm text-gray-600">
                      {item.name}
                    </td>
                    <td className="px-6 py-4">
                      <span className="px-2 py-1 text-xs rounded bg-green-100 text-green-700">
                        활성
                      </span>
                    </td>
                    <td className="px-6 py-4">
                      <div className="flex gap-2">
                        <Button size="sm" variant="outline">
                          수정
                        </Button>
                        <Button size="sm" variant="ghost" className="text-red-500">
                          삭제
                        </Button>
                      </div>
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          </CardContent>
        </Card>
      </div>
    </div>
  );
}
```

### 예제 2: 빈 상태 페이지

```tsx
export default function EmptyStatePage() {
  return (
    <div className="min-h-screen bg-gray-50">
      <div className="w-full max-w-none px-4 py-8 space-y-8">
        {/* 헤더 */}
        <div className="flex items-center justify-between bg-white rounded-lg shadow-sm border p-6">
          <div>
            <h1 className="text-3xl font-bold text-gray-900">데이터 관리</h1>
            <p className="mt-2 text-gray-600">시스템 데이터를 관리합니다</p>
          </div>
          <Button className="bg-orange-500 hover:bg-orange-600">
            <Plus className="w-4 h-4 mr-2" />
            새로 추가
          </Button>
        </div>

        {/* 빈 상태 */}
        <Card className="text-center py-16 bg-white shadow-sm">
          <CardContent className="pt-6">
            <Database className="w-16 h-16 mx-auto mb-4 text-gray-300" />
            <p className="text-gray-500 mb-4">아직 등록된 데이터가 없습니다</p>
            <Button className="bg-orange-500 hover:bg-orange-600">
              <Plus className="w-4 h-4 mr-2" />
              첫 데이터 추가하기
            </Button>
          </CardContent>
        </Card>

        {/* 안내 정보 */}
        <Card className="bg-gradient-to-r from-orange-50 to-amber-50 border-orange-200 shadow-sm">
          <CardHeader>
            <CardTitle className="text-lg flex items-center">
              <Info className="w-5 h-5 mr-2 text-orange-500" />
              데이터 관리 안내
            </CardTitle>
          </CardHeader>
          <CardContent>
            <p className="text-gray-700 mb-4">
              💡 데이터를 추가하여 시스템을 사용해보세요!
            </p>
            <ul className="space-y-2 text-sm text-gray-600">
              <li className="flex items-start">
                <span className="text-orange-500 mr-2">✓</span>
                <span>기능 설명 1</span>
              </li>
              <li className="flex items-start">
                <span className="text-orange-500 mr-2">✓</span>
                <span>기능 설명 2</span>
              </li>
            </ul>
          </CardContent>
        </Card>
      </div>
    </div>
  );
}
```

---

## ✅ 체크리스트

### 새 페이지 만들 때
- [ ] `min-h-screen bg-gray-50` 래퍼 사용
- [ ] 헤더 카드 (`bg-white rounded-lg shadow-sm border p-6`) 포함
- [ ] 제목은 `text-3xl font-bold text-gray-900`
- [ ] 설명은 `mt-2 text-gray-600`
- [ ] 주요 버튼은 `bg-orange-500 hover:bg-orange-600`
- [ ] 카드는 `shadow-sm` 클래스 포함
- [ ] 간격은 `space-y-8` 사용

### 새 컴포넌트 만들 때
- [ ] 일관된 패딩 사용 (`p-4`, `p-6`)
- [ ] 호버 효과 추가
- [ ] 전환 애니메이션 적용 (`transition-all duration-200`)
- [ ] 적절한 아이콘 사용 (Lucide React)
- [ ] 반응형 디자인 고려 (`md:`, `lg:`)

---

## 📚 참고 자료

### Tailwind CSS 공식 문서
- https://tailwindcss.com/docs

### shadcn/ui 컴포넌트
- https://ui.shadcn.com/

### Lucide 아이콘
- https://lucide.dev/icons/

---

## 📧 메일 관리 시스템 UI 개선사항

### 최근 업데이트 (2025-01-02)

#### 1. 메일 발송 페이지 헤더 개선
**변경 전:**
```tsx
<div className="flex items-center justify-between">
  <div className="flex items-center gap-3">
    <div className="p-3 bg-gradient-to-br from-blue-100 to-indigo-100 rounded-lg">
      <Send className="w-6 h-6 text-blue-600" />
    </div>
    <div>
      <h1 className="text-2xl font-bold text-gray-900">메일 발송</h1>
      <p className="text-sm text-gray-500">설명</p>
    </div>
  </div>
</div>
```

**변경 후 (표준 헤더 카드 적용):**
```tsx
<Card>
  <CardHeader>
    <CardTitle className="text-2xl">메일 발송</CardTitle>
    <p className="text-sm text-muted-foreground mt-1">
      템플릿을 선택하거나 직접 작성하여 메일을 발송하세요
    </p>
  </CardHeader>
</Card>
```

**개선 사항:**
- ✅ 불필요한 아이콘 제거 (종이비행기)
- ✅ 표준 Card 컴포넌트 사용으로 통일감 향상
- ✅ 다른 페이지와 동일한 헤더 스타일 적용

#### 2. 메일 내용 입력 개선
**변경 전:**
```tsx
<Textarea placeholder="메일 내용을 html로 작성하세요" />
```

**변경 후:**
```tsx
<Textarea 
  placeholder="메일 내용을 입력하세요

줄바꿈은 자동으로 처리됩니다."
/>
<p className="text-xs text-gray-500 mt-1">
  💡 일반 텍스트로 작성하면 자동으로 메일 형식으로 변환됩니다
</p>
```

**개선 사항:**
- ✅ HTML 지식 없이도 사용 가능
- ✅ 일반 텍스트 입력 후 자동 HTML 변환
- ✅ 사용자 친화적인 안내 메시지

#### 3. CC/BCC 기능 추가
**구현 내용:**
```tsx
{/* To 태그 입력 */}
<EmailTagInput
  tags={to}
  onTagsChange={setTo}
  placeholder="받는 사람 이메일"
/>

{/* CC 태그 입력 */}
<EmailTagInput
  tags={cc}
  onTagsChange={setCc}
  placeholder="참조 (선택사항)"
/>

{/* BCC 태그 입력 */}
<EmailTagInput
  tags={bcc}
  onTagsChange={setBcc}
  placeholder="숨은참조 (선택사항)"
/>
```

**특징:**
- ✅ 이메일 주소를 태그 형태로 시각화
- ✅ 쉼표로 구분하여 입력 가능
- ✅ 개별 삭제 가능

#### 4. 파일 첨부 기능 (Phase 1 완료)

**백엔드 구현:**
```typescript
// multer 설정
export const uploadMailAttachment = multer({
  storage,
  fileFilter,
  limits: {
    fileSize: 10 * 1024 * 1024, // 10MB 제한
    files: 5, // 최대 5개 파일
  },
});

// 발송 API
router.post(
  '/simple',
  uploadMailAttachment.array('attachments', 5),
  (req, res) => mailSendSimpleController.sendMail(req, res)
);
```

**보안 기능:**
- ✅ 위험한 파일 확장자 차단 (.exe, .bat, .cmd, .sh 등)
- ✅ 파일 크기 제한 (10MB)
- ✅ 파일 개수 제한 (최대 5개)
- ✅ 안전한 파일명 생성

**프론트엔드 구현 예정 (Phase 1-3):**
- 드래그 앤 드롭 파일 업로드
- 첨부된 파일 목록 표시
- 파일 삭제 기능
- 미리보기에 첨부파일 정보 표시

#### 5. 향후 작업 계획

**Phase 2: 보낸메일함 백엔드**
- 발송 이력 자동 저장 (JSON 파일)
- 발송 상태 관리 (성공/실패)
- 발송 이력 조회 API

**Phase 3: 보낸메일함 프론트엔드**
- `/admin/mail/sent` 페이지
- 발송 목록 테이블
- 상세보기 모달
- 재전송 기능

**Phase 4: 대시보드 통합**
- 대시보드에 "보낸메일함" 링크
- 실제 발송 통계 연동
- 최근 활동 목록

### 메일 시스템 UI 가이드

#### 이메일 태그 입력
```tsx
// 이메일 주소를 시각적으로 표시
<div className="flex flex-wrap gap-2">
  {tags.map((tag, index) => (
    <div 
      key={index}
      className="flex items-center gap-1 px-3 py-1.5 bg-blue-100 text-blue-700 rounded-md text-sm"
    >
      <Mail className="w-3 h-3" />
      {tag}
      <button onClick={() => removeTag(index)}>
        <X className="w-3 h-3" />
      </button>
    </div>
  ))}
</div>
```

#### 파일 첨부 영역 (예정)
```tsx
<div className="border-2 border-dashed border-gray-300 rounded-lg p-6 hover:border-orange-400 transition-colors">
  <input type="file" multiple className="hidden" />
  <div className="text-center">
    <Upload className="w-12 h-12 mx-auto text-gray-400" />
    <p className="mt-2 text-sm text-gray-600">
      파일을 드래그하거나 클릭하여 선택하세요
    </p>
    <p className="text-xs text-gray-500 mt-1">
      최대 5개, 각 10MB 이하
    </p>
  </div>
</div>
```

#### 발송 성공 토스트
```tsx
<div className="fixed top-4 right-4 bg-white rounded-lg shadow-lg border border-green-200 p-4">
  <div className="flex items-center gap-3">
    <CheckCircle className="w-5 h-5 text-green-500" />
    <div>
      <p className="font-medium text-gray-900">메일이 발송되었습니다</p>
      <p className="text-sm text-gray-600">
        {to.length}명에게 전송 완료
      </p>
    </div>
  </div>
</div>
```

---

**이 가이드를 따라 개발하면 일관되고 아름다운 UI를 만들 수 있습니다!** 🎨✨
