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

8.9 KiB

BOM 트리 3단계 구조 개선 — React 구현 요청

작성일: 2026-03-18 요청자: API/MNG 개발팀 대상: React 프론트엔드 상태: MNG 구현 완료, React 구현 대기


1. 개요

품목관리 > 제품상세 화면의 BOM 트리를 2단계(FG → PT 플랫 리스트)에서 3단계(FG → 카테고리 → PT)로 개선한다.

1.1 현재 상태

항목 상태 설명
BOM 데이터 category 필드 완료 items.bom JSON에 category 필드 포함
MNG BOM 트리 3단계 완료 MNG 품목관리에서 3단계 구조 표시
React BOM 트리 2단계 플랫 테이블로 17건 나열

1.2 대상 페이지

/production/screen-production/{code}?mode=view&type=FG&id={id}

1.3 대상 컴포넌트

src/components/items/ItemDetailClient.tsx — 557~611줄 "부품 구성 (BOM)" 섹션


2. 현재 vs 목표 UI

현재 (2단계 — 플랫 테이블)

부품 구성 (BOM)                    총 17개 품목
┌────┬──────────────────────┬──────────────────┬────────┬──────┐
│ 번호 │ 품목코드              │ 품목명           │  수량  │ 단위 │
├────┼──────────────────────┼──────────────────┼────────┼──────┤
│  1 │ EST-RAW-슬랫-방화     │ 스크린 실리카     │ +10.65 │ m²   │
│  2 │ EST-MOTOR-220V-150K  │ 모터 150K(S)     │     x1 │ EA   │
│  3 │ EST-CTRL-노출형       │ 제어기 노출형     │     x1 │ EA   │
│  4 │ BD-케이스-500*380     │ 케이스 500*380   │  +3.22 │ m    │
│  5 │ BD-케이스용 연기차단...│ 케이스용 연기차단재│  +3.22 │ m    │
│ .. │ ...                   │ ...              │    ... │ ...  │
│ 17 │ EST-INSPECTION        │ 검사비           │     x1 │ EA   │
└────┴──────────────────────┴──────────────────┴────────┴──────┘

목표 (3단계 — 카테고리 그룹 접힘/펼침)

부품 구성 (BOM)                    총 17개 품목 (6개 그룹)

▼ 주자재 (1건)
  │ EST-RAW-슬랫-방화      스크린 실리카        x10.65  m²
▼ 모터 (1건)
  │ EST-MOTOR-220V-150K   모터 150K(S) (220V)  x1     EA
▼ 제어기 (1건)
  │ EST-CTRL-노출형        제어기 노출형         x1     EA
▼ 절곡품 (9건)
  │ BD-케이스-500*380      케이스 500*380       x3.22   m
  │ BD-케이스용 연기차단... 케이스용 연기차단재   x3.22   m
  │ BD-마구리-505*385      마구리 505*385       x1     EA
  │ ...
▶ 부자재 (4건)                               ← 접힌 상태
▼ 검사비 (1건)
  │ EST-INSPECTION         검사비              x1     EA

3. BOM 데이터 구조 (API 변경 없음)

현재 API 응답의 item.bom 배열에 이미 category 필드가 포함되어 있다. API 수정 불필요 — React에서 category로 그룹화하면 된다.

3.1 item.bom 배열 예시

[
  {
    "child_item_id": 15657,
    "child_item_code": "EST-RAW-슬랫-방화",
    "quantity": 10.65,
    "unit": "m²",
    "category": "주자재"
  },
  {
    "child_item_id": 15627,
    "child_item_code": "EST-MOTOR-220V-150K",
    "quantity": 1,
    "unit": "EA",
    "category": "모터"
  },
  {
    "child_item_id": 15578,
    "child_item_code": "BD-케이스-500*380",
    "quantity": 3.22,
    "unit": "m",
    "category": "절곡품"
  }
]

3.2 category 값 목록 (순서대로)

category 값 설명 대표 아이템
주자재 주요 원자재 스크린 실리카
모터 모터류 모터 150K, 400K
제어기 제어 장치 제어기 노출형/매입형
절곡품 절곡 가공 부품 케이스, 가이드레일, L-BAR
부자재 보조 자재 샤프트, 각파이프, 앵글
검사비 검사 비용 검사비

4. 구현 가이드

4.1 그룹화 로직 (핵심)

