fix: 그룹 리더 대시보드 작업 저장/삭제 오류 해결 및 작업 분석 시스템 성능 최적화
🔧 그룹 리더 대시보드 수정사항: - API 호출 방식 수정 (modern-dashboard.js) - 서버 API 요구사항에 맞는 데이터 구조 변경 - work_entries 배열 구조로 변경 - work_type_id → task_id 필드명 매핑 - 400 Bad Request 오류 해결 ⚡ 작업 분석 시스템 성능 최적화: - 중복 함수 제거 (isWeekend, isVacationProject 통합) - WorkAnalysisAPI 캐싱 시스템 구현 (5분 만료) - 네임스페이스 조직화 (utils, ui, analysis, render) - ErrorHandler 통합 에러 처리 시스템 - 성능 모니터링 및 메모리 누수 방지 - GPU 가속 CSS 애니메이션 추가 - 디바운스/스로틀 함수 적용 - 의미 없는 통계 카드 제거 📊 작업 분석 페이지 개선: - 프로그레스 바 애니메이션 - 토스트 알림 시스템 - 부드러운 전환 효과 - 반응형 최적화 - 메모리 사용량 모니터링
This commit is contained in:
267
web-ui/js/work-analysis/module-loader.js
Normal file
267
web-ui/js/work-analysis/module-loader.js
Normal file
@@ -0,0 +1,267 @@
|
||||
/**
|
||||
* Work Analysis Module Loader
|
||||
* 작업 분석 모듈들을 순서대로 로드하고 초기화하는 로더
|
||||
*/
|
||||
|
||||
class WorkAnalysisModuleLoader {
|
||||
constructor() {
|
||||
this.modules = [
|
||||
{ name: 'API Client', path: '/js/work-analysis/api-client.js', loaded: false },
|
||||
{ name: 'Data Processor', path: '/js/work-analysis/data-processor.js', loaded: false },
|
||||
{ name: 'State Manager', path: '/js/work-analysis/state-manager.js', loaded: false },
|
||||
{ name: 'Table Renderer', path: '/js/work-analysis/table-renderer.js', loaded: false },
|
||||
{ name: 'Chart Renderer', path: '/js/work-analysis/chart-renderer.js', loaded: false },
|
||||
{ name: 'Main Controller', path: '/js/work-analysis/main-controller.js', loaded: false }
|
||||
];
|
||||
|
||||
this.loadingPromise = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 모든 모듈 로드
|
||||
* @returns {Promise} 로딩 완료 Promise
|
||||
*/
|
||||
async loadAll() {
|
||||
if (this.loadingPromise) {
|
||||
return this.loadingPromise;
|
||||
}
|
||||
|
||||
this.loadingPromise = this._loadModules();
|
||||
return this.loadingPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* 모듈들을 순차적으로 로드
|
||||
*/
|
||||
async _loadModules() {
|
||||
console.log('🚀 작업 분석 모듈 로딩 시작');
|
||||
|
||||
try {
|
||||
// 의존성 순서대로 로드
|
||||
for (const module of this.modules) {
|
||||
await this._loadModule(module);
|
||||
}
|
||||
|
||||
console.log('✅ 모든 작업 분석 모듈 로딩 완료');
|
||||
this._onAllModulesLoaded();
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 모듈 로딩 실패:', error);
|
||||
this._onLoadingError(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 개별 모듈 로드
|
||||
* @param {Object} module - 모듈 정보
|
||||
*/
|
||||
async _loadModule(module) {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.log(`📦 로딩 중: ${module.name}`);
|
||||
|
||||
const script = document.createElement('script');
|
||||
script.src = module.path;
|
||||
script.type = 'text/javascript';
|
||||
|
||||
script.onload = () => {
|
||||
module.loaded = true;
|
||||
console.log(`✅ 로딩 완료: ${module.name}`);
|
||||
resolve();
|
||||
};
|
||||
|
||||
script.onerror = (error) => {
|
||||
console.error(`❌ 로딩 실패: ${module.name}`, error);
|
||||
reject(new Error(`Failed to load ${module.name}: ${module.path}`));
|
||||
};
|
||||
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 모든 모듈 로딩 완료 시 호출
|
||||
*/
|
||||
_onAllModulesLoaded() {
|
||||
// 전역 변수 확인
|
||||
const requiredGlobals = [
|
||||
'WorkAnalysisAPI',
|
||||
'WorkAnalysisDataProcessor',
|
||||
'WorkAnalysisState',
|
||||
'WorkAnalysisTableRenderer',
|
||||
'WorkAnalysisChartRenderer'
|
||||
];
|
||||
|
||||
const missingGlobals = requiredGlobals.filter(name => !window[name]);
|
||||
|
||||
if (missingGlobals.length > 0) {
|
||||
console.warn('⚠️ 일부 전역 객체가 누락됨:', missingGlobals);
|
||||
}
|
||||
|
||||
// 하위 호환성을 위한 전역 함수들 설정
|
||||
this._setupLegacyFunctions();
|
||||
|
||||
// 모듈 로딩 완료 이벤트 발생
|
||||
window.dispatchEvent(new CustomEvent('workAnalysisModulesLoaded', {
|
||||
detail: { modules: this.modules }
|
||||
}));
|
||||
|
||||
console.log('🎉 작업 분석 시스템 준비 완료');
|
||||
}
|
||||
|
||||
/**
|
||||
* 하위 호환성을 위한 전역 함수 설정
|
||||
*/
|
||||
_setupLegacyFunctions() {
|
||||
// 기존 HTML에서 사용하던 함수들을 새 모듈 시스템으로 연결
|
||||
const legacyFunctions = {
|
||||
// 기간 확정
|
||||
confirmPeriod: () => {
|
||||
if (window.WorkAnalysisMainController) {
|
||||
window.WorkAnalysisMainController.handlePeriodConfirm();
|
||||
}
|
||||
},
|
||||
|
||||
// 분석 모드 변경
|
||||
switchAnalysisMode: (mode) => {
|
||||
if (window.WorkAnalysisState) {
|
||||
window.WorkAnalysisState.setAnalysisMode(mode);
|
||||
}
|
||||
},
|
||||
|
||||
// 탭 변경
|
||||
switchTab: (tabId) => {
|
||||
if (window.WorkAnalysisState) {
|
||||
window.WorkAnalysisState.setCurrentTab(tabId);
|
||||
}
|
||||
},
|
||||
|
||||
// 개별 분석 함수들
|
||||
analyzeWorkStatus: () => {
|
||||
if (window.WorkAnalysisMainController) {
|
||||
window.WorkAnalysisMainController.analyzeWorkStatus();
|
||||
}
|
||||
},
|
||||
|
||||
analyzeProjectDistribution: () => {
|
||||
if (window.WorkAnalysisMainController) {
|
||||
window.WorkAnalysisMainController.analyzeProjectDistribution();
|
||||
}
|
||||
},
|
||||
|
||||
analyzeWorkerPerformance: () => {
|
||||
if (window.WorkAnalysisMainController) {
|
||||
window.WorkAnalysisMainController.analyzeWorkerPerformance();
|
||||
}
|
||||
},
|
||||
|
||||
analyzeErrorAnalysis: () => {
|
||||
if (window.WorkAnalysisMainController) {
|
||||
window.WorkAnalysisMainController.analyzeErrorAnalysis();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 전역 함수로 등록
|
||||
Object.assign(window, legacyFunctions);
|
||||
|
||||
console.log('🔗 하위 호환성 함수 설정 완료');
|
||||
}
|
||||
|
||||
/**
|
||||
* 로딩 에러 처리
|
||||
*/
|
||||
_onLoadingError(error) {
|
||||
// 에러 UI 표시
|
||||
const container = document.querySelector('.analysis-container');
|
||||
if (container) {
|
||||
const errorHTML = `
|
||||
<div style="
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 50vh;
|
||||
text-align: center;
|
||||
color: #ef4444;
|
||||
">
|
||||
<div style="font-size: 4rem; margin-bottom: 1rem;">⚠️</div>
|
||||
<h2 style="margin-bottom: 1rem;">모듈 로딩 실패</h2>
|
||||
<p style="margin-bottom: 2rem; color: #666;">
|
||||
작업 분석 시스템을 로드하는 중 오류가 발생했습니다.<br>
|
||||
페이지를 새로고침하거나 관리자에게 문의하세요.
|
||||
</p>
|
||||
<button onclick="location.reload()" style="
|
||||
padding: 0.75rem 1.5rem;
|
||||
background: #3b82f6;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 0.5rem;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
">
|
||||
페이지 새로고침
|
||||
</button>
|
||||
<details style="margin-top: 2rem; text-align: left; max-width: 600px;">
|
||||
<summary style="cursor: pointer; color: #666;">기술적 세부사항</summary>
|
||||
<pre style="
|
||||
background: #f8f9fa;
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
margin-top: 1rem;
|
||||
overflow-x: auto;
|
||||
font-size: 0.875rem;
|
||||
">${error.message}</pre>
|
||||
</details>
|
||||
</div>
|
||||
`;
|
||||
|
||||
container.innerHTML = errorHTML;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 로딩 상태 확인
|
||||
* @returns {Object} 로딩 상태 정보
|
||||
*/
|
||||
getLoadingStatus() {
|
||||
const total = this.modules.length;
|
||||
const loaded = this.modules.filter(m => m.loaded).length;
|
||||
|
||||
return {
|
||||
total,
|
||||
loaded,
|
||||
percentage: Math.round((loaded / total) * 100),
|
||||
isComplete: loaded === total,
|
||||
modules: this.modules.map(m => ({
|
||||
name: m.name,
|
||||
loaded: m.loaded
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 모듈 로딩 상태 확인
|
||||
* @param {string} moduleName - 모듈명
|
||||
* @returns {boolean} 로딩 완료 여부
|
||||
*/
|
||||
isModuleLoaded(moduleName) {
|
||||
const module = this.modules.find(m => m.name === moduleName);
|
||||
return module ? module.loaded : false;
|
||||
}
|
||||
}
|
||||
|
||||
// 전역 인스턴스 생성
|
||||
window.WorkAnalysisModuleLoader = new WorkAnalysisModuleLoader();
|
||||
|
||||
// 자동 로딩 시작 (DOM이 준비되면)
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
window.WorkAnalysisModuleLoader.loadAll();
|
||||
});
|
||||
} else {
|
||||
// DOM이 이미 준비된 경우 즉시 로딩
|
||||
window.WorkAnalysisModuleLoader.loadAll();
|
||||
}
|
||||
|
||||
// Export는 브라우저 환경에서 제거됨
|
||||
Reference in New Issue
Block a user