feat: 단가관리 페이지 마이그레이션 및 HR 관리 기능 추가

## 단가관리 (Pricing Management)
- 단가 목록 페이지 (IntegratedListTemplateV2 공통 템플릿 적용)
- 단가 등록/수정 폼 (원가/마진 자동 계산)
- 이력 조회, 수정 이력, 최종 확정 다이얼로그
- 판매관리 > 단가관리 네비게이션 메뉴 추가

## HR 관리 (Human Resources)
- 사원관리 (목록, 등록, 수정, 상세, CSV 업로드)
- 부서관리 (트리 구조)
- 근태관리 (기본 구조)

## 품목관리 개선
- Radix UI Select controlled mode 버그 수정 (key prop 적용)
- DynamicItemForm 파일 업로드 지원
- 수정 페이지 데이터 로딩 개선

## 문서화
- 단가관리 마이그레이션 체크리스트
- HR 관리 구현 체크리스트
- Radix UI Select 버그 수정 가이드

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
byeongcheolryu
2025-12-06 11:36:38 +09:00
parent 751e65f59b
commit 48dbba0e5f
59 changed files with 9888 additions and 101 deletions

View File

@@ -0,0 +1,319 @@
# 부서관리 화면 구현 체크리스트
> **생성일**: 2025-12-05
> **상태**: 구현 완료 (API 연동 대기)
> **경로**: `인사관리 > 부서관리`
> **테스트 URL**: `http://localhost:3000/ko/hr/department-management`
---
## 0. 핵심 요구사항
### 트리 구조 - 무제한 깊이
```
회사명
├── 부서A
│ ├── 팀A-1
│ │ ├── 파트A-1-1
│ │ │ └── ... (무제한)
│ │ └── 파트A-1-2
│ └── 팀A-2
├── 부서B
│ └── 팀B-1
└── 부서C
```
- **깊이 제한 없음**: 부서 > 하위부서 > 하위부서 > ... 무한 재귀 가능
- **재귀적 데이터 구조**: `children` 배열로 무한 중첩 표현
- **동적 들여쓰기**: depth에 따라 padding-left 계산 (`depth * 24px` 등)
- **+/- 토글**: 하위 항목이 있는 모든 노드에 펼침/접힘 버튼 표시
---
## 1. 화면 분석 요약
### 1.1 스크린샷 기반 기능 정리
| 번호 | 구분 | 기능 | 설명 |
|------|------|------|------|
| 01 | 전체 선택 | 체크박스 | 전체 선택/해제 토글, 디폴트 해제 |
| 02 | 개별 선택 | 체크박스 | 개별 선택/해제 토글, 디폴트 해제 |
| 03 | 추가 버튼 | 상단 액션 | 관리 권한 없으면 숨김, 선택한 부서의 하위 부서 일괄 생성 |
| 04 | 삭제 버튼 | 상단 액션 | 관리 권한 없으면 숨김, "선택한 부서 N개를 삭제하시겠습니까?" Alert |
| 05 | 축소 버튼 | 트리 (-) | 클릭 시 확대 버튼으로 변경, 하위 부서 숨김 |
| 06 | 확대 버튼 | 트리 (+) | 클릭 시 축소 버튼으로 변경, 하위 부서 표시 |
| 07 | 추가 버튼 | 행 호버 | 관리 권한 없으면 숨김, 부서 추가 팝업 표시 |
| 08 | 수정 버튼 | 행 호버 | 관리 권한 없으면 숨김, 부서 수정 팝업 표시 |
| 09 | 삭제 버튼 | 행 호버 | 관리 권한 없으면 숨김, "{부서명} 부서를 삭제하시겠습니까?" Alert |
### 1.2 팝업 구성
| 팝업 | 구성요소 | 비고 |
|------|----------|------|
| 부서 추가 | 부서명 인풋박스 + 취소/등록 버튼 | 기존 부서명 표시, 수정 가능 |
| 부서 수정 | 부서명 인풋박스 + 취소/수정 버튼 | 기존 부서명 표시 |
### 1.3 삭제 로직
- 삭제 시 해당 부서의 인원은 **회사(기본) 인원으로 변경**됨
---
## 2. 컴포넌트 구조
```
src/
├── app/[locale]/(protected)/hr/
│ └── department-management/
│ └── page.tsx # 페이지 진입점
└── components/hr/
└── DepartmentManagement/
├── index.tsx # 메인 컴포넌트
├── DepartmentStats.tsx # 전체 부서 카운트 카드
├── DepartmentToolbar.tsx # 검색 + 추가/삭제 버튼
├── DepartmentTree.tsx # 트리 구조 테이블
├── DepartmentTreeItem.tsx # 트리 행 (재귀)
├── DepartmentDialog.tsx # 추가/수정 팝업
└── types.ts # 타입 정의
```
---
## 3. 타입 정의
```typescript
// types.ts
/**
* 부서 데이터 (무제한 깊이 재귀 구조)
* - depth: 렌더링 시 들여쓰기 계산용 (0부터 시작, 제한 없음)
* - children: 하위 부서 배열 (재귀적으로 무한 중첩 가능)
*/
interface Department {
id: number;
name: string;
parentId: number | null;
depth: number; // 깊이 (0: 최상위, 1, 2, 3, ... 무제한)
children?: Department[]; // 하위 부서 (재귀 - 무제한 깊이)
}
interface DepartmentDialogProps {
isOpen: boolean;
onOpenChange: (open: boolean) => void;
mode: 'add' | 'edit';
parentDepartment?: Department; // 추가 시 부모 부서
department?: Department; // 수정 시 대상 부서
onSubmit: (name: string) => void;
}
/**
* 트리 아이템 Props (재귀 렌더링)
*/
interface DepartmentTreeItemProps {
department: Department;
depth: number; // 현재 깊이 (들여쓰기 계산용)
isExpanded: boolean; // 펼침 상태
isSelected: boolean; // 선택 상태
onToggleExpand: (id: number) => void;
onToggleSelect: (id: number) => void;
onAdd: (parentId: number) => void;
onEdit: (department: Department) => void;
onDelete: (department: Department) => void;
expandedIds: Set<number>; // 전체 펼침 상태 (하위 재귀용)
selectedIds: Set<number>; // 전체 선택 상태 (하위 재귀용)
}
```
---
## 4. 구현 체크리스트
### Phase 1: 기본 구조 설정
- [x] `types.ts` - Department 타입 및 Props 인터페이스 정의
- [x] `page.tsx` - 라우트 페이지 생성 (`/hr/department-management`)
- [x] `index.tsx` - DepartmentManagement 메인 컴포넌트 (PageLayout + PageHeader)
### Phase 2: 상단 영역 구현
- [x] `DepartmentStats.tsx` - 전체 부서 카운트 카드 ("전체 부서 N개")
- [x] `DepartmentToolbar.tsx` - 검색창 구현
- [x] `DepartmentToolbar.tsx` - 선택 카운트 표시 ("총 N건 | N건 선택")
- [x] `DepartmentToolbar.tsx` - 추가 버튼 (선택한 부서 하위에 일괄 추가)
- [x] `DepartmentToolbar.tsx` - 삭제 버튼 (선택한 부서 일괄 삭제)
### Phase 3: 트리 테이블 구현
- [x] `DepartmentTree.tsx` - 테이블 헤더 (전체 선택 체크박스 + 부서명 + 작업)
- [x] `DepartmentTreeItem.tsx` - 트리 행 기본 구조 (체크박스 + 들여쓰기 + 부서명)
- [x] `DepartmentTreeItem.tsx` - +/- 버튼 (하위 부서 펼침/접힘)
- [x] `DepartmentTreeItem.tsx` - 호버 시 작업 버튼 표시 (추가, 수정, 삭제)
- [x] `DepartmentTreeItem.tsx` - 재귀 렌더링 (하위 부서 표시)
### Phase 4: 다이얼로그 구현
- [x] `DepartmentDialog.tsx` - 부서 추가 팝업 (부서명 입력 + 취소/등록)
- [x] `DepartmentDialog.tsx` - 부서 수정 팝업 (부서명 입력 + 취소/수정)
- [x] AlertDialog - 단일 삭제 확인 ("{부서명} 부서를 삭제하시겠습니까?")
- [x] AlertDialog - 일괄 삭제 확인 ("선택한 부서 N개를 삭제하시겠습니까?")
### Phase 5: 상태 관리 & 인터랙션
- [x] 체크박스 선택 상태 관리 (selectedIds: Set)
- [x] 전체 선택/해제 로직 구현
- [x] 트리 펼침/접힘 상태 관리 (expandedIds: Set)
- [ ] 검색 필터링 로직 구현 (TODO: 추후 구현)
- [x] 목업 데이터 생성 및 연동
### Phase 6: 스타일 및 마무리
- [x] 기존 프로젝트 디자인 패턴 적용 확인
- [x] 반응형 레이아웃 확인 (모바일/데스크톱)
- [x] 호버 애니메이션 및 트랜지션 확인
- [ ] 접근성 확인 (키보드 네비게이션) (TODO: 추후 보완)
---
## 5. 디자인 기준 (기존 프로젝트 패턴)
### 5.1 레이아웃
- **페이지 패딩**: `p-4 md:p-6`
- **섹션 간격**: `space-y-4 md:space-y-6`
- **카드**: `border rounded-lg p-4`
### 5.2 컴포넌트
| 요소 | 컴포넌트 | 비고 |
|------|----------|------|
| 페이지 래퍼 | `PageLayout` | maxWidth="full" |
| 헤더 | `PageHeader` | icon + title + description |
| 카드 | `Card` | 전체 부서 카운트 |
| 입력창 | `Input` | 검색, 부서명 |
| 버튼 | `Button` | variant: default, outline, ghost, destructive |
| 체크박스 | `Checkbox` | 전체/개별 선택 |
| 팝업 | `Dialog` | 추가/수정 |
| 확인창 | `AlertDialog` | 삭제 확인 |
### 5.3 아이콘 (lucide-react)
- `Building2` - 페이지 아이콘
- `Search` - 검색
- `Plus` - 추가
- `Trash2` - 삭제
- `Pencil` - 수정
- `ChevronRight` / `ChevronDown` - 트리 펼침/접힘
- `Minus` - 트리 접힘 버튼
### 5.4 인터랙션
- **행 호버**: `hover:bg-gray-50 transition-colors`
- **작업 버튼**: 호버 시에만 표시 (`opacity-0 group-hover:opacity-100`)
---
## 6. 목업 데이터
```typescript
/**
* 무제한 깊이 트리 구조 목업 데이터
* - depth 0: 회사
* - depth 1: 부서
* - depth 2: 팀
* - depth 3+: 파트, 셀, ... (무제한)
*/
const mockDepartments: Department[] = [
{
id: 1,
name: '회사명',
parentId: null,
depth: 0,
children: [
{
id: 2,
name: '경영지원본부',
parentId: 1,
depth: 1,
children: [
{
id: 4,
name: '인사팀',
parentId: 2,
depth: 2,
children: [
{
id: 7,
name: '채용파트',
parentId: 4,
depth: 3,
children: [
{ id: 10, name: '신입채용셀', parentId: 7, depth: 4, children: [] },
{ id: 11, name: '경력채용셀', parentId: 7, depth: 4, children: [] },
]
},
{ id: 8, name: '교육파트', parentId: 4, depth: 3, children: [] },
]
},
{ id: 5, name: '총무팀', parentId: 2, depth: 2, children: [] },
]
},
{
id: 3,
name: '개발본부',
parentId: 1,
depth: 1,
children: [
{ id: 6, name: '프론트엔드팀', parentId: 3, depth: 2, children: [] },
{ id: 9, name: '백엔드팀', parentId: 3, depth: 2, children: [] },
]
},
]
}
];
/**
* 전체 부서 수 계산 유틸리티 (재귀)
*/
const countAllDepartments = (departments: Department[]): number => {
return departments.reduce((count, dept) => {
return count + 1 + (dept.children ? countAllDepartments(dept.children) : 0);
}, 0);
};
/**
* 모든 부서 ID 추출 유틸리티 (재귀 - 전체 선택용)
*/
const getAllDepartmentIds = (departments: Department[]): number[] => {
return departments.flatMap(dept => [
dept.id,
...(dept.children ? getAllDepartmentIds(dept.children) : [])
]);
};
```
---
## 7. 참고 파일
| 파일 | 참고 내용 |
|------|-----------|
| `src/components/items/ItemMasterDataManagement.tsx` | 전체 구조 패턴 |
| `src/components/organisms/PageHeader.tsx` | 헤더 컴포넌트 |
| `src/components/organisms/PageLayout.tsx` | 레이아웃 컴포넌트 |
| `src/components/ui/dialog.tsx` | 다이얼로그 사용법 |
| `src/components/ui/alert-dialog.tsx` | 확인 다이얼로그 |
| `src/components/ui/checkbox.tsx` | 체크박스 |
---
## 8. 진행 로그
| 날짜 | 작업 내용 | 상태 |
|------|-----------|------|
| 2025-12-05 | 계획서 작성 | 완료 |
| 2025-12-05 | Phase 1: 기본 구조 (types, page, index) | 완료 |
| 2025-12-05 | Phase 2: 상단 영역 (Stats, Toolbar) | 완료 |
| 2025-12-05 | Phase 3: 트리 테이블 (Tree, TreeItem) | 완료 |
| 2025-12-05 | Phase 4: 다이얼로그 (Dialog, AlertDialog) | 완료 |
| 2025-12-05 | Phase 5: 상태 관리 (선택, 펼침, 추가/수정/삭제) | 완료 |
| 2025-12-05 | 빌드 테스트 | 성공 |
---
## 9. 이슈 및 메모
- API 연동 없이 화면만 먼저 구현 (목업 데이터 사용)
- 권한 체크 로직은 추후 API 연동 시 구현 예정
- 삭제 시 인원 이동 로직은 백엔드 API 의존

