From 3b0ac615bf7c1573cf0c82f0a30594668736ca6c Mon Sep 17 00:00:00 2001 From: Hyungi Ahn Date: Fri, 13 Mar 2026 10:20:21 +0900 Subject: [PATCH] =?UTF-8?q?feat(tkuser):=20=ED=86=B5=ED=95=A9=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20=ED=83=AD=EB=B3=84=20=EA=B6=8C=ED=95=9C=20=EC=8B=9C?= =?UTF-8?q?=EC=8A=A4=ED=85=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DEFAULT_PAGES에 tkuser 시스템 10개 페이지 권한 정의 추가 - 권한 관리 UI에 tkuser 섹션 추가 (개인/부서 권한 모두) - 비admin 사용자 로그인 시 effective-permissions 기반 탭 표시 제어 - switchTab()에 권한 guard 추가하여 비허용 탭 접근 차단 Co-Authored-By: Claude Opus 4.6 --- user-management/api/models/permissionModel.js | 22 +++++--- user-management/web/index.html | 27 ++++++++++ user-management/web/static/js/tkuser-core.js | 52 +++++++++++++++++++ user-management/web/static/js/tkuser-tabs.js | 5 ++ user-management/web/static/js/tkuser-users.js | 25 +++++++-- 5 files changed, 121 insertions(+), 10 deletions(-) diff --git a/user-management/api/models/permissionModel.js b/user-management/api/models/permissionModel.js index 1b57103..64fbcc7 100644 --- a/user-management/api/models/permissionModel.js +++ b/user-management/api/models/permissionModel.js @@ -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 }, }; /** diff --git a/user-management/web/index.html b/user-management/web/index.html index 2912587..50a8974 100644 --- a/user-management/web/index.html +++ b/user-management/web/index.html @@ -64,6 +64,9 @@ + @@ -222,6 +225,18 @@
+ +
+
+

통합 관리 (tkuser)

+
+ + | + +
+
+
+
+ +
+
+

통합 관리 (tkuser)

+
+ + | + +
+
+
+
diff --git a/user-management/web/static/js/tkuser-core.js b/user-management/web/static/js/tkuser-core.js index 8de4b23..a073f0f 100644 --- a/user-management/web/static/js/tkuser-core.js +++ b/user-management/web/static/js/tkuser-core.js @@ -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); diff --git a/user-management/web/static/js/tkuser-tabs.js b/user-management/web/static/js/tkuser-tabs.js index 0c4482b..b7df4f7 100644 --- a/user-management/web/static/js/tkuser-tabs.js +++ b/user-management/web/static/js/tkuser-tabs.js @@ -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')); diff --git a/user-management/web/static/js/tkuser-users.js b/user-management/web/static/js/tkuser-users.js index ae4e190..0114e0d 100644 --- a/user-management/web/static/js/tkuser-users.js +++ b/user-management/web/static/js/tkuser-users.js @@ -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 = '저장 중...'; 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 = '저장 중...'; 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 };