// management-dashboard.js - 관리자 대시보드 전용 스크립트 // ================================================================= // 🌐 통합 API 설정 import // ================================================================= import { API, getAuthHeaders, apiCall } from '/js/api-config.js'; // 전역 변수 let workers = []; let workData = []; let filteredWorkData = []; let currentDate = ''; let currentUser = null; // 권한 레벨 매핑 const ACCESS_LEVELS = { worker: 1, group_leader: 2, support_team: 3, admin: 4, system: 5 }; // 한국 시간 기준 오늘 날짜 가져오기 function getKoreaToday() { const today = new Date(); const year = today.getFullYear(); const month = String(today.getMonth() + 1).padStart(2, '0'); const day = String(today.getDate()).padStart(2, '0'); return `${year}-${month}-${day}`; } // 현재 로그인한 사용자 정보 가져오기 function getCurrentUser() { try { const token = localStorage.getItem('sso_token'); if (!token) return null; const payloadBase64 = token.split('.')[1]; if (payloadBase64) { const payload = JSON.parse(atob(payloadBase64)); return payload; } } catch (error) { } try { const userInfo = localStorage.getItem('sso_user'); if (userInfo) { const parsed = JSON.parse(userInfo); return parsed; } } catch (error) { } return null; } // 권한 체크 함수 function checkPermission() { currentUser = getCurrentUser(); if (!currentUser) { showMessage('로그인이 필요합니다.', 'error'); setTimeout(() => { window.location.href = '/'; }, 2000); return false; } const userAccessLevel = currentUser.access_level; const accessLevelValue = ACCESS_LEVELS[userAccessLevel] || 0; console.log('사용자 권한 체크:', { username: currentUser.username || currentUser.name, access_level: userAccessLevel, level_value: accessLevelValue, required_level: ACCESS_LEVELS.group_leader }); if (accessLevelValue < ACCESS_LEVELS.group_leader) { showMessage('그룹장 이상의 권한이 필요합니다. 현재 권한: ' + userAccessLevel, 'error'); setTimeout(() => { window.location.href = '/'; }, 3000); return false; } return true; } // 메시지 표시 function showMessage(message, type = 'info') { const container = document.getElementById('message-container'); container.innerHTML = `
`; if (type === 'success') { setTimeout(() => { hideMessage(); }, 5000); } } function hideMessage() { document.getElementById('message-container').innerHTML = ''; } // 로딩 표시 function showLoading() { document.getElementById('loadingSpinner').style.display = 'flex'; document.getElementById('summarySection').style.display = 'none'; document.getElementById('actionBar').style.display = 'none'; document.getElementById('workersSection').style.display = 'none'; document.getElementById('noDataMessage').style.display = 'none'; } function hideLoading() { document.getElementById('loadingSpinner').style.display = 'none'; } // 작업자 데이터 로드 async function loadWorkers() { try { console.log('작업자 데이터 로딩 중... (통합 API)'); const data = await apiCall(`${API}/workers`); const allWorkers = Array.isArray(data) ? data : (data.data || data.workers || []); // 활성화된 작업자만 필터링 workers = allWorkers.filter(worker => { return worker.status === 'active' || worker.is_active === 1 || worker.is_active === true; }); } catch (error) { console.error('작업자 로딩 오류:', error); throw error; } } // 특정 날짜의 작업 데이터 로드 (개선된 버전) async function loadWorkData(date) { try { console.log(`${date} 날짜의 작업 데이터 로딩 중... (통합 API)`); // 1차: view_all=true로 전체 데이터 시도 let queryParams = `date=${date}&view_all=true`; let data = await apiCall(`${API}/daily-work-reports?${queryParams}`); workData = Array.isArray(data) ? data : (data.data || []); // 데이터가 없으면 다른 방법들 시도 if (workData.length === 0) { // 2차: admin=true로 시도 queryParams = `date=${date}&admin=true`; data = await apiCall(`${API}/daily-work-reports?${queryParams}`); workData = Array.isArray(data) ? data : (data.data || []); if (workData.length === 0) { // 3차: 날짜 경로 파라미터로 시도 data = await apiCall(`${API}/daily-work-reports/date/${date}`); workData = Array.isArray(data) ? data : (data.data || []); if (workData.length === 0) { // 4차: 기본 파라미터만으로 시도 data = await apiCall(`${API}/daily-work-reports?date=${date}`); workData = Array.isArray(data) ? data : (data.data || []); } } } // 디버깅을 위한 상세 로그 if (workData.length > 0) { const uniqueWorkers = [...new Set(workData.map(w => w.worker_name))]; } else { } return workData; } catch (error) { console.error('작업 데이터 로딩 오류:', error); // 에러 시에도 빈 배열 반환하여 앱이 중단되지 않도록 workData = []; // 구체적인 에러 정보 표시 if (error.message.includes('403')) { throw new Error('해당 날짜의 데이터에 접근할 권한이 없습니다.'); } else if (error.message.includes('404')) { throw new Error('해당 날짜에 입력된 작업 데이터가 없습니다.'); } else { throw error; } } } // 대시보드 데이터 로드 async function loadDashboardData() { const selectedDate = document.getElementById('selectedDate').value; if (!selectedDate) { showMessage('날짜를 선택해주세요.', 'error'); return; } currentDate = selectedDate; showLoading(); hideMessage(); try { // 병렬로 데이터 로드 await Promise.all([ loadWorkers(), loadWorkData(selectedDate) ]); // 데이터 분석 및 표시 const dashboardData = analyzeDashboardData(); displayDashboard(dashboardData); hideLoading(); } catch (error) { console.error('대시보드 데이터 로드 실패:', error); hideLoading(); showMessage('데이터를 불러오는 중 오류가 발생했습니다: ' + error.message, 'error'); // 에러 시 데이터 없음 메시지 표시 document.getElementById('noDataMessage').style.display = 'block'; } } // 대시보드 데이터 분석 (개선된 버전) function analyzeDashboardData() { console.log('대시보드 데이터 분석 시작'); // 작업자별 데이터 그룹화 const workerWorkData = {}; workData.forEach(work => { const userId = work.user_id; if (!workerWorkData[userId]) { workerWorkData[userId] = []; } workerWorkData[userId].push(work); }); // 전체 통계 계산 const totalWorkers = workers.length; const workersWithData = Object.keys(workerWorkData).length; const workersWithoutData = totalWorkers - workersWithData; const totalHours = workData.reduce((sum, work) => sum + parseFloat(work.work_hours || 0), 0); const totalEntries = workData.length; const errorCount = workData.filter(work => work.work_status_id === 2).length; // 작업자별 상세 분석 (개선된 버전) const workerAnalysis = workers.map(worker => { const workerWorks = workerWorkData[worker.user_id] || []; const workerHours = workerWorks.reduce((sum, work) => sum + parseFloat(work.work_hours || 0), 0); // 작업 유형 분석 (실제 이름으로) const workTypes = [...new Set(workerWorks.map(work => work.work_type_name).filter(Boolean))]; // 프로젝트 분석 const workerProjects = [...new Set(workerWorks.map(work => work.project_name).filter(Boolean))]; // 기여자 분석 const workerContributors = [...new Set(workerWorks.map(work => work.created_by_name).filter(Boolean))]; // 상태 결정 (더 세밀한 기준) let status = 'missing'; if (workerWorks.length > 0) { if (workerHours >= 6) { status = 'completed'; // 6시간 이상을 완료로 간주 } else { status = 'partial'; // 1시간 이상이지만 6시간 미만은 부분입력 } } // 최근 업데이트 시간 const lastUpdate = workerWorks.length > 0 ? new Date(Math.max(...workerWorks.map(work => new Date(work.created_at)))) : null; return { ...worker, status, totalHours: Math.round(workerHours * 10) / 10, // 소수점 1자리로 반올림 entryCount: workerWorks.length, workTypes, // 작업 유형 배열 (실제 이름) projects: workerProjects, contributors: workerContributors, lastUpdate, works: workerWorks }; }); const summary = { totalWorkers, completedWorkers: workerAnalysis.filter(w => w.status === 'completed').length, missingWorkers: workerAnalysis.filter(w => w.status === 'missing').length, partialWorkers: workerAnalysis.filter(w => w.status === 'partial').length, totalHours: Math.round(totalHours * 10) / 10, totalEntries, errorCount }; console.log('대시보드 분석 결과:', { summary, workerAnalysis }); return { summary, workers: workerAnalysis, date: currentDate }; } // 대시보드 표시 function displayDashboard(data) { displaySummary(data.summary); displayWorkers(data.workers); // 섹션 표시 document.getElementById('summarySection').style.display = 'block'; document.getElementById('actionBar').style.display = 'flex'; document.getElementById('workersSection').style.display = 'block'; // 필터링 설정 filteredWorkData = data.workers; setupFiltering(); } // 요약 섹션 표시 function displaySummary(summary) { document.getElementById('totalWorkers').textContent = summary.totalWorkers; document.getElementById('completedWorkers').textContent = summary.completedWorkers; document.getElementById('missingWorkers').textContent = summary.missingWorkers; document.getElementById('totalHours').textContent = summary.totalHours + 'h'; document.getElementById('totalEntries').textContent = summary.totalEntries; document.getElementById('errorCount').textContent = summary.errorCount; } // 작업자 목록 표시 (테이블 형태로 개선) function displayWorkers(workersData) { const tableBody = document.getElementById('workersTableBody'); tableBody.innerHTML = ''; if (workersData.length === 0) { tableBody.innerHTML = `작업자명: ${worker.worker_name}
총 작업시간: ${worker.totalHours}시간
작업 항목 수: ${worker.entryCount}개
상태: ${worker.status === 'completed' ? '✅ 완료' : worker.status === 'missing' ? '❌ 미입력' : '⚠️ 부분입력'}
작업 유형: ${worker.workTypes && worker.workTypes.length > 0 ? worker.workTypes.join(', ') : '없음'}
작업 ${index + 1}
프로젝트: ${work.project_name || '미지정'}
작업 유형: ${work.work_type_name || '미지정'}
작업 시간: ${work.work_hours}시간
상태: ${work.work_status_name || '미지정'}
${work.error_type_name ? `에러 유형: ${work.error_type_name}
` : ''}입력자: ${work.created_by_name || '미지정'}
입력 시간: ${formatDateTime(work.created_at)}
입력된 작업이 없습니다.
${worker.contributors.join(', ')}