feat: 목록 관리 3개 하위 페이지 권한 시스템 구현
목록 관리를 수신함, 관리함, 폐기함 3개 하위 페이지로 세분화하고 각각 별도의 권한 관리가 가능하도록 시스템 구현 Backend Changes: - page_permissions.py: issues_inbox, issues_management, issues_archive 권한 추가 - 수신함: 기본 접근 허용 (true) - 관리함, 폐기함: 관리자 권한 필요 (false) Frontend Changes: - permissions.js: 3개 하위 페이지 권한 정의 추가 - common-header.js: 드롭다운 하위 메뉴 구조 구현 * 데스크톱: 호버 드롭다운 메뉴 * 모바일: 접을 수 있는 하위 메뉴 - admin.html: 권한 관리 UI에 새 페이지들 추가 Features: - 권한별 하위 메뉴 필터링 - 반응형 드롭다운 메뉴 - 개별 페이지별 권한 제어 - 관리자 페이지에서 세부 권한 설정 가능 Next: 실제 페이지 파일 생성 및 기능 구현 예정
This commit is contained in:
@@ -45,6 +45,9 @@ DEFAULT_PAGES = {
|
||||
'issues_create': {'title': '부적합 등록', 'default_access': True},
|
||||
'issues_view': {'title': '부적합 조회', 'default_access': True},
|
||||
'issues_manage': {'title': '부적합 관리', 'default_access': True},
|
||||
'issues_inbox': {'title': '수신함', 'default_access': True},
|
||||
'issues_management': {'title': '관리함', 'default_access': False},
|
||||
'issues_archive': {'title': '폐기함', 'default_access': False},
|
||||
'projects_manage': {'title': '프로젝트 관리', 'default_access': False},
|
||||
'daily_work': {'title': '일일 공수', 'default_access': False},
|
||||
'reports': {'title': '보고서', 'default_access': False}
|
||||
|
||||
@@ -525,7 +525,10 @@
|
||||
const defaultPages = {
|
||||
'issues_create': { title: '부적합 등록', defaultAccess: true },
|
||||
'issues_view': { title: '부적합 조회', defaultAccess: true },
|
||||
'issues_manage': { title: '부적합 관리', defaultAccess: false },
|
||||
'issues_manage': { title: '부적합 관리', defaultAccess: true },
|
||||
'issues_inbox': { title: '수신함', defaultAccess: true },
|
||||
'issues_management': { title: '관리함', defaultAccess: false },
|
||||
'issues_archive': { title: '폐기함', defaultAccess: false },
|
||||
'projects_manage': { title: '프로젝트 관리', defaultAccess: false },
|
||||
'daily_work': { title: '일일 공수', defaultAccess: false },
|
||||
'reports': { title: '보고서', defaultAccess: false }
|
||||
|
||||
@@ -49,7 +49,33 @@ class CommonHeader {
|
||||
url: '/index.html#list',
|
||||
pageName: 'issues_manage',
|
||||
color: 'text-orange-600',
|
||||
bgColor: 'bg-orange-50 hover:bg-orange-100'
|
||||
bgColor: 'bg-orange-50 hover:bg-orange-100',
|
||||
subMenus: [
|
||||
{
|
||||
id: 'issues_inbox',
|
||||
title: '수신함',
|
||||
icon: 'fas fa-inbox',
|
||||
url: '/issues-inbox.html',
|
||||
pageName: 'issues_inbox',
|
||||
color: 'text-blue-600'
|
||||
},
|
||||
{
|
||||
id: 'issues_management',
|
||||
title: '관리함',
|
||||
icon: 'fas fa-cog',
|
||||
url: '/issues-management.html',
|
||||
pageName: 'issues_management',
|
||||
color: 'text-green-600'
|
||||
},
|
||||
{
|
||||
id: 'issues_archive',
|
||||
title: '폐기함',
|
||||
icon: 'fas fa-archive',
|
||||
url: '/issues-archive.html',
|
||||
pageName: 'issues_archive',
|
||||
color: 'text-gray-600'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'reports',
|
||||
@@ -229,6 +255,10 @@ class CommonHeader {
|
||||
return this.menuItems.filter(menu => {
|
||||
// admin은 모든 메뉴 접근 가능
|
||||
if (this.currentUser?.role === 'admin') {
|
||||
// 하위 메뉴가 있는 경우 하위 메뉴도 필터링
|
||||
if (menu.subMenus) {
|
||||
menu.accessibleSubMenus = menu.subMenus;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -237,7 +267,20 @@ class CommonHeader {
|
||||
return ['issues_create', 'issues_view'].includes(menu.id);
|
||||
}
|
||||
|
||||
return window.canAccessPage(menu.pageName);
|
||||
// 메인 메뉴 권한 체크
|
||||
const hasMainAccess = window.canAccessPage(menu.pageName);
|
||||
|
||||
// 하위 메뉴가 있는 경우 접근 가능한 하위 메뉴 필터링
|
||||
if (menu.subMenus) {
|
||||
menu.accessibleSubMenus = menu.subMenus.filter(subMenu =>
|
||||
window.canAccessPage(subMenu.pageName)
|
||||
);
|
||||
|
||||
// 메인 메뉴 접근 권한이 없어도 하위 메뉴 중 하나라도 접근 가능하면 표시
|
||||
return hasMainAccess || menu.accessibleSubMenus.length > 0;
|
||||
}
|
||||
|
||||
return hasMainAccess;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -248,6 +291,36 @@ class CommonHeader {
|
||||
const isActive = this.currentPage === menu.id;
|
||||
const activeClass = isActive ? 'bg-blue-100 text-blue-700' : `${menu.bgColor} ${menu.color}`;
|
||||
|
||||
// 하위 메뉴가 있는 경우 드롭다운 메뉴 생성
|
||||
if (menu.accessibleSubMenus && menu.accessibleSubMenus.length > 0) {
|
||||
return `
|
||||
<div class="relative group">
|
||||
<button class="nav-item flex items-center px-3 py-2 rounded-md text-sm font-medium transition-all duration-200 ${activeClass}"
|
||||
data-page="${menu.id}">
|
||||
<i class="${menu.icon} mr-2"></i>
|
||||
${menu.title}
|
||||
<i class="fas fa-chevron-down ml-1 text-xs"></i>
|
||||
</button>
|
||||
|
||||
<!-- 드롭다운 메뉴 -->
|
||||
<div class="absolute left-0 mt-1 w-48 bg-white rounded-md shadow-lg border border-gray-200 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 z-50">
|
||||
<div class="py-1">
|
||||
${menu.accessibleSubMenus.map(subMenu => `
|
||||
<a href="${subMenu.url}"
|
||||
class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 ${this.currentPage === subMenu.id ? 'bg-blue-50 text-blue-700' : ''}"
|
||||
data-page="${subMenu.id}"
|
||||
onclick="CommonHeader.navigateToPage(event, '${subMenu.url}', '${subMenu.id}')">
|
||||
<i class="${subMenu.icon} mr-3 ${subMenu.color}"></i>
|
||||
${subMenu.title}
|
||||
</a>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// 일반 메뉴 아이템
|
||||
return `
|
||||
<a href="${menu.url}"
|
||||
class="nav-item flex items-center px-3 py-2 rounded-md text-sm font-medium transition-all duration-200 ${activeClass}"
|
||||
@@ -266,6 +339,37 @@ class CommonHeader {
|
||||
const isActive = this.currentPage === menu.id;
|
||||
const activeClass = isActive ? 'bg-blue-50 text-blue-700 border-blue-500' : 'text-gray-700 hover:bg-gray-50';
|
||||
|
||||
// 하위 메뉴가 있는 경우
|
||||
if (menu.accessibleSubMenus && menu.accessibleSubMenus.length > 0) {
|
||||
return `
|
||||
<div class="mobile-submenu-container">
|
||||
<button class="w-full flex items-center justify-between px-3 py-2 rounded-md text-base font-medium border-l-4 border-transparent ${activeClass}"
|
||||
onclick="this.nextElementSibling.classList.toggle('hidden')"
|
||||
data-page="${menu.id}">
|
||||
<div class="flex items-center">
|
||||
<i class="${menu.icon} mr-3"></i>
|
||||
${menu.title}
|
||||
</div>
|
||||
<i class="fas fa-chevron-down text-xs"></i>
|
||||
</button>
|
||||
|
||||
<!-- 하위 메뉴 -->
|
||||
<div class="hidden ml-6 mt-1 space-y-1">
|
||||
${menu.accessibleSubMenus.map(subMenu => `
|
||||
<a href="${subMenu.url}"
|
||||
class="flex items-center px-3 py-2 rounded-md text-sm font-medium text-gray-600 hover:bg-gray-100 ${this.currentPage === subMenu.id ? 'bg-blue-50 text-blue-700' : ''}"
|
||||
data-page="${subMenu.id}"
|
||||
onclick="CommonHeader.navigateToPage(event, '${subMenu.url}', '${subMenu.id}')">
|
||||
<i class="${subMenu.icon} mr-3 ${subMenu.color}"></i>
|
||||
${subMenu.title}
|
||||
</a>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// 일반 메뉴 아이템
|
||||
return `
|
||||
<a href="${menu.url}"
|
||||
class="nav-item block px-3 py-2 rounded-md text-base font-medium border-l-4 border-transparent ${activeClass}"
|
||||
|
||||
@@ -18,6 +18,9 @@ class PagePermissionManager {
|
||||
'issues_create': { title: '부적합 등록', defaultAccess: true },
|
||||
'issues_view': { title: '부적합 조회', defaultAccess: true },
|
||||
'issues_manage': { title: '부적합 관리', defaultAccess: true },
|
||||
'issues_inbox': { title: '수신함', defaultAccess: true },
|
||||
'issues_management': { title: '관리함', defaultAccess: false },
|
||||
'issues_archive': { title: '폐기함', defaultAccess: false },
|
||||
'projects_manage': { title: '프로젝트 관리', defaultAccess: false },
|
||||
'daily_work': { title: '일일 공수', defaultAccess: false },
|
||||
'reports': { title: '보고서', defaultAccess: false },
|
||||
|
||||
Reference in New Issue
Block a user