// /js/load-navbar.js import { getUser, clearAuthData } from './auth.js'; import { loadComponent } from './component-loader.js'; import { config } from './config.js'; // 역할 이름을 한글로 변환하는 맵 const ROLE_NAMES = { 'system admin': '시스템 관리자', 'admin': '관리자', 'system': '시스템 관리자', 'leader': '그룹장', 'user': '작업자', 'support': '지원팀', 'default': '사용자', }; /** * 네비게이션 바 DOM을 사용자 정보와 역할에 맞게 수정하는 프로세서입니다. * @param {Document} doc - 파싱된 HTML 문서 객체 */ async function processNavbarDom(doc) { const currentUser = getUser(); if (!currentUser) return; // 1. 역할 및 페이지 권한 기반 메뉴 필터링 await filterMenuByPageAccess(doc, currentUser); // 2. 사용자 정보 채우기 populateUserInfo(doc, currentUser); } /** * 사용자의 페이지 접근 권한에 따라 메뉴 항목을 필터링합니다. * @param {Document} doc - 파싱된 HTML 문서 객체 * @param {object} currentUser - 현재 사용자 객체 */ async function filterMenuByPageAccess(doc, currentUser) { const userRole = (currentUser.role || '').toLowerCase(); // Admin은 모든 메뉴 표시 + .admin-only 요소 활성화 if (userRole === 'admin' || userRole === 'system admin') { doc.querySelectorAll('.admin-only').forEach(el => el.classList.add('visible')); return; } try { // 사용자의 페이지 접근 권한 조회 const cached = localStorage.getItem('userPageAccess'); let accessiblePages = null; if (cached) { const cacheData = JSON.parse(cached); // 캐시가 5분 이내인 경우 사용 if (Date.now() - cacheData.timestamp < 5 * 60 * 1000) { accessiblePages = cacheData.pages; } } // 캐시가 없으면 API 호출 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) { console.error('페이지 권한 조회 실패:', response.status); 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); // 메뉴 항목에 data-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.remove(); } }); // Admin 전용 메뉴는 무조건 제거 doc.querySelectorAll('.admin-only').forEach(el => el.remove()); } catch (error) { console.error('메뉴 필터링 오류:', error); } } /** * 네비게이션 바에 사용자 정보를 채웁니다. * @param {Document} doc - 파싱된 HTML 문서 객체 * @param {object} user - 현재 사용자 객체 */ function populateUserInfo(doc, user) { const displayName = user.name || user.username; // 대소문자 구분 없이 처리 const roleLower = (user.role || '').toLowerCase(); const roleName = ROLE_NAMES[roleLower] || ROLE_NAMES.default; const elements = { 'userName': displayName, 'userRole': roleName, 'userInitial': displayName.charAt(0), }; for (const id in elements) { const el = doc.getElementById(id); if (el) el.textContent = elements[id]; } // 메인 대시보드 URL 설정 const dashboardBtn = doc.getElementById('dashboardBtn'); if (dashboardBtn) { dashboardBtn.href = '/pages/dashboard.html'; } } /** * 네비게이션 바와 관련된 모든 이벤트를 설정합니다. */ function setupNavbarEvents() { const logoutButton = document.getElementById('logoutBtn'); if (logoutButton) { logoutButton.addEventListener('click', () => { if (confirm('로그아웃 하시겠습니까?')) { clearAuthData(); window.location.href = config.paths.loginPage; } }); } } /** * 현재 날짜와 시간을 업데이트하는 함수 */ function updateDateTime() { const now = new Date(); // 시간 업데이트 (시 분 초 형식으로 고정) const timeElement = document.getElementById('timeValue'); if (timeElement) { const hours = String(now.getHours()).padStart(2, '0'); const minutes = String(now.getMinutes()).padStart(2, '0'); const seconds = String(now.getSeconds()).padStart(2, '0'); timeElement.textContent = `${hours}시 ${minutes}분 ${seconds}초`; } // 날짜 업데이트 const dateElement = document.getElementById('dateValue'); if (dateElement) { const days = ['일', '월', '화', '수', '목', '금', '토']; const month = now.getMonth() + 1; const date = now.getDate(); const day = days[now.getDay()]; dateElement.textContent = `${month}월 ${date}일 (${day})`; } } // 날씨 아이콘 매핑 const WEATHER_ICONS = { clear: '☀️', rain: '🌧️', snow: '❄️', heat: '🔥', cold: '🥶', wind: '💨', fog: '🌫️', dust: '😷', cloudy: '⛅', overcast: '☁️' }; // 날씨 조건명 const WEATHER_NAMES = { clear: '맑음', rain: '비', snow: '눈', heat: '폭염', cold: '한파', wind: '강풍', fog: '안개', dust: '미세먼지', cloudy: '구름많음', overcast: '흐림' }; /** * 날씨 정보를 가져와서 업데이트하는 함수 */ async function updateWeather() { try { const token = localStorage.getItem('token'); if (!token) return; const response = await fetch(`${window.API_BASE_URL}/tbm/weather/current`, { method: 'GET', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` } }); if (!response.ok) { throw new Error('날씨 API 호출 실패'); } const result = await response.json(); if (result.success && result.data) { const { temperature, conditions, weatherData } = result.data; // 온도 표시 const tempElement = document.getElementById('weatherTemp'); if (tempElement && temperature !== null && temperature !== undefined) { tempElement.textContent = `${Math.round(temperature)}°C`; } // 날씨 아이콘 및 설명 const iconElement = document.getElementById('weatherIcon'); const descElement = document.getElementById('weatherDesc'); if (conditions && conditions.length > 0) { const primaryCondition = conditions[0]; if (iconElement) { iconElement.textContent = WEATHER_ICONS[primaryCondition] || '🌤️'; } if (descElement) { descElement.textContent = WEATHER_NAMES[primaryCondition] || '맑음'; } } else { if (iconElement) iconElement.textContent = '☀️'; if (descElement) descElement.textContent = '맑음'; } // 날씨 섹션 표시 const weatherSection = document.getElementById('weatherSection'); if (weatherSection) { weatherSection.style.opacity = '1'; } } } catch (error) { console.warn('날씨 정보 로드 실패:', error.message); // 실패해도 기본값 표시 const descElement = document.getElementById('weatherDesc'); if (descElement) { descElement.textContent = '날씨 정보 없음'; } } } // ========================================== // 알림 시스템 // ========================================== /** * 알림 관련 이벤트 설정 */ function setupNotificationEvents() { const notificationBtn = document.getElementById('notificationBtn'); const notificationDropdown = document.getElementById('notificationDropdown'); const notificationWrapper = document.getElementById('notificationWrapper'); if (notificationBtn) { notificationBtn.addEventListener('click', (e) => { e.stopPropagation(); notificationDropdown?.classList.toggle('show'); }); } // 외부 클릭시 드롭다운 닫기 document.addEventListener('click', (e) => { if (notificationWrapper && notificationDropdown && !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`, { method: 'GET', headers: { 'Content-Type': 'application/json', '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 = '