refactor: 품목관리 시스템 리팩토링 및 Sales 페이지 추가

DynamicItemForm 개선:
- 품목코드 자동생성 기능 추가
- 조건부 표시 로직 개선
- 불필요한 컴포넌트 정리 (DynamicField, DynamicSection 등)
- 타입 시스템 단순화

새로운 기능:
- Sales 페이지 마이그레이션 (견적관리, 거래처관리)
- 공통 컴포넌트 추가 (atoms, molecules, organisms, templates)

문서화:
- 구현 문서 및 참조 문서 추가

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
byeongcheolryu
2025-12-04 12:48:41 +09:00
parent 0552b02ba9
commit 3be5714805
73 changed files with 9318 additions and 4353 deletions

View File

@@ -1,6 +1,6 @@
# claudedocs 문서 맵
> 프로젝트 기술 문서 인덱스 (Last Updated: 2025-12-01)
> 프로젝트 기술 문서 인덱스 (Last Updated: 2025-12-02)
## 폴더 구조
@@ -9,6 +9,7 @@ claudedocs/
├── _index.md # 이 파일 - 문서 맵
├── auth/ # 🔐 인증 & 토큰 관리
├── item-master/ # 📦 품목기준관리
├── sales/ # 💰 판매관리 (견적/거래처)
├── dashboard/ # 📊 대시보드 & 사이드바
├── api/ # 🔌 API 통합
├── guides/ # 📚 범용 가이드
@@ -39,8 +40,10 @@ claudedocs/
| 파일 | 설명 |
|------|------|
| `[PLAN-2025-12-01] service-layer-refactoring.md` | 📋 **검토 대기** - 서비스 레이어 리팩토링 계획 (도메인 로직 중앙화) |
| `[PLAN-2025-11-28] dynamic-item-form-implementation.md` | **Phase 1-6 완료** - 품목관리 동적 렌더링 구현 (타입, 훅, 필드, 렌더러, 메인폼, Feature Flag) |
| `[PLAN-2025-12-01] service-layer-refactoring.md` | **완료** - 서비스 레이어 리팩토링 계획 (도메인 로직 중앙화) |
| `[REF-2025-12-01] state-sync-solutions.md` | 📋 **참조** - 상태 동기화 문제 및 해결 방안 (정규화, React Query 등) |
| `[PLAN-2025-11-28] dynamic-item-form-implementation.md` | ⚠️ **롤백됨** - 이전 구현 계획 (참조용) |
| `[IMPL-2025-12-02] dynamic-item-form-rebuild.md` | 🔄 **진행중** - 품목관리 동적 페이지 재구현 (디자인 100% 동일 유지) |
| `[API-REQUEST-2025-11-28] dynamic-page-rendering-api.md` | ⭐ **v3.1** - 동적 페이지 렌더링 API 요청서 (ID 기반 통일) |
| `[PLAN-2025-11-27] item-form-component-separation.md` | ✅ **완료** - ItemForm 컴포넌트 분리 (1607→415줄, 74% 감소) |
| `[IMPL-2025-11-27] realtime-sync-fixes.md` | 실시간 동기화 수정 (BOM, 섹션 복제, 항목 수정, **페이지 삭제 시 섹션 동기화** 2025-11-28) |
@@ -56,6 +59,14 @@ claudedocs/
---
## 💰 sales/ - 판매관리 (견적/거래처)
| 파일 | 설명 |
|------|------|
| `[PLAN-2025-12-02] sales-pages-migration.md` | 📋 **신규** - 견적관리/거래처관리 마이그레이션 계획 |
---
## 📊 dashboard/ - 대시보드 & 사이드바
| 파일 | 설명 |

View File

@@ -0,0 +1,110 @@
# DynamicItemForm 수정 내역 (2025-12-02)
## 완료된 작업
### 1. 디자인 수정 (2카드 → 1카드)
- **파일**: `src/components/items/DynamicItemForm/index.tsx`
- **내용**: 첫 번째 섹션을 "기본 정보" 카드에 통합
- **결과**: 목업과 동일한 단일 카드 레이아웃
### 2. 필드 순서 정렬
- **순서**: 품목유형 → 품목명 → 규격(사양) → 품목코드(자동생성) → 단위 → 비고
- **방법**: `item_name`, `specification` 필드 우선 렌더링 후 품목코드, 나머지 필드 순서
### 3. 단위 드롭다운 field_key 매칭 개선
- **파일**: `src/components/items/DynamicItemForm/fields/DropdownField.tsx`
- **수정**: 한글 '단위' 포함 매칭 추가
```typescript
const isUnitField =
fieldKey.toLowerCase().includes('unit') ||
fieldKey.includes('단위') ||
field.field_name.includes('단위') ||
field.field_name.toLowerCase().includes('unit');
```
### 4. aria-hidden 충돌 해결
- **파일**: `src/components/ui/sheet.tsx`, `src/components/ui/select.tsx`
- **수정**: `modal={false}` 추가
- **이유**: Sheet(사이드바)와 Select 드롭다운 간 aria-hidden 충돌 방지
### 5. 필드명 매핑 (프론트 → 백엔드)
- **파일**: `src/components/items/DynamicItemForm/index.tsx`
- **매핑 테이블**:
```typescript
const fieldKeyToBackendKey: Record<string, string> = {
'item_name': 'name',
'specification': 'spec',
'unit': 'unit',
'note': 'note',
'description': 'description',
};
```
- **추가 매핑**:
- `item_type``product_type`
- `item_code``code`
---
## 발견된 백엔드 이슈 (미해결)
### 1. unitOptions 빈 배열
- **현상**: init API 응답에 `unitOptions: []` 빈 배열
- **영향**: 단위 드롭다운에 옵션이 없음
- **해결 필요**:
- 품목기준관리 속성 탭의 단위 데이터를 DB에 저장
- init API에서 `item_unit_options` 테이블 조회 후 반환
### 2. category_id 필수 필드
- **현상**: `Field 'category_id' doesn't have a default value` 에러
- **해결 필요**:
- `products` 테이블의 `category_id` nullable 또는 기본값 설정
- 또는 API에서 기본 category_id 처리
### 3. GET /items API 품목 조회 안됨
- **현상**: 품목 저장 후 목록에서 안 보임
- **응답**: `"data": [], "total": 0`
- **확인 필요**:
- POST `/items``products` 테이블 저장
- GET `/items` → 어떤 테이블 조회하는지?
- tenant_id 조건 확인
---
## 수정된 파일 목록
```
src/components/items/DynamicItemForm/index.tsx
src/components/items/DynamicItemForm/fields/DropdownField.tsx
src/components/items/DynamicItemForm/hooks/useFormStructure.ts
src/components/ui/sheet.tsx
src/components/ui/select.tsx
```
---
## 다음 세션에서 할 일
1. [ ] 백엔드 이슈 해결 확인 (unitOptions, category_id, GET /items)
2. [ ] 단위 드롭다운 정상 동작 테스트
3. [ ] 품목 저장 후 목록에서 조회 테스트
4. [ ] 다른 품목 유형(FG, PT, SM, RM) 테스트
5. [ ] 디버그 console.log 제거
---
## 백엔드 요청 사항 정리
```markdown
### 1. unitOptions 반환
📍 API: GET /api/v1/item-master/init
📍 요청: unitOptions에 item_unit_options 테이블 데이터 포함
### 2. category_id 기본값
📍 테이블: products
📍 요청: category_id NULL 허용 또는 기본값 설정
### 3. GET /items 품목 조회
📍 API: GET /api/v1/items
📍 문제: 저장한 품목이 조회되지 않음
📍 확인: products 테이블 조회하는지, tenant_id 조건 확인
```

