- step1: 데이터 임포트 완료 (170+60건), artisan 커맨드 7개 실행 결과 - step2: API 12개 엔드포인트 완료, item_category 필수 필터 추가 - step3: MNG 샘플 완료 (4개 메뉴, 이미지 473건) - step4: React 구현 가이드 전면 작성 (API 응답 구조, 컴포넌트 설계, 실무 노트) - 코드 체계 변경 불가 사유, 265vs170 차이 설명, 운영 전 정리 항목 추가
16 KiB
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)
응답 구조 (목록):
{
"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
응답 구조 (상세):
{
"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, 빈값 → 그대로 - 폭합계 + 절곡횟수 자동 표시
bendingDataJSON으로 직렬화하여 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 편집
- 순서 변경 (위로/아래로)
componentsJSON으로 직렬화하여 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 컴포넌트 (핵심)
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 컴포넌트
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.phpGuiderailImportLegacy.php,BendingProductImportLegacy.phpBendingModelImportImages.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 불필요