// 작업자 관리 페이지 JavaScript // 전역 변수 let allWorkers = []; let filteredWorkers = []; let currentEditingWorker = null; let currentStatusFilter = 'all'; // 'all', 'active', 'inactive' // 페이지 초기화 document.addEventListener('DOMContentLoaded', function() { console.log('👥 작업자 관리 페이지 초기화 시작'); initializePage(); loadWorkers(); }); // 페이지 초기화 function initializePage() { // 시간 업데이트 시작 updateCurrentTime(); setInterval(updateCurrentTime, 1000); // 사용자 정보 업데이트 updateUserInfo(); // 프로필 메뉴 토글 setupProfileMenu(); // 로그아웃 버튼 setupLogoutButton(); // 검색 입력 이벤트 setupSearchInput(); } // 현재 시간 업데이트 function updateCurrentTime() { const now = new Date(); const timeString = now.toLocaleTimeString('ko-KR', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' }); const timeElement = document.getElementById('timeValue'); if (timeElement) { timeElement.textContent = timeString; } } // 사용자 정보 업데이트 function updateUserInfo() { let userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}'); let authUser = JSON.parse(localStorage.getItem('user') || '{}'); const finalUserInfo = { worker_name: userInfo.worker_name || authUser.username || authUser.worker_name, job_type: userInfo.job_type || authUser.role || authUser.job_type, username: authUser.username || userInfo.username }; const userNameElement = document.getElementById('userName'); const userRoleElement = document.getElementById('userRole'); const userInitialElement = document.getElementById('userInitial'); if (userNameElement) { userNameElement.textContent = finalUserInfo.worker_name || '사용자'; } if (userRoleElement) { const roleMap = { 'leader': '그룹장', 'worker': '작업자', 'admin': '관리자', 'system': '시스템 관리자' }; userRoleElement.textContent = roleMap[finalUserInfo.job_type] || finalUserInfo.job_type || '작업자'; } if (userInitialElement) { const name = finalUserInfo.worker_name || '사용자'; userInitialElement.textContent = name.charAt(0); } } // 프로필 메뉴 설정 function setupProfileMenu() { const userProfile = document.getElementById('userProfile'); const profileMenu = document.getElementById('profileMenu'); if (userProfile && profileMenu) { userProfile.addEventListener('click', function(e) { e.stopPropagation(); const isVisible = profileMenu.style.display === 'block'; profileMenu.style.display = isVisible ? 'none' : 'block'; }); // 외부 클릭 시 메뉴 닫기 document.addEventListener('click', function() { profileMenu.style.display = 'none'; }); } } // 로그아웃 버튼 설정 function setupLogoutButton() { const logoutBtn = document.getElementById('logoutBtn'); if (logoutBtn) { logoutBtn.addEventListener('click', function() { if (confirm('로그아웃 하시겠습니까?')) { localStorage.removeItem('token'); localStorage.removeItem('user'); localStorage.removeItem('userInfo'); window.location.href = '/index.html'; } }); } } // 검색 입력 설정 function setupSearchInput() { const searchInput = document.getElementById('searchInput'); if (searchInput) { searchInput.addEventListener('input', function() { searchWorkers(); }); searchInput.addEventListener('keypress', function(e) { if (e.key === 'Enter') { searchWorkers(); } }); } } // 작업자 목록 로드 async function loadWorkers() { try { console.log('📊 작업자 목록 로딩 시작'); const response = await apiCall('/workers?limit=1000', 'GET'); // 모든 작업자 조회 console.log('📊 API 응답 구조:', response); // API 응답이 { success: true, data: [...] } 형태인 경우 처리 let workerData = []; if (response && response.success && Array.isArray(response.data)) { workerData = response.data; } else if (Array.isArray(response)) { workerData = response; } else { console.warn('작업자 데이터가 배열이 아닙니다:', response); workerData = []; } allWorkers = workerData; console.log(`✅ 작업자 ${allWorkers.length}명 로드 완료`); // 초기 필터 적용 applyAllFilters(); updateStatCardActiveState(); } catch (error) { console.error('작업자 로딩 오류:', error); showToast('작업자 목록을 불러오는데 실패했습니다.', 'error'); allWorkers = []; filteredWorkers = []; renderWorkers(); } } // 작업자 목록 렌더링 function renderWorkers() { const workersGrid = document.getElementById('workersGrid'); const emptyState = document.getElementById('emptyState'); if (!workersGrid || !emptyState) return; if (filteredWorkers.length === 0) { workersGrid.style.display = 'none'; emptyState.style.display = 'block'; return; } workersGrid.style.display = 'grid'; emptyState.style.display = 'none'; const workersHtml = filteredWorkers.map(worker => { // 작업자 상태 및 직책 아이콘 const jobTypeMap = { 'worker': { icon: '👷', text: '작업자', color: '#6b7280' }, 'leader': { icon: '👨‍💼', text: '그룹장', color: '#3b82f6' }, 'admin': { icon: '👨‍💻', text: '관리자', color: '#8b5cf6' } }; const jobType = jobTypeMap[worker.job_type] || jobTypeMap['worker']; const isInactive = worker.status === 'inactive' || worker.is_active === 0 || worker.is_active === false; const isResigned = worker.employment_status === 'resigned'; const hasAccount = worker.user_id !== null && worker.user_id !== undefined; console.log('🎨 카드 렌더링:', { worker_id: worker.worker_id, worker_name: worker.worker_name, status: worker.status, is_active: worker.is_active, isInactive: isInactive, isResigned: isResigned, user_id: worker.user_id, hasAccount: hasAccount }); return `
${isResigned ? '
🚪 퇴사
' : isInactive ? '
🏢 사무직
' : ''}
${worker.worker_name.charAt(0)}

${worker.worker_name} ${hasAccount ? '🔐' : ''} ${isResigned ? '(퇴사)' : isInactive ? '(사무직)' : ''}

${jobType.icon} ${jobType.text} ${hasAccount ? '🔐 계정 연동됨' : '⚪ 계정 없음'} ${worker.phone_number ? `📞 ${worker.phone_number}` : ''} ${worker.email ? `📧 ${worker.email}` : ''} ${worker.department ? `🏢 ${worker.department}` : ''} ${worker.hire_date ? `📅 입사: ${formatDate(worker.hire_date)}` : ''} ${isResigned ? '⚠️ 퇴사 처리됨' : ''}
`; }).join(''); workersGrid.innerHTML = workersHtml; } // 작업자 통계 업데이트 function updateWorkerStats() { const activeWorkers = filteredWorkers.filter(w => w.status !== 'inactive' && w.is_active !== 0 && w.is_active !== false); const inactiveWorkers = filteredWorkers.filter(w => w.status === 'inactive' || w.is_active === 0 || w.is_active === false); const activeWorkersElement = document.getElementById('activeWorkers'); const inactiveWorkersElement = document.getElementById('inactiveWorkers'); const totalWorkersElement = document.getElementById('totalWorkers'); if (activeWorkersElement) { activeWorkersElement.textContent = activeWorkers.length; } if (inactiveWorkersElement) { inactiveWorkersElement.textContent = inactiveWorkers.length; } if (totalWorkersElement) { totalWorkersElement.textContent = filteredWorkers.length; } console.log('📊 작업자 통계:', { 전체: filteredWorkers.length, 활성: activeWorkers.length, 비활성: inactiveWorkers.length }); } // 날짜 포맷팅 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 filterByStatus(status) { currentStatusFilter = status; // 통계 카드 활성화 상태 업데이트 updateStatCardActiveState(); // 필터링 적용 applyAllFilters(); console.log(`🔍 상태 필터 적용: ${status}`); } // 통계 카드 활성화 상태 업데이트 function updateStatCardActiveState() { // 모든 통계 카드에서 active 클래스 제거 document.querySelectorAll('.stat-item').forEach(item => { item.classList.remove('active'); }); // 현재 선택된 필터에 active 클래스 추가 const activeCard = document.querySelector(`.${currentStatusFilter === 'active' ? 'active-stat' : currentStatusFilter === 'inactive' ? 'inactive-stat' : 'total-stat'}`); if (activeCard) { activeCard.classList.add('active'); } } // 모든 필터 적용 (검색 + 상태 + 직책) function applyAllFilters() { const searchInput = document.getElementById('searchInput'); const jobTypeFilter = document.getElementById('jobTypeFilter'); const statusFilter = document.getElementById('statusFilter'); const searchTerm = searchInput ? searchInput.value.toLowerCase().trim() : ''; const jobTypeValue = jobTypeFilter ? jobTypeFilter.value : ''; const statusValue = statusFilter ? statusFilter.value : ''; // 1단계: 상태 필터링 (통계 카드 클릭) let statusFiltered = [...allWorkers]; if (currentStatusFilter === 'active') { statusFiltered = allWorkers.filter(w => w.status !== 'inactive' && w.is_active !== 0 && w.is_active !== false); } else if (currentStatusFilter === 'inactive') { statusFiltered = allWorkers.filter(w => w.status === 'inactive' || w.is_active === 0 || w.is_active === false); } // 2단계: 드롭다운 상태 필터링 if (statusValue) { if (statusValue === 'active') { statusFiltered = statusFiltered.filter(w => w.status !== 'inactive' && w.is_active !== 0 && w.is_active !== false); } else if (statusValue === 'inactive') { statusFiltered = statusFiltered.filter(w => w.status === 'inactive' || w.is_active === 0 || w.is_active === false); } } // 3단계: 직책 필터링 let jobTypeFiltered = statusFiltered; if (jobTypeValue) { jobTypeFiltered = statusFiltered.filter(w => w.job_type === jobTypeValue); } // 4단계: 검색 필터링 if (!searchTerm) { filteredWorkers = jobTypeFiltered; } else { filteredWorkers = jobTypeFiltered.filter(worker => worker.worker_name.toLowerCase().includes(searchTerm) || (worker.phone_number && worker.phone_number.toLowerCase().includes(searchTerm)) || (worker.email && worker.email.toLowerCase().includes(searchTerm)) || (worker.department && worker.department.toLowerCase().includes(searchTerm)) ); } renderWorkers(); updateWorkerStats(); } // 작업자 검색 function searchWorkers() { applyAllFilters(); } // 작업자 필터링 function filterWorkers() { applyAllFilters(); } // 작업자 정렬 function sortWorkers() { const sortBy = document.getElementById('sortBy'); const sortField = sortBy ? sortBy.value : 'created_at'; filteredWorkers.sort((a, b) => { switch (sortField) { case 'worker_name': return a.worker_name.localeCompare(b.worker_name); case 'job_type': const jobOrder = { 'admin': 0, 'leader': 1, 'worker': 2 }; return (jobOrder[a.job_type] || 3) - (jobOrder[b.job_type] || 3); case 'created_at': default: return new Date(b.created_at || 0) - new Date(a.created_at || 0); } }); renderWorkers(); } // 작업자 목록 새로고침 async function refreshWorkerList() { const refreshBtn = document.querySelector('.btn-secondary'); if (refreshBtn) { const originalText = refreshBtn.innerHTML; refreshBtn.innerHTML = '새로고침 중...'; refreshBtn.disabled = true; await loadWorkers(); refreshBtn.innerHTML = originalText; refreshBtn.disabled = false; } else { await loadWorkers(); } showToast('작업자 목록이 새로고침되었습니다.', 'success'); } // 작업자 모달 열기 function openWorkerModal(worker = null) { const modal = document.getElementById('workerModal'); const modalTitle = document.getElementById('modalTitle'); const deleteBtn = document.getElementById('deleteWorkerBtn'); if (!modal) return; currentEditingWorker = worker; if (worker) { // 수정 모드 modalTitle.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 || ''; // 옵션 필드들 - 존재하는 경우에만 설정 const joinDateElem = document.getElementById('joinDate'); if (joinDateElem) joinDateElem.value = worker.join_date || ''; const salaryElem = document.getElementById('salary'); if (salaryElem) salaryElem.value = worker.salary || ''; const annualLeaveElem = document.getElementById('annualLeave'); if (annualLeaveElem) annualLeaveElem.value = worker.annual_leave || ''; // is_active 값 처리 (DB에서 0/1로 오는 경우 대비) const isActiveValue = worker.status !== 'inactive' && worker.is_active !== 0 && worker.is_active !== false; document.getElementById('isActive').checked = isActiveValue; // 계정 연동 여부 확인 (user_id가 있으면 계정 있음) const hasAccountValue = worker.user_id !== null && worker.user_id !== undefined; document.getElementById('hasAccount').checked = hasAccountValue; // employment_status 값 처리 (resigned이면 체크) const isResignedValue = worker.employment_status === 'resigned'; document.getElementById('isResigned').checked = isResignedValue; console.log('🔧 작업자 로드:', { worker_id: worker.worker_id, worker_name: worker.worker_name, job_type: worker.job_type, status: worker.status, is_active_raw: worker.is_active, is_active_processed: isActiveValue, user_id: worker.user_id, has_account: hasAccountValue, employment_status: worker.employment_status, is_resigned: isResignedValue }); } else { // 신규 등록 모드 modalTitle.textContent = '새 작업자 등록'; deleteBtn.style.display = 'none'; // 폼 초기화 document.getElementById('workerForm').reset(); document.getElementById('workerId').value = ''; document.getElementById('isActive').checked = true; document.getElementById('hasAccount').checked = false; document.getElementById('isResigned').checked = false; } modal.style.display = 'flex'; document.body.style.overflow = 'hidden'; // 첫 번째 입력 필드에 포커스 setTimeout(() => { const firstInput = document.getElementById('workerName'); if (firstInput) firstInput.focus(); }, 100); } // 작업자 모달 닫기 function closeWorkerModal() { const modal = document.getElementById('workerModal'); if (modal) { modal.style.display = 'none'; document.body.style.overflow = ''; currentEditingWorker = null; } } // 작업자 편집 function editWorker(workerId) { const worker = allWorkers.find(w => w.worker_id === workerId); if (worker) { openWorkerModal(worker); } else { showToast('작업자를 찾을 수 없습니다.', 'error'); } } // 작업자 저장 async function saveWorker() { try { const form = document.getElementById('workerForm'); // 실제 테이블 구조에 맞는 필드만 사용 const workerData = { worker_name: document.getElementById('workerName').value.trim(), job_type: document.getElementById('jobType').value || null, join_date: document.getElementById('joinDate')?.value || null, salary: document.getElementById('salary')?.value || null, annual_leave: document.getElementById('annualLeave')?.value || null, status: document.getElementById('isActive').checked ? 'active' : 'inactive', employment_status: document.getElementById('isResigned').checked ? 'resigned' : 'employed', create_account: document.getElementById('hasAccount').checked // 계정 생성 여부 }; console.log('💾 저장할 작업자 데이터:', JSON.stringify(workerData, null, 2)); // 필수 필드 검증 if (!workerData.worker_name) { showToast('작업자명은 필수 입력 항목입니다.', 'error'); return; } const workerId = document.getElementById('workerId').value; let response; if (workerId) { // 수정 response = await apiCall(`/workers/${workerId}`, 'PUT', workerData); } else { // 신규 등록 response = await apiCall('/workers', 'POST', workerData); } if (response && (response.success || response.worker_id)) { const action = workerId ? '수정' : '등록'; showToast(`작업자가 성공적으로 ${action}되었습니다.`, 'success'); closeWorkerModal(); await loadWorkers(); } else { throw new Error(response?.message || '저장에 실패했습니다.'); } } catch (error) { console.error('작업자 저장 오류:', error); showToast(error.message || '작업자 저장 중 오류가 발생했습니다.', 'error'); } } // 작업자 상태 토글 (활성화/비활성화) async function toggleWorkerStatus(workerId) { const worker = allWorkers.find(w => w.worker_id === workerId); if (!worker) { showToast('작업자를 찾을 수 없습니다.', 'error'); return; } const isCurrentlyInactive = worker.status === 'inactive' || worker.is_active === 0 || worker.is_active === false; const newStatus = isCurrentlyInactive ? 'active' : 'inactive'; const actionText = isCurrentlyInactive ? '활성화' : '비활성화'; if (!confirm(`"${worker.worker_name}" 작업자를 ${actionText}하시겠습니까?`)) { return; } console.log(`🔄 작업자 상태 변경: ${worker.worker_name} → ${newStatus}`); try { // 실제 테이블 구조에 맞는 필드만 전송 const updateData = { worker_name: worker.worker_name, job_type: worker.job_type || null, status: newStatus, join_date: worker.join_date || null, salary: worker.salary || null, annual_leave: worker.annual_leave || null, employment_status: worker.employment_status || 'employed' }; console.log('📤 전송 데이터:', JSON.stringify(updateData, null, 2)); const response = await window.apiCall(`/workers/${workerId}`, 'PUT', updateData); if (response) { // 로컬 데이터 업데이트 const workerIndex = allWorkers.findIndex(w => w.worker_id === workerId); if (workerIndex !== -1) { allWorkers[workerIndex].status = newStatus; allWorkers[workerIndex].is_active = newStatus === 'active' ? 1 : 0; } // UI 새로고침 applyAllFilters(); updateWorkerStats(); showToast(`${worker.worker_name} 작업자가 ${actionText}되었습니다.`, 'success'); console.log(`✅ 작업자 상태 변경 완료: ${worker.worker_name} → ${newStatus}`); } } catch (error) { console.error('작업자 상태 변경 오류:', error); showToast(`작업자 상태 변경에 실패했습니다: ${error.message}`, 'error'); } } // 작업자 삭제 확인 function confirmDeleteWorker(workerId) { console.log('🔍 삭제 요청된 작업자 ID:', workerId); const worker = allWorkers.find(w => w.worker_id === workerId); if (!worker) { console.error('❌ 작업자를 찾을 수 없음:', workerId); showToast('작업자를 찾을 수 없습니다.', 'error'); return; } console.log('👤 삭제 대상 작업자:', { worker_id: worker.worker_id, worker_name: worker.worker_name, job_type: worker.job_type }); // 더 명확한 확인 메시지 const confirmMessage = `⚠️ 작업자 삭제 확인 ⚠️ 삭제할 작업자: ${worker.worker_name} (ID: ${worker.worker_id}) 직책: ${worker.job_type || '미지정'} 정말로 이 작업자를 삭제하시겠습니까? ⚠️ 주의: 삭제된 작업자와 관련된 모든 데이터가 함께 삭제됩니다. - 작업 보고서 - 이슈 보고서 - 월별 통계 - 그룹 소속 정보 이 작업은 되돌릴 수 없습니다!`; if (confirm(confirmMessage)) { console.log('✅ 사용자가 삭제를 확인함'); deleteWorkerById(workerId); } else { console.log('❌ 사용자가 삭제를 취소함'); } } // 작업자 삭제 (수정 모드에서) function deleteWorker() { if (currentEditingWorker) { confirmDeleteWorker(currentEditingWorker.worker_id); } } // 작업자 삭제 실행 async function deleteWorkerById(workerId) { console.log('🗑️ 작업자 삭제 실행 시작:', workerId); try { const worker = allWorkers.find(w => w.worker_id === workerId); console.log('🔍 삭제 실행 전 작업자 정보:', worker); const response = await window.apiCall(`${window.API}/workers/${workerId}`, 'DELETE'); console.log('📡 삭제 API 응답:', response); if (response && (response.success || response.message)) { console.log('✅ 작업자 삭제 성공'); showToast(`작업자 "${worker?.worker_name || workerId}"가 성공적으로 삭제되었습니다.`, 'success'); closeWorkerModal(); await loadWorkers(); } else { throw new Error(response?.message || '삭제에 실패했습니다.'); } } catch (error) { console.error('❌ 작업자 삭제 오류:', error); showToast(error.message || '작업자 삭제 중 오류가 발생했습니다.', 'error'); } } // 토스트 메시지 표시 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: '1000', 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.openWorkerModal = openWorkerModal; window.closeWorkerModal = closeWorkerModal; window.editWorker = editWorker; window.saveWorker = saveWorker; window.deleteWorker = deleteWorker; window.confirmDeleteWorker = confirmDeleteWorker; window.searchWorkers = searchWorkers; window.filterWorkers = filterWorkers; window.sortWorkers = sortWorkers; window.refreshWorkerList = refreshWorkerList; window.filterByStatus = filterByStatus; window.toggleWorkerStatus = toggleWorkerStatus;