fix(tkuser): hire_date 지원 + 부서 팀장 타부서 선택 허용
- findAll()에 hire_date 추가, update()에 hire_date 처리 - 사용자 편집 모달에 입사일 input 추가 - 사용자 목록에 입사일 뱃지 표시 (미등록 시 주황 경고) - 부서 팀장 드롭다운: 전체 사용자 optgroup 방식으로 변경 - u.id → u.user_id 버그 수정 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -79,7 +79,7 @@ async function findById(userId) {
|
||||
async function findAll() {
|
||||
const db = getPool();
|
||||
const [rows] = await db.query(
|
||||
'SELECT user_id, username, name, department, department_id, role, system1_access, system2_access, system3_access, is_active, last_login, created_at FROM sso_users WHERE partner_company_id IS NULL ORDER BY user_id'
|
||||
'SELECT user_id, username, name, department, department_id, role, system1_access, system2_access, system3_access, is_active, last_login, created_at, hire_date FROM sso_users WHERE partner_company_id IS NULL ORDER BY user_id'
|
||||
);
|
||||
return rows;
|
||||
}
|
||||
@@ -108,6 +108,7 @@ async function update(userId, data) {
|
||||
if (data.system2_access !== undefined) { fields.push('system2_access = ?'); values.push(data.system2_access); }
|
||||
if (data.system3_access !== undefined) { fields.push('system3_access = ?'); values.push(data.system3_access); }
|
||||
if (data.is_active !== undefined) { fields.push('is_active = ?'); values.push(data.is_active); }
|
||||
if (data.hire_date !== undefined) { fields.push('hire_date = ?'); values.push(data.hire_date || null); }
|
||||
if (data.password) {
|
||||
fields.push('password_hash = ?');
|
||||
values.push(await hashPassword(data.password));
|
||||
|
||||
@@ -1043,6 +1043,10 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">입사일</label>
|
||||
<input type="date" id="editHireDate" class="input-field w-full px-3 py-1.5 rounded-lg text-sm">
|
||||
</div>
|
||||
<div class="flex gap-3 pt-3">
|
||||
<button type="button" onclick="closeEditModal()" class="flex-1 px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 text-sm">취소</button>
|
||||
<button type="submit" class="flex-1 px-4 py-2 bg-slate-700 text-white rounded-lg hover:bg-slate-800 text-sm font-medium"><i class="fas fa-save mr-1"></i>저장</button>
|
||||
@@ -1201,6 +1205,7 @@
|
||||
<select id="editDeptLeader" class="input-field w-full px-3 py-1.5 rounded-lg text-sm">
|
||||
<option value="">미지정</option>
|
||||
</select>
|
||||
<p class="text-xs text-gray-400 mt-1">타부서 임원 등도 선택 가능</p>
|
||||
</div>
|
||||
<div class="flex gap-3 pt-3">
|
||||
<button type="button" onclick="closeDepartmentModal()" class="flex-1 px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 text-sm">취소</button>
|
||||
@@ -2322,9 +2327,9 @@
|
||||
<!-- JS: Tabs -->
|
||||
<script src="/static/js/tkuser-tabs.js?v=2026032301"></script>
|
||||
<!-- JS: Individual modules -->
|
||||
<script src="/static/js/tkuser-users.js?v=2026032301"></script>
|
||||
<script src="/static/js/tkuser-users.js?v=2026032302"></script>
|
||||
<script src="/static/js/tkuser-projects.js?v=2026031401"></script>
|
||||
<script src="/static/js/tkuser-departments.js?v=2026032301"></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-workplaces.js?v=2026031401"></script>
|
||||
<script src="/static/js/tkuser-tasks.js?v=2026031401"></script>
|
||||
|
||||
@@ -106,13 +106,30 @@ async function editDepartment(id) {
|
||||
if (!deptUsers || !deptUsers.length) {
|
||||
try { const r = await api('/users'); deptUsers = r.data || r; } catch(e) { /* ignore */ }
|
||||
}
|
||||
const members = (deptUsers || []).filter(u => u.department_id === d.department_id && u.is_active !== 0 && u.is_active !== false);
|
||||
members.forEach(u => {
|
||||
const o = document.createElement('option');
|
||||
o.value = u.id;
|
||||
o.textContent = u.name || u.username;
|
||||
if (d.leader_user_id && d.leader_user_id === u.id) o.selected = true;
|
||||
leaderSel.appendChild(o);
|
||||
const activeUsers = (deptUsers || []).filter(u => u.is_active !== 0 && u.is_active !== false);
|
||||
const byDept = {};
|
||||
activeUsers.forEach(u => {
|
||||
const dName = deptLabel(u.department, u.department_id);
|
||||
if (!byDept[dName]) byDept[dName] = [];
|
||||
byDept[dName].push(u);
|
||||
});
|
||||
const currentDeptName = d.department_name;
|
||||
const sortedKeys = Object.keys(byDept).sort((a, b) => {
|
||||
if (a === currentDeptName) return -1;
|
||||
if (b === currentDeptName) return 1;
|
||||
return a.localeCompare(b, 'ko');
|
||||
});
|
||||
sortedKeys.forEach(dName => {
|
||||
const grp = document.createElement('optgroup');
|
||||
grp.label = dName;
|
||||
byDept[dName].forEach(u => {
|
||||
const o = document.createElement('option');
|
||||
o.value = u.user_id;
|
||||
o.textContent = u.name || u.username;
|
||||
if (d.leader_user_id && d.leader_user_id === u.user_id) o.selected = true;
|
||||
grp.appendChild(o);
|
||||
});
|
||||
leaderSel.appendChild(grp);
|
||||
});
|
||||
|
||||
document.getElementById('editDepartmentModal').classList.remove('hidden');
|
||||
|
||||
@@ -138,6 +138,7 @@ function displayUsers() {
|
||||
<span>${u.username}</span>
|
||||
${u.department||u.department_id?`<span class="px-1.5 py-0.5 rounded bg-green-50 text-green-600">${deptLabel(u.department, u.department_id)}</span>`:''}
|
||||
<span class="px-1.5 py-0.5 rounded ${u.role==='admin'?'bg-red-50 text-red-600':'bg-slate-50 text-slate-500'}">${u.role==='admin'?'관리자':'사용자'}</span>
|
||||
${u.hire_date ? `<span class="px-1.5 py-0.5 rounded bg-blue-50 text-blue-600">입사 ${formatDate(u.hire_date)}</span>` : '<span class="px-1.5 py-0.5 rounded bg-orange-50 text-orange-500">입사일 미등록</span>'}
|
||||
${u.is_active===0||u.is_active===false?'<span class="px-1.5 py-0.5 rounded bg-gray-100 text-gray-400">비활성</span>':''}
|
||||
</div>
|
||||
</div>
|
||||
@@ -165,6 +166,7 @@ function editUser(id) {
|
||||
populateUserDeptSelects();
|
||||
document.getElementById('editDepartmentId').value=u.department_id||'';
|
||||
document.getElementById('editRole').value=u.role;
|
||||
document.getElementById('editHireDate').value = formatDate(u.hire_date);
|
||||
document.getElementById('editUserModal').classList.remove('hidden');
|
||||
}
|
||||
function closeEditModal() { document.getElementById('editUserModal').classList.add('hidden'); }
|
||||
@@ -173,7 +175,7 @@ document.getElementById('editUserForm').addEventListener('submit', async e => {
|
||||
e.preventDefault();
|
||||
const deptIdVal = document.getElementById('editDepartmentId').value;
|
||||
try {
|
||||
await api(`/users/${document.getElementById('editUserId').value}`, { method:'PUT', body: JSON.stringify({ name: document.getElementById('editFullName').value.trim()||null, department_id: deptIdVal ? parseInt(deptIdVal) : null, role: document.getElementById('editRole').value }) });
|
||||
await api(`/users/${document.getElementById('editUserId').value}`, { method:'PUT', body: JSON.stringify({ name: document.getElementById('editFullName').value.trim()||null, department_id: deptIdVal ? parseInt(deptIdVal) : null, role: document.getElementById('editRole').value, hire_date: document.getElementById('editHireDate').value || null }) });
|
||||
showToast('수정되었습니다.'); closeEditModal(); await loadUsers();
|
||||
} catch(e) { showToast(e.message,'error'); }
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user