Files
tk-factory-services/user-management/web/static/js/tkuser-tasks.js
Hyungi Ahn 11cffbd920 refactor: System2/3, User Management SSO 인증 통합
- System2 신고: SSO JWT 인증 전환, API base 정리
- System3 부적합: SSO 인증 매니저 통합, 권한 체계 정비
- User Management: SSO 토큰 기반 사용자 관리 API 연동

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:18:23 +09:00

219 lines
11 KiB
JavaScript

/* ===== Tasks CRUD ===== */
let taskWorkTypes = [], allTasks = [], tasksLoaded = false;
let selectedTaskWorkTypeFilter = null;
async function loadTasksTab() {
await loadWorkTypes();
await loadTasks();
tasksLoaded = true;
}
async function loadWorkTypes() {
try {
const r = await api('/tasks/work-types');
taskWorkTypes = r.data || [];
renderWorkTypeSidebar();
populateTaskWorkTypeSelect();
} catch(e) { console.warn('공정 로드 실패:', e); }
}
function renderWorkTypeSidebar() {
const c = document.getElementById('workTypeSidebar');
if (!c) return;
let html = `<div onclick="filterTasksByWorkType(null)" class="flex items-center justify-between p-2 rounded-lg cursor-pointer transition-colors ${selectedTaskWorkTypeFilter === null ? 'bg-blue-50 ring-1 ring-blue-200' : 'hover:bg-gray-50'}">
<span class="text-sm font-medium ${selectedTaskWorkTypeFilter === null ? 'text-blue-700' : 'text-gray-700'}">전체</span>
<span class="text-xs text-gray-400">${allTasks.length}</span>
</div>`;
// 카테고리별 그룹핑
const grouped = {};
taskWorkTypes.forEach(wt => {
const cat = wt.category || '미분류';
if (!grouped[cat]) grouped[cat] = [];
grouped[cat].push(wt);
});
Object.keys(grouped).sort().forEach(cat => {
html += `<div class="text-[10px] font-semibold text-gray-400 uppercase tracking-wider mt-3 mb-1 px-2">${cat}</div>`;
grouped[cat].forEach(wt => {
const count = allTasks.filter(t => t.work_type_id === wt.id).length;
const isActive = selectedTaskWorkTypeFilter === wt.id;
html += `<div onclick="filterTasksByWorkType(${wt.id})" class="group flex items-center justify-between p-2 rounded-lg cursor-pointer transition-colors ${isActive ? 'bg-blue-50 ring-1 ring-blue-200' : 'hover:bg-gray-50'}">
<span class="text-sm ${isActive ? 'font-medium text-blue-700' : 'text-gray-700'} truncate">${wt.name}</span>
<div class="flex items-center gap-1">
<span class="text-xs text-gray-400">${count}</span>
<button onclick="event.stopPropagation(); editWorkType(${wt.id})" class="p-0.5 text-gray-300 hover:text-slate-600 opacity-0 group-hover:opacity-100 transition-opacity" title="수정"><i class="fas fa-pen text-[10px]"></i></button>
</div>
</div>`;
});
});
// 미지정 작업 수
const noType = allTasks.filter(t => !t.work_type_id).length;
if (noType > 0) {
html += `<div onclick="filterTasksByWorkType(0)" class="flex items-center justify-between p-2 rounded-lg cursor-pointer transition-colors mt-2 ${selectedTaskWorkTypeFilter === 0 ? 'bg-blue-50 ring-1 ring-blue-200' : 'hover:bg-gray-50'}">
<span class="text-sm text-gray-400 italic">미지정</span>
<span class="text-xs text-gray-400">${noType}</span>
</div>`;
}
c.innerHTML = html;
}
function populateTaskWorkTypeSelect() {
const sel = document.getElementById('taskWorkType');
if (!sel) return;
const val = sel.value;
sel.innerHTML = '<option value="">미지정</option>';
taskWorkTypes.forEach(wt => {
sel.innerHTML += `<option value="${wt.id}">${wt.category ? escapeHtml(wt.category) + ' > ' : ''}${escapeHtml(wt.name)}</option>`;
});
sel.value = val;
}
function filterTasksByWorkType(wtId) {
selectedTaskWorkTypeFilter = wtId;
renderWorkTypeSidebar();
displayTasks();
}
async function loadTasks() {
try {
const r = await api('/tasks');
allTasks = r.data || [];
renderWorkTypeSidebar();
displayTasks();
} catch(e) {
document.getElementById('taskList').innerHTML = `<div class="text-red-500 text-center py-6"><i class="fas fa-exclamation-triangle text-xl"></i><p class="text-sm mt-2">${e.message}</p></div>`;
}
}
function displayTasks() {
const c = document.getElementById('taskList');
let filtered = allTasks;
let label = '전체';
if (selectedTaskWorkTypeFilter === 0) {
filtered = allTasks.filter(t => !t.work_type_id);
label = '미지정';
} else if (selectedTaskWorkTypeFilter) {
filtered = allTasks.filter(t => t.work_type_id === selectedTaskWorkTypeFilter);
const wt = taskWorkTypes.find(w => w.id === selectedTaskWorkTypeFilter);
label = wt ? wt.name : '';
}
document.getElementById('taskFilterLabel').textContent = `- ${label}`;
const active = filtered.filter(t => t.is_active).length;
const inactive = filtered.length - active;
document.getElementById('taskStats').textContent = `활성 ${active} / 비활성 ${inactive}`;
if (!filtered.length) { c.innerHTML = '<p class="text-gray-400 text-center py-8 text-sm">등록된 작업이 없습니다.</p>'; return; }
c.innerHTML = filtered.map(t => `
<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="text-sm font-medium text-gray-800 truncate">${escHtml(t.task_name)}</div>
<div class="text-xs text-gray-500 flex items-center gap-1.5 mt-0.5 flex-wrap">
${t.work_type_name ? `<span class="px-1.5 py-0.5 rounded bg-slate-50 text-slate-600">${escHtml(t.work_type_name)}</span>` : '<span class="text-gray-300 italic">미지정</span>'}
${t.description ? `<span class="text-gray-400 truncate max-w-[200px]" title="${escHtml(t.description)}">${escHtml(t.description)}</span>` : ''}
${t.is_active ? '<span class="px-1.5 py-0.5 rounded bg-emerald-50 text-emerald-600">활성</span>' : '<span class="px-1.5 py-0.5 rounded bg-gray-100 text-gray-400">비활성</span>'}
</div>
</div>
<div class="flex gap-1 ml-2 flex-shrink-0">
<button onclick="editTask(${t.task_id})" class="p-1.5 text-slate-500 hover:text-slate-700 hover:bg-slate-200 rounded" title="편집"><i class="fas fa-pen-to-square text-xs"></i></button>
<button onclick="deleteTask(${t.task_id},'${escHtml(t.task_name).replace(/'/g,"\\'")}')" class="p-1.5 text-red-400 hover:text-red-600 hover:bg-red-100 rounded" title="삭제"><i class="fas fa-trash-alt text-xs"></i></button>
</div>
</div>`).join('');
}
// 공정 모달
function openWorkTypeModal(editId) {
document.getElementById('wtEditId').value = '';
document.getElementById('workTypeForm').reset();
document.getElementById('workTypeModalTitle').textContent = '공정 추가';
if (editId) {
const wt = taskWorkTypes.find(w => w.id === editId);
if (!wt) return;
document.getElementById('workTypeModalTitle').textContent = '공정 수정';
document.getElementById('wtEditId').value = wt.id;
document.getElementById('wtName').value = wt.name || '';
document.getElementById('wtCategory').value = wt.category || '';
document.getElementById('wtDesc').value = wt.description || '';
}
document.getElementById('workTypeModal').classList.remove('hidden');
}
function closeWorkTypeModal() { document.getElementById('workTypeModal').classList.add('hidden'); }
function editWorkType(id) { openWorkTypeModal(id); }
document.getElementById('workTypeForm').addEventListener('submit', async e => {
e.preventDefault();
const editId = document.getElementById('wtEditId').value;
const body = {
name: document.getElementById('wtName').value.trim(),
category: document.getElementById('wtCategory').value.trim() || null,
description: document.getElementById('wtDesc').value.trim() || null
};
try {
if (editId) {
await api(`/tasks/work-types/${editId}`, { method: 'PUT', body: JSON.stringify(body) });
showToast('공정이 수정되었습니다.');
} else {
await api('/tasks/work-types', { method: 'POST', body: JSON.stringify(body) });
showToast('공정이 추가되었습니다.');
}
closeWorkTypeModal();
await loadWorkTypes();
await loadTasks();
} catch(e) { showToast(e.message, 'error'); }
});
// 작업 모달
function openTaskModal(editId) {
document.getElementById('taskEditId').value = '';
document.getElementById('taskForm').reset();
document.getElementById('taskActive').checked = true;
document.getElementById('taskModalTitle').textContent = '작업 추가';
populateTaskWorkTypeSelect();
// 사이드바 필터 선택된 공정 자동 선택
if (!editId && selectedTaskWorkTypeFilter && selectedTaskWorkTypeFilter !== 0) {
document.getElementById('taskWorkType').value = selectedTaskWorkTypeFilter;
}
if (editId) {
const t = allTasks.find(x => x.task_id === editId);
if (!t) return;
document.getElementById('taskModalTitle').textContent = '작업 수정';
document.getElementById('taskEditId').value = t.task_id;
document.getElementById('taskName').value = t.task_name || '';
document.getElementById('taskWorkType').value = t.work_type_id || '';
document.getElementById('taskDesc').value = t.description || '';
document.getElementById('taskActive').checked = !!t.is_active;
}
document.getElementById('taskModal').classList.remove('hidden');
}
function closeTaskModal() { document.getElementById('taskModal').classList.add('hidden'); }
function editTask(id) { openTaskModal(id); }
document.getElementById('taskForm').addEventListener('submit', async e => {
e.preventDefault();
const editId = document.getElementById('taskEditId').value;
const body = {
task_name: document.getElementById('taskName').value.trim(),
work_type_id: document.getElementById('taskWorkType').value ? parseInt(document.getElementById('taskWorkType').value) : null,
description: document.getElementById('taskDesc').value.trim() || null,
is_active: document.getElementById('taskActive').checked ? 1 : 0
};
try {
if (editId) {
await api(`/tasks/${editId}`, { method: 'PUT', body: JSON.stringify(body) });
showToast('작업이 수정되었습니다.');
} else {
await api('/tasks', { method: 'POST', body: JSON.stringify(body) });
showToast('작업이 추가되었습니다.');
}
closeTaskModal();
await loadTasks();
} catch(e) { showToast(e.message, 'error'); }
});
async function deleteTask(id, name) {
if (!confirm(`"${name}" 작업을 삭제하시겠습니까?`)) return;
try {
await api(`/tasks/${id}`, { method: 'DELETE' });
showToast('작업이 삭제되었습니다.');
await loadTasks();
} catch(e) { showToast(e.message, 'error'); }
}