View File

@@ -0,0 +1,143 @@
# 사원관리 (Employee Management) 구현 체크리스트
## 개요
- **위치**: 인사관리 > 사원관리
- **경로**: `/[locale]/(protected)/hr/employee-management`
- **생성일**: 2025-12-05
- **상태**: 🔄 진행중
## 화면 구성 분석 (스크린샷 기반)
### 1. 메인 목록 화면
- 통계 카드: 재직(55명), 휴직(5명), 퇴직(1명), 평균근속년수(5.3년)
- 날짜 필터 (기간 선택)
- 액션 버튼: CSV 일괄등록, 사원등록, 사용자 초대
- 검색 및 필터 (전체/사용자 아이디 보유 등)
- 정렬 옵션
- 테이블 컬럼: No, 사원코드, 부서, 직책, 이름, 직급, 휴대폰, 이메일, 입사일, 상태, 사용자아이디, 권한, 작업
### 2. 사원 등록/수정 다이얼로그
- **기본 사원정보**: 이름*, 주민등록번호, 휴대폰, 이메일, 연봉, 급여계좌(은행, 계좌, 예금주)
- **선택적 필드** (설정에 따라 표시): 프로필사진, 사원코드, 성별, 주소(우편번호 찾기)
- **인사정보**: 입사일, 고용형태, 직급, 상태, 부서/직책 (복수 가능)
- **사용자 정보**: 아이디*, 비밀번호*, 권한, 상태
### 3. 필드 설정 팝업
- 사원 상세 필드 ON/OFF: 프로필 사진, 사원코드, 성별, 주소
- 인사 정보 필드 ON/OFF: 입사일, 고용 형태, 직급, 상태, 부서, 직책
### 4. CSV 일괄등록
- 테이블 형태로 데이터 미리보기 표시
- 유효성 검사 결과 표시
---
## 구현 체크리스트
### Phase 1: 기본 구조 설정
- [x] 1.1 types.ts 생성 - Employee, EmployeeFormData 인터페이스 정의
- [x] 1.2 page.tsx 생성 - 라우트 페이지 (/hr/employee-management)
- [x] 1.3 index.tsx 생성 - 메인 컴포넌트 (상태 관리)
- [x] 1.4 mock 데이터 생성 - 테스트용 사원 데이터 (61명: 55 재직, 5 휴직, 1 퇴직)
### Phase 2: 통계 및 목록 화면
- [x] 2.1 EmployeeStats - 통계 카드 (공통 StatCards 활용)
- [x] 2.2 EmployeeToolbar.tsx - 날짜필터, 액션 버튼 영역
- [x] 2.3 메인 목록 구현 - 공통 DataTable 활용
- [x] 2.4 테이블 컬럼 정의 - 모든 필수 컬럼 구현
### Phase 3: 사원 등록/수정 다이얼로그
- [x] 3.1 EmployeeDialog.tsx - 다이얼로그 컨테이너 (create/edit/view 모드 통합)
- [x] 3.2 기본 사원정보 폼 섹션 (EmployeeDialog 내 통합)
- [x] 3.3 선택적 필드 섹션 - 프로필, 사원코드, 성별, 주소 (필드설정 연동)
- [x] 3.4 인사정보 섹션 - 부서/직책 복수 추가/삭제 기능 구현
- [x] 3.5 사용자 정보 섹션 - Switch로 계정 생성 토글
### Phase 4: 필드 설정 기능
- [x] 4.1 FieldSettingsDialog.tsx - 필드 ON/OFF 설정 다이얼로그
- [x] 4.2 필드 설정 상태 관리 (useState 기반)
- [x] 4.3 설정에 따른 폼 필드 동적 렌더링
### Phase 5: CSV 일괄등록
- [x] 5.1 CSVUploadDialog.tsx - CSV 업로드 다이얼로그
- [x] 5.2 CSV 파싱 유틸리티 (내장)
- [x] 5.3 미리보기 테이블 구현
- [x] 5.4 유효성 검사 및 에러 표시
### Phase 6: 추가 기능
- [x] 6.1 사용자 초대 기능 - UI 버튼 준비 (실제 로직은 API 연동 시)
- [x] 6.2 삭제 확인 AlertDialog
- [x] 6.3 필터/검색 기능 완성 (DataTable 내장)
- [x] 6.4 정렬 기능 완성 (DataTable 내장)
### Phase 7: 테스트 및 검증
- [x] 7.1 빌드 테스트 ✅ 성공
- [ ] 7.2 UI 렌더링 확인 (사용자 테스트 필요)
- [ ] 7.3 반응형 디자인 확인 (모바일 카드뷰)
---
## 공통 컴포넌트 활용 계획
| 컴포넌트 | 용도 |
|----------|------|
| DataTable | 사원 목록 테이블 |
| StatCards | 통계 카드 (재직, 휴직, 퇴직, 평균근속년수) |
| SearchFilter | 검색 및 필터 |
| Pagination | 페이지네이션 |
| TabFilter | 상태별 탭 필터 |
| Dialog | 등록/수정 다이얼로그 |
| AlertDialog | 삭제 확인 |
| Switch | 필드 설정 ON/OFF |
| Select | 드롭다운 (은행, 권한 등) |
| Input | 텍스트 입력 필드 |
| Button | 모든 버튼 |
| Badge | 상태 표시 (재직/휴직/퇴직) |
---
## 참고사항
### 외부 이메일 로직 (스크린샷 7)
- 사용자 초대 시 외부 이메일 발송
- 현재는 UI만 구현, 실제 이메일 발송은 API 연동 시 처리
### 부서/직책 복수 선택
- 한 사원이 여러 부서에 소속 가능
- 부서별 직책 개별 설정
- 추가/삭제 버튼으로 관리
### 필드 설정 저장
- 필드 표시 설정은 세션 또는 localStorage에 저장
- 사용자별로 다른 설정 유지 가능
---
## 진행 상황 업데이트
| 날짜 | 완료 항목 | 메모 |
|------|-----------|------|
| 2025-12-05 | 체크리스트 작성 | 스크린샷 분석 완료 |
| 2025-12-05 | Phase 1-6 구현 완료 | types.ts, page.tsx, index.tsx, EmployeeToolbar, EmployeeDialog, FieldSettingsDialog, CSVUploadDialog |
| 2025-12-05 | 빌드 테스트 성공 | 17.1 kB First Load JS |
## 생성된 파일 목록
```
src/components/hr/EmployeeManagement/
├── types.ts # 타입 정의 (Employee, EmployeeFormData, FieldSettings 등)
├── index.tsx # 메인 컴포넌트 (상태관리, DataTable 활용)
├── EmployeeToolbar.tsx # 툴바 (날짜필터, 액션버튼)
├── EmployeeDialog.tsx # 등록/수정/상세 다이얼로그
├── FieldSettingsDialog.tsx # 필드 설정 다이얼로그
└── CSVUploadDialog.tsx # CSV 일괄등록 다이얼로그
src/app/[locale]/(protected)/hr/employee-management/
└── page.tsx # 라우트 페이지
```
## 접속 URL
- 한국어: `http://localhost:3000/ko/hr/employee-management`
- 영어: `http://localhost:3000/en/hr/employee-management`
- 일본어: `http://localhost:3000/ja/hr/employee-management`