# 품목기준관리 Zustand 리팩토링 설계서

> **핵심 목표**: 모든 기능을 100% 동일하게 유지하면서, 수정 절차를 간단화

## 📌 핵심 원칙

```
⚠️ 중요: 모든 품목기준관리 기능을 그대로 가져와야 함
⚠️ 중요: 수정 절차 간단화가 핵심 (3방향 동기화 → 1곳 수정)
⚠️ 중요: 모든 기능이 정확히 동일하게 작동해야 함
```

## 🔴 최종 검증 기준 (가장 중요!)

### 페이지 관계도

```
┌─────────────────────────────────────────────────────────────┐
│                        [DB / API]                           │
│                   (단일 진실 공급원)                          │
└─────────────────────────────────────────────────────────────┘
        ↑                    ↑                    ↓
        │                    │                    │
┌───────┴───────┐  ┌────────┴────────┐  ┌────────┴────────┐
│ 품목기준관리    │  │ 품목기준관리      │  │   품목관리       │
│ 테스트 페이지   │  │ 페이지 (기존)     │  │   페이지         │
│ (Zustand)     │  │ (Context)        │  │ (동적 폼 렌더링)  │
└───────────────┘  └──────────────────┘  └──────────────────┘
     [신규]              [기존]              [최종 사용처]
```

### 검증 시나리오

```
1. 테스트 페이지에서 섹션/필드 수정
        ↓
2. API 호출 → DB 저장
        ↓
3. 품목기준관리 페이지 (기존)에서 동일하게 표시되어야 함
        ↓
4. 품목관리 페이지에서 동적 폼이 변경된 구조로 렌더링되어야 함
```

### 필수 검증 항목

| # | 검증 항목 | 설명 |
|---|----------|------|
| 1 | **API 동일성** | 테스트 페이지가 기존 페이지와 동일한 API 엔드포인트 사용 |
| 2 | **데이터 동일성** | API 응답/요청 데이터 형식 100% 동일 |
| 3 | **기존 페이지 반영** | 테스트 페이지에서 수정 → 기존 품목기준관리 페이지에 반영 |
| 4 | **품목관리 반영** | 테스트 페이지에서 수정 → 품목관리 동적 폼에 반영 |

### 왜 이게 중요한가?

```
테스트 페이지 (Zustand) ──┐
                         ├──→ 같은 API ──→ 같은 DB ──→ 품목관리 페이지
기존 페이지 (Context) ────┘

→ 상태 관리 방식만 다르고, API/DB는 공유
→ 테스트 페이지에서 수정한 내용이 품목관리 페이지에 그대로 적용되어야 함
→ 이것이 성공하면 Zustand 리팩토링이 완전히 검증된 것
```

---

## 1. 현재 문제점 분석

### 1.1 중복 상태 관리 (3방향 동기화)

현재 `ItemMasterContext.tsx`에서 섹션 수정 시:

```typescript
// updateSection() 함수 내부 (Line 1464-1486)
setItemPages(...)           // 1. 계층구조 탭
setSectionTemplates(...)    // 2. 섹션 탭
setIndependentSections(...) // 3. 독립 섹션
```

**문제점**:
- 같은 데이터를 3곳에서 중복 관리
- 한 곳 업데이트 누락 시 데이터 불일치
- 모든 CRUD 함수에 동일 패턴 반복
- 새 기능 추가 시 3곳 모두 수정 필요

### 1.2 현재 상태 변수 목록 (16개)

