// /js/load-sidebar.js // 사이드바 네비게이션 로더 및 컨트롤러 import { getUser } from './auth.js'; import { loadComponent } from './component-loader.js'; /** * 사이드바 DOM을 사용자 권한에 맞게 처리 */ async function processSidebarDom(doc) { const currentUser = getUser(); if (!currentUser) return; 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'; // 1. 관리자 전용 메뉴 표시/숨김 if (isAdmin) { doc.querySelectorAll('.admin-only').forEach(el => el.classList.add('visible')); } else { // 비관리자: 페이지 접근 권한에 따라 메뉴 필터링 await filterMenuByPageAccess(doc, currentUser); } // 2. 현재 페이지 활성화 highlightCurrentPage(doc); // 3. 저장된 상태 복원 restoreSidebarState(doc); } /** * 사용자의 페이지 접근 권한에 따라 메뉴 필터링 */ async function filterMenuByPageAccess(doc, currentUser) { try { const cached = localStorage.getItem('userPageAccess'); let accessiblePages = null; if (cached) { const cacheData = JSON.parse(cached); if (Date.now() - cacheData.timestamp < 5 * 60 * 1000) { accessiblePages = cacheData.pages; } } if (!accessiblePages) { 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; const data = await response.json(); accessiblePages = data.data.pageAccess || []; localStorage.setItem('userPageAccess', JSON.stringify({ pages: accessiblePages, timestamp: Date.now() })); } const accessiblePageKeys = accessiblePages .filter(p => p.can_access === 1) .map(p => p.page_key); // 메뉴 항목 필터링 const menuItems = doc.querySelectorAll('[data-page-key]'); menuItems.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()); } catch (error) { console.error('사이드바 메뉴 필터링 오류:', error); } } /** * 현재 페이지 하이라이트 */ function highlightCurrentPage(doc) { 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'); } } }); } /** * 사이드바 상태 복원 (기본값: 접힌 상태) */ function restoreSidebarState(doc) { 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 categoryName = cat.getAttribute('data-category'); if (categoryName) expanded.push(categoryName); }); localStorage.setItem('sidebarExpanded', JSON.stringify(expanded)); }); }); // 링크 프리페치 - 마우스 올리면 미리 로드 const prefetchedUrls = new Set(); sidebar.querySelectorAll('a.nav-item').forEach(link => { link.addEventListener('mouseenter', () => { const href = link.getAttribute('href'); if (href && !prefetchedUrls.has(href) && !href.startsWith('#')) { prefetchedUrls.add(href); const prefetchLink = document.createElement('link'); prefetchLink.rel = 'prefetch'; prefetchLink.href = href; document.head.appendChild(prefetchLink); } }, { once: true }); }); } /** * 사이드바 초기화 */ async function initSidebar() { // 사이드바 컨테이너가 없으면 생성 let container = document.getElementById('sidebar-container'); if (!container) { container = document.createElement('div'); container.id = 'sidebar-container'; document.body.prepend(container); } if (getUser()) { await loadComponent('sidebar-nav', '#sidebar-container', processSidebarDom); document.body.classList.add('has-sidebar'); setupSidebarEvents(); } } // DOMContentLoaded 시 초기화 document.addEventListener('DOMContentLoaded', initSidebar); export { initSidebar };