refactor(tkqc): UI 스타일 통일 + 일일공수 제거 + 메뉴 정리

- UI: tkuser 스타일로 통일 (dark slate 헤더, flat 배경, gradient/glass 제거)
- tkqc-common.css 공통 스타일시트 신규 생성
- 의견 제시 API 별도 엔드포인트 추가 (모든 사용자 접근 가능)
- 일일 공수 기능 완전 제거 (라우터, 모델, 스키마, DB 테이블 DROP)
- 프로젝트 관리/사용자 관리 메뉴 숨김 (통합관리 페이지로 이관)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-02-13 10:43:37 +09:00
parent ce270b9e4a
commit c52734154f
26 changed files with 294 additions and 782 deletions

View File

@@ -0,0 +1,39 @@
/* tkqc-common.css — tkuser 스타일 통일 */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
background: #f1f5f9;
min-height: 100vh;
}
.input-field {
background: white;
border: 1px solid #e2e8f0;
transition: all 0.2s;
}
.input-field:focus {
outline: none;
border-color: #64748b;
box-shadow: 0 0 0 3px rgba(100,116,139,0.1);
}
.issue-card {
transition: all 0.2s ease;
border-left: 4px solid transparent;
}
.issue-card:hover {
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
}
.badge { display: inline-flex; align-items: center; padding: 0.25rem 0.75rem; border-radius: 9999px; font-size: 0.75rem; font-weight: 500; }
.badge-new { background: #dbeafe; color: #1e40af; }
.badge-processing { background: #fef3c7; color: #92400e; }
.badge-completed { background: #d1fae5; color: #065f46; }
.badge-archived { background: #f3f4f6; color: #374151; }
.badge-cancelled { background: #fee2e2; color: #991b1b; }
.fade-in { opacity: 0; transform: translateY(10px); transition: opacity 0.4s, transform 0.4s; }
.fade-in.visible { opacity: 1; transform: translateY(0); }
.toast-message { transition: all 0.3s ease; }

View File

@@ -273,35 +273,6 @@ 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', {
@@ -320,13 +291,6 @@ 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,10 +270,7 @@ class App {
const titles = {
'dashboard': '대시보드',
'issues': '부적합 사항',
'projects': '프로젝트',
'daily_work': '일일 공수',
'reports': '보고서',
'users': '사용자 관리'
'reports': '보고서'
};
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-600',
bgColor: 'text-slate-600 hover:bg-slate-100'
color: 'text-slate-300',
bgColor: 'text-slate-300 hover:bg-slate-700'
},
{
id: 'issues_inbox',
@@ -41,8 +41,8 @@ class CommonHeader {
icon: 'fas fa-inbox',
url: '/issues-inbox.html',
pageName: 'issues_inbox',
color: 'text-slate-600',
bgColor: 'text-slate-600 hover:bg-slate-100'
color: 'text-slate-300',
bgColor: 'text-slate-300 hover:bg-slate-700'
},
{
id: 'issues_management',
@@ -50,8 +50,8 @@ class CommonHeader {
icon: 'fas fa-cog',
url: '/issues-management.html',
pageName: 'issues_management',
color: 'text-slate-600',
bgColor: 'text-slate-600 hover:bg-slate-100'
color: 'text-slate-300',
bgColor: 'text-slate-300 hover:bg-slate-700'
},
{
id: 'issues_archive',
@@ -59,17 +59,8 @@ class CommonHeader {
icon: 'fas fa-archive',
url: '/issues-archive.html',
pageName: 'issues_archive',
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'
color: 'text-slate-300',
bgColor: 'text-slate-300 hover:bg-slate-700'
},
{
id: 'reports',
@@ -77,8 +68,8 @@ class CommonHeader {
icon: 'fas fa-chart-bar',
url: '/reports.html',
pageName: 'reports',
color: 'text-slate-600',
bgColor: 'text-slate-600 hover:bg-slate-100',
color: 'text-slate-300',
bgColor: 'text-slate-300 hover:bg-slate-700',
subMenus: [
{
id: 'reports_daily',
@@ -86,7 +77,7 @@ class CommonHeader {
icon: 'fas fa-file-excel',
url: '/reports-daily.html',
pageName: 'reports_daily',
color: 'text-slate-600'
color: 'text-slate-300'
},
{
id: 'reports_weekly',
@@ -94,7 +85,7 @@ class CommonHeader {
icon: 'fas fa-calendar-week',
url: '/reports-weekly.html',
pageName: 'reports_weekly',
color: 'text-slate-600'
color: 'text-slate-300'
},
{
id: 'reports_monthly',
@@ -102,29 +93,10 @@ class CommonHeader {
icon: 'fas fa-calendar-alt',
url: '/reports-monthly.html',
pageName: 'reports_monthly',
color: 'text-slate-600'
color: 'text-slate-300'
}
]
},
{
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
}
];
}
@@ -205,14 +177,14 @@ class CommonHeader {
const userRole = this.getUserRoleDisplay();
return `
<header class="bg-white shadow-sm border-b sticky top-0 z-50">
<header class="bg-slate-800 text-white 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-16">
<div class="flex justify-between items-center h-14">
<!-- 로고 및 제목 -->
<div class="flex items-center">
<div class="flex-shrink-0 flex items-center">
<i class="fas fa-shield-halved text-2xl text-slate-700 mr-3"></i>
<h1 class="text-xl font-bold text-gray-900">부적합 관리</h1>
<i class="fas fa-shield-halved text-2xl text-slate-300 mr-3"></i>
<h1 class="text-xl font-bold text-white">부적합 관리</h1>
</div>
</div>
@@ -226,8 +198,8 @@ class CommonHeader {
<!-- 사용자 정보 -->
<div class="flex items-center space-x-3">
<div class="text-right">
<div class="text-sm font-medium text-gray-900">${userDisplayName}</div>
<div class="text-xs text-gray-500">${userRole}</div>
<div class="text-sm font-medium text-white">${userDisplayName}</div>
<div class="text-xs text-slate-400">${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">
@@ -238,7 +210,7 @@ class CommonHeader {
<!-- 드롭다운 메뉴 -->
<div class="relative">
<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">
<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">
<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">
@@ -252,14 +224,14 @@ class CommonHeader {
</div>
<!-- 모바일 메뉴 버튼 -->
<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">
<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">
<i class="fas fa-bars"></i>
</button>
</div>
</div>
<!-- 모바일 메뉴 -->
<div id="mobile-menu" class="md:hidden hidden border-t border-gray-200 py-3">
<div id="mobile-menu" class="md:hidden hidden border-t border-slate-700 py-3">
<div class="space-y-1">
${accessibleMenus.map(menu => this.generateMobileMenuItemHTML(menu)).join('')}
</div>
@@ -370,7 +342,7 @@ class CommonHeader {
*/
generateMobileMenuItemHTML(menu) {
const isActive = this.currentPage === menu.id;
const activeClass = isActive ? 'bg-slate-100 text-slate-800 border-slate-600' : 'text-gray-700 hover:bg-gray-50';
const activeClass = isActive ? 'bg-slate-700 text-white border-white' : 'text-slate-300 hover:bg-slate-700';
// 하위 메뉴가 있는 경우
if (menu.accessibleSubMenus && menu.accessibleSubMenus.length > 0) {
@@ -390,7 +362,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-gray-600 hover:bg-slate-100 ${this.currentPage === subMenu.id ? 'bg-slate-100 text-slate-800' : ''}"
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' : ''}"
data-page="${subMenu.id}"
onclick="CommonHeader.navigateToPage(event, '${subMenu.url}', '${subMenu.id}')">
<i class="${subMenu.icon} mr-3 ${subMenu.color}"></i>
@@ -491,7 +463,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-blue-600"></div>
<div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-slate-600"></div>
<p class="mt-2 text-sm text-gray-600">페이지를 로드하는 중...</p>
</div>
`;
@@ -515,7 +487,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-blue-500"></i>비밀번호 변경
<i class="fas fa-key mr-2 text-slate-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>
@@ -526,21 +498,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-blue-500 focus:border-blue-500"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-slate-500 focus:border-slate-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-blue-500 focus:border-blue-500"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-slate-500 focus:border-slate-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-blue-500 focus:border-blue-500"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-slate-500 focus:border-slate-500"
required placeholder="새 비밀번호를 다시 입력하세요">
</div>
@@ -550,7 +522,7 @@ class CommonHeader {
취소
</button>
<button type="submit"
class="flex-1 px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors">
class="flex-1 px-4 py-2 bg-slate-700 text-white rounded-lg hover:bg-slate-800 transition-colors">
<i class="fas fa-save mr-1"></i>변경
</button>
</div>
@@ -683,10 +655,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-600', 'hover:bg-slate-100');
item.classList.remove('text-slate-300', 'hover:bg-slate-700');
} else {
item.classList.remove('bg-slate-700', 'text-white');
item.classList.add('text-slate-600');
item.classList.add('text-slate-300');
}
});
}

View File

@@ -28,10 +28,7 @@ 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(), '새 항목 생성');
@@ -40,8 +37,6 @@ class KeyboardShortcutManager {
this.register('f', () => this.focusSearchField(), '검색 포커스');
// 관리자 전용 단축키
this.register('ctrl+shift+u', () => this.navigateToPage('/admin.html', 'users_manage'), '사용자 관리 (관리자)');
console.log('⌨️ 키보드 단축키 등록 완료');
}

View File

@@ -169,14 +169,8 @@ 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,10 +57,7 @@ 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: '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 }
{ id: 'reports', url: '/reports.html', priority: 3 }
];
// 권한 체크

View File

@@ -20,10 +20,7 @@ class PagePermissionManager {
'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 },
'users_manage': { title: '사용자 관리', defaultAccess: false }
'reports': { title: '보고서', defaultAccess: false }
};
}
@@ -167,20 +164,6 @@ 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: '보고서',
@@ -188,13 +171,6 @@ class PagePermissionManager {
path: '#reports',
pageName: 'reports'
},
{
id: 'users_manage',
title: '사용자 관리',
icon: 'fas fa-users-cog',
path: '#users/manage',
pageName: 'users_manage'
}
];
// 페이지 권한에 따라 메뉴 필터링