| # | 상태 변수 | 설명 | 중복 여부 |
|---|----------|------|----------|
| 1 | `itemMasters` | 품목 마스터 | - |
| 2 | `specificationMasters` | 규격 마스터 | - |
| 3 | `materialItemNames` | 자재 품목명 | - |
| 4 | `itemCategories` | 품목 분류 | - |
| 5 | `itemUnits` | 단위 | - |
| 6 | `itemMaterials` | 재질 | - |
| 7 | `surfaceTreatments` | 표면처리 | - |
| 8 | `partTypeOptions` | 부품유형 옵션 | - |
| 9 | `partUsageOptions` | 부품용도 옵션 | - |
| 10 | `guideRailOptions` | 가이드레일 옵션 | - |
| 11 | `sectionTemplates` | 섹션 템플릿 | ⚠️ 중복 |
| 12 | `itemMasterFields` | 필드 마스터 | ⚠️ 중복 |
| 13 | `itemPages` | 페이지 (섹션/필드 포함) | ⚠️ 중복 |
| 14 | `independentSections` | 독립 섹션 | ⚠️ 중복 |
| 15 | `independentFields` | 독립 필드 | ⚠️ 중복 |
| 16 | `independentBomItems` | 독립 BOM | ⚠️ 중복 |

**중복 문제가 있는 엔티티**:
- **섹션**: `sectionTemplates`, `itemPages.sections`, `independentSections`
- **필드**: `itemMasterFields`, `itemPages.sections.fields`, `independentFields`
- **BOM**: `itemPages.sections.bom_items`, `independentBomItems`

---

## 2. 리팩토링 설계

### 2.1 정규화된 상태 구조 (Normalized State)

```typescript
// stores/useItemMasterStore.ts
interface ItemMasterState {
  // ===== 정규화된 엔티티 (ID 기반 딕셔너리) =====
  entities: {
    pages: Record<number, PageEntity>;
    sections: Record<number, SectionEntity>;
    fields: Record<number, FieldEntity>;
    bomItems: Record<number, BOMItemEntity>;
  };

  // ===== ID 목록 (순서 관리) =====
  ids: {
    pages: number[];
    independentSections: number[];  // page_id가 null인 섹션
    independentFields: number[];    // section_id가 null인 필드
    independentBomItems: number[];  // section_id가 null인 BOM
  };

  // ===== 참조 데이터 (중복 없음) =====
  references: {
    itemMasters: ItemMaster[];
    specificationMasters: SpecificationMaster[];
    materialItemNames: MaterialItemName[];
    itemCategories: ItemCategory[];
    itemUnits: ItemUnit[];
    itemMaterials: ItemMaterial[];
    surfaceTreatments: SurfaceTreatment[];
    partTypeOptions: PartTypeOption[];
    partUsageOptions: PartUsageOption[];
    guideRailOptions: GuideRailOption[];
  };

  // ===== UI 상태 =====
  ui: {
    isLoading: boolean;
    error: string | null;
    selectedPageId: number | null;
    selectedSectionId: number | null;
  };
}
```

### 2.2 엔티티 구조

```typescript
// 페이지 엔티티 (섹션 ID만 참조)
interface PageEntity {
  id: number;
  page_name: string;
  item_type: string;
  description?: string;
  order_no: number;
  is_active: boolean;
  sectionIds: number[];  // 섹션 객체 대신 ID만 저장
  created_at?: string;
  updated_at?: string;
}

// 섹션 엔티티 (필드/BOM ID만 참조)
interface SectionEntity {
  id: number;
  title: string;
  page_id: number | null;  // null이면 독립 섹션
  order_no: number;
  is_collapsible: boolean;
  default_open: boolean;
  fieldIds: number[];      // 필드 ID 목록
  bomItemIds: number[];    // BOM ID 목록
  created_at?: string;
  updated_at?: string;
}

// 필드 엔티티
interface FieldEntity {
  id: number;
  field_key: string;
  field_name: string;
  field_type: string;
  section_id: number | null;  // null이면 독립 필드
  order_no: number;
  is_required: boolean;
  options?: any;
  default_value?: any;
  created_at?: string;
  updated_at?: string;
}

// BOM 엔티티
interface BOMItemEntity {
  id: number;
  section_id: number | null;  // null이면 독립 BOM
  child_item_code: string;
  child_item_name: string;
  quantity: number;
  unit: string;
  order_no: number;
  created_at?: string;
  updated_at?: string;
}
```

### 2.3 수정 절차 비교

#### Before (현재): 3방향 동기화

