Files
sam-docs/plans/bom-tree-visualization-request.md

7.7 KiB

BOM Tree 시각화 React 구현 요청

날짜: 2026-03-18 요청자: R&D실 (백엔드) 대상: React 프론트엔드 API 상태: 구현 완료 (tree 엔드포인트 존재)


변경 요약

품목관리 또는 품목기준관리에서 BOM(Bill of Materials) 트리를 시각적으로 표시하는 UI를 추가한다.


1. 배경

현재 상태

시스템 BOM Tree UI 비고
MNG (Laravel) 구현됨 3-panel: 검색 + 트리 + 상세
서비스 (React) 미구현 API는 있으나 UI 없음

MNG 참고 화면

MNG 품목관리 (item-management)
┌──────────┬──────────────────────────┬──────────────┐
│ 품목 검색 │   BOM 트리 시각화         │ 품목 상세    │
│          │                          │              │
│ 🔍 검색   │  ▼ [FG] 제품A            │ 코드: P-001  │
│          │    ├─ [PT] 부품B (x2)    │ 이름: 제품A   │
│ · P-001  │    │  └─ [RM] SUS 철판 (x5)│ 유형: 완제품 │
│ · P-002  │    ├─ [SM] 볼트 M8 (x10) │              │
│          │    └─ [RM] EGI 판재 (x3) │              │
└──────────┴──────────────────────────┴──────────────┘

2. 구현 위치

품목관리 > 품목 상세에 BOM 탭 추가

품목관리 (/master-data/item-management)
└─ 품목 상세 (/master-data/item-management/{id})
   ├─ 기본정보 탭 (기존)
   ├─ BOM Tree 탭 ← 신규 추가
   └─ 기타 탭 (기존)

새 메뉴 생성 불필요. 기존 품목 상세 화면에 탭 하나 추가. BOM 데이터가 없는 품목(원자재 등)은 "BOM 정보가 없습니다" 표시.


3. BOM Tree UI 명세

3.1 트리 노드 구조

▼ [FG] P-001 | 제품A                          x1
  ├─ ▼ [PT] PT-001 | 가이드레일 조립품          x2
  │    ├─ [RM] 20000 | SUS1.2*1219*2438        x5
  │    ├─ [SM] 80067 | 가스켓쫄대(삼각)         x10
  │    └─ [SM] 80061 | 8인치후렌지              x2
  ├─ [RM] 30000 | EGI1.2*1219*2438             x3
  └─ [SM] 00043 | 불연지퍼                     x20

3.2 노드 요소

요소 설명
▼/▶ 펼침/접힘 토글 (자식 있을 때만)
유형 뱃지 [FG] [PT] [RM] [SM] [CS] — 색상 구분
코드 품목코드 (monospace)
품목명 품목 이름
수량 x2, x5 — 부모 대비 필요 수량

3.3 유형별 뱃지 색상

유형 코드 색상 설명
완제품 FG 🔵 blue 최상위 제품
부품 PT 🟢 green 조립 부품 (자식 가질 수 있음)
원자재 RM 🟠 orange 미가공 원자재 (leaf)
부자재 SM 🟣 purple 부자재 (leaf)
소모품 CS gray 소모품 (leaf)

3.4 인터랙션

동작 설명
노드 펼침/접힘 화살표 클릭 시 자식 노드 토글
전체 펼침 버튼 클릭 시 모든 노드 펼침
전체 접힘 버튼 클릭 시 루트만 표시
노드 클릭 (선택) 해당 품목 상세 정보 표시 또는 품목 상세 페이지 이동

4. API 스펙 (구현 완료)

4.1 Items BOM Tree

GET /api/v1/items/{id}/bom/tree

응답:

{
  "success": true,
  "data": {
    "id": 100,
    "code": "P-001",
    "name": "제품A",
    "item_type": "FG",
    "specification": "...",
    "unit": "EA",
    "quantity": 1,
    "depth": 0,
    "children": [
      {
        "id": 200,
        "code": "PT-001",
        "name": "가이드레일 조립품",
        "item_type": "PT",
        "quantity": 2,
        "depth": 1,
        "children": [
          {
            "id": 300,
            "code": "20000",
            "name": "SUS1.2*1219*2438",
            "item_type": "RM",
            "quantity": 5,
            "depth": 2,
            "children": []
          }
        ]
      },
      {
        "id": 400,
        "code": "00043",
        "name": "불연지퍼",
        "item_type": "SM",
        "quantity": 20,
        "depth": 1,
        "children": []
      }
    ]
  }
}

