# Step 4: React 절곡품 관리 화면 + 견적 이미지 연동 ⬜ 미착수

> **프로젝트**: React (`sam/react`)
> **선행 조건**: Step 2 (API ✅), Step 3 (MNG 샘플 ✅)
> **상태**: ⬜ 미착수
> **참조**: MNG 샘플 화면, 기존 GuideRailSection 컴포넌트

---

## 1. 개요

MNG에서 샘플로 구현/검증한 절곡품 관리 기능을 React 운영 화면으로 이관한다.
모든 API 엔드포인트는 Step 2에서 완료되어 있으므로, **프론트엔드 구현만** 필요.

---

## 2. API 엔드포인트 (Step 2 완료 — 그대로 사용)

### 2-1. 기초관리 (절곡 부품)
```
GET    /api/v1/bending-items              ← 목록 (필터/검색/페이지네이션)
GET    /api/v1/bending-items/filters      ← 필터 옵션 (분류/재질/모델 distinct)
GET    /api/v1/bending-items/{id}         ← 상세 (options 전체)
POST   /api/v1/bending-items              ← 등록
PUT    /api/v1/bending-items/{id}         ← 수정
DELETE /api/v1/bending-items/{id}         ← 삭제 (soft delete)
```

**응답 구조** (목록):
```json
{
  "success": true,
  "data": {
    "data": [
      {
        "id": 15862, "code": "BD-BE-30",
        "name": "하단마감재(스크린) EGI 3000mm",
        "item_type": "PT", "item_category": "BENDING",
        "item_name": "하단마감재", "item_sep": "스크린",
        "item_bending": "하단마감재", "item_spec": "60*40",
        "material": "EGI 1.55T", "model_name": null,
        "model_UA": "인정", "search_keyword": null,
        "rail_width": null, "registration_date": "2025-07-21",
        "author": "개발자", "memo": null,
        "exit_direction": null, "front_bottom_width": null,
        "box_width": null, "box_height": null,
        "bendingData": [
          {"no":1,"input":15,"rate":"","sum":15,"color":false,"aAngle":false},
          {"no":2,"input":14,"rate":"-1","sum":28,"color":false,"aAngle":false}
        ],
        "prefix": "BE", "length_code": "30", "length_mm": 3000,
        "legacy_bending_num": 288,
        "width_sum": 193, "bend_count": 5,
        "created_at": "2026-02-21 19:47:01"
      }
    ],
    "current_page": 1, "total": 170, "last_page": 6, "per_page": 30
  }
}
```

### 2-2. 절곡품 모델 (가이드레일/케이스/하단마감재 통합)
```
GET    /api/v1/guiderail-models                      ← 모델 목록
GET    /api/v1/guiderail-models/filters              ← 필터 옵션
GET    /api/v1/guiderail-models/{id}                 ← 모델 상세 (부품 조합)
POST   /api/v1/guiderail-models                      ← 모델 등록
PUT    /api/v1/guiderail-models/{id}                 ← 모델 수정
DELETE /api/v1/guiderail-models/{id}                 ← 모델 삭제
```

**카테고리 필터**: `?item_category=GUIDERAIL_MODEL|SHUTTERBOX_MODEL|BOTTOMBAR_MODEL`

**응답 구조** (상세):
```json
{
  "success": true,
  "data": {
    "id": 15914, "code": "GR-KDSS01-벽면형-SUS",
    "name": "KDSS01 벽면형 SUS마감",
    "item_category": "GUIDERAIL_MODEL",
    "model_name": "KDSS01", "check_type": "벽면형",
    "rail_width": 150, "rail_length": 150,
    "finishing_type": "SUS마감",
    "item_sep": "스크린", "model_UA": "인정",
    "components": [
      {
        "orderNumber": 1,
        "itemName": "1번(마감제)", "material": "SUS 1.2T",
        "quantity": 2, "width_sum": 227,
        "bendingData": [
          {"no":1,"input":15,"rate":"0","sum":15,"color":true,"aAngle":false},
          {"no":2,"input":13,"rate":"0","sum":28,"color":false,"aAngle":false}
        ],
        "legacy_bending_num": "170"
      }
    ],
    "material_summary": {"SUS 1.2T": 599, "EGI 1.55T": 894},
    "component_count": 4
  }
}
```

### 2-3. 이미지 (기존 API 재사용)
```
POST   /api/v1/items/{id}/files           ← 업로드 (field_key: 'bending_diagram')
GET    /api/v1/items/{id}/files           ← 목록 조회
GET    /api/v1/files/{id}/view            ← 인라인 표시
DELETE /api/v1/items/{id}/files/{fileId}  ← 삭제
```

---

## 3. React 화면 구성

