feat: 프론트엔드 모듈화 및 공통 헤더 시스템 구현
- 권한 기반 공통 헤더 컴포넌트 구현 - 모바일 친화적 캘린더 날짜 필터 추가 - 페이지 프리로더 및 키보드 단축키 시스템 구현 - Service Worker 기반 캐싱 시스템 추가 Frontend Changes: - components/common-header.js: 권한 기반 동적 메뉴 생성 - components/mobile-calendar.js: 터치/스와이프 지원 캘린더 - core/permissions.js: 페이지 접근 권한 관리 - core/page-manager.js: 페이지 라이프사이클 관리 - core/page-preloader.js: 페이지 프리로딩 최적화 - core/keyboard-shortcuts.js: 키보드 네비게이션 - css/mobile-calendar.css: 모바일 최적화 캘린더 스타일 - sw.js: 3단계 캐싱 전략 서비스 워커 Removed: - auth-common.js, common-header.js (구버전 파일들)
This commit is contained in:
260
frontend/static/js/core/permissions.js
Normal file
260
frontend/static/js/core/permissions.js
Normal file
@@ -0,0 +1,260 @@
|
||||
/**
|
||||
* 단순화된 페이지 권한 관리 시스템
|
||||
* admin/user 구조에서 페이지별 접근 권한을 관리
|
||||
*/
|
||||
|
||||
class PagePermissionManager {
|
||||
constructor() {
|
||||
this.currentUser = null;
|
||||
this.pagePermissions = new Map();
|
||||
this.defaultPages = this.initDefaultPages();
|
||||
}
|
||||
|
||||
/**
|
||||
* 기본 페이지 목록 초기화
|
||||
*/
|
||||
initDefaultPages() {
|
||||
return {
|
||||
'issues_create': { title: '부적합 등록', defaultAccess: true },
|
||||
'issues_view': { title: '부적합 조회', defaultAccess: true },
|
||||
'issues_manage': { title: '부적합 관리', defaultAccess: false },
|
||||
'projects_manage': { title: '프로젝트 관리', defaultAccess: false },
|
||||
'daily_work': { title: '일일 공수', defaultAccess: false },
|
||||
'reports': { title: '보고서', defaultAccess: false },
|
||||
'users_manage': { title: '사용자 관리', defaultAccess: false }
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용자 설정
|
||||
* @param {Object} user - 사용자 객체
|
||||
*/
|
||||
setUser(user) {
|
||||
this.currentUser = user;
|
||||
this.loadPagePermissions();
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용자별 페이지 권한 로드
|
||||
*/
|
||||
async loadPagePermissions() {
|
||||
if (!this.currentUser) return;
|
||||
|
||||
try {
|
||||
// API에서 사용자별 페이지 권한 가져오기
|
||||
const response = await fetch(`/api/users/${this.currentUser.id}/page-permissions`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
|
||||
}
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const pagePermissions = await response.json();
|
||||
this.pagePermissions.clear(); // 기존 권한 초기화
|
||||
pagePermissions.forEach(perm => {
|
||||
this.pagePermissions.set(perm.page_name, perm.can_access);
|
||||
});
|
||||
console.log('페이지 권한 로드 완료:', this.pagePermissions);
|
||||
} else {
|
||||
console.warn('페이지 권한 로드 실패, 기본 권한 사용');
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('페이지 권한 로드 실패, 기본 권한 사용:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 페이지 접근 권한 체크
|
||||
* @param {string} pageName - 체크할 페이지명
|
||||
* @returns {boolean} 접근 권한 여부
|
||||
*/
|
||||
canAccessPage(pageName) {
|
||||
if (!this.currentUser) return false;
|
||||
|
||||
// admin은 모든 페이지 접근 가능
|
||||
if (this.currentUser.role === 'admin') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 개별 페이지 권한이 설정되어 있으면 우선 적용
|
||||
if (this.pagePermissions.has(pageName)) {
|
||||
return this.pagePermissions.get(pageName);
|
||||
}
|
||||
|
||||
// 기본 권한 확인
|
||||
const pageConfig = this.defaultPages[pageName];
|
||||
return pageConfig ? pageConfig.defaultAccess : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* UI 요소 페이지 권한 제어
|
||||
* @param {string} selector - CSS 선택자
|
||||
* @param {string} pageName - 필요한 페이지 권한
|
||||
* @param {string} action - 'show'|'hide'|'disable'|'enable'
|
||||
*/
|
||||
controlElement(selector, pageName, action = 'show') {
|
||||
const elements = document.querySelectorAll(selector);
|
||||
const hasAccess = this.canAccessPage(pageName);
|
||||
|
||||
elements.forEach(element => {
|
||||
switch (action) {
|
||||
case 'show':
|
||||
element.style.display = hasAccess ? '' : 'none';
|
||||
break;
|
||||
case 'hide':
|
||||
element.style.display = hasAccess ? 'none' : '';
|
||||
break;
|
||||
case 'disable':
|
||||
element.disabled = !hasAccess;
|
||||
if (!hasAccess) {
|
||||
element.classList.add('opacity-50', 'cursor-not-allowed');
|
||||
}
|
||||
break;
|
||||
case 'enable':
|
||||
element.disabled = hasAccess;
|
||||
if (hasAccess) {
|
||||
element.classList.remove('opacity-50', 'cursor-not-allowed');
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 메뉴 구성 생성
|
||||
* @returns {Array} 페이지 권한에 따른 메뉴 구성
|
||||
*/
|
||||
getMenuConfig() {
|
||||
const menuItems = [
|
||||
{
|
||||
id: 'issues_create',
|
||||
title: '부적합 등록',
|
||||
icon: 'fas fa-plus-circle',
|
||||
path: '#issues/create',
|
||||
pageName: 'issues_create'
|
||||
},
|
||||
{
|
||||
id: 'issues_view',
|
||||
title: '부적합 조회',
|
||||
icon: 'fas fa-search',
|
||||
path: '#issues/view',
|
||||
pageName: 'issues_view'
|
||||
},
|
||||
{
|
||||
id: 'issues_manage',
|
||||
title: '부적합 관리',
|
||||
icon: 'fas fa-tasks',
|
||||
path: '#issues/manage',
|
||||
pageName: 'issues_manage'
|
||||
},
|
||||
{
|
||||
id: 'projects_manage',
|
||||
title: '프로젝트 관리',
|
||||
icon: 'fas fa-folder-open',
|
||||
path: '#projects/manage',
|
||||
pageName: 'projects_manage'
|
||||
},
|
||||
{
|
||||
id: 'daily_work',
|
||||
title: '일일 공수',
|
||||
icon: 'fas fa-calendar-check',
|
||||
path: '#daily-work',
|
||||
pageName: 'daily_work'
|
||||
},
|
||||
{
|
||||
id: 'reports',
|
||||
title: '보고서',
|
||||
icon: 'fas fa-chart-bar',
|
||||
path: '#reports',
|
||||
pageName: 'reports'
|
||||
},
|
||||
{
|
||||
id: 'users_manage',
|
||||
title: '사용자 관리',
|
||||
icon: 'fas fa-users-cog',
|
||||
path: '#users/manage',
|
||||
pageName: 'users_manage'
|
||||
}
|
||||
];
|
||||
|
||||
// 페이지 권한에 따라 메뉴 필터링
|
||||
return menuItems.filter(item => this.canAccessPage(item.pageName));
|
||||
}
|
||||
|
||||
/**
|
||||
* 페이지 권한 부여
|
||||
* @param {number} userId - 사용자 ID
|
||||
* @param {string} pageName - 페이지명
|
||||
* @param {boolean} canAccess - 접근 허용 여부
|
||||
* @param {string} notes - 메모
|
||||
*/
|
||||
async grantPageAccess(userId, pageName, canAccess, notes = '') {
|
||||
if (this.currentUser.role !== 'admin') {
|
||||
throw new Error('관리자만 권한을 설정할 수 있습니다.');
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/page-permissions/grant', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
user_id: userId,
|
||||
page_name: pageName,
|
||||
can_access: canAccess,
|
||||
notes: notes
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('페이지 권한 설정 실패');
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('페이지 권한 설정 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용자 페이지 권한 목록 조회
|
||||
* @param {number} userId - 사용자 ID
|
||||
* @returns {Array} 페이지 권한 목록
|
||||
*/
|
||||
async getUserPagePermissions(userId) {
|
||||
try {
|
||||
const response = await fetch(`/api/users/${userId}/page-permissions`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('페이지 권한 목록 조회 실패');
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('페이지 권한 목록 조회 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 모든 페이지 목록과 설명 가져오기
|
||||
* @returns {Object} 페이지 목록
|
||||
*/
|
||||
getAllPages() {
|
||||
return this.defaultPages;
|
||||
}
|
||||
}
|
||||
|
||||
// 전역 페이지 권한 관리자 인스턴스
|
||||
window.pagePermissionManager = new PagePermissionManager();
|
||||
|
||||
// 편의 함수들
|
||||
window.canAccessPage = (pageName) => window.pagePermissionManager.canAccessPage(pageName);
|
||||
window.controlElement = (selector, pageName, action) => window.pagePermissionManager.controlElement(selector, pageName, action);
|
||||
Reference in New Issue
Block a user