225 lines
11 KiB
PHP
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'; ?>
|