// 프로젝트 관리 페이지 JavaScript // 전역 변수 let allProjects = []; let filteredProjects = []; let currentEditingProject = null; let currentStatusFilter = 'all'; // 'all', 'active', 'inactive' // 페이지 초기화 document.addEventListener('DOMContentLoaded', function() { console.log('📁 프로젝트 관리 페이지 초기화 시작'); initializePage(); loadProjects(); }); // 페이지 초기화 function initializePage() { // 시간 업데이트 시작 updateCurrentTime(); setInterval(updateCurrentTime, 1000); // 사용자 정보 업데이트 updateUserInfo(); // 프로필 메뉴 토글 setupProfileMenu(); // 로그아웃 버튼 setupLogoutButton(); // 검색 입력 이벤트 setupSearchInput(); } // 현재 시간 업데이트 (시 분 초 형식으로 고정) function updateCurrentTime() { const now = new Date(); const hours = String(now.getHours()).padStart(2, '0'); const minutes = String(now.getMinutes()).padStart(2, '0'); const seconds = String(now.getSeconds()).padStart(2, '0'); const timeString = `${hours}시 ${minutes}분 ${seconds}초`; const timeElement = document.getElementById('timeValue'); if (timeElement) { timeElement.textContent = timeString; } } // navbar/sidebar는 app-init.js에서 공통 처리 function updateUserInfo() { // app-init.js가 navbar 사용자 정보를 처리 } // 프로필 메뉴 설정 function setupProfileMenu() { const userProfile = document.getElementById('userProfile'); const profileMenu = document.getElementById('profileMenu'); if (userProfile && profileMenu) { userProfile.addEventListener('click', function(e) { e.stopPropagation(); const isVisible = profileMenu.style.display === 'block'; profileMenu.style.display = isVisible ? 'none' : 'block'; }); // 외부 클릭 시 메뉴 닫기 document.addEventListener('click', function() { profileMenu.style.display = 'none'; }); } } // 로그아웃 버튼 설정 function setupLogoutButton() { const logoutBtn = document.getElementById('logoutBtn'); if (logoutBtn) { logoutBtn.addEventListener('click', function() { if (confirm('로그아웃 하시겠습니까?')) { localStorage.removeItem('token'); localStorage.removeItem('user'); localStorage.removeItem('userInfo'); window.location.href = '/index.html'; } }); } } // 검색 입력 설정 function setupSearchInput() { const searchInput = document.getElementById('searchInput'); if (searchInput) { searchInput.addEventListener('input', function() { searchProjects(); }); searchInput.addEventListener('keypress', function(e) { if (e.key === 'Enter') { searchProjects(); } }); } } // 프로젝트 목록 로드 async function loadProjects() { try { console.log('📊 프로젝트 목록 로딩 시작'); const response = await apiCall('/projects', 'GET'); console.log('📊 API 응답 구조:', response); // API 응답이 { success: true, data: [...] } 형태인 경우 처리 let projectData = []; if (response && response.success && Array.isArray(response.data)) { projectData = response.data; } else if (Array.isArray(response)) { projectData = response; } else { console.warn('프로젝트 데이터가 배열이 아닙니다:', response); projectData = []; } allProjects = projectData; console.log(`✅ 프로젝트 ${allProjects.length}개 로드 완료`); // 초기 필터 적용 applyAllFilters(); updateStatCardActiveState(); } catch (error) { console.error('프로젝트 로딩 오류:', error); showToast('프로젝트 목록을 불러오는데 실패했습니다.', 'error'); allProjects = []; filteredProjects = []; renderProjects(); } } // 프로젝트 목록 렌더링 function renderProjects() { const projectsGrid = document.getElementById('projectsGrid'); const emptyState = document.getElementById('emptyState'); if (!projectsGrid || !emptyState) return; if (filteredProjects.length === 0) { projectsGrid.style.display = 'none'; emptyState.style.display = 'block'; return; } projectsGrid.style.display = 'grid'; emptyState.style.display = 'none'; const projectsHtml = filteredProjects.map(project => { // 프로젝트 상태 아이콘 및 텍스트 const statusMap = { 'planning': { icon: '📋', text: '계획', color: '#6b7280' }, 'active': { icon: '🚀', text: '진행중', color: '#10b981' }, 'completed': { icon: '✅', text: '완료', color: '#3b82f6' }, 'cancelled': { icon: '❌', text: '취소', color: '#ef4444' } }; const validStatuses = ['planning', 'active', 'completed', 'cancelled']; const safeProjectStatus = validStatuses.includes(project.project_status) ? project.project_status : 'active'; const status = statusMap[safeProjectStatus]; // is_active 값 처리 (DB에서 0/1로 오는 경우 대비) const isInactive = project.is_active === 0 || project.is_active === false || project.is_active === 'false'; // XSS 방지를 위한 안전한 값 const safeProjectId = parseInt(project.project_id) || 0; const safeJobNo = escapeHtml(project.job_no || 'Job No. 없음'); const safeProjectName = escapeHtml(project.project_name || '-'); const safePm = escapeHtml(project.pm || '-'); const safeSite = escapeHtml(project.site || '-'); console.log('🎨 카드 렌더링:', { project_id: project.project_id, project_name: project.project_name, is_active_raw: project.is_active, isInactive: isInactive }); return `
${isInactive ? '
🚫 비활성화됨
' : ''}
${safeJobNo}

${safeProjectName} ${isInactive ? '(비활성)' : ''}

상태 ${status.icon} ${status.text}
계약일 ${project.contract_date ? formatDate(project.contract_date) : '-'}
납기일 ${project.due_date ? formatDate(project.due_date) : '-'}
PM ${safePm}
현장 ${safeSite}
${isInactive ? '
⚠️ 작업보고서에서 숨김
' : ''}
`; }).join(''); projectsGrid.innerHTML = projectsHtml; } // 프로젝트 통계 업데이트 function updateProjectStats() { const activeProjects = filteredProjects.filter(p => p.is_active === 1 || p.is_active === true); const inactiveProjects = filteredProjects.filter(p => p.is_active === 0 || p.is_active === false); const activeProjectsElement = document.getElementById('activeProjects'); const inactiveProjectsElement = document.getElementById('inactiveProjects'); const totalProjectsElement = document.getElementById('totalProjects'); if (activeProjectsElement) { activeProjectsElement.textContent = activeProjects.length; } if (inactiveProjectsElement) { inactiveProjectsElement.textContent = inactiveProjects.length; } if (totalProjectsElement) { totalProjectsElement.textContent = filteredProjects.length; } console.log('📊 프로젝트 통계:', { 전체: filteredProjects.length, 활성: activeProjects.length, 비활성: inactiveProjects.length }); } // 날짜 포맷팅 function formatDate(dateString) { if (!dateString) return ''; const date = new Date(dateString); return date.toLocaleDateString('ko-KR', { year: 'numeric', month: '2-digit', day: '2-digit' }); } // 상태별 필터링 function filterByStatus(status) { currentStatusFilter = status; // 통계 카드 활성화 상태 업데이트 updateStatCardActiveState(); // 필터링 적용 applyAllFilters(); console.log(`🔍 상태 필터 적용: ${status}`); } // 통계 카드 활성화 상태 업데이트 function updateStatCardActiveState() { // 모든 통계 카드에서 active 클래스 제거 document.querySelectorAll('.stat-item').forEach(item => { item.classList.remove('active'); }); // 현재 선택된 필터에 active 클래스 추가 const activeCard = document.querySelector(`.${currentStatusFilter === 'active' ? 'active-stat' : currentStatusFilter === 'inactive' ? 'inactive-stat' : 'total-stat'}`); if (activeCard) { activeCard.classList.add('active'); } } // 모든 필터 적용 (검색 + 상태) function applyAllFilters() { const searchInput = document.getElementById('searchInput'); const searchTerm = searchInput ? searchInput.value.toLowerCase().trim() : ''; // 1단계: 상태 필터링 let statusFiltered = [...allProjects]; if (currentStatusFilter === 'active') { statusFiltered = allProjects.filter(p => p.is_active === 1 || p.is_active === true); } else if (currentStatusFilter === 'inactive') { statusFiltered = allProjects.filter(p => p.is_active === 0 || p.is_active === false); } // 2단계: 검색 필터링 if (!searchTerm) { filteredProjects = statusFiltered; } else { filteredProjects = statusFiltered.filter(project => project.project_name.toLowerCase().includes(searchTerm) || (project.job_no && project.job_no.toLowerCase().includes(searchTerm)) || (project.pm && project.pm.toLowerCase().includes(searchTerm)) || (project.site && project.site.toLowerCase().includes(searchTerm)) ); } renderProjects(); updateProjectStats(); } // 프로젝트 검색 (기존 함수 수정) function searchProjects() { applyAllFilters(); } // 프로젝트 필터링 function filterProjects() { const statusFilter = document.getElementById('statusFilter'); const selectedStatus = statusFilter ? statusFilter.value : ''; // 현재는 상태 필드가 없으므로 기본 필터링만 적용 searchProjects(); } // 프로젝트 정렬 function sortProjects() { const sortBy = document.getElementById('sortBy'); const sortField = sortBy ? sortBy.value : 'created_at'; filteredProjects.sort((a, b) => { switch (sortField) { case 'project_name': return a.project_name.localeCompare(b.project_name); case 'due_date': if (!a.due_date && !b.due_date) return 0; if (!a.due_date) return 1; if (!b.due_date) return -1; return new Date(a.due_date) - new Date(b.due_date); case 'created_at': default: return new Date(b.created_at || 0) - new Date(a.created_at || 0); } }); renderProjects(); } // 프로젝트 목록 새로고침 async function refreshProjectList() { const refreshBtn = document.querySelector('.btn-secondary'); if (refreshBtn) { const originalText = refreshBtn.innerHTML; refreshBtn.innerHTML = '새로고침 중...'; refreshBtn.disabled = true; await loadProjects(); refreshBtn.innerHTML = originalText; refreshBtn.disabled = false; } else { await loadProjects(); } showToast('프로젝트 목록이 새로고침되었습니다.', 'success'); } // 프로젝트 모달 열기 function openProjectModal(project = null) { const modal = document.getElementById('projectModal'); const modalTitle = document.getElementById('modalTitle'); const deleteBtn = document.getElementById('deleteProjectBtn'); if (!modal) return; currentEditingProject = project; if (project) { // 수정 모드 modalTitle.textContent = '프로젝트 수정'; deleteBtn.style.display = 'inline-flex'; // 폼에 데이터 채우기 document.getElementById('projectId').value = project.project_id; document.getElementById('jobNo').value = project.job_no || ''; document.getElementById('projectName').value = project.project_name || ''; document.getElementById('contractDate').value = project.contract_date || ''; document.getElementById('dueDate').value = project.due_date || ''; document.getElementById('deliveryMethod').value = project.delivery_method || ''; document.getElementById('site').value = project.site || ''; document.getElementById('pm').value = project.pm || ''; document.getElementById('projectStatus').value = project.project_status || 'active'; document.getElementById('completedDate').value = project.completed_date || ''; // is_active 값 처리 (DB에서 0/1로 오는 경우 대비) const isActiveValue = project.is_active === 1 || project.is_active === true || project.is_active === 'true'; document.getElementById('isActive').checked = isActiveValue; console.log('🔧 프로젝트 로드:', { project_id: project.project_id, project_name: project.project_name, is_active_raw: project.is_active, is_active_processed: isActiveValue }); } else { // 신규 등록 모드 modalTitle.textContent = '새 프로젝트 등록'; deleteBtn.style.display = 'none'; // 폼 초기화 document.getElementById('projectForm').reset(); document.getElementById('projectId').value = ''; } modal.style.display = 'flex'; document.body.style.overflow = 'hidden'; // 첫 번째 입력 필드에 포커스 setTimeout(() => { const firstInput = document.getElementById('jobNo'); if (firstInput) firstInput.focus(); }, 100); } // 프로젝트 모달 닫기 function closeProjectModal() { const modal = document.getElementById('projectModal'); if (modal) { modal.style.display = 'none'; document.body.style.overflow = ''; currentEditingProject = null; } } // 프로젝트 편집 function editProject(projectId) { const project = allProjects.find(p => p.project_id === projectId); if (project) { openProjectModal(project); } else { showToast('프로젝트를 찾을 수 없습니다.', 'error'); } } // 프로젝트 저장 async function saveProject() { try { const form = document.getElementById('projectForm'); const formData = new FormData(form); const projectData = { job_no: document.getElementById('jobNo').value.trim(), project_name: document.getElementById('projectName').value.trim(), contract_date: document.getElementById('contractDate').value || null, due_date: document.getElementById('dueDate').value || null, delivery_method: document.getElementById('deliveryMethod').value || null, site: document.getElementById('site').value.trim() || null, pm: document.getElementById('pm').value.trim() || null, project_status: document.getElementById('projectStatus').value || 'active', completed_date: document.getElementById('completedDate').value || null, is_active: document.getElementById('isActive').checked ? 1 : 0 }; console.log('💾 저장할 프로젝트 데이터:', projectData); // 필수 필드 검증 if (!projectData.job_no || !projectData.project_name) { showToast('Job No.와 프로젝트명은 필수 입력 항목입니다.', 'error'); return; } const projectId = document.getElementById('projectId').value; let response; if (projectId) { // 수정 response = await apiCall(`/projects/${projectId}`, 'PUT', projectData); } else { // 신규 등록 response = await apiCall('/projects', 'POST', projectData); } if (response && (response.success || response.project_id)) { const action = projectId ? '수정' : '등록'; showToast(`프로젝트가 성공적으로 ${action}되었습니다.`, 'success'); closeProjectModal(); await loadProjects(); } else { throw new Error(response?.message || '저장에 실패했습니다.'); } } catch (error) { console.error('프로젝트 저장 오류:', error); showToast(error.message || '프로젝트 저장 중 오류가 발생했습니다.', 'error'); } } // 프로젝트 삭제 확인 function confirmDeleteProject(projectId) { const project = allProjects.find(p => p.project_id === projectId); if (!project) { showToast('프로젝트를 찾을 수 없습니다.', 'error'); return; } if (confirm(`"${project.project_name}" 프로젝트를 정말 삭제하시겠습니까?\n\n⚠️ 삭제된 프로젝트는 복구할 수 없습니다.`)) { deleteProjectById(projectId); } } // 프로젝트 삭제 (수정 모드에서) function deleteProject() { if (currentEditingProject) { confirmDeleteProject(currentEditingProject.project_id); } } // 프로젝트 삭제 실행 async function deleteProjectById(projectId) { try { const response = await apiCall(`/projects/${projectId}`, 'DELETE'); if (response && response.success) { showToast('프로젝트가 성공적으로 삭제되었습니다.', 'success'); closeProjectModal(); await loadProjects(); } else { throw new Error(response?.message || '삭제에 실패했습니다.'); } } catch (error) { console.error('프로젝트 삭제 오류:', error); showToast(error.message || '프로젝트 삭제 중 오류가 발생했습니다.', 'error'); } } // 토스트 메시지 표시 function showToast(message, type = 'info') { // 기존 토스트 제거 const existingToast = document.querySelector('.toast'); if (existingToast) { existingToast.remove(); } // 새 토스트 생성 const toast = document.createElement('div'); toast.className = `toast toast-${type}`; toast.textContent = message; // 스타일 적용 Object.assign(toast.style, { position: 'fixed', top: '20px', right: '20px', padding: '12px 24px', borderRadius: '8px', color: 'white', fontWeight: '500', zIndex: '1000', transform: 'translateX(100%)', transition: 'transform 0.3s ease' }); // 타입별 배경색 const colors = { success: '#10b981', error: '#ef4444', warning: '#f59e0b', info: '#3b82f6' }; toast.style.backgroundColor = colors[type] || colors.info; document.body.appendChild(toast); // 애니메이션 setTimeout(() => { toast.style.transform = 'translateX(0)'; }, 100); // 자동 제거 setTimeout(() => { toast.style.transform = 'translateX(100%)'; setTimeout(() => { if (toast.parentNode) { toast.remove(); } }, 300); }, 3000); } // 전역 함수로 노출 window.openProjectModal = openProjectModal; window.closeProjectModal = closeProjectModal; window.editProject = editProject; window.saveProject = saveProject; window.deleteProject = deleteProject; window.confirmDeleteProject = confirmDeleteProject; window.searchProjects = searchProjects; window.filterProjects = filterProjects; window.sortProjects = sortProjects; window.refreshProjectList = refreshProjectList; window.filterByStatus = filterByStatus;