/** * Work Analysis Main Controller Module * 작업 분석 페이지의 메인 컨트롤러 - 모든 모듈을 조율하고 사용자 상호작용을 처리 */ class WorkAnalysisMainController { constructor() { this.api = window.WorkAnalysisAPI; this.state = window.WorkAnalysisState; this.dataProcessor = window.WorkAnalysisDataProcessor; this.tableRenderer = window.WorkAnalysisTableRenderer; this.chartRenderer = window.WorkAnalysisChartRenderer; this.init(); } /** * 초기화 */ init() { console.log('🚀 작업 분석 메인 컨트롤러 초기화'); this.setupEventListeners(); this.setupStateListeners(); this.initializeUI(); console.log('✅ 작업 분석 메인 컨트롤러 초기화 완료'); } /** * 이벤트 리스너 설정 */ setupEventListeners() { // 기간 확정 버튼 const confirmButton = document.getElementById('confirmPeriodBtn'); if (confirmButton) { confirmButton.addEventListener('click', () => this.handlePeriodConfirm()); } // 분석 모드 탭 document.querySelectorAll('[data-mode]').forEach(button => { button.addEventListener('click', (e) => { const mode = e.target.dataset.mode; this.handleModeChange(mode); }); }); // 분석 탭 네비게이션 document.querySelectorAll('[data-tab]').forEach(button => { button.addEventListener('click', (e) => { const tabId = e.target.dataset.tab; this.handleTabChange(tabId); }); }); // 개별 분석 실행 버튼들 this.setupAnalysisButtons(); // 날짜 입력 필드 const startDateInput = document.getElementById('startDate'); const endDateInput = document.getElementById('endDate'); if (startDateInput && endDateInput) { [startDateInput, endDateInput].forEach(input => { input.addEventListener('change', () => this.handleDateChange()); }); } } /** * 개별 분석 버튼 설정 */ setupAnalysisButtons() { const buttons = [ { selector: 'button[onclick*="analyzeWorkStatus"]', handler: () => this.analyzeWorkStatus() }, { selector: 'button[onclick*="analyzeProjectDistribution"]', handler: () => this.analyzeProjectDistribution() }, { selector: 'button[onclick*="analyzeWorkerPerformance"]', handler: () => this.analyzeWorkerPerformance() }, { selector: 'button[onclick*="analyzeErrorAnalysis"]', handler: () => this.analyzeErrorAnalysis() } ]; buttons.forEach(({ selector, handler }) => { const button = document.querySelector(selector); if (button) { // 기존 onclick 제거하고 새 이벤트 리스너 추가 button.removeAttribute('onclick'); button.addEventListener('click', handler); } }); } /** * 상태 리스너 설정 */ setupStateListeners() { // 기간 확정 상태 변경 시 UI 업데이트 this.state.subscribe('periodConfirmed', (newState, prevState) => { this.updateAnalysisButtons(newState.isAnalysisEnabled); if (newState.confirmedPeriod.confirmed && !prevState.confirmedPeriod.confirmed) { this.showAnalysisTabs(); } }); // 로딩 상태 변경 시 UI 업데이트 this.state.subscribe('loadingState', (newState) => { if (newState.isLoading) { this.showLoading(newState.loadingMessage); } else { this.hideLoading(); } }); // 탭 변경 시 UI 업데이트 this.state.subscribe('tabChange', (newState) => { this.updateActiveTab(newState.currentTab); }); // 에러 발생 시 처리 this.state.subscribe('errorOccurred', (newState) => { if (newState.lastError) { this.handleError(newState.lastError); } }); } /** * UI 초기화 */ initializeUI() { // 기본 날짜 설정 const currentState = this.state.getState(); const startDateInput = document.getElementById('startDate'); const endDateInput = document.getElementById('endDate'); if (startDateInput && currentState.confirmedPeriod.start) { startDateInput.value = currentState.confirmedPeriod.start; } if (endDateInput && currentState.confirmedPeriod.end) { endDateInput.value = currentState.confirmedPeriod.end; } // 분석 버튼 초기 상태 설정 this.updateAnalysisButtons(false); // 분석 탭 숨김 this.hideAnalysisTabs(); } // ========== 이벤트 핸들러 ========== /** * 기간 확정 처리 */ async handlePeriodConfirm() { try { const startDate = document.getElementById('startDate').value; const endDate = document.getElementById('endDate').value; console.log('🔄 기간 확정 처리 시작:', startDate, '~', endDate); this.state.confirmPeriod(startDate, endDate); this.showToast('기간이 확정되었습니다', 'success'); console.log('✅ 기간 확정 완료 - 각 분석 버튼을 눌러서 데이터를 확인하세요'); } catch (error) { console.error('❌ 기간 확정 처리 오류:', error); this.state.setError(error); this.showToast(error.message, 'error'); } } /** * 분석 모드 변경 처리 */ handleModeChange(mode) { try { this.state.setAnalysisMode(mode); this.updateModeButtons(mode); // 캐시 초기화 this.state.clearCache(); } catch (error) { this.state.setError(error); } } /** * 탭 변경 처리 */ handleTabChange(tabId) { this.state.setCurrentTab(tabId); } /** * 날짜 변경 처리 */ handleDateChange() { // 날짜가 변경되면 기간 확정 상태 해제 this.state.updateState({ confirmedPeriod: { ...this.state.getState().confirmedPeriod, confirmed: false }, isAnalysisEnabled: false }); this.updateAnalysisButtons(false); this.hideAnalysisTabs(); } // ========== 분석 실행 ========== /** * 기본 통계 로드 */ async loadBasicStats() { const currentState = this.state.getState(); const { start, end } = currentState.confirmedPeriod; try { console.log('📊 기본 통계 로딩 시작 - 기간:', start, '~', end); this.state.startLoading('기본 통계를 로딩 중입니다...'); console.log('🌐 API 호출 전 - getBasicStats 호출...'); const statsResponse = await this.api.getBasicStats(start, end); console.log('📊 기본 통계 API 응답:', statsResponse); if (statsResponse.success && statsResponse.data) { const stats = statsResponse.data; // 정상/오류 시간 계산 const totalHours = stats.totalHours || 0; const errorReports = stats.errorRate || 0; const errorHours = Math.round(totalHours * (errorReports / 100)); const normalHours = totalHours - errorHours; const cardData = { totalHours: totalHours, normalHours: normalHours, errorHours: errorHours, workerCount: stats.activeWorkers || stats.activeworkers || 0, errorRate: errorReports }; this.state.setCache('basicStats', cardData); this.updateResultCards(cardData); console.log('✅ 기본 통계 로딩 완료:', cardData); } else { // 기본값으로 카드 업데이트 const defaultData = { totalHours: 0, normalHours: 0, errorHours: 0, workerCount: 0, errorRate: 0 }; this.updateResultCards(defaultData); console.warn('⚠️ 기본 통계 데이터가 없어서 기본값으로 설정'); } } catch (error) { console.error('❌ 기본 통계 로드 실패:', error); // 에러 시에도 기본값으로 카드 업데이트 const defaultData = { totalHours: 0, normalHours: 0, errorHours: 0, workerCount: 0, errorRate: 0 }; this.updateResultCards(defaultData); } finally { this.state.stopLoading(); } } /** * 기간별 작업 현황 분석 */ async analyzeWorkStatus() { if (!this.state.canAnalyze()) { this.showToast('기간을 먼저 확정해주세요', 'warning'); return; } const currentState = this.state.getState(); const { start, end } = currentState.confirmedPeriod; try { this.state.startLoading('기간별 작업 현황을 분석 중입니다...'); // 실제 API 호출 const batchData = await this.api.batchCall([ { name: 'projectWorkType', method: 'getProjectWorkTypeAnalysis', startDate: start, endDate: end }, { name: 'workerStats', method: 'getWorkerStats', startDate: start, endDate: end }, { name: 'recentWork', method: 'getRecentWork', startDate: start, endDate: end, limit: 2000 } ]); console.log('🔍 기간별 작업 현황 API 응답:', batchData); // 데이터 처리 const recentWorkData = batchData.recentWork?.success ? batchData.recentWork.data.data : []; const workerData = batchData.workerStats?.success ? batchData.workerStats.data.data : []; const projectData = this.dataProcessor.aggregateProjectData(recentWorkData); // 테이블 렌더링 this.tableRenderer.renderWorkStatusTable(projectData, workerData, recentWorkData); this.showToast('기간별 작업 현황 분석이 완료되었습니다', 'success'); } catch (error) { console.error('❌ 기간별 작업 현황 분석 오류:', error); this.state.setError(error); this.showToast('기간별 작업 현황 분석에 실패했습니다', 'error'); } finally { this.state.stopLoading(); } } /** * 프로젝트별 분포 분석 */ async analyzeProjectDistribution() { if (!this.state.canAnalyze()) { this.showToast('기간을 먼저 확정해주세요', 'warning'); return; } const currentState = this.state.getState(); const { start, end } = currentState.confirmedPeriod; try { this.state.startLoading('프로젝트별 분포를 분석 중입니다...'); // 실제 API 호출 const distributionData = await this.api.getProjectDistributionData(start, end); console.log('🔍 프로젝트별 분포 API 응답:', distributionData); // 데이터 처리 const recentWorkData = distributionData.recentWork?.success ? distributionData.recentWork.data.data : []; const workerData = distributionData.workerStats?.success ? distributionData.workerStats.data.data : []; const projectData = this.dataProcessor.aggregateProjectData(recentWorkData); console.log('📊 취합된 프로젝트 데이터:', projectData); // 테이블 렌더링 this.tableRenderer.renderProjectDistributionTable(projectData, workerData); this.showToast('프로젝트별 분포 분석이 완료되었습니다', 'success'); } catch (error) { console.error('❌ 프로젝트별 분포 분석 오류:', error); this.state.setError(error); this.showToast('프로젝트별 분포 분석에 실패했습니다', 'error'); } finally { this.state.stopLoading(); } } /** * 작업자별 성과 분석 */ async analyzeWorkerPerformance() { if (!this.state.canAnalyze()) { this.showToast('기간을 먼저 확정해주세요', 'warning'); return; } const currentState = this.state.getState(); const { start, end } = currentState.confirmedPeriod; try { this.state.startLoading('작업자별 성과를 분석 중입니다...'); const workerStatsResponse = await this.api.getWorkerStats(start, end); console.log('👤 작업자 통계 API 응답:', workerStatsResponse); if (workerStatsResponse.success && workerStatsResponse.data) { this.chartRenderer.renderWorkerPerformanceChart(workerStatsResponse.data); this.showToast('작업자별 성과 분석이 완료되었습니다', 'success'); } else { throw new Error('작업자 데이터를 가져올 수 없습니다'); } } catch (error) { console.error('❌ 작업자별 성과 분석 오류:', error); this.state.setError(error); this.showToast('작업자별 성과 분석에 실패했습니다', 'error'); } finally { this.state.stopLoading(); } } /** * 오류 분석 */ async analyzeErrorAnalysis() { if (!this.state.canAnalyze()) { this.showToast('기간을 먼저 확정해주세요', 'warning'); return; } const currentState = this.state.getState(); const { start, end } = currentState.confirmedPeriod; try { this.state.startLoading('오류 분석을 진행 중입니다...'); // 병렬로 API 호출 const [recentWorkResponse, errorAnalysisResponse] = await Promise.all([ this.api.getRecentWork(start, end, 2000), this.api.getErrorAnalysis(start, end) ]); console.log('🔍 오류 분석 API 응답:', recentWorkResponse); if (recentWorkResponse.success && recentWorkResponse.data) { this.tableRenderer.renderErrorAnalysisTable(recentWorkResponse.data); this.showToast('오류 분석이 완료되었습니다', 'success'); } else { throw new Error('작업 데이터를 가져올 수 없습니다'); } } catch (error) { console.error('❌ 오류 분석 실패:', error); this.state.setError(error); this.showToast('오류 분석에 실패했습니다', 'error'); } finally { this.state.stopLoading(); } } // ========== UI 업데이트 ========== /** * 결과 카드 업데이트 */ updateResultCards(stats) { const cards = { totalHours: stats.totalHours || 0, normalHours: stats.normalHours || 0, errorHours: stats.errorHours || 0, workerCount: stats.activeWorkers || 0, errorRate: stats.errorRate || 0 }; Object.entries(cards).forEach(([key, value]) => { const element = document.getElementById(key); if (element) { element.textContent = typeof value === 'number' ? (key.includes('Rate') ? `${value}%` : value.toLocaleString()) : value; } }); } /** * 분석 버튼 상태 업데이트 */ updateAnalysisButtons(enabled) { const buttons = document.querySelectorAll('.chart-analyze-btn'); buttons.forEach(button => { button.disabled = !enabled; button.style.opacity = enabled ? '1' : '0.5'; }); } /** * 분석 탭 표시 */ showAnalysisTabs() { const tabNavigation = document.getElementById('analysisTabNavigation'); if (tabNavigation) { tabNavigation.style.display = 'block'; } } /** * 분석 탭 숨김 */ hideAnalysisTabs() { const tabNavigation = document.getElementById('analysisTabNavigation'); if (tabNavigation) { tabNavigation.style.display = 'none'; } } /** * 활성 탭 업데이트 */ updateActiveTab(tabId) { // 탭 버튼 업데이트 document.querySelectorAll('.tab-button').forEach(button => { button.classList.remove('active'); if (button.dataset.tab === tabId) { button.classList.add('active'); } }); // 탭 컨텐츠 업데이트 document.querySelectorAll('.tab-content').forEach(content => { content.classList.remove('active'); if (content.id === `${tabId}-tab`) { content.classList.add('active'); } }); } /** * 모드 버튼 업데이트 */ updateModeButtons(mode) { document.querySelectorAll('[data-mode]').forEach(button => { button.classList.remove('active'); if (button.dataset.mode === mode) { button.classList.add('active'); } }); } /** * 로딩 표시 */ showLoading(message = '분석 중입니다...') { const loadingElement = document.getElementById('loadingState'); if (loadingElement) { const textElement = loadingElement.querySelector('.loading-text'); if (textElement) { textElement.textContent = message; } loadingElement.style.display = 'flex'; } } /** * 로딩 숨김 */ hideLoading() { const loadingElement = document.getElementById('loadingState'); if (loadingElement) { loadingElement.style.display = 'none'; } } /** * 토스트 메시지 표시 */ showToast(message, type = 'info') { console.log(`📢 ${type.toUpperCase()}: ${message}`); // 간단한 토스트 구현 (실제로는 더 정교한 토스트 라이브러리 사용 권장) if (type === 'error') { alert(`❌ ${message}`); } else if (type === 'success') { console.log(`✅ ${message}`); } else if (type === 'warning') { alert(`⚠️ ${message}`); } } /** * 에러 처리 */ handleError(errorInfo) { console.error('❌ 에러 발생:', errorInfo); this.showToast(errorInfo.message, 'error'); } // ========== 유틸리티 ========== /** * 컨트롤러 상태 디버그 */ debug() { console.log('🔍 메인 컨트롤러 상태:'); console.log('- API 클라이언트:', this.api); console.log('- 상태 관리자:', this.state.getState()); console.log('- 차트 상태:', this.chartRenderer.getChartStatus()); } } // 전역 인스턴스 생성 및 초기화 document.addEventListener('DOMContentLoaded', () => { window.WorkAnalysisMainController = new WorkAnalysisMainController(); }); // Export는 브라우저 환경에서 제거됨