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

@@ -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');
}
});
}