Files
sam-docs/plans/bom-tree-3level-react-request.md

12 KiB

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):

{
  "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 데이터 로드

// FG 품목 상세 페이지에서 BOM 트리 로드
const [bomTree, setBomTree] = useState<BomTreeNode | null>(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 타입 정의

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 렌더링 컴포넌트

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 (
    <div className={isCategory ? '' : 'ml-6'}>
      {/* 노드 행 */}
      <div
        className={`flex items-center gap-2 py-1.5 px-3 rounded ${
          isCategory
            ? 'bg-gray-50 hover:bg-gray-100 cursor-pointer'
            : 'hover:bg-gray-50'
        }`}
        onClick={() => hasChildren && setIsOpen(!isOpen)}
      >
        {/* 토글 아이콘 */}
        {hasChildren && (
          isOpen
            ? <ChevronDown className="h-4 w-4 text-gray-400 shrink-0" />
            : <ChevronRight className="h-4 w-4 text-gray-400 shrink-0" />
        )}
        {!hasChildren && <span className="w-4" />}

        {isCategory ? (
          /* 카테고리 헤더 */
          <>
            <span className="text-sm font-semibold text-gray-700">{node.name}</span>
            <Badge variant="outline" className="text-xs">{node.count}</Badge>
          </>
        ) : (
          /* 품목 행 */
          <>
            <Badge className="text-xs">{node.item_type}</Badge>
            <code className="text-xs text-gray-400">{node.code}</code>
            <span className="text-sm text-gray-700 flex-1 truncate">{node.name}</span>
            {node.quantity && (
              <span className="text-xs font-medium text-blue-600 ml-auto">
                x{node.quantity}
              </span>
            )}
            <span className="text-xs text-gray-400">{node.unit}</span>
          </>
        )}
      </div>

      {/* 하위 노드 (접힘/펼침) */}
      {isOpen && hasChildren && (
        <div className={isCategory ? 'border-l-2 border-gray-200 ml-2' : ''}>
          {node.children.map((child, i) => (
            <BomTreeNode key={child.id || `cat-${i}`} node={child} />
          ))}
        </div>
      )}
    </div>
  );
}

4.4 Card 섹션 교체

기존 ItemDetailClient.tsx 557~611줄의 BOM Card를 아래로 교체:

{item.itemType === 'FG' && bomTree && bomTree.children?.length > 0 && (
  <Card>
    <CardHeader>
      <div className="flex items-center justify-between">
        <CardTitle className="flex items-center gap-2">
          <Package className="h-5 w-5" />
          부품 구성 (BOM)
        </CardTitle>
        <Badge variant="outline" className="bg-blue-50 text-blue-700">
           {bomTree.children.reduce((sum, cat) => sum + (cat.count || 0), 0)} 품목
          · {bomTree.children.length} 그룹
        </Badge>
      </div>
    </CardHeader>
    <CardContent>
      {bomTree.children.map((catNode, i) => (
        <BomTreeNode key={i} node={catNode} />
      ))}
    </CardContent>
  </Card>
)}

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