/** * issues-management.js — 관리함 페이지 스크립트 */ let currentUser = null; let issues = []; let projects = []; let filteredIssues = []; let currentIssueId = null; let currentTab = 'in_progress'; // 기본값: 진행 중 // 완료 반려 패턴 제거 (해결방안 표시용) function cleanManagementComment(text) { if (!text) return ''; // 기존 데이터에서 완료 반려 패턴 제거 return text.replace(/\[완료 반려[^\]]*\][^\n]*\n*/g, '').trim(); } // API 로드 후 초기화 함수 async function initializeManagement() { const token = TokenManager.getToken(); if (!token) { window.location.href = '/index.html'; return; } try { const user = await AuthAPI.getCurrentUser(); currentUser = user; localStorage.setItem('currentUser', JSON.stringify(user)); // 공통 헤더 초기화 await window.commonHeader.init(user, 'issues_management'); // 페이지 접근 권한 체크 setTimeout(() => { if (!canAccessPage('issues_management')) { alert('관리함 페이지에 접근할 권한이 없습니다.'); window.location.href = '/index.html'; return; } }, 500); // 데이터 로드 await loadProjects(); await loadIssues(); } catch (error) { console.error('인증 실패:', error); TokenManager.removeToken(); TokenManager.removeUser(); window.location.href = '/index.html'; } } // 프로젝트 로드 async function loadProjects() { try { const apiUrl = window.API_BASE_URL || '/api'; const response = await fetch(`${apiUrl}/projects/`, { headers: { 'Authorization': `Bearer ${TokenManager.getToken()}`, 'Content-Type': 'application/json' } }); if (response.ok) { projects = await response.json(); updateProjectFilter(); } } catch (error) { console.error('프로젝트 로드 실패:', error); } } // 부적합 목록 로드 (관리자는 모든 부적합 조회) async function loadIssues() { try { let endpoint = '/api/issues/admin/all'; const response = await fetch(endpoint, { headers: { 'Authorization': `Bearer ${TokenManager.getToken()}`, 'Content-Type': 'application/json' } }); if (response.ok) { const allIssues = await response.json(); // 관리함에서는 진행 중(in_progress)과 완료됨(completed) 상태만 표시 let filteredIssues = allIssues.filter(issue => issue.review_status === 'in_progress' || issue.review_status === 'completed' ); // 수신함에서 넘어온 순서대로 No. 재할당 (reviewed_at 기준) filteredIssues.sort((a, b) => new Date(a.reviewed_at) - new Date(b.reviewed_at)); // 프로젝트별로 그룹화하여 No. 재할당 const projectGroups = {}; filteredIssues.forEach(issue => { if (!projectGroups[issue.project_id]) { projectGroups[issue.project_id] = []; } projectGroups[issue.project_id].push(issue); }); // 각 프로젝트별로 순번 재할당 Object.keys(projectGroups).forEach(projectId => { projectGroups[projectId].forEach((issue, index) => { issue.project_sequence_no = index + 1; }); }); issues = filteredIssues; filterIssues(); } else { throw new Error('부적합 목록을 불러올 수 없습니다.'); } } catch (error) { console.error('부적합 로드 실패:', error); alert('부적합 목록을 불러오는데 실패했습니다.'); } } // 탭 전환 함수 function switchTab(tab) { currentTab = tab; // 탭 버튼 스타일 업데이트 const inProgressTab = document.getElementById('inProgressTab'); const completedTab = document.getElementById('completedTab'); const additionalInfoBtn = document.getElementById('additionalInfoBtn'); if (tab === 'in_progress') { inProgressTab.className = 'flex-1 px-4 py-2 text-sm font-medium rounded-md transition-colors duration-200 bg-blue-500 text-white'; completedTab.className = 'flex-1 px-4 py-2 text-sm font-medium rounded-md transition-colors duration-200 text-gray-600 hover:text-gray-900'; // 진행 중 탭에서만 추가 정보 버튼 표시 additionalInfoBtn.style.display = 'block'; } else { inProgressTab.className = 'flex-1 px-4 py-2 text-sm font-medium rounded-md transition-colors duration-200 text-gray-600 hover:text-gray-900'; completedTab.className = 'flex-1 px-4 py-2 text-sm font-medium rounded-md transition-colors duration-200 bg-green-500 text-white'; // 완료됨 탭에서는 추가 정보 버튼 숨김 additionalInfoBtn.style.display = 'none'; } filterIssues(); // 이미 updateStatistics()가 포함됨 } // 통계 업데이트 함수 function updateStatistics() { const projectFilter = document.getElementById('projectFilter').value; // 선택된 프로젝트에 따른 이슈 필터링 const projectIssues = projectFilter ? issues.filter(issue => issue.project_id == projectFilter) : issues; // 상태별 카운트 const totalCount = projectIssues.length; const inProgressCount = projectIssues.filter(issue => issue.review_status === 'in_progress' && !issue.completion_requested_at ).length; const pendingCompletionCount = projectIssues.filter(issue => issue.review_status === 'in_progress' && issue.completion_requested_at ).length; const completedCount = projectIssues.filter(issue => issue.review_status === 'completed').length; // 통계 업데이트 document.getElementById('totalCount').textContent = totalCount; document.getElementById('inProgressCount').textContent = inProgressCount; document.getElementById('pendingCompletionCount').textContent = pendingCompletionCount; document.getElementById('completedCount').textContent = completedCount; } // 필터링 및 표시 함수들 function filterIssues() { const projectFilter = document.getElementById('projectFilter').value; filteredIssues = issues.filter(issue => { // 현재 탭에 따른 상태 필터링 if (issue.review_status !== currentTab) return false; // 프로젝트 필터링 if (projectFilter && issue.project_id != projectFilter) return false; return true; }); sortIssues(); displayIssues(); updateStatistics(); // 통계 업데이트 추가 } function sortIssues() { const sortOrder = document.getElementById('sortOrder').value; filteredIssues.sort((a, b) => { switch (sortOrder) { case 'newest': return new Date(b.report_date) - new Date(a.report_date); case 'oldest': return new Date(a.report_date) - new Date(b.report_date); default: return new Date(b.report_date) - new Date(a.report_date); } }); } function displayIssues() { const container = document.getElementById('issuesList'); const emptyState = document.getElementById('emptyState'); if (filteredIssues.length === 0) { container.innerHTML = ''; emptyState.classList.remove('hidden'); return; } emptyState.classList.add('hidden'); // 날짜별로 그룹화 (상태에 따라 다른 날짜 기준 사용) const groupedByDate = {}; filteredIssues.forEach(issue => { let date; if (currentTab === 'in_progress') { // 진행 중: 업로드한 날짜 기준 date = new Date(issue.report_date).toLocaleDateString('ko-KR'); } else { // 완료됨: 완료된 날짜 기준 (없으면 업로드 날짜) const completionDate = issue.actual_completion_date || issue.report_date; date = new Date(completionDate).toLocaleDateString('ko-KR'); } if (!groupedByDate[date]) { groupedByDate[date] = []; } groupedByDate[date].push(issue); }); // 날짜별 그룹을 HTML로 생성 const dateGroups = Object.keys(groupedByDate).map(date => { const issues = groupedByDate[date]; const groupId = `group-${date.replace(/\./g, '-')}`; return `

