Files
sam-api/public/tenant/tenant/user_list.php
2025-08-10 02:36:50 +09:00

348 lines
16 KiB
PHP

<!-- 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:40px;">
<div class="card shadow p-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<h4 class="mb-0">회원(유저) 목록</h4>
<button class="btn btn-primary" id="btnOpenAdd">+ 회원 등록</button>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle text-center" id="userTable">
<thead class="table-light">
<tr id="theadRow">
<!-- JS가 동적 컬럼(옵션) 포함하여 렌더 -->
</tr>
</thead>
<tbody id="userTbody"><!-- JS 렌더 --></tbody>
</table>
</div>
</div>
</div>
<!-- 등록 모달 -->
<div class="modal fade" id="userAddModal" 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">회원 등록</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="닫기"></button>
</div>
<form id="userAddForm" autocomplete="off" enctype="multipart/form-data">
<div class="modal-body">
<div class="mb-2">
<label class="form-label">회원 아이디 <span class="text-danger">*</span></label>
<input type="text" class="form-control" name="user_id" maxlength="100" required>
</div>
<div class="mb-2">
<label class="form-label">이름 <span class="text-danger">*</span></label>
<input type="text" class="form-control" name="name" maxlength="100" required>
</div>
<div class="mb-2">
<label class="form-label">이메일 <span class="text-danger">*</span></label>
<input type="email" class="form-control" name="email" maxlength="255" required>
</div>
<div class="mb-2">
<label class="form-label">전화번호</label>
<input type="text" class="form-control" name="phone" maxlength="30">
</div>
<div class="row g-2">
<div class="col">
<label class="form-label">비밀번호 <span class="text-danger">*</span></label>
<input type="password" class="form-control" name="password" maxlength="30" required>
</div>
<div class="col">
<label class="form-label">비밀번호 확인 <span class="text-danger">*</span></label>
<input type="password" class="form-control" name="password2" maxlength="30" required>
</div>
</div>
<div class="mb-2">
<label class="form-label">프로필 사진</label>
<input type="file" class="form-control" name="profile_photo" accept="image/*">
</div>
<!-- 옵션 필드(샘플: 사번/계좌번호) -->
<div id="addOptionFields"><!-- JS 렌더 --></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">등록</button>
</div>
</form>
</div>
</div>
</div>
<!-- 수정 모달 -->
<div class="modal fade" id="userEditModal" 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">회원 정보 수정</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="닫기"></button>
</div>
<form id="userEditForm" autocomplete="off" enctype="multipart/form-data">
<div class="modal-body">
<input type="hidden" name="id" id="edit_id">
<div class="mb-2">
<label class="form-label">회원 아이디</label>
<input type="text" class="form-control" id="edit_user_id" readonly>
</div>
<div class="mb-2">
<label class="form-label">이름 <span class="text-danger">*</span></label>
<input type="text" class="form-control" name="name" id="edit_name" maxlength="100" required>
</div>
<div class="mb-2">
<label class="form-label">이메일 <span class="text-danger">*</span></label>
<input type="email" class="form-control" name="email" id="edit_email" maxlength="255" required>
</div>
<div class="mb-2">
<label class="form-label">전화번호</label>
<input type="text" class="form-control" name="phone" id="edit_phone" maxlength="30">
</div>
<div class="mb-2">
<label class="form-label">프로필 사진</label>
<div id="edit_photo_preview" class="mb-2"></div>
<input type="file" class="form-control" name="profile_photo" accept="image/*">
</div>
<div class="row g-2">
<div class="col">
<label class="form-label">비밀번호 변경</label>
<input type="password" class="form-control" name="password" maxlength="30" placeholder="변경 시 입력">
</div>
<div class="col">
<label class="form-label">비밀번호 확인</label>
<input type="password" class="form-control" name="password2" maxlength="30" placeholder="변경 시 입력">
</div>
</div>
<!-- 옵션 필드(동적) -->
<div id="editOptionFields" class="mt-2"><!-- JS 렌더 --></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">저장</button>
</div>
</form>
</div>
</div>
</div>
<!-- 삭제 확인 모달 -->
<div class="modal fade" id="userDeleteModal" 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="del_user_id">
</div>
<div class="modal-footer">
<button class="btn btn-outline-secondary" type="button" data-bs-dismiss="modal">취소</button>
<button class="btn btn-danger" type="button" id="btnConfirmDelete">삭제</button>
</div>
</div>
</div>
</div>
<script>
$(function(){
// ------- 샘플: 이 테넌트에서 허용된 옵션 -------
const allowedOptions = ['사번','계좌번호'];
// ------- 샘플 유저 데이터 -------
let SEQ = 2;
const users = [
{ id:1, user_id:'kevin', name:'권혁성', email:'kevin@sample.com', phone:'010-1111-2222',
options: {'사번':'A001','계좌번호':'111-2222-3333'}, created_at:'2024-07-01', profile_photo_path:'' },
{ id:2, user_id:'sally', name:'김슬기', email:'sally@sample.com', phone:'010-3333-4444',
options: {'사번':'A002','계좌번호':'222-3333-4444'}, created_at:'2024-07-10', profile_photo_path:'' },
];
// ------- 유틸 -------
function escapeHtml(s){ return String(s??'').replace(/[&<>"']/g, m=>({ '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;' }[m])); }
// ------- 테이블 헤더 렌더(옵션 포함 동적) -------
function renderThead(){
const fixed = ['#','회원ID','이름','이메일','전화번호'];
const tail = ['가입일','관리'];
const ths = [
...fixed.map(t=>`<th>${t}</th>`),
...allowedOptions.map(o=>`<th>${escapeHtml(o)}</th>`),
...tail.map(t=>`<th>${t}</th>`)
].join('');
$('#theadRow').html(ths);
}
// ------- 리스트 렌더 -------
function renderTbody(){
const rows = users.map(u=>{
const opts = allowedOptions.map(o=>{
const v = (u.options && u.options[o]) ? u.options[o] : '-';
return `<td>${escapeHtml(v)}</td>`;
}).join('');
return `
<tr data-id="${u.id}">
<td>${u.id}</td>
<td>${escapeHtml(u.user_id)}</td>
<td>${escapeHtml(u.name)}</td>
<td>${escapeHtml(u.email)}</td>
<td>${escapeHtml(u.phone||'')}</td>
${opts}
<td>${u.created_at||''}</td>
<td>
<div class="d-flex justify-content-center gap-1">
<button class="btn btn-sm btn-outline-secondary btn-edit" data-id="${u.id}">수정</button>
<button class="btn btn-sm btn-outline-danger btn-del" data-id="${u.id}">삭제</button>
</div>
</td>
</tr>
`;
}).join('');
$('#userTbody').html(rows);
}
// ------- 옵션 입력 필드 렌더 -------
function renderOptionInputs($wrap, values={}){
const html = allowedOptions.map(o=>{
const key = o; // 표시 라벨과 키 동일(샘플)
const val = values[key] ?? '';
return `
<div class="mb-2">
<label class="form-label">${escapeHtml(o)}</label>
<input type="text" class="form-control" name="opt__${encodeURIComponent(key)}" value="${escapeHtml(val)}">
</div>
`;
}).join('');
$wrap.html(html);
}
// 초기 렌더
renderThead();
renderTbody();
// ------- 등록 모달 열기 -------
$('#btnOpenAdd').on('click', function(){
$('#userAddForm')[0].reset();
renderOptionInputs($('#addOptionFields'), {});
new bootstrap.Modal('#userAddModal').show();
});
// ------- 등록 처리(프로토타입: 클라이언트 메모리) -------
$('#userAddForm').on('submit', function(e){
e.preventDefault();
const get = n => $(this).find(`[name="${n}"]`).val();
const uid = get('user_id').trim();
const name = get('name').trim();
const email = get('email').trim();
const phone = get('phone').trim();
const pw1 = get('password'), pw2 = get('password2');
if(uid.length<2){ alert('회원 아이디를 2글자 이상 입력하세요.'); return; }
if(name.length<2){ alert('이름을 2글자 이상 입력하세요.'); return; }
if(!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)){ alert('이메일을 올바르게 입력하세요.'); return; }
if(pw1.length<4){ alert('비밀번호는 4글자 이상이어야 합니다.'); return; }
if(pw1!==pw2){ alert('비밀번호가 일치하지 않습니다.'); return; }
// 옵션 수집
const opts = {};
allowedOptions.forEach(o=>{
const v = $(this).find(`[name="opt__${encodeURIComponent(o)}"]`).val().trim();
if(v) opts[o]=v;
});
const id = ++SEQ;
users.push({
id, user_id:uid, name, email, phone,
options: opts, created_at: new Date().toISOString().slice(0,10),
profile_photo_path:''
});
renderTbody();
bootstrap.Modal.getInstance(document.getElementById('userAddModal')).hide();
// 실제 연동 예:
// const fd = new FormData(this);
// allowedOptions.forEach(o=> fd.append('options['+o+']', $(this).find(`[name="opt__${encodeURIComponent(o)}"]`).val()));
// $.ajax({url:'/tenant/tenant/user_add_process.php', method:'POST', data:fd, processData:false, contentType:false})
// .done(()=>location.reload());
});
// ------- 수정 모달 열기 -------
$(document).on('click', '.btn-edit', function(){
const id = +$(this).data('id');
const u = users.find(x=>x.id===id);
if(!u) return;
$('#edit_id').val(u.id);
$('#edit_user_id').val(u.user_id);
$('#edit_name').val(u.name);
$('#edit_email').val(u.email);
$('#edit_phone').val(u.phone||'');
$('#edit_photo_preview').html(u.profile_photo_path ? `<img src="${escapeHtml(u.profile_photo_path)}" alt="프로필" style="height:40px;">` : '');
renderOptionInputs($('#editOptionFields'), u.options||{});
new bootstrap.Modal('#userEditModal').show();
});
// ------- 수정 처리(프로토타입) -------
$('#userEditForm').on('submit', function(e){
e.preventDefault();
const id = +$('#edit_id').val();
const name = $('#edit_name').val().trim();
const email = $('#edit_email').val().trim();
const phone = $('#edit_phone').val().trim();
const pw1 = $(this).find('[name="password"]').val();
const pw2 = $(this).find('[name="password2"]').val();
if(name.length<2){ alert('이름은 2글자 이상 입력하세요.'); return; }
if(!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)){ alert('이메일을 올바르게 입력하세요.'); return; }
if(pw1 || pw2){
if(pw1.length<4){ alert('비밀번호는 4글자 이상이어야 합니다.'); return; }
if(pw1!==pw2){ alert('비밀번호가 일치하지 않습니다.'); return; }
}
const u = users.find(x=>x.id===id);
if(!u) return;
u.name = name; u.email = email; u.phone = phone;
// 옵션 업데이트
const opts = {};
allowedOptions.forEach(o=>{
const v = $('#editOptionFields').find(`[name="opt__${encodeURIComponent(o)}"]`).val().trim();
if(v) opts[o]=v;
});
u.options = opts;
renderTbody();
bootstrap.Modal.getInstance(document.getElementById('userEditModal')).hide();
// 실제 연동 예:
// const fd = new FormData(this);
// allowedOptions.forEach(o=> fd.append('options['+o+']', $('#editOptionFields').find(`[name="opt__${encodeURIComponent(o)}"]`).val()));
// $.ajax({url:'/tenant/tenant/user_edit_process.php', method:'POST', data:fd, processData:false, contentType:false})
// .done(()=>location.reload());
});
// ------- 삭제 -------
$(document).on('click', '.btn-del', function(){
$('#del_user_id').val(+$(this).data('id'));
new bootstrap.Modal('#userDeleteModal').show();
});
$('#btnConfirmDelete').on('click', function(){
const id = +$('#del_user_id').val();
const i = users.findIndex(x=>x.id===id);
if(i>-1) users.splice(i,1);
renderTbody();
bootstrap.Modal.getInstance(document.getElementById('userDeleteModal')).hide();
// 실제:
// location.href = '/tenant/tenant/user_delete.php?id='+id;
});
});
</script>
<?php include '../inc/footer.php'; ?>