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

332 lines
16 KiB
PHP

<!-- 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 } ?>
</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'; ?>