refactor: TBM/작업보고 코드 통합 및 API 쿼리 버그 수정

- 공통 유틸리티 추출 (common/utils.js, common/base-state.js)
- TBM 모바일 인라인 JS/CSS 외부 파일로 분리 (tbm-mobile.js, tbm-mobile.css)
- 미사용 코드 삭제 (index.js, work-report-*.js 등 5개 파일)
- TBM/작업보고 state.js, utils.js를 공통 모듈 기반으로 전환
- 작업보고서 SSO 인증 호환 수정 (token/user 함수)
- tbmModel.js: incomplete-reports 쿼리에서 users→sso_users 조인 수정, leader_name 조인 추가
- docker-compose.yml: system1-web 볼륨 마운트 추가
- 모바일 인계(handover) 기능 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-03-05 07:51:24 +09:00
parent 22a37ac4d9
commit 4388628788
89 changed files with 5296 additions and 5046 deletions

View File

@@ -714,6 +714,8 @@
'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 }
};
@@ -767,7 +769,10 @@
'issues_archive': { title: '🗃️ 폐기함', icon: 'fas fa-archive', color: 'text-gray-600' }
},
'시스템 관리': {
'reports': { title: '보고서', icon: 'fas fa-chart-bar', color: 'text-red-600' }
'projects_manage': { title: '프로젝트 관리', icon: 'fas fa-folder-open', color: 'text-indigo-600' },
'daily_work': { title: '일일 공수', icon: 'fas fa-calendar-check', color: 'text-blue-600' },
'reports': { title: '보고서', icon: 'fas fa-chart-bar', color: 'text-red-600' },
'users_manage': { title: '사용자 관리', icon: 'fas fa-users-cog', color: 'text-purple-600' }
}
};
@@ -834,7 +839,7 @@
const allPages = [
'issues_create', 'issues_view', 'issues_manage',
'issues_inbox', 'issues_management', 'issues_archive',
'reports'
'projects_manage', 'daily_work', 'reports', 'users_manage'
];
const permissions = {};

View File