```typescript
const updateSection = async (sectionId, updates) => {
  const response = await api.update(sectionId, updates);

  // 1. itemPages 업데이트
  setItemPages(prev => prev.map(page => ({
    ...page,
    sections: page.sections.map(s => s.id === sectionId ? {...s, ...updates} : s)
  })));

  // 2. sectionTemplates 업데이트
  setSectionTemplates(prev => prev.map(t =>
    t.id === sectionId ? {...t, ...updates} : t
  ));

  // 3. independentSections 업데이트
  setIndependentSections(prev => prev.map(s =>
    s.id === sectionId ? {...s, ...updates} : s
  ));
};
```

#### After (Zustand): 1곳만 수정

```typescript
const updateSection = async (sectionId, updates) => {
  const response = await api.update(sectionId, updates);

  // 딱 1곳만 수정하면 끝!
  set((state) => ({
    entities: {
      ...state.entities,
      sections: {
        ...state.entities.sections,
        [sectionId]: { ...state.entities.sections[sectionId], ...updates }
      }
    }
  }));
};
```

### 2.4 파생 상태 (Selectors)

```typescript
// 계층구조 탭용: 페이지 + 섹션 + 필드 조합
const usePageWithDetails = (pageId: number) => {
  return useItemMasterStore((state) => {
    const page = state.entities.pages[pageId];
    if (!page) return null;

    return {
      ...page,
      sections: page.sectionIds.map(sId => {
        const section = state.entities.sections[sId];
        return {
          ...section,
          fields: section.fieldIds.map(fId => state.entities.fields[fId]),
          bom_items: section.bomItemIds.map(bId => state.entities.bomItems[bId]),
        };
      }),
    };
  });
};

// 섹션 탭용: 모든 섹션 (페이지 연결 여부 무관)
const useAllSections = () => {
  return useItemMasterStore((state) =>
    Object.values(state.entities.sections)
  );
};

// 독립 섹션만
const useIndependentSections = () => {
  return useItemMasterStore((state) =>
    state.ids.independentSections.map(id => state.entities.sections[id])
  );
};
```

---

## 3. 기능 매핑 체크리스트

### 3.1 페이지 관리

| 기존 함수 | 새 함수 | 상태 |
|----------|--------|------|
| `loadItemPages` | `loadPages` | ⬜ |
| `addItemPage` | `createPage` | ⬜ |
| `updateItemPage` | `updatePage` | ⬜ |
| `deleteItemPage` | `deletePage` | ⬜ |

### 3.2 섹션 관리

| 기존 함수 | 새 함수 | 상태 |
|----------|--------|------|
| `loadSectionTemplates` | `loadSections` | ⬜ |
| `loadIndependentSections` | (loadSections에 통합) | ⬜ |
| `addSectionTemplate` | `createSection` | ⬜ |
| `addSectionToPage` | `createSectionInPage` | ⬜ |
| `createIndependentSection` | `createSection` (page_id: null) | ⬜ |
| `updateSectionTemplate` | `updateSection` | ⬜ |
| `updateSection` | `updateSection` | ⬜ |
| `deleteSectionTemplate` | `deleteSection` | ⬜ |
| `deleteSection` | `deleteSection` | ⬜ |
| `linkSectionToPage` | `linkSectionToPage` | ⬜ |
| `unlinkSectionFromPage` | `unlinkSectionFromPage` | ⬜ |
| `getSectionUsage` | `getSectionUsage` | ⬜ |

### 3.3 필드 관리

| 기존 함수 | 새 함수 | 상태 |
|----------|--------|------|
| `loadItemMasterFields` | `loadFields` | ⬜ |
| `loadIndependentFields` | (loadFields에 통합) | ⬜ |
| `addItemMasterField` | `createField` | ⬜ |
| `addFieldToSection` | `createFieldInSection` | ⬜ |
| `createIndependentField` | `createField` (section_id: null) | ⬜ |
| `updateItemMasterField` | `updateField` | ⬜ |
| `updateField` | `updateField` | ⬜ |
| `deleteItemMasterField` | `deleteField` | ⬜ |
| `deleteField` | `deleteField` | ⬜ |
| `linkFieldToSection` | `linkFieldToSection` | ⬜ |
| `unlinkFieldFromSection` | `unlinkFieldFromSection` | ⬜ |
| `getFieldUsage` | `getFieldUsage` | ⬜ |

