// 全域變數 let currentPage = 1; let currentView = 'list'; let issues = []; let projects = []; // 頁面載入完成後初始化 document.addEventListener('DOMContentLoaded', function() { loadProjects(); loadIssues(); loadStats(); }); // 載入專案列表 async function loadProjects() { try { const response = await fetch('/api/projects'); const result = await response.json(); if (result.success) { projects = result.data; updateProjectSelects(); } else { showAlert('載入專案失敗: ' + result.message, 'error'); } } catch (error) { console.error('載入專案錯誤:', error); showAlert('載入專案時發生錯誤', 'error'); } } // 更新專案選擇器 function updateProjectSelects() { const projectFilter = document.getElementById('projectFilter'); const issueProject = document.getElementById('issueProject'); // 清空現有選項 projectFilter.innerHTML = ''; issueProject.innerHTML = ''; // 添加專案選項 projects.forEach(project => { const option1 = new Option(project.name, project.id); const option2 = new Option(project.name, project.id); projectFilter.appendChild(option1); issueProject.appendChild(option2); }); } // 載入問題列表 async function loadIssues(page = 1) { currentPage = page; showLoading(true); try { const projectId = document.getElementById('projectFilter').value; const status = document.getElementById('statusFilter').value; const priority = document.getElementById('priorityFilter').value; let url = `/api/issues?page=${page}&limit=10`; if (projectId) url += `&project_id=${projectId}`; if (status) url += `&status=${status}`; if (priority) url += `&priority=${priority}`; const response = await fetch(url); const result = await response.json(); if (result.success) { issues = result.data; renderIssues(); renderPagination(result.pagination); updateStats(); } else { showAlert('載入問題失敗: ' + result.message, 'error'); } } catch (error) { console.error('載入問題錯誤:', error); showAlert('載入問題時發生錯誤', 'error'); } finally { showLoading(false); } } // 渲染問題列表 function renderIssues() { const issuesList = document.getElementById('issuesList'); if (issues.length === 0) { issuesList.innerHTML = `

沒有找到問題

目前沒有符合篩選條件的問題

`; return; } const issuesHtml = issues.map(issue => `
${escapeHtml(issue.title)}
#${issue.id} ${issue.project_name || '未分類'} ${issue.assignee || '未指派'} ${formatDate(issue.created_at)}
${getStatusText(issue.status)} ${getPriorityText(issue.priority)}
${issue.description ? `
${escapeHtml(issue.description)}
` : ''}
`).join(''); issuesList.innerHTML = issuesHtml; } // 渲染分頁 function renderPagination(pagination) { const paginationDiv = document.getElementById('pagination'); if (pagination.pages <= 1) { paginationDiv.innerHTML = ''; return; } let paginationHtml = ''; // 上一頁按鈕 paginationHtml += ` `; // 頁碼按鈕 const startPage = Math.max(1, pagination.page - 2); const endPage = Math.min(pagination.pages, pagination.page + 2); for (let i = startPage; i <= endPage; i++) { paginationHtml += ` `; } // 下一頁按鈕 paginationHtml += ` `; paginationDiv.innerHTML = paginationHtml; } // 載入統計資料 async function loadStats() { try { const response = await fetch('/api/issues'); const result = await response.json(); if (result.success) { const stats = { open: 0, in_progress: 0, closed: 0, total: result.data.length }; result.data.forEach(issue => { if (issue.status === 'open') stats.open++; else if (issue.status === 'in_progress') stats.in_progress++; else if (issue.status === 'closed') stats.closed++; }); updateStatsDisplay(stats); } } catch (error) { console.error('載入統計錯誤:', error); } } // 更新統計顯示 function updateStatsDisplay(stats) { document.getElementById('openCount').textContent = stats.open; document.getElementById('progressCount').textContent = stats.in_progress; document.getElementById('closedCount').textContent = stats.closed; document.getElementById('totalCount').textContent = stats.total; } // 更新統計(從當前問題列表) function updateStats() { const stats = { open: 0, in_progress: 0, closed: 0, total: issues.length }; issues.forEach(issue => { if (issue.status === 'open') stats.open++; else if (issue.status === 'in_progress') stats.in_progress++; else if (issue.status === 'closed') stats.closed++; }); updateStatsDisplay(stats); } // 顯示問題詳情 async function showIssueDetail(issueId) { try { const response = await fetch(`/api/issues/${issueId}`); const result = await response.json(); if (result.success) { const issue = result.data; const modal = document.getElementById('issueDetailModal'); const title = document.getElementById('issueDetailTitle'); const content = document.getElementById('issueDetailContent'); title.textContent = `#${issue.id} ${issue.title}`; content.innerHTML = `
${issue.project_name || '未分類'} ${issue.assignee || '未指派'} ${formatDate(issue.created_at)}
${getStatusText(issue.status)} ${getPriorityText(issue.priority)}

