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:
@@ -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 ${
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -208,22 +208,45 @@
|
||||
|
||||
<!-- 필터 및 검색 -->
|
||||
<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 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>
|
||||
<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">
|
||||
<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>
|
||||
@@ -231,8 +254,8 @@
|
||||
</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">
|
||||
<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>
|
||||
@@ -244,42 +267,28 @@
|
||||
</select>
|
||||
</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>
|
||||
<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>
|
||||
</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>
|
||||
|
||||
@@ -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 ${
|
||||
|
||||
@@ -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 ${
|
||||
|
||||
@@ -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 ${
|
||||
|
||||
@@ -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>
|
||||
</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}
|
||||
<span class="text-xl font-bold text-white">GroupWare</span>
|
||||
</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>
|
||||
<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>
|
||||
${text}
|
||||
${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>
|
||||
`;
|
||||
}
|
||||
|
||||
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');
|
||||
});
|
||||
}
|
||||
// 필요한 이벤트 리스너가 있으면 여기에 추가
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user