### 3-1. 메뉴 구조 (사이드바)

```
생산 관리
├─ ... (기존)
└─ 절곡품 관리
   ├─ 기초관리         /bending/base
   ├─ 절곡품 (가이드레일) /bending/products
   ├─ 케이스           /bending/cases
   └─ 하단마감재       /bending/bottombars
```

### 3-2. 기초관리 화면

**목록 (`/bending/base`)**:
| 컬럼 | API 필드 |
|------|---------|
| NO | id |
| 코드 | code |
| 대분류 | item_sep (스크린=파란배지, 철재=주황배지) |
| 인정 | model_UA |
| 분류 | item_bending |
| 품명 | item_name |
| 규격 | item_spec |
| 재질 | material |
| 모델 | model_name |
| 폭합 | width_sum |
| 절곡수 | bend_count |
| 등록일 | created_at |

**필터**: item_sep, item_bending, material, search (HTMX 실시간 검색)

**폼 (`/bending/base/{id}/edit`)**:

기본 정보 (4열 그리드):
- 코드*, 이름*, 품명*, 대분류*(select)
- 분류*(datalist), 재질*(datalist), 규격, 모델(datalist)
- 인정여부(select), 등록일(date), 작성자, 검색어

케이스 전용 (분류=케이스 시 표시):
- 점검구 방향(select), 전면밑(mm), 레일폭(mm), 케이스 너비(mm), 케이스 높이(mm)

절곡 입력 테이블:
- 동적 열 추가/삭제
- 행: 입력(number) → 연신율(text) → 연신율후(자동) → 합계(자동) → 음영(checkbox) → A각(checkbox)
- 연신율 규칙: `-1` → input-1mm, `1` → input+1mm, 빈값 → 그대로
- 폭합계 + 절곡횟수 자동 표시
- `bendingData` JSON으로 직렬화하여 API 전송

이미지: 파일 업로드 + Ctrl+V 클립보드 + 미리보기

### 3-3. 절곡품 화면 (3가지 타입)

**목록** (공통 테이블):
| 컬럼 | API 필드 |
|------|---------|
| NO | id |
| 모델명 | model_name |
| 대분류 | item_sep |
| 인정 | model_UA |
| 형상 | check_type |
| 레일폭×높이 | rail_width × rail_length |
| 마감 | finishing_type |
| 부품수 | component_count |
| 소요자재량 | material_summary |

**필터**: `item_category`(필수!), item_sep, model_UA, check_type, model_name, search

> ⚠️ `item_category` 없이 호출하면 3개 카테고리 60건이 섞여서 나옴

**폼 — 타입별 헤더 차이**:

| 필드 | 가이드레일 | 케이스 | 하단마감재 |
|------|:---:|:---:|:---:|
| 등록일/작성자/비고 | ✅ | ✅ | ✅ |
| 외형치수 (가로×세로) | ✅ 너비×폭 | ✅ 폭×높이+전면밑+레일폭 | ✅ 폭×높이 |
| 대분류 라디오 | ✅ | ❌ | ✅ |
| 인정/비인정 라디오 | ✅ | ❌ | ✅ |
| 형태 라디오 (벽면/측면) | ✅ | ❌ | ❌ |
| 점검구 방향 라디오 | ❌ | ✅ | ❌ |
| 모델 select | ✅ | ❌ | ✅ |
| 마감 select | ✅ | ❌ | ✅ |
| 품목검색어 | ✅ | ✅ | ✅ |

**절곡 부품 조립 섹션**:
- 부품별 절곡 테이블 (inline 편집)
- 부품 추가: 기초관리 검색 모달 (필터+체크박스+선택적용)
- 부품 삭제: DOM 즉시 제거
- 품명/재질/수량 inline 편집
- 순서 변경 (위로/아래로)
- `components` JSON으로 직렬화하여 API 전송

**재질별 폭합**: components에서 자동 계산 (`material_summary`)

**작업지시서 PDF**: 별도 인쇄 페이지 (`/{type}/{id}/print`)
- 레거시 포맷: 번호 | 재질 | 절곡치수(합계+음영) | 폭합 | 수량
- A각 표시 행
- A4 가로 인쇄 최적화

---

## 4. React 컴포넌트 구조 (설계안)

