/** * Work Analysis State Manager Module * 작업 분석 페이지의 상태 관리를 담당하는 모듈 */ class WorkAnalysisStateManager { constructor() { this.state = { // 분석 설정 analysisMode: 'period', // 'period' | 'project' confirmedPeriod: { start: null, end: null, confirmed: false }, // UI 상태 currentTab: 'work-status', isAnalysisEnabled: false, isLoading: false, // 데이터 캐시 cache: { basicStats: null, chartData: null, projectDistribution: null, errorAnalysis: null }, // 에러 상태 lastError: null }; this.listeners = new Map(); this.init(); } /** * 초기화 */ init() { console.log('🔧 상태 관리자 초기화'); // 기본 날짜 설정 (현재 월) const now = new Date(); const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1); const endOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0); this.updateState({ confirmedPeriod: { start: this.formatDate(startOfMonth), end: this.formatDate(endOfMonth), confirmed: false } }); } /** * 상태 업데이트 * @param {Object} updates - 업데이트할 상태 */ updateState(updates) { const prevState = { ...this.state }; this.state = { ...this.state, ...updates }; console.log('🔄 상태 업데이트:', updates); // 리스너들에게 상태 변경 알림 this.notifyListeners(prevState, this.state); } /** * 상태 리스너 등록 * @param {string} key - 리스너 키 * @param {Function} callback - 콜백 함수 */ subscribe(key, callback) { this.listeners.set(key, callback); } /** * 상태 리스너 제거 * @param {string} key - 리스너 키 */ unsubscribe(key) { this.listeners.delete(key); } /** * 리스너들에게 알림 */ notifyListeners(prevState, newState) { this.listeners.forEach((callback, key) => { try { callback(newState, prevState); } catch (error) { console.error(`❌ 리스너 ${key} 오류:`, error); } }); } // ========== 분석 설정 관리 ========== /** * 분석 모드 변경 * @param {string} mode - 분석 모드 ('period' | 'project') */ setAnalysisMode(mode) { if (mode !== 'period' && mode !== 'project') { throw new Error('유효하지 않은 분석 모드입니다.'); } this.updateState({ analysisMode: mode, currentTab: 'work-status' // 모드 변경 시 첫 번째 탭으로 리셋 }); } /** * 기간 확정 * @param {string} startDate - 시작일 * @param {string} endDate - 종료일 */ confirmPeriod(startDate, endDate) { // 날짜 유효성 검사 if (!startDate || !endDate) { throw new Error('시작일과 종료일을 모두 입력해주세요.'); } const start = new Date(startDate); const end = new Date(endDate); if (start > end) { throw new Error('시작일이 종료일보다 늦을 수 없습니다.'); } // 최대 1년 제한 const maxDays = 365; const daysDiff = Math.ceil((end - start) / (1000 * 60 * 60 * 24)); if (daysDiff > maxDays) { throw new Error(`분석 기간은 최대 ${maxDays}일까지 가능합니다.`); } this.updateState({ confirmedPeriod: { start: startDate, end: endDate, confirmed: true }, isAnalysisEnabled: true, // 기간 변경 시 캐시 초기화 cache: { basicStats: null, chartData: null, projectDistribution: null, errorAnalysis: null } }); console.log('✅ 기간 확정:', startDate, '~', endDate); } /** * 현재 탭 변경 * @param {string} tabId - 탭 ID */ setCurrentTab(tabId) { const validTabs = ['work-status', 'project-distribution', 'worker-performance', 'error-analysis']; if (!validTabs.includes(tabId)) { console.warn('유효하지 않은 탭 ID:', tabId); return; } this.updateState({ currentTab: tabId }); // DOM 업데이트 직접 수행 this.updateTabDOM(tabId); console.log('🔄 탭 전환:', tabId); } /** * 탭 DOM 업데이트 * @param {string} tabId - 탭 ID */ updateTabDOM(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'); } }); } // ========== 로딩 상태 관리 ========== /** * 로딩 시작 * @param {string} message - 로딩 메시지 */ startLoading(message = '분석 중입니다...') { this.updateState({ isLoading: true, loadingMessage: message }); } /** * 로딩 종료 */ stopLoading() { this.updateState({ isLoading: false, loadingMessage: null }); } // ========== 데이터 캐시 관리 ========== /** * 캐시 데이터 저장 * @param {string} key - 캐시 키 * @param {*} data - 저장할 데이터 */ setCache(key, data) { this.updateState({ cache: { ...this.state.cache, [key]: { data, timestamp: Date.now() } } }); } /** * 캐시 데이터 조회 * @param {string} key - 캐시 키 * @param {number} maxAge - 최대 유효 시간 (밀리초) * @returns {*} 캐시된 데이터 또는 null */ getCache(key, maxAge = 5 * 60 * 1000) { // 기본 5분 const cached = this.state.cache[key]; if (!cached) { return null; } const age = Date.now() - cached.timestamp; if (age > maxAge) { console.log('🗑️ 캐시 만료:', key); return null; } console.log('📦 캐시 히트:', key); return cached.data; } /** * 캐시 초기화 * @param {string} key - 특정 키만 초기화 (선택사항) */ clearCache(key = null) { if (key) { this.updateState({ cache: { ...this.state.cache, [key]: null } }); } else { this.updateState({ cache: { basicStats: null, chartData: null, projectDistribution: null, errorAnalysis: null } }); } } // ========== 에러 관리 ========== /** * 에러 설정 * @param {Error|string} error - 에러 객체 또는 메시지 */ setError(error) { const errorInfo = { message: error instanceof Error ? error.message : error, timestamp: Date.now(), stack: error instanceof Error ? error.stack : null }; this.updateState({ lastError: errorInfo, isLoading: false }); console.error('❌ 에러 발생:', errorInfo); } /** * 에러 초기화 */ clearError() { this.updateState({ lastError: null }); } // ========== 유틸리티 ========== /** * 날짜 포맷팅 * @param {Date} date - 날짜 객체 * @returns {string} YYYY-MM-DD 형식 */ formatDate(date) { return date.toISOString().split('T')[0]; } /** * 현재 상태 조회 * @returns {Object} 현재 상태 */ getState() { return { ...this.state }; } /** * 분석 가능 여부 확인 * @returns {boolean} 분석 가능 여부 */ canAnalyze() { return this.state.confirmedPeriod.confirmed && this.state.confirmedPeriod.start && this.state.confirmedPeriod.end && !this.state.isLoading; } /** * 상태 디버그 정보 출력 */ debug() { console.log('🔍 현재 상태:', this.state); console.log('👂 등록된 리스너:', Array.from(this.listeners.keys())); } } // 전역 인스턴스 생성 window.WorkAnalysisState = new WorkAnalysisStateManager(); // 하위 호환성을 위한 전역 변수들 Object.defineProperty(window, 'currentAnalysisMode', { get: () => window.WorkAnalysisState.state.analysisMode, set: (value) => window.WorkAnalysisState.setAnalysisMode(value) }); Object.defineProperty(window, 'confirmedStartDate', { get: () => window.WorkAnalysisState.state.confirmedPeriod.start }); Object.defineProperty(window, 'confirmedEndDate', { get: () => window.WorkAnalysisState.state.confirmedPeriod.end }); Object.defineProperty(window, 'isAnalysisEnabled', { get: () => window.WorkAnalysisState.state.isAnalysisEnabled }); // Export는 브라우저 환경에서 제거됨