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

273 lines
8.9 KiB
Markdown
Raw Normal View History

# 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 배열 예시
```json
[
{
"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 그룹화 로직 (핵심)
```typescript
// 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 접힘/펼침 상태 관리
```typescript
// 카테고리별 접힘 상태 (기본: 모두 펼침)
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 렌더링 구조
```tsx
{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줄)
```tsx
{/* 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.php``buildBomNode()` 메서드
- `mng/resources/views/item-management/index.blade.php``renderBomTree()` 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