4.2 Products BOM Tree (제품 기반)

GET /api/v1/products/{id}/bom

응답 구조 동일type 필드가 PRODUCT 또는 MATERIAL로 구분.

4.3 BOM Flat List (참고)

GET /api/v1/items/{id}/bom          → 1단계 자식만 (flat)
GET /api/v1/items/{id}/bom/tree     → 전체 계층 (recursive)

5. TypeScript 타입

interface BomTreeNode {
  id: number;
  code: string;
  name: string;
  item_type: 'FG' | 'PT' | 'RM' | 'SM' | 'CS';
  specification?: string;
  unit?: string;
  quantity: number;
  depth: number;
  children: BomTreeNode[];
}

// 유형별 뱃지 색상 매핑
const ITEM_TYPE_COLORS: Record<string, string> = {
  FG: 'bg-blue-100 text-blue-800',
  PT: 'bg-green-100 text-green-800',
  RM: 'bg-orange-100 text-orange-800',
  SM: 'bg-purple-100 text-purple-800',
  CS: 'bg-gray-100 text-gray-800',
};

const ITEM_TYPE_LABELS: Record<string, string> = {
  FG: '완제품', PT: '부품', RM: '원자재', SM: '부자재', CS: '소모품',
};

6. 컴포넌트 구조 제안

BomTreeViewer/
├─ BomTreeViewer.tsx      # 메인 컨테이너 (API 호출 + 상태 관리)
├─ BomTreeNode.tsx        # 개별 노드 (재귀 렌더링)
├─ BomTreeToolbar.tsx     # 전체 펼침/접힘 버튼
└─ types.ts               # BomTreeNode 타입

재귀 렌더링 핵심

function BomTreeNode({ node, level = 0 }: { node: BomTreeNode; level?: number }) {
  const [isOpen, setIsOpen] = useState(level < 2); // 2단계까지 기본 펼침
  const hasChildren = node.children.length > 0;

  return (
    <div style={{ marginLeft: level * 24 }}>
      <div className="flex items-center gap-2 py-1 hover:bg-gray-50 rounded">
        {/* 펼침/접힘 */}
        {hasChildren ? (
          <button onClick={() => setIsOpen(!isOpen)}>
            {isOpen ? '▼' : '▶'}
          </button>
        ) : <span className="w-4" />}

        {/* 유형 뱃지 */}
        <span className={`text-xs px-1.5 py-0.5 rounded ${ITEM_TYPE_COLORS[node.item_type]}`}>
          {node.item_type}
        </span>

        {/* 코드 + 이름 */}
        <span className="font-mono text-sm text-gray-500">{node.code}</span>
        <span className="text-sm">{node.name}</span>

        {/* 수량 */}
        <span className="text-sm text-blue-600 ml-auto">x{node.quantity}</span>
      </div>

      {/* 재귀: 자식 노드 */}
      {isOpen && hasChildren && node.children.map(child => (
        <BomTreeNode key={child.id} node={child} level={level + 1} />
      ))}
    </div>
  );
}

7. Server Action 예시

// actions.ts
export async function getBomTree(itemId: number) {
  return fetchApi(`/items/${itemId}/bom/tree`);
}

작업 체크리스트

  • BomTreeViewer 컴포넌트 생성 (재귀 트리 렌더링)
  • 유형별 뱃지 색상 적용 (FG/PT/RM/SM/CS)
  • 펼침/접힘 토글 구현
  • 전체 펼침/접힘 버튼
  • GET /api/v1/items/{id}/bom/tree 연동
  • 품목 상세 또는 독립 페이지에 배치
  • (선택) 노드 클릭 시 품목 상세 이동

참고

  • MNG BOM Tree 구현: mng/resources/views/item-management/index.blade.php (라인 248-316)
  • API Tree 빌더: api/app/Services/Products/ProductComponentResolver.php
  • Items BOM API: api/app/Http/Controllers/Api/V1/ItemsBomController.php