View File

@@ -0,0 +1,223 @@
# 품목 등록 동적 페이지 재구현
## 작업 일자: 2025-12-02
## 문서 버전
| 버전 | 날짜 | 작성자 | 내용 |
|------|------|--------|------|
| 1.0 | 2025-12-02 | Claude | 롤백 후 재구현 시작 |
| 1.1 | 2025-12-02 | Claude | Phase 1-8 완료 |
---
## 배경
- 이전 작업에서 DynamicItemForm 구현이 디자인 누락으로 롤백됨
- 기존 ItemForm (목업 데이터 기반)의 디자인을 100% 유지하면서 동적 렌더링 구현 필요
---
## 핵심 원칙
1. **디자인 100% 동일**: 기존 ItemForm과 완벽히 동일한 UI/UX
2. **밸리데이션 동일**: 에러 메시지 위치, 스타일 동일
3. **버튼/아이콘 동일**: 모든 버튼, 아이콘 위치 및 스타일 동일
4. **화면 등장 순서 동일**: 섹션 표시 순서, 조건부 렌더링 동일
5. **동적 구성**: 품목기준관리 API 기반으로 필드/섹션 동적 생성
---
## 체크리스트
### Phase 1: 현재 상태 분석
- [x] 기존 ItemForm 디자인 완전 분석
- [x] 품목 유형 선택 UI
- [x] 기본 정보 섹션 레이아웃
- [x] BOM 섹션 레이아웃
- [x] 전개도 섹션 레이아웃
- [x] 버튼 위치 및 스타일
- [x] 밸리데이션 에러 표시 방식
- [x] 품목관리 API 응답 구조 확인 (init, pages.getStructure)
### Phase 2: DynamicItemForm 폴더 생성
- [x] 기본 폴더 구조 생성
```
src/components/items/DynamicItemForm/
├── index.tsx
├── types.ts
├── hooks/
│ ├── index.ts
│ ├── useFormStructure.ts
│ └── useDynamicFormState.ts
└── fields/
├── index.ts
├── TextField.tsx
├── DropdownField.tsx
├── NumberField.tsx
├── DateField.tsx
├── TextareaField.tsx
├── CheckboxField.tsx
└── DynamicFieldRenderer.tsx
```
### Phase 3: 타입 정의
- [x] `types.ts` - 동적 폼 관련 타입 정의
- [x] API 응답 타입과 프론트엔드 타입 매핑
- [x] SimpleUnitOption 타입 추가
### Phase 4: 훅 구현
- [x] `useFormStructure.ts` - API에서 폼 구조 로드
- [x] `useDynamicFormState.ts` - 동적 상태 관리
### Phase 5: 필드 컴포넌트 구현 (디자인 100% 동일)
- [x] `TextField.tsx` - 기존 Input 스타일 동일
- [x] `DropdownField.tsx` - 기존 Select 스타일 동일
- [x] `NumberField.tsx` - 기존 숫자 입력 스타일 동일
- [x] `DateField.tsx` - 기존 날짜 선택 스타일 동일
- [x] `TextareaField.tsx` - 기존 텍스트영역 스타일 동일
- [x] `CheckboxField.tsx` - 기존 체크박스 스타일 동일
- [x] `DynamicFieldRenderer.tsx` - 필드 타입별 렌더러
### Phase 6: 메인 폼 컴포넌트
- [x] `index.tsx` - 메인 동적 폼 컴포넌트
- [x] 기존 ItemForm과 동일한 레이아웃
- [x] 동일한 헤더 스타일 (FormHeader 인라인)
- [x] 동일한 ValidationAlert 스타일
- [x] 동일한 섹션 Card 스타일
- [x] 동일한 버튼 배치
### Phase 7: 기존 ItemForm 주석처리 및 전환
- [x] `/items/create/page.tsx` - DynamicItemForm으로 전환
- [x] 기존 ItemForm import 주석처리
- [x] 전환 테스트 준비
### Phase 8: API 연동
- [x] POST /api/proxy/items 연동 구현
- [x] 품목 등록 후 목록 페이지 이동
- [x] 에러 처리
### Phase 9: 테스트
- [x] TypeScript 빌드 검증 완료 (DynamicItemForm 에러 없음)
- [ ] 품목 유형별 등록 테스트 (FG, PT, RM, SM, CS)
- [ ] 밸리데이션 테스트
- [ ] API 연동 테스트
- [ ] 품목 목록에 등록된 데이터 표시 확인
---
## 테스트 가이드
### 사전 조건
1. 백엔드 서버 실행 (sam-api)
2. 프론트엔드 개발 서버 실행 (`npm run dev`)
3. 품목기준관리에서 페이지/섹션/필드 설정 완료
### 테스트 항목
#### 1. 품목 등록 페이지 접근
```
URL: http://localhost:3000/items/create
```
- [ ] 페이지 로딩 시 "폼 구조를 불러오는 중..." 표시
- [ ] 품목 유형 선택 전 안내 메시지 표시
#### 2. 품목 유형 선택
- [ ] FG (완제품) 선택 시 해당 폼 구조 로드
- [ ] PT (부품) 선택 시 해당 폼 구조 로드
- [ ] RM (원자재) 선택 시 해당 폼 구조 로드
- [ ] SM (부자재) 선택 시 해당 폼 구조 로드
- [ ] CS (소모품) 선택 시 해당 폼 구조 로드
#### 3. 필드 렌더링
- [ ] 텍스트 필드 정상 렌더링
- [ ] 숫자 필드 정상 렌더링
- [ ] 드롭다운 필드 정상 렌더링 (단위 옵션 포함)
- [ ] 체크박스 필드 정상 렌더링
- [ ] 날짜 필드 정상 렌더링
- [ ] 텍스트영역 필드 정상 렌더링
#### 4. 밸리데이션
- [ ] 필수 필드 빈값 시 에러 메시지 표시
- [ ] ValidationAlert에 에러 목록 표시
- [ ] 에러 필드에 빨간 테두리 표시
#### 5. API 연동
- [ ] 저장 버튼 클릭 시 POST /api/proxy/items 호출
- [ ] 저장 성공 시 /items 페이지로 이동
- [ ] 저장 실패 시 에러 메시지 표시
#### 6. 디자인 검증
- [ ] 기존 ItemForm과 레이아웃 동일
- [ ] 헤더 스타일 동일 (아이콘, 버튼 위치)
- [ ] 섹션 Card 스타일 동일
- [ ] 필드 라벨/에러 스타일 동일
---
## 생성된 파일
### DynamicItemForm 컴포넌트
- `src/components/items/DynamicItemForm/index.tsx` - 메인 폼 (388줄)
- `src/components/items/DynamicItemForm/types.ts` - 타입 정의
- `src/components/items/DynamicItemForm/hooks/index.ts` - 훅 export
- `src/components/items/DynamicItemForm/hooks/useFormStructure.ts` - 폼 구조 로드
- `src/components/items/DynamicItemForm/hooks/useDynamicFormState.ts` - 상태 관리
- `src/components/items/DynamicItemForm/fields/index.ts` - 필드 export
- `src/components/items/DynamicItemForm/fields/TextField.tsx`
- `src/components/items/DynamicItemForm/fields/NumberField.tsx`
- `src/components/items/DynamicItemForm/fields/DropdownField.tsx`
- `src/components/items/DynamicItemForm/fields/CheckboxField.tsx`
- `src/components/items/DynamicItemForm/fields/DateField.tsx`
- `src/components/items/DynamicItemForm/fields/TextareaField.tsx`
- `src/components/items/DynamicItemForm/fields/DynamicFieldRenderer.tsx`
### 수정된 파일
- `src/app/[locale]/(protected)/items/create/page.tsx` - DynamicItemForm 사용으로 변경
---
## API 흐름
```
1. useFormStructure(itemType)
└─> itemMasterApi.init()
└─> /api/proxy/item-master/init
└─> pages, sections, fields, unitOptions 반환
2. itemMasterApi.pages.getStructure(pageId)
└─> /api/proxy/item-master/pages/{id}/structure
└─> page, sections, directFields 반환
3. 품목 등록
└─> POST /api/proxy/items
└─> body: DynamicFormData
```
---
## 디자인 매칭 요소
### 레이아웃
- `form` > `space-y-6`
- `Card` > `CardHeader` > `CardTitle` > `CardContent`
- `grid grid-cols-1 md:grid-cols-2 gap-4`
### 필드 스타일
- Label with required marker: `<span className="text-red-500"> *</span>`
- Error state: `className={error ? 'border-red-500' : ''}`
- Error message: `<p className="text-xs text-red-500 mt-1">{error}</p>`
- Helper text: `<p className="text-xs text-muted-foreground mt-1">* ...</p>`
### 버튼 스타일
- Cancel: `variant="outline" size="sm"`
- Submit: `size="sm" disabled={!selectedItemType || isSubmitting}`
- Icons: `X`, `Save` from lucide-react
---
## 변경 이력
| 날짜 | 작업 내용 |
|------|----------|
| 2025-12-02 | 문서 생성, Phase 1 시작 |
| 2025-12-02 | Phase 1-8 완료: DynamicItemForm 전체 구현 |