### 3.4 BOM 관리

| 기존 함수 | 새 함수 | 상태 |
|----------|--------|------|
| `loadIndependentBomItems` | `loadBomItems` | ⬜ |
| `addBOMItem` | `createBomItem` | ⬜ |
| `createIndependentBomItem` | `createBomItem` (section_id: null) | ⬜ |
| `updateBOMItem` | `updateBomItem` | ⬜ |
| `deleteBOMItem` | `deleteBomItem` | ⬜ |

### 3.5 참조 데이터 관리

| 기존 함수 | 새 함수 | 상태 |
|----------|--------|------|
| `addItemMaster` / `updateItemMaster` / `deleteItemMaster` | `itemMasterActions` | ⬜ |
| `addSpecificationMaster` / `updateSpecificationMaster` / `deleteSpecificationMaster` | `specificationActions` | ⬜ |
| `addMaterialItemName` / `updateMaterialItemName` / `deleteMaterialItemName` | `materialItemNameActions` | ⬜ |
| `addItemCategory` / `updateItemCategory` / `deleteItemCategory` | `categoryActions` | ⬜ |
| `addItemUnit` / `updateItemUnit` / `deleteItemUnit` | `unitActions` | ⬜ |
| `addItemMaterial` / `updateItemMaterial` / `deleteItemMaterial` | `materialActions` | ⬜ |
| `addSurfaceTreatment` / `updateSurfaceTreatment` / `deleteSurfaceTreatment` | `surfaceTreatmentActions` | ⬜ |
| `addPartTypeOption` / `updatePartTypeOption` / `deletePartTypeOption` | `partTypeActions` | ⬜ |
| `addPartUsageOption` / `updatePartUsageOption` / `deletePartUsageOption` | `partUsageActions` | ⬜ |
| `addGuideRailOption` / `updateGuideRailOption` / `deleteGuideRailOption` | `guideRailActions` | ⬜ |

---

## 4. 구현 계획

### Phase 1: 기반 구축 ✅ 완료 (2025-12-20)

- [x] Zustand, Immer 설치
- [x] 테스트 페이지 라우트 생성 (`/items-management-test`)
- [x] 기본 스토어 구조 생성 (`useItemMasterStore.ts`)
- [x] 타입 정의 (`types.ts`)

### Phase 2: API 연동 ✅ 완료 (2025-12-20)

- [x] 기존 API 구조 분석 (`item-master.ts`)
- [x] API 응답 → 정규화 상태 변환 함수 (`normalizers.ts`)
- [x] 스토어에 `initFromApi()` 함수 구현
- [x] 테스트 페이지에서 실제 API 데이터 로드 기능 추가

**생성된 파일**:
- `src/stores/item-master/normalizers.ts` - API 응답 정규화 함수

**테스트 페이지 기능**:
- "실제 API 로드" 버튼 - 백엔드 API에서 실제 데이터 로드
- "테스트 데이터 로드" 버튼 - 하드코딩된 테스트 데이터 로드
- 데이터 소스 표시 (API/테스트/없음)

### Phase 3: 핵심 엔티티 구현

- [x] 페이지 CRUD 구현 (로컬 상태)
- [x] 섹션 CRUD 구현 (로컬 상태)
- [x] 필드 CRUD 구현 (로컬 상태)
- [x] BOM CRUD 구현 (로컬 상태)
- [x] link/unlink 기능 구현 (로컬 상태)
- [ ] API 연동 CRUD (DB 저장) - **다음 단계**

### Phase 3: 참조 데이터 구현

- [ ] 품목 마스터 관리
- [ ] 규격 마스터 관리
- [ ] 분류/단위/재질 등 옵션 관리

