fix : Front Page [Ver 0.1] - 회원가입 옵션 설정

This commit is contained in:
2025-08-12 13:20:31 +09:00
parent 89e712fa32
commit 93042d189d
5 changed files with 1183 additions and 9 deletions

View File

@@ -72,6 +72,7 @@
['라벨'=>'회사 선택','href'=>'/tenant/member/tenant_select.php'],
['라벨'=>'내정보 수정','href'=>'/tenant/member/profile_edit.php'],
['라벨'=>'탈퇴','href'=>'/tenant/member/withdraw.php'],
['라벨'=>'회원가입 컬럼설정','href'=>'/tenant/settings/member_fields.php'],
],
],
'permission' => [
@@ -104,6 +105,7 @@
['라벨'=>'부서 관리','href'=>'/tenant/tenant/department_list.php'],
['라벨'=>'역할 관리','href'=>'/tenant/tenant/role_list.php'],
['라벨'=>'유저 관리','href'=>'/tenant/tenant/user_list.php'],
['라벨'=>'옵션 관리','href'=>'/tenant/tenant/option.php'],
],
],
];

View File

@@ -78,7 +78,7 @@
</select>
</div>
<div class="col-md-1">
<label class="form-label mb-1">부호(호수)</label>
<label class="form-label mb-1">부호</label>
<input id="g_room" class="form-control" placeholder="예: 1205">
</div>
<div class="col-md-1">
@@ -99,25 +99,25 @@
<div class="col-md-2">
<label class="form-label mb-1">오픈사이즈</label>
<div class="input-group">
<input id="g_open_w" class="form-control" placeholder="가로(mm)" inputmode="numeric">
<input id="g_open_w" class="form-control" placeholder="가로(mm)" inputmode="numeric" value="2000">
<span class="input-group-text">×</span>
<input id="g_open_h" class="form-control" placeholder="세로(mm)" inputmode="numeric">
<input id="g_open_h" class="form-control" placeholder="세로(mm)" inputmode="numeric" value="1000">
</div>
</div>
<div class="col-md-2">
<label class="form-label mb-1">제작사이즈</label>
<div class="input-group">
<input id="g_make_w" class="form-control" placeholder="가로(mm)" inputmode="numeric">
<input id="g_make_w" class="form-control" placeholder="가로(mm)" inputmode="numeric" value="2160">
<span class="input-group-text">×</span>
<input id="g_make_h" class="form-control" placeholder="세로(mm)" inputmode="numeric">
<input id="g_make_h" class="form-control" placeholder="세로(mm)" inputmode="numeric" value="1350">
</div>
</div>
<div class="col-md-3">
<label class="form-label mb-1">제작치수</label>
<div class="row g-2">
<div class="col"><input id="g_mk_w" class="form-control" placeholder="가로" inputmode="numeric"></div>
<div class="col"><input id="g_mk_h" class="form-control" placeholder="높이" inputmode="numeric"></div>
<div class="col"><input id="g_mk_edge" class="form-control" placeholder="마구리" inputmode="numeric"></div>
<div class="col"><input id="g_mk_w" class="form-control" placeholder="가로" inputmode="numeric" value="160"></div>
<div class="col"><input id="g_mk_h" class="form-control" placeholder="높이" inputmode="numeric" value="350"></div>
<div class="col"><input id="g_mk_edge" class="form-control" placeholder="마구리" inputmode="numeric" value="50"></div>
</div>
</div>
</div>
@@ -131,7 +131,7 @@
<!-- ========== 세부 항목 (계산 결과) ========== -->
<div class="card">
<div class="card-header"><strong>세부 항목 (읽기전용)</strong></div>
<div class="card-header"><strong>세부 항목</strong></div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-sam table-hover align-middle m-0" id="detailTable">

View File

