修正詳細評審頁面結果

This commit is contained in:
2025-09-23 22:46:01 +08:00
parent d94b1786d6
commit f018e71577
10 changed files with 558 additions and 24 deletions

View File

@@ -0,0 +1,168 @@
import { NextRequest, NextResponse } from 'next/server';
import { EvaluationService } from '@/lib/services/database';
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const evaluationId = parseInt(params.id);
if (isNaN(evaluationId)) {
return NextResponse.json(
{ success: false, error: '無效的評審ID' },
{ status: 400 }
);
}
console.log(`📊 開始獲取評審詳細數據: ID=${evaluationId}`);
// 獲取評審詳細數據
const evaluationWithDetails = await EvaluationService.findWithDetails(evaluationId);
if (!evaluationWithDetails) {
return NextResponse.json(
{ success: false, error: '找不到指定的評審記錄' },
{ status: 404 }
);
}
console.log(`✅ 成功獲取評審數據: 專案=${evaluationWithDetails.project?.title}`);
// 處理反饋數據,按類型分組
const feedbackByType = {
criteria: evaluationWithDetails.feedback?.filter(f => f.feedback_type === 'criteria') || [],
strength: evaluationWithDetails.feedback?.filter(f => f.feedback_type === 'strength') || [],
improvement: evaluationWithDetails.feedback?.filter(f => f.feedback_type === 'improvement') || [],
overall: evaluationWithDetails.feedback?.filter(f => f.feedback_type === 'overall') || []
};
// 為每個評分標準獲取對應的 AI 評語、優點和改進建議
const criteriaWithFeedback = evaluationWithDetails.scores?.map(score => {
const criteriaId = score.criteria_item_id;
const criteriaName = score.criteria_item_name || '未知項目';
// 獲取該評分標準的 AI 評語
const criteriaFeedback = feedbackByType.criteria
.filter(f => f.criteria_item_id === criteriaId)
.map(f => f.content)
.filter((content, index, arr) => arr.indexOf(content) === index); // 去重
// 獲取該評分標準的優點
const strengths = feedbackByType.strength
.filter(f => f.criteria_item_id === criteriaId)
.map(f => f.content)
.filter((content, index, arr) => arr.indexOf(content) === index); // 去重
// 獲取該評分標準的改進建議
const improvements = feedbackByType.improvement
.filter(f => f.criteria_item_id === criteriaId)
.map(f => f.content)
.filter((content, index, arr) => arr.indexOf(content) === index); // 去重
return {
name: criteriaName,
score: Number(score.score) || 0,
maxScore: Number(score.max_score) || 10,
weight: Number(score.weight) || 1,
weightedScore: Number(score.weighted_score) || 0,
percentage: Number(score.percentage) || 0,
feedback: criteriaFeedback[0] || '無評語', // 使用第一個評語
strengths: strengths,
improvements: improvements
};
}) || [];
// 生成圖表數據
const chartData = {
// 各項目得分 - 使用 evaluation_scores 的 score 資料
barChart: criteriaWithFeedback.map(criteria => ({
name: criteria.name,
score: criteria.score
})),
// 權重分布 - 使用 evaluation_scores 的 weighted_score 資料
pieChart: criteriaWithFeedback.map(criteria => ({
name: criteria.name,
value: criteria.weightedScore,
weight: criteria.weight
})),
// 能力雷達圖 - 使用 evaluation_scores 的 score 資料
radarChart: criteriaWithFeedback.map(criteria => ({
subject: criteria.name,
score: criteria.score,
fullMark: criteria.maxScore
}))
};
// 轉換數據格式以符合前端需求
const formattedData = {
projectTitle: evaluationWithDetails.project?.title || '未知專案',
overallScore: Number(evaluationWithDetails.overall_score) || 0,
totalPossible: Number(evaluationWithDetails.max_possible_score) || 100,
grade: evaluationWithDetails.grade || 'N/A',
performanceStatus: evaluationWithDetails.performance_status || 'N/A',
recommendedStars: evaluationWithDetails.recommended_stars || 0,
analysisDate: evaluationWithDetails.created_at ? new Date(evaluationWithDetails.created_at).toISOString().split('T')[0] : new Date().toISOString().split('T')[0],
criteria: criteriaWithFeedback,
overview: {
excellentItems: evaluationWithDetails.excellent_items || 0,
improvementItems: evaluationWithDetails.improvement_items || 0,
overallPerformance: Number(evaluationWithDetails.overall_score) || 0
},
detailedAnalysis: {
summary: feedbackByType.overall[0]?.content || '無詳細分析',
keyFindings: feedbackByType.overall.map(f => f.content).filter((content, index, arr) => arr.indexOf(content) === index) // 去重
},
improvementSuggestions: {
// 整體改進建議 - evaluation_feedback 的第一筆 overall
overallSuggestions: feedbackByType.overall[0]?.content || '無改進建議',
// 繼續保持的優勢 - evaluation_feedback 的 strength
maintainStrengths: feedbackByType.strength
.filter(f => f.criteria_item_id) // 只取有 criteria_item_id 的
.map(f => ({
title: f.criteria_item_name || '優勢',
description: f.content || '無描述'
}))
.filter((item, index, arr) => arr.findIndex(i => i.title === item.title) === index), // 去重
// 重點改進方向 - evaluation_feedback 的 improvement (除了最後3筆)
keyImprovements: feedbackByType.improvement
.filter(f => f.criteria_item_id) // 只取有 criteria_item_id 的
.slice(0, -3) // 排除最後3筆
.map(f => ({
title: f.criteria_item_name || '改進項目',
description: f.content || '無描述',
suggestions: [f.content || '無建議']
}))
.filter((item, index, arr) => arr.findIndex(i => i.title === item.title) === index), // 去重
// 下一步行動計劃 - evaluation_feedback 的 improvement 最後3筆
actionPlan: feedbackByType.improvement
.filter(f => f.criteria_item_id) // 只取有 criteria_item_id 的
.slice(-3) // 取最後3筆
.map((f, index) => ({
phase: `階段 ${index + 1}`,
description: f.content || '無描述'
}))
},
chartData: chartData
};
console.log(`📋 評審數據格式化完成: 專案=${formattedData.projectTitle}, 分數=${formattedData.overallScore}`);
return NextResponse.json({
success: true,
data: formattedData
});
} catch (error) {
console.error('❌ 獲取評審詳細數據失敗:', error);
return NextResponse.json(
{ success: false, error: '獲取評審詳細數據失敗' },
{ status: 500 }
);
}
}