### Phase 4: 파생 상태 & 셀렉터

- [ ] 계층구조 뷰용 셀렉터
- [ ] 섹션 탭용 셀렉터
- [ ] 필드 탭용 셀렉터
- [ ] 독립 항목 셀렉터

### Phase 5: UI 연동

- [ ] 테스트 페이지 컴포넌트 생성
- [ ] 기존 컴포넌트 재사용 (스토어만 교체)
- [ ] 동작 검증

### Phase 6: 검증 & 마이그레이션

- [ ] 기존 페이지와 1:1 동작 비교
- [ ] 엣지 케이스 테스트
- [ ] 성능 비교
- [ ] 기존 페이지 마이그레이션 결정

---

## 5. 파일 구조

```
src/
├── stores/
│   └── item-master/
│       ├── useItemMasterStore.ts      # 메인 스토어
│       ├── slices/
│       │   ├── pageSlice.ts           # 페이지 액션
│       │   ├── sectionSlice.ts        # 섹션 액션
│       │   ├── fieldSlice.ts          # 필드 액션
│       │   ├── bomSlice.ts            # BOM 액션
│       │   └── referenceSlice.ts      # 참조 데이터 액션
│       ├── selectors/
│       │   ├── pageSelectors.ts       # 페이지 파생 상태
│       │   ├── sectionSelectors.ts    # 섹션 파생 상태
│       │   └── fieldSelectors.ts      # 필드 파생 상태
│       └── types.ts                   # 타입 정의
│
├── app/[locale]/(protected)/
│   └── items-management-test/
│       └── page.tsx                   # 테스트 페이지
```

---

## 6. 테스트 시나리오

### 6.1 섹션 수정 동기화 테스트

```
시나리오: 섹션 이름 수정
1. 계층구조 탭에서 섹션 선택
2. 섹션 이름 "기본정보" → "기본 정보" 수정
3. 검증:
   - [ ] 계층구조 탭에 반영
   - [ ] 섹션 탭에 반영
   - [ ] 독립 섹션(연결 해제 시) 반영
   - [ ] API 호출 1회만 발생
```

### 6.2 필드 이동 테스트

```
시나리오: 필드를 다른 섹션으로 이동
1. 섹션 A에서 필드 선택
2. 섹션 B로 이동 (unlink → link)
3. 검증:
   - [ ] 섹션 A에서 필드 제거
   - [ ] 섹션 B에 필드 추가
   - [ ] 계층구조 탭 반영
   - [ ] 필드 탭에서 section_id 변경
```

### 6.3 독립 → 연결 테스트

```
시나리오: 독립 섹션을 페이지에 연결
1. 독립 섹션 선택
2. 페이지에 연결 (linkSectionToPage)
3. 검증:
   - [ ] 독립 섹션 목록에서 제거
   - [ ] 페이지의 섹션 목록에 추가
   - [ ] 섹션 탭에서 page_id 변경
```

---

## 7. 롤백 계획

문제 발생 시:
1. 테스트 페이지 라우트 제거
2. 스토어 코드 삭제
3. 기존 `ItemMasterContext` 그대로 사용

**리스크 최소화**:
- 기존 코드 수정 없음
- 새 코드만 추가
- 언제든 롤백 가능

---

## 8. 성공 기준

| 항목 | 기준 |
|-----|------|
| **기능 동등성** | 기존 모든 기능 100% 동작 |
| **동기화** | 1곳 수정으로 모든 뷰 업데이트 |
| **코드량** | CRUD 함수 코드 50% 이상 감소 |
| **버그** | 데이터 불일치 버그 0건 |
| **성능** | 기존 대비 동등 또는 향상 |

---

## 변경 이력

| 날짜 | 작성자 | 내용 |
|-----|--------|------|
| 2025-12-20 | Claude | 초안 작성 |
| 2025-12-20 | Claude | Phase 1 완료 - 기반 구축 |
| 2025-12-20 | Claude | Phase 2 완료 - API 연동 (normalizers.ts, initFromApi) |