// /js/app-init.js // 앱 초기화 - 인증, 네비바, 사이드바를 한 번에 로드 // 모든 페이지에서 이 하나의 스크립트만 로드하면 됨 (function() { 'use strict'; // ===== 캐시 설정 ===== const CACHE_DURATION = 10 * 60 * 1000; // 10분 const COMPONENT_CACHE_PREFIX = 'component_'; // ===== 인증 함수 ===== function isLoggedIn() { const token = localStorage.getItem('token'); return token && token !== 'undefined' && token !== 'null'; } function getUser() { const user = localStorage.getItem('user'); return user ? JSON.parse(user) : null; } function clearAuthData() { localStorage.removeItem('token'); localStorage.removeItem('user'); localStorage.removeItem('userPageAccess'); } // ===== 페이지 권한 캐시 ===== let pageAccessPromise = null; async function getPageAccess(currentUser) { if (!currentUser || !currentUser.user_id) return null; // 캐시 확인 const cached = localStorage.getItem('userPageAccess'); if (cached) { try { const cacheData = JSON.parse(cached); if (Date.now() - cacheData.timestamp < CACHE_DURATION) { return cacheData.pages; } } catch (e) { localStorage.removeItem('userPageAccess'); } } // 이미 로딩 중이면 기존 Promise 반환 if (pageAccessPromise) return pageAccessPromise; // 새로운 API 호출 pageAccessPromise = (async () => { try { const response = await fetch(`${window.API_BASE_URL}/users/${currentUser.user_id}/page-access`, { method: 'GET', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('token')}` } }); if (!response.ok) return null; const data = await response.json(); const pages = data.data.pageAccess || []; localStorage.setItem('userPageAccess', JSON.stringify({ pages: pages, timestamp: Date.now() })); return pages; } catch (error) { console.error('페이지 권한 조회 오류:', error); return null; } finally { pageAccessPromise = null; } })(); return pageAccessPromise; } async function getAccessiblePageKeys(currentUser) { const pages = await getPageAccess(currentUser); if (!pages) return []; return pages.filter(p => p.can_access === 1).map(p => p.page_key); } // ===== 현재 페이지 키 추출 ===== function getCurrentPageKey() { const path = window.location.pathname; if (!path.startsWith('/pages/')) return null; const pagePath = path.substring(7).replace('.html', ''); return pagePath.replace(/\//g, '.'); } // ===== 컴포넌트 로더 ===== async function loadComponent(name, selector, processor) { const container = document.querySelector(selector); if (!container) return; const paths = { 'navbar': '/components/navbar.html', 'sidebar-nav': '/components/sidebar-nav.html' }; const componentPath = paths[name]; if (!componentPath) return; try { const cacheKey = COMPONENT_CACHE_PREFIX + name; let html = sessionStorage.getItem(cacheKey); if (!html) { const response = await fetch(componentPath); if (!response.ok) throw new Error('컴포넌트 로드 실패'); html = await response.text(); try { sessionStorage.setItem(cacheKey, html); } catch (e) {} } if (processor) { const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); await processor(doc); container.innerHTML = doc.body.innerHTML; } else { container.innerHTML = html; } } catch (error) { console.error(`컴포넌트 로드 오류 (${name}):`, error); } } // ===== 네비바 처리 ===== const ROLE_NAMES = { 'system admin': '시스템 관리자', 'admin': '관리자', 'leader': '그룹장', 'user': '작업자', 'support': '지원팀', 'default': '사용자' }; async function processNavbar(doc, currentUser, accessiblePageKeys) { const userRole = (currentUser.role || '').toLowerCase(); const isAdmin = userRole === 'admin' || userRole === 'system admin'; if (isAdmin) { doc.querySelectorAll('.admin-only').forEach(el => el.classList.add('visible')); } else { doc.querySelectorAll('[data-page-key]').forEach(item => { const pageKey = item.getAttribute('data-page-key'); if (pageKey === 'dashboard' || pageKey.startsWith('profile.')) return; if (!accessiblePageKeys.includes(pageKey)) item.remove(); }); doc.querySelectorAll('.admin-only').forEach(el => el.remove()); } // 사용자 정보 표시 const displayName = currentUser.name || currentUser.username; const roleName = ROLE_NAMES[userRole] || ROLE_NAMES.default; const setElementText = (id, text) => { const el = doc.getElementById(id); if (el) el.textContent = text; }; setElementText('userName', displayName); setElementText('userRole', roleName); setElementText('userInitial', displayName.charAt(0)); } // ===== 사이드바 처리 ===== async function processSidebar(doc, currentUser, accessiblePageKeys) { const userRole = (currentUser.role || '').toLowerCase(); const accessLevel = (currentUser.access_level || '').toLowerCase(); // role 또는 access_level로 관리자 확인 const isAdmin = userRole === 'admin' || userRole === 'system admin' || userRole === 'system' || accessLevel === 'admin' || accessLevel === 'system'; if (isAdmin) { doc.querySelectorAll('.admin-only').forEach(el => el.classList.add('visible')); } else { doc.querySelectorAll('[data-page-key]').forEach(item => { const pageKey = item.getAttribute('data-page-key'); if (pageKey === 'dashboard' || pageKey.startsWith('profile.')) return; if (!accessiblePageKeys.includes(pageKey)) item.style.display = 'none'; }); doc.querySelectorAll('.nav-category.admin-only').forEach(el => el.remove()); } // 현재 페이지 하이라이트 const currentPath = window.location.pathname; doc.querySelectorAll('.nav-item').forEach(item => { const href = item.getAttribute('href'); if (href && currentPath.includes(href.replace(/^\//, ''))) { item.classList.add('active'); const category = item.closest('.nav-category'); if (category) category.classList.add('expanded'); } }); // 저장된 상태 복원 (기본값: 접힌 상태) const isCollapsed = localStorage.getItem('sidebarCollapsed') !== 'false'; const sidebar = doc.querySelector('.sidebar-nav'); if (isCollapsed && sidebar) { sidebar.classList.add('collapsed'); document.body.classList.add('sidebar-collapsed'); } const expandedCategories = JSON.parse(localStorage.getItem('sidebarExpanded') || '[]'); expandedCategories.forEach(category => { const el = doc.querySelector(`[data-category="${category}"]`); if (el) el.classList.add('expanded'); }); } // ===== 사이드바 이벤트 설정 ===== function setupSidebarEvents() { const sidebar = document.getElementById('sidebarNav'); const toggle = document.getElementById('sidebarToggle'); if (!sidebar || !toggle) return; toggle.addEventListener('click', () => { sidebar.classList.toggle('collapsed'); document.body.classList.toggle('sidebar-collapsed'); localStorage.setItem('sidebarCollapsed', sidebar.classList.contains('collapsed')); }); sidebar.querySelectorAll('.nav-category-header').forEach(header => { header.addEventListener('click', () => { const category = header.closest('.nav-category'); category.classList.toggle('expanded'); const expanded = []; sidebar.querySelectorAll('.nav-category.expanded').forEach(cat => { const name = cat.getAttribute('data-category'); if (name) expanded.push(name); }); localStorage.setItem('sidebarExpanded', JSON.stringify(expanded)); }); }); } // ===== 네비바 이벤트 설정 ===== function setupNavbarEvents() { const logoutButton = document.getElementById('logoutBtn'); if (logoutButton) { logoutButton.addEventListener('click', () => { if (confirm('로그아웃 하시겠습니까?')) { clearAuthData(); window.location.href = '/index.html'; } }); } // 알림 버튼 이벤트 const notificationBtn = document.getElementById('notificationBtn'); const notificationDropdown = document.getElementById('notificationDropdown'); const notificationWrapper = document.getElementById('notificationWrapper'); if (notificationBtn && notificationDropdown) { notificationBtn.addEventListener('click', (e) => { e.stopPropagation(); notificationDropdown.classList.toggle('show'); }); document.addEventListener('click', (e) => { if (notificationWrapper && !notificationWrapper.contains(e.target)) { notificationDropdown.classList.remove('show'); } }); } } // ===== 알림 로드 ===== async function loadNotifications() { try { const token = localStorage.getItem('token'); if (!token) return; const response = await fetch(`${window.API_BASE_URL}/notifications/unread`, { headers: { 'Authorization': `Bearer ${token}` } }); if (!response.ok) return; const result = await response.json(); if (result.success) { const notifications = result.data || []; updateNotificationBadge(notifications.length); renderNotificationList(notifications); } } catch (error) { console.warn('알림 로드 오류:', error.message); } } function updateNotificationBadge(count) { const badge = document.getElementById('notificationBadge'); const btn = document.getElementById('notificationBtn'); if (!badge) return; if (count > 0) { badge.textContent = count > 99 ? '99+' : count; badge.style.display = 'flex'; btn?.classList.add('has-notifications'); } else { badge.style.display = 'none'; btn?.classList.remove('has-notifications'); } } function renderNotificationList(notifications) { const list = document.getElementById('notificationList'); if (!list) return; if (notifications.length === 0) { list.innerHTML = '