Files
sam-api/public/tenant/product/bom.php
hskwon cc206fdbed style: Laravel Pint 코드 포맷팅 적용
- PSR-12 스타일 가이드 준수
- 302개 파일 스타일 이슈 자동 수정
- 코드 로직 변경 없음 (포맷팅만)
2025-11-06 17:45:49 +09:00

225 lines
11 KiB
PHP

<!-- SAM RULES: include=../inc/header.php; base=/tenant; width=1280; js=jQuery+BS5 -->
<?php
// BOM 템플릿 편집기
$CURRENT_SECTION = 'item';
include '../inc/header.php';
$modelId = isset($_GET['model_id']) ? (int) $_GET['model_id'] : 0;
?>
<div class="container" style="max-width:1280px; margin-top:24px;">
<div class="d-flex justify-content-between align-items-center mb-3">
<div>
<h1 class="h5 m-0">BOM 템플릿 편집기</h1>
<div class="text-muted small">모델 ID: <code id="modelIdView"><?php echo $modelId; ?></code> — 모델 버전 선택 후 구성 편집</div>
</div>
<div class="d-flex gap-2">
<a href="/tenant/item/model_list.php" class="btn btn-outline-secondary"><i class="bi bi-list-ul"></i> 목록</a>
<a href="/tenant/item/model_create.php?id=<?php echo $modelId; ?>" class="btn btn-outline-primary"><i class="bi bi-pencil"></i> 모델 수정</a>
<button class="btn btn-success" id="btnSave"><i class="bi bi-save"></i> 저장</button>
</div>
</div>
<div class="card mb-3">
<div class="card-body row g-3 align-items-end">
<div class="col-md-3">
<label class="form-label">모델 버전</label>
<select id="modelVersion" class="form-select">
<!-- API 연동 전 샘플 -->
<option value="v1">v1.0 (DRAFT)</option>
<option value="v1r">v1.0 (RELEASED)</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label">그룹 추가</label>
<div class="input-group">
<input id="groupName" class="form-control" placeholder="예: 본체, 절곡물" />
<button id="btnAddGroup" class="btn btn-outline-primary"><i class="bi bi-plus-lg"></i></button>
</div>
</div>
<div class="col-md-6 text-end">
<div class="form-text">※ 그룹 = BOM 템플릿 내 상위 분류 (예: 본체/절곡물/모터/부자재)</div>
</div>
</div>
</div>
<div class="row g-3">
<div class="col-lg-4">
<div class="card h-100">
<div class="card-header bg-white d-flex justify-content-between align-items-center">
<h2 class="h6 m-0">BOM 그룹 (템플릿)</h2>
<button id="btnExpand" class="btn btn-sm btn-outline-secondary">모두 펼치기</button>
</div>
<div class="card-body" style="max-height:520px; overflow:auto;">
<ul class="list-group" id="groupList">
<!-- JS 렌더링: 그룹/하위아이템 -->
</ul>
</div>
</div>
</div>
<div class="col-lg-8">
<div class="card h-100">
<div class="card-header bg-white d-flex justify-content-between align-items-center">
<h2 class="h6 m-0">아이템 상세</h2>
<div class="d-flex gap-2">
<button id="btnAddItem" class="btn btn-sm btn-outline-primary"><i class="bi bi-plus-lg"></i> 아이템 추가</button>
<button id="btnDelNode" class="btn btn-sm btn-outline-danger"><i class="bi bi-trash"></i> 선택 삭제</button>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table align-middle" id="itemTable">
<thead class="table-light">
<tr>
<th style="width:160px;">구분</th>
<th style="width:160px;">참조코드</th>
<th>명칭</th>
<th style="width:120px;">수량</th>
<th style="width:140px;">옵션/조건</th>
<th style="width:80px;" class="text-center">삭제</th>
</tr>
</thead>
<tbody>
<!-- JS 렌더링 -->
</tbody>
</table>
</div>
<div class="form-text">
※ 구분: PRODUCT(서브모델) / MATERIAL(자재) / PART(부품). 옵션/조건은 견적-선택값과 매핑(예: 가이드레일=벽면형).
</div>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
(function(){
const modelId = Number($('#modelIdView').text()) || 0;
// 샘플 데이터: 그룹과 아이템
let GROUPS = [
{id:'g1', name:'본체', items:[{id:'i1', type:'MATERIAL', ref:'SILICA-SET', name:'실리카원단+내화실 세트', qty:1, cond:'-'}, {id:'i2', type:'PRODUCT', ref:'SLAT-JOINT-SET', name:'슬랫+조인트바 세트', qty:1, cond:'-'}]},
{id:'g2', name:'절곡물', items:[{id:'i3', type:'PRODUCT', ref:'GUIDE-WALL-C', name:'가이드레일(벽면형/C형)', qty:1, cond:'옵션: 벽면형'}, {id:'i4', type:'PRODUCT', ref:'GUIDE-SIDE-D', name:'가이드레일(측면형/D형)', qty:1, cond:'옵션: 측면형'}]},
{id:'g3', name:'모터', items:[{id:'i5', type:'PART', ref:'MOTOR-SET', name:'모터+브라켓', qty:1, cond:'-'}]},
{id:'g4', name:'부자재', items:[{id:'i6', type:'PART', ref:'SHAFT-2IN', name:'감기샤프트 2인치', qty:1, cond:'-'}, {id:'i7', type:'PART', ref:'BOX-PIPE', name:'각파이프', qty:2, cond:'-'}]},
];
// 그룹 렌더링
function renderGroups(){
const $ul = $('#groupList').empty();
GROUPS.forEach(g=>{
const $li = $(`<li class="list-group-item">
<div class="d-flex justify-content-between align-items-center">
<div class="fw-semibold">${g.name}</div>
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-secondary btn-rename" data-id="${g.id}"><i class="bi bi-pencil"></i></button>
<button class="btn btn-outline-danger btn-del-group" data-id="${g.id}"><i class="bi bi-trash"></i></button>
</div>
</div>
</li>`);
$li.on('click', function(){ selectGroup(g.id); });
$ul.append($li);
});
}
let currentGroupId = null;
function selectGroup(id){
currentGroupId = id;
const g = GROUPS.find(x=>x.id===id);
renderItems(g ? g.items : []);
$('#groupList .list-group-item').removeClass('active');
$('#groupList .list-group-item').filter(function(){ return $(this).text().trim().startsWith(g.name); }).addClass('active');
}
function renderItems(items){
const $tb = $('#itemTable tbody').empty();
items.forEach(it=>{
const $tr = $(`<tr data-id="${it.id}">
<td>
<select class="form-select form-select-sm type">
<option ${it.type==='PRODUCT'?'selected':''}>PRODUCT</option>
<option ${it.type==='PART'?'selected':''}>PART</option>
<option ${it.type==='MATERIAL'?'selected':''}>MATERIAL</option>
</select>
</td>
<td><input class="form-control form-control-sm ref" value="${it.ref}"></td>
<td><input class="form-control form-control-sm name" value="${it.name}"></td>
<td style="width:120px;"><input type="number" step="0.001" class="form-control form-control-sm qty" value="${it.qty}"></td>
<td><input class="form-control form-control-sm cond" placeholder="예: 옵션=벽면형" value="${it.cond||''}"></td>
<td class="text-center"><button class="btn btn-sm btn-outline-danger btn-del-item"><i class="bi bi-x-lg"></i></button></td>
</tr>`);
$tb.append($tr);
});
}
// 이벤트 바인딩
$('#btnAddGroup').on('click', function(e){
e.preventDefault();
const name = ($('#groupName').val()||'').trim();
if(!name) return alert('그룹명을 입력하세요.');
const id = 'g'+(Date.now());
GROUPS.push({id, name, items:[]});
$('#groupName').val('');
renderGroups();
});
$('#groupList').on('click', '.btn-del-group', function(e){
e.stopPropagation();
const id = $(this).data('id');
if(!confirm('그룹을 삭제할까요? 하위 아이템도 함께 삭제됩니다.')) return;
GROUPS = GROUPS.filter(g=>g.id!==id);
currentGroupId = null;
renderGroups();
renderItems([]);
});
$('#groupList').on('click', '.btn-rename', function(e){
e.stopPropagation();
const id = $(this).data('id');
const g = GROUPS.find(x=>x.id===id);
const name = prompt('새 그룹명', g?.name||'');
if(name){ g.name = name; renderGroups(); }
});
$('#btnAddItem').on('click', function(){
if(!currentGroupId) return alert('먼저 그룹을 선택하세요.');
const g = GROUPS.find(x=>x.id===currentGroupId);
g.items.push({id:'i'+Date.now(), type:'PART', ref:'', name:'', qty:1, cond:''});
renderItems(g.items);
});
$('#itemTable').on('click', '.btn-del-item', function(){
const $tr = $(this).closest('tr');
const id = $tr.data('id');
const g = GROUPS.find(x=>x.id===currentGroupId);
g.items = g.items.filter(x=>x.id!==id);
renderItems(g.items);
});
// 저장 (템플릿 -> items payload)
$('#btnSave').on('click', function(){
// collect
const payload = {
model_id: modelId,
version: $('#modelVersion').val(),
groups: GROUPS.map(g=>({
name: g.name,
items: g.items.map(it=>({ type: $('.type', `tr[data-id="${it.id}"]`).val() || it.type,
ref: $('.ref', `tr[data-id="${it.id}"]`).val() || it.ref,
name: $('.name',`tr[data-id="${it.id}"]`).val() || it.name,
qty: Number($('.qty', `tr[data-id="${it.id}"]`).val() || it.qty),
cond: $('.cond',`tr[data-id="${it.id}"]`).val() || it.cond }))
}))
};
console.log('BOM TEMPLATE SAVE', payload);
alert('샘플: 콘솔에 저장 payload 출력. API 연동 필요');
});
// 초기 렌더
renderGroups();
if(GROUPS.length) selectGroup(GROUPS[0].id);
})();
</script>
<?php include '../inc/footer.php'; ?>