${date}

(${issues.length}건) ${currentTab === 'in_progress' ? '업로드일' : '완료일'}
${issues.map(issue => createIssueRow(issue)).join('')}
`; }).join(''); container.innerHTML = dateGroups; } // 이슈 행 생성 함수 function createIssueRow(issue) { const project = projects.find(p => p.id === issue.project_id); const isInProgress = issue.review_status === 'in_progress'; const isCompleted = issue.review_status === 'completed'; if (isInProgress) { // 진행 중 - 편집 가능한 형태 return createInProgressRow(issue, project); } else { // 완료됨 - 입력 여부 표시 + 클릭으로 상세보기 return createCompletedRow(issue, project); } } // 진행 중 카드 생성 function createInProgressRow(issue, project) { // 상태 판별 const isPendingCompletion = issue.completion_requested_at; const isOverdue = issue.expected_completion_date && new Date(issue.expected_completion_date) < new Date(); const isUrgent = issue.expected_completion_date && (new Date(issue.expected_completion_date) - new Date()) / (1000 * 60 * 60 * 24) <= 3 && !isOverdue; // 상태 설정 let statusConfig = { text: '진행 중', bgColor: 'bg-gradient-to-r from-blue-500 to-blue-600', icon: 'fas fa-cog fa-spin', dotColor: 'bg-white' }; if (isPendingCompletion) { statusConfig = { text: '완료 대기', bgColor: 'bg-gradient-to-r from-purple-500 to-purple-600', icon: 'fas fa-hourglass-half', dotColor: 'bg-white' }; } else if (isOverdue) { statusConfig = { text: '지연됨', bgColor: 'bg-gradient-to-r from-red-500 to-red-600', icon: 'fas fa-clock', dotColor: 'bg-white' }; } else if (isUrgent) { statusConfig = { text: '긴급', bgColor: 'bg-gradient-to-r from-orange-500 to-orange-600', icon: 'fas fa-exclamation-triangle', dotColor: 'bg-white' }; } return `
No.${issue.project_sequence_no || '-'}
${project ? project.project_name : '프로젝트 미지정'}
${statusConfig.text}

${getIssueTitle(issue)}

${isPendingCompletion ? ` ` : ` `}
${!isPendingCompletion ? ` ` : ` 완료 대기 중 `}
${getIssueDetail(issue)}
${getCategoryText(issue.category || issue.final_category)}
${issue.location_info ? `
${issue.location_info}
` : ''}
${(() => { 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, idx) => ` 업로드 사진 ${idx + 1} `).join('')}
`; })()}
${isPendingCompletion ? `

완료 신청 정보

