Files
sam-api/public/tenant/approval/pool.php

266 lines
10 KiB
PHP
Raw Normal View History

<!-- SAM RULES: include=../inc/header.php; base=/tenant; width=1280; js=jQuery+BS5 -->
<?php
$CURRENT_SECTION = 'approval';
include '../inc/header.php';
?>
<div class="container" style="max-width:1280px; margin-top:24px;">
<div class="card shadow-sm">
<div class="card-header d-flex justify-content-between align-items-center">
<strong>결재권자 관리</strong>
<!-- 툴바 -->
<form id="toolbar" class="sam-toolbar d-flex align-items-center gap-2 flex-nowrap" role="search" onsubmit="return false;">
<select class="form-select form-select-sm" id="type" style="width:140px;">
<option value="user">개인</option>
<option value="department">부서</option>
<option value="role">역할</option>
</select>
<input class="form-control form-control-sm" id="keyword" placeholder="이름/코드" style="width:260px;">
<div class="sam-actions d-flex flex-nowrap gap-2">
<button class="btn btn-sm btn-outline-secondary" id="find" type="button">검색</button>
<button class="btn btn-sm btn-primary" id="add" type="button">추가</button>
2025-08-10 02:36:50 +09:00
</div>
</form>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover align-middle text-center m-0" id="poolTable">
<thead class="table-light">
<tr>
<th style="width:120px;">유형</th>
<th>대상</th>
<th style="width:140px;">관리</th>
</tr>
</thead>
<tbody id="poolRows"><!-- JS 렌더 --></tbody>
2025-08-10 02:36:50 +09:00
</table>
</div>
</div>
</div>
</div>
<!-- 검색/추가 모달 -->
<div class="modal fade" id="pickModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">대상 선택</h5>
<button class="btn-close" data-bs-dismiss="modal" aria-label="닫기"></button>
</div>
<div class="modal-body">
<!-- 검색바(모달 내부에서도 재검색 가능) -->
<div class="d-flex align-items-center gap-2 mb-2">
<select class="form-select form-select-sm" id="pm_type" style="width:140px;"></select>
<input class="form-control form-control-sm" id="pm_keyword" placeholder="이름/코드">
<button class="btn btn-sm btn-outline-secondary" id="pm_find" type="button">검색</button>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle text-center m-0">
<thead class="table-light">
<tr>
<th style="width:100px;">유형</th>
<th>코드</th>
<th>이름</th>
<th style="width:120px;">선택</th>
</tr>
</thead>
<tbody id="pm_rows"><!-- JS 렌더 --></tbody>
</table>
</div>
</div>
<div class="modal-footer">
<div class="text-muted small">선택 즉시 풀에 추가됩니다.</div>
</div>
</div>
</div>
</div>
<style>
/* 툴바 전체를 한 줄로 고정 */
.sam-toolbar { display:flex; flex-wrap:nowrap; gap:.5rem; }
.sam-toolbar > * { flex:0 0 auto; min-width:0; } /* 넓이 강제분배 방지 */
/* 버튼 한글 줄바꿈 방지 (전역 break-all 무력화) */
.sam-toolbar .btn,
.sam-actions .btn {
display:inline-flex;
align-items:center;
justify-content:center;
white-space:nowrap !important;
word-break:normal !important; /* ← 핵심 */
overflow-wrap:normal !important;/* ← 핵심 */
line-height:1.5;
padding:.25rem .6rem; /* btn-sm 기본과 유사 */
flex:0 0 auto;
}
/* 입력 폭이 너무 좁아 버튼을 밀어내지 않도록 적당한 폭 부여 */
#keyword { width:260px; min-width:160px; }
/* 매우 좁은 화면일 때만 버튼 폭/패딩을 더 줄임 */
@media (max-width: 576px){
#keyword{ width:140px; }
.sam-toolbar .btn{ padding:.2rem .45rem; }
}
</style>
<script>
$(function(){
// -----------------------------
// 1) 샘플 데이터 (실서버에선 API로 대체)
// -----------------------------
const SAMPLE = {
user: [
{ code:'u001', name:'권혁성(kevin)' },
{ code:'u002', name:'김슬기(sally)' },
{ code:'u003', name:'홍길동(hong)' },
],
department: [
{ code:'d001', name:'개발팀' },
{ code:'d002', name:'영업팀' },
{ code:'d003', name:'생산팀' },
],
role: [
{ code:'r001', name:'최고관리자' },
{ code:'r002', name:'일반관리자' },
{ code:'r003', name:'일반직원' },
]
};
// 결재권자 풀 (메모리) : {type, code, name}
const pool = [
{type:'department', code:'d001', name:'개발팀'},
{type:'role', code:'r002', name:'일반관리자'},
{type:'user', code:'u001', name:'권혁성(kevin)'},
];
// -----------------------------
// 2) 렌더 함수
// -----------------------------
function renderPool(){
if(pool.length===0){
$('#poolRows').html('<tr><td colspan="3" class="text-muted py-4">등록된 결재권자가 없습니다.</td></tr>');
return;
}
const html = pool.map((p,idx)=>`
<tr>
<td>${labelType(p.type)}</td>
<td class="text-start">${escapeHtml(p.name)} <span class="text-muted">/ ${escapeHtml(p.code)}</span></td>
<td>
<button class="btn btn-sm btn-outline-danger" data-remove="${idx}">삭제</button>
</td>
</tr>
`).join('');
$('#poolRows').html(html);
}
function renderPickRows(list, type){
if(list.length===0){
$('#pm_rows').html('<tr><td colspan="4" class="text-muted py-4">검색 결과가 없습니다.</td></tr>');
return;
}
const html = list.map(x=>`
<tr>
<td>${labelType(type)}</td>
<td>${escapeHtml(x.code)}</td>
<td class="text-start">${escapeHtml(x.name)}</td>
<td><button class="btn btn-sm btn-primary" data-pick="${type}|${x.code}">선택</button></td>
</tr>
`).join('');
$('#pm_rows').html(html);
}
// -----------------------------
// 3) 유틸
// -----------------------------
function labelType(t){
switch(t){
case 'user': return '개인';
case 'department': return '부서';
case 'role': return '역할';
default: return t;
}
}
function escapeHtml(s){return String(s??'').replace(/[&<>"']/g,m=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m]));}
function uniquePush(arr, item){
if(!arr.some(x=>x.type===item.type && x.code===item.code)) arr.push(item);
}
// -----------------------------
// 4) 이벤트: 삭제
// -----------------------------
$(document).on('click','[data-remove]',function(){
const i = +$(this).data('remove');
if(i>=0){ pool.splice(i,1); renderPool(); }
// 실제: $.post('/api/approval/pool_delete.php', { type: pool[i].type, code: pool[i].code })
});
// -----------------------------
// 5) 검색 & 추가 (툴바)
// -----------------------------
$('#find').on('click', function(){
openPicker(false); // 조회만
});
$('#add').on('click', function(){
openPicker(true); // 선택 시 추가
2025-08-10 02:36:50 +09:00
});
// -----------------------------
// 6) 모달 열기/검색/선택
// -----------------------------
function openPicker(isAddMode){
const type = $('#type').val();
const kw = $('#keyword').val().trim().toLowerCase();
// 모달 상단 타입/키워드 동기화
$('#pm_type').html($('#type').html()).val(type);
$('#pm_keyword').val($('#keyword').val());
// 첫 렌더
const list = filterList(type, kw);
renderPickRows(list, type);
// 모달 띄우기
const m = new bootstrap.Modal('#pickModal');
m.show();
// 모달 내 검색
$('#pm_find').off('click').on('click', function(){
const t2 = $('#pm_type').val();
const k2 = $('#pm_keyword').val().trim().toLowerCase();
renderPickRows(filterList(t2, k2), t2);
});
// 선택 → 추가
$(document).off('click.pmPick').on('click.pmPick','[data-pick]', function(){
const [t,c] = String($(this).data('pick')).split('|');
const item = (SAMPLE[t]||[]).find(x=>x.code===c);
if(!item) return;
// 추가 모드일 때만 풀에 반영
uniquePush(pool, {type:t, code:item.code, name:item.name});
renderPool();
bootstrap.Modal.getInstance(document.getElementById('pickModal')).hide();
// 실제: $.post('/api/approval/pool_add.php', { type:t, code:item.code })
});
}
function filterList(type, kw){
const base = SAMPLE[type] || [];
if(!kw) return base;
return base.filter(x =>
String(x.code).toLowerCase().includes(kw) ||
String(x.name).toLowerCase().includes(kw)
);
}
// 최초 렌더
renderPool();
});
</script>
2025-08-10 02:36:50 +09:00
<?php include '../inc/footer.php'; ?>