```
src/pages/bending/
├─ base/
│  ├─ BendingBaseList.tsx          ← 기초관리 목록
│  └─ BendingBaseForm.tsx          ← 등록/수정/조회 (mode 분기)
├─ products/
│  ├─ BendingProductList.tsx       ← 절곡품 목록 (category prop으로 3타입 공용)
│  ├─ BendingProductForm.tsx       ← 등록/수정/조회 (타입별 헤더 분기)
│  └─ BendingProductPrint.tsx      ← 작업지시서 인쇄
├─ components/
│  ├─ BendingTable.tsx             ← 절곡 입력 테이블 (공용 컴포넌트)
│  ├─ BendingSearchModal.tsx       ← 기초관리 부품 검색 모달
│  ├─ PartListEditor.tsx           ← 부품 조립 편집기
│  ├─ MaterialSummary.tsx          ← 재질별 폭합 표시
│  └─ GuiderailPreview.tsx         ← 견적 페이지용 미리보기
└─ hooks/
   ├─ useBendingItems.ts           ← API 호출 훅
   └─ useGuiderailModels.ts        ← API 호출 훅
```

### 4-1. BendingTable 컴포넌트 (핵심)

```tsx
interface BendingData {
  no: number;
  input: number;
  rate: string;    // '' | '-1' | '1'
  sum: number;     // 자동 계산
  color: boolean;  // 음영 마킹
  aAngle: boolean; // A각 표시
}

interface BendingTableProps {
  data: BendingData[];
  onChange: (data: BendingData[]) => void;
  readOnly?: boolean;
}
```

### 4-2. BendingSearchModal 컴포넌트

```tsx
interface BendingSearchModalProps {
  open: boolean;
  onClose: () => void;
  onSelect: (items: BendingItem[]) => void;
  filters?: { item_sep?: string; item_bending?: string; };
}
```

### 4-3. GuiderailPreview 컴포넌트 (견적 페이지 연동)

```
┌─────────────────────────────────────────────────────┐
│ 가이드레일: KSS01 벽면형 | 인정 | SUS마감 | 70×120  │
├──────────────────────┬──────────────────────────────┤
│  전개도 이미지        │  부품 조합                    │
│  ┌────────────────┐  │  # │ 부품    │ 재질  │ 수량  │
│  │                │  │  1 │ 마감재  │ SUS  │ 2    │
│  │  (R2 이미지)   │  │  2 │ 본체   │ EGI  │ 1    │
│  │                │  │  3 │ C형   │ EGI  │ 1    │
│  └────────────────┘  │  4 │ D형   │ EGI  │ 1    │
│                      │  재질별 폭합               │
│                      │  SUS: 406 | EGI: 398      │
└──────────────────────┴──────────────────────────────┘
```

---

## 5. 데이터 현황 (Step 1~3 완료 시점)

| 항목 | 건수 | 상태 |
|------|:---:|:---:|
| 기초관리 (BENDING) | 170건 | ✅ |
| ├ 전개도 임포트 | 139/170건 | ✅ (31건 chandj 원본 없음) |
| 가이드레일 모델 (GUIDERAIL_MODEL) | 20건 | ✅ |
| 케이스 모델 (SHUTTERBOX_MODEL) | 30건 | ✅ |
| 하단마감재 모델 (BOTTOMBAR_MODEL) | 10건 | ✅ |
| DB 메뉴 | 4개 | ✅ |

---

## 6. 주의사항

- ✅ 기존 GuideRailSection (작업지시서용) 무변경 — 별도 컴포넌트
- ✅ 기존 BendingInfoBuilder / PrefixResolver 무변경
- ✅ 이미지 없는 모델: 텍스트만 표시 (graceful degradation)
- ✅ MNG는 **샘플 확인용** — React가 **운영용**
- ✅ MNG/React 모두 동일한 API 엔드포인트 호출
- ⚠️ tenant_id 287 하드코딩 → React에서는 Sanctum Bearer 토큰으로 자동 해결
- ⚠️ **인증 전환**: React 작업 시 `ApiKeyMiddleware.php`의 `allowWithoutAuth`에서 bending 관련 4줄 제거 → 다른 API와 동일한 2중 인증 (API Key + Bearer) 적용
- ⚠️ 재고 데이터 (stocks 153건) 이미 존재 — React에서 재고 연동 시 기존 Stock API 사용
- ⚠️ **운영 배포 전 정리 필요** (options 불필요 키 삭제):
  - `source` (5130_migration 등) — 마이그레이션 추적용, 운영 불필요
  - `legacy_prod`, `legacy_spec`, `legacy_slength` — PREFIX 생성 완료, 삭제 가능
  - `legacy_bending_num`, `legacy_num`, `legacy_guiderail_num` — 이미지 매핑 완료, 삭제 가능
  - `lot_managed`, `consumption_method`, `production_source`, `input_tracking` — 재고 시스템 별도 관리
  - `author = '개발자'` — 실제 담당자로 변경 필요
  - ⚠️ 레거시 키 삭제 후 `bending:import-*` 커맨드 재실행 불가 — 운영 확정 후 정리