${(() => { const photos = [ issue.completion_photo_path, issue.completion_photo_path2, issue.completion_photo_path3, issue.completion_photo_path4, issue.completion_photo_path5 ].filter(p => p); if (photos.length === 0) { return '

완료 사진 없음

'; } return `
${photos.map(path => ` 완료 사진 `).join('')}
`; })()}

${issue.completion_comment || '코멘트 없음'}

${new Date(issue.completion_requested_at).toLocaleString('ko-KR')}

` : ''}
${statusConfig.text} 신고일: ${new Date(issue.report_date).toLocaleDateString('ko-KR')}
`; } // 완료됨 행 생성 (입력 여부 표시) function createCompletedRow(issue, project) { // 완료 날짜 포맷팅 const completedDate = issue.completed_at ? new Date(issue.completed_at).toLocaleDateString('ko-KR') : '미완료'; return `
No.${issue.project_sequence_no || '-'}
${project ? project.project_name : '프로젝트 미지정'}
완료됨
완료일: ${completedDate}

${getIssueTitle(issue)}

기본 정보

${getIssueDetail(issue)}

원인분류: ${getCategoryText(issue.final_category || issue.category) || '-'}
확인자: ${getReporterNames(issue) || '-'}

관리 정보

해결방안 (확정): ${cleanManagementComment(issue.management_comment) || '-'}
담당부서: ${getDepartmentText(issue.responsible_department) || '-'}
담당자: ${issue.responsible_person || '-'}
원인부서: ${getDepartmentText(issue.cause_department) || '-'}
관리 코멘트: ${cleanManagementComment(issue.management_comment) || '-'}

완료 정보

${(() => { const photos = [ issue.completion_photo_path, issue.completion_photo_path2, issue.completion_photo_path3, issue.completion_photo_path4, issue.completion_photo_path5 ].filter(p => p); if (photos.length === 0) { return '

완료 사진 없음

'; } return `
${photos.map(path => ` 완료 사진 `).join('')}
`; })()}

${issue.completion_comment || '코멘트 없음'}

${issue.completion_requested_at ? `

${new Date(issue.completion_requested_at).toLocaleString('ko-KR')}

` : ''}

업로드 사진

${(() => { 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, idx) => ` 업로드 사진 ${idx + 1} `).join('')}
`; })()}
`; } // 입력 여부 아이콘 생성 function getStatusIcon(value) { if (value && value.toString().trim() !== '') { return ''; } else { return ''; } } // 사진 상태 아이콘 생성 function getPhotoStatusIcon(photo1, photo2) { const count = (photo1 ? 1 : 0) + (photo2 ? 1 : 0); if (count > 0) { return `${count}장`; } else { return ''; } } // 테이블 헤더 생성 함수 (더 이상 사용하지 않음 - 모든 탭이 카드 형식으로 변경됨) function createTableHeader() { // 레거시 함수 - 더 이상 사용되지 않음 return ''; } // 편집 가능한 필드 생성 함수 function createEditableField(fieldName, value, type, issueId, editable, options = null) { if (!editable) { return value || '-'; } const fieldId = `${fieldName}_${issueId}`; switch (type) { case 'textarea': return ``; case 'select': if (options) { const optionsHtml = options.map(opt => `` ).join(''); return ``; } break; case 'date': return ``; case 'text': default: return ``; } return value || '-'; } // 부서 옵션 생성 함수 function getDepartmentOptions() { return [ { value: '', text: '선택하세요' }, { value: 'production', text: '생산' }, { value: 'quality', text: '품질' }, { value: 'purchasing', text: '구매' }, { value: 'design', text: '설계' }, { value: 'sales', text: '영업' } ]; } // 날짜 그룹 토글 함수 function toggleDateGroup(groupId) { const content = document.getElementById(groupId); const icon = document.getElementById(`icon-${groupId}`); if (content.classList.contains('collapsed')) { content.classList.remove('collapsed'); icon.style.transform = 'rotate(0deg)'; } else { content.classList.add('collapsed'); icon.style.transform = 'rotate(-90deg)'; } } // 상태 변경 모달 function openStatusModal(issueId) { currentIssueId = issueId; document.getElementById('statusModal').classList.remove('hidden'); } function closeStatusModal() { currentIssueId = null; document.getElementById('statusModal').classList.add('hidden'); document.getElementById('newStatus').value = 'processing'; document.getElementById('statusNote').value = ''; } async function updateStatus() { if (!currentIssueId) return; const newStatus = document.getElementById('newStatus').value; const note = document.getElementById('statusNote').value; try { const response = await fetch(`/api/issues/${currentIssueId}`, { method: 'PUT', headers: { 'Authorization': `Bearer ${TokenManager.getToken()}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ status: newStatus, note: note }) }); if (response.ok) { await loadIssues(); closeStatusModal(); alert('상태가 성공적으로 변경되었습니다.'); } else { throw new Error('상태 변경에 실패했습니다.'); } } catch (error) { console.error('상태 변경 실패:', error); alert('상태 변경에 실패했습니다.'); } } // 완료 처리 함수 async function completeIssue(issueId) { if (!confirm('이 부적합을 완료 처리하시겠습니까?')) { return; } try { const response = await fetch(`/api/inbox/${issueId}/status`, { method: 'POST', headers: { 'Authorization': `Bearer ${TokenManager.getToken()}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ review_status: 'completed' }) }); if (response.ok) { alert('완료 처리되었습니다.'); await loadIssues(); // 목록 새로고침 } else { const error = await response.json(); throw new Error(error.detail || '완료 처리에 실패했습니다.'); } } catch (error) { console.error('완료 처리 실패:', error); alert(error.message || '완료 처리 중 오류가 발생했습니다.'); } } // 이슈 변경사항 저장 함수 async function saveIssueChanges(issueId) { try { // 편집된 필드들의 값 수집 const updates = {}; const fields = ['management_comment', 'responsible_department', 'responsible_person', 'expected_completion_date', 'cause_department']; fields.forEach(field => { const element = document.getElementById(`${field}_${issueId}`); if (element) { let value = element.value.trim(); if (value === '' || value === '선택하세요') { value = null; } else if (field === 'expected_completion_date' && value) { // 날짜 필드는 ISO datetime 형식으로 변환 value = value + 'T00:00:00'; } updates[field] = value; } }); console.log('Sending updates:', updates); // API 호출 const response = await fetch(`/api/issues/${issueId}/management`, { method: 'PUT', headers: { 'Authorization': `Bearer ${TokenManager.getToken()}`, 'Content-Type': 'application/json' }, body: JSON.stringify(updates) }); console.log('Response status:', response.status); if (response.ok) { alert('변경사항이 저장되었습니다.'); await loadIssues(); // 목록 새로고침 } else { const errorText = await response.text(); console.error('API Error Response:', errorText); let errorMessage = '저장에 실패했습니다.'; try { const errorJson = JSON.parse(errorText); errorMessage = errorJson.detail || JSON.stringify(errorJson); } catch (e) { errorMessage = errorText || '저장에 실패했습니다.'; } throw new Error(errorMessage); } } catch (error) { console.error('저장 실패:', error); console.error('Error details:', error); alert(error.message || '저장 중 오류가 발생했습니다.'); } } // 완료된 이슈 상세보기 모달 함수들 let currentModalIssueId = null; async function openIssueDetailModal(issueId) { currentModalIssueId = issueId; const issue = issues.find(i => i.id === issueId); if (!issue) return; const project = projects.find(p => p.id === issue.project_id); // 모달 제목 설정 document.getElementById('modalTitle').innerHTML = ` 부적합 No.${issue.project_sequence_no || '-'}
상세 정보
`; // 모달 내용 생성 const modalContent = document.getElementById('modalContent'); modalContent.innerHTML = createModalContent(issue, project); // 모달 표시 document.getElementById('issueDetailModal').classList.remove('hidden'); } function closeIssueDetailModal() { document.getElementById('issueDetailModal').classList.add('hidden'); currentModalIssueId = null; } function createModalContent(issue, project) { return `