@@ -0,0 +1,320 @@
<!-- SAM RULES: include=../inc/header.php; base=/tenant; width=1280; js=jQuery+BS5 -->
<?php
// 섹션은 임시로 'member' 사용 (상단 2뎁스에 노출만 필요)
$CURRENT_SECTION = 'member';
include '../inc/header.php';
// ====== 샘플 데이터 (실구현은 DB 조인) ======
// setting_field_defs
$FIELD_DEFS = [
['field_key'=>'name','label'=>'이름','data_type'=>'string','input_type'=>'text','option_source'=>null],
['field_key'=>'email','label'=>'이메일','data_type'=>'string','input_type'=>'text','option_source'=>null],
['field_key'=>'phone','label'=>'전화번호','data_type'=>'string','input_type'=>'text','option_source'=>null],
['field_key'=>'department','label'=>'부서','data_type'=>'string','input_type'=>'select','option_source'=>'tenant_list'],
['field_key'=>'position','label'=>'직급','data_type'=>'string','input_type'=>'select','option_source'=>'tenant_list'],
['field_key'=>'job_title','label'=>'직책','data_type'=>'string','input_type'=>'select','option_source'=>'tenant_list'],
['field_key'=>'manager_id','label'=>'직속 상급자','data_type'=>'bigint','input_type'=>'select_user','option_source'=>null],
['field_key'=>'hire_date','label'=>'입사일','data_type'=>'date','input_type'=>'date','option_source'=>null],
['field_key'=>'employment_type','label'=>'고용형태','data_type'=>'string','input_type'=>'select','option_source'=>'tenant_list'],
['field_key'=>'work_location','label'=>'근무지','data_type'=>'string','input_type'=>'select','option_source'=>'tenant_list'],
['field_key'=>'work_type','label'=>'근무형태','data_type'=>'string','input_type'=>'select','option_source'=>'tenant_list'],
['field_key'=>'gender','label'=>'성별','data_type'=>'string','input_type'=>'select','option_source'=>'tenant_list'],
['field_key'=>'bank_name','label'=>'은행','data_type'=>'string','input_type'=>'select','option_source'=>'tenant_list'],
['field_key'=>'bank_account','label'=>'계좌번호','data_type'=>'string','input_type'=>'text','option_source'=>null],
['field_key'=>'employee_no','label'=>'사번','data_type'=>'string','input_type'=>'text','option_source'=>null],
['field_key'=>'profile_photo','label'=>'프로필 사진','data_type'=>'file','input_type'=>'file','option_source'=>null],
];
// tenant_option_groups (id, name) — select 항목에서 선택할 그룹
$OPTION_GROUPS = [
['id'=>101,'group_key'=>'department','name'=>'부서 그룹'],
['id'=>102,'group_key'=>'position','name'=>'직급 그룹'],
['id'=>103,'group_key'=>'job_title','name'=>'직책 그룹'],
['id'=>104,'group_key'=>'employment_type','name'=>'고용형태 그룹'],
['id'=>105,'group_key'=>'work_location','name'=>'근무지 그룹'],
['id'=>106,'group_key'=>'work_type','name'=>'근무형태 그룹'],
['id'=>107,'group_key'=>'gender','name'=>'성별 그룹'],
['id'=>108,'group_key'=>'bank_name','name'=>'은행 그룹'],
];
// tenant_field_settings (샘플: enabled/required/sort/order/group)
// key = field_key
$SETTINGS = [
'name' => ['enabled'=>1,'required'=>1,'sort_order'=>10,'option_group_id'=>null],
'email' => ['enabled'=>1,'required'=>1,'sort_order'=>20,'option_group_id'=>null],
'phone' => ['enabled'=>1,'required'=>0,'sort_order'=>30,'option_group_id'=>null],
'department' => ['enabled'=>1,'required'=>1,'sort_order'=>40,'option_group_id'=>101],
'position' => ['enabled'=>1,'required'=>0,'sort_order'=>50,'option_group_id'=>102],
'job_title' => ['enabled'=>0,'required'=>0,'sort_order'=>60,'option_group_id'=>103],
'manager_id' => ['enabled'=>0,'required'=>0,'sort_order'=>70,'option_group_id'=>null],
'hire_date' => ['enabled'=>1,'required'=>0,'sort_order'=>80,'option_group_id'=>null],
'employment_type' => ['enabled'=>1,'required'=>0,'sort_order'=>90,'option_group_id'=>104],
'work_location' => ['enabled'=>1,'required'=>0,'sort_order'=>100,'option_group_id'=>105],
'work_type' => ['enabled'=>0,'required'=>0,'sort_order'=>110,'option_group_id'=>106],
'gender' => ['enabled'=>0,'required'=>0,'sort_order'=>120,'option_group_id'=>107],
'bank_name' => ['enabled'=>0,'required'=>0,'sort_order'=>130,'option_group_id'=>108],
'bank_account' => ['enabled'=>0,'required'=>0,'sort_order'=>140,'option_group_id'=>null],
'employee_no' => ['enabled'=>1,'required'=>0,'sort_order'=>150,'option_group_id'=>null],
'profile_photo' => ['enabled'=>0,'required'=>0,'sort_order'=>160,'option_group_id'=>null],
];
// helper: 옵션그룹 셀렉트 HTML
function render_group_select($field_key,$sel,$groups,$def){
if (!($def['input_type']==='select' && $def['option_source']==='tenant_list')) return '<span class="text-muted">—</span>';
$html = '<select class="form-select form-select-sm opt-group" data-field="'.$field_key.'" style="min-width:160px;">';
$html .= '<option value="">(그룹 선택)</option>';
foreach($groups as $g){
$s = ($sel==$g['id'])?'selected':'';
$html .= '<option value="'.$g['id'].'" '.$s.'>'.htmlspecialchars($g['name']).'</option>';
}
$html .= '</select>';
return $html;
}
// 기존 render_group_select() 제거하고 ↓로 교체
function group_name_by_id($id, $groups)
{
foreach ($groups as $g) {
if ((string)$g['id'] === (string)$id) return $g['name'];
}
return null;
}
function render_group_label($field_key, $sel_id, $groups, $def)
{
// select + tenant_list 타입만 노출, 그 외는 대시
if (!($def['input_type'] === 'select' && $def['option_source'] === 'tenant_list')) {
return '<span class="text-muted">—</span>';
}
if (!$sel_id) return '<span class="badge bg-light text-muted">미지정</span>';
$name = htmlspecialchars(group_name_by_id($sel_id, $groups) ?: '알 수 없음');
// 필요하면 관리 링크 표시
return '<span class="badge bg-secondary-subtle text-dark">' . $name . '</span>'
. ' <a href="/tenant/settings/option_groups.php?selected=' . $sel_id . '" class="btn btn-sm btn-outline-secondary ms-1">관리</a>';
}
?>
<div class="container" style="max-width:1280px; margin-top:18px;">
<!-- 상단 툴바 -->
<div class="card mb-3">
<div class="sam-toolbar">
<div class="sam-title">회원가입 필드 설정</div>
<div class="sam-actions">
<button type="button" class="btn btn-outline-secondary btn-sm" id="btnAllOn">모두 사용</button>
<button type="button" class="btn btn-outline-secondary btn-sm" id="btnAllOff">모두 미사용</button>
<button type="button" class="btn btn-primary btn-sm" id="btnSaveAll">일괄 저장</button>
</div>
</div>
<div class="p-3">
<div class="small text-muted mb-1">미리보기 · 회원가입 화면 노출 순서</div>
<div id="preview" class="d-flex flex-wrap gap-2"></div>
</div>
</div>
<!-- 설정 테이블 -->
<div class="card p-3">
<div class="table-responsive">
<table class="table table-sam align-middle" id="fieldsTable">
<thead>
<tr>
<th style="width:150px;">순서</th>
<th class="text-start">필드</th>
<th style="width:120px;">입력유형</th>
<th style="width:180px;">옵션 그룹</th>
<th style="width:100px;">필수</th>
<th style="width:100px;">사용</th>
<th style="width:140px;">관리</th>
</tr>
</thead>
<tbody>
<?php
// sort_order 순으로 정렬된 것처럼 표시
usort($FIELD_DEFS, function($a,$b) use($SETTINGS){
$sa = $SETTINGS[$a['field_key']]['sort_order'] ?? 9999;
$sb = $SETTINGS[$b['field_key']]['sort_order'] ?? 9999;
return $sa <=> $sb;
});
foreach($FIELD_DEFS as $def):
$key = $def['field_key'];
$set = $SETTINGS[$key] ?? ['enabled'=>0,'required'=>0,'sort_order'=>9999,'option_group_id'=>null];
$enabled = (int)$set['enabled'];
$required = (int)$set['required'];
$order = (int)$set['sort_order'];
$groupId = $set['option_group_id'];
?>
<tr data-field="<?= htmlspecialchars($key) ?>">
<td>
<div class="input-group input-group-sm">
<button class="btn btn-outline-secondary btn-order" data-dir="-10" type="button">▲</button>
<input type="number" class="form-control form-control-sm sort-order text-center" value="<?= $order ?>">
<button class="btn btn-outline-secondary btn-order" data-dir="10" type="button">▼</button>
</div>
</td>
<td class="text-start">
<div class="fw-semibold"><?= htmlspecialchars($def['label']) ?></div>
<div class="text-muted small"><?= htmlspecialchars($key) ?> · <?= htmlspecialchars($def['data_type']) ?></div>
</td>
<td><span class="badge rounded-pill bg-light text-dark"><?= htmlspecialchars($def['input_type']) ?></span></td>
<td><?= render_group_label($key,$groupId,$OPTION_GROUPS,$def) ?></td>
<td>
<div class="form-check form-switch d-flex justify-content-center m-0">
<input class="form-check-input toggle-req" type="checkbox" <?= $required?'checked':'' ?> <?= $enabled? '' : 'disabled' ?>>
</div>
</td>
<td>
<div class="form-check form-switch d-flex justify-content-center m-0">
<input class="form-check-input toggle-on" type="checkbox" <?= $enabled?'checked':'' ?>>
</div>
</td>
<td>
<button class="btn btn-sm btn-outline-primary btn-apply" type="button">저장</button>
<button class="btn btn-sm btn-outline-secondary btn-reset" type="button">되돌리기</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<style>
#preview .pill{
display:inline-flex; align-items:center; gap:6px;
background:#eef3ff; color:#2c4a85; border:1px solid #d9e4ff;
padding:.25rem .5rem; border-radius:999px; font-size:.85rem;
}
#preview .pill .req{ color:#c1121f; font-weight:600; }
#fieldsTable td .input-group-sm .form-control { max-width:70px; }
#fieldsTable .btn-order { width:34px; }
</style>
<script>
$(function(){
// —— 메모리 상태(실제 구현은 서버 조인 결과로 초기화)
const defs = <?= json_encode($FIELD_DEFS, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES) ?>;
const initSettings = <?= json_encode($SETTINGS, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES) ?>;
let state = JSON.parse(JSON.stringify(initSettings)); // deep copy
// 프리뷰 갱신
function renderPreview(){
const list = Object.keys(state)
.map(k => ({k, ...state[k], label: findDef(k)?.label || k}))
.filter(x => x.enabled)
.sort((a,b)=> a.sort_order - b.sort_order);
const html = list.map(x => `
<span class="pill" data-k="${x.k}">
${escapeHtml(x.label)}
${x.required ? '<span class="req">*</span>' : ''}
<small class="text-muted ms-1">(${x.sort_order})</small>
</span>
`).join('');
$('#preview').html(html || '<span class="text-muted">사용 중인 필드가 없습니다.</span>');
}
function findDef(key){ return defs.find(d => d.field_key===key); }
function escapeHtml(s){ return String(s??'').replace(/[&<>"']/g, m=>({ '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;' }[m])); }
// 테이블 → state 동기화(행 하나)
function pullRow($tr){
const key = $tr.data('field');
const enabled = $tr.find('.toggle-on').prop('checked') ? 1:0;
const required = $tr.find('.toggle-req').prop('checked') ? 1:0;
const sort_order = parseInt($tr.find('.sort-order').val(),10) || 0;
const groupSel = $tr.find('.opt-group');
const option_group_id = groupSel.length ? (groupSel.val()? parseInt(groupSel.val(),10): null) : null;
state[key] = { enabled, required, sort_order, option_group_id };
}
// state → 테이블(행 하나) 재적용
function pushRow($tr){
const key = $tr.data('field');
const s = state[key] || {};
$tr.find('.sort-order').val(s.sort_order ?? 0);
$tr.find('.toggle-on').prop('checked', !!s.enabled);
$tr.find('.toggle-req').prop('checked', !!s.required).prop('disabled', !s.enabled);
const groupSel = $tr.find('.opt-group');
if (groupSel.length){
groupSel.val(s.option_group_id ? String(s.option_group_id) : '');
}
}
// 개별 저장 (모의)
$(document).on('click', '.btn-apply', function(){
const $tr = $(this).closest('tr');
pullRow($tr);
renderPreview();
// 실제:
// $.post('/tenant/api/settings/member_field_save.php', { field_key:..., ...state[field_key] })
alert('저장(모의): '+$tr.data('field'));
});
// 되돌리기
$(document).on('click', '.btn-reset', function(){
const key = $(this).closest('tr').data('field');
state[key] = JSON.parse(JSON.stringify(initSettings[key] || {enabled:0,required:0,sort_order:9999,option_group_id:null}));
pushRow($(this).closest('tr'));
renderPreview();
});
// 사용 스위치
$(document).on('change', '.toggle-on', function(){
const $tr = $(this).closest('tr');
const on = $(this).prop('checked');
$tr.find('.toggle-req').prop('disabled', !on);
pullRow($tr); renderPreview();
});
// 필수 스위치
$(document).on('change', '.toggle-req', function(){
const $tr = $(this).closest('tr');
pullRow($tr); renderPreview();
});
// 옵션그룹 변경
$(document).on('change', '.opt-group', function(){
pullRow($(this).closest('tr')); renderPreview();
});
// 순서 숫자 직접 수정
$(document).on('input', '.sort-order', function(){
pullRow($(this).closest('tr')); renderPreview();
});
// 순서 버튼 ▲▼
$(document).on('click', '.btn-order', function(){
const $tr = $(this).closest('tr');
const dir = parseInt($(this).data('dir'),10);
const $inp = $tr.find('.sort-order');
const cur = parseInt($inp.val(),10) || 0;
$inp.val(cur + dir).trigger('input');
});
// 모두 사용/미사용
$('#btnAllOn').on('click', function(){
$('#fieldsTable tbody tr').each(function(){
$(this).find('.toggle-on').prop('checked', true).trigger('change');
});
});
$('#btnAllOff').on('click', function(){
$('#fieldsTable tbody tr').each(function(){
$(this).find('.toggle-on').prop('checked', false).trigger('change');
});
});
// 일괄 저장(모의)
$('#btnSaveAll').on('click', function(){
// 실제: $.post('/tenant/api/settings/member_fields_bulk_save.php', { payload: JSON.stringify(state) })
console.log('[BULK SAVE]', state);
alert('일괄 저장(모의). 콘솔을 확인하세요.');
});
// 초기 렌더
renderPreview();
});
</script>
<?php include '../inc/footer.php'; ?>

View File

@@ -0,0 +1,422 @@
<!-- SAM RULES: include=../inc/header.php; base=/tenant; width=1280; js=jQuery+BS5 -->
<?php
$CURRENT_SECTION='member'; // 상단 1뎁스 "회사" 섹션에 노출
include '../inc/header.php';
?>
<div class="container" style="max-width:1280px; margin-top:24px;">
<!-- 상단 툴바 -->
<div class="sam-toolbar shadow-sm mb-3">
<div class="sam-title">옵션 그룹 관리</div>
<div class="sam-actions">
<input type="text" class="form-control form-control-sm" id="kw" placeholder="그룹키/이름 검색">
<button class="btn btn-sm btn-outline-secondary" id="btnFind">검색</button>
<button class="btn btn-sm btn-primary" id="btnAddGroup">+ 그룹 추가</button>
</div>
</div>
<div class="row g-3">
<!-- 그룹 목록 -->
<div class="col-12 col-lg-5">
<div class="card shadow-sm">
<div class="card-header d-flex justify-content-between align-items-center">
<strong>그룹 목록</strong>
<small class="text-muted" id="grpCount"></small>
</div>
<div class="table-responsive">
<table class="table table-sam table-hover m-0" id="grpTable">
<thead><tr>
<th style="width:90px;">ID</th>
<th>그룹키</th>
<th>이름</th>
<th style="width:120px;">관리</th>
</tr></thead>
<tbody><!-- JS 렌더 --></tbody>
</table>
</div>
</div>
</div>
<!-- 값(항목) 목록 -->
<div class="col-12 col-lg-7">
<div class="card shadow-sm">
<div class="card-header d-flex justify-content-between align-items-center">
<div>
<strong>항목 값</strong>
<span class="text-muted ms-2" id="selGroupLabel">선택된 그룹 없음</span>
</div>
<div class="d-flex gap-2">
<button class="btn btn-sm btn-outline-secondary" id="btnSortValueUp" title="선택 항목 위로">▲</button>
<button class="btn btn-sm btn-outline-secondary" id="btnSortValueDown" title="선택 항목 아래로">▼</button>
<button class="btn btn-sm btn-primary" id="btnAddValue">+ 항목 추가</button>
</div>
</div>
<div class="table-responsive">
<table class="table table-sam table-hover m-0" id="valTable">
<thead><tr>
<th style="width:90px;">ID</th>
<th style="width:160px;">value_key</th>
<th>라벨</th>
<th style="width:90px;">정렬</th>
<th style="width:90px;">사용</th>
<th style="width:120px;">관리</th>
</tr></thead>
<tbody><!-- JS 렌더 --></tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- 그룹 추가/수정 모달 -->
<div class="modal fade" id="groupModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-md modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="groupModalTitle">그룹 추가</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="닫기"></button>
</div>
<form id="groupForm" autocomplete="off">
<div class="modal-body">
<input type="hidden" name="id" id="g_id">
<div class="mb-2">
<label class="form-label">그룹키 <span class="text-danger">*</span></label>
<input type="text" name="group_key" id="g_key" class="form-control" maxlength="64" required placeholder="예: position, job_title">
<div class="form-text">영문/숫자/언더스코어 권장, 테넌트 내 유니크</div>
</div>
<div class="mb-2">
<label class="form-label">이름 <span class="text-danger">*</span></label>
<input type="text" name="name" id="g_name" class="form-control" maxlength="100" required placeholder="화면 표기용 이름">
</div>
<div class="mb-2">
<label class="form-label">설명</label>
<input type="text" name="description" id="g_desc" class="form-control" maxlength="255">
</div>
</div>
<div class="modal-footer">
<button class="btn btn-outline-secondary" data-bs-dismiss="modal" type="button">취소</button>
<button class="btn btn-primary" type="submit">저장</button>
</div>
</form>
</div>
</div>
</div>
<!-- 값 추가/수정 모달 -->
<div class="modal fade" id="valueModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-md modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="valueModalTitle">항목 추가</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="닫기"></button>
</div>
<form id="valueForm" autocomplete="off">
<div class="modal-body">
<input type="hidden" name="id" id="v_id">
<input type="hidden" name="group_id" id="v_group_id">
<div class="mb-2">
<label class="form-label">value_key <span class="text-danger">*</span></label>
<input type="text" name="value_key" id="v_key" class="form-control" maxlength="64" required placeholder="예: manager, engineer">
</div>
<div class="mb-2">
<label class="form-label">라벨 <span class="text-danger">*</span></label>
<input type="text" name="value_label" id="v_label" class="form-control" maxlength="100" required placeholder="예: 과장, 대리">
</div>
<div class="mb-2">
<label class="form-label">정렬</label>
<input type="number" name="sort_order" id="v_sort" class="form-control" value="0">
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="v_active" checked>
<label class="form-check-label" for="v_active">사용</label>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-outline-secondary" data-bs-dismiss="modal" type="button">취소</button>
<button class="btn btn-primary" type="submit">저장</button>
</div>
</form>
</div>
</div>
</div>
<style>
/* 선택된 행 강조 */
#grpTable tbody tr.active,
#valTable tbody tr.active { background:#eef3ff; }
</style>
<script>
$(function(){
// ===== 샘플 데이터 (실서비스에선 Ajax로 교체) =====
// 그룹
let GROUPS = [
{id:101, tenant_id:1, group_key:'position', name:'직급', description:'회사 직급'},
{id:102, tenant_id:1, group_key:'job_title', name:'직책', description:'직책/보임직'},
{id:103, tenant_id:1, group_key:'employment_type', name:'고용형태', description:'정규/계약/인턴'},
{id:104, tenant_id:1, group_key:'work_location', name:'근무지', description:'본사/공장/현장'},
{id:105, tenant_id:1, group_key:'work_type', name:'근무형태', description:'주간/야간/교대'},
{id:106, tenant_id:1, group_key:'gender', name:'성별', description:''},
{id:107, tenant_id:1, group_key:'bank_name', name:'은행', description:'급여계좌 은행'}
];
// 값
let VALUES = [
{id:1, group_id:101, value_key:'staff', value_label:'사원', sort_order:10, is_active:1},
{id:2, group_id:101, value_key:'assistant', value_label:'대리', sort_order:20, is_active:1},
{id:3, group_id:102, value_key:'team_lead', value_label:'팀장', sort_order:10, is_active:1},
{id:4, group_id:103, value_key:'regular', value_label:'정규직', sort_order:10, is_active:1},
{id:5, group_id:103, value_key:'contract', value_label:'계약직', sort_order:20, is_active:1},
{id:6, group_id:104, value_key:'hq', value_label:'본사', sort_order:10, is_active:1},
{id:7, group_id:105, value_key:'day', value_label:'주간', sort_order:10, is_active:1},
{id:8, group_id:106, value_key:'male', value_label:'남', sort_order:10, is_active:1},
{id:9, group_id:106, value_key:'female', value_label:'여', sort_order:20, is_active:1},
{id:10, group_id:107, value_key:'kb', value_label:'국민은행', sort_order:10, is_active:1},
{id:11, group_id:107, value_key:'shinhan', value_label:'신한은행', sort_order:20, is_active:1}
];
// 상태
let selectedGroupId = null;
let valSelectedId = null;
// ===== 유틸 =====
const $grpBody = $('#grpTable tbody');
const $valBody = $('#valTable tbody');
function esc(s){ return String(s??'').replace(/[&<>"']/g, m=>({ '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;', "'":'&#39;' }[m])); }
function groupById(id){ return GROUPS.find(g=>g.id==id); }
function valuesOf(gid){ return VALUES.filter(v=>v.group_id==gid).sort((a,b)=>a.sort_order-b.sort_order || a.id-b.id); }
// ===== 렌더 =====
function renderGroups(list){
const html = list.map(g=>`
<tr data-id="${g.id}" class="${g.id==selectedGroupId?'active':''}">
<td>${g.id}</td>
<td class="text-start">${esc(g.group_key)}</td>
<td class="text-start">${esc(g.name)}</td>
<td>
<div class="d-flex justify-content-center gap-1">
<button class="btn btn-sm btn-outline-secondary btn-edit-group">수정</button>
<button class="btn btn-sm btn-outline-danger btn-del-group">삭제</button>
</div>
</td>
</tr>
`).join('');
$grpBody.html(html);
$('#grpCount').text(`총 ${list.length}건`);
}
function renderValues(){
if(!selectedGroupId){
$('#selGroupLabel').text('선택된 그룹 없음');
$valBody.html(`<tr><td colspan="6" class="text-muted">좌측에서 그룹을 선택하세요.</td></tr>`);
return;
}
const g = groupById(selectedGroupId);
$('#selGroupLabel').text(`(${g.group_key}) ${g.name}`);
const rows = valuesOf(selectedGroupId);
if(rows.length===0){
$valBody.html(`<tr><td colspan="6" class="text-muted">항목이 없습니다. [항목 추가]를 눌러 등록하세요.</td></tr>`);
return;
}
const html = rows.map(v=>`
<tr data-id="${v.id}" class="${v.id==valSelectedId?'active':''}">
<td>${v.id}</td>
<td class="text-start"><code>${esc(v.value_key)}</code></td>
<td class="text-start">${esc(v.value_label)}</td>
<td>${v.sort_order}</td>
<td>${v.is_active?'<span class="badge bg-success-subtle text-success">Y</span>':'<span class="badge bg-secondary-subtle text-muted">N</span>'}</td>
<td>
<div class="d-flex justify-content-center gap-1">
<button class="btn btn-sm btn-outline-secondary btn-edit-val">수정</button>
<button class="btn btn-sm btn-outline-danger btn-del-val">삭제</button>
</div>
</td>
</tr>
`).join('');
$valBody.html(html);
}
// 최초 렌더
renderGroups(GROUPS);
renderValues();
// ===== 검색 =====
$('#btnFind').on('click', ()=>{
const q = $('#kw').val().trim().toLowerCase();
const list = !q ? GROUPS : GROUPS.filter(g =>
[g.group_key, g.name].some(x=> String(x).toLowerCase().includes(q))
);
renderGroups(list);
});
// ===== 그룹 선택 =====
$(document).on('click', '#grpTable tbody tr', function(e){
if($(e.target).closest('button').length) return; // 버튼 클릭은 제외
selectedGroupId = +$(this).data('id');
valSelectedId = null;
renderGroups(GROUPS);
renderValues();
});
// ===== 그룹 추가/수정/삭제 =====
$('#btnAddGroup').on('click', ()=>{
$('#groupModalTitle').text('그룹 추가');
$('#g_id').val('');
$('#g_key').val('');
$('#g_name').val('');
$('#g_desc').val('');
new bootstrap.Modal('#groupModal').show();
});
$(document).on('click', '.btn-edit-group', function(){
const id = +$(this).closest('tr').data('id');
const g = groupById(id); if(!g) return;
$('#groupModalTitle').text('그룹 수정');
$('#g_id').val(g.id);
$('#g_key').val(g.group_key);
$('#g_name').val(g.name);
$('#g_desc').val(g.description||'');
new bootstrap.Modal('#groupModal').show();
});
$('#groupForm').on('submit', function(e){
e.preventDefault();
const id = $('#g_id').val();
const payload = {
id: id? +id : null,
group_key: $('#g_key').val().trim(),
name: $('#g_name').val().trim(),
description: $('#g_desc').val().trim()
};
if(payload.group_key.length<2 || payload.name.length<1){ alert('그룹키/이름을 확인하세요.'); return; }
if(payload.id){
const g = groupById(payload.id);
if(!g) return;
g.group_key = payload.group_key;
g.name = payload.name;
g.description = payload.description;
}else{
const newId = (Math.max(0, ...GROUPS.map(x=>x.id)) + 1);
GROUPS.push({id:newId, tenant_id:1, ...payload});
}
bootstrap.Modal.getInstance(document.getElementById('groupModal')).hide();
renderGroups(GROUPS);
renderValues();
// 실제 API 예)
// $.post('/tenant/api/settings/group_save.php', payload).done(()=>location.reload());
});
$(document).on('click', '.btn-del-group', function(){
const id = +$(this).closest('tr').data('id');
const g = groupById(id); if(!g) return;
if(!confirm(`그룹(${g.name}) 및 모든 항목을 삭제할까요?`)) return;
// 값 삭제
VALUES = VALUES.filter(v=>v.group_id!=id);
// 그룹 삭제
GROUPS = GROUPS.filter(x=>x.id!=id);
if(selectedGroupId==id) { selectedGroupId=null; valSelectedId=null; }
renderGroups(GROUPS);
renderValues();
// 실제 API 예)
// location.href = '/tenant/api/settings/group_delete.php?id='+id;
});
// ===== 값(항목) 추가/수정/삭제 =====
$('#btnAddValue').on('click', ()=>{
if(!selectedGroupId){ alert('좌측에서 그룹을 선택하세요.'); return; }
$('#valueModalTitle').text('항목 추가');
$('#v_id').val('');
$('#v_group_id').val(selectedGroupId);
$('#v_key').val('');
$('#v_label').val('');
$('#v_sort').val( (valuesOf(selectedGroupId).slice(-1)[0]?.sort_order || 0) + 10 );
$('#v_active').prop('checked', true);
new bootstrap.Modal('#valueModal').show();
});
$(document).on('click', '.btn-edit-val', function(){
const id = +$(this).closest('tr').data('id');
const v = VALUES.find(x=>x.id==id); if(!v) return;
$('#valueModalTitle').text('항목 수정');
$('#v_id').val(v.id);
$('#v_group_id').val(v.group_id);
$('#v_key').val(v.value_key);
$('#v_label').val(v.value_label);
$('#v_sort').val(v.sort_order);
$('#v_active').prop('checked', !!v.is_active);
new bootstrap.Modal('#valueModal').show();
});
$('#valueForm').on('submit', function(e){
e.preventDefault();
const id = $('#v_id').val();
const payload = {
id: id? +id : null,
group_id: +$('#v_group_id').val(),
value_key: $('#v_key').val().trim(),
value_label: $('#v_label').val().trim(),
sort_order: parseInt($('#v_sort').val(),10) || 0,
is_active: $('#v_active').is(':checked') ? 1 : 0
};
if(!payload.group_id){ alert('그룹을 선택하세요.'); return; }
if(payload.value_key.length<1 || payload.value_label.length<1){ alert('value_key/라벨을 확인하세요.'); return; }
if(payload.id){
const v = VALUES.find(x=>x.id==payload.id);
if(!v) return;
Object.assign(v, payload);
}else{
const newId = (Math.max(0, ...VALUES.map(x=>x.id)) + 1);
VALUES.push({id:newId, ...payload});
}
bootstrap.Modal.getInstance(document.getElementById('valueModal')).hide();
renderValues();
// 실제 API 예)
// $.post('/tenant/api/settings/value_save.php', payload).done(()=>renderValues());
});
$(document).on('click', '.btn-del-val', function(){
const id = +$(this).closest('tr').data('id');
const v = VALUES.find(x=>x.id==id); if(!v) return;
if(!confirm(`항목(${v.value_label})을 삭제할까요?`)) return;
VALUES = VALUES.filter(x=>x.id!=id);
if(valSelectedId==id) valSelectedId=null;
renderValues();
// 실제 API 예)
// location.href = '/tenant/api/settings/value_delete.php?id='+id;
});
// 값 행 선택 (정렬 이동 대상 선택용)
$(document).on('click', '#valTable tbody tr', function(e){
if($(e.target).closest('button').length) return;
valSelectedId = +$(this).data('id');
renderValues();
});
// 정렬 이동 (클라이언트 사이드 재배치)
function moveSelected(delta){
if(!selectedGroupId || !valSelectedId) return;
const list = valuesOf(selectedGroupId);
const idx = list.findIndex(v=>v.id==valSelectedId);
if(idx<0) return;
const newIdx = idx + delta;
if(newIdx<0 || newIdx>=list.length) return;
// swap sort_order
const a = list[idx], b = list[newIdx];
const tmp = a.sort_order; a.sort_order = b.sort_order; b.sort_order = tmp;
renderValues();
// 실제 API 예) 서버에서 sort_order 리시퀀싱
// $.post('/tenant/api/settings/value_reorder.php', {group_id:selectedGroupId, id:valSelectedId, dir:(delta<0?'up':'down')})
}
$('#btnSortValueUp').on('click', ()=>moveSelected(-1));
$('#btnSortValueDown').on('click', ()=>moveSelected(1));
});
</script>
<?php include '../inc/footer.php'; ?>

View File

@@ -0,0 +1,430 @@
<!-- SAM RULES: include=../inc/header.php; base=/tenant; width=1280; js=jQuery+BS5 -->
<?php
// 네비게이션은 '회사' 섹션에 묶어둘게요(관리 성격)
$CURRENT_SECTION='tenant';
include '../inc/header.php';
?>
<div class="container" style="max-width:1280px; margin-top:24px;">
<div class="card">
<div class="sam-toolbar">
<div class="sam-title">공통코드 / 옵션 관리</div>
<div class="sam-actions">
<input type="text" class="form-control form-control-sm" id="kwGroup" placeholder="그룹 검색(키/이름)">
<button class="btn btn-sm btn-outline-secondary" id="btnFindGroup">검색</button>
<button class="btn btn-sm btn-primary" id="btnOpenAddGroup">+ 그룹 생성</button>
</div>
</div>
<div class="row g-0">
<!-- 좌측: 그룹 리스트 -->
<div class="col-12 col-md-4 border-end">
<div class="p-3">
<div class="d-flex justify-content-between align-items-center mb-2">
<strong>그룹 목록</strong>
<div class="text-muted small" id="groupCount"></div>
</div>
<div class="table-responsive" style="max-height:520px; overflow:auto;">
<table class="table table-sam" id="tblGroups">
<thead><tr><th style="width:80px;">ID</th><th>그룹키</th><th>이름</th><th style="width:90px;">관리</th></tr></thead>
<tbody><!-- JS render --></tbody>
</table>
</div>
</div>
</div>
<!-- 우측: 항목(값) 리스트 -->
<div class="col-12 col-md-8">
<div class="p-3">
<div class="d-flex align-items-center justify-content-between mb-2">
<div>
<strong>항목 목록</strong>
<span class="text-muted ms-2" id="currentGroupLabel">선택된 그룹이 없습니다</span>
</div>
<div class="d-flex align-items-center gap-2">
<input type="text" class="form-control form-control-sm" id="kwValue" placeholder="항목 검색(코드/라벨)">
<button class="btn btn-sm btn-outline-secondary" id="btnFindValue">검색</button>
<button class="btn btn-sm btn-primary" id="btnOpenAddValue" disabled>+ 항목 추가</button>
</div>
</div>
<div class="table-responsive" style="max-height:520px; overflow:auto;">
<table class="table table-sam" id="tblValues">
<thead>
<tr>
<th style="width:80px;">ID</th>
<th>코드(value_key)</th>
<th>라벨(value_label)</th>
<th style="width:90px;">정렬</th>
<th style="width:90px;">사용</th>
<th style="width:120px;">관리</th>
</tr>
</thead>
<tbody><!-- JS render --></tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 그룹 추가/수정 모달 -->
<div class="modal fade" id="groupModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-md modal-dialog-centered">
<form class="modal-content" id="groupForm" autocomplete="off">
<div class="modal-header">
<h5 class="modal-title" id="groupModalTitle">그룹 생성</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="닫기"></button>
</div>
<div class="modal-body">
<input type="hidden" name="id" id="group_id">
<div class="mb-2">
<label class="form-label">그룹키 <span class="text-danger">*</span></label>
<input type="text" class="form-control" name="group_key" id="group_key" placeholder="예: position, job_title" required>
<div class="form-text">영문/코드 권장. 테넌트 내 유일해야 합니다.</div>
</div>
<div class="mb-2">
<label class="form-label">이름 <span class="text-danger">*</span></label>
<input type="text" class="form-control" name="name" id="group_name" required>
</div>
<div class="mb-2">
<label class="form-label">설명</label>
<input type="text" class="form-control" name="description" id="group_desc" maxlength="255">
</div>
</div>
<div class="modal-footer">
<button class="btn btn-outline-secondary" type="button" data-bs-dismiss="modal">취소</button>
<button class="btn btn-primary" type="submit" id="btnSaveGroup">저장</button>
</div>
</form>
</div>
</div>
<!-- 항목 추가/수정 모달 -->
<div class="modal fade" id="valueModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-md modal-dialog-centered">
<form class="modal-content" id="valueForm" autocomplete="off">
<div class="modal-header">
<h5 class="modal-title" id="valueModalTitle">항목 추가</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="닫기"></button>
</div>
<div class="modal-body">
<input type="hidden" name="id" id="value_id">
<input type="hidden" name="group_id" id="value_group_id">
<div class="mb-2">
<label class="form-label">코드(value_key) <span class="text-danger">*</span></label>
<input type="text" class="form-control" name="value_key" id="value_key" required>
<div class="form-text">그룹 내 유일해야 합니다.</div>
</div>
<div class="mb-2">
<label class="form-label">라벨(value_label) <span class="text-danger">*</span></label>
<input type="text" class="form-control" name="value_label" id="value_label" required>
</div>
<div class="mb-2">
<label class="form-label">정렬(sort_order)</label>
<input type="number" class="form-control" name="sort_order" id="sort_order" value="0">
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="is_active" checked>
<label class="form-check-label" for="is_active">사용함</label>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-outline-secondary" type="button" data-bs-dismiss="modal">취소</button>
<button class="btn btn-primary" type="submit" id="btnSaveValue">저장</button>
</div>
</form>
</div>
</div>
<!-- 삭제 확인 모달 (그룹/항목 공용) -->
<div class="modal fade" id="confirmModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-sm modal-dialog-centered">
<div class="modal-content">
<div class="modal-header"><h5 class="modal-title">삭제 확인</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="닫기"></button>
</div>
<div class="modal-body">
<div class="small">정말 삭제하시겠어요?</div>
<input type="hidden" id="confirm_target" value="">
<input type="hidden" id="confirm_type" value=""> <!-- group | value -->
</div>
<div class="modal-footer">
<button class="btn btn-outline-secondary" data-bs-dismiss="modal" type="button">취소</button>
<button class="btn btn-danger" id="btnConfirmDelete" type="button">삭제</button>
</div>
</div>
</div>
</div>
<style>
/* 좌측 리스트 선택 표시 */
#tblGroups tbody tr.active { background:#eef4ff; }
/* 작은 토글 느낌 */
.toggle-yes { color:#2c4a85; font-weight:600; }
.toggle-no { color:#888; }
</style>
<script>
// ====== 프로토타입 데이터 ======
// 실제에선 tenant_id=세션테넌트 기준으로 API 연동
let GROUPS = [
{id: 101, group_key:'position', name:'직급', description:'회사 직급'},
{id: 102, group_key:'job_title', name:'직책', description:'직책/보임직'},
{id: 103, group_key:'employment_type', name:'고용형태', description:'정규/계약/인턴'},
{id: 104, group_key:'work_location', name:'근무지', description:'본사/공장/현장'},
{id: 105, group_key:'work_type', name:'근무형태', description:'주간/야간/교대'},
{id: 106, group_key:'gender', name:'성별', description:''},
{id: 107, group_key:'bank_name', name:'은행', description:'급여계좌 은행'}
];
// group_id별 값
let VALUES = {
101:[{id:1, group_id:101, value_key:'staff', value_label:'사원', sort_order:10, is_active:1},
{id:2, group_id:101, value_key:'assistant', value_label:'대리', sort_order:20, is_active:1}],
102:[{id:3, group_id:102, value_key:'team_lead', value_label:'팀장', sort_order:10, is_active:1}],
103:[{id:4, group_id:103, value_key:'regular', value_label:'정규직', sort_order:10, is_active:1},
{id:5, group_id:103, value_key:'contract', value_label:'계약직', sort_order:20, is_active:1}],
104:[{id:6, group_id:104, value_key:'hq', value_label:'본사', sort_order:10, is_active:1}],
105:[{id:7, group_id:105, value_key:'day', value_label:'주간', sort_order:10, is_active:1}],
106:[{id:8, group_id:106, value_key:'male', value_label:'남', sort_order:10, is_active:1},
{id:9, group_id:106, value_key:'female', value_label:'여', sort_order:20, is_active:1}],
107:[{id:10, group_id:107, value_key:'kb', value_label:'국민은행', sort_order:10, is_active:1},
{id:11, group_id:107, value_key:'shinhan', value_label:'신한은행', sort_order:20, is_active:1}]
};
let seqValue = 1000; // 데모용 id 증가
let currentGroupId = null;
let filterGroupKW = '';
let filterValueKW = '';
function esc(s){ return String(s??'').replace(/[&<>"']/g, m=>({ '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;' }[m])); }
// ====== 렌더링 ======
function renderGroups(){
const list = GROUPS.filter(g => {
if(!filterGroupKW) return true;
const s = filterGroupKW.toLowerCase();
return [g.group_key, g.name].some(v => String(v).toLowerCase().includes(s));
}).sort((a,b)=> a.name.localeCompare(b.name,'ko'));
$('#groupCount').text(`${list.length}개`);
const html = list.map(g=>`
<tr data-id="${g.id}" class="${g.id===currentGroupId?'active':''}">
<td>${g.id}</td>
<td class="text-start">${esc(g.group_key)}</td>
<td class="text-start">${esc(g.name)}</td>
<td>
<div class="d-flex justify-content-center gap-1">
<button class="btn btn-sm btn-outline-secondary btn-edit-group" data-id="${g.id}">수정</button>
<button class="btn btn-sm btn-outline-danger btn-del-group" data-id="${g.id}">삭제</button>
</div>
</td>
</tr>
`).join('');
$('#tblGroups tbody').html(html);
}
function renderValues(){
const labelSpan = currentGroupId
? (()=>{ const g = GROUPS.find(x=>x.id===currentGroupId); return g ? `(${esc(g.group_key)} / ${esc(g.name)})` : '' })()
: '선택된 그룹이 없습니다';
$('#currentGroupLabel').text(labelSpan);
$('#btnOpenAddValue').prop('disabled', !currentGroupId);
const rows = VALUES[currentGroupId] || [];
const list = rows
.filter(v=>{
if(!filterValueKW) return true;
const s = filterValueKW.toLowerCase();
return [v.value_key, v.value_label].some(x => String(x).toLowerCase().includes(s));
})
.sort((a,b)=> a.sort_order-b.sort_order || a.value_label.localeCompare(b.value_label,'ko'));
const html = list.map(v=>`
<tr data-id="${v.id}">
<td>${v.id}</td>
<td class="text-start">${esc(v.value_key)}</td>
<td class="text-start">${esc(v.value_label)}</td>
<td>${v.sort_order}</td>
<td>${v.is_active?'<span class="toggle-yes">Y</span>':'<span class="toggle-no">N</span>'}</td>
<td>
<div class="d-flex justify-content-center gap-1">
<button class="btn btn-sm btn-outline-secondary btn-edit-value" data-id="${v.id}">수정</button>
<button class="btn btn-sm btn-outline-danger btn-del-value" data-id="${v.id}">삭제</button>
</div>
</td>
</tr>
`).join('');
$('#tblValues tbody').html(html);
}
// ====== 그룹 이벤트 ======
$(document).on('click', '#tblGroups tbody tr', function(e){
// 행 클릭 시 선택(버튼 클릭은 제외)
if($(e.target).closest('button').length) return;
const id = +$(this).data('id');
currentGroupId = id;
renderGroups();
renderValues();
});
$('#btnFindGroup').on('click', function(){
filterGroupKW = $('#kwGroup').val().trim();
renderGroups();
});
$('#btnOpenAddGroup').on('click', function(){
$('#groupModalTitle').text('그룹 생성');
$('#groupForm')[0].reset();
$('#group_id').val('');
new bootstrap.Modal('#groupModal').show();
});
$(document).on('click', '.btn-edit-group', function(){
const id = +$(this).data('id');
const g = GROUPS.find(x=>x.id===id);
if(!g) return;
$('#groupModalTitle').text('그룹 수정');
$('#group_id').val(g.id);
$('#group_key').val(g.group_key);
$('#group_name').val(g.name);
$('#group_desc').val(g.description||'');
new bootstrap.Modal('#groupModal').show();
});
$(document).on('click', '.btn-del-group', function(){
const id = +$(this).data('id');
$('#confirm_type').val('group');
$('#confirm_target').val(id);
new bootstrap.Modal('#confirmModal').show();
});
$('#groupForm').on('submit', function(e){
e.preventDefault();
const id = $('#group_id').val();
const payload = {
group_key: $('#group_key').val().trim(),
name: $('#group_name').val().trim(),
description: $('#group_desc').val().trim()
};
if(!payload.group_key || !payload.name){
alert('그룹키와 이름은 필수입니다.'); return;
}
if(id){ // 수정
const g = GROUPS.find(x=>x.id==id);
if(!g) return;
g.group_key = payload.group_key;
g.name = payload.name;
g.description = payload.description;
// 실제: POST /tenant/api/options/group_update.php
}else{ // 생성
const newId = Math.max(0, ...GROUPS.map(x=>x.id)) + 1;
GROUPS.push({id:newId, ...payload});
VALUES[newId] = [];
currentGroupId = newId;
// 실제: POST /tenant/api/options/group_create.php
}
bootstrap.Modal.getInstance(document.getElementById('groupModal')).hide();
renderGroups(); renderValues();
});
// ====== 항목 이벤트 ======
$('#btnFindValue').on('click', function(){
filterValueKW = $('#kwValue').val().trim();
renderValues();
});
$('#btnOpenAddValue').on('click', function(){
if(!currentGroupId){ alert('그룹을 먼저 선택하세요.'); return; }
$('#valueModalTitle').text('항목 추가');
$('#valueForm')[0].reset();
$('#value_id').val('');
$('#value_group_id').val(currentGroupId);
$('#is_active').prop('checked', true);
new bootstrap.Modal('#valueModal').show();
});
$(document).on('click', '.btn-edit-value', function(){
const id = +$(this).data('id');
const list = VALUES[currentGroupId] || [];
const v = list.find(x=>x.id===id);
if(!v) return;
$('#valueModalTitle').text('항목 수정');
$('#value_id').val(v.id);
$('#value_group_id').val(v.group_id);
$('#value_key').val(v.value_key);
$('#value_label').val(v.value_label);
$('#sort_order').val(v.sort_order);
$('#is_active').prop('checked', !!v.is_active);
new bootstrap.Modal('#valueModal').show();
});
$(document).on('click', '.btn-del-value', function(){
const id = +$(this).data('id');
$('#confirm_type').val('value');
$('#confirm_target').val(id);
new bootstrap.Modal('#confirmModal').show();
});
$('#valueForm').on('submit', function(e){
e.preventDefault();
const id = $('#value_id').val();
const gid = +$('#value_group_id').val();
const payload = {
group_id: gid,
value_key: $('#value_key').val().trim(),
value_label: $('#value_label').val().trim(),
sort_order: parseInt($('#sort_order').val(),10) || 0,
is_active: $('#is_active').is(':checked') ? 1 : 0
};
if(!payload.value_key || !payload.value_label){
alert('코드와 라벨은 필수입니다.'); return;
}
const list = VALUES[gid] || (VALUES[gid]=[]);
if(id){ // 수정
const v = list.find(x=>x.id==id);
if(!v) return;
Object.assign(v, payload);
// 실제: POST /tenant/api/options/value_update.php
}else{ // 추가
const newId = ++seqValue;
list.push({id:newId, ...payload});
// 실제: POST /tenant/api/options/value_create.php
}
bootstrap.Modal.getInstance(document.getElementById('valueModal')).hide();
renderValues();
});
// ====== 공용 삭제 확정 ======
$('#btnConfirmDelete').on('click', function(){
const type = $('#confirm_type').val();
const targetId = +$('#confirm_target').val();
if(type==='group'){
// 그룹 삭제: 값도 함께 제거(외래키 ON DELETE CASCADE 성격)
GROUPS = GROUPS.filter(g=>g.id!==targetId);
delete VALUES[targetId];
if(currentGroupId===targetId) currentGroupId = null;
// 실제: GET /tenant/api/options/group_delete.php?id=...
renderGroups(); renderValues();
}else if(type==='value'){
if(!currentGroupId){ return; }
const list = VALUES[currentGroupId] || [];
const idx = list.findIndex(v=>v.id===targetId);
if(idx>-1) list.splice(idx,1);
// 실제: GET /tenant/api/options/value_delete.php?id=...
renderValues();
}
bootstrap.Modal.getInstance(document.getElementById('confirmModal')).hide();
});
// ====== 초기 렌더 ======
$(function(){
renderGroups();
// 기본 선택: 첫 그룹
if(GROUPS.length){
currentGroupId = GROUPS[0].id;
renderGroups(); renderValues();
}
});
</script>
<?php include '../inc/footer.php'; ?>