/** * issue-view.js — 부적합 조회 페이지 스크립트 */ let currentUser = null; let issues = []; let projects = []; // 프로젝트 데이터 캐시 let currentRange = 'week'; // 기본값: 이번 주 // 애니메이션 함수들 function animateHeaderAppearance() { console.log('헤더 애니메이션 시작'); // 헤더 요소 찾기 (공통 헤더가 생성한 요소) const headerElement = document.querySelector('header') || document.querySelector('[class*="header"]') || document.querySelector('nav'); if (headerElement) { headerElement.classList.add('header-fade-in'); setTimeout(() => { headerElement.classList.add('visible'); // 헤더 애니메이션 완료 후 본문 애니메이션 setTimeout(() => { animateContentAppearance(); }, 200); }, 50); } else { // 헤더를 찾지 못했으면 바로 본문 애니메이션 animateContentAppearance(); } } // 본문 컨텐츠 애니메이션 function animateContentAppearance() { // 모든 content-fade-in 요소들을 순차적으로 애니메이션 const contentElements = document.querySelectorAll('.content-fade-in'); contentElements.forEach((element, index) => { setTimeout(() => { element.classList.add('visible'); }, index * 100); // 100ms씩 지연 }); } // API 로드 후 초기화 함수 async function initializeIssueView() { const token = TokenManager.getToken(); if (!token) { window.location.href = '/index.html'; return; } try { const user = await AuthAPI.getCurrentUser(); currentUser = user; localStorage.setItem('sso_user', JSON.stringify(user)); // 공통 헤더 초기화 await window.commonHeader.init(user, 'issues_view'); // 헤더 초기화 후 부드러운 애니메이션 시작 setTimeout(() => { animateHeaderAppearance(); }, 100); // 사용자 역할에 따른 페이지 제목 설정 updatePageTitle(user); // 페이지 접근 권한 체크 (부적합 조회 페이지) setTimeout(() => { if (!canAccessPage('issues_view')) { alert('부적합 조회 페이지에 접근할 권한이 없습니다.'); window.location.href = '/index.html'; return; } }, 500); } catch (error) { console.error('인증 실패:', error); TokenManager.removeToken(); TokenManager.removeUser(); window.location.href = '/index.html'; return; } // 프로젝트 로드 await loadProjects(); // 기본 날짜 설정 (이번 주) setDefaultDateRange(); // 기본값: 이번 주 데이터 로드 await loadIssues(); setDateRange('week'); } // showImageModal은 photo-modal.js에서 제공됨 // 기본 날짜 범위 설정 function setDefaultDateRange() { const today = new Date(); const weekStart = new Date(today); weekStart.setDate(today.getDate() - today.getDay()); // 이번 주 일요일 // 날짜 입력 필드에 기본값 설정 document.getElementById('startDateInput').value = formatDateForInput(weekStart); document.getElementById('endDateInput').value = formatDateForInput(today); } // 날짜를 input[type="date"] 형식으로 포맷 function formatDateForInput(date) { return date.toISOString().split('T')[0]; } // 날짜 필터 적용 function applyDateFilter() { const startDate = document.getElementById('startDateInput').value; const endDate = document.getElementById('endDateInput').value; if (!startDate || !endDate) { alert('시작날짜와 끝날짜를 모두 선택해주세요.'); return; } if (new Date(startDate) > new Date(endDate)) { alert('시작날짜는 끝날짜보다 이전이어야 합니다.'); return; } // 필터 적용 filterIssues(); } // 사용자 역할에 따른 페이지 제목 업데이트 function updatePageTitle(user) { const titleElement = document.getElementById('pageTitle'); const descriptionElement = document.getElementById('pageDescription'); if (user.role === 'admin') { titleElement.innerHTML = ` 전체 부적합 조회 `; descriptionElement.textContent = '모든 사용자가 등록한 부적합 사항을 관리할 수 있습니다'; } else { titleElement.innerHTML = ` 내 부적합 조회 `; descriptionElement.textContent = '내가 등록한 부적합 사항을 확인할 수 있습니다'; } } // 프로젝트 로드 (API 기반) async function loadProjects() { try { // 모든 프로젝트 로드 (활성/비활성 모두 - 기존 데이터 조회를 위해) projects = await ProjectsAPI.getAll(false); const projectFilter = document.getElementById('projectFilter'); // 기존 옵션 제거 (전체 프로젝트 옵션 제외) projectFilter.innerHTML = ''; // 모든 프로젝트 추가 projects.forEach(project => { const option = document.createElement('option'); option.value = project.id; option.textContent = `${project.job_no} / ${project.project_name}${!project.is_active ? ' (비활성)' : ''}`; projectFilter.appendChild(option); }); } catch (error) { console.error('프로젝트 로드 실패:', error); } } // 이슈 필터링 // 검토 상태 확인 함수 function isReviewCompleted(issue) { return issue.status === 'complete' && issue.work_hours && issue.work_hours > 0; } // 날짜 필터링 함수 function filterByDate(issues, dateFilter) { const now = new Date(); const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); switch (dateFilter) { case 'today': return issues.filter(issue => { const issueDate = new Date(issue.report_date); return issueDate >= today; }); case 'week': const weekStart = new Date(today); weekStart.setDate(today.getDate() - today.getDay()); return issues.filter(issue => { const issueDate = new Date(issue.report_date); return issueDate >= weekStart; }); case 'month': const monthStart = new Date(today.getFullYear(), today.getMonth(), 1); return issues.filter(issue => { const issueDate = new Date(issue.report_date); return issueDate >= monthStart; }); default: return issues; } } // 날짜 범위별 필터링 함수 function filterByDateRange(issues, range) { const now = new Date(); const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); switch (range) { case 'today': return issues.filter(issue => { const issueDate = new Date(issue.created_at); const issueDay = new Date(issueDate.getFullYear(), issueDate.getMonth(), issueDate.getDate()); return issueDay.getTime() === today.getTime(); }); case 'week': const weekStart = new Date(today); weekStart.setDate(today.getDate() - today.getDay()); const weekEnd = new Date(weekStart); weekEnd.setDate(weekStart.getDate() + 6); weekEnd.setHours(23, 59, 59, 999); return issues.filter(issue => { const issueDate = new Date(issue.created_at); return issueDate >= weekStart && issueDate <= weekEnd; }); case 'month': const monthStart = new Date(today.getFullYear(), today.getMonth(), 1); const monthEnd = new Date(today.getFullYear(), today.getMonth() + 1, 0); monthEnd.setHours(23, 59, 59, 999); return issues.filter(issue => { const issueDate = new Date(issue.created_at); return issueDate >= monthStart && issueDate <= monthEnd; }); default: return issues; } } function filterIssues() { // 필터 값 가져오기 const selectedProjectId = document.getElementById('projectFilter').value; const reviewStatusFilter = document.getElementById('reviewStatusFilter').value; let filteredIssues = [...issues]; // 프로젝트 필터 적용 if (selectedProjectId) { filteredIssues = filteredIssues.filter(issue => { const issueProjectId = issue.project_id || issue.projectId; return issueProjectId && (issueProjectId == selectedProjectId || issueProjectId.toString() === selectedProjectId.toString()); }); } // 워크플로우 상태 필터 적용 if (reviewStatusFilter) { filteredIssues = filteredIssues.filter(issue => { // 새로운 워크플로우 시스템 사용 if (issue.review_status) { return issue.review_status === reviewStatusFilter; } // 기존 데이터 호환성을 위한 폴백 else { const isCompleted = isReviewCompleted(issue); if (reviewStatusFilter === 'pending_review') return !isCompleted; if (reviewStatusFilter === 'completed') return isCompleted; return false; } }); } // 날짜 범위 필터 적용 (입력 필드에서 선택된 범위) const startDateInput = document.getElementById('startDateInput').value; const endDateInput = document.getElementById('endDateInput').value; if (startDateInput && endDateInput) { filteredIssues = filteredIssues.filter(issue => { const issueDate = new Date(issue.report_date); const startOfDay = new Date(startDateInput); startOfDay.setHours(0, 0, 0, 0); const endOfDay = new Date(endDateInput); endOfDay.setHours(23, 59, 59, 999); return issueDate >= startOfDay && issueDate <= endOfDay; }); } // 전역 변수에 필터링된 결과 저장 window.filteredIssues = filteredIssues; displayResults(); } // 프로젝트 정보 표시용 함수 function getProjectInfo(projectId) { if (!projectId) { return '프로젝트 미지정'; } // 전역 projects 배열에서 찾기 const project = projects.find(p => p.id == projectId); if (project) { return `${project.job_no} / ${project.project_name}`; } return `프로젝트 ID: ${projectId} (정보 없음)`; } // 날짜 범위 설정 및 자동 조회 function setDateRange(range) { currentRange = range; const today = new Date(); let startDate, endDate; switch (range) { case 'today': startDate = new Date(today); endDate = new Date(today); break; case 'week': startDate = new Date(today); startDate.setDate(today.getDate() - today.getDay()); // 이번 주 일요일 endDate = new Date(today); break; case 'month': startDate = new Date(today.getFullYear(), today.getMonth(), 1); // 이번 달 1일 endDate = new Date(today); break; case 'all': startDate = new Date(2020, 0, 1); // 충분히 과거 날짜 endDate = new Date(today); break; default: return; } // 날짜 입력 필드 업데이트 document.getElementById('startDateInput').value = formatDateForInput(startDate); document.getElementById('endDateInput').value = formatDateForInput(endDate); // 필터 적용 filterIssues(); } // 부적합 사항 로드 (자신이 올린 내용만) async function loadIssues() { const container = document.getElementById('issueResults'); container.innerHTML = `

