8.4 KiB
8.4 KiB
BOM 트리 3단계 구조 — React 구현 요청
작성일: 2026-03-18 요청자: API/MNG 개발팀 대상: React 프론트엔드 상태: API 구현 완료, React 구현 대기
1. 개요
품목관리 > 제품상세 화면의 BOM 표시를 플랫 테이블에서 3단계 트리(FG → 카테고리 → PT)로 개선한다. API가 이미 3단계 구조를 반환하므로, React는 렌더링만 변경하면 된다.
1.1 현재 상태
| 항목 | 상태 |
|---|---|
| API BOM 트리 3단계 | ✅ 완료 (GET /api/v1/items/{id}/bom/tree) |
| API BOM 목록 category 필드 | ✅ 완료 (GET /api/v1/items/{id}/bom) |
| MNG BOM 트리 3단계 | ✅ 완료 (참고용) |
| React BOM 표시 | ❌ 플랫 테이블 (개선 필요) |
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
현재 (플랫 테이블)
부품 구성 (BOM) 총 17개 품목
┌────┬──────────────────────┬──────────────────┬────────┬──────┐
│ 번호 │ 품목코드 │ 품목명 │ 수량 │ 단위 │
├────┼──────────────────────┼──────────────────┼────────┼──────┤
│ 1 │ EST-RAW-슬랫-방화 │ 스크린 실리카 │ +10.65 │ m² │
│ 2 │ EST-MOTOR-220V-150K │ 모터 150K(S) │ x1 │ EA │
│ .. │ ...17건 나열... │ │ │ │
└────┴──────────────────────┴──────────────────┴────────┴──────┘
목표 (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
│ ...
▶ 부자재 (4건) ← 접힌 상태
▼ 검사비 (1건)
│ EST-INSPECTION 검사비 x1 EA
3. API 응답 구조 (구현 완료)
3.1 BOM 트리 엔드포인트
GET /api/v1/items/{id}/bom/tree
응답 예시 (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,
"name": "절곡품",
"item_type": "CAT",
"count": 9,
"children": [ "...9개 PT 품목..." ]
},
{
"id": 0,
"name": "부자재",
"item_type": "CAT",
"count": 4,
"children": [ "...4개 PT 품목..." ]
},
{
"id": 0,
"name": "검사비",
"item_type": "CAT",
"count": 1,
"children": [ "...1개 PT 품목..." ]
}
]
}
}
3.2 핵심 구조
| 필드 | 설명 |
|---|---|
item_type: "CAT" |
카테고리 그룹 노드 (가상 노드, id=0) |
item_type: "FG"/"PT" |
실제 품목 노드 |
count |
CAT 노드에만 존재 — 하위 품목 건수 |
quantity |
품목 노드에만 존재 — 소요 수량 |
children |
하위 노드 배열 (재귀) |
3.3 BOM 목록 엔드포인트 (category 필드 포함)
GET /api/v1/items/{id}/bom
각 항목에 category 필드가 추가되었다:
[
{
"child_item_id": 15657,
"child_item_code": "EST-RAW-슬랫-방화",
"child_item_name": "스크린 실리카",
"child_item_type": "PT",
"unit": "m²",
"quantity": 10.65,
"category": "주자재"
}
]
4. React 구현 가이드
4.1 방법 A: BOM 트리 API 활용 (권장)
/api/v1/items/{id}/bom/tree를 호출하여 3단계 구조를 그대로 렌더링한다.
// 1. BOM 트리 데이터 로드
const [bomTree, setBomTree] = useState(null);
useEffect(() => {
if (item?.id) {
fetch(`/api/proxy/items/${item.id}/bom/tree`)
.then(res => res.json())
.then(data => setBomTree(data.data));
}
}, [item?.id]);
// 2. 재귀 렌더링
function BomTreeNode({ node }) {
const [isOpen, setIsOpen] = useState(true);
const isCategory = node.item_type === 'CAT';
return (
<div className={isCategory ? '' : 'ml-4'}>
<div
className={`flex items-center gap-2 py-1.5 px-2 rounded ${isCategory ? 'bg-gray-50 cursor-pointer hover:bg-gray-100' : 'hover:bg-gray-50'}`}
onClick={() => isCategory && setIsOpen(!isOpen)}
>
{node.children?.length > 0 && (
isOpen ? <ChevronDown className="h-4 w-4" /> : <ChevronRight className="h-4 w-4" />
)}
{isCategory ? (
<>
<span className="text-sm font-semibold">{node.name}</span>
<Badge variant="outline">{node.count}건</Badge>
</>
) : (
<>
<Badge>{node.item_type}</Badge>
<span className="text-sm">{node.name}</span>
<code className="text-xs text-gray-400">{node.code}</code>
{node.quantity && <span className="ml-auto text-blue-600">x{node.quantity}</span>}
</>
)}
</div>
{isOpen && node.children?.map((child, i) => (
<BomTreeNode key={child.id || i} node={child} />
))}
</div>
);
}
4.2 방법 B: 기존 item.bom의 category로 그룹화
API 추가 호출 없이 기존 item.bom 데이터의 category 필드로 프론트에서 그룹화한다.
const groupedBom = useMemo(() => {
if (!item.bom) 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]);
방법 A가 권장 — API가 구조를 보장하므로 프론트 로직이 단순해진다.
5. 수정 대상
| 파일 | 수정 내용 |
|---|---|
src/components/items/ItemDetailClient.tsx |
557~611줄 BOM Card 섹션을 트리 UI로 교체 |
src/types/item.ts (선택) |
BOMLine 타입에 category?: string 추가 |
6. 체크리스트
- BOM 트리 API 호출 (
/api/v1/items/{id}/bom/tree) 또는 기존item.bom의 category 그룹화 item_type === 'CAT'노드는 카테고리 헤더로 렌더링 (접힘/펼침)- 카테고리 헤더에 건수(
count) Badge 표시 - 품목 노드에 코드, 품목명, 수량, 단위 표시
- 기존 BOM 조건 유지 (FG 또는 PT 비절곡품만 표시)
- MNG 화면(admin.codebridge-x.com > 품목관리)에서 동작 확인 가능
관련 문서
changes/20260318_item-management-bom-tree.md— MNG BOM 트리 3단계 구현 완료rules/item-policy.md— 품목 정책
최종 업데이트: 2026-03-18