feat: 업무일지 공용코드 연동 및 UI/UX 개선

- 업무일지 공용코드 동적 로드 (상태, 요청부서, 패키지, 프로세스, 업무형태)
- 업무형태 선택 모달 구현 (프로세스 > 분류 > 항목 3단계 선택)
- ESC 키로 모달 닫기 기능 추가
- 필터 섹션 디자인 개선 및 접기/펼치기 기능 추가
- 프로젝트명 50자 초과 시 말줄임(...) 표시
- 상태 "진행중" 오렌지 색상으로 변경
- 테이블 row height 축소 및 날짜 구분선 제거
- 네비게이션 로고 클릭 시 대시보드 이동 기능 추가
- 대시보드 개인정보 표시 제거 (휴가자 명단)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
backuppc
2025-11-11 13:50:49 +09:00
parent 709ca7c85a
commit e19b404580
7 changed files with 648 additions and 235 deletions

View File

@@ -364,12 +364,12 @@
<div class="container mx-auto px-4">
<div class="flex items-center justify-between h-16">
<div class="flex items-center space-x-8">
<div class="flex items-center space-x-2">
<a href="/Dashboard/" class="flex items-center space-x-2 hover:opacity-80 transition-opacity cursor-pointer">
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
</svg>
<span class="text-xl font-bold text-white">GroupWare</span>
</div>
</a>
<nav class="hidden md:flex space-x-1">
${visibleItems.map(item => `
<a href="${item.url}" class="px-3 py-2 rounded-md text-sm font-medium transition-colors ${

View File

@@ -123,9 +123,6 @@
<body class="bg-gradient-to-br from-blue-900 via-purple-900 to-indigo-900 min-h-screen text-white">
<div class="container mx-auto px-4 py-8">
<!-- 헤더 -->
<div class="text-center mb-8 animate-fade-in">
</div>
<!-- 통계 카드 -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-6 mb-8">

View File

@@ -208,78 +208,87 @@
<!-- 필터 및 검색 -->
<div class="glass-effect rounded-lg mb-6 animate-slide-up">
<div class="p-4">
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4">
<!-- 좌측: 필터 컨트롤 -->
<div class="lg:col-span-2 space-y-3">
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3">
<div>
<label class="block text-xs font-medium text-white/80 mb-1">조회기간</label>
<div class="flex space-x-1">
<input type="date" id="startDate" class="flex-1 bg-white/20 border border-white/30 rounded-md px-2 py-1 text-white placeholder-white/60 focus:outline-none focus:ring-1 focus:ring-white/50 focus:border-transparent text-xs">
<span class="flex items-center text-white/60 text-xs">~</span>
<input type="date" id="endDate" class="flex-1 bg-white/20 border border-white/30 rounded-md px-2 py-1 text-white placeholder-white/60 focus:outline-none focus:ring-1 focus:ring-white/50 focus:border-transparent text-xs">
</div>
</div>
<div>
<label class="block text-xs font-medium text-white/80 mb-1">상태</label>
<select id="statusFilter" class="w-full bg-white/20 border border-white/30 rounded-md px-2 py-1 text-white focus:outline-none focus:ring-1 focus:ring-white/50 focus:border-transparent text-xs">
<option value="">전체</option>
<option value="진행중">진행중</option>
<option value="완료">완료</option>
<option value="대기">대기</option>
</select>
</div>
<div>
<label class="block text-xs font-medium text-white/80 mb-1">타입</label>
<select id="typeFilter" class="w-full bg-white/20 border border-white/30 rounded-md px-2 py-1 text-white focus:outline-none focus:ring-1 focus:ring-white/50 focus:border-transparent text-xs">
<option value="">전체</option>
<option value="개발">개발</option>
<option value="유지보수">유지보수</option>
<option value="분석">분석</option>
<option value="테스트">테스트</option>
<option value="문서작업">문서작업</option>
<option value="회의">회의</option>
<option value="기타">기타</option>
</select>
<div class="p-5">
<!-- 상단: 헤더 및 액션 버튼들 -->
<div class="flex flex-wrap items-center justify-between gap-2">
<button id="toggleFilterBtn" class="text-sm font-semibold text-white flex items-center hover:text-white/80 transition-colors">
<i data-feather="filter" class="w-4 h-4 mr-2"></i>
필터 및 검색
<i id="filterToggleIcon" data-feather="chevron-down" class="w-4 h-4 ml-2 transform rotate-180 transition-transform"></i>
</button>
<div class="flex flex-wrap gap-2">
<button id="clearFilterBtn" class="bg-white/10 hover:bg-white/20 text-white text-sm px-3 py-1.5 rounded-md flex items-center transition-colors" title="필터 초기화">
<i data-feather="refresh-cw" class="w-3.5 h-3.5 mr-1.5"></i>
초기화
</button>
<button id="exportBtn" class="bg-success-500 hover:bg-success-600 text-white text-sm px-3 py-1.5 rounded-md flex items-center transition-colors" title="엑셀 다운로드">
<i data-feather="download" class="w-3.5 h-3.5 mr-1.5"></i>
엑셀
</button>
<button id="addJobBtn" class="bg-primary-500 hover:bg-primary-600 text-white text-sm px-3 py-1.5 rounded-md flex items-center transition-colors" title="업무일지 추가">
<i data-feather="plus" class="w-3.5 h-3.5 mr-1.5"></i>
추가
</button>
</div>
</div>
<!-- 필터 컨트롤 -->
<div id="filterContent" class="space-y-3 mt-4 pt-4 border-t border-white/10 hidden">
<!-- 첫번째 줄: 조회기간, 상태, 타입 -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3">
<div class="sm:col-span-1 lg:col-span-2">
<label class="block text-xs font-medium text-white/70 mb-1.5">조회기간</label>
<div class="flex items-center space-x-2">
<input type="date" id="startDate" class="flex-1 bg-white/10 border border-white/20 rounded-md px-3 py-1.5 text-white text-sm focus:outline-none focus:ring-2 focus:ring-primary-400 focus:border-transparent">
<span class="text-white/50 text-sm">~</span>
<input type="date" id="endDate" class="flex-1 bg-white/10 border border-white/20 rounded-md px-3 py-1.5 text-white text-sm focus:outline-none focus:ring-2 focus:ring-primary-400 focus:border-transparent">
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
<div>
<label class="block text-xs font-medium text-white/80 mb-1">사용자</label>
<select id="userFilter" class="w-full bg-white/20 border border-white/30 rounded-md px-2 py-1 text-white focus:outline-none focus:ring-1 focus:ring-white/50 focus:border-transparent text-xs">
<option value="">전체</option>
</select>
</div>
<div>
<label class="block text-xs font-medium text-white/80 mb-1">프로젝트</label>
<select id="projectFilter" class="w-full bg-white/20 border border-white/30 rounded-md px-2 py-1 text-white focus:outline-none focus:ring-1 focus:ring-white/50 focus:border-transparent text-xs">
<option value="">전체</option>
</select>
</div>
<div>
<label class="block text-xs font-medium text-white/70 mb-1.5">상태</label>
<select id="statusFilter" class="w-full bg-white/10 border border-white/20 rounded-md px-3 py-1.5 text-white text-sm focus:outline-none focus:ring-2 focus:ring-primary-400 focus:border-transparent">
<option value="">전체</option>
<option value="진행중">진행중</option>
<option value="완료">완료</option>
<option value="대기">대기</option>
</select>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
<div>
<label class="block text-xs font-medium text-white/80 mb-1">검색</label>
<input type="text" id="searchInput" placeholder="업무 내용 검색..." class="w-full bg-white/20 border border-white/30 rounded-md px-2 py-1 text-white placeholder-white/60 focus:outline-none focus:ring-1 focus:ring-white/50 focus:border-transparent text-xs">
</div>
<div>
<label class="block text-xs font-medium text-white/70 mb-1.5">타입</label>
<select id="typeFilter" class="w-full bg-white/10 border border-white/20 rounded-md px-3 py-1.5 text-white text-sm focus:outline-none focus:ring-2 focus:ring-primary-400 focus:border-transparent">
<option value="">전체</option>
<option value="개발">개발</option>
<option value="유지보수">유지보수</option>
<option value="분석">분석</option>
<option value="테스트">테스트</option>
<option value="문서작업">문서작업</option>
<option value="회의">회의</option>
<option value="기타">기타</option>
</select>
</div>
</div>
<!-- 우측: 액션 버튼들 -->
<div class="flex flex-col space-y-2 justify-center">
<button id="addJobBtn" class="bg-primary-500 hover:bg-primary-600 text-white px-4 py-2 rounded-md flex items-center justify-center transition-colors" title="업무일지 추가">
<i data-feather="plus" class="w-4 h-4 mr-2"></i>
업무일지 추가
</button>
<button id="exportBtn" class="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded-md flex items-center justify-center transition-colors" title="엑셀 다운로드">
<i data-feather="download" class="w-4 h-4 mr-2"></i>
엑셀 다운로드
</button>
<button id="clearFilterBtn" class="bg-white/20 hover:bg-white/30 text-white px-4 py-2 rounded-md flex items-center justify-center transition-colors" title="필터 초기화">
<i data-feather="refresh-cw" class="w-4 h-4 mr-2"></i>
필터 초기화
</button>
<!-- 두번째 줄: 사용자, 프로젝트, 검색 -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
<div>
<label class="block text-xs font-medium text-white/70 mb-1.5">사용자</label>
<select id="userFilter" class="w-full bg-white/10 border border-white/20 rounded-md px-3 py-1.5 text-white text-sm focus:outline-none focus:ring-2 focus:ring-primary-400 focus:border-transparent">
<option value="">전체</option>
</select>
</div>
<div>
<label class="block text-xs font-medium text-white/70 mb-1.5">프로젝트</label>
<select id="projectFilter" class="w-full bg-white/10 border border-white/20 rounded-md px-3 py-1.5 text-white text-sm focus:outline-none focus:ring-2 focus:ring-primary-400 focus:border-transparent">
<option value="">전체</option>
</select>
</div>
<div>
<label class="block text-xs font-medium text-white/70 mb-1.5">검색</label>
<div class="relative">
<i data-feather="search" class="w-4 h-4 absolute left-3 top-1/2 transform -translate-y-1/2 text-white/40"></i>
<input type="text" id="searchInput" placeholder="업무 내용 검색..." class="w-full bg-white/10 border border-white/20 rounded-md pl-9 pr-3 py-1.5 text-white placeholder-white/40 text-sm focus:outline-none focus:ring-2 focus:ring-primary-400 focus:border-transparent">
</div>
</div>
</div>
</div>
</div>
@@ -291,23 +300,23 @@
<table class="min-w-full divide-y divide-white/20">
<thead class="bg-white/10">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider cursor-pointer hover:bg-white/20 transition-colors" data-sort="pdate">
<th class="px-6 py-2 text-left text-xs font-medium text-white/80 uppercase tracking-wider cursor-pointer hover:bg-white/20 transition-colors" data-sort="pdate">
날짜 <i data-feather="chevron-down" class="w-4 h-4 inline ml-1"></i>
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider cursor-pointer hover:bg-white/20 transition-colors" data-sort="status">
<th class="px-6 py-2 text-left text-xs font-medium text-white/80 uppercase tracking-wider cursor-pointer hover:bg-white/20 transition-colors" data-sort="status">
상태 <i data-feather="chevron-down" class="w-4 h-4 inline ml-1"></i>
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider cursor-pointer hover:bg-white/20 transition-colors" data-sort="hrs">
근무시간 <i data-feather="chevron-down" class="w-4 h-4 inline ml-1"></i>
<th class="px-6 py-2 text-left text-xs font-medium text-white/80 uppercase tracking-wider cursor-pointer hover:bg-white/20 transition-colors" data-sort="hrs">
시간 <i data-feather="chevron-down" class="w-4 h-4 inline ml-1"></i>
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider cursor-pointer hover:bg-white/20 transition-colors" data-sort="projectName">
<th class="px-6 py-2 text-left text-xs font-medium text-white/80 uppercase tracking-wider cursor-pointer hover:bg-white/20 transition-colors" data-sort="projectName">
프로젝트명 <i data-feather="chevron-down" class="w-4 h-4 inline ml-1"></i>
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider">업무내용</th>
<th class="px-6 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider cursor-pointer hover:bg-white/20 transition-colors" data-sort="requestpart">
<th class="px-6 py-2 text-left text-xs font-medium text-white/80 uppercase tracking-wider">업무내용</th>
<th class="px-6 py-2 text-left text-xs font-medium text-white/80 uppercase tracking-wider cursor-pointer hover:bg-white/20 transition-colors" data-sort="requestpart">
요청부서 <i data-feather="chevron-down" class="w-4 h-4 inline ml-1"></i>
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider cursor-pointer hover:bg-white/20 transition-colors" data-sort="type">
<th class="px-6 py-2 text-left text-xs font-medium text-white/80 uppercase tracking-wider cursor-pointer hover:bg-white/20 transition-colors hidden" data-sort="type">
타입 <i data-feather="chevron-down" class="w-4 h-4 inline ml-1"></i>
</th>
</tr>
@@ -390,6 +399,69 @@
</div>
</div>
<!-- 업무형태 선택 모달 -->
<div id="jobTypeModal" class="fixed inset-0 bg-black/50 backdrop-blur-sm hidden z-50">
<div class="flex items-center justify-center min-h-screen p-4">
<div class="glass-effect rounded-2xl w-full max-w-4xl animate-slide-up">
<!-- 모달 헤더 -->
<div class="p-6 border-b border-white/10">
<div class="flex items-center justify-between">
<h2 class="text-xl font-bold text-white flex items-center">
<svg class="w-6 h-6 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path>
</svg>
업무형태 선택
</h2>
<button id="closeJobTypeModal" class="text-white/70 hover:text-white transition-colors">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
</div>
<!-- 모달 내용 -->
<div class="p-6">
<div class="grid grid-cols-3 gap-4">
<!-- 프로세스 목록 -->
<div>
<h3 class="text-white font-semibold mb-3">프로세스</h3>
<div id="processListForJobType" class="space-y-1 max-h-96 overflow-y-auto">
<!-- 프로세스 목록이 여기에 로드됩니다 -->
</div>
</div>
<!-- 분류 목록 -->
<div>
<h3 class="text-white font-semibold mb-3">분류</h3>
<div id="groupList" class="space-y-1 max-h-96 overflow-y-auto">
<!-- 분류 목록이 여기에 로드됩니다 -->
</div>
</div>
<!-- 항목 목록 -->
<div>
<h3 class="text-white font-semibold mb-3">항목</h3>
<div id="itemList" class="space-y-1 max-h-96 overflow-y-auto">
<!-- 항목 목록이 여기에 로드됩니다 -->
</div>
</div>
</div>
</div>
<!-- 모달 푸터 -->
<div class="px-6 py-4 border-t border-white/10 flex justify-end space-x-3 bg-black/10 rounded-b-2xl">
<button type="button" onclick="closeJobTypeModal()" class="bg-white/20 hover:bg-white/30 text-white px-4 py-2 rounded-lg transition-colors">
취소
</button>
<button type="button" onclick="confirmJobTypeSelection()" class="bg-primary-500 hover:bg-primary-600 text-white px-6 py-2 rounded-lg transition-colors">
선택
</button>
</div>
</div>
</div>
</div>
<script>
/**
* 공통 네비게이션 컴포넌트
@@ -458,12 +530,12 @@
<div class="container mx-auto px-4">
<div class="flex items-center justify-between h-16">
<div class="flex items-center space-x-8">
<div class="flex items-center space-x-2">
<a href="/Dashboard/" class="flex items-center space-x-2 hover:opacity-80 transition-opacity cursor-pointer">
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path>
</svg>
<span class="text-xl font-bold text-white">GroupWare</span>
</div>
</a>
<nav class="hidden md:flex space-x-1">
${visibleItems.map(item => `
<a href="${item.url}" class="px-3 py-2 rounded-md text-sm font-medium transition-colors ${
@@ -539,6 +611,11 @@
// 전역 변수
let jobData = [];
let filteredData = [];
let statusCodes = []; // 공용코드 12번 (상태 코드)
let requestDeptCodes = []; // 공용코드 13번 (요청부서)
let packageCodes = []; // 공용코드 14번 (패키지)
let jobTypeCodes = []; // 공용코드 15번 (업무형태 - 트리구조)
let processCodes = []; // 공용코드 16번 (프로세스)
let currentPage = 1;
let pageSize = 25;
let sortColumn = 'pdate';
@@ -555,6 +632,7 @@
setTimeout(() => {
const addJobBtn = document.getElementById('addJobBtn');
const exportBtn = document.getElementById('exportBtn');
const toggleFilterBtn = document.getElementById('toggleFilterBtn');
if (addJobBtn) {
addJobBtn.addEventListener('click', showAddJobModal);
@@ -562,7 +640,20 @@
if (exportBtn) {
exportBtn.addEventListener('click', exportToExcel);
}
if (toggleFilterBtn) {
toggleFilterBtn.addEventListener('click', toggleFilter);
}
}, 100);
// ESC 키로 모달 닫기
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape' || event.key === 'Esc') {
const modal = document.getElementById('detailModal');
if (modal && !modal.classList.contains('hidden')) {
closeModal();
}
}
});
});
function initializeApp() {
@@ -578,6 +669,16 @@
console.log('사용자 목록 로드 시작...');
loadUserList();
// 상태코드 로드 (공용코드 12번)
console.log('상태코드 로드 시작...');
loadStatusCodes();
// 추가 공용코드 로드
loadRequestDeptCodes(); // 공용코드 13번
loadPackageCodes(); // 공용코드 14번
loadJobTypeCodes(); // 공용코드 15번
loadProcessCodes(); // 공용코드 16번
// 이벤트 리스너 등록
document.getElementById('startDate').addEventListener('change', loadJobData);
document.getElementById('endDate').addEventListener('change', loadJobData);
@@ -806,6 +907,166 @@
});
}
// 공용코드 12번(상태코드) 로드
function loadStatusCodes() {
fetch('/Common/GetList?grp=12')
.then(response => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}: 상태코드를 불러오는데 실패했습니다.`);
}
return response.json();
})
.then(data => {
console.log('상태코드 원본 데이터:', data);
if (data && Array.isArray(data)) {
statusCodes = data;
console.log('상태코드 로드 완료:', statusCodes.length, '개');
console.log('첫번째 상태코드 샘플:', statusCodes[0]);
} else {
console.warn('상태코드 데이터가 배열이 아닙니다:', data);
// 기본 상태코드 설정
statusCodes = [
{ svalue: '진행 중' },
{ svalue: '완료' },
{ svalue: '대기' },
{ svalue: '보류' }
];
}
})
.catch(error => {
console.error('상태코드 로드 중 오류:', error);
// 에러 시 기본 상태코드 사용
statusCodes = [
{ svalue: '진행 중' },
{ svalue: '완료' },
{ svalue: '대기' },
{ svalue: '보류' }
];
});
}
// 상태코드 드롭다운 옵션 생성 헬퍼 함수
function generateStatusOptions(selectedValue = '') {
console.log('generateStatusOptions 호출 - selectedValue:', selectedValue);
console.log('현재 statusCodes:', statusCodes);
let options = '<option value="">선택하세요</option>';
statusCodes.forEach((status, index) => {
const value = status.svalue || status.code || '';
const text = status.svalue || status.memo || status.code || '';
const selected = value === selectedValue ? 'selected' : '';
console.log(`옵션 ${index}: value="${value}", text="${text}", selected=${selected}`);
options += `<option value="${value}" ${selected}>${text}</option>`;
});
return options;
}
// 공용코드 13번(요청부서) 로드
function loadRequestDeptCodes() {
fetch('/Common/GetList?grp=13')
.then(response => response.json())
.then(data => {
if (data && Array.isArray(data)) {
requestDeptCodes = data;
console.log('요청부서 코드 로드 완료:', requestDeptCodes.length, '개');
}
})
.catch(error => {
console.error('요청부서 코드 로드 중 오류:', error);
requestDeptCodes = [];
});
}
// 공용코드 14번(패키지) 로드
function loadPackageCodes() {
fetch('/Common/GetList?grp=14')
.then(response => response.json())
.then(data => {
if (data && Array.isArray(data)) {
packageCodes = data;
console.log('패키지 코드 로드 완료:', packageCodes.length, '개');
}
})
.catch(error => {
console.error('패키지 코드 로드 중 오류:', error);
packageCodes = [];
});
}
// 공용코드 15번(업무형태 - 트리구조) 로드
function loadJobTypeCodes() {
fetch('/Common/GetList?grp=15')
.then(response => response.json())
.then(data => {
console.log('업무형태 원본 데이터:', data);
if (data && Array.isArray(data)) {
jobTypeCodes = data;
console.log('업무형태 코드 로드 완료:', jobTypeCodes.length, '개');
if (jobTypeCodes.length > 0) {
console.log('업무형태 첫번째 샘플:', jobTypeCodes[0]);
console.log('업무형태 데이터 구조 확인:');
console.log(' - svalue2 (프로세스):', jobTypeCodes[0].svalue2);
console.log(' - svalue (분류):', jobTypeCodes[0].svalue);
console.log(' - memo (항목):', jobTypeCodes[0].memo);
}
} else {
console.warn('업무형태 데이터가 배열이 아닙니다:', data);
jobTypeCodes = [];
}
})
.catch(error => {
console.error('업무형태 코드 로드 중 오류:', error);
jobTypeCodes = [];
});
}
// 공용코드 16번(프로세스) 로드
function loadProcessCodes() {
fetch('/Common/GetList?grp=16')
.then(response => response.json())
.then(data => {
if (data && Array.isArray(data)) {
processCodes = data;
console.log('프로세스 코드 로드 완료:', processCodes.length, '개');
}
})
.catch(error => {
console.error('프로세스 코드 로드 중 오류:', error);
processCodes = [];
});
}
// 드롭다운 옵션 생성 헬퍼 함수들
function generateRequestDeptOptions(selectedValue = '') {
let options = '<option value="">선택하세요</option>';
requestDeptCodes.forEach(dept => {
const value = dept.svalue || '';
const selected = value === selectedValue ? 'selected' : '';
options += `<option value="${value}" ${selected}>${value}</option>`;
});
return options;
}
function generatePackageOptions(selectedValue = '') {
let options = '<option value="">선택하세요</option>';
packageCodes.forEach(pkg => {
const value = pkg.svalue || '';
const selected = value === selectedValue ? 'selected' : '';
options += `<option value="${value}" ${selected}>${value}</option>`;
});
return options;
}
function generateProcessOptions(selectedValue = '') {
let options = '<option value="">선택하세요</option>';
processCodes.forEach(proc => {
const value = proc.svalue || '';
const selected = value === selectedValue ? 'selected' : '';
options += `<option value="${value}" ${selected}>${value}</option>`;
});
return options;
}
function filterData() {
const statusFilter = document.getElementById('statusFilter').value;
const typeFilter = document.getElementById('typeFilter').value;
@@ -856,6 +1117,13 @@
});
}
// 텍스트 자르기 함수 (50자 제한)
function truncateText(text, maxLength = 50) {
if (!text) return '-';
if (text.length <= maxLength) return text;
return text.substring(0, maxLength) + '...';
}
function renderTable() {
const tbody = document.getElementById('jobTableBody');
const startIndex = (currentPage - 1) * pageSize;
@@ -875,23 +1143,23 @@
pageData.forEach((item, index) => {
const currentDate = item.pdate ? item.pdate.substring(0, 10) : null;
// 날짜가 변경되면 구분선 추가 (첫 번째 항목 제외)
if (lastDate && currentDate && lastDate !== currentDate) {
const separatorRow = document.createElement('tr');
separatorRow.className = 'border-t-2 border-white/20';
separatorRow.innerHTML = `
<td colspan="7" class="px-6 py-2">
<div class="flex items-center">
<div class="flex-grow border-t border-white/30"></div>
<span class="px-4 text-xs text-white/60 bg-white/10 rounded-full">
${formatDate(currentDate)}
</span>
<div class="flex-grow border-t border-white/30"></div>
</div>
</td>
`;
tbody.appendChild(separatorRow);
}
// 날짜 구분선 숨김
// if (lastDate && currentDate && lastDate !== currentDate) {
// const separatorRow = document.createElement('tr');
// separatorRow.className = 'border-t-2 border-white/20';
// separatorRow.innerHTML = `
// <td colspan="7" class="px-6 py-2">
// <div class="flex items-center">
// <div class="flex-grow border-t border-white/30"></div>
// <span class="px-4 text-xs text-white/60 bg-white/10 rounded-full">
// ${formatDate(currentDate)}
// </span>
// <div class="flex-grow border-t border-white/30"></div>
// </div>
// </td>
// `;
// tbody.appendChild(separatorRow);
// }
const row = document.createElement('tr');
row.className = 'hover:bg-white/10 cursor-pointer transition-colors';
@@ -923,32 +1191,32 @@
</button>` : '';
row.innerHTML = `
<td class="px-6 py-4 whitespace-nowrap text-sm text-white">
<td class="px-6 py-2 whitespace-nowrap text-sm text-white">
${formatDate(item.pdate)}
</td>
<td class="px-6 py-4 whitespace-nowrap">
<td class="px-6 py-2 whitespace-nowrap">
<span class="inline-flex px-2 py-1 text-xs font-semibold rounded-full ${statusColor}">
${item.status || '-'}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-white">
<td class="px-6 py-2 whitespace-nowrap text-sm text-white">
${workTimeDisplay}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-white">
<td class="px-6 py-2 whitespace-nowrap text-sm text-white">
<div class="flex items-center">
${projectButton}
<span>${item.projectName || '-'}</span>
<span title="${item.projectName || ''}">${truncateText(item.projectName)}</span>
</div>
</td>
<td class="px-6 py-4 text-sm text-white">
<td class="px-6 py-2 text-sm text-white">
<div class="max-w-xs truncate" title="${item.description || ''}">
${item.description || '-'}
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-white">
<td class="px-6 py-2 whitespace-nowrap text-sm text-white">
${item.requestpart || '-'}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-white">
<td class="px-6 py-2 whitespace-nowrap text-sm text-white hidden">
${item.type || '-'}
</td>
`;
@@ -963,8 +1231,10 @@
function getStatusColor(status) {
switch (status) {
case '진행 중': return 'bg-blue-100 text-blue-800';
case '진행 완료': return 'bg-green-100 text-green-800';
case '진행중':
case '진행 ': return 'bg-orange-100 text-orange-800';
case '진행 완료':
case '완료': return 'bg-green-100 text-green-800';
case '대기': return 'bg-yellow-100 text-yellow-800';
default: return 'bg-gray-100 text-gray-800';
}
@@ -986,6 +1256,20 @@
document.getElementById('nextPage').disabled = currentPage >= maxPage;
}
// 필터 토글 함수
function toggleFilter() {
const filterContent = document.getElementById('filterContent');
const toggleIcon = document.getElementById('filterToggleIcon');
if (filterContent.classList.contains('hidden')) {
filterContent.classList.remove('hidden');
toggleIcon.classList.remove('rotate-180');
} else {
filterContent.classList.add('hidden');
toggleIcon.classList.add('rotate-180');
}
}
function showAddJobModal() {
const modal = document.getElementById('detailModal');
const content = document.getElementById('modalContent');
@@ -1009,31 +1293,37 @@
<div>
<label class="block text-white/70 text-sm font-medium mb-2">상태 *</label>
<select id="editStatus" class="w-full bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all" required>
<option value="">선택하세요</option>
<option value="진행 중" selected>진행 중</option>
<option value="완료">완료</option>
<option value="대기">대기</option>
<option value="보류">보류</option>
${generateStatusOptions('진행 중')}
</select>
</div>
<div>
<label class="block text-white/70 text-sm font-medium mb-2">요청부서</label>
<input type="text" id="editRequestpart" value=""
class="w-full bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg px-4 py-2 text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all">
<select id="editRequestpart" class="w-full bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all">
${generateRequestDeptOptions('')}
</select>
</div>
<div>
<label class="block text-white/70 text-sm font-medium mb-2">타입</label>
<select id="editType" class="w-full bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all">
<option value="">선택하세요</option>
<option value="개발">개발</option>
<option value="유지보수">유지보수</option>
<option value="분석">분석</option>
<option value="테스트">테스트</option>
<option value="문서작업">문서작업</option>
<option value="회의">회의</option>
<option value="기타">기타</option>
<label class="block text-white/70 text-sm font-medium mb-2">패키지</label>
<select id="editPackage" class="w-full bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all">
${generatePackageOptions('')}
</select>
</div>
<div>
<label class="block text-white/70 text-sm font-medium mb-2">프로세스</label>
<select id="editJobProcess" class="w-full bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all">
${generateProcessOptions('')}
</select>
</div>
<div>
<label class="block text-white/70 text-sm font-medium mb-2">업무형태</label>
<button type="button" id="editJobTypeDisplay" onclick="showJobTypeSelector()"
class="w-full bg-blue-500 hover:bg-blue-600 border border-blue-400 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-blue-400 transition-all text-left">
선택하세요
</button>
<input type="hidden" id="editJobType" value="">
<input type="hidden" id="editJobGrp" value="">
<input type="hidden" id="editJobProcess2" value="">
</div>
<div>
<label class="block text-white/70 text-sm font-medium mb-2">근무시간 (시간) *</label>
<input type="number" id="editHrs" value="8" step="any"
@@ -1134,31 +1424,37 @@
<div>
<label class="block text-white/70 text-sm font-medium mb-2">상태 *</label>
<select id="editStatus" class="w-full bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all" required>
<option value="">선택하세요</option>
<option value="진행 중" ${item.status === '진행 중' ? 'selected' : ''}>진행 중</option>
<option value="완료" ${item.status === '완료' ? 'selected' : ''}>완료</option>
<option value="대기" ${item.status === '대기' ? 'selected' : ''}>대기</option>
<option value="보류" ${item.status === '보류' ? 'selected' : ''}>보류</option>
${generateStatusOptions(item.status)}
</select>
</div>
<div>
<label class="block text-white/70 text-sm font-medium mb-2">요청부서</label>
<input type="text" id="editRequestpart" value="${item.requestpart || ''}"
class="w-full bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg px-4 py-2 text-white placeholder-white/50 focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all">
<select id="editRequestpart" class="w-full bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all">
${generateRequestDeptOptions(item.requestpart)}
</select>
</div>
<div>
<label class="block text-white/70 text-sm font-medium mb-2">타입</label>
<select id="editType" class="w-full bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all">
<option value="">선택하세요</option>
<option value="개발" ${item.type === '개발' ? 'selected' : ''}>개발</option>
<option value="유지보수" ${item.type === '유지보수' ? 'selected' : ''}>유지보수</option>
<option value="분석" ${item.type === '분석' ? 'selected' : ''}>분석</option>
<option value="테스트" ${item.type === '테스트' ? 'selected' : ''}>테스트</option>
<option value="문서작업" ${item.type === '문서작업' ? 'selected' : ''}>문서작업</option>
<option value="회의" ${item.type === '회의' ? 'selected' : ''}>회의</option>
<option value="기타" ${item.type === '기타' ? 'selected' : ''}>기타</option>
<label class="block text-white/70 text-sm font-medium mb-2">패키지</label>
<select id="editPackage" class="w-full bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all">
${generatePackageOptions(item.package)}
</select>
</div>
<div>
<label class="block text-white/70 text-sm font-medium mb-2">프로세스</label>
<select id="editJobProcess" class="w-full bg-white/20 backdrop-blur-sm border border-white/30 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-primary-400 transition-all">
${generateProcessOptions(item.jobprocess)}
</select>
</div>
<div>
<label class="block text-white/70 text-sm font-medium mb-2">업무형태</label>
<button type="button" id="editJobTypeDisplay" onclick="showJobTypeSelector()"
class="w-full bg-blue-500 hover:bg-blue-600 border border-blue-400 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-blue-400 transition-all text-left">
${item.jobtype ? (item.jobprocess2 + ' | ' + item.jobgrp + ' | ' + item.jobtype) : '선택하세요'}
</button>
<input type="hidden" id="editJobType" value="${item.jobtype || ''}">
<input type="hidden" id="editJobGrp" value="${item.jobgrp || ''}">
<input type="hidden" id="editJobProcess2" value="${item.jobprocess2 || ''}">
</div>
<div>
<label class="block text-white/70 text-sm font-medium mb-2">근무시간 (시간) *</label>
<input type="number" id="editHrs" value="${item.hrs || ''}" step="any"
@@ -1227,6 +1523,160 @@
document.getElementById('detailModal').classList.add('hidden');
}
// 업무형태 선택 관련 함수들
let selectedJobType = { process: '', group: '', item: '' };
function showJobTypeSelector() {
console.log('showJobTypeSelector 호출됨');
console.log('jobTypeCodes 데이터:', jobTypeCodes);
console.log('jobTypeCodes 길이:', jobTypeCodes.length);
if (jobTypeCodes.length === 0) {
alert('업무형태 데이터를 불러오는 중입니다. 잠시 후 다시 시도해주세요.');
return;
}
// 모달 열기
document.getElementById('jobTypeModal').classList.remove('hidden');
// 프로세스 목록 렌더링
renderProcessListForJobType();
// 닫기 버튼 이벤트
document.getElementById('closeJobTypeModal').addEventListener('click', closeJobTypeModal);
}
function closeJobTypeModal() {
document.getElementById('jobTypeModal').classList.add('hidden');
}
function renderProcessListForJobType() {
const processListDiv = document.getElementById('processListForJobType');
console.log('renderProcessListForJobType 호출됨');
console.log('jobTypeCodes:', jobTypeCodes);
if (!jobTypeCodes || jobTypeCodes.length === 0) {
processListDiv.innerHTML = '<p class="text-white/50 text-sm p-4">데이터가 없습니다.</p>';
return;
}
// 프로세스 목록 추출 (svalue2 기준으로 그룹화)
const processes = [...new Set(jobTypeCodes.map(item => item.svalue2 || 'N/A'))].sort();
console.log('프로세스 목록:', processes);
if (processes.length === 0) {
processListDiv.innerHTML = '<p class="text-white/50 text-sm p-4">프로세스가 없습니다.</p>';
return;
}
processListDiv.innerHTML = processes.map(proc => `
<button type="button"
onclick="selectProcess('${proc}')"
class="w-full text-left px-4 py-2 rounded-lg hover:bg-white/20 text-white transition-colors">
${proc}
</button>
`).join('');
console.log('프로세스 버튼 생성 완료');
}
function selectProcess(processName) {
selectedJobType.process = processName;
selectedJobType.group = '';
selectedJobType.item = '';
// 선택된 프로세스 하이라이트
document.querySelectorAll('#processListForJobType button').forEach(btn => {
btn.classList.remove('bg-white/30');
});
event.target.classList.add('bg-white/30');
// 분류 목록 렌더링
renderGroupList(processName);
// 항목 목록 초기화
document.getElementById('itemList').innerHTML = '<p class="text-white/50 text-sm">분류를 선택하세요</p>';
}
function renderGroupList(processName) {
const groupListDiv = document.getElementById('groupList');
// 선택된 프로세스의 분류 목록 추출
const groups = [...new Set(
jobTypeCodes
.filter(item => (item.svalue2 || 'N/A') === processName)
.map(item => item.svalue || 'N/A')
)].sort();
groupListDiv.innerHTML = groups.map(grp => `
<button type="button"
onclick="selectGroup('${grp}')"
class="w-full text-left px-4 py-2 rounded-lg hover:bg-white/20 text-white transition-colors">
${grp}
</button>
`).join('');
}
function selectGroup(groupName) {
selectedJobType.group = groupName;
selectedJobType.item = '';
// 선택된 분류 하이라이트
document.querySelectorAll('#groupList button').forEach(btn => {
btn.classList.remove('bg-white/30');
});
event.target.classList.add('bg-white/30');
// 항목 목록 렌더링
renderItemList(selectedJobType.process, groupName);
}
function renderItemList(processName, groupName) {
const itemListDiv = document.getElementById('itemList');
// 선택된 프로세스와 분류의 항목 목록 추출
const items = jobTypeCodes
.filter(item => (item.svalue2 || 'N/A') === processName && (item.svalue || 'N/A') === groupName)
.map(item => item.memo || '')
.sort();
itemListDiv.innerHTML = items.map(itm => `
<button type="button"
onclick="selectItem('${itm}')"
class="w-full text-left px-4 py-2 rounded-lg hover:bg-white/20 text-white transition-colors">
${itm}
</button>
`).join('');
}
function selectItem(itemName) {
selectedJobType.item = itemName;
// 선택된 항목 하이라이트
document.querySelectorAll('#itemList button').forEach(btn => {
btn.classList.remove('bg-white/30');
});
event.target.classList.add('bg-white/30');
}
function confirmJobTypeSelection() {
if (!selectedJobType.process || !selectedJobType.group || !selectedJobType.item) {
alert('프로세스, 분류, 항목을 모두 선택해주세요.');
return;
}
// 선택된 값을 폼에 반영
const displayText = `${selectedJobType.process} | ${selectedJobType.group} | ${selectedJobType.item}`;
document.getElementById('editJobTypeDisplay').textContent = displayText;
document.getElementById('editJobType').value = selectedJobType.item;
document.getElementById('editJobGrp').value = selectedJobType.group;
document.getElementById('editJobProcess2').value = selectedJobType.process;
// 모달 닫기
closeJobTypeModal();
}
async function handleAddSubmit(event) {
event.preventDefault();
@@ -1236,7 +1686,11 @@
formData.append('status', document.getElementById('editStatus').value);
formData.append('projectName', document.getElementById('editProjectName').value);
formData.append('requestpart', document.getElementById('editRequestpart').value);
formData.append('type', document.getElementById('editType').value);
formData.append('package', document.getElementById('editPackage').value);
formData.append('jobprocess', document.getElementById('editJobProcess').value);
formData.append('jobtype', document.getElementById('editJobType').value);
formData.append('jobgrp', document.getElementById('editJobGrp').value);
formData.append('jobprocess2', document.getElementById('editJobProcess2').value);
formData.append('hrs', document.getElementById('editHrs').value);
formData.append('ot', document.getElementById('editOt').value);
formData.append('otStart', document.getElementById('editOtStart').value);
@@ -1292,7 +1746,11 @@
formData.append('status', document.getElementById('editStatus').value);
formData.append('projectName', document.getElementById('editProjectName').value);
formData.append('requestpart', document.getElementById('editRequestpart').value);
formData.append('type', document.getElementById('editType').value);
formData.append('package', document.getElementById('editPackage').value);
formData.append('jobprocess', document.getElementById('editJobProcess').value);
formData.append('jobtype', document.getElementById('editJobType').value);
formData.append('jobgrp', document.getElementById('editJobGrp').value);
formData.append('jobprocess2', document.getElementById('editJobProcess2').value);
formData.append('hrs', document.getElementById('editHrs').value);
formData.append('ot', document.getElementById('editOt').value);
formData.append('otStart', document.getElementById('editOtStart').value);
@@ -1406,7 +1864,7 @@
</div>
<div>
<label class="block text-sm font-medium text-gray-700">프로젝트명</label>
<p class="mt-1 text-sm text-gray-900">${item.projectName || '-'}</p>
<p class="mt-1 text-sm text-gray-900" title="${item.projectName || ''}">${truncateText(item.projectName)}</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700">요청부서</label>

View File

@@ -490,12 +490,12 @@
<div class="container mx-auto px-4">
<div class="flex items-center justify-between h-16">
<div class="flex items-center space-x-8">
<div class="flex items-center space-x-2">
<a href="/Dashboard/" class="flex items-center space-x-2 hover:opacity-80 transition-opacity cursor-pointer">
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
<span class="text-xl font-bold text-white">GroupWare</span>
</div>
</a>
<nav class="hidden md:flex space-x-1">
${visibleItems.map(item => `
<a href="${item.url}" class="px-3 py-2 rounded-md text-sm font-medium transition-colors ${

View File

@@ -545,12 +545,12 @@
<div class="container mx-auto px-4">
<div class="flex items-center justify-between h-16">
<div class="flex items-center space-x-8">
<div class="flex items-center space-x-2">
<a href="/Dashboard/" class="flex items-center space-x-2 hover:opacity-80 transition-opacity cursor-pointer">
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"></path>
</svg>
<span class="text-xl font-bold text-white">GroupWare</span>
</div>
</a>
<nav class="hidden md:flex space-x-1">
${menuItems.map(item => `
<a href="${item.url}" class="px-3 py-2 rounded-md text-sm font-medium transition-colors ${

View File

@@ -423,12 +423,12 @@
<div class="container mx-auto px-4">
<div class="flex items-center justify-between h-16">
<div class="flex items-center space-x-8">
<div class="flex items-center space-x-2">
<a href="/Dashboard/" class="flex items-center space-x-2 hover:opacity-80 transition-opacity cursor-pointer">
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2M12 12l2 2 4-4"></path>
</svg>
<span class="text-xl font-bold text-white">GroupWare</span>
</div>
</a>
<nav class="hidden md:flex space-x-1">
${menuItems.map(item => `
<a href="${item.url}" class="px-3 py-2 rounded-md text-sm font-medium transition-colors ${

View File

@@ -13,11 +13,6 @@ class CommonNavigation {
createNavigation() {
const nav = document.createElement('nav');
nav.className = 'glass-effect border-b border-white/10';
nav.style.cssText = `
background: rgba(255, 255, 255, 0.25);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.18);
`;
nav.innerHTML = this.getNavigationHTML();
// body의 첫 번째 자식으로 추가
@@ -25,89 +20,52 @@ class CommonNavigation {
}
getNavigationHTML() {
const menuItems = [
{ key: 'dashboard', title: '대시보드', url: '/Dashboard/', icon: 'M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2H5a2 2 0 00-2-2z M8 5a2 2 0 012-2h4a2 2 0 012 2v2H8V5z' },
{ key: 'common', title: '공용코드', url: '/Common', icon: 'M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z' },
{ key: 'jobreport', title: '업무일지', url: '/Jobreport/', icon: 'M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2' },
{ key: 'kuntae', title: '근태관리', url: '/Kuntae/', icon: 'M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z' },
{ key: 'todo', title: '할일관리', url: '/Todo/', icon: 'M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2M12 12l2 2 4-4' },
{ key: 'project', title: '프로젝트', url: '/Project/', icon: 'M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10' }
];
return `
<div class="container mx-auto px-4" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);">
<div class="container mx-auto px-4">
<div class="flex items-center justify-between h-16">
<!-- 로고/타이틀 -->
<div class="flex items-center">
<h2 class="text-xl font-bold text-white">GroupWare</h2>
</div>
<!-- 메뉴 -->
<div class="hidden md:flex items-center space-x-8">
${this.getMenuItemHTML('dashboard', '/Dashboard/', '대시보드', 'M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2H5a2 2 0 00-2-2z M8 5a2 2 0 012-2h4a2 2 0 012 2v2H8V5z')}
${this.getMenuItemHTML('common', '/Common', '공용코드', 'M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z')}
${this.getMenuItemHTML('jobreport', '/Jobreport/', '업무일지', 'M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2')}
${this.getMenuItemHTML('kuntae', '/Kuntae/', '근태관리', 'M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z')}
${this.getMenuItemHTML('todo', '/Todo/', '할일관리', 'M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2M12 12l2 2 4-4')}
${this.getMenuItemHTML('project', '/Project/', '프로젝트', 'M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10')}
${this.getMenuItemHTML('purchase', '/Purchase/', '구매관리', 'M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z')}
${this.getMenuItemHTML('customer', '/Customer/', '고객관리', 'M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z')}
</div>
<!-- 모바일 메뉴 버튼 -->
<div class="md:hidden">
<button id="mobile-menu-button" class="text-white/80 hover:text-white transition-colors p-2">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
<div class="flex items-center space-x-8">
<a href="/Dashboard/" class="flex items-center space-x-2 hover:opacity-80 transition-opacity cursor-pointer">
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2M12 12l2 2 4-4"></path>
</svg>
</button>
<span class="text-xl font-bold text-white">GroupWare</span>
</a>
<nav class="hidden md:flex space-x-1">
${menuItems.map(item => `
<a href="${item.url}" class="px-3 py-2 rounded-md text-sm font-medium transition-colors ${
this.currentPage === item.key
? 'bg-white/20 text-white'
: 'text-white/60 hover:text-white hover:bg-white/10'
}">
<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="${item.icon}"></path>
</svg>
${item.title}
</a>
`).join('')}
</nav>
</div>
<div class="flex items-center space-x-4">
<div class="text-sm text-white/60">
<span id="currentUser">사용자</span>
</div>
</div>
</div>
<!-- 모바일 메뉴 -->
<div id="mobile-menu" class="md:hidden hidden border-t border-white/10 pt-4 pb-4">
${this.getMobileMenuItemHTML('dashboard', '/Dashboard/', '대시보드', 'M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2H5a2 2 0 00-2-2z M8 5a2 2 0 012-2h4a2 2 0 012 2v2H8V5z')}
${this.getMobileMenuItemHTML('common', '/Common', '공용코드', 'M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z')}
${this.getMobileMenuItemHTML('jobreport', '/Jobreport/', '업무일지', 'M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2')}
${this.getMobileMenuItemHTML('kuntae', '/Kuntae/', '근태관리', 'M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z')}
${this.getMobileMenuItemHTML('todo', '/Todo/', '할일관리', 'M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2M12 12l2 2 4-4')}
${this.getMobileMenuItemHTML('project', '/Project/', '프로젝트', 'M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10')}
${this.getMobileMenuItemHTML('purchase', '/Purchase/', '구매관리', 'M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z')}
${this.getMobileMenuItemHTML('customer', '/Customer/', '고객관리', 'M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z')}
</div>
</div>
`;
}
getMenuItemHTML(pageKey, href, text, svgPath) {
const isActive = this.currentPage === pageKey;
const activeClass = isActive ? 'text-white bg-white/20' : 'text-white/80 hover:text-white hover:bg-white/10';
return `
<a href="${href}" class="${activeClass} transition-colors px-3 py-2 rounded-lg">
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="${svgPath}"></path>
</svg>
${text}
</a>
`;
}
getMobileMenuItemHTML(pageKey, href, text, svgPath) {
const isActive = this.currentPage === pageKey;
const activeClass = isActive ? 'text-white bg-white/20' : 'text-white/80 hover:text-white hover:bg-white/10';
return `
<a href="${href}" class="block ${activeClass} transition-colors px-3 py-2 rounded-lg mb-2">
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="${svgPath}"></path>
</svg>
${text}
</a>
`;
}
addEventListeners() {
// 모바일 메뉴 토글
const mobileMenuButton = document.getElementById('mobile-menu-button');
const mobileMenu = document.getElementById('mobile-menu');
if (mobileMenuButton && mobileMenu) {
mobileMenuButton.addEventListener('click', function() {
mobileMenu.classList.toggle('hidden');
});
}
// 필요한 이벤트 리스너가 있으면 여기에 추가
}
}