데이터를 불러오는 중...

`; try { // 모든 이슈 가져오기 const allIssues = await IssuesAPI.getAll(); // 자신이 올린 이슈만 필터링 issues = allIssues .filter(issue => issue.reporter_id === currentUser.id) .sort((a, b) => new Date(b.report_date) - new Date(a.report_date)); // 결과 표시 filterIssues(); } catch (error) { console.error('부적합 사항 로드 실패:', error); container.innerHTML = `

데이터를 불러오는데 실패했습니다.

`; } } // 결과 표시 (시간순 나열) function displayResults() { const container = document.getElementById('issueResults'); // 필터링된 결과 사용 (filterIssues에서 설정됨) const filteredIssues = window.filteredIssues || issues; if (filteredIssues.length === 0) { const emptyMessage = currentUser.role === 'admin' ? '조건에 맞는 부적합 사항이 없습니다.' : '아직 등록한 부적합 사항이 없습니다.
부적합 등록 페이지에서 새로운 부적합을 등록해보세요.'; container.innerHTML = `

${emptyMessage}

${currentUser.role !== 'admin' ? `
부적합 등록하기
` : ''}
`; return; } // 워크플로우 상태별로 분류 및 정렬 const groupedIssues = { pending_review: filteredIssues.filter(issue => issue.review_status === 'pending_review' || (!issue.review_status && !isReviewCompleted(issue)) ), in_progress: filteredIssues.filter(issue => issue.review_status === 'in_progress'), completed: filteredIssues.filter(issue => issue.review_status === 'completed' || (!issue.review_status && isReviewCompleted(issue)) ), disposed: filteredIssues.filter(issue => issue.review_status === 'disposed') }; container.innerHTML = ''; // 각 상태별로 표시 const statusConfig = [ { key: 'pending_review', title: '수신함 (검토 대기)', icon: 'fas fa-inbox', color: 'text-orange-700' }, { key: 'in_progress', title: '관리함 (진행 중)', icon: 'fas fa-cog', color: 'text-blue-700' }, { key: 'completed', title: '관리함 (완료됨)', icon: 'fas fa-check-circle', color: 'text-green-700' }, { key: 'disposed', title: '폐기함 (폐기됨)', icon: 'fas fa-trash', color: 'text-gray-700' } ]; statusConfig.forEach((config, index) => { const issues = groupedIssues[config.key]; if (issues.length > 0) { const header = document.createElement('div'); header.className = index > 0 ? 'mb-4 mt-8' : 'mb-4'; header.innerHTML = `

