# 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. 또는 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 불필요