feat: 다수 기능 개선 - 순찰, 출근, 작업분석, 모바일 UI 등
- 순찰/점검 기능 개선 (zone-detail 페이지 추가) - 출근/근태 시스템 개선 (연차 조회, 근무현황) - 작업분석 대분류 그룹화 및 마이그레이션 스크립트 - 모바일 네비게이션 UI 추가 - NAS 배포 도구 및 문서 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
209
deploy/tkfb-package/web-ui/js/load-sidebar.js
Normal file
209
deploy/tkfb-package/web-ui/js/load-sidebar.js
Normal file
@@ -0,0 +1,209 @@
|
||||
// /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 };
|
||||
Reference in New Issue
Block a user