${config.title} (${issues.length}건)

`; container.appendChild(header); issues.forEach(issue => { container.appendChild(createIssueCard(issue, config.key === 'completed')); }); } }); } // 워크플로우 상태 표시 함수 function getWorkflowStatusBadge(issue) { const status = issue.review_status || (isReviewCompleted(issue) ? 'completed' : 'pending_review'); const statusConfig = { 'pending_review': { text: '검토 대기', class: 'bg-orange-100 text-orange-700', icon: 'fas fa-inbox' }, 'in_progress': { text: '진행 중', class: 'bg-blue-100 text-blue-700', icon: 'fas fa-cog' }, 'completed': { text: '완료됨', class: 'bg-green-100 text-green-700', icon: 'fas fa-check-circle' }, 'disposed': { text: '폐기됨', class: 'bg-gray-100 text-gray-700', icon: 'fas fa-trash' } }; const config = statusConfig[status] || statusConfig['pending_review']; return ` ${config.text} `; } // 부적합 사항 카드 생성 함수 (조회용) function createIssueCard(issue, isCompleted) { const categoryNames = { material_missing: '자재누락', design_error: '설계미스', incoming_defect: '입고자재 불량', inspection_miss: '검사미스' }; const categoryColors = { material_missing: 'bg-yellow-100 text-yellow-700 border-yellow-300', design_error: 'bg-blue-100 text-blue-700 border-blue-300', incoming_defect: 'bg-red-100 text-red-700 border-red-300', inspection_miss: 'bg-purple-100 text-purple-700 border-purple-300' }; const div = document.createElement('div'); // 검토 완료 상태에 따른 스타일링 const baseClasses = 'rounded-lg transition-colors border-l-4 mb-4'; const statusClasses = isCompleted ? 'bg-gray-100 opacity-75' : 'bg-gray-50 hover:bg-gray-100'; const borderColor = categoryColors[issue.category]?.split(' ')[2] || 'border-gray-300'; div.className = `${baseClasses} ${statusClasses} ${borderColor}`; const dateStr = DateUtils.formatKST(issue.report_date, true); const relativeTime = DateUtils.getRelativeTime(issue.report_date); const projectInfo = getProjectInfo(issue.project_id || issue.projectId); // 수정/삭제 권한 확인 (본인이 등록한 부적합만) const canEdit = issue.reporter_id === currentUser.id; const canDelete = issue.reporter_id === currentUser.id || currentUser.role === 'admin'; div.innerHTML = `
${getWorkflowStatusBadge(issue)}
${projectInfo}
${(() => { const photos = [ issue.photo_path, issue.photo_path2, issue.photo_path3, issue.photo_path4, issue.photo_path5 ].filter(p => p); if (photos.length === 0) { return `
`; } return photos.map(path => ` `).join(''); })()}
${categoryNames[issue.category] || issue.category} ${issue.work_hours ? ` ${issue.work_hours}시간 ` : '시간 미입력' }

