feat(tkuser): 통합 관리 탭별 권한 시스템 추가
- DEFAULT_PAGES에 tkuser 시스템 10개 페이지 권한 정의 추가 - 권한 관리 UI에 tkuser 섹션 추가 (개인/부서 권한 모두) - 비admin 사용자 로그인 시 effective-permissions 기반 탭 표시 제어 - switchTab()에 권한 guard 추가하여 비허용 탭 접근 차단 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -20,10 +20,6 @@ const DEFAULT_PAGES = {
|
||||
's1.inspection.daily_patrol': { title: '일일순회점검', system: 'system1', group: '공장 관리', default_access: false },
|
||||
's1.inspection.checkin': { title: '출근 체크', system: 'system1', group: '공장 관리', default_access: true },
|
||||
's1.inspection.work_status': { title: '근무 현황', system: 'system1', group: '공장 관리', default_access: false },
|
||||
// 안전 관리
|
||||
's1.safety.visit_request': { title: '출입 신청', system: 'system1', group: '안전 관리', default_access: true },
|
||||
's1.safety.management': { title: '안전 관리', system: 'system1', group: '안전 관리', default_access: false },
|
||||
's1.safety.checklist_manage': { title: '체크리스트 관리', system: 'system1', group: '안전 관리', default_access: false },
|
||||
// 근태 관리
|
||||
's1.attendance.my_vacation_info': { title: '내 연차 정보', system: 'system1', group: '근태 관리', default_access: true },
|
||||
's1.attendance.monthly': { title: '월간 근태', system: 'system1', group: '근태 관리', default_access: true },
|
||||
@@ -63,8 +59,22 @@ const DEFAULT_PAGES = {
|
||||
'purchasing_partner_checkin': { title: '협력업체 체크인', system: 'tkpurchase', group: '협력업체', default_access: false },
|
||||
|
||||
// ===== tksafety - 안전 관리 =====
|
||||
'safety_visit': { title: '방문 관리', system: 'tksafety', group: '안전 관리', default_access: false },
|
||||
'safety_education': { title: '안전교육 관리', system: 'tksafety', group: '안전 관리', default_access: false },
|
||||
'safety_visit_request': { title: '출입 신청', system: 'tksafety', group: '안전 관리', default_access: true },
|
||||
'safety_visit_management': { title: '출입 관리', system: 'tksafety', group: '안전 관리', default_access: false },
|
||||
'safety_training': { title: '안전교육 실시', system: 'tksafety', group: '안전 관리', default_access: false },
|
||||
'safety_checklist': { title: '체크리스트 관리', system: 'tksafety', group: '안전 관리', default_access: false },
|
||||
|
||||
// ===== tkuser - 통합 관리 =====
|
||||
'tkuser.users': { title: '사용자 관리', system: 'tkuser', group: '통합 관리', default_access: false },
|
||||
'tkuser.projects': { title: '프로젝트 관리', system: 'tkuser', group: '통합 관리', default_access: false },
|
||||
'tkuser.workplaces': { title: '작업장 관리', system: 'tkuser', group: '통합 관리', default_access: false },
|
||||
'tkuser.workers': { title: '작업자 관리', system: 'tkuser', group: '통합 관리', default_access: false },
|
||||
'tkuser.departments': { title: '부서 관리', system: 'tkuser', group: '통합 관리', default_access: false },
|
||||
'tkuser.issue_types': { title: '이슈 유형 관리', system: 'tkuser', group: '통합 관리', default_access: false },
|
||||
'tkuser.tasks': { title: '작업 관리', system: 'tkuser', group: '통합 관리', default_access: false },
|
||||
'tkuser.vacations': { title: '휴가 관리', system: 'tkuser', group: '통합 관리', default_access: false },
|
||||
'tkuser.partners': { title: '협력업체 관리', system: 'tkuser', group: '통합 관리', default_access: false },
|
||||
'tkuser.notification_recipients': { title: '알림 수신자 관리', system: 'tkuser', group: '통합 관리', default_access: false },
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -64,6 +64,9 @@
|
||||
<button class="tab-btn px-4 py-2 rounded-lg text-sm font-medium whitespace-nowrap" onclick="switchTab('partners')">
|
||||
<i class="fas fa-truck mr-2"></i>협력업체
|
||||
</button>
|
||||
<button class="tab-btn px-4 py-2 rounded-lg text-sm font-medium whitespace-nowrap" onclick="switchTab('notificationRecipients')">
|
||||
<i class="fas fa-bell mr-2"></i>알림 수신자
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -222,6 +225,18 @@
|
||||
</div>
|
||||
<div id="dept-tksafety-perms" class="p-4 border border-t-0 border-orange-100 rounded-b-lg space-y-4"></div>
|
||||
</div>
|
||||
<!-- tkuser - 통합 관리 -->
|
||||
<div>
|
||||
<div class="flex items-center justify-between bg-slate-50 px-4 py-2 rounded-t-lg border border-slate-200">
|
||||
<h4 class="font-semibold text-slate-800"><i class="fas fa-cogs mr-2"></i>통합 관리 (tkuser)</h4>
|
||||
<div class="flex gap-2">
|
||||
<button onclick="toggleDeptSystemAll('tkuser', true)" class="text-xs text-slate-600 hover:underline">전체 허용</button>
|
||||
<span class="text-gray-300">|</span>
|
||||
<button onclick="toggleDeptSystemAll('tkuser', false)" class="text-xs text-slate-600 hover:underline">전체 해제</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="dept-tkuser-perms" class="p-4 border border-t-0 border-slate-200 rounded-b-lg space-y-4"></div>
|
||||
</div>
|
||||
<!-- 저장 -->
|
||||
<div class="flex items-center gap-3 pt-2">
|
||||
<button id="saveDeptPermBtn" class="px-6 py-2.5 bg-slate-700 text-white rounded-lg hover:bg-slate-800 text-sm font-medium">
|
||||
@@ -309,6 +324,18 @@
|
||||
</div>
|
||||
<div id="tksafety-perms" class="p-4 border border-t-0 border-orange-100 rounded-b-lg space-y-4"></div>
|
||||
</div>
|
||||
<!-- tkuser - 통합 관리 -->
|
||||
<div>
|
||||
<div class="flex items-center justify-between bg-slate-50 px-4 py-2 rounded-t-lg border border-slate-200">
|
||||
<h4 class="font-semibold text-slate-800"><i class="fas fa-cogs mr-2"></i>통합 관리 (tkuser)</h4>
|
||||
<div class="flex gap-2">
|
||||
<button onclick="toggleSystemAll('tkuser', true)" class="text-xs text-slate-600 hover:underline">전체 허용</button>
|
||||
<span class="text-gray-300">|</span>
|
||||
<button onclick="toggleSystemAll('tkuser', false)" class="text-xs text-slate-600 hover:underline">전체 해제</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="tkuser-perms" class="p-4 border border-t-0 border-slate-200 rounded-b-lg space-y-4"></div>
|
||||
</div>
|
||||
|
||||
<!-- 저장 버튼 -->
|
||||
<div class="flex items-center gap-3 pt-2">
|
||||
|
||||
@@ -80,6 +80,8 @@ function doLogout() {
|
||||
|
||||
/* ===== State ===== */
|
||||
let currentUser = null;
|
||||
// null = admin(제한없음), Set = 허용된 탭만
|
||||
let currentUserAllowedTabs = null;
|
||||
|
||||
/* ===== Init ===== */
|
||||
async function init() {
|
||||
@@ -124,7 +126,57 @@ async function init() {
|
||||
await loadDepartmentsCache();
|
||||
await loadUsers();
|
||||
} else {
|
||||
// 비밀번호 변경은 항상 표시
|
||||
document.getElementById('passwordChangeSection').classList.remove('hidden');
|
||||
|
||||
// tkuser 탭별 권한 확인
|
||||
try {
|
||||
const result = await api(`/permissions/users/${currentUser.id}/effective-permissions`);
|
||||
if (result.permissions) {
|
||||
// TKUSER_PAGES에서 TAB_PERM_MAP 동적 생성
|
||||
const TAB_PERM_MAP = {};
|
||||
if (typeof TKUSER_PAGES !== 'undefined') {
|
||||
Object.values(TKUSER_PAGES).flat().forEach(p => { TAB_PERM_MAP[p.key] = p.tab; });
|
||||
}
|
||||
|
||||
currentUserAllowedTabs = new Set();
|
||||
for (const [permKey, tabName] of Object.entries(TAB_PERM_MAP)) {
|
||||
const info = result.permissions[permKey];
|
||||
if (info && info.can_access) {
|
||||
currentUserAllowedTabs.add(tabName);
|
||||
}
|
||||
}
|
||||
|
||||
// 허용된 탭이 있으면 tabNav 표시
|
||||
if (currentUserAllowedTabs.size > 0) {
|
||||
document.getElementById('tabNav').classList.remove('hidden');
|
||||
// 비허용 탭 버튼 숨김
|
||||
document.querySelectorAll('.tab-btn').forEach(btn => {
|
||||
const onclick = btn.getAttribute('onclick') || '';
|
||||
const match = onclick.match(/switchTab\('(\w+)'\)/);
|
||||
if (match) {
|
||||
const tabName = match[1];
|
||||
// permissions 탭은 admin 전용
|
||||
if (tabName === 'permissions') {
|
||||
btn.style.display = 'none';
|
||||
} else if (!currentUserAllowedTabs.has(tabName)) {
|
||||
btn.style.display = 'none';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// tkuser.users 권한 시 adminSection 표시
|
||||
if (currentUserAllowedTabs.has('users')) {
|
||||
document.getElementById('adminSection').classList.remove('hidden');
|
||||
await loadDepartmentsCache();
|
||||
await loadUsers();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
console.error('[tkuser] 권한 확인 실패:', e);
|
||||
showToast('권한 정보를 불러올 수 없습니다', 'error');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[tkuser] init 오류:', e);
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
/* ===== Tab ===== */
|
||||
function switchTab(name) {
|
||||
// 권한 guard: currentUserAllowedTabs가 Set이면 허용된 탭만 접근
|
||||
if (typeof currentUserAllowedTabs !== 'undefined'
|
||||
&& currentUserAllowedTabs
|
||||
&& !currentUserAllowedTabs.has(name)) return;
|
||||
|
||||
document.querySelectorAll('[id^="tab-"]').forEach(el => el.classList.add('hidden'));
|
||||
document.getElementById('tab-' + name)?.classList.remove('hidden');
|
||||
document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));
|
||||
|
||||
@@ -79,6 +79,21 @@ const TKSAFETY_PAGES = {
|
||||
]
|
||||
};
|
||||
|
||||
const TKUSER_PAGES = {
|
||||
'통합 관리': [
|
||||
{ key: 'tkuser.users', title: '사용자 관리', icon: 'fa-users', def: false, tab: 'users' },
|
||||
{ key: 'tkuser.projects', title: '프로젝트 관리', icon: 'fa-folder-open', def: false, tab: 'projects' },
|
||||
{ key: 'tkuser.workplaces', title: '작업장 관리', icon: 'fa-building', def: false, tab: 'workplaces' },
|
||||
{ key: 'tkuser.workers', title: '작업자 관리', icon: 'fa-hard-hat', def: false, tab: 'workers' },
|
||||
{ key: 'tkuser.departments', title: '부서 관리', icon: 'fa-sitemap', def: false, tab: 'departments' },
|
||||
{ key: 'tkuser.issue_types', title: '이슈 유형 관리', icon: 'fa-exclamation-triangle', def: false, tab: 'issueTypes' },
|
||||
{ key: 'tkuser.tasks', title: '작업 관리', icon: 'fa-tasks', def: false, tab: 'tasks' },
|
||||
{ key: 'tkuser.vacations', title: '휴가 관리', icon: 'fa-umbrella-beach', def: false, tab: 'vacations' },
|
||||
{ key: 'tkuser.partners', title: '협력업체 관리', icon: 'fa-truck', def: false, tab: 'partners' },
|
||||
{ key: 'tkuser.notification_recipients', title: '알림 수신자 관리', icon: 'fa-bell', def: false, tab: 'notificationRecipients' },
|
||||
]
|
||||
};
|
||||
|
||||
/* ===== Permissions Tab State ===== */
|
||||
let permissionsTabLoaded = false;
|
||||
|
||||
@@ -204,7 +219,7 @@ document.getElementById('permissionUserSelect').addEventListener('change', async
|
||||
async function loadUserPermissions(userId) {
|
||||
currentPermissions = {};
|
||||
currentPermSources = {};
|
||||
const allDefs = { ...SYSTEM1_PAGES, ...SYSTEM3_PAGES, ...TKPURCHASE_PAGES, ...TKSAFETY_PAGES };
|
||||
const allDefs = { ...SYSTEM1_PAGES, ...SYSTEM3_PAGES, ...TKPURCHASE_PAGES, ...TKSAFETY_PAGES, ...TKUSER_PAGES };
|
||||
Object.values(allDefs).flat().forEach(p => { currentPermissions[p.key] = p.def; currentPermSources[p.key] = 'default'; });
|
||||
try {
|
||||
const result = await api(`/permissions/users/${userId}/effective-permissions`);
|
||||
@@ -222,6 +237,7 @@ function renderPermissionGrid() {
|
||||
renderSystemPerms('s3-perms', SYSTEM3_PAGES, 'purple');
|
||||
renderSystemPerms('tkpurchase-perms', TKPURCHASE_PAGES, 'green');
|
||||
renderSystemPerms('tksafety-perms', TKSAFETY_PAGES, 'orange');
|
||||
renderSystemPerms('tkuser-perms', TKUSER_PAGES, 'slate');
|
||||
}
|
||||
|
||||
function sourceLabel(src) {
|
||||
@@ -317,7 +333,7 @@ document.getElementById('savePermissionsBtn').addEventListener('click', async ()
|
||||
btn.disabled = true; btn.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i>저장 중...';
|
||||
|
||||
try {
|
||||
const allPages = [...Object.values(SYSTEM1_PAGES).flat(), ...Object.values(SYSTEM3_PAGES).flat(), ...Object.values(TKPURCHASE_PAGES).flat(), ...Object.values(TKSAFETY_PAGES).flat()];
|
||||
const allPages = [...Object.values(SYSTEM1_PAGES).flat(), ...Object.values(SYSTEM3_PAGES).flat(), ...Object.values(TKPURCHASE_PAGES).flat(), ...Object.values(TKSAFETY_PAGES).flat(), ...Object.values(TKUSER_PAGES).flat()];
|
||||
const permissions = allPages.map(p => {
|
||||
const cb = document.getElementById('perm_' + p.key);
|
||||
return { page_name: p.key, can_access: cb ? cb.checked : false };
|
||||
@@ -365,7 +381,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
async function loadDeptPermissions(deptId) {
|
||||
deptPermissions = {};
|
||||
const allDefs = { ...SYSTEM1_PAGES, ...SYSTEM3_PAGES, ...TKPURCHASE_PAGES, ...TKSAFETY_PAGES };
|
||||
const allDefs = { ...SYSTEM1_PAGES, ...SYSTEM3_PAGES, ...TKPURCHASE_PAGES, ...TKSAFETY_PAGES, ...TKUSER_PAGES };
|
||||
Object.values(allDefs).flat().forEach(p => { deptPermissions[p.key] = p.def; });
|
||||
try {
|
||||
const result = await api(`/permissions/departments/${deptId}/permissions`);
|
||||
@@ -378,6 +394,7 @@ function renderDeptPermissionGrid() {
|
||||
renderDeptSystemPerms('dept-s3-perms', SYSTEM3_PAGES, 'purple');
|
||||
renderDeptSystemPerms('dept-tkpurchase-perms', TKPURCHASE_PAGES, 'green');
|
||||
renderDeptSystemPerms('dept-tksafety-perms', TKSAFETY_PAGES, 'orange');
|
||||
renderDeptSystemPerms('dept-tkuser-perms', TKUSER_PAGES, 'slate');
|
||||
}
|
||||
|
||||
function renderDeptSystemPerms(containerId, pageDef, color) {
|
||||
@@ -456,7 +473,7 @@ async function saveDeptPermissions() {
|
||||
btn.disabled = true; btn.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i>저장 중...';
|
||||
|
||||
try {
|
||||
const allPages = [...Object.values(SYSTEM1_PAGES).flat(), ...Object.values(SYSTEM3_PAGES).flat(), ...Object.values(TKPURCHASE_PAGES).flat(), ...Object.values(TKSAFETY_PAGES).flat()];
|
||||
const allPages = [...Object.values(SYSTEM1_PAGES).flat(), ...Object.values(SYSTEM3_PAGES).flat(), ...Object.values(TKPURCHASE_PAGES).flat(), ...Object.values(TKSAFETY_PAGES).flat(), ...Object.values(TKUSER_PAGES).flat()];
|
||||
const permissions = allPages.map(p => {
|
||||
const cb = document.getElementById('dperm_' + p.key);
|
||||
return { page_name: p.key, can_access: cb ? cb.checked : false };
|
||||
|
||||
Reference in New Issue
Block a user