View File

@@ -260,7 +260,7 @@ export default function HistoryPage() {
{item.status === "completed" ? (
<>
<Button asChild variant="outline" size="sm">
<Link href={`/results?id=${item.id}`}>
<Link href={`/results?id=${item.evaluation_id}`}>
<Eye className="h-4 w-4" />
</Link>
</Button>

View File

@@ -1,6 +1,7 @@
"use client"
import { useState, useEffect } from "react"
import { useSearchParams } from "next/navigation"
import { Sidebar } from "@/components/sidebar"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
@@ -128,32 +129,58 @@ export default function ResultsPage() {
const [activeTab, setActiveTab] = useState("overview")
const [evaluationData, setEvaluationData] = useState(null)
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState(null)
const { toast } = useToast()
const searchParams = useSearchParams()
useEffect(() => {
// 從 localStorage 獲取評審結果
const storedData = localStorage.getItem('evaluationResult')
if (storedData) {
const loadEvaluationData = async () => {
try {
const data = JSON.parse(storedData)
setEvaluationData(data)
setIsLoading(true)
setError(null)
// 檢查是否有 URL 參數中的評審 ID
const evaluationId = searchParams.get('id')
if (evaluationId) {
// 從 API 獲取評審數據
console.log(`📊 從 API 獲取評審數據: ID=${evaluationId}`)
const response = await fetch(`/api/evaluation/${evaluationId}`)
const result = await response.json()
if (result.success) {
setEvaluationData(result.data)
console.log('✅ 成功載入評審數據:', result.data.projectTitle)
} else {
throw new Error(result.error || '獲取評審數據失敗')
}
} else {
// 回退到從 localStorage 獲取評審結果
console.log('📊 從 localStorage 獲取評審結果')
const storedData = localStorage.getItem('evaluationResult')
if (storedData) {
const data = JSON.parse(storedData)
setEvaluationData(data)
console.log('✅ 成功載入 localStorage 數據')
} else {
throw new Error('沒有找到評審結果')
}
}
} catch (error) {
console.error('解析評審結果失敗:', error)
console.error('載入評審結果失敗:', error)
setError(error.message)
toast({
title: "數據錯誤",
description: "無法載入評審結果,請重新進行評審",
title: "載入失敗",
description: error.message || "無法載入評審結果,請重新進行評審",
variant: "destructive",
})
} finally {
setIsLoading(false)
}
} else {
toast({
title: "無評審結果",
description: "請先進行評審以查看結果",
variant: "destructive",
})
}
setIsLoading(false)
}, [toast])
loadEvaluationData()
}, [searchParams, toast])
// 如果正在載入,顯示載入狀態
if (isLoading) {
@@ -174,8 +201,8 @@ export default function ResultsPage() {
)
}
// 如果沒有數據,顯示錯誤狀態
if (!evaluationData) {
// 如果沒有數據或發生錯誤,顯示錯誤狀態
if (!evaluationData || error) {
return (
<div className="min-h-screen bg-background">
<Sidebar />
@@ -183,10 +210,17 @@ export default function ResultsPage() {
<div className="max-w-6xl mx-auto">
<div className="flex items-center justify-center h-64">
<div className="text-center">
<p className="text-muted-foreground mb-4"></p>
<Button onClick={() => window.location.href = '/upload'}>
</Button>
<p className="text-muted-foreground mb-4">
{error ? `載入失敗: ${error}` : '沒有找到評審結果'}
</p>
<div className="space-x-2">
<Button onClick={() => window.location.href = '/upload'}>
</Button>
<Button variant="outline" onClick={() => window.location.href = '/history'}>
</Button>
</div>
</div>
</div>
</div>
@@ -196,7 +230,7 @@ export default function ResultsPage() {
}
// 使用真實數據或回退到模擬數據
const results = evaluationData.evaluation?.fullData || mockResults
const results = evaluationData.evaluation?.fullData || evaluationData || mockResults
// 計算統計數據 - 基於 criteria_items 的平均分作為閾值
const calculateOverview = (criteria: any[]) => {