View File

@@ -0,0 +1,81 @@
# 품목코드 자동생성 로직
## 개요
품목코드는 품목기준관리에서 별도 필드로 등록하지 않고, **프론트엔드(DynamicItemForm)에서 자동 생성**한다.
## 배경
- 품목기준관리 API에 "자동생성 필드" 설정 기능이 아직 없음 (TODO 상태)
- 당장 테스트를 위해 프론트에서 처리하기로 결정 (2025-12-02)
## 자동생성 규칙 (2025-12-03 업데이트)
### PT (부품) - 영문약어-순번 형식
```
품목명 선택 → 영문약어 매핑 조회 → 기존 품목코드에서 순번 계산 → 코드 생성
예시:
- 가이드레일 → GR → GR-001, GR-002, ...
- 모터 → MOTOR → MOTOR-001, MOTOR-002, ...
- 제어기 → CTL → CTL-001, CTL-002, ...
```
### 기타 품목 (FG, SM, RM, CS) - 품목명-규격 형식
```
IF 해당 페이지에 item_name 필드 존재
AND specification 필드 존재
THEN
→ "품목코드 (자동생성)" 읽기전용 필드 자동 표시
→ 값: {품목명}-{규격}
→ 저장 시 품목관리 API의 code로 전달
```
## 영향 범위
### 품목기준관리에 등록할 필드 (CS 소모품 예시)
| 필드명 | field_key | field_type | 비고 |
|--------|-----------|------------|------|
| 품목명 | `item_name` | textbox | 필수 |
| 규격(사양) | `specification` | textbox | 필수 |
| 단위 | `unit` | dropdown | 필수 |
| 비고 | `note` | textarea | 선택 |
### 품목기준관리에 등록하지 않는 필드
| 필드명 | 이유 |
|--------|------|
| 품목코드 | 프론트에서 자동생성 |
## 구현 위치
- **파일**: `src/components/items/DynamicItemForm/index.tsx`
- **로직**:
1. `structure`에서 `item_name`, `specification` 필드 존재 여부 체크
2. 둘 다 있으면 품목코드 읽기전용 필드 렌더링
3. 값은 `formData.item_name` + `-` + `formData.specification`
4. 저장 시 `submitData.item_code`에 포함
## 향후 계획
- 품목기준관리 API에 `properties.autoGenerate` 설정 기능 추가 시 마이그레이션
- 예시 설정:
```json
{
"field_name": "품목코드",
"field_type": "textbox",
"properties": {
"autoGenerate": {
"formula": "{item_name}-{specification}",
"readonly": true
}
}
}
```
## 관련 문서
- `[PLAN-2025-11-28] dynamic-item-form-implementation.md`
- `[IMPL-2025-12-02] dynamic-item-form-rebuild.md`
- `[REF] item-code-hardcoding.md` - PT 품목코드 하드코딩 내역 상세

View File

