/** * m-management.js — 관리함 모바일 페이지 로직 */ var currentUser = null; var issues = []; var projects = []; var filteredIssues = []; var currentTab = 'in_progress'; var currentIssueId = null; var rejectIssueId = null; function cleanManagementComment(text) { if (!text) return ''; return text.replace(/\[완료 반려[^\]]*\][^\n]*\n*/g, '').trim(); } // ===== 초기화 ===== async function initialize() { currentUser = await mCheckAuth(); if (!currentUser) return; await loadProjects(); await loadIssues(); renderBottomNav('management'); hideLoading(); } async function loadProjects() { try { var resp = await fetch(API_BASE_URL + '/projects/', { headers: { 'Authorization': 'Bearer ' + TokenManager.getToken() } }); if (resp.ok) { projects = await resp.json(); var sel = document.getElementById('projectFilter'); sel.innerHTML = ''; projects.forEach(function (p) { sel.innerHTML += ''; }); } } catch (e) { console.error('프로젝트 로드 실패:', e); } } async function loadIssues() { try { var resp = await fetch(API_BASE_URL + '/issues/admin/all', { headers: { 'Authorization': 'Bearer ' + TokenManager.getToken() } }); if (resp.ok) { var all = await resp.json(); var filtered = all.filter(function (i) { return i.review_status === 'in_progress' || i.review_status === 'completed'; }); // 프로젝트별 순번 filtered.sort(function (a, b) { return new Date(a.reviewed_at) - new Date(b.reviewed_at); }); var groups = {}; filtered.forEach(function (issue) { if (!groups[issue.project_id]) groups[issue.project_id] = []; groups[issue.project_id].push(issue); }); Object.keys(groups).forEach(function (pid) { groups[pid].forEach(function (issue, idx) { issue.project_sequence_no = idx + 1; }); }); issues = filtered; filterIssues(); } } catch (e) { console.error('이슈 로드 실패:', e); } } // ===== 탭 전환 ===== function switchTab(tab) { currentTab = tab; document.getElementById('tabInProgress').classList.toggle('active', tab === 'in_progress'); document.getElementById('tabCompleted').classList.toggle('active', tab === 'completed'); document.getElementById('additionalInfoBtn').style.display = tab === 'in_progress' ? 'flex' : 'none'; filterIssues(); } // ===== 통계 ===== function updateStatistics() { var pid = document.getElementById('projectFilter').value; var pi = pid ? issues.filter(function (i) { return i.project_id == pid; }) : issues; document.getElementById('totalCount').textContent = pi.length; document.getElementById('inProgressCount').textContent = pi.filter(function (i) { return i.review_status === 'in_progress' && !i.completion_requested_at; }).length; document.getElementById('pendingCompletionCount').textContent = pi.filter(function (i) { return i.review_status === 'in_progress' && i.completion_requested_at; }).length; document.getElementById('completedCount').textContent = pi.filter(function (i) { return i.review_status === 'completed'; }).length; } // ===== 필터 ===== function filterIssues() { var pid = document.getElementById('projectFilter').value; filteredIssues = issues.filter(function (i) { if (i.review_status !== currentTab) return false; if (pid && i.project_id != pid) return false; return true; }); filteredIssues.sort(function (a, b) { return new Date(b.report_date) - new Date(a.report_date); }); renderIssues(); updateStatistics(); } // ===== 이슈 상태 ===== function getIssueStatus(issue) { if (issue.review_status === 'completed') return 'completed'; if (issue.completion_requested_at) return 'pending_completion'; if (issue.expected_completion_date) { var diff = (new Date(issue.expected_completion_date) - new Date()) / 86400000; if (diff < 0) return 'overdue'; if (diff <= 3) return 'urgent'; } return 'in_progress'; } function getStatusBadgeHtml(status) { var map = { 'in_progress': ' 진행 중', 'urgent': ' 긴급', 'overdue': ' 지연됨', 'pending_completion': ' 완료 대기', 'completed': ' 완료됨' }; return map[status] || map['in_progress']; } // ===== 렌더링 ===== function renderIssues() { var container = document.getElementById('issuesList'); var empty = document.getElementById('emptyState'); if (!filteredIssues.length) { container.innerHTML = ''; empty.classList.remove('hidden'); return; } empty.classList.add('hidden'); // 날짜별 그룹 var grouped = {}; var dateObjs = {}; filteredIssues.forEach(function (issue) { var dateToUse = currentTab === 'completed' ? (issue.actual_completion_date || issue.report_date) : issue.report_date; var d = new Date(dateToUse); var key = d.toLocaleDateString('ko-KR'); if (!grouped[key]) { grouped[key] = []; dateObjs[key] = d; } grouped[key].push(issue); }); var html = Object.keys(grouped) .sort(function (a, b) { return dateObjs[b] - dateObjs[a]; }) .map(function (dateKey) { var issues = grouped[dateKey]; return '
' + '' + '' + dateKey + '' + '(' + issues.length + '건)' + '' + (currentTab === 'in_progress' ? '업로드일' : '완료일') + '' + '
' + issues.map(function (issue) { return currentTab === 'in_progress' ? renderInProgressCard(issue) : renderCompletedCard(issue); }).join('') + '
'; }).join(''); container.innerHTML = html; } function renderInProgressCard(issue) { var project = projects.find(function (p) { return p.id === issue.project_id; }); var status = getIssueStatus(issue); var isPending = status === 'pending_completion'; var photos = getPhotoPaths(issue); // 관리 필드 표시 var mgmtHtml = '
' + '
해결방안: ' + escapeHtml(cleanManagementComment(issue.management_comment) || '-') + '
' + '
담당부서: ' + getDepartmentText(issue.responsible_department) + '
' + '
담당자: ' + escapeHtml(issue.responsible_person || '-') + '
' + '
조치예상일: ' + (issue.expected_completion_date ? formatKSTDate(issue.expected_completion_date) : '-') + '
' + '
'; // 완료 대기 정보 var completionInfoHtml = ''; if (isPending) { var cPhotos = getCompletionPhotoPaths(issue); completionInfoHtml = '
' + '
완료 신청 정보
' + (cPhotos.length ? renderPhotoThumbs(cPhotos) : '') + '
' + escapeHtml(issue.completion_comment || '코멘트 없음') + '
' + '
신청: ' + formatKSTDateTime(issue.completion_requested_at) + '
' + '
'; } // 액션 버튼 var actionHtml = ''; if (isPending) { actionHtml = '
' + '' + '' + '
'; } else { actionHtml = '
' + '' + '' + '
'; } return '
' + '
' + '
No.' + (issue.project_sequence_no || '-') + '' + '' + escapeHtml(project ? project.project_name : '미지정') + '
' + getStatusBadgeHtml(status) + '
' + '
' + escapeHtml(getIssueTitle(issue)) + '
' + '
' + '
' + escapeHtml(getIssueDetail(issue)) + '
' + '
' + '' + getCategoryText(issue.category || issue.final_category) + '' + '' + escapeHtml(issue.reporter?.full_name || issue.reporter?.username || '-') + '' + '
' + (photos.length ? renderPhotoThumbs(photos) : '') + mgmtHtml + completionInfoHtml + '
' + actionHtml + '' + '
'; } function renderCompletedCard(issue) { var project = projects.find(function (p) { return p.id === issue.project_id; }); var completedDate = issue.completed_at ? formatKSTDate(issue.completed_at) : '-'; return '
' + '
' + '
No.' + (issue.project_sequence_no || '-') + '' + '' + escapeHtml(project ? project.project_name : '미지정') + '
' + ' 완료' + '
' + '
' + escapeHtml(getIssueTitle(issue)) + '
' + '' + '
'; } // ===== 편집 시트 ===== function openEditMgmtSheet(issueId) { currentIssueId = issueId; var issue = issues.find(function (i) { return i.id === issueId; }); if (!issue) return; // 프로젝트 셀렉트 채우기 var projSel = document.getElementById('editProject'); projSel.innerHTML = ''; projects.forEach(function (p) { projSel.innerHTML += ''; }); projSel.disabled = (issue.review_status === 'completed'); document.getElementById('editManagementComment').value = cleanManagementComment(issue.management_comment) || ''; document.getElementById('editResponsibleDept').value = issue.responsible_department || ''; document.getElementById('editResponsiblePerson').value = issue.responsible_person || ''; document.getElementById('editExpectedDate').value = issue.expected_completion_date ? issue.expected_completion_date.split('T')[0] : ''; // 원본 사진 보충 UI 초기화 var slotKeys = ['photo_path', 'photo_path2', 'photo_path3', 'photo_path4', 'photo_path5']; var existingPhotos = slotKeys.map(function (k) { return issue[k]; }).filter(function (p) { return p; }); var emptyCount = 5 - existingPhotos.length; var existingEl = document.getElementById('editExistingPhotos'); existingEl.innerHTML = existingPhotos.length ? existingPhotos.map(function (p) { return '기존 사진'; }).join('') : '기존 사진 없음'; var slotInfoEl = document.getElementById('editPhotoSlotInfo'); slotInfoEl.textContent = emptyCount > 0 ? '(남은 슬롯: ' + emptyCount + '장)' : '(가득 참)'; var photoInput = document.getElementById('editPhotoInput'); photoInput.value = ''; photoInput.disabled = (emptyCount === 0); document.getElementById('editPhotoPreview').innerHTML = ''; openSheet('editMgmt'); } // 파일 input change 시 미리보기 렌더 function previewEditPhotos(event) { var files = event.target.files; var preview = document.getElementById('editPhotoPreview'); preview.innerHTML = ''; if (!files || !files.length) return; Array.prototype.forEach.call(files, function (file) { var reader = new FileReader(); reader.onload = function (e) { var img = document.createElement('img'); img.src = e.target.result; img.style.cssText = 'width:52px;height:52px;object-fit:cover;border-radius:6px;border:2px solid #10b981'; img.alt = '추가 예정'; preview.appendChild(img); }; reader.readAsDataURL(file); }); } function fileToBase64(file) { return new Promise(function (resolve, reject) { var reader = new FileReader(); reader.onload = function (e) { resolve(e.target.result); }; reader.onerror = reject; reader.readAsDataURL(file); }); } async function saveManagementEdit() { if (!currentIssueId) return; try { var updates = { management_comment: document.getElementById('editManagementComment').value.trim() || null, responsible_department: document.getElementById('editResponsibleDept').value || null, responsible_person: document.getElementById('editResponsiblePerson').value.trim() || null, expected_completion_date: document.getElementById('editExpectedDate').value ? document.getElementById('editExpectedDate').value + 'T00:00:00' : null }; // 원본 사진 보충 — 빈 슬롯에만 채움 var photoInput = document.getElementById('editPhotoInput'); if (photoInput && photoInput.files && photoInput.files.length > 0) { var currentIssue = issues.find(function (i) { return i.id === currentIssueId; }); if (currentIssue) { var slotKeys = ['photo_path', 'photo_path2', 'photo_path3', 'photo_path4', 'photo_path5']; var emptySlots = []; slotKeys.forEach(function (k, idx) { if (!currentIssue[k]) emptySlots.push(idx + 1); }); if (emptySlots.length === 0) { showToast('원본 사진 슬롯이 가득 찼습니다', 'warning'); return; } var filesToUpload = Array.prototype.slice.call(photoInput.files, 0, emptySlots.length); if (photoInput.files.length > emptySlots.length) { showToast('빈 슬롯 ' + emptySlots.length + '장 중 처음 ' + emptySlots.length + '장만 업로드됩니다', 'info'); } for (var i = 0; i < filesToUpload.length; i++) { var base64 = await fileToBase64(filesToUpload[i]); var slotNum = emptySlots[i]; var fieldName = slotNum === 1 ? 'photo' : 'photo' + slotNum; updates[fieldName] = base64; } } } // 프로젝트 변경 확인 var newProjectId = parseInt(document.getElementById('editProject').value); var issue = issues.find(function (i) { return i.id === currentIssueId; }); if (newProjectId && issue && newProjectId !== issue.project_id) { // 프로젝트 변경은 /issues/{id} PUT으로 별도 호출 var projResp = await fetch(API_BASE_URL + '/issues/' + currentIssueId, { method: 'PUT', headers: { 'Authorization': 'Bearer ' + TokenManager.getToken(), 'Content-Type': 'application/json' }, body: JSON.stringify({ project_id: newProjectId }) }); if (!projResp.ok) { var projErr = await projResp.json(); throw new Error(projErr.detail || '프로젝트 변경 실패'); } } var resp = await fetch(API_BASE_URL + '/issues/' + currentIssueId + '/management', { method: 'PUT', headers: { 'Authorization': 'Bearer ' + TokenManager.getToken(), 'Content-Type': 'application/json' }, body: JSON.stringify(updates) }); if (resp.ok) { showToast('저장되었습니다.', 'success'); closeSheet('editMgmt'); await loadIssues(); } else { var err = await resp.json(); throw new Error(err.detail || '저장 실패'); } } catch (e) { showToast('오류: ' + e.message, 'error'); } } // ===== 완료 처리 ===== async function confirmCompletion(issueId) { if (!confirm('완료 처리하시겠습니까?')) return; try { var resp = await fetch(API_BASE_URL + '/inbox/' + issueId + '/status', { method: 'POST', headers: { 'Authorization': 'Bearer ' + TokenManager.getToken(), 'Content-Type': 'application/json' }, body: JSON.stringify({ review_status: 'completed' }) }); if (resp.ok) { showToast('완료 처리되었습니다.', 'success'); await loadIssues(); } else { var err = await resp.json(); throw new Error(err.detail || '완료 처리 실패'); } } catch (e) { showToast('오류: ' + e.message, 'error'); } } // ===== 반려 ===== function openRejectSheet(issueId) { rejectIssueId = issueId; document.getElementById('rejectReason').value = ''; openSheet('reject'); } async function submitReject() { if (!rejectIssueId) return; var reason = document.getElementById('rejectReason').value.trim(); if (!reason) { showToast('반려 사유를 입력해주세요.', 'warning'); return; } try { var resp = await fetch(API_BASE_URL + '/issues/' + rejectIssueId + '/reject-completion', { method: 'POST', headers: { 'Authorization': 'Bearer ' + TokenManager.getToken(), 'Content-Type': 'application/json' }, body: JSON.stringify({ rejection_reason: reason }) }); if (resp.ok) { showToast('반려 처리되었습니다.', 'success'); closeSheet('reject'); await loadIssues(); } else { var err = await resp.json(); throw new Error(err.detail || '반려 실패'); } } catch (e) { showToast('오류: ' + e.message, 'error'); } } // ===== 추가 정보 ===== function openAdditionalInfoSheet() { var inProgressIssues = issues.filter(function (i) { return i.review_status === 'in_progress'; }); var sel = document.getElementById('additionalIssueSelect'); sel.innerHTML = ''; inProgressIssues.forEach(function (i) { var p = projects.find(function (pr) { return pr.id === i.project_id; }); sel.innerHTML += ''; }); document.getElementById('additionalCauseDept').value = ''; document.getElementById('additionalCausePerson').value = ''; document.getElementById('additionalCauseDetail').value = ''; openSheet('additional'); } function loadAdditionalInfo() { var id = parseInt(document.getElementById('additionalIssueSelect').value); if (!id) return; var issue = issues.find(function (i) { return i.id === id; }); if (!issue) return; document.getElementById('additionalCauseDept').value = issue.cause_department || ''; document.getElementById('additionalCausePerson').value = issue.responsible_person_detail || ''; document.getElementById('additionalCauseDetail').value = issue.cause_detail || ''; } async function saveAdditionalInfo() { var id = parseInt(document.getElementById('additionalIssueSelect').value); if (!id) { showToast('이슈를 선택해주세요.', 'warning'); return; } try { var resp = await fetch(API_BASE_URL + '/issues/' + id + '/management', { method: 'PUT', headers: { 'Authorization': 'Bearer ' + TokenManager.getToken(), 'Content-Type': 'application/json' }, body: JSON.stringify({ cause_department: document.getElementById('additionalCauseDept').value || null, responsible_person_detail: document.getElementById('additionalCausePerson').value.trim() || null, cause_detail: document.getElementById('additionalCauseDetail').value.trim() || null }) }); if (resp.ok) { showToast('추가 정보가 저장되었습니다.', 'success'); closeSheet('additional'); await loadIssues(); } else { var err = await resp.json(); throw new Error(err.detail || '저장 실패'); } } catch (e) { showToast('오류: ' + e.message, 'error'); } } // ===== 완료됨 상세보기 ===== function openDetailSheet(issueId) { var issue = issues.find(function (i) { return i.id === issueId; }); if (!issue) return; var project = projects.find(function (p) { return p.id === issue.project_id; }); var photos = getPhotoPaths(issue); var cPhotos = getCompletionPhotoPaths(issue); document.getElementById('detailSheetTitle').innerHTML = 'No.' + (issue.project_sequence_no || '-') + ' 상세 정보'; document.getElementById('detailSheetBody').innerHTML = // 기본 정보 '
' + '
기본 정보
' + '
프로젝트: ' + escapeHtml(project ? project.project_name : '-') + '
' + '
부적합명: ' + escapeHtml(getIssueTitle(issue)) + '
' + '
' + escapeHtml(getIssueDetail(issue)) + '
' + '
분류: ' + getCategoryText(issue.final_category || issue.category) + '
' + '
확인자: ' + escapeHtml(getReporterNames(issue)) + '
' + (photos.length ? '
업로드 사진
' + renderPhotoThumbs(photos) + '
' : '') + '
' + // 관리 정보 '
' + '
관리 정보
' + '
해결방안: ' + escapeHtml(cleanManagementComment(issue.management_comment) || '-') + '
' + '
담당부서: ' + getDepartmentText(issue.responsible_department) + '
' + '
담당자: ' + escapeHtml(issue.responsible_person || '-') + '
' + '
원인부서: ' + getDepartmentText(issue.cause_department) + '
' + '
' + // 완료 정보 '
' + '
완료 정보
' + (cPhotos.length ? '
완료 사진
' + renderPhotoThumbs(cPhotos) + '
' : '
완료 사진 없음
') + '
완료 코멘트: ' + escapeHtml(issue.completion_comment || '-') + '
' + (issue.completion_requested_at ? '
완료 신청일: ' + formatKSTDateTime(issue.completion_requested_at) + '
' : '') + (issue.completed_at ? '
최종 완료일: ' + formatKSTDateTime(issue.completed_at) + '
' : '') + '
'; openSheet('detail'); } // ===== 시작 ===== document.addEventListener('DOMContentLoaded', initialize);