/** * 메인 애플리케이션 JavaScript * 통합된 SPA 애플리케이션의 핵심 로직 */ class App { constructor() { this.currentUser = null; this.currentPage = 'dashboard'; this.modules = new Map(); this.sidebarCollapsed = false; this.init(); } /** * 애플리케이션 초기화 */ async init() { try { // 인증 확인 await this.checkAuth(); // API 스크립트 로드 await this.loadAPIScript(); // 권한 시스템 초기화 window.pagePermissionManager.setUser(this.currentUser); // UI 초기화 this.initializeUI(); // 라우터 초기화 this.initializeRouter(); // 알림 벨 로드 this._loadNotificationBell(); // 대시보드 데이터 로드 await this.loadDashboardData(); } catch (error) { console.error('앱 초기화 실패:', error); this.redirectToLogin(); } } /** * 인증 확인 */ async checkAuth() { // 쿠키 우선 검증: 쿠키 없고 localStorage에만 토큰이 있으면 정리 const cookieToken = this._cookieGet('sso_token'); const localToken = localStorage.getItem('sso_token'); if (!cookieToken && localToken) { ['sso_token','sso_user','sso_refresh_token','token','user','access_token', 'currentUser','current_user','userInfo','userPageAccess'].forEach(k => localStorage.removeItem(k)); throw new Error('쿠키 없음 - 로그아웃 상태'); } // SSO 쿠키 우선, localStorage 폴백 const token = cookieToken || localToken; if (!token) { throw new Error('토큰 없음'); } const ssoUser = this._cookieGet('sso_user') || localStorage.getItem('sso_user'); if (ssoUser) { try { this.currentUser = JSON.parse(ssoUser); return; } catch(e) {} } throw new Error('사용자 정보 없음'); } _cookieGet(name) { const match = document.cookie.match(new RegExp('(?:^|; )' + name + '=([^;]*)')); return match ? decodeURIComponent(match[1]) : null; } /** * API 스크립트 동적 로드 */ async loadAPIScript() { return new Promise((resolve, reject) => { const script = document.createElement('script'); script.src = `/static/js/api.js?v=${Date.now()}`; script.onload = resolve; script.onerror = reject; document.head.appendChild(script); }); } /** * UI 초기화 */ initializeUI() { // 사용자 정보 표시 this.updateUserDisplay(); // 네비게이션 메뉴 생성 this.createNavigationMenu(); // 이벤트 리스너 등록 this.registerEventListeners(); } /** * 사용자 정보 표시 업데이트 */ updateUserDisplay() { const userInitial = document.getElementById('userInitial'); const userDisplayName = document.getElementById('userDisplayName'); const userRole = document.getElementById('userRole'); const displayName = this.currentUser.full_name || this.currentUser.username; const initial = displayName.charAt(0).toUpperCase(); userInitial.textContent = initial; userDisplayName.textContent = displayName; userRole.textContent = this.getRoleDisplayName(this.currentUser.role); } /** * 역할 표시명 가져오기 */ getRoleDisplayName(role) { const roleNames = { 'admin': '관리자', 'user': '사용자' }; return roleNames[role] || role; } /** * 네비게이션 메뉴 생성 */ createNavigationMenu() { const menuConfig = window.pagePermissionManager.getMenuConfig(); const navigationMenu = document.getElementById('navigationMenu'); navigationMenu.innerHTML = ''; menuConfig.forEach(item => { const menuItem = this.createMenuItem(item); navigationMenu.appendChild(menuItem); }); } /** * 메뉴 아이템 생성 */ createMenuItem(item) { const li = document.createElement('li'); // 단순한 단일 메뉴 아이템만 지원 li.innerHTML = ` `; return li; } /** * 라우터 초기화 */ initializeRouter() { // 해시 변경 감지 window.addEventListener('hashchange', () => { this.handleRouteChange(); }); // 초기 라우트 처리 this.handleRouteChange(); } /** * 라우트 변경 처리 */ async handleRouteChange() { const hash = window.location.hash.substring(1) || 'dashboard'; const [module, action] = hash.split('/'); try { await this.loadModule(module, action); this.updateActiveNavigation(hash); this.updatePageTitle(module, action); } catch (error) { console.error('라우트 처리 실패:', error); this.showError('페이지를 로드할 수 없습니다.'); } } /** * 모듈 로드 */ async loadModule(module, action = 'list') { if (module === 'dashboard') { this.showDashboard(); return; } // 모듈이 이미 로드되어 있는지 확인 if (!this.modules.has(module)) { await this.loadModuleScript(module); } // 모듈 실행 const moduleInstance = this.modules.get(module); if (moduleInstance && typeof moduleInstance.render === 'function') { const content = await moduleInstance.render(action); this.showDynamicContent(content); } } /** * 모듈 스크립트 로드 */ async loadModuleScript(module) { return new Promise((resolve, reject) => { const script = document.createElement('script'); script.src = `/static/js/modules/${module}/${module}.js?v=${Date.now()}`; script.onload = () => { // 모듈이 전역 객체에 등록되었는지 확인 const moduleClass = window[module.charAt(0).toUpperCase() + module.slice(1) + 'Module']; if (moduleClass) { this.modules.set(module, new moduleClass()); } resolve(); }; script.onerror = reject; document.head.appendChild(script); }); } /** * 대시보드 표시 */ showDashboard() { document.getElementById('dashboard').classList.remove('hidden'); document.getElementById('dynamicContent').classList.add('hidden'); this.currentPage = 'dashboard'; } /** * 동적 콘텐츠 표시 */ showDynamicContent(content) { document.getElementById('dashboard').classList.add('hidden'); const dynamicContent = document.getElementById('dynamicContent'); dynamicContent.innerHTML = content; dynamicContent.classList.remove('hidden'); } /** * 네비게이션 활성화 상태 업데이트 */ updateActiveNavigation(hash) { // 모든 네비게이션 아이템에서 active 클래스 제거 document.querySelectorAll('.nav-item').forEach(item => { item.classList.remove('active'); }); // 현재 페이지에 해당하는 네비게이션 아이템에 active 클래스 추가 // 구현 필요 } /** * 페이지 제목 업데이트 */ updatePageTitle(module, action) { const titles = { 'dashboard': '대시보드', 'issues': '부적합 사항', 'projects': '프로젝트', 'reports': '보고서' }; const title = titles[module] || module; document.getElementById('pageTitle').textContent = title; } /** * 대시보드 데이터 로드 */ async loadDashboardData() { try { // 통계 데이터 로드 (임시 데이터) document.getElementById('totalIssues').textContent = '0'; document.getElementById('activeProjects').textContent = '0'; document.getElementById('monthlyHours').textContent = '0'; document.getElementById('completionRate').textContent = '0%'; // 실제 API 호출로 대체 예정 // const stats = await API.getDashboardStats(); // this.updateDashboardStats(stats); } catch (error) { console.error('대시보드 데이터 로드 실패:', error); } } /** * 이벤트 리스너 등록 */ registerEventListeners() { // 비밀번호 변경은 CommonHeader에서 처리 // 모바일 반응형 window.addEventListener('resize', () => { if (window.innerWidth >= 768) { this.hideMobileOverlay(); } }); } /** * 페이지 이동 */ navigateTo(path) { window.location.hash = path.startsWith('#') ? path.substring(1) : path; // 모바일에서 사이드바 닫기 if (window.innerWidth < 768) { this.toggleSidebar(); } } /** * 사이드바 토글 */ toggleSidebar() { const sidebar = document.getElementById('sidebar'); const mainContent = document.getElementById('mainContent'); const mobileOverlay = document.getElementById('mobileOverlay'); if (window.innerWidth < 768) { // 모바일 if (sidebar.classList.contains('collapsed')) { sidebar.classList.remove('collapsed'); mobileOverlay.classList.add('active'); } else { sidebar.classList.add('collapsed'); mobileOverlay.classList.remove('active'); } } else { // 데스크톱 if (this.sidebarCollapsed) { sidebar.classList.remove('collapsed'); mainContent.classList.remove('expanded'); this.sidebarCollapsed = false; } else { sidebar.classList.add('collapsed'); mainContent.classList.add('expanded'); this.sidebarCollapsed = true; } } } /** * 모바일 오버레이 숨기기 */ hideMobileOverlay() { document.getElementById('sidebar').classList.add('collapsed'); document.getElementById('mobileOverlay').classList.remove('active'); } // 비밀번호 변경 기능은 CommonHeader.js에서 처리됩니다. /** * 로그아웃 */ logout() { if (window.authManager) { window.authManager.clearAuth(); } else { localStorage.removeItem('sso_token'); localStorage.removeItem('sso_user'); } this.redirectToLogin(); } /** * 중앙 로그인 페이지로 리다이렉트 */ redirectToLogin() { const hostname = window.location.hostname; if (hostname.includes('technicalkorea.net')) { window.location.href = window.location.protocol + '//tkfb.technicalkorea.net/dashboard?redirect=' + encodeURIComponent(window.location.href); } else { window.location.href = window.location.protocol + '//' + hostname + ':30000/dashboard?redirect=' + encodeURIComponent(window.location.href); } } /** * 알림 벨 로드 */ _loadNotificationBell() { var h = window.location.hostname; var s = document.createElement('script'); s.src = (h.includes('technicalkorea.net') ? 'https://tkfb.technicalkorea.net' : window.location.protocol + '//' + h + ':30000') + '/shared/notification-bell.js?v=4'; document.head.appendChild(s); } /** * 로딩 표시 */ showLoading() { document.getElementById('loadingOverlay').classList.add('active'); } /** * 로딩 숨기기 */ hideLoading() { document.getElementById('loadingOverlay').classList.remove('active'); } /** * 성공 메시지 표시 */ showSuccess(message) { this.showToast(message, 'success'); } /** * 에러 메시지 표시 */ showError(message) { this.showToast(message, 'error'); } /** * 토스트 메시지 표시 */ showToast(message, type = 'info') { const toast = document.createElement('div'); toast.className = `fixed bottom-4 right-4 px-6 py-3 rounded-lg text-white z-50 ${ type === 'success' ? 'bg-green-500' : type === 'error' ? 'bg-red-500' : 'bg-blue-500' }`; toast.innerHTML = `
${message}
`; document.body.appendChild(toast); setTimeout(() => { toast.remove(); }, 3000); } } // 전역 함수들 (HTML에서 호출) function toggleSidebar() { window.app.toggleSidebar(); } // 비밀번호 변경 기능은 CommonHeader.showPasswordModal()을 사용합니다. function logout() { window.app.logout(); } // 앱 초기화 document.addEventListener('DOMContentLoaded', () => { window.app = new App(); });