@@ -195,9 +195,9 @@ src/components/items/
- [x] DrawingCanvasSimple (파일 업로드 기반)
- [x] BOMTable (품목 구성 관리)
- [x] BendingDetailTable (전개도 상세 입력 + 자동 계산)
- [ ] 페이지 라우트 업데이트 (API 구현 후 진행)
- [ ] `/items/create/page.tsx`
- [ ] `/items/[id]/edit/page.tsx`
- [x] 페이지 라우트 업데이트 ✅ 완료 (2025-12-01)
- [x] `/items/create/page.tsx` → DynamicItemForm 연동
- [x] `/items/[id]/edit/page.tsx` → DynamicItemForm + 실제 API 연동
### Phase 7: 테스트 및 검증
- [ ] 품목 유형별 테스트
@@ -347,4 +347,5 @@ src/components/items/
| 날짜 | 버전 | 변경 내용 |
|------|------|----------|
| 2025-11-28 | 1.0 | 초안 작성 |
| 2025-11-28 | 1.0 | 초안 작성 |
| 2025-12-01 | 1.1 | Phase 6 페이지 라우트 업데이트 완료 (API 연동) |

View File

@@ -0,0 +1,225 @@
# 품목기준관리 상태 동기화 문제 및 해결 방안
**작성일**: 2025-12-01
**상태**: 참조 문서
**관련**: 서비스 레이어 리팩토링 후 발생한 동기화 버그 분석
---
## 1. 오늘 발생한 버그 분석
### 1.1 실시간 동기화 문제 (sectionsAsTemplates 순서)
```
문제: 섹션탭에서 항목 추가 → 계층구조만 업데이트, 항목탭/속성탭은 안됨
원인: Map 중복 제거 시 unlinkedSections가 linkedSections를 덮어씀
해결: [...unlinkedSections, ...linkedSections] 순서로 변경
```
**리팩토링으로 해결 가능?** ❌ 아니다. **데이터 소스 우선순위** 문제.
### 1.2 422 Validation Error
```
문제: 섹션탭에서 일반 섹션 항목 추가 시 422 에러
원인: 섹션탭은 POST /fields, 계층구조탭은 POST /sections/{id}/fields 사용
해결: 섹션탭도 addFieldToSection() 사용하도록 통일
```
**리팩토링으로 해결 가능?** ⚠️ 부분적. 서비스 레이어에서 API 선택을 중앙화할 수 있었지만, 두 탭이 **다른 API를 호출하는 것 자체**가 문제.
### 1.3 페이지 삭제 시 필드 소실
```
문제: 페이지 삭제 → 섹션의 필드까지 사라짐
원인: refreshIndependentSections()가 필드 없이 섹션만 반환
해결: 페이지의 섹션들을 직접 independentSections에 추가
```
**리팩토링으로 해결 가능?** ❌ 아니다. **백엔드 API 응답 불완전성** + **상태 동기화 전략** 문제.
---
## 2. 근본 원인: 동기화가 애매한 구조
### 2.1 현재 아키텍처의 문제점
```
┌─────────────────────────────────────────────────┐
│ 같은 "섹션" 데이터가 2곳에서 관리됨 │
├─────────────────────────────────────────────────┤
│ 1. itemPages[].sections[] ← 페이지에 연결된 섹션 │
│ 2. independentSections[] ← 독립 섹션 │
├─────────────────────────────────────────────────┤
│ 문제: 두 소스가 동기화되지 않으면 불일치 발생! │
└─────────────────────────────────────────────────┘
```
### 2.2 서비스 레이어 리팩토링의 한계
| 해결 가능 | 해결 불가 |
|-----------|-----------|
| ✅ validation/파싱 로직 중복 제거 | ❌ 상태 동기화 문제 |
| ✅ 코드 유지보수성 향상 | ❌ 데이터 소스 불일치 |
| ✅ 도메인 로직 중앙화 | ❌ API 응답 불완전성 |
---
## 3. 해결 방안
### 방법 1: 정규화된 상태 (Normalized State) ⭐ 권장
```typescript
// 현재 구조 (중첩, 중복 가능)
{
itemPages: [
{ id: 1, sections: [{ id: 10, fields: [...] }] }
],
independentSections: [
{ id: 10, fields: [...] } // 같은 섹션이 다른 데이터로 존재!
]
}
// 정규화된 구조 (Single Source of Truth)
{
entities: {
pages: { 1: { id: 1, sectionIds: [10] } },
sections: { 10: { id: 10, fieldIds: [100, 101] } },
fields: { 100: {...}, 101: {...} }
},
// 관계는 ID로만 참조
ui: {
selectedPageId: 1,
independentSectionIds: [20, 30] // ID만 저장
}
}
```
**장점:**
- 데이터 중복 없음 → 불일치 원천 차단
- 한 곳만 수정하면 모든 곳에 반영
- 메모리 효율적
**구현 도구:** Zustand + immer 또는 Redux Toolkit
---
### 방법 2: Derived State 패턴
```typescript
// Context에서 원본 데이터는 하나만 유지
const [allSections, setAllSections] = useState<Section[]>([]);
const [pageRelations, setPageRelations] = useState<{pageId: number, sectionId: number}[]>([]);
// 필요한 데이터는 계산으로 도출 (useMemo)
const sectionsForPage = useMemo(() =>
allSections.filter(s =>
pageRelations.some(r => r.pageId === selectedPageId && r.sectionId === s.id)
), [allSections, pageRelations, selectedPageId]
);
const independentSections = useMemo(() =>
allSections.filter(s =>
!pageRelations.some(r => r.sectionId === s.id)
), [allSections, pageRelations]
);
```
**장점:**
- 기존 Context 구조 유지 가능
- 점진적 마이그레이션 가능
---
### 방법 3: React Query / TanStack Query
```typescript
// 서버 상태를 캐시로 관리
const { data: sections } = useQuery({
queryKey: ['sections'],
queryFn: () => api.getSections({ includeFields: true })
});
// 뮤테이션 후 자동 갱신
const deletePage = useMutation({
mutationFn: api.deletePage,
onSuccess: () => {
// 관련 쿼리 자동 갱신
queryClient.invalidateQueries(['sections']);
queryClient.invalidateQueries(['pages']);
}
});
```
**장점:**
- 서버-클라이언트 동기화 자동화
- 캐싱, 재시도, 낙관적 업데이트 내장
- 수동 상태 관리 대폭 감소
---
### 방법 4: 백엔드 API 개선
```
현재: GET /sections → 섹션만 반환 (필드 없음)
개선: GET /sections?include=fields → 섹션 + 필드 반환
```
**또는 GraphQL:**
```graphql
query {
sections {
id
title
fields { # 필요한 관계 데이터 명시적 요청
id
field_name
}
}
}
```
---
## 4. 현실적인 적용 순서
| 단계 | 방법 | 난이도 | 효과 | 설명 |
|------|------|--------|------|------|
| 1단계 | Derived State 패턴 | 🟢 낮음 | 즉시 적용 가능 | 기존 구조 유지하며 파생 데이터 정리 |
| 2단계 | 백엔드 API 개선 요청 | 🟡 중간 | 근본 해결 | `include=fields` 파라미터 추가 |
| 3단계 | React Query 도입 | 🟡 중간 | 동기화 자동화 | 서버 상태 관리 단순화 |
| 4단계 | 정규화된 상태 | 🔴 높음 | 완전한 해결 | 대규모 리팩토링 필요 |
---
## 5. 프로젝트 추천 방향
### 단기 (즉시 적용 가능)
- Derived State 패턴으로 `sectionsAsTemplates` 같은 파생 데이터 정리
- 중복 데이터 소스 최소화
### 중기 (백엔드 협업 필요)
- 백엔드에 `GET /sections?include=fields` API 추가 요청
- 관계 데이터 포함 응답으로 프론트엔드 동기화 부담 감소
### 장기 (대규모 개선)
- React Query 도입으로 서버 상태 관리 단순화
- 또는 정규화된 상태 구조로 전환
---
## 6. 결론
| 구분 | 내용 |
|------|------|
| **서비스 레이어** | 도메인 로직 중복 해결 ✅ |
| **상태 동기화** | 아키텍처 레벨 개선 필요 ⚠️ |
| **근본 원인** | 같은 데이터가 여러 곳에서 관리됨 |
| **해결 방향** | Single Source of Truth 패턴 적용 |
---
## 7. 관련 문서
- `[PLAN-2025-12-01] service-layer-refactoring.md` - 서비스 레이어 리팩토링 계획
- `[REF-2025-11-26] item-master-hooks-refactoring.md` - 훅 분리 작업 기록

