// 작업자 관리 페이지 JavaScript (부서 기반) // 전역 변수 let departments = []; let currentDepartmentId = null; let allWorkers = []; let filteredWorkers = []; let currentEditingWorker = null; // 페이지 초기화 document.addEventListener('DOMContentLoaded', async () => { console.log('👥 작업자 관리 페이지 초기화 시작'); await waitForApiConfig(); await loadDepartments(); }); // API 설정 로드 대기 async function waitForApiConfig() { let retryCount = 0; while (!window.apiCall && retryCount < 50) { await new Promise(resolve => setTimeout(resolve, 100)); retryCount++; } if (!window.apiCall) { console.error('API 설정 로드 실패'); } } // ============================================ // 부서 관련 함수 // ============================================ // 부서 목록 로드 async function loadDepartments() { try { const response = await window.apiCall('/departments'); const result = response; if (result && result.success) { departments = result.data; renderDepartmentList(); updateParentDepartmentSelect(); console.log('✅ 부서 목록 로드 완료:', departments.length + '개'); } else if (Array.isArray(result)) { departments = result; renderDepartmentList(); updateParentDepartmentSelect(); } } catch (error) { console.error('부서 목록 로드 실패:', error); showToast('부서 목록을 불러오는데 실패했습니다.', 'error'); } } // 부서 목록 렌더링 function renderDepartmentList() { const container = document.getElementById('departmentList'); if (!container) return; if (departments.length === 0) { container.innerHTML = `
등록된 부서가 없습니다.
`; return; } container.innerHTML = departments.map(dept => `
${dept.department_name} ${dept.worker_count || 0}명
`).join(''); } // 부서 선택 async function selectDepartment(departmentId) { currentDepartmentId = departmentId; renderDepartmentList(); const dept = departments.find(d => d.department_id === departmentId); document.getElementById('workerListTitle').textContent = `${dept.department_name} 작업자`; document.getElementById('addWorkerBtn').style.display = 'inline-flex'; document.getElementById('workerToolbar').style.display = 'flex'; await loadWorkersByDepartment(departmentId); } // 상위 부서 선택 옵션 업데이트 function updateParentDepartmentSelect() { const select = document.getElementById('parentDepartment'); if (!select) return; const currentId = document.getElementById('departmentId')?.value; select.innerHTML = '' + departments .filter(d => d.department_id !== parseInt(currentId)) .map(d => ``) .join(''); } // 부서 모달 열기 function openDepartmentModal(departmentId = null) { const modal = document.getElementById('departmentModal'); const title = document.getElementById('departmentModalTitle'); const form = document.getElementById('departmentForm'); const deleteBtn = document.getElementById('deleteDeptBtn'); updateParentDepartmentSelect(); if (departmentId) { const dept = departments.find(d => d.department_id === departmentId); title.textContent = '부서 수정'; deleteBtn.style.display = 'inline-flex'; document.getElementById('departmentId').value = dept.department_id; document.getElementById('departmentName').value = dept.department_name; document.getElementById('parentDepartment').value = dept.parent_id || ''; document.getElementById('departmentDescription').value = dept.description || ''; document.getElementById('displayOrder').value = dept.display_order || 0; document.getElementById('isActiveDept').checked = dept.is_active !== 0 && dept.is_active !== false; } else { title.textContent = '새 부서 등록'; deleteBtn.style.display = 'none'; form.reset(); document.getElementById('departmentId').value = ''; document.getElementById('isActiveDept').checked = true; } modal.style.display = 'flex'; document.body.style.overflow = 'hidden'; setTimeout(() => { document.getElementById('departmentName').focus(); }, 100); } // 부서 모달 닫기 function closeDepartmentModal() { const modal = document.getElementById('departmentModal'); if (modal) { modal.style.display = 'none'; document.body.style.overflow = ''; } } // 부서 저장 async function saveDepartment() { const departmentId = document.getElementById('departmentId').value; const data = { department_name: document.getElementById('departmentName').value.trim(), parent_id: document.getElementById('parentDepartment').value || null, description: document.getElementById('departmentDescription').value.trim(), display_order: parseInt(document.getElementById('displayOrder').value) || 0, is_active: document.getElementById('isActiveDept').checked }; if (!data.department_name) { showToast('부서명은 필수 입력 항목입니다.', 'error'); return; } try { const url = departmentId ? `/departments/${departmentId}` : '/departments'; const method = departmentId ? 'PUT' : 'POST'; const response = await window.apiCall(url, method, data); if (response && response.success) { showToast(response.message || '부서가 저장되었습니다.', 'success'); closeDepartmentModal(); await loadDepartments(); } else { throw new Error(response?.error || '저장 실패'); } } catch (error) { console.error('부서 저장 실패:', error); showToast('부서 저장에 실패했습니다.', 'error'); } } // 부서 수정 function editDepartment(departmentId) { openDepartmentModal(departmentId); } // 부서 삭제 확인 function confirmDeleteDepartment(departmentId) { const dept = departments.find(d => d.department_id === departmentId); if (!dept) return; const workerCount = dept.worker_count || 0; let message = `"${dept.department_name}" 부서를 삭제하시겠습니까?`; if (workerCount > 0) { message += `\n\n⚠️ 이 부서에는 ${workerCount}명의 작업자가 있습니다.\n삭제하면 작업자들의 부서 정보가 제거됩니다.`; } if (confirm(message)) { deleteDepartment(departmentId); } } // 부서 삭제 async function deleteDepartment(departmentId = null) { const id = departmentId || document.getElementById('departmentId').value; if (!id) return; try { const response = await window.apiCall(`/departments/${id}`, 'DELETE'); if (response && response.success) { showToast('부서가 삭제되었습니다.', 'success'); closeDepartmentModal(); if (currentDepartmentId === parseInt(id)) { currentDepartmentId = null; document.getElementById('workerListTitle').textContent = '부서를 선택하세요'; document.getElementById('addWorkerBtn').style.display = 'none'; document.getElementById('workerToolbar').style.display = 'none'; document.getElementById('workerList').innerHTML = `

