Files
TK-FB-Project/web-ui/js/load-sidebar.js
Hyungi Ahn 74d3a78aa3 feat: 페이지 구조 재구성 및 사이드바 네비게이션 구현
- 페이지 폴더 재구성: safety/, attendance/ 폴더 신규 생성
  - work/ → safety/: 이슈 신고, 출입 신청 관련 페이지 이동
  - common/ → attendance/: 근태/휴가 관련 페이지 이동
  - admin/ 정리: safety-* 파일들을 safety/로 이동

- 사이드바 네비게이션 메뉴 구현
  - 카테고리별 메뉴: 작업관리, 안전관리, 근태관리, 시스템관리
  - 접기/펼치기 기능 및 상태 저장
  - 관리자 전용 메뉴 자동 표시/숨김

- 날씨 API 연동 (기상청 단기예보)
  - TBM 및 navbar에 현재 날씨 표시
  - weatherService.js 추가

- 안전 체크리스트 확장
  - 기본/날씨별/작업별 체크 유형 추가
  - checklist-manage.html 페이지 추가

- 이슈 신고 시스템 구현
  - workIssueController, workIssueModel, workIssueRoutes 추가

- DB 마이그레이션 파일 추가 (실행 대기)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 14:27:22 +09:00

191 lines
5.4 KiB
JavaScript

// /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 isAdmin = userRole === 'admin' || userRole === 'system admin' || userRole === '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') === 'true';
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));
});
});
}
/**
* 사이드바 초기화
*/
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 };