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>
333 lines
12 KiB
JavaScript
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);
|