부서를 선택해주세요

왼쪽에서 부서를 선택하면 해당 부서의 작업자가 표시됩니다.

`; } await loadDepartments(); } else { throw new Error(response?.error || '삭제 실패'); } } catch (error) { console.error('부서 삭제 실패:', error); showToast(error.message || '부서 삭제에 실패했습니다.', 'error'); } } // ============================================ // 작업자 관련 함수 // ============================================ // 부서별 작업자 로드 async function loadWorkersByDepartment(departmentId) { try { const response = await window.apiCall(`/departments/${departmentId}/workers`); if (response && response.success) { allWorkers = response.data; filteredWorkers = [...allWorkers]; renderWorkerList(); console.log(`✅ ${departmentId} 부서 작업자 로드: ${allWorkers.length}명`); } else if (Array.isArray(response)) { allWorkers = response; filteredWorkers = [...allWorkers]; renderWorkerList(); } } catch (error) { console.error('작업자 목록 로드 실패:', error); showToast('작업자 목록을 불러오는데 실패했습니다.', 'error'); } } // 작업자 목록 렌더링 function renderWorkerList() { const container = document.getElementById('workerList'); if (!container) return; if (filteredWorkers.length === 0) { container.innerHTML = `

작업자가 없습니다

"+ 작업자 추가" 버튼을 눌러 작업자를 등록하세요.