${issue.description}

${issue.location_info ? `
${issue.location_info}
` : ''}
${issue.reporter?.full_name || issue.reporter?.username || '알 수 없음'} ${dateStr} ${relativeTime}
${(canEdit || canDelete) ? `
${canEdit ? ` ` : ''} ${canDelete ? ` ` : ''}
` : ''}
`; return div; } // 관리 버튼 클릭 처리 function handleAdminClick() { if (currentUser.role === 'admin') { // 관리자: 사용자 관리 페이지로 이동 window.location.href = 'admin.html'; } } // 비밀번호 변경 모달 표시 function showPasswordChangeModal() { const modal = document.createElement('div'); modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50'; modal.onclick = (e) => { if (e.target === modal) modal.remove(); }; modal.innerHTML = `

비밀번호 변경

`; document.body.appendChild(modal); // 폼 제출 이벤트 처리 document.getElementById('passwordChangeForm').addEventListener('submit', handlePasswordChange); } // 비밀번호 변경 처리 async function handlePasswordChange(e) { e.preventDefault(); const currentPassword = document.getElementById('currentPassword').value; const newPassword = document.getElementById('newPassword').value; const confirmPassword = document.getElementById('confirmPassword').value; // 새 비밀번호 확인 if (newPassword !== confirmPassword) { alert('새 비밀번호가 일치하지 않습니다.'); return; } // 현재 비밀번호 확인 (localStorage 기반) let users = JSON.parse(localStorage.getItem('work-report-users') || '[]'); // 기본 사용자가 없으면 생성 if (users.length === 0) { users = [ { username: 'hyungi', full_name: '관리자', password: 'djg3-jj34-X3Q3', role: 'admin' } ]; localStorage.setItem('work-report-users', JSON.stringify(users)); } let user = users.find(u => u.username === currentUser.username); // 사용자가 없으면 기본값으로 생성 if (!user) { const username = currentUser.username; user = { username: username, full_name: username === 'hyungi' ? '관리자' : username, password: 'djg3-jj34-X3Q3', role: username === 'hyungi' ? 'admin' : 'user' }; users.push(user); localStorage.setItem('work-report-users', JSON.stringify(users)); } if (user.password !== currentPassword) { alert('현재 비밀번호가 올바르지 않습니다.'); return; } try { // 비밀번호 변경 user.password = newPassword; localStorage.setItem('work-report-users', JSON.stringify(users)); // 현재 사용자 정보도 업데이트 currentUser.password = newPassword; localStorage.setItem('sso_user', JSON.stringify(currentUser)); alert('비밀번호가 성공적으로 변경되었습니다.'); document.querySelector('.fixed').remove(); // 모달 닫기 } catch (error) { alert('비밀번호 변경에 실패했습니다: ' + error.message); } } // 로그아웃 함수 function logout() { TokenManager.removeToken(); TokenManager.removeUser(); window.location.href = 'index.html'; } // 수정 모달 표시 function showEditModal(issue) { const categoryNames = { material_missing: '자재누락', design_error: '설계미스', incoming_defect: '입고자재 불량', inspection_miss: '검사미스' }; const modal = document.createElement('div'); modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4'; modal.onclick = (e) => { if (e.target === modal) modal.remove(); }; modal.innerHTML = `

