# BOM 트리 3단계 그룹 표시 — React 프론트엔드 구현 요청서 > **작성일**: 2026-03-18 > **요청자**: R&D실 (백엔드 API 개발 완료) > **대상**: React 프론트엔드 개발자 > **배포 상태**: 개발서버 배포 완료 (`api.dev.codebridge-x.com`) --- ## 1. 작업 개요 품목관리 > 제품상세 화면의 "부품 구성 (BOM)" 섹션을 **플랫 테이블**에서 **카테고리별 접힘/펼침 그룹 트리**로 교체한다. **현재**: 17건의 부품이 번호순 테이블로 나열 **변경**: 주자재/모터/제어기/절곡품/부자재/검사비 6개 그룹으로 묶어서 트리 표시 > API가 3단계 트리 구조를 이미 반환하므로, 프론트엔드는 렌더링만 변경하면 된다. --- ## 2. 화면 설계 ### 2.1 현재 화면 (교체 대상) ``` 부품 구성 (BOM) 총 17개 품목 ┌────┬──────────────────────┬──────────────────┬────────┬──────┐ │ 번호 │ 품목코드 │ 품목명 │ 수량 │ 단위 │ ├────┼──────────────────────┼──────────────────┼────────┼──────┤ │ 1 │ EST-RAW-슬랫-방화 │ 스크린 실리카 │ 10.65 │ m² │ │ 2 │ EST-MOTOR-220V-150K │ 모터 150K(S) │ 1 │ EA │ │ .. │ ... │ ... │ ... │ ... │ │ 17 │ EST-INSPECTION │ 검사비 │ 1 │ EA │ └────┴──────────────────────┴──────────────────┴────────┴──────┘ ``` ### 2.2 변경 후 화면 ``` 부품 구성 (BOM) 총 17개 품목 · 6개 그룹 ▼ 주자재 (1건) │ PT EST-RAW-슬랫-방화 스크린 실리카 x10.65 m² ▼ 모터 (1건) │ PT EST-MOTOR-220V-150K 모터 150K(S) (220V) x1 EA ▼ 제어기 (1건) │ PT EST-CTRL-노출형 제어기 노출형 x1 EA ▼ 절곡품 (9건) │ PT BD-케이스-500*380 케이스 500*380 x3.22 m │ PT BD-케이스용-연기차단... 케이스용 연기차단재 x3.22 m │ PT BD-마구리-505*385 마구리 505*385 x1 EA │ PT BD-가이드레일-KSS02... 가이드레일 KSS02... x6.5 m │ PT BD-가이드레일용-연기... 가이드레일용 연기... x6.5 m │ PT 00035 철재용하장바(SUS)3000 x3 m │ PT BD-L-BAR-KSS02-17*60 L-BAR KSS02 17*60 x3.22 m │ PT BD-보강평철-50 보강평철 50 x3.22 m │ PT 90201 KD환봉(30파이) x1 EA ▶ 부자재 (4건) ← 접힌 상태 (클릭하면 펼침) ▼ 검사비 (1건) │ PT EST-INSPECTION 검사비 x1 Dev ``` ### 2.3 동작 설명 | 동작 | 설명 | |------|------| | 카테고리 헤더 클릭 | 하위 품목 접힘/펼침 토글 | | 기본 상태 | 모든 카테고리 펼침 | | 품목 행 클릭 | 기존과 동일 (해당 품목 상세 표시 등) | --- ## 3. API 엔드포인트 ### 3.1 BOM 트리 조회 — `GET /api/v1/items/{id}/bom/tree` 기존 엔드포인트. 인증: `X-API-KEY` + `Bearer Token` (기존과 동일). **응답 예시** (`GET /api/v1/items/15531/bom/tree`): ```json { "success": true, "message": "조회 완료", "data": { "id": 15531, "code": "FG-KSS02-측면형-SUS", "name": "KSS02 스크린 SUS마감 측면형", "item_type": "FG", "unit": "EA", "depth": 1, "children": [ { "id": 0, "code": "", "name": "주자재", "item_type": "CAT", "unit": "", "depth": 2, "count": 1, "children": [ { "id": 15657, "code": "EST-RAW-슬랫-방화", "name": "스크린 실리카", "item_type": "PT", "unit": "EA", "depth": 3, "quantity": 10.65, "children": [] } ] }, { "id": 0, "code": "", "name": "모터", "item_type": "CAT", "unit": "", "depth": 2, "count": 1, "children": [ { "id": 15627, "code": "EST-MOTOR-220V-150K", "name": "모터 150K(S) (220V)", "item_type": "PT", "unit": "EA", "depth": 3, "quantity": 1, "children": [] } ] }, { "id": 0, "code": "", "name": "절곡품", "item_type": "CAT", "unit": "", "depth": 2, "count": 9, "children": [ { "id": 15578, "code": "BD-케이스-500*380", "name": "케이스 500*380", "item_type": "PT", "unit": "m", "depth": 3, "quantity": 3.22, "children": [] } ] }, { "id": 0, "code": "", "name": "부자재", "item_type": "CAT", "count": 4, "children": ["...4개 PT 품목..."] }, { "id": 0, "code": "", "name": "검사비", "item_type": "CAT", "count": 1, "children": ["...1개 PT 품목..."] } ] } } ``` ### 3.2 응답 구조 설명 | 필드 | 타입 | 설명 | |------|------|------| | `item_type` | `"FG"` / `"CAT"` / `"PT"` | FG=루트 완제품, **CAT=카테고리 그룹 (가상 노드)**, PT=실제 부품 | | `id` | number | CAT 노드는 `0` (가상), 나머지는 실제 품목 ID | | `count` | number | CAT 노드에만 존재 — 하위 품목 건수 | | `quantity` | number | PT 노드에만 존재 — 소요 수량 | | `children` | array | 하위 노드 배열 (재귀) | | `code` | string | CAT 노드는 빈 문자열, PT 노드는 품목코드 | ### 3.3 노드 판별 로직 ``` item_type === "CAT" → 카테고리 헤더로 렌더링 (접힘/펼침, count 표시) item_type !== "CAT" → 품목 행으로 렌더링 (코드, 품목명, 수량, 단위) ``` --- ## 4. 구현 가이드 ### 4.1 데이터 로드 ```typescript // FG 품목 상세 페이지에서 BOM 트리 로드 const [bomTree, setBomTree] = useState(null); useEffect(() => { if (item?.id && item?.itemType === 'FG') { fetch(`/api/proxy/items/${item.id}/bom/tree`) .then(res => res.json()) .then(data => setBomTree(data.data)); } }, [item?.id]); ``` ### 4.2 타입 정의 ```typescript interface BomTreeNode { id: number; code: string; name: string; item_type: 'FG' | 'CAT' | 'PT' | string; unit: string; depth: number; count?: number; // CAT 노드 — 하위 품목 건수 quantity?: number; // PT 노드 — 소요 수량 children: BomTreeNode[]; } ``` ### 4.3 렌더링 컴포넌트 ```tsx function BomTreeNode({ node }: { node: BomTreeNode }) { const [isOpen, setIsOpen] = useState(true); const isCategory = node.item_type === 'CAT'; const hasChildren = node.children && node.children.length > 0; return (
{/* 노드 행 */}
hasChildren && setIsOpen(!isOpen)} > {/* 토글 아이콘 */} {hasChildren && ( isOpen ? : )} {!hasChildren && } {isCategory ? ( /* 카테고리 헤더 */ <> {node.name} {node.count}건 ) : ( /* 품목 행 */ <> {node.item_type} {node.code} {node.name} {node.quantity && ( x{node.quantity} )} {node.unit} )}
{/* 하위 노드 (접힘/펼침) */} {isOpen && hasChildren && (
{node.children.map((child, i) => ( ))}
)}
); } ``` ### 4.4 Card 섹션 교체 기존 `ItemDetailClient.tsx` 557~611줄의 BOM Card를 아래로 교체: ```tsx {item.itemType === 'FG' && bomTree && bomTree.children?.length > 0 && (
부품 구성 (BOM) 총 {bomTree.children.reduce((sum, cat) => sum + (cat.count || 0), 0)}개 품목 · {bomTree.children.length}개 그룹
{bomTree.children.map((catNode, i) => ( ))}
)} ``` --- ## 5. 수정 대상 파일 | 파일 | 변경 내용 | |------|----------| | `src/components/items/ItemDetailClient.tsx` | 557~611줄 BOM Card → 트리 컴포넌트로 교체 | | `src/types/item.ts` (선택) | `BomTreeNode` 타입 추가 | --- ## 6. 테스트 방법 1. 개발서버(`dev.codebridge-x.com`) 접속 2. 품목관리 > FG 품목 선택 (예: `KSS02 스크린 SUS마감 측면형`) 3. BOM 섹션에서 카테고리 그룹 표시 확인 4. 각 카테고리 헤더 클릭 → 접힘/펼침 동작 확인 5. MNG(`admin.codebridge-x.com`) > 품목관리에서 동일 품목의 BOM 탭과 비교 ### 테스트 데이터 tenant `프론트_테스트회사`의 FG 품목 18건에 BOM 데이터가 등록되어 있다. | 품목코드 | 품목명 | BOM 건수 | |---------|--------|---------| | FG-KSS01-벽면형-SUS | KSS01 스크린 SUS마감 벽면형 | 17건 | | FG-KSS02-측면형-SUS | KSS02 스크린 SUS마감 측면형 | 17건 | | FG-KQTS01-벽면형-SUS | KQTS01 철재 SUS마감 벽면형 | 17건 | | ... (총 18개 FG) | | | --- ## 7. 체크리스트 - [ ] `GET /api/v1/items/{id}/bom/tree` 호출하여 3단계 트리 데이터 로드 - [ ] `item_type === "CAT"` 노드를 카테고리 헤더로 렌더링 - [ ] 카테고리 헤더에 건수(`count`) Badge 표시 - [ ] 카테고리 클릭 시 접힘/펼침 토글 - [ ] 품목 노드에 타입 뱃지, 코드, 품목명, 수량, 단위 표시 - [ ] FG 품목에서만 BOM 섹션 표시 (기존 조건 유지) - [ ] BOM 데이터 없는 품목은 섹션 미표시 --- ## 관련 문서 - `changes/20260318_item-management-bom-tree.md` — MNG BOM 트리 3단계 구현 완료 내역 --- **최종 업데이트**: 2026-03-18