/**
* 공통 헤더 컴포넌트
* 권한 기반으로 메뉴를 동적으로 생성하고 부드러운 페이지 전환을 제공
*/
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'
}
]
},
];
}
/**
* 헤더 초기화
* @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 `
${accessibleMenus.map(menu => this.generateMenuItemHTML(menu)).join('')}
${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 `
${menu.title}
${menu.accessibleSubMenus.map(subMenu => `
`).join('')}
`;
}
// 외부 링크 (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;