- 새로운 프로젝트 관리 화면 추가 (ProjectController, ProjectModel) - 로그인된 사용자 기반 필터링 기능 구현 - 프로젝트 CRUD 기능 완성 (생성, 조회, 수정, 삭제) - 컬럼 표시/숨김 기능으로 사용자 정의 뷰 지원 - 상태별 프로젝트 현황 대시보드 - 엑셀 내보내기 기능 - 반응형 디자인 및 glass-effect UI 적용 - 할일관리/근태관리와 일관된 레이아웃 구조 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1077 lines
59 KiB
HTML
1077 lines
59 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="ko">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>프로젝트 관리</title>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<script src="https://unpkg.com/feather-icons"></script>
|
|
<style>
|
|
.glass-effect {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
backdrop-filter: blur(10px);
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
}
|
|
|
|
.animate-slide-up {
|
|
animation: slideUp 0.5s ease-out;
|
|
}
|
|
|
|
@keyframes slideUp {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(20px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
.custom-scrollbar::-webkit-scrollbar {
|
|
width: 6px;
|
|
height: 6px;
|
|
}
|
|
|
|
.custom-scrollbar::-webkit-scrollbar-track {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border-radius: 3px;
|
|
}
|
|
|
|
.custom-scrollbar::-webkit-scrollbar-thumb {
|
|
background: rgba(255, 255, 255, 0.3);
|
|
border-radius: 3px;
|
|
}
|
|
|
|
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
|
background: rgba(255, 255, 255, 0.5);
|
|
}
|
|
|
|
.bg-primary-500 {
|
|
background-color: #3b82f6;
|
|
}
|
|
|
|
.bg-primary-600 {
|
|
background-color: #2563eb;
|
|
}
|
|
|
|
.hover\:bg-primary-600:hover {
|
|
background-color: #2563eb;
|
|
}
|
|
|
|
.bg-green-500 {
|
|
background-color: #10b981;
|
|
}
|
|
|
|
.bg-green-600 {
|
|
background-color: #059669;
|
|
}
|
|
|
|
.hover\:bg-green-600:hover {
|
|
background-color: #059669;
|
|
}
|
|
|
|
.bg-red-500 {
|
|
background-color: #ef4444;
|
|
}
|
|
|
|
.bg-red-600 {
|
|
background-color: #dc2626;
|
|
}
|
|
|
|
.hover\:bg-red-600:hover {
|
|
background-color: #dc2626;
|
|
}
|
|
|
|
select option {
|
|
background-color: #1f2937;
|
|
color: white;
|
|
padding: 8px;
|
|
}
|
|
|
|
select option:hover {
|
|
background-color: #374151;
|
|
}
|
|
|
|
select option:checked {
|
|
background-color: #3b82f6;
|
|
}
|
|
|
|
select option:focus {
|
|
background-color: #374151;
|
|
}
|
|
|
|
.hidden-column {
|
|
display: none;
|
|
}
|
|
|
|
.show-all-columns .hidden-column {
|
|
display: table-cell;
|
|
}
|
|
|
|
.column-toggle-btn {
|
|
position: relative;
|
|
}
|
|
|
|
.column-dropdown {
|
|
position: absolute;
|
|
top: 100%;
|
|
right: 0;
|
|
background: rgba(31, 41, 55, 0.95);
|
|
backdrop-filter: blur(10px);
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
border-radius: 8px;
|
|
padding: 12px;
|
|
min-width: 200px;
|
|
z-index: 1000;
|
|
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3);
|
|
}
|
|
|
|
.column-dropdown label {
|
|
display: flex;
|
|
items-center: space-x-2;
|
|
padding: 6px 0;
|
|
cursor: pointer;
|
|
color: white;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.column-dropdown input[type="checkbox"] {
|
|
margin-right: 8px;
|
|
}
|
|
|
|
.filter-section {
|
|
max-height: 500px;
|
|
transition: max-height 0.3s ease-out, opacity 0.3s ease-out;
|
|
overflow: hidden;
|
|
opacity: 1;
|
|
}
|
|
|
|
.filter-section.hidden {
|
|
max-height: 0;
|
|
opacity: 0;
|
|
padding-top: 0;
|
|
padding-bottom: 0;
|
|
}
|
|
|
|
.filter-toggle-btn {
|
|
transition: transform 0.3s ease;
|
|
}
|
|
|
|
.filter-toggle-btn.rotated {
|
|
transform: rotate(180deg);
|
|
}
|
|
</style>
|
|
</head>
|
|
<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="glass-effect rounded-lg overflow-hidden animate-slide-up">
|
|
<div class="px-6 py-4 border-b border-white/10 flex items-center justify-between">
|
|
<h2 class="text-xl font-semibold text-white flex items-center">
|
|
<svg class="w-5 h-5 mr-2" 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>
|
|
프로젝트 목록
|
|
</h2>
|
|
<div class="flex space-x-3">
|
|
<button id="addProjectBtnHeader" class="bg-primary-500 hover:bg-primary-600 text-white px-4 py-2 rounded-lg transition-colors flex items-center text-sm" title="프로젝트 추가">
|
|
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
|
|
</svg>
|
|
프로젝트 추가
|
|
</button>
|
|
<button id="filterToggleBtn" class="bg-white/20 hover:bg-white/30 text-white px-4 py-2 rounded-lg transition-colors flex items-center text-sm" title="필터 표시/숨김">
|
|
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707L13 14v6a1 1 0 01-.707.293l-4-1A1 1 0 018 19v-5L0.293 7.293A1 1 0 010 6.586V4z"></path>
|
|
</svg>
|
|
필터
|
|
<svg class="w-4 h-4 ml-2 filter-toggle-btn transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 상태별 카드 -->
|
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 p-4 border-b border-white/10">
|
|
<div class="bg-white/10 rounded-lg p-4 text-center">
|
|
<div class="text-2xl font-bold text-blue-300 mb-1" id="progressCount">0</div>
|
|
<div class="text-sm text-white/60">진행</div>
|
|
</div>
|
|
<div class="bg-white/10 rounded-lg p-4 text-center">
|
|
<div class="text-2xl font-bold text-green-300 mb-1" id="completedCount">0</div>
|
|
<div class="text-sm text-white/60">완료</div>
|
|
</div>
|
|
<div class="bg-white/10 rounded-lg p-4 text-center">
|
|
<div class="text-2xl font-bold text-yellow-300 mb-1" id="waitingCount">0</div>
|
|
<div class="text-sm text-white/60">대기</div>
|
|
</div>
|
|
<div class="bg-white/10 rounded-lg p-4 text-center">
|
|
<div class="text-2xl font-bold text-red-300 mb-1" id="stoppedCount">0</div>
|
|
<div class="text-sm text-white/60">중단</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 필터 영역 -->
|
|
<div class="p-4 filter-section hidden border-b border-white/10">
|
|
<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>
|
|
<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>
|
|
<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/80 mb-1">담당자</label>
|
|
<select id="managerFilter" 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="my">내 프로젝트</option>
|
|
<option value="all">전체 프로젝트</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 우측: 액션 버튼들 -->
|
|
<div class="flex flex-wrap gap-2 justify-end">
|
|
<div class="column-toggle-btn relative">
|
|
<button id="columnToggleBtn" class="bg-purple-500 hover:bg-purple-600 text-white px-4 py-2 rounded-lg flex items-center transition-colors text-sm" title="컬럼 표시/숨김">
|
|
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17V7m0 10a2 2 0 01-2 2H5a2 2 0 01-2-2V7a2 2 0 012-2h2a2 2 0 012 2m0 10a2 2 0 002 2h2a2 2 0 002-2M9 7a2 2 0 012-2h2a2 2 0 012 2m0 10V7m0 10a2 2 0 002 2h2a2 2 0 002-2V7a2 2 0 00-2-2H9z"></path>
|
|
</svg>
|
|
컬럼 설정
|
|
</button>
|
|
<div id="columnDropdown" class="column-dropdown hidden">
|
|
<div class="text-sm font-medium text-white/80 mb-2">표시할 컬럼 선택</div>
|
|
<label><input type="checkbox" checked disabled> 상태</label>
|
|
<label><input type="checkbox" checked disabled> 프로젝트명</label>
|
|
<label><input type="checkbox" checked disabled> 자산번호</label>
|
|
<label><input type="checkbox" checked disabled> 장비모델</label>
|
|
<label><input type="checkbox" checked disabled> 우선순위</label>
|
|
<label><input type="checkbox" checked disabled> 요청국가</label>
|
|
<label><input type="checkbox" checked disabled> 프로젝트관리자</label>
|
|
<label><input type="checkbox" checked disabled> 시작일</label>
|
|
<label><input type="checkbox" checked disabled> 완료일</label>
|
|
<hr class="border-white/20 my-2">
|
|
<label><input type="checkbox" id="col-serial"> 시리얼번호</label>
|
|
<label><input type="checkbox" id="col-plant"> 요청공장</label>
|
|
<label><input type="checkbox" id="col-line"> 요청라인</label>
|
|
<label><input type="checkbox" id="col-package"> 요청부서패키지</label>
|
|
<label><input type="checkbox" id="col-staff"> 요청자</label>
|
|
<label><input type="checkbox" id="col-process"> 프로젝트공정</label>
|
|
<label><input type="checkbox" id="col-expire"> 만료일</label>
|
|
<label><input type="checkbox" id="col-delivery"> 출고일</label>
|
|
<label><input type="checkbox" id="col-design"> 설계담당</label>
|
|
<label><input type="checkbox" id="col-electric"> 전장담당</label>
|
|
<label><input type="checkbox" id="col-program"> 프로그램담당</label>
|
|
<label><input type="checkbox" id="col-budget-due"> 예산만기일</label>
|
|
<label><input type="checkbox" id="col-budget"> 예산</label>
|
|
<label><input type="checkbox" id="col-jasmin"> 웹관리번호</label>
|
|
</div>
|
|
</div>
|
|
<button id="exportBtn" class="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded-lg flex items-center transition-colors text-sm" title="엑셀 다운로드">
|
|
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 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>
|
|
엑셀 다운로드
|
|
</button>
|
|
<button id="clearFilterBtn" class="bg-white/20 hover:bg-white/30 text-white px-4 py-2 rounded-lg flex items-center transition-colors text-sm" title="필터 초기화">
|
|
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
|
|
</svg>
|
|
필터 초기화
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 데이터 테이블 -->
|
|
<div class="px-4 py-3 bg-white/5">
|
|
<div class="text-sm text-white/70 font-medium">프로젝트 현황</div>
|
|
</div>
|
|
<div class="overflow-x-auto custom-scrollbar">
|
|
<table class="w-full divide-y divide-white/20">
|
|
<thead class="bg-white/10">
|
|
<tr>
|
|
<th class="px-4 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider border-r border-white/20 w-20">상태</th>
|
|
<th class="px-4 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider border-r border-white/20 min-w-32">프로젝트명</th>
|
|
<th class="px-4 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider border-r border-white/20 w-24">자산번호</th>
|
|
<th class="px-4 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider border-r border-white/20 w-28">장비모델</th>
|
|
<th class="px-4 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider border-r border-white/20 w-24">우선순위</th>
|
|
<th class="px-4 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider border-r border-white/20 w-20">요청국가</th>
|
|
<th class="px-4 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider border-r border-white/20 w-28">프로젝트관리자</th>
|
|
<th class="px-4 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider border-r border-white/20 w-24">시작일</th>
|
|
<th class="px-4 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider border-r border-white/20 w-24">완료일</th>
|
|
<th class="px-3 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider border-r border-white/20 w-24 hidden-column col-serial">시리얼번호</th>
|
|
<th class="px-3 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider border-r border-white/20 w-20 hidden-column col-plant">요청공장</th>
|
|
<th class="px-3 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider border-r border-white/20 w-20 hidden-column col-line">요청라인</th>
|
|
<th class="px-3 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider border-r border-white/20 w-28 hidden-column col-package">요청부서패키지</th>
|
|
<th class="px-3 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider border-r border-white/20 w-20 hidden-column col-staff">요청자</th>
|
|
<th class="px-3 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider border-r border-white/20 w-24 hidden-column col-process">프로젝트공정</th>
|
|
<th class="px-3 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider border-r border-white/20 w-24 hidden-column col-expire">만료일</th>
|
|
<th class="px-3 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider border-r border-white/20 w-24 hidden-column col-delivery">출고일</th>
|
|
<th class="px-3 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider border-r border-white/20 w-24 hidden-column col-design">설계담당</th>
|
|
<th class="px-3 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider border-r border-white/20 w-24 hidden-column col-electric">전장담당</th>
|
|
<th class="px-3 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider border-r border-white/20 w-24 hidden-column col-program">프로그램담당</th>
|
|
<th class="px-3 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider border-r border-white/20 w-24 hidden-column col-budget-due">예산만기일</th>
|
|
<th class="px-3 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider border-r border-white/20 w-20 hidden-column col-budget">예산</th>
|
|
<th class="px-3 py-3 text-left text-xs font-medium text-white/80 uppercase tracking-wider w-24 hidden-column col-jasmin">웹관리번호</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="projectTableBody" class="divide-y divide-white/10">
|
|
<tr>
|
|
<td colspan="23" class="px-6 py-4 text-center text-white/60">데이터를 불러오는 중...</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- 페이징 -->
|
|
<div class="px-4 py-4 border-t border-white/10">
|
|
<div class="flex items-center justify-between">
|
|
<div class="text-sm text-white/60">
|
|
총 <span id="totalCount">0</span>개 중 <span id="currentRange">0-0</span>개 표시
|
|
</div>
|
|
<div class="flex items-center space-x-2">
|
|
<button id="prevPage" class="px-3 py-1 bg-white/20 hover:bg-white/30 text-white rounded-md text-sm disabled:opacity-50 disabled:cursor-not-allowed">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
|
|
</svg>
|
|
</button>
|
|
<div class="flex space-x-1">
|
|
<div id="pageNumbers" class="flex space-x-1"></div>
|
|
</div>
|
|
<button id="nextPage" class="px-3 py-1 bg-white/20 hover:bg-white/30 text-white rounded-md text-sm disabled:opacity-50 disabled:cursor-not-allowed">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 프로젝트 추가/편집 모달 -->
|
|
<div id="projectModal" class="fixed inset-0 bg-black bg-opacity-50 hidden z-50">
|
|
<div class="flex items-center justify-center min-h-screen p-4">
|
|
<div class="glass-effect rounded-lg w-full max-w-2xl animate-slide-up">
|
|
<div class="p-6">
|
|
<div class="flex justify-between items-center mb-6">
|
|
<h3 id="modalTitle" class="text-xl font-semibold">프로젝트 추가</h3>
|
|
<button onclick="closeProjectModal()" class="text-white/60 hover:text-white">
|
|
<i data-feather="x" class="w-6 h-6"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<form id="projectForm">
|
|
<input type="hidden" id="projectIdx" name="idx">
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-white/80 mb-2">프로젝트명 *</label>
|
|
<input type="text" id="projectName" name="name" required class="w-full bg-white/20 border border-white/30 rounded-md px-3 py-2 text-white placeholder-white/60 focus:outline-none focus:ring-1 focus:ring-white/50 focus:border-transparent">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-white/80 mb-2">부서</label>
|
|
<input type="text" id="projectProcess" name="process" class="w-full bg-white/20 border border-white/30 rounded-md px-3 py-2 text-white placeholder-white/60 focus:outline-none focus:ring-1 focus:ring-white/50 focus:border-transparent">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-white/80 mb-2">시작일</label>
|
|
<input type="date" id="projectSdate" name="sdate" class="w-full bg-white/20 border border-white/30 rounded-md px-3 py-2 text-white focus:outline-none focus:ring-1 focus:ring-white/50 focus:border-transparent">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-white/80 mb-2">종료일</label>
|
|
<input type="date" id="projectEdate" name="edate" class="w-full bg-white/20 border border-white/30 rounded-md px-3 py-2 text-white focus:outline-none focus:ring-1 focus:ring-white/50 focus:border-transparent">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-white/80 mb-2">개발완료일</label>
|
|
<input type="date" id="projectDdate" name="ddate" class="w-full bg-white/20 border border-white/30 rounded-md px-3 py-2 text-white focus:outline-none focus:ring-1 focus:ring-white/50 focus:border-transparent">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-white/80 mb-2">운영개시일</label>
|
|
<input type="date" id="projectOdate" name="odate" class="w-full bg-white/20 border border-white/30 rounded-md px-3 py-2 text-white focus:outline-none focus:ring-1 focus:ring-white/50 focus:border-transparent">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-white/80 mb-2">담당자</label>
|
|
<input type="text" id="projectUserManager" name="userManager" class="w-full bg-white/20 border border-white/30 rounded-md px-3 py-2 text-white placeholder-white/60 focus:outline-none focus:ring-1 focus:ring-white/50 focus:border-transparent">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-white/80 mb-2">상태</label>
|
|
<select id="projectStatus" name="status" class="w-full bg-white/20 border border-white/30 rounded-md px-3 py-2 text-white focus:outline-none focus:ring-1 focus:ring-white/50 focus:border-transparent">
|
|
<option value="진행">진행</option>
|
|
<option value="완료">완료</option>
|
|
<option value="대기">대기</option>
|
|
<option value="중단">중단</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-4">
|
|
<label class="block text-sm font-medium text-white/80 mb-2">메모</label>
|
|
<textarea id="projectMemo" name="memo" rows="3" class="w-full bg-white/20 border border-white/30 rounded-md px-3 py-2 text-white placeholder-white/60 focus:outline-none focus:ring-1 focus:ring-white/50 focus:border-transparent"></textarea>
|
|
</div>
|
|
|
|
<div class="flex justify-between items-center">
|
|
<button type="button" id="deleteProjectBtn" onclick="deleteCurrentProject()" class="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-md transition-colors hidden">
|
|
삭제
|
|
</button>
|
|
<div class="flex space-x-2">
|
|
<button type="button" onclick="closeProjectModal()" class="bg-white/20 hover:bg-white/30 text-white px-4 py-2 rounded-md transition-colors">
|
|
취소
|
|
</button>
|
|
<button type="submit" class="bg-primary-500 hover:bg-primary-600 text-white px-4 py-2 rounded-md transition-colors">
|
|
저장
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 삭제 확인 모달 -->
|
|
<div id="deleteModal" class="fixed inset-0 bg-black bg-opacity-50 hidden z-50">
|
|
<div class="flex items-center justify-center min-h-screen p-4">
|
|
<div class="glass-effect rounded-lg w-full max-w-md animate-slide-up">
|
|
<div class="p-6">
|
|
<div class="flex items-center mb-4">
|
|
<i data-feather="alert-triangle" class="w-8 h-8 text-red-400 mr-3"></i>
|
|
<h3 class="text-lg font-semibold">삭제 확인</h3>
|
|
</div>
|
|
<p class="text-white/80 mb-6">선택한 프로젝트를 삭제하시겠습니까?<br><span class="text-sm text-gray-500">이 작업은 되돌릴 수 없습니다.</span></p>
|
|
<div class="flex justify-end space-x-2">
|
|
<button onclick="cancelDelete()" class="bg-white/20 hover:bg-white/30 text-white px-4 py-2 rounded-md transition-colors">
|
|
취소
|
|
</button>
|
|
<button onclick="confirmDelete()" class="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-md transition-colors">
|
|
삭제
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let projects = [];
|
|
let currentProjectIdx = null;
|
|
let sortColumn = 'name';
|
|
let sortDirection = 'asc';
|
|
let currentUser = '';
|
|
let currentPage = 1;
|
|
let itemsPerPage = 10;
|
|
let filteredProjects = [];
|
|
|
|
// 초기화
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
initializeApp();
|
|
});
|
|
|
|
// 네비게이션 클래스
|
|
class CommonNavigation {
|
|
constructor(currentPage = '') {
|
|
this.currentPage = currentPage;
|
|
this.createNavigation();
|
|
}
|
|
|
|
async createNavigation() {
|
|
try {
|
|
const response = await fetch('/Common/GetNavigationMenu');
|
|
const data = await response.json();
|
|
|
|
if (data.Success && data.Data) {
|
|
this.createNavigationFromData(data.Data);
|
|
} else {
|
|
this.createFallbackNavigation();
|
|
}
|
|
} catch (error) {
|
|
console.error('Navigation initialization failed:', error);
|
|
this.createFallbackNavigation();
|
|
}
|
|
}
|
|
|
|
createNavigationFromData(menuItems) {
|
|
const nav = document.createElement('nav');
|
|
nav.className = 'glass-effect border-b border-white/10';
|
|
nav.innerHTML = this.getNavigationHTML(menuItems);
|
|
document.body.insertBefore(nav, document.body.firstChild);
|
|
}
|
|
|
|
createFallbackNavigation() {
|
|
this.createNavigation();
|
|
}
|
|
|
|
createNavigation() {
|
|
const nav = document.createElement('nav');
|
|
nav.className = 'glass-effect border-b border-white/10';
|
|
nav.innerHTML = this.getNavigationHTML();
|
|
document.body.insertBefore(nav, document.body.firstChild);
|
|
}
|
|
|
|
getNavigationHTML(menuItems = null) {
|
|
if (!menuItems) {
|
|
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">
|
|
<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">
|
|
<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>
|
|
<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>
|
|
`;
|
|
}
|
|
}
|
|
|
|
function initNavigation(currentPage = '') {
|
|
try {
|
|
new CommonNavigation(currentPage);
|
|
} catch (error) {
|
|
console.error('Navigation initialization failed:', error);
|
|
new CommonNavigation(currentPage);
|
|
}
|
|
}
|
|
|
|
function initializeApp() {
|
|
initNavigation('project');
|
|
getCurrentUser();
|
|
loadProjects();
|
|
setupEventListeners();
|
|
}
|
|
|
|
function getCurrentUser() {
|
|
// 현재 로그인된 사용자 정보 가져오기
|
|
fetch('/Common/GetCurrentUser')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.Success && data.Data) {
|
|
currentUser = data.Data.userName || data.Data.name || '사용자';
|
|
document.getElementById('currentUser').textContent = currentUser;
|
|
} else {
|
|
currentUser = '사용자';
|
|
document.getElementById('currentUser').textContent = currentUser;
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error getting current user:', error);
|
|
currentUser = '사용자';
|
|
document.getElementById('currentUser').textContent = currentUser;
|
|
});
|
|
}
|
|
|
|
function setupEventListeners() {
|
|
// 필터 이벤트
|
|
document.getElementById('statusFilter').addEventListener('change', loadProjects);
|
|
document.getElementById('searchInput').addEventListener('input', filterData);
|
|
document.getElementById('managerFilter').addEventListener('change', loadProjects);
|
|
|
|
// 버튼 이벤트
|
|
document.getElementById('addProjectBtnHeader').addEventListener('click', showAddProjectModal);
|
|
document.getElementById('exportBtn').addEventListener('click', exportToExcel);
|
|
document.getElementById('clearFilterBtn').addEventListener('click', clearFilters);
|
|
|
|
// 컬럼 토글 이벤트
|
|
document.getElementById('columnToggleBtn').addEventListener('click', toggleColumnDropdown);
|
|
setupColumnToggles();
|
|
|
|
// 필터 토글 이벤트
|
|
document.getElementById('filterToggleBtn').addEventListener('click', toggleFilterSection);
|
|
|
|
// 페이징 이벤트
|
|
document.getElementById('prevPage').addEventListener('click', () => changePage(currentPage - 1));
|
|
document.getElementById('nextPage').addEventListener('click', () => changePage(currentPage + 1));
|
|
|
|
// 폼 제출
|
|
document.getElementById('projectForm').addEventListener('submit', handleFormSubmit);
|
|
|
|
// 드롭다운 외부 클릭 시 닫기
|
|
document.addEventListener('click', function(event) {
|
|
const dropdown = document.getElementById('columnDropdown');
|
|
const toggleBtn = document.getElementById('columnToggleBtn');
|
|
if (!dropdown.contains(event.target) && !toggleBtn.contains(event.target)) {
|
|
dropdown.classList.add('hidden');
|
|
}
|
|
});
|
|
}
|
|
|
|
function loadProjects() {
|
|
const status = document.getElementById('statusFilter').value;
|
|
const userFilter = document.getElementById('managerFilter').value;
|
|
|
|
fetch(`/Project/GetProjects?status=${status}&userFilter=${userFilter}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (!data.Success) {
|
|
console.error('Error:', data.Message);
|
|
document.getElementById('projectTableBody').innerHTML = '<tr><td colspan="23" class="px-6 py-4 text-center text-red-400">데이터를 불러오는데 실패했습니다.</td></tr>';
|
|
return;
|
|
}
|
|
projects = data.Data || [];
|
|
if (data.CurrentUser) {
|
|
currentUser = data.CurrentUser;
|
|
document.getElementById('currentUser').textContent = currentUser;
|
|
}
|
|
currentPage = 1;
|
|
filterData();
|
|
updateStatusCounts();
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
document.getElementById('projectTableBody').innerHTML = '<tr><td colspan="23" class="px-6 py-4 text-center text-red-400">데이터를 불러오는데 실패했습니다.</td></tr>';
|
|
});
|
|
}
|
|
|
|
function updateStatusCounts() {
|
|
const counts = {
|
|
'진행': 0,
|
|
'완료': 0,
|
|
'대기': 0,
|
|
'중단': 0
|
|
};
|
|
|
|
projects.forEach(project => {
|
|
const status = project.상태 || '진행';
|
|
counts[status] = (counts[status] || 0) + 1;
|
|
});
|
|
|
|
document.getElementById('progressCount').textContent = counts['진행'] || 0;
|
|
document.getElementById('completedCount').textContent = counts['완료'] || 0;
|
|
document.getElementById('waitingCount').textContent = counts['대기'] || 0;
|
|
document.getElementById('stoppedCount').textContent = counts['중단'] || 0;
|
|
}
|
|
|
|
function renderProjects() {
|
|
const tbody = document.getElementById('projectTableBody');
|
|
|
|
if (filteredProjects.length === 0) {
|
|
tbody.innerHTML = '<tr><td colspan="23" class="px-6 py-4 text-center text-white/60">데이터가 없습니다.</td></tr>';
|
|
updatePagination();
|
|
return;
|
|
}
|
|
|
|
// 페이징 처리
|
|
const startIndex = (currentPage - 1) * itemsPerPage;
|
|
const endIndex = startIndex + itemsPerPage;
|
|
const paginatedProjects = filteredProjects.slice(startIndex, endIndex);
|
|
|
|
tbody.innerHTML = paginatedProjects.map(project => `
|
|
<tr onclick="editProject(${project.idx})" class="hover:bg-white/10 cursor-pointer transition-colors">
|
|
<td class="px-4 py-4 text-sm border-r border-white/20">
|
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${getStatusClass(project.상태)}">
|
|
${project.상태 || '진행'}
|
|
</span>
|
|
</td>
|
|
<td class="px-4 py-4 text-sm border-r border-white/20 font-medium">${project.프로젝트명 || ''}</td>
|
|
<td class="px-4 py-4 text-sm border-r border-white/20">${project.자산번호 || ''}</td>
|
|
<td class="px-4 py-4 text-sm border-r border-white/20">${project.장비모델 || ''}</td>
|
|
<td class="px-4 py-4 text-sm border-r border-white/20">${project.우선순위 || ''}</td>
|
|
<td class="px-4 py-4 text-sm border-r border-white/20">${project.요청국가 || ''}</td>
|
|
<td class="px-4 py-4 text-sm border-r border-white/20">${project.프로젝트관리자 || ''}</td>
|
|
<td class="px-4 py-4 text-sm border-r border-white/20">${formatDateToMonthDay(project.시작일)}</td>
|
|
<td class="px-4 py-4 text-sm border-r border-white/20">${formatDateToMonthDay(project.완료일)}</td>
|
|
<td class="px-3 py-4 text-sm border-r border-white/20 hidden-column col-serial">${project.시리얼번호 || ''}</td>
|
|
<td class="px-3 py-4 text-sm border-r border-white/20 hidden-column col-plant">${project.요청공장 || ''}</td>
|
|
<td class="px-3 py-4 text-sm border-r border-white/20 hidden-column col-line">${project.요청라인 || ''}</td>
|
|
<td class="px-3 py-4 text-sm border-r border-white/20 hidden-column col-package">${project.요청부서패키지 || ''}</td>
|
|
<td class="px-3 py-4 text-sm border-r border-white/20 hidden-column col-staff">${project.요청자 || ''}</td>
|
|
<td class="px-3 py-4 text-sm border-r border-white/20 hidden-column col-process">${project.프로젝트공정 || ''}</td>
|
|
<td class="px-3 py-4 text-sm border-r border-white/20 hidden-column col-expire">${formatDateToMonthDay(project.만료일)}</td>
|
|
<td class="px-3 py-4 text-sm border-r border-white/20 hidden-column col-delivery">${formatDateToMonthDay(project.출고일)}</td>
|
|
<td class="px-3 py-4 text-sm border-r border-white/20 hidden-column col-design">${project.설계담당 || ''}</td>
|
|
<td class="px-3 py-4 text-sm border-r border-white/20 hidden-column col-electric">${project.전장담당 || ''}</td>
|
|
<td class="px-3 py-4 text-sm border-r border-white/20 hidden-column col-program">${project.프로그램담당 || ''}</td>
|
|
<td class="px-3 py-4 text-sm border-r border-white/20 hidden-column col-budget-due">${formatDateToMonthDay(project.예산만기일)}</td>
|
|
<td class="px-3 py-4 text-sm border-r border-white/20 hidden-column col-budget">${project.예산 || ''}</td>
|
|
<td class="px-3 py-4 text-sm hidden-column col-jasmin">${project.웹관리번호 ? `<a href="https://scwa.amkor.co.kr/jasmine/view/${project.웹관리번호}" target="_blank" class="text-blue-300 hover:text-blue-200 underline">${project.웹관리번호}</a>` : ''}</td>
|
|
</tr>
|
|
`).join('');
|
|
|
|
updatePagination();
|
|
}
|
|
|
|
function filterProjects() {
|
|
const searchTerm = document.getElementById('searchInput').value.toLowerCase();
|
|
|
|
return projects.filter(project => {
|
|
const matchesSearch = !searchTerm ||
|
|
(project.프로젝트명 && project.프로젝트명.toLowerCase().includes(searchTerm)) ||
|
|
(project.프로젝트공정 && project.프로젝트공정.toLowerCase().includes(searchTerm)) ||
|
|
(project.자산번호 && project.자산번호.toLowerCase().includes(searchTerm)) ||
|
|
(project.장비모델 && project.장비모델.toLowerCase().includes(searchTerm)) ||
|
|
(project.시리얼번호 && project.시리얼번호.toLowerCase().includes(searchTerm)) ||
|
|
(project.프로젝트관리자 && project.프로젝트관리자.toLowerCase().includes(searchTerm)) ||
|
|
(project.설계담당 && project.설계담당.toLowerCase().includes(searchTerm)) ||
|
|
(project.전장담당 && project.전장담당.toLowerCase().includes(searchTerm)) ||
|
|
(project.프로그램담당 && project.프로그램담당.toLowerCase().includes(searchTerm));
|
|
|
|
return matchesSearch;
|
|
});
|
|
}
|
|
|
|
function filterData() {
|
|
filteredProjects = filterProjects();
|
|
currentPage = 1;
|
|
renderProjects();
|
|
updatePagination();
|
|
}
|
|
|
|
function updatePagination() {
|
|
const totalPages = Math.ceil(filteredProjects.length / itemsPerPage);
|
|
const startIndex = (currentPage - 1) * itemsPerPage + 1;
|
|
const endIndex = Math.min(currentPage * itemsPerPage, filteredProjects.length);
|
|
|
|
// 총 개수와 현재 범위 업데이트
|
|
document.getElementById('totalCount').textContent = filteredProjects.length;
|
|
document.getElementById('currentRange').textContent = `${startIndex}-${endIndex}`;
|
|
|
|
// 이전/다음 버튼 상태 업데이트
|
|
document.getElementById('prevPage').disabled = currentPage <= 1;
|
|
document.getElementById('nextPage').disabled = currentPage >= totalPages;
|
|
|
|
// 페이지 번호 생성
|
|
const pageNumbersContainer = document.getElementById('pageNumbers');
|
|
pageNumbersContainer.innerHTML = '';
|
|
|
|
const maxVisiblePages = 5;
|
|
let startPage = Math.max(1, currentPage - Math.floor(maxVisiblePages / 2));
|
|
let endPage = Math.min(totalPages, startPage + maxVisiblePages - 1);
|
|
|
|
if (endPage - startPage + 1 < maxVisiblePages) {
|
|
startPage = Math.max(1, endPage - maxVisiblePages + 1);
|
|
}
|
|
|
|
for (let i = startPage; i <= endPage; i++) {
|
|
const pageButton = document.createElement('button');
|
|
pageButton.className = `px-3 py-1 rounded-md text-sm transition-colors ${
|
|
i === currentPage
|
|
? 'bg-primary-500 text-white'
|
|
: 'bg-white/20 hover:bg-white/30 text-white'
|
|
}`;
|
|
pageButton.textContent = i;
|
|
pageButton.addEventListener('click', () => changePage(i));
|
|
pageNumbersContainer.appendChild(pageButton);
|
|
}
|
|
}
|
|
|
|
function changePage(page) {
|
|
const totalPages = Math.ceil(filteredProjects.length / itemsPerPage);
|
|
if (page >= 1 && page <= totalPages) {
|
|
currentPage = page;
|
|
renderProjects();
|
|
}
|
|
}
|
|
|
|
function clearFilters() {
|
|
document.getElementById('searchInput').value = '';
|
|
document.getElementById('statusFilter').value = '진행';
|
|
document.getElementById('managerFilter').value = 'my';
|
|
loadProjects();
|
|
}
|
|
|
|
function showAddProjectModal() {
|
|
currentProjectIdx = null;
|
|
document.getElementById('modalTitle').textContent = '프로젝트 추가';
|
|
document.getElementById('projectForm').reset();
|
|
document.getElementById('deleteProjectBtn').classList.add('hidden');
|
|
document.getElementById('projectModal').classList.remove('hidden');
|
|
}
|
|
|
|
function editProject(idx) {
|
|
currentProjectIdx = idx;
|
|
|
|
fetch(`/Project/GetProject?id=${idx}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (!data.Success) {
|
|
alert('프로젝트 정보를 불러오는데 실패했습니다: ' + data.Message);
|
|
return;
|
|
}
|
|
|
|
const project = data.Data;
|
|
document.getElementById('modalTitle').textContent = '프로젝트 편집';
|
|
document.getElementById('projectIdx').value = project.idx;
|
|
document.getElementById('projectName').value = project.프로젝트명 || '';
|
|
document.getElementById('projectProcess').value = project.프로젝트공정 || '';
|
|
document.getElementById('projectSdate').value = project.시작일 || '';
|
|
document.getElementById('projectEdate').value = project.완료일 || '';
|
|
document.getElementById('projectDdate').value = project.만료일 || '';
|
|
document.getElementById('projectOdate').value = project.출고일 || '';
|
|
document.getElementById('projectUserManager').value = project.프로젝트관리자 || '';
|
|
document.getElementById('projectStatus').value = project.상태 || '진행';
|
|
document.getElementById('projectMemo').value = project.memo || '';
|
|
|
|
document.getElementById('deleteProjectBtn').classList.remove('hidden');
|
|
document.getElementById('projectModal').classList.remove('hidden');
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
alert('프로젝트 정보를 불러오는데 실패했습니다.');
|
|
});
|
|
}
|
|
|
|
function closeProjectModal() {
|
|
document.getElementById('projectModal').classList.add('hidden');
|
|
document.getElementById('deleteModal').classList.add('hidden');
|
|
}
|
|
|
|
function handleFormSubmit(e) {
|
|
e.preventDefault();
|
|
|
|
const projectData = {
|
|
idx: currentProjectIdx ? parseInt(document.getElementById('projectIdx').value) : 0,
|
|
name: document.getElementById('projectName').value,
|
|
process: document.getElementById('projectProcess').value,
|
|
sdate: document.getElementById('projectSdate').value || null,
|
|
edate: document.getElementById('projectEdate').value || null,
|
|
ddate: document.getElementById('projectDdate').value || null,
|
|
odate: document.getElementById('projectOdate').value || null,
|
|
userManager: document.getElementById('projectUserManager').value,
|
|
status: document.getElementById('projectStatus').value,
|
|
memo: document.getElementById('projectMemo').value
|
|
};
|
|
|
|
const url = currentProjectIdx ? '/Project/UpdateProject' : '/Project/CreateProject';
|
|
const method = currentProjectIdx ? 'PUT' : 'POST';
|
|
|
|
fetch(url, {
|
|
method: method,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify(projectData)
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (!data.Success) {
|
|
alert('오류: ' + data.Message);
|
|
} else {
|
|
alert(data.Message);
|
|
closeProjectModal();
|
|
loadProjects();
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
alert('저장 중 오류가 발생했습니다.');
|
|
});
|
|
}
|
|
|
|
function deleteCurrentProject() {
|
|
document.getElementById('deleteModal').classList.remove('hidden');
|
|
}
|
|
|
|
function cancelDelete() {
|
|
document.getElementById('deleteModal').classList.add('hidden');
|
|
}
|
|
|
|
function confirmDelete() {
|
|
if (!currentProjectIdx) return;
|
|
|
|
fetch(`/Project/DeleteProject?id=${currentProjectIdx}`, {
|
|
method: 'DELETE'
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (!data.Success) {
|
|
alert('오류: ' + data.Message);
|
|
} else {
|
|
alert(data.Message);
|
|
closeProjectModal();
|
|
loadProjects();
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
alert('삭제 중 오류가 발생했습니다.');
|
|
});
|
|
}
|
|
|
|
function getStatusClass(status) {
|
|
switch(status) {
|
|
case '진행':
|
|
return 'bg-blue-500/20 text-blue-300';
|
|
case '완료':
|
|
return 'bg-green-500/20 text-green-300';
|
|
case '대기':
|
|
return 'bg-yellow-500/20 text-yellow-300';
|
|
case '중단':
|
|
return 'bg-red-500/20 text-red-300';
|
|
default:
|
|
return 'bg-gray-500/20 text-gray-300';
|
|
}
|
|
}
|
|
|
|
function exportToExcel() {
|
|
const filteredProjects = filterProjects();
|
|
if (filteredProjects.length === 0) {
|
|
alert('내보낼 데이터가 없습니다.');
|
|
return;
|
|
}
|
|
|
|
// CSV 형식으로 데이터 변환
|
|
const headers = ['상태', '자산번호', '장비모델', '시리얼번호', '우선순위', '요청국가', '요청공장', '요청라인', '요청부서패키지', '요청자', '프로젝트공정', '시작일', '완료일', '만료일', '출고일', '프로젝트명', '프로젝트관리자', '설계담당', '전장담당', '프로그램담당', '예산만기일', '예산', '웹관리번호'];
|
|
const csvContent = [
|
|
headers.join(','),
|
|
...filteredProjects.map(project => [
|
|
project.상태 || '',
|
|
project.자산번호 || '',
|
|
project.장비모델 || '',
|
|
project.시리얼번호 || '',
|
|
project.우선순위 || '',
|
|
project.요청국가 || '',
|
|
project.요청공장 || '',
|
|
project.요청라인 || '',
|
|
project.요청부서패키지 || '',
|
|
project.요청자 || '',
|
|
project.프로젝트공정 || '',
|
|
project.시작일 || '',
|
|
project.완료일 || '',
|
|
project.만료일 || '',
|
|
project.출고일 || '',
|
|
project.프로젝트명 || '',
|
|
project.프로젝트관리자 || '',
|
|
project.설계담당 || '',
|
|
project.전장담당 || '',
|
|
project.프로그램담당 || '',
|
|
project.예산만기일 || '',
|
|
project.예산 || '',
|
|
project.웹관리번호 || ''
|
|
].join(','))
|
|
].join('\n');
|
|
|
|
// 파일 다운로드
|
|
const blob = new Blob(['\ufeff' + csvContent], { type: 'text/csv;charset=utf-8;' });
|
|
const link = document.createElement('a');
|
|
const url = URL.createObjectURL(blob);
|
|
link.setAttribute('href', url);
|
|
link.setAttribute('download', `프로젝트목록_${new Date().toISOString().split('T')[0]}.csv`);
|
|
link.style.visibility = 'hidden';
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
document.body.removeChild(link);
|
|
}
|
|
|
|
function toggleColumnDropdown(event) {
|
|
event.stopPropagation();
|
|
const dropdown = document.getElementById('columnDropdown');
|
|
dropdown.classList.toggle('hidden');
|
|
}
|
|
|
|
function setupColumnToggles() {
|
|
const columnMappings = {
|
|
'col-serial': 'col-serial',
|
|
'col-plant': 'col-plant',
|
|
'col-line': 'col-line',
|
|
'col-package': 'col-package',
|
|
'col-staff': 'col-staff',
|
|
'col-process': 'col-process',
|
|
'col-expire': 'col-expire',
|
|
'col-delivery': 'col-delivery',
|
|
'col-design': 'col-design',
|
|
'col-electric': 'col-electric',
|
|
'col-program': 'col-program',
|
|
'col-budget-due': 'col-budget-due',
|
|
'col-budget': 'col-budget',
|
|
'col-jasmin': 'col-jasmin'
|
|
};
|
|
|
|
Object.keys(columnMappings).forEach(checkboxId => {
|
|
const checkbox = document.getElementById(checkboxId);
|
|
if (checkbox) {
|
|
checkbox.addEventListener('change', function() {
|
|
toggleColumn(columnMappings[checkboxId], this.checked);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
function toggleColumn(columnClass, show) {
|
|
const elements = document.querySelectorAll('.' + columnClass);
|
|
elements.forEach(element => {
|
|
if (show) {
|
|
element.classList.remove('hidden-column');
|
|
} else {
|
|
element.classList.add('hidden-column');
|
|
}
|
|
});
|
|
}
|
|
|
|
function formatDateToMonthDay(dateString) {
|
|
if (!dateString) return '';
|
|
|
|
try {
|
|
const date = new Date(dateString);
|
|
if (isNaN(date.getTime())) return dateString; // 유효하지 않은 날짜면 원본 반환
|
|
|
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
const day = String(date.getDate()).padStart(2, '0');
|
|
return `${month}-${day}`;
|
|
} catch (error) {
|
|
return dateString; // 에러 발생시 원본 반환
|
|
}
|
|
}
|
|
|
|
function toggleFilterSection() {
|
|
const filterSection = document.querySelector('.filter-section');
|
|
const toggleIcon = document.querySelector('.filter-toggle-btn');
|
|
|
|
filterSection.classList.toggle('hidden');
|
|
toggleIcon.classList.toggle('rotated');
|
|
}
|
|
</script>
|
|
</body>
|
|
</html> |