/** * 공통 헤더 컴포넌트 * 권한 기반으로 메뉴를 동적으로 생성하고 부드러운 페이지 전환을 제공 */ class CommonHeader { constructor() { this.currentUser = null; this.currentPage = ''; this.menuItems = this.initMenuItems(); } /** * 메뉴 아이템 정의 */ initMenuItems() { return [ { id: 'issues_dashboard', title: '현황판', icon: 'fas fa-chart-line', url: '/issues-dashboard.html', pageName: 'issues_dashboard', color: 'text-slate-600', bgColor: 'text-slate-600 hover:bg-slate-100' }, { id: 'issues_inbox', title: '수신함', icon: 'fas fa-inbox', url: '/issues-inbox.html', pageName: 'issues_inbox', color: 'text-slate-600', bgColor: 'text-slate-600 hover:bg-slate-100' }, { id: 'issues_management', title: '관리함', icon: 'fas fa-cog', url: '/issues-management.html', pageName: 'issues_management', color: 'text-slate-600', bgColor: 'text-slate-600 hover:bg-slate-100' }, { id: 'issues_archive', title: '폐기함', icon: 'fas fa-archive', url: '/issues-archive.html', pageName: 'issues_archive', color: 'text-slate-600', bgColor: 'text-slate-600 hover:bg-slate-100' }, { id: 'reports', title: '보고서', icon: 'fas fa-chart-bar', url: '/reports.html', pageName: 'reports', color: 'text-slate-600', bgColor: 'text-slate-600 hover:bg-slate-100', subMenus: [ { id: 'reports_daily', title: '일일보고서', icon: 'fas fa-file-excel', url: '/reports-daily.html', pageName: 'reports_daily', color: 'text-slate-600' }, { id: 'reports_weekly', title: '주간보고서', icon: 'fas fa-calendar-week', url: '/reports-weekly.html', pageName: 'reports_weekly', color: 'text-slate-600' }, { id: 'reports_monthly', title: '월간보고서', icon: 'fas fa-calendar-alt', url: '/reports-monthly.html', pageName: 'reports_monthly', color: 'text-slate-600' } ] }, { id: 'ai_assistant', title: 'AI 어시스턴트', icon: 'fas fa-robot', url: '/ai-assistant.html', pageName: 'ai_assistant', color: 'text-purple-600', bgColor: 'text-purple-600 hover:bg-purple-50' }, ]; } /** * 헤더 초기화 * @param {Object} user - 현재 사용자 정보 * @param {string} currentPage - 현재 페이지 ID */ async init(user, currentPage = '') { this.currentUser = user; this.currentPage = currentPage; // 권한 시스템이 로드될 때까지 대기 await this.waitForPermissionSystem(); this.render(); this.bindEvents(); // 키보드 단축키 초기화 this.initializeKeyboardShortcuts(); // 페이지 프리로더 초기화 this.initializePreloader(); } /** * 권한 시스템 로드 대기 */ async waitForPermissionSystem() { let attempts = 0; const maxAttempts = 50; // 5초 대기 while (!window.pagePermissionManager && attempts < maxAttempts) { await new Promise(resolve => setTimeout(resolve, 100)); attempts++; } if (window.pagePermissionManager && this.currentUser) { window.pagePermissionManager.setUser(this.currentUser); // 권한 로드 대기 await new Promise(resolve => setTimeout(resolve, 300)); } } /** * 헤더 렌더링 */ render() { const headerHTML = this.generateHeaderHTML(); // 기존 헤더가 있으면 교체, 없으면 body 상단에 추가 let headerContainer = document.getElementById('common-header'); if (headerContainer) { headerContainer.innerHTML = headerHTML; } else { headerContainer = document.createElement('div'); headerContainer.id = 'common-header'; headerContainer.innerHTML = headerHTML; document.body.insertBefore(headerContainer, document.body.firstChild); } } /** * 현재 페이지 업데이트 * @param {string} pageName - 새로운 페이지 이름 */ updateCurrentPage(pageName) { this.currentPage = pageName; this.render(); } /** * 헤더 HTML 생성 */ generateHeaderHTML() { const accessibleMenus = this.getAccessibleMenus(); const userDisplayName = this.currentUser?.full_name || this.currentUser?.username || '사용자'; const userRole = this.getUserRoleDisplay(); return `

부적합 관리

${userDisplayName}
${userRole}
${userDisplayName.charAt(0).toUpperCase()}
`; } /** * 접근 가능한 메뉴 필터링 */ getAccessibleMenus() { return this.menuItems.filter(menu => { // admin은 모든 메뉴 접근 가능 if (this.currentUser?.role === 'admin') { // 하위 메뉴가 있는 경우 하위 메뉴도 필터링 if (menu.subMenus) { menu.accessibleSubMenus = menu.subMenus; } return true; } // 권한 시스템이 로드되지 않았으면 기본 메뉴만 if (!window.canAccessPage) { return ['issues_dashboard', 'issues_inbox'].includes(menu.id); } // 메인 메뉴 권한 체크 const hasMainAccess = window.canAccessPage(menu.pageName); // 하위 메뉴가 있는 경우 접근 가능한 하위 메뉴 필터링 if (menu.subMenus) { menu.accessibleSubMenus = menu.subMenus.filter(subMenu => window.canAccessPage(subMenu.pageName) ); // 메인 메뉴 접근 권한이 없어도 하위 메뉴 중 하나라도 접근 가능하면 표시 return hasMainAccess || menu.accessibleSubMenus.length > 0; } return hasMainAccess; }); } /** * 데스크톱 메뉴 아이템 HTML 생성 */ generateMenuItemHTML(menu) { const isActive = this.currentPage === menu.id; const activeClass = isActive ? 'bg-slate-700 text-white' : `${menu.bgColor} ${menu.color}`; // 하위 메뉴가 있는 경우 드롭다운 메뉴 생성 if (menu.accessibleSubMenus && menu.accessibleSubMenus.length > 0) { return `
`; } // 외부 링크 (tkuser 등) if (menu.external) { return ` ${menu.title} `; } // 일반 메뉴 아이템 return ` ${menu.title} `; } /** * 모바일 메뉴 아이템 HTML 생성 */ generateMobileMenuItemHTML(menu) { const isActive = this.currentPage === menu.id; const activeClass = isActive ? 'bg-slate-100 text-slate-800 border-slate-600' : 'text-gray-700 hover:bg-gray-50'; // 하위 메뉴가 있는 경우 if (menu.accessibleSubMenus && menu.accessibleSubMenus.length > 0) { return `
`; } // 일반 메뉴 아이템 return ` ${menu.title} `; } /** * 사용자 역할 표시명 가져오기 */ getUserRoleDisplay() { const roleNames = { 'admin': '관리자', 'user': '사용자' }; return roleNames[this.currentUser?.role] || '사용자'; } /** * 이벤트 바인딩 */ bindEvents() { // 사용자 메뉴 토글 const userMenuButton = document.getElementById('user-menu-button'); const userMenu = document.getElementById('user-menu'); if (userMenuButton && userMenu) { userMenuButton.addEventListener('click', (e) => { e.stopPropagation(); userMenu.classList.toggle('hidden'); }); // 외부 클릭 시 메뉴 닫기 document.addEventListener('click', () => { userMenu.classList.add('hidden'); }); } // 모바일 메뉴 토글 const mobileMenuButton = document.getElementById('mobile-menu-button'); const mobileMenu = document.getElementById('mobile-menu'); if (mobileMenuButton && mobileMenu) { mobileMenuButton.addEventListener('click', () => { mobileMenu.classList.toggle('hidden'); }); } } /** * 페이지 네비게이션 (부드러운 전환) */ static navigateToPage(event, url, pageId) { event.preventDefault(); // 현재 페이지와 같으면 무시 if (window.commonHeader?.currentPage === pageId) { return; } // 로딩 표시 CommonHeader.showPageTransition(); // 페이지 이동 setTimeout(() => { window.location.href = url; }, 150); // 부드러운 전환을 위한 딜레이 } /** * 페이지 전환 로딩 표시 */ static showPageTransition() { // 기존 로딩이 있으면 제거 const existingLoader = document.getElementById('page-transition-loader'); if (existingLoader) { existingLoader.remove(); } const loader = document.createElement('div'); loader.id = 'page-transition-loader'; loader.className = 'fixed inset-0 bg-white bg-opacity-75 flex items-center justify-center z-50'; loader.innerHTML = `

페이지를 로드하는 중...

`; document.body.appendChild(loader); } /** * 비밀번호 변경 모달 표시 */ static showPasswordModal() { // 기존 모달이 있으면 제거 const existingModal = document.getElementById('passwordChangeModal'); if (existingModal) { existingModal.remove(); } // 비밀번호 변경 모달 생성 const modalHTML = `

비밀번호 변경

`; document.body.insertAdjacentHTML('beforeend', modalHTML); // 폼 제출 이벤트 리스너 추가 document.getElementById('passwordChangeForm').addEventListener('submit', CommonHeader.handlePasswordChange); // ESC 키로 모달 닫기 document.addEventListener('keydown', function(e) { if (e.key === 'Escape') { CommonHeader.hidePasswordModal(); } }); } /** * 비밀번호 변경 모달 숨기기 */ static hidePasswordModal() { const modal = document.getElementById('passwordChangeModal'); if (modal) { modal.remove(); } } /** * 비밀번호 변경 처리 */ static async handlePasswordChange(e) { e.preventDefault(); const currentPassword = document.getElementById('currentPasswordInput').value; const newPassword = document.getElementById('newPasswordInput').value; const confirmPassword = document.getElementById('confirmPasswordInput').value; // 새 비밀번호 확인 if (newPassword !== confirmPassword) { CommonHeader.showToast('새 비밀번호가 일치하지 않습니다.', 'error'); return; } if (newPassword.length < 6) { CommonHeader.showToast('새 비밀번호는 최소 6자 이상이어야 합니다.', 'error'); return; } try { // AuthAPI가 있는지 확인 if (typeof AuthAPI === 'undefined') { throw new Error('AuthAPI가 로드되지 않았습니다.'); } // API를 통한 비밀번호 변경 await AuthAPI.changePassword(currentPassword, newPassword); CommonHeader.showToast('비밀번호가 성공적으로 변경되었습니다.', 'success'); CommonHeader.hidePasswordModal(); } catch (error) { console.error('비밀번호 변경 실패:', error); CommonHeader.showToast('현재 비밀번호가 올바르지 않거나 변경에 실패했습니다.', 'error'); } } /** * 토스트 메시지 표시 */ static showToast(message, type = 'success') { // 기존 토스트 제거 const existingToast = document.querySelector('.toast-message'); if (existingToast) { existingToast.remove(); } const toast = document.createElement('div'); toast.className = `toast-message fixed bottom-4 right-4 px-4 py-3 rounded-lg text-white z-[10000] shadow-lg transform transition-all duration-300 ${ type === 'success' ? 'bg-green-500' : 'bg-red-500' }`; const icon = type === 'success' ? 'fa-check-circle' : 'fa-exclamation-circle'; toast.innerHTML = `${message}`; document.body.appendChild(toast); // 애니메이션 효과 setTimeout(() => toast.classList.add('translate-x-0'), 10); setTimeout(() => { toast.classList.add('opacity-0', 'translate-x-full'); setTimeout(() => toast.remove(), 300); }, 3000); } /** * 로그아웃 */ static logout() { if (confirm('로그아웃 하시겠습니까?')) { if (window.authManager) { window.authManager.logout(); } else { localStorage.removeItem('access_token'); localStorage.removeItem('sso_token'); localStorage.removeItem('sso_user'); localStorage.removeItem('currentUser'); var hostname = window.location.hostname; if (hostname.includes('technicalkorea.net')) { window.location.href = window.location.protocol + '//tkfb.technicalkorea.net/login'; } else { window.location.href = window.location.protocol + '//' + hostname + ':30000/login'; } } } } /** * 현재 페이지 업데이트 */ updateCurrentPage(pageId) { this.currentPage = pageId; // 활성 메뉴 업데이트 document.querySelectorAll('.nav-item').forEach(item => { const itemPageId = item.getAttribute('data-page'); if (itemPageId === pageId) { item.classList.add('bg-slate-700', 'text-white'); item.classList.remove('text-slate-600', 'hover:bg-slate-100'); } else { item.classList.remove('bg-slate-700', 'text-white'); item.classList.add('text-slate-600'); } }); } /** * 키보드 단축키 초기화 */ initializeKeyboardShortcuts() { if (window.keyboardShortcuts) { window.keyboardShortcuts.setUser(this.currentUser); console.log('⌨️ 키보드 단축키 사용자 설정 완료'); } } /** * 페이지 프리로더 초기화 */ initializePreloader() { if (window.pagePreloader) { // 사용자 설정 후 프리로더 초기화 setTimeout(() => { window.pagePreloader.init(); console.log('🚀 페이지 프리로더 초기화 완료'); }, 1000); // 권한 시스템 로드 후 실행 } } } // 전역 인스턴스 window.commonHeader = new CommonHeader(); // 전역 함수로 노출 window.CommonHeader = CommonHeader;