// ✅ modern-dashboard.js - 모던 대시보드 JavaScript // API 설정 및 함수들은 api-config.js에서 로드됨 // window.API, window.apiCall, window.getAuthHeaders 사용 // 인증 관련 함수들 function getAuthData() { const token = localStorage.getItem('token'); const user = localStorage.getItem('user'); return { token, user: user ? JSON.parse(user) : null }; } // 전역 변수 let currentUser = null; let workersData = []; let workData = []; let selectedDate = new Date().toISOString().split('T')[0]; // DOM 요소 const elements = { currentTime: document.getElementById('currentTime'), timeValue: document.getElementById('timeValue'), userName: document.getElementById('userName'), userRole: document.getElementById('userRole'), userInitial: document.getElementById('userInitial'), selectedDate: document.getElementById('selectedDate'), refreshBtn: document.getElementById('refreshBtn'), logoutBtn: document.getElementById('logoutBtn'), // 요약 카드 todayWorkers: document.getElementById('todayWorkers'), totalHours: document.getElementById('totalHours'), activeProjects: document.getElementById('activeProjects'), errorCount: document.getElementById('errorCount'), // 컨테이너 workStatusContainer: document.getElementById('workStatusContainer'), workersContainer: document.getElementById('workersContainer'), toastContainer: document.getElementById('toastContainer') }; // ========== 초기화 ========== // document.addEventListener('DOMContentLoaded', async () => { // API 함수가 로드될 때까지 기다림 let retryCount = 0; const maxRetries = 50; // 5초 대기 while (!window.apiCall && retryCount < maxRetries) { await new Promise(resolve => setTimeout(resolve, 100)); retryCount++; } if (!window.apiCall) { console.error('❌ API 함수를 로드할 수 없습니다.'); showToast('시스템을 초기화할 수 없습니다. 페이지를 새로고침해주세요.', 'error'); return; } try { await initializeDashboard(); } catch (error) { console.error('대시보드 초기화 오류:', error); showToast('대시보드를 불러오는 중 오류가 발생했습니다.', 'error'); } }); async function initializeDashboard() { console.log('🚀 모던 대시보드 초기화 시작'); // 사용자 정보 설정 setupUserInfo(); // 시간 업데이트 시작 updateCurrentTime(); setInterval(updateCurrentTime, 1000); // 날짜 설정 elements.selectedDate.value = selectedDate; // 이벤트 리스너 설정 setupEventListeners(); // 데이터 로드 await loadDashboardData(); // 관리자 권한 확인 checkAdminAccess(); console.log('✅ 모던 대시보드 초기화 완료'); } // ========== 사용자 정보 설정 ========== // function setupUserInfo() { const authData = getAuthData(); if (authData && authData.user) { currentUser = authData.user; // 사용자 이름 설정 elements.userName.textContent = currentUser.name || currentUser.username; // 사용자 역할 설정 const roleMap = { 'admin': '관리자', 'system': '시스템 관리자', 'group_leader': '그룹장', 'leader': '그룹장', 'user': '작업자' }; elements.userRole.textContent = roleMap[currentUser.role] || '작업자'; // 아바타 초기값 설정 const initial = (currentUser.name || currentUser.username).charAt(0); elements.userInitial.textContent = initial; console.log('👤 사용자 정보 설정 완료:', currentUser.name); } } // ========== 시간 업데이트 ========== // function updateCurrentTime() { const now = new Date(); const timeString = now.toLocaleTimeString('ko-KR', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' }); elements.timeValue.textContent = timeString; } // ========== 이벤트 리스너 ========== // function setupEventListeners() { // 날짜 변경 elements.selectedDate.addEventListener('change', (e) => { selectedDate = e.target.value; loadDashboardData(); }); // 새로고침 버튼 elements.refreshBtn.addEventListener('click', () => { loadDashboardData(); showToast('데이터를 새로고침했습니다.', 'success'); }); // 로그아웃 버튼 elements.logoutBtn.addEventListener('click', () => { if (confirm('로그아웃하시겠습니까?')) { localStorage.clear(); window.location.href = '/index.html'; } }); // 뷰 컨트롤 버튼들 const listViewBtn = document.getElementById('listViewBtn'); const cardViewBtn = document.getElementById('cardViewBtn'); if (listViewBtn) { listViewBtn.addEventListener('click', () => { displayWorkers(workersData, 'list'); updateViewButtons('list'); }); } if (cardViewBtn) { cardViewBtn.addEventListener('click', () => { displayWorkers(workersData, 'card'); updateViewButtons('card'); }); } } // ========== 데이터 로드 ========== // async function loadDashboardData() { console.log('📊 대시보드 데이터 로딩 시작'); try { // 로딩 상태 표시 showLoadingState(); // 병렬로 데이터 로드 const [workersResult, workResult] = await Promise.all([ loadWorkers(), loadWorkData(selectedDate) ]); // 요약 데이터 업데이트 updateSummaryCards(); // 작업 현황 표시 displayWorkStatus(); // 작업자 현황 표시 displayWorkers(workersData, 'card'); console.log('✅ 대시보드 데이터 로딩 완료'); } catch (error) { console.error('❌ 대시보드 데이터 로딩 오류:', error); showErrorState(); showToast('데이터를 불러오는 중 오류가 발생했습니다.', 'error'); } } async function loadWorkers() { try { console.log('👥 작업자 데이터 로딩...'); const response = await window.apiCall(`${window.API}/workers`); workersData = Array.isArray(response) ? response : (response.data || []); console.log(`✅ 작업자 ${workersData.length}명 로드 완료`); return workersData; } catch (error) { console.error('작업자 데이터 로딩 오류:', error); workersData = []; throw error; } } async function loadWorkData(date) { try { console.log(`📋 ${date} 작업 데이터 로딩...`); const response = await window.apiCall(`${window.API}/daily-work-reports?date=${date}&view_all=true`); workData = Array.isArray(response) ? response : (response.data || []); console.log(`✅ 작업 데이터 ${workData.length}건 로드 완료`); return workData; } catch (error) { console.error('작업 데이터 로딩 오류:', error); workData = []; throw error; } } // ========== 요약 카드 업데이트 ========== // function updateSummaryCards() { // 오늘 작업자 수 const todayWorkersCount = new Set(workData.map(w => w.worker_id)).size; updateSummaryCard(elements.todayWorkers, todayWorkersCount, '명'); // 총 작업 시간 const totalHours = workData.reduce((sum, work) => sum + parseFloat(work.work_hours || 0), 0); updateSummaryCard(elements.totalHours, totalHours.toFixed(1), '시간'); // 진행 중인 프로젝트 const activeProjectsCount = new Set(workData.map(w => w.project_id)).size; updateSummaryCard(elements.activeProjects, activeProjectsCount, '개'); // 오류 발생 건수 const errorCount = workData.filter(w => w.work_status_id === 2).length; updateSummaryCard(elements.errorCount, errorCount, '건'); } function updateSummaryCard(element, value, unit) { if (element) { const numberElement = element.querySelector('.value-number'); const unitElement = element.querySelector('.value-unit'); if (numberElement) numberElement.textContent = value; if (unitElement) unitElement.textContent = unit; } } // ========== 작업 현황 표시 ========== // function displayWorkStatus() { if (!elements.workStatusContainer) return; if (workData.length === 0) { elements.workStatusContainer.innerHTML = `
📭

