diff --git a/app/api/history/route.ts b/app/api/history/route.ts new file mode 100644 index 0000000..557facc --- /dev/null +++ b/app/api/history/route.ts @@ -0,0 +1,99 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { ProjectService, EvaluationService, ProjectFileService } from '@/lib/services/database'; + +export async function GET(request: NextRequest) { + try { + console.log('📊 開始獲取歷史記錄數據...'); + + // 獲取所有專案及其評審結果 + const projects = await ProjectService.getAll(); + console.log(`📋 找到 ${projects.length} 個專案`); + + const historyData = []; + + for (const project of projects) { + // 獲取該專案的最新評審結果 + const latestEvaluation = await EvaluationService.findByProjectId(project.id); + + // 獲取該專案的文件信息 + const projectFiles = await ProjectFileService.findByProjectId(project.id); + + // 判斷文件類型 + let fileType = 'Unknown'; + if (projectFiles.length > 0) { + const fileTypeFromDB = projectFiles[0].file_type; + if (fileTypeFromDB) { + // 根據 file_type 判斷文件類型 + if (fileTypeFromDB.toLowerCase().includes('ppt') || fileTypeFromDB.toLowerCase().includes('powerpoint')) { + fileType = 'PPT'; + } else if (fileTypeFromDB.toLowerCase().includes('mp4') || fileTypeFromDB.toLowerCase().includes('avi') || fileTypeFromDB.toLowerCase().includes('mov')) { + fileType = 'Video'; + } else if (fileTypeFromDB.toLowerCase().includes('pdf')) { + fileType = 'PDF'; + } else if (fileTypeFromDB.toLowerCase().includes('html') || fileTypeFromDB.toLowerCase().includes('htm')) { + fileType = 'Website'; + } else { + // 如果無法從 file_type 判斷,嘗試從檔名判斷 + const fileName = projectFiles[0].original_name.toLowerCase(); + if (fileName.includes('.ppt') || fileName.includes('.pptx')) { + fileType = 'PPT'; + } else if (fileName.includes('.mp4') || fileName.includes('.avi') || fileName.includes('.mov')) { + fileType = 'Video'; + } else if (fileName.includes('.pdf')) { + fileType = 'PDF'; + } else if (fileName.includes('.html') || fileName.includes('.htm')) { + fileType = 'Website'; + } else { + fileType = fileTypeFromDB.toUpperCase(); + } + } + } + } else { + // 如果沒有文件記錄,嘗試從專案標題判斷 + if (project.title.includes('PPT') || project.title.includes('簡報')) { + fileType = 'PPT'; + } else if (project.title.includes('網站') || project.title.includes('Website')) { + fileType = 'Website'; + } else if (project.title.includes('影片') || project.title.includes('Video')) { + fileType = 'Video'; + } + } + + // 格式化日期 + const formatDate = (date: Date) => { + return new Date(date).toISOString().split('T')[0]; + }; + + historyData.push({ + id: project.id.toString(), + title: project.title, + type: fileType, + score: latestEvaluation?.overall_score || 0, + grade: latestEvaluation?.grade || '-', + date: formatDate(project.created_at), + status: project.status === 'completed' ? 'completed' : 'processing', + evaluation_id: latestEvaluation?.id || null, + description: project.description || '', + created_at: project.created_at, + updated_at: project.updated_at + }); + } + + // 按創建時間倒序排列 + historyData.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()); + + console.log(`✅ 成功獲取 ${historyData.length} 條歷史記錄`); + + return NextResponse.json({ + success: true, + data: historyData + }); + + } catch (error) { + console.error('❌ 獲取歷史記錄失敗:', error); + return NextResponse.json( + { success: false, error: '獲取歷史記錄失敗' }, + { status: 500 } + ); + } +} diff --git a/app/api/history/stats/route.ts b/app/api/history/stats/route.ts new file mode 100644 index 0000000..3a57603 --- /dev/null +++ b/app/api/history/stats/route.ts @@ -0,0 +1,56 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { ProjectService, EvaluationService } from '@/lib/services/database'; + +export async function GET(request: NextRequest) { + try { + console.log('📊 開始獲取統計數據...'); + + // 獲取所有專案 + const projects = await ProjectService.getAll(); + console.log(`📋 找到 ${projects.length} 個專案`); + + // 計算統計數據 + const totalProjects = projects.length; + const completedProjects = projects.filter(p => p.status === 'completed').length; + const processingProjects = projects.filter(p => p.status === 'analyzing' || p.status === 'pending').length; + + // 計算平均分數 + let totalScore = 0; + let scoredProjects = 0; + + for (const project of projects) { + if (project.status === 'completed') { + const evaluation = await EvaluationService.findByProjectId(project.id); + console.log(`專案 ${project.id} (${project.title}): 狀態=${project.status}, 評審=${evaluation ? '有' : '無'}, 分數=${evaluation?.overall_score || '無'}`); + if (evaluation && evaluation.overall_score) { + totalScore += evaluation.overall_score; + scoredProjects++; + } + } + } + + const averageScore = scoredProjects > 0 ? Math.round(totalScore / scoredProjects) : 0; + console.log(`平均分數計算: 總分=${totalScore}, 有分數的專案數=${scoredProjects}, 平均分數=${averageScore}`); + + const stats = { + totalProjects, + completedProjects, + processingProjects, + averageScore + }; + + console.log('✅ 統計數據:', stats); + + return NextResponse.json({ + success: true, + data: stats + }); + + } catch (error) { + console.error('❌ 獲取統計數據失敗:', error); + return NextResponse.json( + { success: false, error: '獲取統計數據失敗' }, + { status: 500 } + ); + } +} diff --git a/app/history/page.tsx b/app/history/page.tsx index f6a07db..474520d 100644 --- a/app/history/page.tsx +++ b/app/history/page.tsx @@ -1,95 +1,115 @@ "use client" -import { useState } from "react" +import { useState, useEffect } from "react" import { Sidebar } from "@/components/sidebar" import { Button } from "@/components/ui/button" import { Card, CardContent } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" import { Input } from "@/components/ui/input" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" -import { FileText, Calendar, Search, Eye, Download, Trash2 } from "lucide-react" +import { FileText, Calendar, Search, Eye, Download, Trash2, Loader2 } from "lucide-react" import Link from "next/link" -// 模擬歷史記錄數據 -const mockHistory = [ - { - id: "1", - title: "產品介紹簡報", - type: "PPT", - score: 82, - grade: "B+", - date: "2024-01-15", - status: "completed", - }, - { - id: "2", - title: "市場分析報告", - type: "Website", - score: 76, - grade: "B", - date: "2024-01-12", - status: "completed", - }, - { - id: "3", - title: "產品演示影片", - type: "Video", - score: 88, - grade: "A-", - date: "2024-01-10", - status: "completed", - }, - { - id: "4", - title: "技術架構說明", - type: "PPT", - score: 0, - grade: "-", - date: "2024-01-08", - status: "processing", - }, - { - id: "5", - title: "用戶體驗設計", - type: "Website", - score: 91, - grade: "A", - date: "2024-01-05", - status: "completed", - }, -] +// 歷史記錄數據類型 +interface HistoryItem { + id: string; + title: string; + type: string; + score: number; + grade: string; + date: string; + status: 'completed' | 'processing'; + evaluation_id?: number; + description?: string; + created_at: string; + updated_at: string; +} + +// 統計數據類型 +interface StatsData { + totalProjects: number; + completedProjects: number; + processingProjects: number; + averageScore: number; +} export default function HistoryPage() { const [searchTerm, setSearchTerm] = useState("") const [filterType, setFilterType] = useState("all") const [filterStatus, setFilterStatus] = useState("all") - - const filteredHistory = mockHistory.filter((item) => { - const matchesSearch = item.title.toLowerCase().includes(searchTerm.toLowerCase()) - const matchesType = filterType === "all" || item.type === filterType - const matchesStatus = filterStatus === "all" || item.status === filterStatus - return matchesSearch && matchesType && matchesStatus + const [historyData, setHistoryData] = useState([]) + const [statsData, setStatsData] = useState({ + totalProjects: 0, + completedProjects: 0, + processingProjects: 0, + averageScore: 0 }) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + + // 載入歷史記錄數據 + useEffect(() => { + const loadHistoryData = async () => { + try { + setLoading(true); + setError(null); + + // 並行獲取歷史記錄和統計數據 + const [historyResponse, statsResponse] = await Promise.all([ + fetch('/api/history'), + fetch('/api/history/stats') + ]); + + if (!historyResponse.ok || !statsResponse.ok) { + throw new Error('獲取數據失敗'); + } + + const historyResult = await historyResponse.json(); + const statsResult = await statsResponse.json(); + + if (historyResult.success && statsResult.success) { + setHistoryData(historyResult.data); + setStatsData(statsResult.data); + } else { + throw new Error(historyResult.error || statsResult.error || '數據載入失敗'); + } + } catch (err) { + console.error('載入歷史記錄失敗:', err); + setError(err instanceof Error ? err.message : '載入數據時發生錯誤'); + } finally { + setLoading(false); + } + }; + + loadHistoryData(); + }, []); + + const filteredHistory = historyData.filter((item) => { + const matchesSearch = item.title.toLowerCase().includes(searchTerm.toLowerCase()); + const matchesType = filterType === "all" || item.type === filterType; + const matchesStatus = filterStatus === "all" || item.status === filterStatus; + return matchesSearch && matchesType && matchesStatus; + }); const getGradeColor = (grade: string) => { - if (grade.startsWith("A")) return "bg-green-100 text-green-800" - if (grade.startsWith("B")) return "bg-blue-100 text-blue-800" - if (grade.startsWith("C")) return "bg-yellow-100 text-yellow-800" - return "bg-gray-100 text-gray-800" - } + if (grade.startsWith("A")) return "bg-green-100 text-green-800"; + if (grade.startsWith("B")) return "bg-blue-100 text-blue-800"; + if (grade.startsWith("C")) return "bg-yellow-100 text-yellow-800"; + return "bg-gray-100 text-gray-800"; + }; const getTypeIcon = (type: string) => { switch (type) { case "PPT": - return + return ; case "Video": - return + return ; case "Website": - return + return ; default: - return + return ; } - } + }; return (
@@ -147,14 +167,14 @@ export default function HistoryPage() {
-
{mockHistory.length}
+
{statsData.totalProjects}
總評審數
- {mockHistory.filter((item) => item.status === "completed").length} + {statsData.completedProjects}
已完成
@@ -162,10 +182,7 @@ export default function HistoryPage() {
- {Math.round( - mockHistory.filter((item) => item.score > 0).reduce((sum, item) => sum + item.score, 0) / - mockHistory.filter((item) => item.score > 0).length, - )} + {statsData.averageScore}
平均分數
@@ -173,88 +190,115 @@ export default function HistoryPage() {
- {mockHistory.filter((item) => item.status === "processing").length} + {statsData.processingProjects}
處理中
- {/* History List */} -
- {filteredHistory.map((item) => ( - - -
-
-
- {getTypeIcon(item.type)} -
-
-

{item.title}

-
-
- - {item.date} -
- {item.type} - - {item.status === "completed" ? "已完成" : "處理中"} - -
-
-
- -
- {item.status === "completed" && ( -
-
{item.score}
- {item.grade} -
- )} - -
- {item.status === "completed" ? ( - <> - - - - ) : ( - - )} - -
-
-
-
-
- ))} -
- - {filteredHistory.length === 0 && ( + {/* Loading State */} + {loading && ( - -

沒有找到相關記錄

-

嘗試調整搜尋條件或篩選器

-
)} + + {/* History List */} + {!loading && !error && ( +
+ {filteredHistory.map((item) => ( + + +
+
+
+ {getTypeIcon(item.type)} +
+
+

{item.title}

+
+
+ + {item.date} +
+ {item.type} + + {item.status === "completed" ? "已完成" : "處理中"} + +
+
+
+ +
+ {item.status === "completed" && ( +
+
{item.score}
+ {item.grade} +
+ )} + +
+ {item.status === "completed" ? ( + <> + + + + ) : ( + + )} + +
+
+
+
+
+ ))} + + {filteredHistory.length === 0 && ( + + + +

沒有找到相關記錄

+

嘗試調整搜尋條件或篩選器

+ +
+
+ )} +
+ )}
) -} +} \ No newline at end of file diff --git a/lib/services/database.ts b/lib/services/database.ts index 2a9ec81..fabb4f5 100644 --- a/lib/services/database.ts +++ b/lib/services/database.ts @@ -164,7 +164,7 @@ export class CriteriaItemService { items = []; } // 手動排序項目 - items.sort((a, b) => (a.sort_order || 0) - (b.sort_order || 0)); + items.sort((a: any, b: any) => (a.sort_order || 0) - (b.sort_order || 0)); } catch (error) { console.error('解析 items JSON 失敗:', error); console.error('原始 items 數據:', row.items); @@ -190,7 +190,7 @@ export class CriteriaItemService { name: row.name, description: row.description, weight: Number(row.weight) || 0, - maxScore: Number(row.max_score) || 10, // 映射 max_score 到 maxScore 並轉換為數字 + max_score: Number(row.max_score) || 10, sort_order: row.sort_order, created_at: row.created_at, updated_at: row.updated_at @@ -240,6 +240,14 @@ export class ProjectService { return rows.length > 0 ? rows[0] : null; } + static async getAll(): Promise { + const sql = ` + SELECT * FROM projects + ORDER BY created_at DESC + `; + return await query(sql, []) as Project[]; + } + static async findByUserId(userId: number, limit = 20, offset = 0): Promise { const sql = ` SELECT * FROM projects @@ -266,7 +274,7 @@ export class ProjectService { template, files, websites, - evaluation, + evaluation: evaluation || undefined, }; }