ERP-node/UI_개선사항_문서.md

16 KiB

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

📋 문서 목적

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


🎨 디자인 시스템 개요

디자인 철학

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

기술 스택

  • CSS Framework: Tailwind CSS
  • UI Library: shadcn/ui
  • Icons: Lucide React

📐 페이지 기본 구조

1. 표준 페이지 레이아웃

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. 구조 설명

최상위 래퍼

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

컨테이너

<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

헤더 카드

<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)

<Button className="bg-orange-500 hover:bg-orange-600">
  <Plus className="w-4 h-4 mr-2" />
  버튼 텍스트
</Button>

보조 버튼 (Secondary)

<Button variant="outline" size="sm">
  <RefreshCw className="w-4 h-4 mr-2" />
  새로고침
</Button>

위험 버튼 (Danger)

<Button 
  variant="ghost" 
  className="text-red-500 hover:text-red-600"
>
  <Trash2 className="w-4 h-4" />
  삭제
</Button>

2. 카드 (Card)

기본 카드

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

강조 카드

<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. 테이블

기본 테이블 구조

<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)

입력 필드

<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>

셀렉트

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

5. 빈 상태 (Empty State)

<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. 로딩 상태

<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)

orange-50   #fff7ed    /* 매우 연한 배경 */
orange-100  #ffedd5    /* 연한 배경 */
orange-500  #f97316    /* 주요 버튼, 강조 */
orange-600  #ea580c    /* 버튼 호버 */

회색 (Gray)

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    /* 주요 제목 */

상태 색상

/* 성공 */
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

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

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)

/* 페이지 제목 */
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)

/* 일반 텍스트 */
text-sm text-gray-600
/* 14px, #4b5563 */

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

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

🎭 인터랙션 패턴

호버 효과

/* 버튼 호버 */
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

포커스 효과

/* 입력 필드 포커스 */
focus:outline-none 
focus:ring-2 
focus:ring-orange-500 
focus:border-orange-500

전환 효과

/* 일반 전환 */
transition-all duration-200

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

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

🔲 그리드 시스템

반응형 그리드

{/* 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>

브레이크포인트

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: 관리 페이지 (데이터 있음)

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: 빈 상태 페이지

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 공식 문서

shadcn/ui 컴포넌트

Lucide 아이콘


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