작업 데이터가 없습니다

${selectedDate}에 등록된 작업이 없습니다.

`; return; } // 프로젝트별 작업 현황 그룹화 const projectGroups = groupWorkDataByProject(); elements.workStatusContainer.innerHTML = `
${Object.entries(projectGroups).map(([projectName, works]) => `

📁 ${projectName}

${works.length}건
총 시간 ${works.reduce((sum, w) => sum + parseFloat(w.work_hours || 0), 0).toFixed(1)}h
작업자 ${new Set(works.map(w => w.worker_id)).size}명
오류 ${works.filter(w => w.work_status_id === 2).length}건
`).join('')}
`; } function groupWorkDataByProject() { const groups = {}; workData.forEach(work => { const projectName = work.project_name || '미지정 프로젝트'; if (!groups[projectName]) { groups[projectName] = []; } groups[projectName].push(work); }); return groups; } // ========== 작업자 현황 표시 ========== // function displayWorkers(workers, viewType = 'card') { if (!elements.workersContainer) return; if (workers.length === 0) { elements.workersContainer.innerHTML = `
👥

작업자 데이터가 없습니다

등록된 작업자가 없습니다.

`; return; } if (viewType === 'list') { displayWorkersAsList(workers); } else { displayWorkersAsCards(workers); } } function displayWorkersAsCards(workers) { elements.workersContainer.innerHTML = `
${workers.map(worker => { const todayWork = workData.filter(w => w.worker_id === worker.worker_id); const totalHours = todayWork.reduce((sum, w) => sum + parseFloat(w.work_hours || 0), 0); const hasError = todayWork.some(w => w.work_status_id === 2); return `
${worker.worker_name.charAt(0)}

${worker.worker_name}

${worker.job_type || '작업자'}

오늘 작업 ${todayWork.length}건
작업 시간 ${totalHours.toFixed(1)}h
${hasError ? `
오류 ⚠️
` : ''}
`; }).join('')}
`; } function displayWorkersAsList(workers) { elements.workersContainer.innerHTML = `
${workers.map(worker => { const todayWork = workData.filter(w => w.worker_id === worker.worker_id); const totalHours = todayWork.reduce((sum, w) => sum + parseFloat(w.work_hours || 0), 0); const hasError = todayWork.some(w => w.work_status_id === 2); return ` `; }).join('')}
작업자 직종 오늘 작업 작업 시간 상태
${worker.worker_name.charAt(0)}
${worker.worker_name}
${worker.job_type || '작업자'} ${todayWork.length}건 ${totalHours.toFixed(1)}시간 ${todayWork.length > 0 ? '작업 중' : '대기'} ${hasError ? '오류' : ''}
`; } // ========== 뷰 버튼 업데이트 ========== // function updateViewButtons(activeView) { const listBtn = document.getElementById('listViewBtn'); const cardBtn = document.getElementById('cardViewBtn'); if (listBtn && cardBtn) { listBtn.classList.toggle('btn-primary', activeView === 'list'); listBtn.classList.toggle('btn-secondary', activeView !== 'list'); cardBtn.classList.toggle('btn-primary', activeView === 'card'); cardBtn.classList.toggle('btn-secondary', activeView !== 'card'); } } // ========== 관리자 권한 확인 ========== // function checkAdminAccess() { const adminElements = document.querySelectorAll('.admin-only'); const isAdmin = currentUser && ['admin', 'system'].includes(currentUser.access_level); adminElements.forEach(element => { if (isAdmin) { element.classList.add('visible'); } }); } // ========== 상태 표시 ========== // function showLoadingState() { const loadingHTML = `

