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:
@@ -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/ - 대시보드 & 사이드바
|
||||
|
||||
| 파일 | 설명 |
|
||||
|
||||
@@ -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 조건 확인
|
||||
```
|
||||
@@ -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 전체 구현 |
|
||||
@@ -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 품목코드 하드코딩 내역 상세
|
||||
@@ -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 연동) |
|
||||
225
claudedocs/item-master/[REF-2025-12-01] state-sync-solutions.md
Normal file
225
claudedocs/item-master/[REF-2025-12-01] state-sync-solutions.md
Normal 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` - 훅 분리 작업 기록
|
||||
241
claudedocs/item-master/[REF] item-code-hardcoding.md
Normal file
241
claudedocs/item-master/[REF] item-code-hardcoding.md
Normal 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 | 하드코딩 내역 문서화 |
|
||||
455
claudedocs/sales/[PLAN-2025-12-02] sales-pages-migration.md
Normal file
455
claudedocs/sales/[PLAN-2025-12-02] sales-pages-migration.md
Normal 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/
|
||||
```
|
||||
Reference in New Issue
Block a user