feat: 부적합 현황판 페이지 신규 추가 - 프로젝트별 진행 현황 대시보드
📊 Dashboard Overview Page: - 진행 중인 부적합들을 프로젝트별로 한눈에 볼 수 있는 현황판 - 실시간 통계 및 시각적 대시보드 구현 - 프로젝트 선택 및 다양한 정렬 옵션 제공 🎯 Key Features: - 전체 통계 카드 (전체 진행 중, 오늘 신규, 지연 위험, 활성 프로젝트) - 프로젝트별 그룹화된 이슈 카드 표시 - 긴급도 기반 우선순위 표시 (마감일 3일 이내) - 프로젝트 필터링 및 정렬 기능 🎨 Visual Design: - 그라데이션 통계 카드 with 호버 애니메이션 - 프로젝트 카드 with 좌측 테두리 호버 효과 - 이슈 미니 카드 with 긴급도 색상 구분 - 반응형 그리드 레이아웃 📋 Dashboard Components: - 4개 통계 카드: 진행 중, 신규, 지연 위험, 활성 프로젝트 - 프로젝트 선택 드롭다운 - 정렬 옵션: 우선순위, 신고일순, 마감일순 - 프로젝트별 이슈 그룹화 표시 🔧 Technical Implementation: - issues_dashboard 페이지 권한 추가 - 진행 중 상태(in_progress) 이슈만 필터링 - 긴급도 계산 로직 (마감일 기준) - 프로젝트별 그룹화 및 통계 계산 - 공통 헤더 및 권한 시스템 적용 🚀 Interactive Features: - 이슈 카드 클릭 → 관리함 상세보기 이동 - 실시간 새로고침 기능 - 프로젝트별 필터링 - 우선순위/날짜/마감일 기준 정렬 💡 User Experience: - 로딩 애니메이션 및 페이드인 효과 - 빈 상태 메시지 - 긴급 이슈 시각적 강조 - 직관적인 네비게이션 Expected Result: ✅ 진행 중인 부적합 현황을 한눈에 파악 ✅ 프로젝트별 작업 우선순위 확인 ✅ 지연 위험 이슈 조기 발견 ✅ 효율적인 부적합 관리 워크플로우
This commit is contained in:
Binary file not shown.
@@ -48,6 +48,7 @@ DEFAULT_PAGES = {
|
||||
'issues_inbox': {'title': '수신함', 'default_access': True},
|
||||
'issues_management': {'title': '관리함', 'default_access': False},
|
||||
'issues_archive': {'title': '폐기함', 'default_access': False},
|
||||
'issues_dashboard': {'title': '현황판', 'default_access': False},
|
||||
'projects_manage': {'title': '프로젝트 관리', 'default_access': False},
|
||||
'daily_work': {'title': '일일 공수', 'default_access': False},
|
||||
'reports': {'title': '보고서', 'default_access': False},
|
||||
|
||||
576
frontend/issues-dashboard.html
Normal file
576
frontend/issues-dashboard.html
Normal file
@@ -0,0 +1,576 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>부적합 현황판</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<style>
|
||||
.fade-in { opacity: 0; animation: fadeIn 0.5s ease-in forwards; }
|
||||
@keyframes fadeIn { to { opacity: 1; } }
|
||||
|
||||
.header-fade-in { opacity: 0; animation: headerFadeIn 0.6s ease-out forwards; }
|
||||
@keyframes headerFadeIn { to { opacity: 1; transform: translateY(0); } from { transform: translateY(-10px); } }
|
||||
|
||||
.content-fade-in { opacity: 0; animation: contentFadeIn 0.7s ease-out 0.2s forwards; }
|
||||
@keyframes contentFadeIn { to { opacity: 1; transform: translateY(0); } from { transform: translateY(20px); } }
|
||||
|
||||
/* 대시보드 카드 스타일 */
|
||||
.dashboard-card {
|
||||
transition: all 0.3s ease;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
|
||||
.dashboard-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.project-card {
|
||||
transition: all 0.2s ease;
|
||||
border-left: 4px solid transparent;
|
||||
}
|
||||
|
||||
.project-card:hover {
|
||||
transform: translateX(5px);
|
||||
border-left-color: #3b82f6;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.issue-mini-card {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.issue-mini-card:hover {
|
||||
transform: scale(1.02);
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
background: linear-gradient(90deg, #10b981 0%, #059669 100%);
|
||||
transition: width 0.8s ease;
|
||||
}
|
||||
|
||||
/* 반응형 그리드 */
|
||||
.dashboard-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-50 min-h-screen">
|
||||
<!-- 로딩 스크린 -->
|
||||
<div id="loadingScreen" class="fixed inset-0 bg-white z-50 flex items-center justify-center">
|
||||
<div class="text-center">
|
||||
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
|
||||
<p class="text-gray-600">현황판을 불러오는 중...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 로그인 스크린 -->
|
||||
<div id="loginScreen" class="hidden fixed inset-0 bg-gray-100 z-40 flex items-center justify-center">
|
||||
<div class="bg-white p-8 rounded-xl shadow-lg max-w-md w-full mx-4">
|
||||
<h2 class="text-2xl font-bold text-center mb-6 text-gray-800">로그인</h2>
|
||||
<form id="loginForm">
|
||||
<div class="mb-4">
|
||||
<input type="text" id="username" placeholder="사용자명" required
|
||||
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||
</div>
|
||||
<div class="mb-6">
|
||||
<input type="password" id="password" placeholder="비밀번호" required
|
||||
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||
</div>
|
||||
<button type="submit" class="w-full bg-blue-600 text-white py-3 rounded-lg hover:bg-blue-700 transition-colors">
|
||||
로그인
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 메인 콘텐츠 -->
|
||||
<div id="mainContent" class="min-h-screen">
|
||||
<!-- 공통 헤더 -->
|
||||
<div id="commonHeader"></div>
|
||||
|
||||
<!-- 메인 콘텐츠 -->
|
||||
<main class="container mx-auto px-4 py-8 content-fade-in" style="padding-top: 80px;">
|
||||
<!-- 페이지 헤더 -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-6 mb-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900 flex items-center">
|
||||
<i class="fas fa-chart-line text-blue-500 mr-3"></i>
|
||||
부적합 현황판
|
||||
</h1>
|
||||
<p class="text-gray-600 mt-1">진행 중인 부적합 사항을 프로젝트별로 한눈에 확인하세요</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 전체 통계 대시보드 -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
|
||||
<div class="dashboard-card text-white p-6 rounded-xl">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-blue-100 text-sm">전체 진행 중</p>
|
||||
<p class="text-3xl font-bold" id="totalInProgress">0</p>
|
||||
</div>
|
||||
<i class="fas fa-tasks text-4xl text-blue-200"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-gradient-to-br from-green-400 to-green-600 text-white p-6 rounded-xl dashboard-card">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-green-100 text-sm">오늘 신규</p>
|
||||
<p class="text-3xl font-bold" id="todayNew">0</p>
|
||||
</div>
|
||||
<i class="fas fa-plus-circle text-4xl text-green-200"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-gradient-to-br from-yellow-400 to-orange-500 text-white p-6 rounded-xl dashboard-card">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-yellow-100 text-sm">지연 위험</p>
|
||||
<p class="text-3xl font-bold" id="delayRisk">0</p>
|
||||
</div>
|
||||
<i class="fas fa-exclamation-triangle text-4xl text-yellow-200"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-gradient-to-br from-purple-400 to-purple-600 text-white p-6 rounded-xl dashboard-card">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-purple-100 text-sm">활성 프로젝트</p>
|
||||
<p class="text-3xl font-bold" id="activeProjects">0</p>
|
||||
</div>
|
||||
<i class="fas fa-project-diagram text-4xl text-purple-200"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 프로젝트 선택 및 필터 -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-6 mb-6">
|
||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between space-y-4 md:space-y-0">
|
||||
<div class="flex items-center space-x-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">프로젝트 선택</label>
|
||||
<select id="projectFilter" class="px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 min-w-[200px]" onchange="filterByProject()">
|
||||
<option value="">전체 프로젝트</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">정렬 기준</label>
|
||||
<select id="sortOrder" class="px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500" onchange="sortIssues()">
|
||||
<option value="priority">우선순위</option>
|
||||
<option value="date">신고일순</option>
|
||||
<option value="deadline">마감일순</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<button onclick="refreshDashboard()" class="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors">
|
||||
<i class="fas fa-sync-alt mr-2"></i>새로고침
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 프로젝트별 현황 -->
|
||||
<div id="projectDashboard" class="space-y-6">
|
||||
<!-- 동적으로 생성될 프로젝트 카드들 -->
|
||||
</div>
|
||||
|
||||
<!-- 빈 상태 -->
|
||||
<div id="emptyState" class="hidden text-center py-12">
|
||||
<i class="fas fa-chart-line text-6xl text-gray-300 mb-4"></i>
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-2">진행 중인 부적합이 없습니다</h3>
|
||||
<p class="text-gray-500">새로운 부적합이 등록되면 여기에 표시됩니다.</p>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- 스크립트 -->
|
||||
<script src="/static/js/permissions.js?v=1.1"></script>
|
||||
<script src="/static/js/common-header.js?v=1.1"></script>
|
||||
<script src="/static/js/page-manager.js?v=1.1"></script>
|
||||
<script src="/static/js/auth-manager.js?v=1.1"></script>
|
||||
|
||||
<script>
|
||||
let currentUser = null;
|
||||
let allIssues = [];
|
||||
let projects = [];
|
||||
let filteredIssues = [];
|
||||
|
||||
// 애니메이션 함수들
|
||||
function animateHeaderAppearance() {
|
||||
const header = document.getElementById('commonHeader');
|
||||
if (header) {
|
||||
header.classList.add('header-fade-in');
|
||||
}
|
||||
}
|
||||
|
||||
function animateContentAppearance() {
|
||||
const content = document.querySelector('.content-fade-in');
|
||||
if (content) {
|
||||
content.classList.add('visible');
|
||||
}
|
||||
}
|
||||
|
||||
// 페이지 초기화
|
||||
async function initializeDashboard() {
|
||||
try {
|
||||
// 인증 확인
|
||||
currentUser = await window.authManager.checkAuth();
|
||||
if (!currentUser) {
|
||||
showLoginScreen();
|
||||
return;
|
||||
}
|
||||
|
||||
// 페이지 권한 확인
|
||||
if (!window.pagePermissionManager) {
|
||||
window.pagePermissionManager = new PagePermissionManager(currentUser);
|
||||
}
|
||||
|
||||
await window.pagePermissionManager.loadPermissions();
|
||||
|
||||
if (!window.pagePermissionManager.canAccessPage('issues_dashboard')) {
|
||||
alert('현황판 접근 권한이 없습니다.');
|
||||
window.location.href = '/';
|
||||
return;
|
||||
}
|
||||
|
||||
// 공통 헤더 초기화
|
||||
if (window.commonHeader) {
|
||||
await window.commonHeader.init(currentUser, 'issues_dashboard');
|
||||
setTimeout(() => animateHeaderAppearance(), 100);
|
||||
}
|
||||
|
||||
// 데이터 로드
|
||||
await Promise.all([
|
||||
loadProjects(),
|
||||
loadInProgressIssues()
|
||||
]);
|
||||
|
||||
updateDashboard();
|
||||
hideLoadingScreen();
|
||||
|
||||
} catch (error) {
|
||||
console.error('대시보드 초기화 실패:', error);
|
||||
alert('대시보드를 불러오는데 실패했습니다.');
|
||||
hideLoadingScreen();
|
||||
}
|
||||
}
|
||||
|
||||
// 로딩 스크린 관리
|
||||
function hideLoadingScreen() {
|
||||
document.getElementById('loadingScreen').style.display = 'none';
|
||||
}
|
||||
|
||||
function showLoginScreen() {
|
||||
document.getElementById('loadingScreen').style.display = 'none';
|
||||
document.getElementById('loginScreen').classList.remove('hidden');
|
||||
}
|
||||
|
||||
// 데이터 로드 함수들
|
||||
async function loadProjects() {
|
||||
try {
|
||||
const response = await fetch('/api/projects/', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem('access_token')}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
projects = await response.json();
|
||||
updateProjectFilter();
|
||||
} else {
|
||||
throw new Error('프로젝트 목록을 불러올 수 없습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('프로젝트 로드 실패:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadInProgressIssues() {
|
||||
try {
|
||||
const response = await fetch('/api/issues/admin/all', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem('access_token')}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const allData = await response.json();
|
||||
// 진행 중 상태만 필터링
|
||||
allIssues = allData.filter(issue => issue.review_status === 'in_progress');
|
||||
filteredIssues = [...allIssues];
|
||||
} else {
|
||||
throw new Error('부적합 목록을 불러올 수 없습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('부적합 로드 실패:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 프로젝트 필터 업데이트
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
// 대시보드 업데이트
|
||||
function updateDashboard() {
|
||||
updateStatistics();
|
||||
updateProjectCards();
|
||||
}
|
||||
|
||||
// 통계 업데이트
|
||||
function updateStatistics() {
|
||||
const today = new Date().toDateString();
|
||||
const todayIssues = allIssues.filter(issue =>
|
||||
new Date(issue.report_date).toDateString() === today
|
||||
);
|
||||
|
||||
// 지연 위험 계산 (예상일이 지났거나 3일 이내)
|
||||
const delayRiskIssues = allIssues.filter(issue => {
|
||||
if (!issue.expected_completion_date) return false;
|
||||
const expectedDate = new Date(issue.expected_completion_date);
|
||||
const now = new Date();
|
||||
const diffDays = (expectedDate - now) / (1000 * 60 * 60 * 24);
|
||||
return diffDays <= 3; // 3일 이내 또는 지연
|
||||
});
|
||||
|
||||
// 활성 프로젝트 (진행 중인 부적합이 있는 프로젝트)
|
||||
const activeProjectIds = new Set(allIssues.map(issue => issue.project_id));
|
||||
|
||||
document.getElementById('totalInProgress').textContent = allIssues.length;
|
||||
document.getElementById('todayNew').textContent = todayIssues.length;
|
||||
document.getElementById('delayRisk').textContent = delayRiskIssues.length;
|
||||
document.getElementById('activeProjects').textContent = activeProjectIds.size;
|
||||
}
|
||||
|
||||
// 프로젝트 카드 업데이트
|
||||
function updateProjectCards() {
|
||||
const container = document.getElementById('projectDashboard');
|
||||
const emptyState = document.getElementById('emptyState');
|
||||
|
||||
if (filteredIssues.length === 0) {
|
||||
container.innerHTML = '';
|
||||
emptyState.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
emptyState.classList.add('hidden');
|
||||
|
||||
// 프로젝트별 그룹화
|
||||
const projectGroups = {};
|
||||
filteredIssues.forEach(issue => {
|
||||
const projectId = issue.project_id || 'unassigned';
|
||||
if (!projectGroups[projectId]) {
|
||||
projectGroups[projectId] = [];
|
||||
}
|
||||
projectGroups[projectId].push(issue);
|
||||
});
|
||||
|
||||
// 프로젝트 카드 생성
|
||||
const projectCards = Object.keys(projectGroups).map(projectId => {
|
||||
const issues = projectGroups[projectId];
|
||||
const project = projects.find(p => p.id == projectId);
|
||||
const projectName = project ? project.project_name : '프로젝트 미지정';
|
||||
|
||||
return createProjectCard(projectName, issues);
|
||||
}).join('');
|
||||
|
||||
container.innerHTML = projectCards;
|
||||
}
|
||||
|
||||
// 프로젝트 카드 생성
|
||||
function createProjectCard(projectName, issues) {
|
||||
const urgentCount = issues.filter(issue => {
|
||||
if (!issue.expected_completion_date) return false;
|
||||
const expectedDate = new Date(issue.expected_completion_date);
|
||||
const now = new Date();
|
||||
const diffDays = (expectedDate - now) / (1000 * 60 * 60 * 24);
|
||||
return diffDays <= 3;
|
||||
}).length;
|
||||
|
||||
return `
|
||||
<div class="project-card bg-white rounded-xl shadow-sm p-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-900">${projectName}</h3>
|
||||
<div class="flex items-center space-x-2">
|
||||
<span class="bg-blue-100 text-blue-800 px-3 py-1 rounded-full text-sm font-medium">
|
||||
${issues.length}건
|
||||
</span>
|
||||
${urgentCount > 0 ? `<span class="bg-red-100 text-red-800 px-3 py-1 rounded-full text-sm font-medium">
|
||||
긴급 ${urgentCount}건
|
||||
</span>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-grid">
|
||||
${issues.map(issue => createIssueMiniCard(issue)).join('')}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// 이슈 미니 카드 생성
|
||||
function createIssueMiniCard(issue) {
|
||||
const isUrgent = issue.expected_completion_date &&
|
||||
(new Date(issue.expected_completion_date) - new Date()) / (1000 * 60 * 60 * 24) <= 3;
|
||||
|
||||
const urgentClass = isUrgent ? 'border-red-200 bg-red-50' : 'border-gray-200 bg-white';
|
||||
|
||||
return `
|
||||
<div class="issue-mini-card ${urgentClass} border rounded-lg p-4 cursor-pointer"
|
||||
onclick="viewIssueDetail(${issue.id})">
|
||||
<div class="flex items-start justify-between mb-2">
|
||||
<span class="bg-blue-100 text-blue-800 px-2 py-1 rounded text-xs font-medium">
|
||||
No.${issue.project_sequence_no || '-'}
|
||||
</span>
|
||||
${isUrgent ? '<i class="fas fa-exclamation-triangle text-red-500"></i>' : ''}
|
||||
</div>
|
||||
|
||||
<p class="text-sm text-gray-800 mb-2 line-clamp-2">
|
||||
${issue.final_description || issue.description}
|
||||
</p>
|
||||
|
||||
<div class="flex items-center justify-between text-xs text-gray-500">
|
||||
<span>${new Date(issue.report_date).toLocaleDateString('ko-KR')}</span>
|
||||
${issue.expected_completion_date ?
|
||||
`<span class="${isUrgent ? 'text-red-600 font-medium' : ''}">
|
||||
마감: ${new Date(issue.expected_completion_date).toLocaleDateString('ko-KR')}
|
||||
</span>` :
|
||||
'<span>마감일 미정</span>'
|
||||
}
|
||||
</div>
|
||||
|
||||
${issue.responsible_department ?
|
||||
`<div class="mt-2">
|
||||
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs bg-gray-100 text-gray-800">
|
||||
${getDepartmentText(issue.responsible_department)}
|
||||
</span>
|
||||
</div>` : ''
|
||||
}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// 필터 및 정렬 함수들
|
||||
function filterByProject() {
|
||||
const projectId = document.getElementById('projectFilter').value;
|
||||
|
||||
if (projectId) {
|
||||
filteredIssues = allIssues.filter(issue => issue.project_id == projectId);
|
||||
} else {
|
||||
filteredIssues = [...allIssues];
|
||||
}
|
||||
|
||||
sortIssues();
|
||||
updateProjectCards();
|
||||
}
|
||||
|
||||
function sortIssues() {
|
||||
const sortOrder = document.getElementById('sortOrder').value;
|
||||
|
||||
filteredIssues.sort((a, b) => {
|
||||
switch (sortOrder) {
|
||||
case 'priority':
|
||||
// 긴급도 우선 (마감일이 가까운 순)
|
||||
const aUrgent = a.expected_completion_date ?
|
||||
(new Date(a.expected_completion_date) - new Date()) / (1000 * 60 * 60 * 24) : 999;
|
||||
const bUrgent = b.expected_completion_date ?
|
||||
(new Date(b.expected_completion_date) - new Date()) / (1000 * 60 * 60 * 24) : 999;
|
||||
return aUrgent - bUrgent;
|
||||
case 'date':
|
||||
return new Date(b.report_date) - new Date(a.report_date);
|
||||
case 'deadline':
|
||||
if (!a.expected_completion_date && !b.expected_completion_date) return 0;
|
||||
if (!a.expected_completion_date) return 1;
|
||||
if (!b.expected_completion_date) return -1;
|
||||
return new Date(a.expected_completion_date) - new Date(b.expected_completion_date);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 유틸리티 함수들
|
||||
function getDepartmentText(department) {
|
||||
const departments = {
|
||||
'production': '생산',
|
||||
'quality': '품질',
|
||||
'purchasing': '구매',
|
||||
'design': '설계',
|
||||
'sales': '영업'
|
||||
};
|
||||
return departments[department] || department;
|
||||
}
|
||||
|
||||
function viewIssueDetail(issueId) {
|
||||
window.location.href = `/issues-management.html#issue-${issueId}`;
|
||||
}
|
||||
|
||||
function refreshDashboard() {
|
||||
location.reload();
|
||||
}
|
||||
|
||||
// 로그인 처리
|
||||
document.getElementById('loginForm').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const username = document.getElementById('username').value;
|
||||
const password = document.getElementById('password').value;
|
||||
|
||||
try {
|
||||
const user = await window.authManager.login(username, password);
|
||||
if (user) {
|
||||
document.getElementById('loginScreen').classList.add('hidden');
|
||||
await initializeDashboard();
|
||||
}
|
||||
} catch (error) {
|
||||
alert('로그인에 실패했습니다.');
|
||||
}
|
||||
});
|
||||
|
||||
// 페이지 로드 시 초기화
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// AuthManager 로드 대기
|
||||
const checkAuthManager = () => {
|
||||
if (window.authManager) {
|
||||
initializeDashboard();
|
||||
} else {
|
||||
setTimeout(checkAuthManager, 100);
|
||||
}
|
||||
};
|
||||
checkAuthManager();
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- API 스크립트 동적 로드 -->
|
||||
<script>
|
||||
function initializeDashboardApp() {
|
||||
console.log('✅ API 스크립트 로드 완료 (issues-dashboard.html)');
|
||||
}
|
||||
|
||||
// API 스크립트 동적 로드
|
||||
const script = document.createElement('script');
|
||||
script.src = '/static/js/api.js?v=' + Date.now();
|
||||
script.onload = initializeDashboardApp;
|
||||
document.body.appendChild(script);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user