부적합 수정

`; document.body.appendChild(modal); // 폼 제출 이벤트 처리 document.getElementById('editIssueForm').addEventListener('submit', async (e) => { e.preventDefault(); const updateData = { category: document.getElementById('editCategory').value, description: document.getElementById('editDescription').value, project_id: parseInt(document.getElementById('editProject').value) }; try { await IssuesAPI.update(issue.id, updateData); alert('수정되었습니다.'); modal.remove(); // 목록 새로고침 await loadIssues(); } catch (error) { console.error('수정 실패:', error); alert('수정에 실패했습니다: ' + error.message); } }); } // 삭제 확인 다이얼로그 function confirmDelete(issueId) { const modal = document.createElement('div'); modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50'; modal.onclick = (e) => { if (e.target === modal) modal.remove(); }; modal.innerHTML = `

부적합 삭제

이 부적합 사항을 삭제하시겠습니까?
삭제된 데이터는 로그로 보관되지만 복구할 수 없습니다.

`; document.body.appendChild(modal); } // 삭제 처리 async function handleDelete(issueId) { try { await IssuesAPI.delete(issueId); alert('삭제되었습니다.'); // 모달 닫기 const modal = document.querySelector('.fixed'); if (modal) modal.remove(); // 목록 새로고침 await loadIssues(); } catch (error) { console.error('삭제 실패:', error); alert('삭제에 실패했습니다: ' + error.message); } } // API 스크립트 동적 로딩 const script = document.createElement('script'); script.src = '/static/js/api.js?v=20260213'; script.onload = function() { console.log('API 스크립트 로드 완료 (issue-view.html)'); // API 로드 후 초기화 시작 initializeIssueView(); }; script.onerror = function() { console.error('API 스크립트 로드 실패'); }; document.head.appendChild(script);