描述

${issue.description || '無描述'}

${issue.comments && issue.comments.length > 0 ? `

評論 (${issue.comments.length})

${issue.comments.map(comment => `
${escapeHtml(comment.author)} ${formatDate(comment.created_at)}
${escapeHtml(comment.content)}
`).join('')}
` : ''}
`; showModal('issueDetailModal'); } else { showAlert('載入問題詳情失敗: ' + result.message, 'error'); } } catch (error) { console.error('載入問題詳情錯誤:', error); showAlert('載入問題詳情時發生錯誤', 'error'); } } // 顯示新增問題模態框 function showCreateIssueModal() { showModal('createIssueModal'); } // 顯示新增專案模態框 function showCreateProjectModal() { showModal('createProjectModal'); } // 建立問題 async function createIssue() { const form = document.getElementById('createIssueForm'); const formData = new FormData(form); const issueData = { title: formData.get('title'), description: formData.get('description'), project_id: formData.get('project_id') || null, priority: formData.get('priority'), assignee: formData.get('assignee'), reporter: formData.get('reporter') }; if (!issueData.title) { showAlert('請填寫問題標題', 'error'); return; } try { const response = await fetch('/api/issues', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(issueData) }); const result = await response.json(); if (result.success) { showAlert('問題建立成功!', 'success'); closeModal('createIssueModal'); form.reset(); loadIssues(); loadStats(); } else { showAlert('建立問題失敗: ' + result.message, 'error'); } } catch (error) { console.error('建立問題錯誤:', error); showAlert('建立問題時發生錯誤', 'error'); } } // 建立專案 async function createProject() { const form = document.getElementById('createProjectForm'); const formData = new FormData(form); const projectData = { name: formData.get('name'), description: formData.get('description'), status: formData.get('status') }; if (!projectData.name) { showAlert('請填寫專案名稱', 'error'); return; } try { const response = await fetch('/api/projects', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(projectData) }); const result = await response.json(); if (result.success) { showAlert('專案建立成功!', 'success'); closeModal('createProjectModal'); form.reset(); loadProjects(); } else { showAlert('建立專案失敗: ' + result.message, 'error'); } } catch (error) { console.error('建立專案錯誤:', error); showAlert('建立專案時發生錯誤', 'error'); } } // 切換檢視模式 function toggleView(view, el) { currentView = view; // 更新按鈕 active 樣式 const buttons = document.querySelectorAll('.view-toggle .btn'); buttons.forEach(btn => btn.classList.remove('active')); el.classList.add('active'); // 切換容器樣式 const issuesList = document.getElementById('issuesList'); if (view === 'grid') { issuesList.classList.remove('issues-list'); issuesList.classList.add('issues-grid'); } else { issuesList.classList.remove('issues-grid'); issuesList.classList.add('issues-list'); } renderIssues(); } // 顯示模態框 function showModal(modalId) { const modal = document.getElementById(modalId); modal.classList.add('show'); modal.style.display = 'flex'; } // 關閉模態框 function closeModal(modalId) { const modal = document.getElementById(modalId); modal.classList.remove('show'); modal.style.display = 'none'; } // 顯示載入指示器 function showLoading(show) { const loading = document.getElementById('loadingIndicator'); loading.style.display = show ? 'block' : 'none'; } // 顯示警告訊息 function showAlert(message, type = 'info') { // 移除現有的警告 const existingAlert = document.querySelector('.alert'); if (existingAlert) { existingAlert.remove(); } const alert = document.createElement('div'); alert.className = `alert alert-${type}`; alert.textContent = message; document.querySelector('.container').insertBefore(alert, document.querySelector('.stats-grid')); // 3秒後自動移除 setTimeout(() => { if (alert.parentNode) { alert.remove(); } }, 3000); } // 工具函數 function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } function formatDate(dateString) { const date = new Date(dateString); return date.toLocaleDateString('zh-TW', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }); } function getStatusText(status) { const statusMap = { 'open': '待處理', 'in_progress': '處理中', 'closed': '已完成' }; return statusMap[status] || status; } function getPriorityText(priority) { const priorityMap = { 'high': '高', 'medium': '中', 'low': '低' }; return priorityMap[priority] || priority; } // 點擊模態框外部關閉 window.onclick = function(event) { const modals = document.querySelectorAll('.modal'); modals.forEach(modal => { if (event.target === modal) { modal.classList.remove('show'); modal.style.display = 'none'; } }); }