기본 정보 (수신함 확정)

${project ? project.project_name : '-'}
${issue.final_description || issue.description}
${getCategoryText(issue.final_category || issue.category)}
${getReporterNames(issue)}
${(() => { 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, idx) => ` 업로드 사진 ${idx + 1} `).join('')}
`; })()}

관리 정보 (편집 가능)

${(() => { const photos = [ issue.completion_photo_path, issue.completion_photo_path2, issue.completion_photo_path3, issue.completion_photo_path4, issue.completion_photo_path5 ].filter(p => p); if (photos.length > 0) { return `

현재 완료 사진 (${photos.length}장)

${photos.map(path => ` 완료 사진 `).join('')}
`; } return ''; })()}

※ 최대 5장까지 업로드 가능합니다. 새로운 사진을 업로드하면 기존 사진을 모두 교체합니다.

`; } async function saveModalChanges() { if (!currentModalIssueId) return; try { // 편집된 필드들의 값 수집 const updates = {}; const fields = ['management_comment', 'responsible_department', 'responsible_person', 'expected_completion_date', 'cause_department']; fields.forEach(field => { const element = document.getElementById(`modal_${field}`); if (element) { let value = element.value.trim(); if (value === '' || value === '선택하세요') { value = null; } updates[field] = value; } }); // 완료 사진 처리 (최대 5장) const photoInput = document.getElementById('modal_completion_photo'); const photoFiles = photoInput.files; if (photoFiles && photoFiles.length > 0) { const maxPhotos = Math.min(photoFiles.length, 5); for (let i = 0; i < maxPhotos; i++) { const base64 = await fileToBase64(photoFiles[i]); const fieldName = i === 0 ? 'completion_photo' : `completion_photo${i + 1}`; updates[fieldName] = base64; } console.log(`📸 ${maxPhotos}장의 완료 사진 처리 완료`); } console.log('Modal sending updates:', updates); // API 호출 const response = await fetch(`/api/issues/${currentModalIssueId}/management`, { method: 'PUT', headers: { 'Authorization': `Bearer ${TokenManager.getToken()}`, 'Content-Type': 'application/json' }, body: JSON.stringify(updates) }); console.log('Modal response status:', response.status); if (response.ok) { alert('변경사항이 저장되었습니다.'); closeIssueDetailModal(); await loadIssues(); // 목록 새로고침 } else { const errorText = await response.text(); console.error('Modal API Error Response:', errorText); let errorMessage = '저장에 실패했습니다.'; try { const errorJson = JSON.parse(errorText); errorMessage = errorJson.detail || JSON.stringify(errorJson); } catch (e) { errorMessage = errorText || '저장에 실패했습니다.'; } throw new Error(errorMessage); } } catch (error) { console.error('모달 저장 실패:', error); console.error('Error details:', error); alert(error.message || '저장 중 오류가 발생했습니다.'); } } // 파일을 Base64로 변환하는 함수 function fileToBase64(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(file); reader.onload = () => resolve(reader.result); reader.onerror = error => reject(error); }); } // 기타 함수들 function viewIssueDetail(issueId) { window.location.href = `/issue-view.html#detail-${issueId}`; } // 유틸리티 함수들 function updateProjectFilter() { const projectFilter = document.getElementById('projectFilter'); projectFilter.innerHTML = ''; projects.forEach(project => { const option = document.createElement('option'); option.value = project.id; option.textContent = project.project_name; projectFilter.appendChild(option); }); } function getPriorityBadge(priority) { const priorityMap = { 'high': { text: '높음', class: 'bg-red-100 text-red-800' }, 'medium': { text: '보통', class: 'bg-yellow-100 text-yellow-800' }, 'low': { text: '낮음', class: 'bg-green-100 text-green-800' } }; const p = priorityMap[priority] || { text: '보통', class: 'bg-gray-100 text-gray-800' }; return `${p.text}`; } // API 스크립트 동적 로딩 const script = document.createElement('script'); script.src = '/static/js/api.js?v=20260213'; script.onload = function() { console.log('✅ API 스크립트 로드 완료 (issues-management.js)'); initializeManagement(); }; script.onerror = function() { console.error('❌ API 스크립트 로드 실패'); }; document.head.appendChild(script); // 추가 정보 모달 관련 함수들 let selectedIssueId = null; function openAdditionalInfoModal() { // 진행 중 탭에서 선택된 이슈가 있는지 확인 const inProgressIssues = allIssues.filter(issue => issue.review_status === 'in_progress'); if (inProgressIssues.length === 0) { alert('진행 중인 부적합이 없습니다.'); return; } // 첫 번째 진행 중 이슈를 기본 선택 (추후 개선 가능) selectedIssueId = inProgressIssues[0].id; // 기존 데이터 로드 loadAdditionalInfo(selectedIssueId); document.getElementById('additionalInfoModal').classList.remove('hidden'); } function closeAdditionalInfoModal() { document.getElementById('additionalInfoModal').classList.add('hidden'); selectedIssueId = null; // 폼 초기화 document.getElementById('additionalInfoForm').reset(); } async function loadAdditionalInfo(issueId) { try { const response = await fetch(`/api/management/${issueId}/additional-info`, { headers: { 'Authorization': `Bearer ${TokenManager.getToken()}`, 'Content-Type': 'application/json' } }); if (response.ok) { const data = await response.json(); // 폼에 기존 데이터 채우기 document.getElementById('causeDepartment').value = data.cause_department || ''; document.getElementById('responsiblePersonDetail').value = data.responsible_person_detail || ''; document.getElementById('causeDetail').value = data.cause_detail || ''; } } catch (error) { console.error('추가 정보 로드 실패:', error); } } // 추가 정보 폼 제출 처리 (요소가 존재할 때만) const additionalInfoForm = document.getElementById('additionalInfoForm'); if (additionalInfoForm) { additionalInfoForm.addEventListener('submit', async function(e) { e.preventDefault(); if (!selectedIssueId) { alert('선택된 부적합이 없습니다.'); return; } const formData = { cause_department: document.getElementById('causeDepartment').value || null, responsible_person_detail: document.getElementById('responsiblePersonDetail').value || null, cause_detail: document.getElementById('causeDetail').value || null }; try { const response = await fetch(`/api/management/${selectedIssueId}/additional-info`, { method: 'PUT', headers: { 'Authorization': `Bearer ${TokenManager.getToken()}`, 'Content-Type': 'application/json' }, body: JSON.stringify(formData) }); if (response.ok) { const result = await response.json(); alert('추가 정보가 성공적으로 저장되었습니다.'); closeAdditionalInfoModal(); // 목록 새로고침 loadIssues(); } else { const error = await response.json(); alert(`저장 실패: ${error.detail || '알 수 없는 오류'}`); } } catch (error) { console.error('추가 정보 저장 실패:', error); alert('저장 중 오류가 발생했습니다.'); } }); } // 상세 내용 편집 관련 함수들 function toggleDetailEdit(issueId) { const displayDiv = document.getElementById(`detail-display-${issueId}`); const editDiv = document.getElementById(`detail-edit-${issueId}`); if (displayDiv && editDiv) { displayDiv.classList.add('hidden'); editDiv.classList.remove('hidden'); // 텍스트 영역에 포커스 const textarea = document.getElementById(`detail-textarea-${issueId}`); if (textarea) { textarea.focus(); } } } function cancelDetailEdit(issueId) { const displayDiv = document.getElementById(`detail-display-${issueId}`); const editDiv = document.getElementById(`detail-edit-${issueId}`); if (displayDiv && editDiv) { displayDiv.classList.remove('hidden'); editDiv.classList.add('hidden'); // 원래 값으로 복원 const issue = issues.find(i => i.id === issueId); if (issue) { const textarea = document.getElementById(`detail-textarea-${issueId}`); if (textarea) { textarea.value = getIssueDetail(issue); } } } } async function saveDetailEdit(issueId) { const textarea = document.getElementById(`detail-textarea-${issueId}`); if (!textarea) return; const newDetailContent = textarea.value.trim(); try { // 현재 이슈 정보 가져오기 const issue = issues.find(i => i.id === issueId); if (!issue) { alert('이슈 정보를 찾을 수 없습니다.'); return; } // 부적합명과 새로운 상세 내용을 결합 const issueTitle = getIssueTitle(issue); const combinedDescription = issueTitle + (newDetailContent ? '\n' + newDetailContent : ''); const response = await fetch(`/api/management/${issueId}`, { method: 'PUT', headers: { 'Authorization': `Bearer ${TokenManager.getToken()}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ final_description: combinedDescription }) }); if (response.ok) { // 성공 시 이슈 데이터 업데이트 issue.final_description = combinedDescription; // 표시 영역 업데이트 const displayDiv = document.getElementById(`detail-display-${issueId}`); if (displayDiv) { const contentDiv = displayDiv.querySelector('div'); if (contentDiv) { contentDiv.textContent = newDetailContent || '상세 내용 없음'; } } // 편집 모드 종료 cancelDetailEdit(issueId); alert('상세 내용이 성공적으로 저장되었습니다.'); } else { const error = await response.json(); alert(`저장 실패: ${error.detail || '알 수 없는 오류'}`); } } catch (error) { console.error('상세 내용 저장 실패:', error); alert('저장 중 오류가 발생했습니다.'); } } // 완료 확인 모달 열기 (진행 중 -> 완료 처리용) function openCompletionConfirmModal(issueId) { openIssueEditModal(issueId, true); // 완료 처리 모드로 열기 } // 이슈 수정 모달 열기 (모든 진행 중 상태에서 사용) function openIssueEditModal(issueId, isCompletionMode = false) { const issue = issues.find(i => i.id === issueId); if (!issue) return; const project = projects.find(p => p.id === issue.project_id); const isPendingCompletion = issue.completion_requested_at; // 모달 내용 생성 const modalContent = `

이슈 수정 - No.${issue.project_sequence_no || '-'}

기본 정보

업로드 사진

${(() => { 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, idx) => ` 업로드 사진 ${idx + 1} `).join('')}
`; })()}

관리 정보

완료 신청 정보

${(() => { const photos = [ issue.completion_photo_path, issue.completion_photo_path2, issue.completion_photo_path3, issue.completion_photo_path4, issue.completion_photo_path5 ].filter(p => p); if (photos.length > 0) { return `

현재 완료 사진 (${photos.length}장)

${photos.map(path => ` 완료 사진 `).join('')}
`; } else { return `

사진 없음

`; } })()}

※ 최대 5장까지 업로드 가능합니다. 새로운 사진을 업로드하면 기존 사진을 모두 교체합니다.

${isPendingCompletion ? `

${new Date(issue.completion_requested_at).toLocaleString('ko-KR')}

` : ''}
`; // 모달을 body에 추가 document.body.insertAdjacentHTML('beforeend', modalContent); // 파일 선택 이벤트 리스너 추가 const fileInput = document.getElementById(`edit-completion-photo-${issue.id}`); const filenameSpan = document.getElementById(`photo-filename-${issue.id}`); if (fileInput && filenameSpan) { fileInput.addEventListener('change', function(e) { if (e.target.files && e.target.files.length > 0) { const fileCount = Math.min(e.target.files.length, 5); filenameSpan.textContent = `${fileCount}개 파일 선택됨`; filenameSpan.className = 'text-sm text-green-600 font-medium'; } else { filenameSpan.textContent = ''; filenameSpan.className = 'text-sm text-gray-600'; } }); } } // 이슈 수정 모달 닫기 function closeIssueEditModal() { const modal = document.getElementById('issueEditModal'); if (modal) { modal.remove(); } } // 모달에서 이슈 저장 async function saveIssueFromModal(issueId) { const title = document.getElementById(`edit-issue-title-${issueId}`).value.trim(); const detail = document.getElementById(`edit-issue-detail-${issueId}`).value.trim(); const category = document.getElementById(`edit-category-${issueId}`).value; const managementComment = document.getElementById(`edit-management-comment-${issueId}`).value.trim(); const department = document.getElementById(`edit-department-${issueId}`).value; const person = document.getElementById(`edit-person-${issueId}`).value.trim(); const date = document.getElementById(`edit-date-${issueId}`).value; const causeDepartment = document.getElementById(`edit-cause-department-${issueId}`).value; // 완료 신청 정보 (완료 대기 상태일 때만) const completionCommentElement = document.getElementById(`edit-completion-comment-${issueId}`); const completionPhotoElement = document.getElementById(`edit-completion-photo-${issueId}`); let completionComment = null; const completionPhotos = {}; // 완료 사진들을 저장할 객체 if (completionCommentElement) { completionComment = completionCommentElement.value.trim(); } // 완료 사진 처리 (최대 5장) if (completionPhotoElement && completionPhotoElement.files.length > 0) { try { const files = completionPhotoElement.files; const maxPhotos = Math.min(files.length, 5); console.log(`🔍 총 ${maxPhotos}개의 완료 사진 업로드 시작`); for (let i = 0; i < maxPhotos; i++) { const file = files[i]; console.log(`🔍 파일 ${i + 1} 정보:`, { name: file.name, size: file.size, type: file.type }); const base64 = await fileToBase64(file); const base64Data = base64.split(',')[1]; // Base64 데이터만 추출 const fieldName = i === 0 ? 'completion_photo' : `completion_photo${i + 1}`; completionPhotos[fieldName] = base64Data; console.log(`✅ 파일 ${i + 1} 변환 완료 (${fieldName})`); } } catch (error) { console.error('파일 변환 오류:', error); alert('완료 사진 업로드 중 오류가 발생했습니다.'); return; } } if (!title) { alert('부적합명을 입력해주세요.'); return; } const combinedDescription = title + (detail ? '\n' + detail : ''); const requestBody = { final_description: combinedDescription, final_category: category, management_comment: managementComment || null, responsible_department: department || null, responsible_person: person || null, expected_completion_date: date || null, cause_department: causeDepartment || null }; // 완료 신청 정보가 있으면 추가 if (completionComment !== null) { requestBody.completion_comment = completionComment || null; } // 완료 사진들 추가 (최대 5장) for (const [key, value] of Object.entries(completionPhotos)) { requestBody[key] = value; } try { const response = await fetch(`/api/management/${issueId}`, { method: 'PUT', headers: { 'Authorization': `Bearer ${TokenManager.getToken()}`, 'Content-Type': 'application/json' }, body: JSON.stringify(requestBody) }); if (response.ok) { // 저장 성공 후 데이터 새로고침하고 모달은 유지 await initializeManagement(); // 페이지 새로고침 // 저장된 이슈 정보 다시 로드하여 모달 업데이트 const updatedIssue = issues.find(i => i.id === issueId); if (updatedIssue) { // 완료 사진이 저장되었는지 확인 if (updatedIssue.completion_photo_path) { alert('✅ 완료 사진이 성공적으로 저장되었습니다!'); } else { alert('⚠️ 저장은 완료되었지만 완료 사진 저장에 실패했습니다. 다시 시도해주세요.'); } // 모달 내용 업데이트 (완료 사진 표시 갱신) const photoContainer = document.querySelector(`#issueEditModal img[alt*="완료 사진"]`)?.parentElement; if (photoContainer && updatedIssue.completion_photo_path) { // HEIC 파일인지 확인 const isHeic = updatedIssue.completion_photo_path.toLowerCase().endsWith('.heic'); if (isHeic) { // HEIC 파일은 다운로드 링크로 표시 photoContainer.innerHTML = `

완료 사진 (HEIC)

다운로드하여 확인
`; } else { // 일반 이미지는 미리보기 표시 photoContainer.innerHTML = `
현재 완료 사진

현재 완료 사진

클릭하면 크게 볼 수 있습니다

`; } } } else { alert('이슈가 성공적으로 저장되었습니다.'); closeIssueEditModal(); } } else { const error = await response.json(); alert(`저장 실패: ${error.detail || '알 수 없는 오류'}`); } } catch (error) { console.error('저장 오류:', error); alert('저장 중 오류가 발생했습니다.'); } } // 완료 대기 상태 관련 함수들 function editIssue(issueId) { // 수정 모드로 전환 (완료 대기 상태를 해제) if (confirm('완료 대기 상태를 해제하고 수정 모드로 전환하시겠습니까?')) { // 완료 신청 정보 초기화 API 호출 resetCompletionRequest(issueId); } } function rejectCompletion(issueId) { const reason = prompt('반려 사유를 입력하세요:'); if (reason && reason.trim()) { // 반려 처리 API 호출 rejectCompletionRequest(issueId, reason.trim()); } } function confirmCompletion(issueId) { // 완료 확인 모달 열기 (수정 가능) - 통합 모달 사용 openIssueEditModal(issueId, true); } // 완료 신청 초기화 (수정 모드로 전환) async function resetCompletionRequest(issueId) { try { const response = await fetch(`/api/issues/${issueId}/reset-completion`, { method: 'POST', headers: { 'Authorization': `Bearer ${TokenManager.getToken()}`, 'Content-Type': 'application/json' } }); if (response.ok) { alert('완료 대기 상태가 해제되었습니다. 수정이 가능합니다.'); initializeManagement(); // 페이지 새로고침 } else { const error = await response.json(); alert(`상태 변경 실패: ${error.detail || '알 수 없는 오류'}`); } } catch (error) { console.error('상태 변경 오류:', error); alert('상태 변경 중 오류가 발생했습니다.'); } } // 완료 신청 반려 async function rejectCompletionRequest(issueId, reason) { try { const response = await fetch(`/api/issues/${issueId}/reject-completion`, { method: 'POST', headers: { 'Authorization': `Bearer ${TokenManager.getToken()}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ rejection_reason: reason }) }); if (response.ok) { alert('완료 신청이 반려되었습니다.'); initializeManagement(); // 페이지 새로고침 } else { const error = await response.json(); alert(`반려 처리 실패: ${error.detail || '알 수 없는 오류'}`); } } catch (error) { console.error('반려 처리 오류:', error); alert('반려 처리 중 오류가 발생했습니다.'); } } // 완료 확인 모달 열기 function openCompletionConfirmModal(issueId) { const issue = issues.find(i => i.id === issueId); if (!issue) return; const project = projects.find(p => p.id === issue.project_id); // 모달 내용 생성 const modalContent = `

완료 확인 - No.${issue.project_sequence_no || '-'}

기본 정보

프로젝트: ${project ? project.project_name : '-'}
부적합명: ${getIssueTitle(issue)}
상세내용: ${getIssueDetail(issue)}
원인분류: ${getCategoryText(issue.final_category || issue.category)}

관리 정보

해결방안 (확정): ${cleanManagementComment(issue.management_comment) || '-'}
담당부서: ${issue.responsible_department || '-'}
담당자: ${issue.responsible_person || '-'}
조치예상일: ${issue.expected_completion_date ? new Date(issue.expected_completion_date).toLocaleDateString('ko-KR') : '-'}

완료 신청 정보

완료 사진: ${issue.completion_photo_path ? `
완료 사진
` : '

완료 사진 없음

'}
완료 코멘트:

${issue.completion_comment || '코멘트 없음'}

신청일시:

${new Date(issue.completion_requested_at).toLocaleString('ko-KR')}

업로드 사진

${issue.photo_path ? `업로드 사진 1` : '
사진 없음
'} ${issue.photo_path2 ? `업로드 사진 2` : '
사진 없음
'}
`; // 모달을 body에 추가 document.body.insertAdjacentHTML('beforeend', modalContent); } // 완료 확인 모달 닫기 function closeCompletionConfirmModal() { const modal = document.getElementById('completionConfirmModal'); if (modal) { modal.remove(); } } // 저장 후 완료 처리 (최종확인) async function saveAndCompleteIssue(issueId) { if (!confirm('수정 내용을 저장하고 이 부적합을 최종 완료 처리하시겠습니까?\n완료 처리 후에는 수정할 수 없습니다.')) { return; } const title = document.getElementById(`edit-issue-title-${issueId}`).value.trim(); const detail = document.getElementById(`edit-issue-detail-${issueId}`).value.trim(); const category = document.getElementById(`edit-category-${issueId}`).value; const managementComment = document.getElementById(`edit-management-comment-${issueId}`).value.trim(); const department = document.getElementById(`edit-department-${issueId}`).value; const person = document.getElementById(`edit-person-${issueId}`).value.trim(); const date = document.getElementById(`edit-date-${issueId}`).value; const causeDepartment = document.getElementById(`edit-cause-department-${issueId}`).value; // 완료 신청 정보 (완료 대기 상태일 때만) const completionCommentElement = document.getElementById(`edit-completion-comment-${issueId}`); const completionPhotoElement = document.getElementById(`edit-completion-photo-${issueId}`); let completionComment = null; let completionPhoto = null; if (completionCommentElement) { completionComment = completionCommentElement.value.trim(); } if (completionPhotoElement && completionPhotoElement.files[0]) { try { const file = completionPhotoElement.files[0]; console.log('🔍 업로드할 파일 정보:', { name: file.name, size: file.size, type: file.type, lastModified: file.lastModified }); const base64 = await fileToBase64(file); console.log('🔍 Base64 변환 완료 - 전체 길이:', base64.length); console.log('🔍 Base64 헤더:', base64.substring(0, 50)); completionPhoto = base64.split(',')[1]; // Base64 데이터만 추출 console.log('🔍 헤더 제거 후 길이:', completionPhoto.length); console.log('🔍 전송할 Base64 시작 부분:', completionPhoto.substring(0, 50)); } catch (error) { console.error('파일 변환 오류:', error); alert('완료 사진 업로드 중 오류가 발생했습니다.'); return; } } if (!title) { alert('부적합명을 입력해주세요.'); return; } const combinedDescription = title + (detail ? '\n' + detail : ''); const requestBody = { final_description: combinedDescription, final_category: category, management_comment: managementComment || null, responsible_department: department || null, responsible_person: person || null, expected_completion_date: date || null, cause_department: causeDepartment || null, review_status: 'completed' // 완료 상태로 변경 }; // 완료 신청 정보가 있으면 추가 if (completionComment !== null) { requestBody.completion_comment = completionComment || null; } if (completionPhoto !== null) { requestBody.completion_photo = completionPhoto; } try { // 1. 먼저 수정 내용 저장 const saveResponse = await fetch(`/api/management/${issueId}`, { method: 'PUT', headers: { 'Authorization': `Bearer ${TokenManager.getToken()}`, 'Content-Type': 'application/json' }, body: JSON.stringify(requestBody) }); if (saveResponse.ok) { alert('부적합이 수정되고 최종 완료 처리되었습니다.'); closeIssueEditModal(); initializeManagement(); // 페이지 새로고침 } else { const error = await saveResponse.json(); alert(`저장 및 완료 처리 실패: ${error.detail || '알 수 없는 오류'}`); } } catch (error) { console.error('저장 및 완료 처리 오류:', error); alert('저장 및 완료 처리 중 오류가 발생했습니다.'); } } // 최종 완료 확인 (기존 함수 - 필요시 사용) async function finalConfirmCompletion(issueId) { if (!confirm('이 부적합을 최종 완료 처리하시겠습니까?\n완료 처리 후에는 수정할 수 없습니다.')) { return; } try { const response = await fetch(`/api/issues/${issueId}/final-completion`, { method: 'POST', headers: { 'Authorization': `Bearer ${TokenManager.getToken()}`, 'Content-Type': 'application/json' } }); if (response.ok) { alert('부적합이 최종 완료 처리되었습니다.'); closeIssueEditModal(); initializeManagement(); // 페이지 새로고침 } else { const error = await response.json(); alert(`완료 처리 실패: ${error.detail || '알 수 없는 오류'}`); } } catch (error) { console.error('완료 처리 오류:', error); alert('완료 처리 중 오류가 발생했습니다.'); } } // 삭제 확인 다이얼로그 function confirmDeleteIssue(issueId) { const modal = document.createElement('div'); modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-[60]'; modal.onclick = (e) => { if (e.target === modal) modal.remove(); }; modal.innerHTML = `

부적합 삭제

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

`; document.body.appendChild(modal); } // 삭제 처리 함수 async function handleDeleteIssueFromManagement(issueId) { try { const response = await fetch(`/api/issues/${issueId}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${TokenManager.getToken()}`, 'Content-Type': 'application/json' } }); if (response.ok) { alert('부적합이 삭제되었습니다.\n삭제 로그가 기록되었습니다.'); // 모달들 닫기 const deleteModal = document.querySelector('.fixed'); if (deleteModal) deleteModal.remove(); closeIssueEditModal(); // 페이지 새로고침 initializeManagement(); } else { const error = await response.json(); alert(`삭제 실패: ${error.detail || '알 수 없는 오류'}`); } } catch (error) { console.error('삭제 오류:', error); alert('삭제 중 오류가 발생했습니다: ' + error.message); } }