feat: 부적합 조회 페이지를 새로운 워크플로우 시스템에 맞게 업데이트
🔄 Workflow Status System Integration: - 기존 '검토 상태' → '워크플로우 상태'로 변경 - 4단계 워크플로우 지원: 수신함, 관리함(진행중), 관리함(완료), 폐기함 - 기존 데이터와의 호환성 유지 (폴백 로직) 📋 Filter Updates: - issue-view.html: 워크플로우 상태 필터 추가 - index.html: 목록 섹션 워크플로우 상태 필터 추가 - 4개 상태별 필터링: pending_review, in_progress, completed, disposed 🎨 Visual Enhancements: - 워크플로우 상태별 배지 시스템 구현 - 아이콘 및 색상 코딩: 수신함(주황), 진행중(파랑), 완료(녹색), 폐기(회색) - 상태별 그룹화 표시 개선 🔧 Technical Implementation: - getWorkflowStatusBadge() 함수 추가 - review_status 필드 기반 필터링 - 기존 isReviewCompleted() 함수와 호환성 유지 - 상태별 분류 로직 개선 📊 Status Configuration: - pending_review: 수신함 (검토 대기) - fas fa-inbox - in_progress: 관리함 (진행 중) - fas fa-cog - completed: 관리함 (완료됨) - fas fa-check-circle - disposed: 폐기함 (폐기됨) - fas fa-trash 🔄 Backward Compatibility: - 기존 데이터 자동 매핑 - 점진적 마이그레이션 지원 - 오류 없는 상태 전환 Expected Result: ✅ 부적합 조회에서 4단계 워크플로우 상태 확인 가능 ✅ 수신함/관리함/폐기함별 필터링 지원 ✅ 시각적으로 구분되는 상태 배지 표시 ✅ 기존 데이터와 새 시스템 모두 호환
This commit is contained in:
@@ -403,15 +403,17 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- 검토 상태 필터 -->
|
||||
<!-- 워크플로우 상태 필터 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">검토 상태</label>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">워크플로우 상태</label>
|
||||
<select id="reviewStatusFilter" class="w-full px-3 py-2 border border-gray-300 rounded text-sm" onchange="displayIssueList()">
|
||||
<option value="">전체</option>
|
||||
<option value="pending">검토 필요</option>
|
||||
<option value="completed">검토 완료</option>
|
||||
<option value="pending_review">수신함 (검토 대기)</option>
|
||||
<option value="in_progress">관리함 (진행 중)</option>
|
||||
<option value="completed">관리함 (완료됨)</option>
|
||||
<option value="disposed">폐기함 (폐기됨)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 날짜 필터 -->
|
||||
<div>
|
||||
@@ -1403,11 +1405,20 @@
|
||||
});
|
||||
}
|
||||
|
||||
// 검토 상태 필터 적용
|
||||
// 워크플로우 상태 필터 적용
|
||||
if (reviewStatusFilter) {
|
||||
filteredIssues = filteredIssues.filter(issue => {
|
||||
const isCompleted = isReviewCompleted(issue);
|
||||
return reviewStatusFilter === 'completed' ? isCompleted : !isCompleted;
|
||||
// 새로운 워크플로우 시스템 사용
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1421,9 +1432,20 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// 검토 상태별로 분류 및 정렬
|
||||
const pendingIssues = filteredIssues.filter(issue => !isReviewCompleted(issue));
|
||||
const completedIssues = filteredIssues.filter(issue => isReviewCompleted(issue));
|
||||
// 워크플로우 상태별로 분류 및 정렬
|
||||
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')
|
||||
};
|
||||
|
||||
const pendingIssues = groupedIssues.pending_review;
|
||||
const completedIssues = [...groupedIssues.in_progress, ...groupedIssues.completed, ...groupedIssues.disposed];
|
||||
|
||||
// 검토 필요 항목을 먼저 표시
|
||||
if (pendingIssues.length > 0) {
|
||||
|
||||
@@ -135,13 +135,15 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- 검토 상태 필터 -->
|
||||
<!-- 워크플로우 상태 필터 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">📋 검토 상태</label>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">📋 워크플로우 상태</label>
|
||||
<select id="reviewStatusFilter" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500" onchange="filterIssues()">
|
||||
<option value="">전체</option>
|
||||
<option value="pending">검토 필요</option>
|
||||
<option value="completed">검토 완료</option>
|
||||
<option value="pending_review">수신함 (검토 대기)</option>
|
||||
<option value="in_progress">관리함 (진행 중)</option>
|
||||
<option value="completed">관리함 (완료됨)</option>
|
||||
<option value="disposed">폐기함 (폐기됨)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -513,11 +515,20 @@
|
||||
});
|
||||
}
|
||||
|
||||
// 검토 상태 필터 적용
|
||||
// 워크플로우 상태 필터 적용
|
||||
if (reviewStatusFilter) {
|
||||
filteredIssues = filteredIssues.filter(issue => {
|
||||
const isCompleted = isReviewCompleted(issue);
|
||||
return reviewStatusFilter === 'completed' ? isCompleted : !isCompleted;
|
||||
// 새로운 워크플로우 시스템 사용
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -668,45 +679,64 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// 검토 상태별로 분류 및 정렬
|
||||
const pendingIssues = filteredIssues.filter(issue => !isReviewCompleted(issue));
|
||||
const completedIssues = filteredIssues.filter(issue => isReviewCompleted(issue));
|
||||
// 워크플로우 상태별로 분류 및 정렬
|
||||
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 = '';
|
||||
|
||||
// 검토 필요 항목을 먼저 표시
|
||||
if (pendingIssues.length > 0) {
|
||||
const pendingHeader = document.createElement('div');
|
||||
pendingHeader.className = 'mb-4';
|
||||
pendingHeader.innerHTML = `
|
||||
<h3 class="text-md font-semibold text-orange-700 flex items-center">
|
||||
<i class="fas fa-exclamation-triangle mr-2"></i>검토 필요 (${pendingIssues.length}건)
|
||||
</h3>
|
||||
`;
|
||||
container.appendChild(pendingHeader);
|
||||
|
||||
pendingIssues.forEach(issue => {
|
||||
container.appendChild(createIssueCard(issue, false));
|
||||
});
|
||||
}
|
||||
// 각 상태별로 표시
|
||||
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' }
|
||||
];
|
||||
|
||||
// 검토 완료 항목을 아래에 표시
|
||||
if (completedIssues.length > 0) {
|
||||
const completedHeader = document.createElement('div');
|
||||
completedHeader.className = 'mb-4 mt-8';
|
||||
completedHeader.innerHTML = `
|
||||
<h3 class="text-md font-semibold text-green-700 flex items-center">
|
||||
<i class="fas fa-check-circle mr-2"></i>검토 완료 (${completedIssues.length}건)
|
||||
</h3>
|
||||
`;
|
||||
container.appendChild(completedHeader);
|
||||
|
||||
completedIssues.forEach(issue => {
|
||||
container.appendChild(createIssueCard(issue, true));
|
||||
});
|
||||
}
|
||||
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 = `
|
||||
<h3 class="text-md font-semibold ${config.color} flex items-center">
|
||||
<i class="${config.icon} mr-2"></i>${config.title} (${issues.length}건)
|
||||
</h3>
|
||||
`;
|
||||
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 `<span class="px-2 py-1 rounded-full text-xs font-medium ${config.class}">
|
||||
<i class="${config.icon} mr-1"></i>${config.text}
|
||||
</span>`;
|
||||
}
|
||||
|
||||
// 부적합 사항 카드 생성 함수 (조회용)
|
||||
function createIssueCard(issue, isCompleted) {
|
||||
const categoryNames = {
|
||||
@@ -740,10 +770,7 @@
|
||||
<!-- 프로젝트 정보 및 상태 (오른쪽 상단) -->
|
||||
<div class="flex justify-between items-start p-2 pb-0">
|
||||
<div class="flex items-center gap-2">
|
||||
${isCompleted ?
|
||||
'<div class="px-2 py-1 bg-green-100 text-green-800 rounded-full text-xs font-medium"><i class="fas fa-check-circle mr-1"></i>검토완료</div>' :
|
||||
'<div class="px-2 py-1 bg-orange-100 text-orange-800 rounded-full text-xs font-medium"><i class="fas fa-exclamation-triangle mr-1"></i>검토필요</div>'
|
||||
}
|
||||
${getWorkflowStatusBadge(issue)}
|
||||
</div>
|
||||
<div class="px-2 py-1 bg-blue-100 text-blue-800 rounded-full text-xs font-medium">
|
||||
<i class="fas fa-folder-open mr-1"></i>${projectInfo}
|
||||
|
||||
Reference in New Issue
Block a user