// item.bom 배열을 category별로 그룹화
const groupedBom = useMemo(() => {
  if (!item.bom || item.bom.length === 0) return {};

  const groups: Record<string, typeof item.bom> = {};
  for (const line of item.bom) {
    const cat = line.category || '기타';
    if (!groups[cat]) groups[cat] = [];
    groups[cat].push(line);
  }
  return groups;
}, [item.bom]);

// 카테고리 순서 보장 (선택)
const categoryOrder = ['주자재', '모터', '제어기', '절곡품', '부자재', '검사비'];
const sortedCategories = Object.keys(groupedBom).sort((a, b) => {
  const ia = categoryOrder.indexOf(a);
  const ib = categoryOrder.indexOf(b);
  return (ia === -1 ? 999 : ia) - (ib === -1 ? 999 : ib);
});

4.2 접힘/펼침 상태 관리

// 카테고리별 접힘 상태 (기본: 모두 펼침)
const [collapsedCategories, setCollapsedCategories] = useState<Set<string>>(new Set());

const toggleCategory = (category: string) => {
  setCollapsedCategories(prev => {
    const next = new Set(prev);
    if (next.has(category)) next.delete(category);
    else next.add(category);
    return next;
  });
};

4.3 렌더링 구조

{sortedCategories.map(category => {
  const items = groupedBom[category];
  const isCollapsed = collapsedCategories.has(category);

  return (
    <div key={category}>
      {/* 카테고리 헤더 (클릭으로 접기/펼치기) */}
      <div
        className="flex items-center gap-2 py-2 px-3 bg-gray-50 hover:bg-gray-100 cursor-pointer rounded"
        onClick={() => toggleCategory(category)}
      >
        {isCollapsed ? <ChevronRight className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
        <span className="text-sm font-semibold text-gray-700">{category}</span>
        <Badge variant="outline" className="text-xs">{items.length}</Badge>
      </div>

      {/* 품목 리스트 (접힌 상태면 숨김) */}
      {!isCollapsed && (
        <Table>
          <TableBody>
            {items.map((line, index) => (
              <TableRow key={line.id || index} className="border-l-2 border-gray-200 ml-4">
                <TableCell>
                  <code className="text-xs bg-gray-100 px-2 py-1 rounded">{line.childItemCode}</code>
                </TableCell>
                <TableCell>{line.childItemName}</TableCell>
                <TableCell className="text-right">{line.quantity}</TableCell>
                <TableCell>{line.unit}</TableCell>
              </TableRow>
            ))}
          </TableBody>
        </Table>
      )}
    </div>
  );
})}

5. 수정 대상 파일

파일 수정 내용
src/components/items/ItemDetailClient.tsx 557~611줄 BOM 섹션을 카테고리 그룹 UI로 교체

5.1 현재 코드 (교체 대상: 557~611줄)

{/* BOM 정보 - 절곡 부품은 제외 */}
{(item.itemType === 'FG' || (item.itemType === 'PT' && item.partType !== 'BENDING')) && item.bom && item.bom.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">
           {item.bom.length} 품목
        </Badge>
      </div>
    </CardHeader>
    <CardContent>
      <div className="border rounded-lg overflow-hidden">
        <Table>
          ...플랫 테이블...
        </Table>
      </div>
    </CardContent>
  </Card>
)}

6. 참고: MNG 구현 (동작 확인 가능)

MNG(admin.codebridge-x.com) > 품목관리에서 FG 품목 선택 시 BOM 탭에서 3단계 트리를 확인할 수 있다.

MNG 구현 파일:

  • mng/app/Services/ItemManagementService.phpbuildBomNode() 메서드
  • mng/resources/views/item-management/index.blade.phprenderBomTree() JS 함수

MNG 핵심 로직: BOM 데이터의 category 필드가 있으면 가상 카테고리 노드(item_type='CAT')를 생성하여 중간 계층으로 삽입한다. React에서는 이를 프론트엔드에서 처리하면 된다.


7. 체크리스트

  • item.bom 배열의 category 필드로 그룹화
  • 카테고리별 접힘/펼침 토글 구현
  • 카테고리 헤더에 건수 Badge 표시
  • category 없는 항목은 "기타"로 분류
  • 절곡품 Badge 표시 유지 (line.isBending)
  • 기존 BOM 조건 유지 (FG 또는 PT 비절곡품만 표시)

관련 문서

  • changes/20260318_item-management-bom-tree.md — MNG BOM 트리 3단계 구현 완료 내역
  • rules/item-policy.md — 품목 정책

최종 업데이트: 2026-03-18