@@ -6,10 +6,6 @@
<title>일일 공수 입력</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- 공통 스타일 -->
<link rel="stylesheet" href="/static/css/tkqc-common.css">
<style>
:root {
--primary: #3b82f6;
@@ -20,55 +16,62 @@
--gray-200: #e5e7eb;
--gray-300: #d1d5db;
}
body {
background-color: var(--gray-50);
}
.btn-primary {
background-color: var(--primary);
color: white;
transition: all 0.2s;
}
.btn-primary:hover {
background-color: var(--primary-dark);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
}
.btn-success {
background-color: var(--success);
color: white;
transition: all 0.2s;
}
.btn-success:hover {
background-color: #059669;
transform: translateY(-1px);
}
.input-field {
border: 1px solid var(--gray-300);
background: white;
transition: all 0.2s;
}
.input-field:focus {
border-color: var(--primary);
outline: none;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
.work-card {
background: white;
border-radius: 12px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
transition: all 0.2s;
}
.work-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.summary-card {
background: #2563eb;
background: linear-gradient(135deg, #3b82f6, #2563eb);
color: white;
}
.nav-link {
padding: 0.5rem 1rem;
border-radius: 0.5rem;
@@ -76,24 +79,61 @@
transition: all 0.2s;
white-space: nowrap;
}
.nav-link:hover {
background-color: #f3f4f6;
color: #1f2937;
}
.nav-link.active {
background-color: #3b82f6;
color: white;
}
/* 부드러운 페이드인 애니메이션 */
.fade-in {
opacity: 0;
transform: translateY(20px);
transition: opacity 0.6s ease-out, transform 0.6s ease-out;
}
.fade-in.visible {
opacity: 1;
transform: translateY(0);
}
/* 헤더 전용 빠른 페이드인 */
.header-fade-in {
opacity: 0;
transform: translateY(-10px);
transition: opacity 0.4s ease-out, transform 0.4s ease-out;
}
.header-fade-in.visible {
opacity: 1;
transform: translateY(0);
}
/* 본문 컨텐츠 지연 페이드인 */
.content-fade-in {
opacity: 0;
transform: translateY(30px);
transition: opacity 0.8s ease-out, transform 0.8s ease-out;
transition-delay: 0.2s;
}
.content-fade-in.visible {
opacity: 1;
transform: translateY(0);
}
</style>
</head>
<body>
<div class="min-h-screen">
<div class="min-h-screen bg-gray-50">
<!-- 공통 헤더가 여기에 자동으로 삽입됩니다 -->
<!-- 메인 컨텐츠 -->
<main class="container mx-auto px-4 py-6 max-w-2xl content-fade-in" style="padding-top: 72px;">
<main class="container mx-auto px-4 py-6 max-w-2xl content-fade-in" style="padding-top: 80px;">
<!-- 입력 카드 -->
<div class="work-card p-6 mb-6">
<h2 class="text-lg font-semibold text-gray-800 mb-6">

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 B

File diff suppressed because it is too large Load Diff

View File

@@ -6,10 +6,6 @@
<title>프로젝트 관리 - 작업보고서 시스템</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- 공통 스타일 -->
<link rel="stylesheet" href="/static/css/tkqc-common.css">
<style>
:root {
--primary: #3b82f6;
@@ -22,35 +18,78 @@
--gray-200: #e5e7eb;
--gray-300: #d1d5db;
}
body {
background-color: var(--gray-50);
}
.btn-primary {
background-color: var(--primary);
color: white;
transition: all 0.2s;
}
.btn-primary:hover {
background-color: var(--primary-dark);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
}
.input-field {
border: 1px solid var(--gray-300);
background: white;
transition: all 0.2s;
}
.input-field:focus {
border-color: var(--primary);
outline: none;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
/* 부드러운 페이드인 애니메이션 */
.fade-in {
opacity: 0;
transform: translateY(20px);
transition: opacity 0.6s ease-out, transform 0.6s ease-out;
}
.fade-in.visible {
opacity: 1;
transform: translateY(0);
}
/* 헤더 전용 빠른 페이드인 */
.header-fade-in {
opacity: 0;
transform: translateY(-10px);
transition: opacity 0.4s ease-out, transform 0.4s ease-out;
}
.header-fade-in.visible {
opacity: 1;
transform: translateY(0);
}
/* 본문 컨텐츠 지연 페이드인 */
.content-fade-in {
opacity: 0;
transform: translateY(30px);
transition: opacity 0.8s ease-out, transform 0.8s ease-out;
transition-delay: 0.2s;
}
.content-fade-in.visible {
opacity: 1;
transform: translateY(0);
}
</style>
</head>
<body>
<!-- 공통 헤더가 여기에 자동으로 삽입됩니다 -->
<!-- 메인 컨텐츠 -->
<main class="container mx-auto px-4 py-8 max-w-4xl content-fade-in" style="padding-top: 72px;">
<main class="container mx-auto px-4 py-8 max-w-4xl content-fade-in" style="padding-top: 80px;">
<!-- 프로젝트 생성 섹션 -->
<div class="bg-white rounded-xl shadow-sm p-6 mb-8">
<h2 class="text-lg font-semibold text-gray-800 mb-4">

View File

@@ -10,15 +10,21 @@
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- 공통 스타일 -->
<link rel="stylesheet" href="/static/css/tkqc-common.css">
<!-- Custom Styles -->
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
body {
font-family: 'Inter', sans-serif;
}
</style>
</head>
<body>
<body class="bg-gray-50 min-h-screen">
<!-- 공통 헤더가 여기에 자동으로 삽입됩니다 -->
<!-- Main Content -->
<main class="container mx-auto px-4 py-8" style="padding-top: 72px;">
<main class="container mx-auto px-4 py-8" style="padding-top: 80px;">
<!-- 페이지 헤더 -->
<div class="bg-white rounded-xl shadow-sm p-6 mb-6">
<div class="flex items-center justify-between mb-4">

View File

@@ -10,15 +10,21 @@
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- 공통 스타일 -->
<link rel="stylesheet" href="/static/css/tkqc-common.css">
<!-- Custom Styles -->
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
body {
font-family: 'Inter', sans-serif;
}
</style>
</head>
<body>
<body class="bg-gray-50 min-h-screen">
<!-- 공통 헤더가 여기에 자동으로 삽입됩니다 -->
<!-- Main Content -->
<main class="container mx-auto px-4 py-8" style="padding-top: 72px;">
<main class="container mx-auto px-4 py-8" style="padding-top: 80px;">
<!-- 페이지 헤더 -->
<div class="bg-white rounded-xl shadow-sm p-6 mb-6">
<div class="flex items-center justify-between mb-4">

View File

@@ -10,44 +10,49 @@
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- 공통 스타일 -->
<link rel="stylesheet" href="/static/css/tkqc-common.css">
<!-- Custom Styles -->
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
body {
font-family: 'Inter', sans-serif;
}
.report-card {
transition: all 0.2s ease;
transition: all 0.3s ease;
border-left: 4px solid transparent;
}
.report-card:hover {
transform: translateY(-4px);
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.15);
border-left-color: #3b82f6;
}
.report-card.daily-report {
border-left-color: #10b981;
}
.report-card.daily-report:hover {
border-left-color: #059669;
}
.gradient-bg {
background: #6366f1;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.stats-card {
background: #ec4899;
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
}
</style>
</head>
<body>
<body class="bg-gray-50">
<!-- 공통 헤더 -->
<div id="commonHeader"></div>
<!-- Main Content -->
<main class="container mx-auto px-4 py-8" style="padding-top: 72px;">
<main class="container mx-auto px-4 py-8" style="padding-top: 80px;">
<!-- 페이지 헤더 -->
<div class="bg-white rounded-xl shadow-sm p-6 mb-6">
<div class="flex items-center justify-between mb-4">

View File

@@ -273,6 +273,35 @@ const IssuesAPI = {
getStats: () => apiRequest('/issues/stats/summary')
};
// Daily Work API
const DailyWorkAPI = {
create: (workData) => apiRequest('/daily-work/', {
method: 'POST',
body: JSON.stringify(workData)
}),
getAll: (params = {}) => {
const queryString = new URLSearchParams(params).toString();
return apiRequest(`/daily-work/${queryString ? '?' + queryString : ''}`);
},
get: (id) => apiRequest(`/daily-work/${id}`),
update: (id, workData) => apiRequest(`/daily-work/${id}`, {
method: 'PUT',
body: JSON.stringify(workData)
}),
delete: (id) => apiRequest(`/daily-work/${id}`, {
method: 'DELETE'
}),
getStats: (params = {}) => {
const queryString = new URLSearchParams(params).toString();
return apiRequest(`/daily-work/stats/summary${queryString ? '?' + queryString : ''}`);
}
};
// Reports API
const ReportsAPI = {
getSummary: (startDate, endDate) => apiRequest('/reports/summary', {
@@ -291,6 +320,13 @@ const ReportsAPI = {
return apiRequest(`/reports/issues?${params}`);
},
getDailyWorks: (startDate, endDate) => {
const params = new URLSearchParams({
start_date: startDate,
end_date: endDate
}).toString();
return apiRequest(`/reports/daily-works?${params}`);
}
};
// 권한 체크

View File

@@ -270,7 +270,10 @@ class App {
const titles = {
'dashboard': '대시보드',
'issues': '부적합 사항',
'reports': '보고서'
'projects': '프로젝트',
'daily_work': '일일 공수',
'reports': '보고서',
'users': '사용자 관리'
};
const title = titles[module] || module;

View File

@@ -32,8 +32,8 @@ class CommonHeader {
icon: 'fas fa-chart-line',
url: '/issues-dashboard.html',
pageName: 'issues_dashboard',
color: 'text-slate-300',
bgColor: 'text-slate-300 hover:bg-slate-700'
color: 'text-slate-600',
bgColor: 'text-slate-600 hover:bg-slate-100'
},
{
id: 'issues_inbox',
@@ -41,8 +41,8 @@ class CommonHeader {
icon: 'fas fa-inbox',
url: '/issues-inbox.html',
pageName: 'issues_inbox',
color: 'text-slate-300',
bgColor: 'text-slate-300 hover:bg-slate-700'
color: 'text-slate-600',
bgColor: 'text-slate-600 hover:bg-slate-100'
},
{
id: 'issues_management',
@@ -50,8 +50,8 @@ class CommonHeader {
icon: 'fas fa-cog',
url: '/issues-management.html',
pageName: 'issues_management',
color: 'text-slate-300',
bgColor: 'text-slate-300 hover:bg-slate-700'
color: 'text-slate-600',
bgColor: 'text-slate-600 hover:bg-slate-100'
},
{
id: 'issues_archive',
@@ -59,8 +59,17 @@ class CommonHeader {
icon: 'fas fa-archive',
url: '/issues-archive.html',
pageName: 'issues_archive',
color: 'text-slate-300',
bgColor: 'text-slate-300 hover:bg-slate-700'
color: 'text-slate-600',
bgColor: 'text-slate-600 hover:bg-slate-100'
},
{
id: 'daily_work',
title: '일일 공수',
icon: 'fas fa-calendar-check',
url: '/daily-work.html',
pageName: 'daily_work',
color: 'text-slate-600',
bgColor: 'text-slate-600 hover:bg-slate-100'
},
{
id: 'reports',
@@ -68,8 +77,8 @@ class CommonHeader {
icon: 'fas fa-chart-bar',
url: '/reports.html',
pageName: 'reports',
color: 'text-slate-300',
bgColor: 'text-slate-300 hover:bg-slate-700',
color: 'text-slate-600',
bgColor: 'text-slate-600 hover:bg-slate-100',
subMenus: [
{
id: 'reports_daily',
@@ -77,7 +86,7 @@ class CommonHeader {
icon: 'fas fa-file-excel',
url: '/reports-daily.html',
pageName: 'reports_daily',
color: 'text-slate-300'
color: 'text-slate-600'
},
{
id: 'reports_weekly',
@@ -85,7 +94,7 @@ class CommonHeader {
icon: 'fas fa-calendar-week',
url: '/reports-weekly.html',
pageName: 'reports_weekly',
color: 'text-slate-300'
color: 'text-slate-600'
},
{
id: 'reports_monthly',
@@ -93,10 +102,29 @@ class CommonHeader {
icon: 'fas fa-calendar-alt',
url: '/reports-monthly.html',
pageName: 'reports_monthly',
color: 'text-slate-300'
color: 'text-slate-600'
}
]
},
{
id: 'projects_manage',
title: '프로젝트 관리',
icon: 'fas fa-folder-open',
url: '/project-management.html',
pageName: 'projects_manage',
color: 'text-slate-600',
bgColor: 'text-slate-600 hover:bg-slate-100'
},
{
id: 'users_manage',
title: '사용자 관리',
icon: 'fas fa-users-cog',
url: this._getUserManageUrl(),
pageName: 'users_manage',
color: 'text-slate-600',
bgColor: 'text-slate-600 hover:bg-slate-100',
external: true
}
];
}
@@ -177,14 +205,14 @@ class CommonHeader {
const userRole = this.getUserRoleDisplay();
return `
<header class="bg-slate-800 text-white sticky top-0 z-50">
<header class="bg-white shadow-sm border-b sticky top-0 z-50">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between items-center h-14">
<div class="flex justify-between items-center h-16">
<!-- 로고 및 제목 -->
<div class="flex items-center">
<div class="flex-shrink-0 flex items-center">
<i class="fas fa-shield-halved text-2xl text-slate-300 mr-3"></i>
<h1 class="text-xl font-bold text-white">부적합 관리</h1>
<i class="fas fa-shield-halved text-2xl text-slate-700 mr-3"></i>
<h1 class="text-xl font-bold text-gray-900">부적합 관리</h1>
</div>
</div>
@@ -198,8 +226,8 @@ class CommonHeader {
<!-- 사용자 정보 -->
<div class="flex items-center space-x-3">
<div class="text-right">
<div class="text-sm font-medium text-white">${userDisplayName}</div>
<div class="text-xs text-slate-400">${userRole}</div>
<div class="text-sm font-medium text-gray-900">${userDisplayName}</div>
<div class="text-xs text-gray-500">${userRole}</div>
</div>
<div class="w-8 h-8 bg-slate-600 rounded-full flex items-center justify-center">
<span class="text-white text-sm font-semibold">
@@ -210,7 +238,7 @@ class CommonHeader {
<!-- 드롭다운 메뉴 -->
<div class="relative">
<button id="user-menu-button" class="p-2 text-slate-400 hover:text-white focus:outline-none focus:ring-2 focus:ring-slate-500 focus:ring-offset-2 focus:ring-offset-slate-800 rounded-md">
<button id="user-menu-button" class="p-2 text-gray-400 hover:text-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 rounded-md">
<i class="fas fa-chevron-down"></i>
</button>
<div id="user-menu" class="hidden absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1 ring-1 ring-black ring-opacity-5">
@@ -224,14 +252,14 @@ class CommonHeader {
</div>
<!-- 모바일 메뉴 버튼 -->
<button id="mobile-menu-button" class="md:hidden p-2 text-slate-400 hover:text-white focus:outline-none focus:ring-2 focus:ring-slate-500 focus:ring-offset-2 focus:ring-offset-slate-800 rounded-md">
<button id="mobile-menu-button" class="md:hidden p-2 text-gray-400 hover:text-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 rounded-md">
<i class="fas fa-bars"></i>
</button>
</div>
</div>
<!-- 모바일 메뉴 -->
<div id="mobile-menu" class="md:hidden hidden border-t border-slate-700 py-3">
<div id="mobile-menu" class="md:hidden hidden border-t border-gray-200 py-3">
<div class="space-y-1">
${accessibleMenus.map(menu => this.generateMobileMenuItemHTML(menu)).join('')}
</div>
@@ -342,7 +370,7 @@ class CommonHeader {
*/
generateMobileMenuItemHTML(menu) {
const isActive = this.currentPage === menu.id;
const activeClass = isActive ? 'bg-slate-700 text-white border-white' : 'text-slate-300 hover:bg-slate-700';
const activeClass = isActive ? 'bg-slate-100 text-slate-800 border-slate-600' : 'text-gray-700 hover:bg-gray-50';
// 하위 메뉴가 있는 경우
if (menu.accessibleSubMenus && menu.accessibleSubMenus.length > 0) {
@@ -362,7 +390,7 @@ class CommonHeader {
<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-slate-400 hover:bg-slate-700 hover:text-white ${this.currentPage === subMenu.id ? 'bg-slate-700 text-white' : ''}"
class="flex items-center px-3 py-2 rounded-md text-sm font-medium text-gray-600 hover:bg-slate-100 ${this.currentPage === subMenu.id ? 'bg-slate-100 text-slate-800' : ''}"
data-page="${subMenu.id}"
onclick="CommonHeader.navigateToPage(event, '${subMenu.url}', '${subMenu.id}')">
<i class="${subMenu.icon} mr-3 ${subMenu.color}"></i>
@@ -463,7 +491,7 @@ class CommonHeader {
loader.className = 'fixed inset-0 bg-white bg-opacity-75 flex items-center justify-center z-50';
loader.innerHTML = `
<div class="text-center">
<div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-slate-600"></div>
<div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
<p class="mt-2 text-sm text-gray-600">페이지를 로드하는 중...</p>
</div>
`;
@@ -487,7 +515,7 @@ class CommonHeader {
<div class="bg-white rounded-xl p-6 w-96 max-w-md mx-4 shadow-2xl">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold text-gray-800">
<i class="fas fa-key mr-2 text-slate-500"></i>비밀번호 변경
<i class="fas fa-key mr-2 text-blue-500"></i>비밀번호 변경
</h3>
<button onclick="CommonHeader.hidePasswordModal()" class="text-gray-400 hover:text-gray-600 transition-colors">
<i class="fas fa-times text-lg"></i>
@@ -498,21 +526,21 @@ class CommonHeader {
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">현재 비밀번호</label>
<input type="password" id="currentPasswordInput"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-slate-500 focus:border-slate-500"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
required placeholder="현재 비밀번호를 입력하세요">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">새 비밀번호</label>
<input type="password" id="newPasswordInput"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-slate-500 focus:border-slate-500"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
required minlength="6" placeholder="새 비밀번호 (최소 6자)">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">새 비밀번호 확인</label>
<input type="password" id="confirmPasswordInput"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-slate-500 focus:border-slate-500"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
required placeholder="새 비밀번호를 다시 입력하세요">
</div>
@@ -522,7 +550,7 @@ class CommonHeader {
취소
</button>
<button type="submit"
class="flex-1 px-4 py-2 bg-slate-700 text-white rounded-lg hover:bg-slate-800 transition-colors">
class="flex-1 px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors">
<i class="fas fa-save mr-1"></i>변경
</button>
</div>
@@ -655,10 +683,10 @@ class CommonHeader {
const itemPageId = item.getAttribute('data-page');
if (itemPageId === pageId) {
item.classList.add('bg-slate-700', 'text-white');
item.classList.remove('text-slate-300', 'hover:bg-slate-700');
item.classList.remove('text-slate-600', 'hover:bg-slate-100');
} else {
item.classList.remove('bg-slate-700', 'text-white');
item.classList.add('text-slate-300');
item.classList.add('text-slate-600');
}
});
}

View File

@@ -28,7 +28,10 @@ class KeyboardShortcutManager {
// 네비게이션 단축키
this.register('g h', () => this.navigateToPage('/index.html', 'issues_create'), '홈 (부적합 등록)');
this.register('g v', () => this.navigateToPage('/issue-view.html', 'issues_view'), '부적합 조회');
this.register('g d', () => this.navigateToPage('/daily-work.html', 'daily_work'), '일일 공수');
this.register('g p', () => this.navigateToPage('/project-management.html', 'projects_manage'), '프로젝트 관리');
this.register('g r', () => this.navigateToPage('/reports.html', 'reports'), '보고서');
this.register('g a', () => this.navigateToPage('/admin.html', 'users_manage'), '관리자');
// 액션 단축키
this.register('n', () => this.triggerNewAction(), '새 항목 생성');
@@ -37,6 +40,8 @@ class KeyboardShortcutManager {
this.register('f', () => this.focusSearchField(), '검색 포커스');
// 관리자 전용 단축키
this.register('ctrl+shift+u', () => this.navigateToPage('/admin.html', 'users_manage'), '사용자 관리 (관리자)');
console.log('⌨️ 키보드 단축키 등록 완료');
}

View File

@@ -169,8 +169,14 @@ class PageManager {
return new IssuesViewModule(options);
case 'issues_manage':
return new IssuesManageModule(options);
case 'projects_manage':
return new ProjectsManageModule(options);
case 'daily_work':
return new DailyWorkModule(options);
case 'reports':
return new ReportsModule(options);
case 'users_manage':
return new UsersManageModule(options);
default:
console.warn(`알 수 없는 페이지 ID: ${pageId}`);
return null;

View File

@@ -57,7 +57,10 @@ class PagePreloader {
{ id: 'issues_create', url: '/index.html', priority: 1 },
{ id: 'issues_view', url: '/issue-view.html', priority: 1 },
{ id: 'issues_manage', url: '/index.html#list', priority: 2 },
{ id: 'reports', url: '/reports.html', priority: 3 }
{ id: 'projects_manage', url: '/project-management.html', priority: 3 },
{ id: 'daily_work', url: '/daily-work.html', priority: 2 },
{ id: 'reports', url: '/reports.html', priority: 3 },
{ id: 'users_manage', url: '/admin.html', priority: 4 }
];
// 권한 체크

View File

@@ -20,7 +20,10 @@ class PagePermissionManager {
'issues_inbox': { title: '수신함', defaultAccess: true },
'issues_management': { title: '관리함', defaultAccess: false },
'issues_archive': { title: '폐기함', defaultAccess: false },
'reports': { title: '보고서', defaultAccess: false }
'projects_manage': { title: '프로젝트 관리', defaultAccess: false },
'daily_work': { title: '일일 공수', defaultAccess: false },
'reports': { title: '보고서', defaultAccess: false },
'users_manage': { title: '사용자 관리', defaultAccess: false }
};
}
@@ -164,6 +167,20 @@ class PagePermissionManager {
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: '보고서',
@@ -171,6 +188,13 @@ class PagePermissionManager {
path: '#reports',
pageName: 'reports'
},
{
id: 'users_manage',
title: '사용자 관리',
icon: 'fas fa-users-cog',
path: '#users/manage',
pageName: 'users_manage'
}
];
// 페이지 권한에 따라 메뉴 필터링