feat(tkuser): 사용자 목록 검색 + 부서 필터 기능 추가
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -149,7 +149,16 @@
|
|||||||
|
|
||||||
<!-- 사용자 목록 (넓게) -->
|
<!-- 사용자 목록 (넓게) -->
|
||||||
<div class="lg:col-span-3 bg-white rounded-xl shadow-sm p-5">
|
<div class="lg:col-span-3 bg-white rounded-xl shadow-sm p-5">
|
||||||
<h2 class="text-base font-semibold text-gray-800 mb-4"><i class="fas fa-users text-slate-400 mr-2"></i>사용자 목록</h2>
|
<h2 class="text-base font-semibold text-gray-800 mb-3">
|
||||||
|
<i class="fas fa-users text-slate-400 mr-2"></i>사용자 목록
|
||||||
|
<span id="activeUserCount" class="text-xs font-normal text-gray-400 ml-1"></span>
|
||||||
|
</h2>
|
||||||
|
<div class="flex gap-2 mb-3">
|
||||||
|
<input type="text" id="userSearchInput" class="input-field flex-1 px-3 py-1.5 rounded-lg text-sm" placeholder="이름/아이디 검색">
|
||||||
|
<select id="userDeptFilter" class="input-field px-2 py-1.5 rounded-lg text-sm">
|
||||||
|
<option value="">전체 부서</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<div id="userList" class="space-y-2 max-h-[420px] overflow-y-auto">
|
<div id="userList" class="space-y-2 max-h-[420px] overflow-y-auto">
|
||||||
<div class="text-gray-400 text-center py-8"><i class="fas fa-spinner fa-spin text-2xl"></i><p class="mt-2 text-sm">로딩 중...</p></div>
|
<div class="text-gray-400 text-center py-8"><i class="fas fa-spinner fa-spin text-2xl"></i><p class="mt-2 text-sm">로딩 중...</p></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -2351,7 +2360,7 @@
|
|||||||
<!-- JS: Tabs -->
|
<!-- JS: Tabs -->
|
||||||
<script src="/static/js/tkuser-tabs.js?v=2026032301"></script>
|
<script src="/static/js/tkuser-tabs.js?v=2026032301"></script>
|
||||||
<!-- JS: Individual modules -->
|
<!-- JS: Individual modules -->
|
||||||
<script src="/static/js/tkuser-users.js?v=2026032303"></script>
|
<script src="/static/js/tkuser-users.js?v=2026032501"></script>
|
||||||
<script src="/static/js/tkuser-projects.js?v=2026031401"></script>
|
<script src="/static/js/tkuser-projects.js?v=2026031401"></script>
|
||||||
<script src="/static/js/tkuser-departments.js?v=2026032302"></script>
|
<script src="/static/js/tkuser-departments.js?v=2026032302"></script>
|
||||||
<script src="/static/js/tkuser-issue-types.js?v=2026031401"></script>
|
<script src="/static/js/tkuser-issue-types.js?v=2026031401"></script>
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ async function loadUsers() {
|
|||||||
try {
|
try {
|
||||||
const r = await api('/users'); users = r.data || r;
|
const r = await api('/users'); users = r.data || r;
|
||||||
displayUsers(); updatePermissionUserSelect();
|
displayUsers(); updatePermissionUserSelect();
|
||||||
populateUserDeptSelects();
|
populateUserDeptSelects(); populateUserDeptFilter();
|
||||||
const hireDateInput = document.getElementById('newHireDate');
|
const hireDateInput = document.getElementById('newHireDate');
|
||||||
if (hireDateInput && !hireDateInput.value) hireDateInput.value = getSeoulToday();
|
if (hireDateInput && !hireDateInput.value) hireDateInput.value = getSeoulToday();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -129,6 +129,15 @@ function populateUserDeptSelects() {
|
|||||||
sel.value = val;
|
sel.value = val;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// 필터용 부서 셀렉트 (populateUserDeptSelects는 모달 폼용)
|
||||||
|
function populateUserDeptFilter() {
|
||||||
|
const sel = document.getElementById('userDeptFilter');
|
||||||
|
if (!sel) return;
|
||||||
|
const val = sel.value;
|
||||||
|
sel.innerHTML = '<option value="">전체 부서</option>';
|
||||||
|
departmentsCache.forEach(d => { const o = document.createElement('option'); o.value = d.department_id; o.textContent = d.department_name; sel.appendChild(o); });
|
||||||
|
sel.value = val;
|
||||||
|
}
|
||||||
function renderUserRow(u, isResigned) {
|
function renderUserRow(u, isResigned) {
|
||||||
return `<div class="flex items-center justify-between p-2.5 bg-gray-50 rounded-lg hover:bg-gray-100 transition-colors">
|
return `<div class="flex items-center justify-between p-2.5 bg-gray-50 rounded-lg hover:bg-gray-100 transition-colors">
|
||||||
<div class="flex-1 min-w-0">
|
<div class="flex-1 min-w-0">
|
||||||
@@ -151,12 +160,38 @@ function renderUserRow(u, isResigned) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function displayUsers() {
|
function displayUsers() {
|
||||||
const activeUsers = users.filter(u => u.is_active !== 0 && u.is_active !== false);
|
const searchTerm = (document.getElementById('userSearchInput')?.value || '').trim().toLowerCase();
|
||||||
const resignedUsers = users.filter(u => u.is_active === 0 || u.is_active === false);
|
const deptFilterVal = document.getElementById('userDeptFilter')?.value || '';
|
||||||
|
|
||||||
|
let filtered = users;
|
||||||
|
|
||||||
|
if (searchTerm) {
|
||||||
|
filtered = filtered.filter(u => {
|
||||||
|
const name = (u.name || '').toLowerCase();
|
||||||
|
const username = (u.username || '').toLowerCase();
|
||||||
|
return name.includes(searchTerm) || username.includes(searchTerm);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deptFilterVal) {
|
||||||
|
const deptId = parseInt(deptFilterVal);
|
||||||
|
filtered = filtered.filter(u => u.department_id === deptId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeUsers = filtered.filter(u => u.is_active !== 0 && u.is_active !== false);
|
||||||
|
const resignedUsers = filtered.filter(u => u.is_active === 0 || u.is_active === false);
|
||||||
|
|
||||||
const c = document.getElementById('userList');
|
const c = document.getElementById('userList');
|
||||||
if (!activeUsers.length) { c.innerHTML = '<p class="text-gray-400 text-center py-4 text-sm">등록된 사용자가 없습니다.</p>'; }
|
const countEl = document.getElementById('activeUserCount');
|
||||||
else { c.innerHTML = activeUsers.map(u => renderUserRow(u, false)).join(''); }
|
if (countEl) countEl.textContent = `(${activeUsers.length}명)`;
|
||||||
|
|
||||||
|
if (!activeUsers.length) {
|
||||||
|
c.innerHTML = searchTerm || deptFilterVal
|
||||||
|
? '<p class="text-gray-400 text-center py-4 text-sm">검색 결과가 없습니다.</p>'
|
||||||
|
: '<p class="text-gray-400 text-center py-4 text-sm">등록된 사용자가 없습니다.</p>';
|
||||||
|
} else {
|
||||||
|
c.innerHTML = activeUsers.map(u => renderUserRow(u, false)).join('');
|
||||||
|
}
|
||||||
|
|
||||||
const resignedSection = document.getElementById('resignedSection');
|
const resignedSection = document.getElementById('resignedSection');
|
||||||
const resignedList = document.getElementById('resignedUserList');
|
const resignedList = document.getElementById('resignedUserList');
|
||||||
@@ -419,6 +454,16 @@ function populateDeptPermSelect() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
// 사용자 검색 + 부서 필터
|
||||||
|
let userSearchTimeout;
|
||||||
|
const userSearchEl = document.getElementById('userSearchInput');
|
||||||
|
if (userSearchEl) userSearchEl.addEventListener('input', () => {
|
||||||
|
clearTimeout(userSearchTimeout);
|
||||||
|
userSearchTimeout = setTimeout(displayUsers, 300);
|
||||||
|
});
|
||||||
|
const userDeptFilterEl = document.getElementById('userDeptFilter');
|
||||||
|
if (userDeptFilterEl) userDeptFilterEl.addEventListener('change', displayUsers);
|
||||||
|
|
||||||
const sel = document.getElementById('deptPermSelect');
|
const sel = document.getElementById('deptPermSelect');
|
||||||
if (sel) sel.addEventListener('change', async e => {
|
if (sel) sel.addEventListener('change', async e => {
|
||||||
selectedDeptId = e.target.value;
|
selectedDeptId = e.target.value;
|
||||||
|
|||||||
Reference in New Issue
Block a user