refactor: 완료 반려 필드 분리 및 데이터 구조 개선
- backend: completion_rejection_reason 등 전용 필드 추가 - 기존 management_comment에 섞여있던 완료 반려 내용 분리 - 현황판: 완료 반려 내역 별도 카드로 표시 - 관리함: 해결방안에 완료 반려 내용 제외하여 표시 - DB 마이그레이션: completion_rejected_at, completion_rejected_by_id, completion_rejection_reason 필드 추가 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -425,6 +425,13 @@
|
||||
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 = localStorage.getItem('access_token');
|
||||
@@ -782,6 +789,9 @@
|
||||
<button onclick="saveIssueChanges(${issue.id})" class="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors">
|
||||
<i class="fas fa-save mr-1"></i>저장
|
||||
</button>
|
||||
<button onclick="confirmDeleteIssue(${issue.id})" class="px-4 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors">
|
||||
<i class="fas fa-trash mr-1"></i>삭제
|
||||
</button>
|
||||
<button onclick="confirmCompletion(${issue.id})" class="px-4 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600 transition-colors">
|
||||
<i class="fas fa-check mr-1"></i>완료처리
|
||||
</button>
|
||||
@@ -852,9 +862,9 @@
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">
|
||||
<i class="fas fa-lightbulb text-yellow-500 mr-1"></i>해결방안
|
||||
<i class="fas fa-lightbulb text-yellow-500 mr-1"></i>해결방안 (확정)
|
||||
</label>
|
||||
<textarea id="solution_${issue.id}" rows="3" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 resize-none ${isPendingCompletion ? 'bg-gray-100 cursor-not-allowed' : ''}" placeholder="해결 방안을 입력하세요..." ${isPendingCompletion ? 'readonly' : ''}>${issue.solution || ''}</textarea>
|
||||
<textarea id="management_comment_${issue.id}" rows="3" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 resize-none ${isPendingCompletion ? 'bg-gray-100 cursor-not-allowed' : ''}" placeholder="확정된 해결 방안을 입력하세요..." ${isPendingCompletion ? 'readonly' : ''}>${cleanManagementComment(issue.management_comment)}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
@@ -987,11 +997,11 @@
|
||||
관리 정보
|
||||
</h4>
|
||||
<div class="space-y-2 text-sm">
|
||||
<div><span class="font-medium text-blue-700">해결방안:</span> <span class="text-blue-900">${issue.solution || '-'}</span></div>
|
||||
<div><span class="font-medium text-blue-700">해결방안 (확정):</span> <span class="text-blue-900">${cleanManagementComment(issue.management_comment) || '-'}</span></div>
|
||||
<div><span class="font-medium text-blue-700">담당부서:</span> <span class="text-blue-900">${getDepartmentText(issue.responsible_department) || '-'}</span></div>
|
||||
<div><span class="font-medium text-blue-700">담당자:</span> <span class="text-blue-900">${issue.responsible_person || '-'}</span></div>
|
||||
<div><span class="font-medium text-blue-700">원인부서:</span> <span class="text-blue-900">${getDepartmentText(issue.cause_department) || '-'}</span></div>
|
||||
<div><span class="font-medium text-blue-700">관리 코멘트:</span> <span class="text-blue-900">${issue.management_comment || '-'}</span></div>
|
||||
<div><span class="font-medium text-blue-700">관리 코멘트:</span> <span class="text-blue-900">${cleanManagementComment(issue.management_comment) || '-'}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1277,7 +1287,7 @@
|
||||
try {
|
||||
// 편집된 필드들의 값 수집
|
||||
const updates = {};
|
||||
const fields = ['solution', 'responsible_department', 'responsible_person', 'expected_completion_date', 'cause_department', 'management_comment'];
|
||||
const fields = ['management_comment', 'responsible_department', 'responsible_person', 'expected_completion_date', 'cause_department'];
|
||||
|
||||
fields.forEach(field => {
|
||||
const element = document.getElementById(`${field}_${issueId}`);
|
||||
@@ -1406,8 +1416,8 @@
|
||||
|
||||
<div class="space-y-3">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">해결방안</label>
|
||||
<textarea id="modal_solution" rows="3" class="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">${issue.solution || ''}</textarea>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">해결방안 (확정)</label>
|
||||
<textarea id="modal_management_comment" rows="3" class="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500" placeholder="확정된 해결 방안을 입력하세요...">${cleanManagementComment(issue.management_comment)}</textarea>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@@ -1440,7 +1450,7 @@
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">의견</label>
|
||||
<textarea id="modal_management_comment" rows="3" class="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">${issue.management_comment || ''}</textarea>
|
||||
<textarea id="modal_management_comment" rows="3" class="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">${cleanManagementComment(issue.management_comment)}</textarea>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@@ -1465,7 +1475,7 @@
|
||||
try {
|
||||
// 편집된 필드들의 값 수집
|
||||
const updates = {};
|
||||
const fields = ['solution', 'responsible_department', 'responsible_person', 'expected_completion_date', 'cause_department', 'management_comment'];
|
||||
const fields = ['management_comment', 'responsible_department', 'responsible_person', 'expected_completion_date', 'cause_department'];
|
||||
|
||||
fields.forEach(field => {
|
||||
const element = document.getElementById(`modal_${field}`);
|
||||
@@ -1872,8 +1882,8 @@
|
||||
<h4 class="font-semibold text-green-800 mb-3">관리 정보</h4>
|
||||
<div class="space-y-3">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">해결방안</label>
|
||||
<textarea id="edit-solution-${issue.id}" rows="3" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-green-500 text-sm resize-none" placeholder="해결 방안을 입력하세요...">${issue.solution || ''}</textarea>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">해결방안 (확정)</label>
|
||||
<textarea id="edit-management-comment-${issue.id}" rows="3" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-green-500 text-sm resize-none" placeholder="확정된 해결 방안을 입력하세요...">${cleanManagementComment(issue.management_comment)}</textarea>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
@@ -1903,7 +1913,7 @@
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">관리 코멘트</label>
|
||||
<textarea id="edit-management-comment-${issue.id}" rows="2" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-green-500 text-sm resize-none" placeholder="관리 코멘트를 입력하세요...">${issue.management_comment || ''}</textarea>
|
||||
<textarea id="edit-management-comment-${issue.id}" rows="2" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-green-500 text-sm resize-none" placeholder="관리 코멘트를 입력하세요...">${cleanManagementComment(issue.management_comment)}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1964,6 +1974,9 @@
|
||||
<button onclick="saveIssueFromModal(${issue.id})" class="px-6 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors">
|
||||
<i class="fas fa-save mr-2"></i>저장
|
||||
</button>
|
||||
<button onclick="confirmDeleteIssue(${issue.id})" class="px-6 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors">
|
||||
<i class="fas fa-trash mr-2"></i>삭제
|
||||
</button>
|
||||
<button onclick="saveAndCompleteIssue(${issue.id})" class="px-6 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600 transition-colors">
|
||||
<i class="fas fa-check-circle mr-2"></i>최종확인
|
||||
</button>
|
||||
@@ -2016,12 +2029,11 @@
|
||||
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 solution = document.getElementById(`edit-solution-${issueId}`).value.trim();
|
||||
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 managementComment = document.getElementById(`edit-management-comment-${issueId}`).value.trim();
|
||||
|
||||
// 완료 신청 정보 (완료 대기 상태일 때만)
|
||||
const completionCommentElement = document.getElementById(`edit-completion-comment-${issueId}`);
|
||||
@@ -2064,16 +2076,15 @@
|
||||
}
|
||||
|
||||
const combinedDescription = title + (detail ? '\n' + detail : '');
|
||||
|
||||
|
||||
const requestBody = {
|
||||
final_description: combinedDescription,
|
||||
final_category: category,
|
||||
solution: solution || null,
|
||||
management_comment: managementComment || null,
|
||||
responsible_department: department || null,
|
||||
responsible_person: person || null,
|
||||
expected_completion_date: date || null,
|
||||
cause_department: causeDepartment || null,
|
||||
management_comment: managementComment || null
|
||||
cause_department: causeDepartment || null
|
||||
};
|
||||
|
||||
// 완료 신청 정보가 있으면 추가
|
||||
@@ -2267,7 +2278,7 @@
|
||||
<div class="bg-green-50 p-4 rounded-lg">
|
||||
<h4 class="font-semibold text-green-800 mb-2">관리 정보</h4>
|
||||
<div class="space-y-2 text-sm">
|
||||
<div><span class="font-medium">해결방안:</span> ${issue.solution || '-'}</div>
|
||||
<div><span class="font-medium">해결방안 (확정):</span> ${cleanManagementComment(issue.management_comment) || '-'}</div>
|
||||
<div><span class="font-medium">담당부서:</span> ${issue.responsible_department || '-'}</div>
|
||||
<div><span class="font-medium">담당자:</span> ${issue.responsible_person || '-'}</div>
|
||||
<div><span class="font-medium">조치예상일:</span> ${issue.expected_completion_date ? new Date(issue.expected_completion_date).toLocaleDateString('ko-KR') : '-'}</div>
|
||||
@@ -2344,12 +2355,11 @@
|
||||
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 solution = document.getElementById(`edit-solution-${issueId}`).value.trim();
|
||||
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 managementComment = document.getElementById(`edit-management-comment-${issueId}`).value.trim();
|
||||
|
||||
// 완료 신청 정보 (완료 대기 상태일 때만)
|
||||
const completionCommentElement = document.getElementById(`edit-completion-comment-${issueId}`);
|
||||
@@ -2392,16 +2402,15 @@
|
||||
}
|
||||
|
||||
const combinedDescription = title + (detail ? '\n' + detail : '');
|
||||
|
||||
|
||||
const requestBody = {
|
||||
final_description: combinedDescription,
|
||||
final_category: category,
|
||||
solution: solution || null,
|
||||
management_comment: managementComment || null,
|
||||
responsible_department: department || null,
|
||||
responsible_person: person || null,
|
||||
expected_completion_date: date || null,
|
||||
cause_department: causeDepartment || null,
|
||||
management_comment: managementComment || null,
|
||||
review_status: 'completed' // 완료 상태로 변경
|
||||
};
|
||||
|
||||
@@ -2466,6 +2475,75 @@
|
||||
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 = `
|
||||
<div class="bg-white rounded-lg p-6 w-96 max-w-md mx-4">
|
||||
<div class="text-center mb-4">
|
||||
<div class="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-red-100 mb-4">
|
||||
<i class="fas fa-exclamation-triangle text-red-600 text-xl"></i>
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold mb-2">부적합 삭제</h3>
|
||||
<p class="text-sm text-gray-600">
|
||||
이 부적합 사항을 삭제하시겠습니까?<br>
|
||||
<strong class="text-red-600">삭제된 데이터는 로그로 보관되지만 복구할 수 없습니다.</strong>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<button onclick="this.closest('.fixed').remove()"
|
||||
class="flex-1 px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors">
|
||||
취소
|
||||
</button>
|
||||
<button onclick="handleDeleteIssueFromManagement(${issueId})"
|
||||
class="flex-1 px-4 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors">
|
||||
<i class="fas fa-trash mr-1"></i>삭제
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(modal);
|
||||
}
|
||||
|
||||
// 삭제 처리 함수
|
||||
async function handleDeleteIssueFromManagement(issueId) {
|
||||
try {
|
||||
const response = await fetch(`/api/issues/${issueId}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem('access_token')}`,
|
||||
'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);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- 추가 정보 입력 모달 -->
|
||||
|
||||
Reference in New Issue
Block a user