Files
tk-factory-services/system3-nonconformance/web/static/js/pages/issues-archive.js
Hyungi Ahn bf4000c4ae refactor: 코드 분리 + 성능 최적화 + 모바일 개선
tkqc 5개 페이지 인라인 JS/CSS를 외부 파일로 추출 (HTML 82% 감소)
tkuser index.html을 CSS 1개 + JS 10개 모듈로 분리 (3283→1155줄)

- 공통 유틸 추출: issue-helpers, photo-modal, toast
- 공통 CSS 확장: tkqc-common.css (모바일 반응형 포함)
- 모바일 하단 네비게이션 추가 (mobile-bottom-nav.js)
- nginx: JS/CSS 1시간 캐싱 + gzip 압축 활성화
- Tailwind CDN preload, 캐시버스터 통일 (?v=20260213)
- 카메라 capture="environment" 추가
- tkuser Dockerfile에 static/ 디렉토리 복사 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 12:11:51 +09:00

333 lines
12 KiB
JavaScript

/**
* issues-archive.js — 폐기함 페이지 스크립트
*/
let currentUser = null;
let issues = [];
let projects = [];
let filteredIssues = [];
// API 로드 후 초기화 함수
async function initializeArchive() {
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_archive');
// 페이지 접근 권한 체크
setTimeout(() => {
if (!canAccessPage('issues_archive')) {
alert('폐기함 페이지에 접근할 권한이 없습니다.');
window.location.href = '/index.html';
return;
}
}, 500);
// 데이터 로드
await loadProjects();
await loadArchivedIssues();
} 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 loadArchivedIssues() {
try {
let endpoint = '/api/issues/';
// 관리자인 경우 전체 부적합 조회 API 사용
if (currentUser.role === 'admin') {
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();
// 폐기된 부적합만 필터링 (폐기함 전용)
issues = allIssues.filter(issue =>
issue.review_status === 'disposed'
);
filterIssues();
updateStatistics();
renderCharts();
} else {
throw new Error('부적합 목록을 불러올 수 없습니다.');
}
} catch (error) {
console.error('부적합 로드 실패:', error);
alert('부적합 목록을 불러오는데 실패했습니다.');
}
}
// 필터링 및 표시
function filterIssues() {
const projectFilter = document.getElementById('projectFilter').value;
const statusFilter = document.getElementById('statusFilter').value;
const periodFilter = document.getElementById('periodFilter').value;
const categoryFilter = document.getElementById('categoryFilter').value;
const searchInput = document.getElementById('searchInput').value.toLowerCase();
filteredIssues = issues.filter(issue => {
if (projectFilter && issue.project_id != projectFilter) return false;
if (statusFilter && issue.status !== statusFilter) return false;
if (categoryFilter && issue.category !== categoryFilter) return false;
// 기간 필터
if (periodFilter) {
const issueDate = new Date(issue.updated_at || issue.created_at);
const now = new Date();
switch (periodFilter) {
case 'week':
const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
if (issueDate < weekAgo) return false;
break;
case 'month':
const monthAgo = new Date(now.getFullYear(), now.getMonth() - 1, now.getDate());
if (issueDate < monthAgo) return false;
break;
case 'quarter':
const quarterAgo = new Date(now.getFullYear(), now.getMonth() - 3, now.getDate());
if (issueDate < quarterAgo) return false;
break;
case 'year':
const yearAgo = new Date(now.getFullYear() - 1, now.getMonth(), now.getDate());
if (issueDate < yearAgo) return false;
break;
}
}
if (searchInput) {
const searchText = `${issue.description} ${issue.reporter?.username || ''}`.toLowerCase();
if (!searchText.includes(searchInput)) return false;
}
return true;
});
sortIssues();
displayIssues();
}
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);
case 'completed':
return new Date(b.disposed_at || b.report_date) - new Date(a.disposed_at || a.report_date);
case 'category':
return (a.category || '').localeCompare(b.category || '');
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');
container.innerHTML = filteredIssues.map(issue => {
const project = projects.find(p => p.id === issue.project_id);
// 폐기함은 폐기된 것만 표시
const completedDate = issue.disposed_at ? new Date(issue.disposed_at).toLocaleDateString('ko-KR') : 'Invalid Date';
const statusText = '폐기';
const cardClass = 'archived-card';
return `
<div class="issue-card p-6 ${cardClass} cursor-pointer"
onclick="viewArchivedIssue(${issue.id})">
<div class="flex items-start justify-between">
<div class="flex-1">
<div class="flex items-center space-x-3 mb-2">
<span class="badge badge-${getStatusBadgeClass(issue.status)}">${getStatusText(issue.status)}</span>
${project ? `<span class="text-sm text-gray-500">${project.project_name}</span>` : ''}
<span class="text-sm text-gray-400">${completedDate}</span>
</div>
<h3 class="text-lg font-medium text-gray-900 mb-2">${issue.description}</h3>
<div class="flex items-center text-sm text-gray-500 space-x-4">
<span><i class="fas fa-user mr-1"></i>${issue.reporter?.username || '알 수 없음'}</span>
${issue.category ? `<span><i class="fas fa-tag mr-1"></i>${getCategoryText(issue.category)}</span>` : ''}
<span><i class="fas fa-clock mr-1"></i>${statusText}: ${completedDate}</span>
</div>
</div>
<div class="flex items-center space-x-2 ml-4">
<i class="fas fa-${getStatusIcon(issue.status)} text-2xl ${getStatusColor(issue.status)}"></i>
</div>
</div>
</div>
`;
}).join('');
}
// 통계 업데이트
function updateStatistics() {
const completed = issues.filter(issue => issue.status === 'completed').length;
const archived = issues.filter(issue => issue.status === 'archived').length;
const cancelled = issues.filter(issue => issue.status === 'cancelled').length;
const thisMonth = issues.filter(issue => {
const issueDate = new Date(issue.updated_at || issue.created_at);
const now = new Date();
return issueDate.getMonth() === now.getMonth() && issueDate.getFullYear() === now.getFullYear();
}).length;
document.getElementById('completedCount').textContent = completed;
document.getElementById('archivedCount').textContent = archived;
document.getElementById('cancelledCount').textContent = cancelled;
document.getElementById('thisMonthCount').textContent = thisMonth;
}
// 차트 렌더링 (간단한 텍스트 기반)
function renderCharts() {
renderMonthlyChart();
renderCategoryChart();
}
function renderMonthlyChart() {
const canvas = document.getElementById('monthlyChart');
const ctx = canvas.getContext('2d');
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
ctx.fillStyle = '#374151';
ctx.font = '16px Inter';
ctx.textAlign = 'center';
ctx.fillText('월별 완료 현황 차트', canvas.width / 2, canvas.height / 2);
ctx.font = '12px Inter';
ctx.fillText('(차트 라이브러리 구현 예정)', canvas.width / 2, canvas.height / 2 + 20);
}
function renderCategoryChart() {
const canvas = document.getElementById('categoryChart');
const ctx = canvas.getContext('2d');
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
ctx.fillStyle = '#374151';
ctx.font = '16px Inter';
ctx.textAlign = 'center';
ctx.fillText('카테고리별 분포 차트', canvas.width / 2, canvas.height / 2);
ctx.font = '12px Inter';
ctx.fillText('(차트 라이브러리 구현 예정)', canvas.width / 2, canvas.height / 2 + 20);
}
// 기타 함수들
function generateReport() {
alert('통계 보고서를 생성합니다.');
}
function cleanupArchive() {
if (confirm('오래된 보관 데이터를 정리하시겠습니까?')) {
alert('데이터 정리가 완료되었습니다.');
}
}
function viewArchivedIssue(issueId) {
window.location.href = `/issue-view.html#detail-${issueId}`;
}
// 유틸리티 함수들
function updateProjectFilter() {
const projectFilter = document.getElementById('projectFilter');
projectFilter.innerHTML = '<option value="">전체 프로젝트</option>';
projects.forEach(project => {
const option = document.createElement('option');
option.value = project.id;
option.textContent = project.project_name;
projectFilter.appendChild(option);
});
}
// 페이지 전용 유틸리티 (shared에 없는 것들)
function getStatusIcon(status) {
const iconMap = {
'completed': 'check-circle',
'archived': 'archive',
'cancelled': 'times-circle'
};
return iconMap[status] || 'archive';
}
function getStatusColor(status) {
const colorMap = {
'completed': 'text-green-500',
'archived': 'text-gray-500',
'cancelled': 'text-red-500'
};
return colorMap[status] || 'text-gray-500';
}
// API 스크립트 동적 로딩
const script = document.createElement('script');
script.src = '/static/js/api.js?v=20260213';
script.onload = function() {
console.log('API 스크립트 로드 완료 (issues-archive.html)');
initializeArchive();
};
script.onerror = function() {
console.error('API 스크립트 로드 실패');
};
document.head.appendChild(script);