데이터를 불러오는 중...

`; if (elements.workStatusContainer) { elements.workStatusContainer.innerHTML = loadingHTML; } if (elements.workersContainer) { elements.workersContainer.innerHTML = loadingHTML; } } function showErrorState() { const errorHTML = `
⚠️

데이터를 불러올 수 없습니다

네트워크 연결을 확인하고 다시 시도해주세요.

`; if (elements.workStatusContainer) { elements.workStatusContainer.innerHTML = errorHTML; } if (elements.workersContainer) { elements.workersContainer.innerHTML = errorHTML; } } // ========== 토스트 알림 ========== // function showToast(message, type = 'info', duration = 3000) { if (!elements.toastContainer) return; const toast = document.createElement('div'); toast.className = `toast ${type}`; const iconMap = { success: '✅', error: '❌', warning: '⚠️', info: 'ℹ️' }; toast.innerHTML = `
${iconMap[type] || 'ℹ️'}
${message}
`; elements.toastContainer.appendChild(toast); // 자동 제거 setTimeout(() => { if (toast.parentElement) { toast.remove(); } }, duration); } // ========== 전역 함수 (HTML에서 호출) ========== // window.loadDashboardData = loadDashboardData; window.showToast = showToast; window.updateSummaryCards = updateSummaryCards; window.displayWorkers = displayWorkers;