`; return; } const tableHtml = ` ${filteredWorkers.map(worker => { const jobTypeMap = { 'worker': '작업자', 'leader': '그룹장', 'admin': '관리자' }; const jobType = jobTypeMap[worker.job_type] || worker.job_type || '-'; const isInactive = worker.status === 'inactive'; const isResigned = worker.employment_status === 'resigned'; const hasAccount = worker.user_id !== null && worker.user_id !== undefined; let statusClass = 'active'; let statusText = '현장직'; if (isResigned) { statusClass = 'resigned'; statusText = '퇴사'; } else if (isInactive) { statusClass = 'inactive'; statusText = '사무직'; } return ` `; }).join('')}
이름 직책 상태 입사일 계정 관리
${worker.worker_name.charAt(0)}
${worker.worker_name}
${jobType} ${statusText} ${worker.join_date ? formatDate(worker.join_date) : '-'}
`; container.innerHTML = tableHtml; } // 작업자 필터링 function filterWorkers() { const searchTerm = document.getElementById('workerSearch')?.value.toLowerCase().trim() || ''; const statusValue = document.getElementById('statusFilter')?.value || ''; filteredWorkers = allWorkers.filter(worker => { // 검색 필터 const matchesSearch = !searchTerm || worker.worker_name.toLowerCase().includes(searchTerm) || (worker.job_type && worker.job_type.toLowerCase().includes(searchTerm)); // 상태 필터 let matchesStatus = true; if (statusValue === 'active') { matchesStatus = worker.status !== 'inactive' && worker.employment_status !== 'resigned'; } else if (statusValue === 'inactive') { matchesStatus = worker.status === 'inactive'; } else if (statusValue === 'resigned') { matchesStatus = worker.employment_status === 'resigned'; } return matchesSearch && matchesStatus; }); renderWorkerList(); } // 작업자 모달 열기 function openWorkerModal(workerId = null) { const modal = document.getElementById('workerModal'); const title = document.getElementById('workerModalTitle'); const deleteBtn = document.getElementById('deleteWorkerBtn'); if (!currentDepartmentId) { showToast('먼저 부서를 선택해주세요.', 'error'); return; } if (workerId) { const worker = allWorkers.find(w => w.worker_id === workerId); if (!worker) { showToast('작업자를 찾을 수 없습니다.', 'error'); return; } currentEditingWorker = worker; title.textContent = '작업자 정보 수정'; deleteBtn.style.display = 'inline-flex'; document.getElementById('workerId').value = worker.worker_id; document.getElementById('workerName').value = worker.worker_name || ''; document.getElementById('jobType').value = worker.job_type || 'worker'; document.getElementById('joinDate').value = worker.join_date ? worker.join_date.split('T')[0] : ''; document.getElementById('salary').value = worker.salary || ''; document.getElementById('annualLeave').value = worker.annual_leave || 0; document.getElementById('isActiveWorker').checked = worker.status !== 'inactive'; document.getElementById('createAccount').checked = worker.user_id !== null && worker.user_id !== undefined; document.getElementById('isResigned').checked = worker.employment_status === 'resigned'; } else { currentEditingWorker = null; title.textContent = '새 작업자 등록'; deleteBtn.style.display = 'none'; document.getElementById('workerForm').reset(); document.getElementById('workerId').value = ''; document.getElementById('isActiveWorker').checked = true; document.getElementById('createAccount').checked = false; document.getElementById('isResigned').checked = false; } modal.style.display = 'flex'; document.body.style.overflow = 'hidden'; setTimeout(() => { document.getElementById('workerName').focus(); }, 100); } // 작업자 모달 닫기 function closeWorkerModal() { const modal = document.getElementById('workerModal'); if (modal) { modal.style.display = 'none'; document.body.style.overflow = ''; currentEditingWorker = null; } } // 작업자 편집 function editWorker(workerId) { openWorkerModal(workerId); } // 작업자 저장 async function saveWorker() { const workerId = document.getElementById('workerId').value; const workerData = { worker_name: document.getElementById('workerName').value.trim(), job_type: document.getElementById('jobType').value || 'worker', join_date: document.getElementById('joinDate').value || null, salary: document.getElementById('salary').value || null, annual_leave: document.getElementById('annualLeave').value || 0, status: document.getElementById('isActiveWorker').checked ? 'active' : 'inactive', employment_status: document.getElementById('isResigned').checked ? 'resigned' : 'employed', create_account: document.getElementById('createAccount').checked, department_id: currentDepartmentId }; if (!workerData.worker_name) { showToast('작업자명은 필수 입력 항목입니다.', 'error'); return; } try { let response; if (workerId) { response = await window.apiCall(`/workers/${workerId}`, 'PUT', workerData); } else { response = await window.apiCall('/workers', 'POST', workerData); } if (response && (response.success || response.data)) { const action = workerId ? '수정' : '등록'; showToast(`작업자가 성공적으로 ${action}되었습니다.`, 'success'); closeWorkerModal(); await loadDepartments(); await loadWorkersByDepartment(currentDepartmentId); } else { throw new Error(response?.message || '저장에 실패했습니다.'); } } catch (error) { console.error('작업자 저장 오류:', error); showToast(error.message || '작업자 저장 중 오류가 발생했습니다.', 'error'); } } // 작업자 삭제 확인 function confirmDeleteWorker(workerId) { const worker = allWorkers.find(w => w.worker_id === workerId); if (!worker) { showToast('작업자를 찾을 수 없습니다.', 'error'); return; } const confirmMessage = `"${worker.worker_name}" 작업자를 삭제하시겠습니까?\n\n⚠️ 관련된 모든 데이터(작업보고서, 이슈 등)가 함께 삭제됩니다.`; if (confirm(confirmMessage)) { deleteWorkerById(workerId); } } // 작업자 삭제 (모달에서) function deleteWorker() { if (currentEditingWorker) { confirmDeleteWorker(currentEditingWorker.worker_id); } } // 작업자 삭제 실행 async function deleteWorkerById(workerId) { try { const response = await window.apiCall(`/workers/${workerId}`, 'DELETE'); if (response && (response.success || response.message)) { showToast('작업자가 삭제되었습니다.', 'success'); closeWorkerModal(); await loadDepartments(); await loadWorkersByDepartment(currentDepartmentId); } else { throw new Error(response?.error || '삭제 실패'); } } catch (error) { console.error('작업자 삭제 오류:', error); showToast(error.message || '작업자 삭제에 실패했습니다.', 'error'); } } // ============================================ // 유틸리티 함수 // ============================================ // 날짜 포맷팅 function formatDate(dateString) { if (!dateString) return ''; const date = new Date(dateString); return date.toLocaleDateString('ko-KR', { year: 'numeric', month: '2-digit', day: '2-digit' }); } // 토스트 메시지 표시 function showToast(message, type = 'info') { const existingToast = document.querySelector('.toast'); if (existingToast) { existingToast.remove(); } const toast = document.createElement('div'); toast.className = `toast toast-${type}`; toast.textContent = message; Object.assign(toast.style, { position: 'fixed', top: '20px', right: '20px', padding: '12px 24px', borderRadius: '8px', color: 'white', fontWeight: '500', zIndex: '10000', transform: 'translateX(100%)', transition: 'transform 0.3s ease' }); const colors = { success: '#10b981', error: '#ef4444', warning: '#f59e0b', info: '#3b82f6' }; toast.style.backgroundColor = colors[type] || colors.info; document.body.appendChild(toast); setTimeout(() => { toast.style.transform = 'translateX(0)'; }, 100); setTimeout(() => { toast.style.transform = 'translateX(100%)'; setTimeout(() => { if (toast.parentNode) { toast.remove(); } }, 300); }, 3000); } // ============================================ // 전역 함수 노출 // ============================================ window.openDepartmentModal = openDepartmentModal; window.closeDepartmentModal = closeDepartmentModal; window.saveDepartment = saveDepartment; window.editDepartment = editDepartment; window.deleteDepartment = deleteDepartment; window.confirmDeleteDepartment = confirmDeleteDepartment; window.selectDepartment = selectDepartment; window.openWorkerModal = openWorkerModal; window.closeWorkerModal = closeWorkerModal; window.saveWorker = saveWorker; window.editWorker = editWorker; window.deleteWorker = deleteWorker; window.confirmDeleteWorker = confirmDeleteWorker; window.filterWorkers = filterWorkers;