View File

@@ -0,0 +1,241 @@
# 품목코드/품목명 자동생성 하드코딩 내역
> MVP용 프론트엔드 구현 - 추후 백엔드 API 또는 품목기준관리 설정으로 이관 필요
## 개요
PT(부품) 품목 등록 시 품목코드와 품목명을 자동 생성하는 로직이 프론트엔드에 하드코딩되어 있습니다.
이 문서는 해당 하드코딩 내역을 정리하여 추후 백엔드 이관 시 참고할 수 있도록 합니다.
## 품목코드/품목명 생성 규칙
### 적용 범위
| 품목유형 | 품목코드 형식 | 품목명 형식 | 예시 |
|---------|-------------|------------|------|
| **PT (부품)** | `영문약어-순번` | 한글 조합 | `GR-001` / `가이드레일 130×80` |
| FG (제품) | `품목명-규격` | 직접 입력 | `스크린-2400` |
| SM (부자재) | `품목명-규격` | 직접 입력 | `볼트-M8` |
| RM (원자재) | `품목명-규격` | 직접 입력 | `알루미늄-T1.5` |
| CS (소모품) | `품목명-규격` | 직접 입력 | `테이프-50mm` |
---
## 하드코딩 항목 1: 영문약어 매핑 테이블
### 파일 위치
`src/components/items/DynamicItemForm/utils/itemCodeGenerator.ts`
### 상수명
`ITEM_CODE_PREFIX_MAP`
### 내용
```typescript
export const ITEM_CODE_PREFIX_MAP: Record<string, string> = {
// 부품 - 조립품
'가이드레일': 'GR',
'케이스': 'CASE',
'브라켓': 'BRK',
// 부품 - 구매품
'모터': 'MOTOR',
'제어기': 'CTL',
'전동개폐기': 'OPENER',
'스위치': 'SW',
'센서': 'SENSOR',
'리모컨': 'REMOTE',
// 부품 - 절곡품
'레일': 'RAIL',
'커버': 'COVER',
'플레이트': 'PLATE',
// 제품
'스크린': 'SCREEN',
'셔터': 'SHUTTER',
'방화스크린': 'FIRE-SCR',
'롤스크린': 'ROLL-SCR',
// 원자재
'알루미늄': 'ALU',
'스틸': 'STEEL',
'철판': 'STEEL',
// 부자재/소모품
'볼트': 'BOLT',
'너트': 'NUT',
'와셔': 'WASHER',
'나사': 'SCREW',
};
```
### 마이그레이션 방안
- 품목기준관리 API에 `영문약어 설정` 기능 추가
- 또는 별도 `item_code_prefix` 테이블 생성
---
## 하드코딩 항목 2: 절곡품 코드 체계
### 파일 위치
`src/components/items/DynamicItemForm/utils/itemCodeGenerator.ts`
### 상수명
`BENDING_CODE_SYSTEM`
### 내용
```typescript
export const BENDING_CODE_SYSTEM = {
// 품목명코드 (category2)
품목명코드: {
'R': '가이드레일',
'S': '스크린',
'C': '케이스',
'B': '박스',
'T': '트림',
'L': '라스틱',
'G': '기타',
},
// 종류코드 (category3)
종류코드: {
'M': '마감',
'T': '티',
'C': '채널',
'D': '단면',
'S': '상부',
'U': '하부',
'F': '플랫',
'P': '피스',
'L': '리드',
'B': '브라켓',
'E': '엔드',
'I': '이음',
'A': '각재',
},
// 길이코드 매핑 (mm → 코드)
길이코드: {
1219: '12',
2438: '24',
3000: '30',
3500: '35',
4000: '40',
4150: '41',
4200: '42',
4300: '43',
},
};
```
### 사용 예시
- 품목명코드 `R` + 종류코드 `C` + 길이코드 `24` = `RC24`
- 가이드레일 채널 2438mm
### 마이그레이션 방안
- 품목기준관리 API에 `코드 체계 설정` 기능 추가
- 또는 별도 `bending_code_system` 테이블 생성
---
## 하드코딩 항목 3: 조립품 설치유형 매핑
### 파일 위치
`src/components/items/DynamicItemForm/utils/itemCodeGenerator.ts`
### 상수명
`INSTALLATION_TYPE_MAP`
### 내용
```typescript
export const INSTALLATION_TYPE_MAP: Record<string, string> = {
'standard': '표준형',
'top': '상부형',
'bottom': '하부형',
'side': '측면형',
'custom': '맞춤형',
};
```
### 사용 예시
- 품목명 `가이드레일` + 설치유형 `standard``가이드레일표준형`
### 마이그레이션 방안
- 품목기준관리 필드 옵션으로 설정 가능하도록 변경
---
## 자동생성 함수 목록
### 1. generateItemCode (품목코드 생성)
```typescript
// 용도: PT(부품) 품목코드 자동생성
// 형식: 영문약어-순번 (예: GR-001, MOTOR-002)
generateItemCode(itemName: string, existingCodes: string[]): string
```
### 2. generateBendingItemCode (절곡품 품목코드)
```typescript
// 용도: 절곡품 전용 품목코드
// 형식: 품목명코드 + 종류코드 + 길이코드 (예: RC24)
generateBendingItemCode(category2: string, category3: string, lengthMm: number): string
```
### 3. generateAssemblyItemName (조립품 품목명)
```typescript
// 용도: 조립품 품목명 자동생성
// 형식: 품목명 + 설치유형 + 측면규격*길이코드 (예: 가이드레일표준형50*60*24)
generateAssemblyItemName(itemName, installationType, sideSpecWidth, sideSpecHeight, lengthMm): string
```
### 4. generateBendingItemName (절곡품 품목명)
```typescript
// 용도: 절곡품 품목명 자동생성
// 형식: 품목명 + 종류 + 규격 (예: 가이드레일 채널 50×30)
generateBendingItemName(category2Label, category3Label, specification): string
```
### 5. generatePurchasedItemName (구매품 품목명)
```typescript
// 용도: 구매품 품목명 자동생성
// 형식: 품목명 + 규격 (예: 모터 0.4KW)
generatePurchasedItemName(itemName, specification): string
```
---
## 추후 개발 계획
### Phase 1: 품목기준관리 설정 확장
1. `영문약어` 필드 추가 (품목명 필드 옵션에 매핑)
2. `코드생성규칙` 설정 UI 추가
3. API에서 코드 생성 규칙 반환
### Phase 2: 백엔드 이관
1. 품목 저장 시 백엔드에서 코드 자동 생성
2. 순번 관리를 DB 시퀀스로 변경 (동시성 처리)
3. 프론트엔드 하드코딩 제거
### Phase 3: 고급 기능
1. 품목별 코드 생성 규칙 커스터마이징
2. 코드 중복 검사 강화
3. 코드 변경 이력 관리
---
## 관련 파일
| 파일 | 설명 |
|-----|------|
| `src/components/items/DynamicItemForm/utils/itemCodeGenerator.ts` | 하드코딩된 매핑 및 생성 함수 |
| `src/components/items/DynamicItemForm/index.tsx` | 품목코드 자동생성 로직 호출 |
| `claudedocs/item-master/[IMPL-2025-12-02] item-code-auto-generation.md` | 기존 품목코드 자동생성 문서 |
---
## 변경 이력
| 날짜 | 내용 |
|-----|------|
| 2025-12-03 | PT 품목코드 `영문약어-순번` 형식 구현 |
| 2025-12-03 | 하드코딩 내역 문서화 |