- ⚠️ **운영 안정화 후 마이그레이션 커맨드 삭제 가능** (1회성 도구):
  - `BendingFillOptions.php`, `BendingImportLegacy.php`, `BendingImportImages.php`
  - `GuiderailImportLegacy.php`, `BendingProductImportLegacy.php`
  - `BendingModelImportImages.php`, `BendingModelImportAssemblyImages.php`
  - ⚠️ R2 이미지 + files 레코드는 운영 데이터 — 삭제 불가

---

## 7. MNG 샘플에서 발견된 실무 구현 노트

> React 구현 시 참고할 MNG 작업 중 발견된 이슈 및 해결 방법

### 7-1. API 호출 주의사항

| 이슈 | 원인 | 해결 |
|------|------|------|
| PUT/POST JSON body 파싱 안 됨 | Laravel API가 form-data만 파싱 | React axios는 JSON 자동 처리되므로 문제 없음 |
| `bendingData` 전송 | MNG form은 hidden input JSON 문자열 | React는 객체 배열 그대로 전송 가능 |
| pagination 메타 누락 | `ResourceCollection` 감싸면 메타 사라짐 | `paginator.transform()` 방식으로 수정 완료 |
| `unique:items,code` | Store 시 코드 중복 체크 | React form에서 에러 메시지 표시 필요 |

### 7-2. 절곡 테이블 구현 핵심 로직

```
연신율 보정 규칙:
  rate = ""   → 보정 없음 (input 그대로)
  rate = "-1" → input - 1mm (절곡 시 1mm 줄어듦)
  rate = "1"  → input + 1mm

합계 = 보정 후 값의 누적합
폭합계 = 마지막 합계값

절곡횟수 = rate가 빈 문자열이 아닌 열의 수
```

### 7-3. 부품 추가 모달 동작

```
1. [+ 부품 추가] 클릭 → 모달 열기
2. GET /api/v1/bending-items?item_sep=&item_bending=&material=&search=&size=100
3. 체크박스로 복수 선택
4. [선택 적용] → 선택된 아이템의 bendingData를 components에 push
5. components JSON 직렬화 → hidden input → form submit
```

### 7-4. 타입별 라우트 매핑

| React 라우트 | API 파라미터 | MNG 참고 |
|-------------|------------|---------|
| `/bending/base/*` | `/api/v1/bending-items` | `BendingBaseController` |
| `/bending/products/*` | `/api/v1/guiderail-models?item_category=GUIDERAIL_MODEL` (20건) | `BendingProductController` |
| `/bending/cases/*` | `/api/v1/guiderail-models?item_category=SHUTTERBOX_MODEL` (30건) | 동일 컨트롤러 |
| `/bending/bottombars/*` | `/api/v1/guiderail-models?item_category=BOTTOMBAR_MODEL` (10건) | 동일 컨트롤러 |

> ⚠️ `item_category` 파라미터 누락 시 60건 전부 반환됨 — React에서 반드시 포함할 것

### 7-5. 작업지시서 PDF

MNG에서는 `window.print()` 기반 별도 인쇄 페이지(`/print`)로 구현.
React에서는 동일 방식 또는 html2pdf.js / react-to-print 라이브러리 사용 가능.

**인쇄 포맷 (레거시 동일):**
- 헤더: 모델명, 형태, 규격, 마감
- 테이블: 번호 | 재질 | 절곡치수(합계+음영) | A각 | 폭합 | 수량
- 재질별 폭합 요약
- A4 가로

### 7-6. 이미지 업로드 흐름

```
React:
1. <input type="file"> 또는 Ctrl+V 클립보드
2. POST /api/v1/items/{itemId}/files  (FormData: file + field_key=bending_diagram)
3. 응답: { file_id, file_url }
4. 표시: GET /api/v1/files/{fileId}/view (inline 이미지)
```

### 7-7. 메뉴 구조 (사이드바)

```
절곡품 관리
├─ 기초관리      /bending/base         (170건)
├─ 가이드레일    /bending/products     (20건, GUIDERAIL_MODEL)
├─ 케이스        /bending/cases        (30건, SHUTTERBOX_MODEL)
└─ 하단마감재    /bending/bottombars   (10건, BOTTOMBAR_MODEL)
```

React 사이드바 메뉴는 DB `menus` 테이블 기반 동적 렌더링 — 이미 등록 완료.

### 7-8. 재고 연동 (향후)

절곡 부품 재고는 SAM 기존 재고 시스템에 통합:
- `stocks` 테이블: `item_type = 'bent_part'` (153건)
- `stock_lots` 테이블: LOT 기반 FIFO 재고
- 기존 Stock API 사용 가능 — 별도 재고 API 불필요