View File

@@ -0,0 +1,455 @@
# 판매관리 페이지 마이그레이션 계획
> **작성일**: 2025-12-02
> **소스**: `sam-design/sam-design`
> **타겟**: `sam-react-prod` (Next.js)
> **예상 소요 시간**: 4~6시간
---
## 0. 예상 작업 시간
| Phase | 작업 내용 | 예상 시간 |
|-------|----------|----------|
| Phase 1 | 공통 UI 컴포넌트 (~1,400줄) | 1~2시간 |
| Phase 2 | 거래처관리 (~500줄) | 30분~1시간 |
| Phase 3 | 견적관리 + 하위 컴포넌트 (~1,500줄+) | 2~3시간 |
| **총합** | **~3,400줄+** | **4~6시간** |
> **참고**: 테스트/디버깅 시간 포함, 예상치 못한 이슈 발생 시 추가 소요 가능
---
## 1. 마이그레이션 대상 요약
| 페이지 | 파일 | 크기 | 분할 필요 | 템플릿 |
|--------|------|------|----------|--------|
| 견적관리 | `QuoteManagement.tsx` | 1,001줄 | O (경계선) | IntegratedListTemplateV2 |
| 거래처관리 | `CustomerAccountManagement.tsx` | 425줄 | X | ListPageTemplate |
---
## 2. 견적관리 (`QuoteManagement.tsx`)
### 2.1 파일 정보
- **경로**: `sam-design/src/components/QuoteManagement.tsx`
- **크기**: 1,001줄
- **템플릿**: `IntegratedListTemplateV2`
### 2.2 컴포넌트 구조
```
QuoteManagement
├── IntegratedListTemplateV2 (목록 템플릿)
│ ├── PageHeader (제목: "견적 목록")
│ ├── StatCards (4개 통계 카드)
│ │ ├── 이번 달 견적 금액
│ │ ├── 진행중 견적 금액
│ │ ├── 이번 주 신규 견적
│ │ └── 이번 달 수주 전환율
│ ├── SearchFilter (검색바)
│ ├── TabChip 탭
│ │ ├── 전체
│ │ ├── 최초작성
│ │ ├── 수정중
│ │ ├── 최종확정
│ │ └── 수주전환
│ ├── DataTable (데스크톱 테이블)
│ └── ListMobileCard (모바일 카드)
├── QuoteManagement3Write (등록/수정 화면)
├── QuoteManagement3Detail (상세 화면)
├── QuoteDetailView (상세 화면 - 샘플용)
└── StandardDialog (산출내역서/삭제 확인)
```
### 2.3 주요 기능
- 목록 조회 (페이지네이션, 검색, 필터)
- 탭 기반 상태 필터링
- 체크박스 선택 + 일괄 삭제
- 모바일 인피니티 스크롤
- CRUD (등록/조회/수정/삭제)
- 산출내역서 다이얼로그
### 2.4 분할 제안
```
src/app/[locale]/(protected)/sales/quotes/
├── page.tsx # 목록 (라우팅 + 상태 관리)
├── components/
│ ├── QuoteList.tsx # 목록 컴포넌트
│ ├── QuoteTableRow.tsx # 테이블 행
│ └── QuoteMobileCard.tsx # 모바일 카드
├── create/
│ └── page.tsx # 견적 등록
├── [id]/
│ ├── page.tsx # 견적 상세
│ └── edit/
│ └── page.tsx # 견적 수정
```
### 2.5 의존성 컴포넌트
| 컴포넌트 | 경로 | 마이그레이션 필요 |
|----------|------|------------------|
| IntegratedListTemplateV2 | templates/ | O |
| PageHeader | organisms/ | 이미 존재 |
| PageLayout | organisms/ | 이미 존재 |
| StatCards | organisms/ | O |
| SearchFilter | organisms/ | O |
| TabChip | atoms/ | O |
| ListMobileCard | organisms/ | O |
| DataTable | organisms/ | O |
| StandardDialog | molecules/ | O |
| QuoteManagement3Write | components/ | O (별도 분석 필요) |
| QuoteManagement3Detail | components/ | O (별도 분석 필요) |
---
## 3. 거래처관리 (`CustomerAccountManagement.tsx`)
### 3.1 파일 정보
- **경로**: `sam-design/src/components/CustomerAccountManagement.tsx`
- **크기**: 425줄
- **템플릿**: `ListPageTemplate`
### 3.2 컴포넌트 구조
```
CustomerAccountManagement
├── ListPageTemplate (목록 템플릿)
│ ├── PageHeader (제목: "매출처 목록")
│ ├── StatCards (3개 통계 카드)
│ │ ├── 전체 거래처
│ │ ├── 활성 거래처
│ │ └── 비활성 거래처
│ ├── SearchFilter (검색바)
│ ├── DataTable (데스크톱 테이블)
│ │ └── columns: 코드, 거래처명, 사업자번호, 대표자, 전화번호, 업태, 업종, 상태, 관리
│ └── MobileCard (모바일 카드)
└── Dialog (등록/수정 모달)
└── FormFields: 거래처명, 사업자번호, 대표자, 전화번호, 주소, 이메일, 업태, 업종
```
### 3.3 주요 기능
- 거래처 목록 조회 (검색)
- CRUD (등록/수정/삭제)
- 모달 기반 폼
### 3.4 Next.js 구조 제안
```
src/app/[locale]/(protected)/sales/customers/
├── page.tsx # 목록 + 모달 (425줄이라 단일 파일 가능)
└── components/
└── CustomerForm.tsx # 폼 분리 (선택)
```
### 3.5 의존성 컴포넌트
| 컴포넌트 | 경로 | 마이그레이션 필요 |
|----------|------|------------------|
| ListPageTemplate | templates/ | O |
| PageHeader | organisms/ | 이미 존재 |
| PageLayout | organisms/ | 이미 존재 |
| StatCards | organisms/ | O |
| SearchFilter | organisms/ | O |
| DataTable | organisms/ | O |
| MobileCard | organisms/ | O |
| Dialog | ui/ | 이미 존재 |
---
## 4. 공통 테이블 UI 기준 (중요)
### 4.1 최종 기준: 견적관리 페이지 테이블
> **공통 테이블 UI는 `QuoteManagement.tsx`의 테이블 형태를 최종 기준으로 적용**
거래처관리를 포함한 모든 목록 페이지는 견적관리 페이지의 테이블 UI를 따릅니다.
### 4.2 테이블 화면 구조
```
┌─────────────────────────────────────────────────────────────┐
│ [PageHeader] 견적 목록 [등록 버튼] │
├─────────────────────────────────────────────────────────────┤
│ [StatCards] 4개 통계 카드 (2x2 또는 4열) │
├─────────────────────────────────────────────────────────────┤
│ [SearchFilter] 검색바 + 추가 필터 │
├─────────────────────────────────────────────────────────────┤
│ [TabChip] 전체 | 최초작성 | 수정중 | 최종확정 | 수주전환 │
├───┬───────────────────────────────────────────────────┬─────┤
│ ☐ │ 번호 | 견적번호 | 접수일 | 상태 | ... | 비고 | 작업 │
├───┼───────────────────────────────────────────────────┼─────┤
│ ☐ │ 10 | Q2024-001| 2024-01 | 진행 | ... | - | [편집]│
│ ☐ │ 9 | Q2024-002| 2024-01 | 확정 | ... | - | [편집]│
├───┴───────────────────────────────────────────────────┴─────┤
│ [Pagination] 전체 100개 중 1-20개 표시 [이전] 1 2 3 [다음]│
└─────────────────────────────────────────────────────────────┘
```
### 4.3 반응형 구조
| 화면 크기 | 뷰 타입 | 특징 |
|-----------|---------|------|
| **데스크톱** (1280px+) | 테이블 뷰 | 체크박스 + 전체 컬럼 + 페이지네이션 |
| **태블릿** (~1279px) | 카드 뷰 | 2~3열 그리드 + 인피니티 스크롤 |
| **모바일** (~767px) | 카드 뷰 | 1열 + 인피니티 스크롤 |
**반응형 전환 기준**:
- `xl:` (1280px+) → 테이블 뷰
- `md:` (768px~1279px) → 카드 그리드 (2~3열)
- 기본 → 카드 단일 열
### 4.4 테이블 기능 목록
#### 필수 기능
- [ ] **체크박스 선택**: 개별 선택 + 전체 선택
- [ ] **일괄 삭제**: 2개 이상 선택 시 삭제 버튼 표시
- [ ] **역순 번호**: totalCount 기준 역순 (최신이 1번이 아닌 N번)
- [ ] **행 클릭**: 상세 페이지로 이동
- [ ] **액션 버튼**: 선택된 행에만 수정/삭제 버튼 표시
#### 페이지네이션 (데스크톱)
- [ ] 페이지당 20개 표시
- [ ] 현재 페이지 표시 (1-20 / 100개)
- [ ] 이전/다음 버튼
- [ ] 페이지 번호 (현재 ±2 범위)
#### 인피니티 스크롤 (모바일/태블릿)
- [ ] Intersection Observer 사용
- [ ] 20개씩 추가 로드
- [ ] Sentinel 요소로 트리거
### 4.5 적용 대상
| 페이지 | 적용 내용 |
|--------|----------|
| **견적관리** | 기준 페이지 (이미 적용됨) |
| **거래처관리** | 동일한 테이블 UI 적용 필요 |
| **향후 목록 페이지** | 모두 동일한 공통 테이블 UI 사용 |
### 4.6 공통 테이블 컴포넌트 구조
```
공통 테이블 UI
├── IntegratedListTemplateV2 (템플릿)
│ ├── PageHeader
│ ├── StatCards
│ ├── SearchFilter
│ ├── TabChip (선택적)
│ ├── DataTable (데스크톱)
│ │ ├── 체크박스 컬럼
│ │ ├── 데이터 컬럼들
│ │ ├── 액션 컬럼
│ │ └── 페이지네이션
│ └── ListMobileCard (모바일/태블릿)
│ ├── 체크박스
│ ├── 헤더 뱃지
│ ├── 정보 그리드
│ └── 액션 버튼 (선택 시)
```
---
## 5. 공통 UI 컴포넌트 마이그레이션
### 5.1 마이그레이션 필요 목록
| 컴포넌트 | 소스 경로 | 타겟 경로 | 크기 | 우선순위 |
|----------|----------|----------|------|----------|
| StatCards | organisms/StatCards.tsx | organisms/ | 53줄 | P1 |
| SearchFilter | organisms/SearchFilter.tsx | organisms/ | 54줄 | P1 |
| DataTable | organisms/DataTable.tsx | organisms/ | 313줄 | P1 |
| MobileCard | organisms/MobileCard.tsx | organisms/ | 104줄 | P1 |
| ListMobileCard | organisms/ListMobileCard.tsx | organisms/ | 169줄 | P1 |
| TabChip | atoms/TabChip.tsx | atoms/ | ~50줄 | P2 |
| ListPageTemplate | templates/ListPageTemplate.tsx | templates/ | 143줄 | P2 |
| IntegratedListTemplateV2 | templates/IntegratedListTemplateV2.tsx | templates/ | 491줄 | P2 |
| StandardDialog | molecules/StandardDialog.tsx | molecules/ | ~150줄 | P3 |
| InfoField | organisms/ListMobileCard.tsx | organisms/ | 8줄 | P1 |
### 5.2 Next.js 타겟 구조
```
src/components/
├── atoms/
│ └── TabChip.tsx # NEW
├── molecules/
│ └── StandardDialog.tsx # NEW
├── organisms/
│ ├── PageHeader.tsx # 기존
│ ├── PageLayout.tsx # 기존
│ ├── StatCards.tsx # NEW
│ ├── SearchFilter.tsx # NEW
│ ├── DataTable.tsx # NEW
│ ├── MobileCard.tsx # NEW
│ └── ListMobileCard.tsx # NEW
├── templates/
│ ├── ListPageTemplate.tsx # NEW
│ └── IntegratedListTemplateV2.tsx # NEW
└── ui/ # 기존 (변경 없음)
```
### 5.3 컴포넌트 상세
#### StatCards (53줄)
```typescript
interface StatCardData {
label: string;
value: string | number;
icon?: LucideIcon;
iconColor?: string;
trend?: { value: string; isPositive: boolean };
}
```
- 2x2 또는 4열 그리드
- 아이콘 + 라벨 + 값 + 트렌드
#### SearchFilter (54줄)
```typescript
interface SearchFilterProps {
searchValue: string;
onSearchChange: (value: string) => void;
searchPlaceholder?: string;
filterButton?: boolean;
onFilterClick?: () => void;
extraActions?: ReactNode;
}
```
- 검색 아이콘 + Input
- 모바일 플레이스홀더 변경
#### DataTable (313줄)
```typescript
interface Column<T> {
key: keyof T | string;
label: string;
type?: CellType; // text, number, currency, date, status, badge, actions, custom
render?: (value: any, row: T, index?: number) => ReactNode;
}
```
- 타입별 셀 렌더링
- 페이지네이션 지원
- 로딩/빈 상태 처리
#### ListMobileCard (169줄)
```typescript
interface ListMobileCardProps {
id: string;
isSelected: boolean;
onToggleSelection: () => void;
onCardClick?: () => void;
headerBadges?: ReactNode;
title: string;
statusBadge?: ReactNode;
infoGrid: ReactNode;
actions?: ReactNode;
}
```
- 체크박스 포함
- 선택 시 스타일 변경
- InfoField 하위 컴포넌트
---
## 6. 작업 순서 (권장)
### Phase 1: 공통 UI 컴포넌트 (선행) ✅ 완료
- [x] StatCards 마이그레이션
- [x] SearchFilter 마이그레이션
- [x] DataTable 마이그레이션
- [x] MobileCard 마이그레이션
- [x] ListMobileCard + InfoField 마이그레이션
- [x] TabChip 마이그레이션
- [x] ListPageTemplate 마이그레이션
- [x] IntegratedListTemplateV2 마이그레이션
- [x] BadgeSm 마이그레이션 (추가)
- [x] StatusBadge 마이그레이션 (추가)
- [x] IconWithBadge 마이그레이션 (추가)
- [x] TableActions 마이그레이션 (추가)
- [x] EmptyState 마이그레이션 (추가)
- [x] ScreenVersionHistory 마이그레이션 (추가)
- [x] StandardDialog 마이그레이션 (추가)
- [x] formatAmount 유틸리티 마이그레이션 (추가)
### Phase 2: 거래처관리 (간단한 것 먼저) ✅ 완료
- [x] 라우트 생성: `/sales/customers`
- [x] CustomerAccountManagement 마이그레이션
- [x] 테스트 및 검증 (빌드 통과)
### Phase 3: 견적관리 (복잡한 것) ✅ 완료
- [x] 라우트 생성: `/sales/quotes`
- [x] QuoteManagement 목록 페이지 마이그레이션
- [x] Separator 컴포넌트 추가 (의존성)
- [x] 테스트 및 검증 (빌드 통과)
- [ ] QuoteManagement3Write 분석 및 마이그레이션 (향후 작업)
- [ ] QuoteManagement3Detail 분석 및 마이그레이션 (향후 작업)
---
## 7. 주의사항
### 7.1 React → Next.js 마이그레이션 체크리스트
- [ ] `'use client'` 디렉티브 추가 (상태/이벤트 사용 시)
- [ ] `localStorage` 접근 시 `typeof window !== 'undefined'` 체크
- [ ] `useRouter``next/navigation`으로 변경
- [ ] 이미지 → `next/image` 사용
- [ ] 링크 → `next/link` 사용
### 6.2 스타일 호환성
- sam-design: Tailwind CSS + shadcn/ui
- sam-react-prod: Tailwind CSS + shadcn/ui (동일)
- cn() 유틸리티 경로 확인 필요
### 6.3 의존성 확인
```bash
# sam-design에서 사용하는 라이브러리
- lucide-react
- sonner (toast)
- react-hook-form ()
- zod (validation)
```
---
## 7. 예상 작업량
| 항목 | 예상 크기 |
|------|----------|
| 공통 UI 컴포넌트 | ~1,400줄 |
| 거래처관리 | ~500줄 |
| 견적관리 (분할 포함) | ~1,500줄+ |
| **총합** | **~3,400줄+** |
---
## 8. 관련 파일 참조
### sam-design 소스 파일
```
sam-design/sam-design/src/components/
├── QuoteManagement.tsx
├── CustomerAccountManagement.tsx
├── atoms/
│ └── TabChip.tsx
├── molecules/
│ └── StandardDialog.tsx
├── organisms/
│ ├── StatCards.tsx
│ ├── SearchFilter.tsx
│ ├── DataTable.tsx
│ ├── MobileCard.tsx
│ └── ListMobileCard.tsx
└── templates/
├── ListPageTemplate.tsx
└── IntegratedListTemplateV2.tsx
```
### Next.js 타겟 경로
```
sam-react-prod/src/
├── app/[locale]/(protected)/sales/
│ ├── quotes/
│ │ └── page.tsx
│ └── customers/
│ └── page.tsx
└── components/
├── atoms/
├── molecules/
├── organisms/
└── templates/
```