新增 AI 解析、評分結果

This commit is contained in:
2025-09-22 17:35:22 +08:00
parent 9d4c586ad3
commit 1d6b1b61b7
16 changed files with 1954 additions and 404 deletions

View File

@@ -1,6 +1,6 @@
"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, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
@@ -88,31 +88,105 @@ const mockResults = {
],
}
const chartData = mockResults.criteria.map((item) => ({
name: item.name,
score: item.score,
maxScore: item.maxScore,
percentage: (item.score / item.maxScore) * 100,
}))
const pieData = mockResults.criteria.map((item) => ({
name: item.name,
value: item.weightedScore,
weight: item.weight,
}))
const radarData = mockResults.criteria.map((item) => ({
subject: item.name,
score: item.score,
fullMark: item.maxScore,
}))
// 圖表數據將在組件內部動態生成
const COLORS = ["#0891b2", "#6366f1", "#f59e0b", "#dc2626", "#10b981"]
export default function ResultsPage() {
const [activeTab, setActiveTab] = useState("overview")
const [evaluationData, setEvaluationData] = useState(null)
const [isLoading, setIsLoading] = useState(true)
const { toast } = useToast()
useEffect(() => {
// 從 localStorage 獲取評審結果
const storedData = localStorage.getItem('evaluationResult')
if (storedData) {
try {
const data = JSON.parse(storedData)
setEvaluationData(data)
} catch (error) {
console.error('解析評審結果失敗:', error)
toast({
title: "數據錯誤",
description: "無法載入評審結果,請重新進行評審",
variant: "destructive",
})
}
} else {
toast({
title: "無評審結果",
description: "請先進行評審以查看結果",
variant: "destructive",
})
}
setIsLoading(false)
}, [toast])
// 如果正在載入,顯示載入狀態
if (isLoading) {
return (
<div className="min-h-screen bg-background">
<Sidebar />
<main className="md:ml-64 p-6">
<div className="max-w-6xl mx-auto">
<div className="flex items-center justify-center h-64">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary mx-auto mb-4"></div>
<p className="text-muted-foreground">...</p>
</div>
</div>
</div>
</main>
</div>
)
}
// 如果沒有數據,顯示錯誤狀態
if (!evaluationData) {
return (
<div className="min-h-screen bg-background">
<Sidebar />
<main className="md:ml-64 p-6">
<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>
</div>
</div>
</div>
</main>
</div>
)
}
// 使用真實數據或回退到模擬數據
const results = evaluationData.evaluation?.fullData || mockResults
// 確保所有必要的數據結構存在
const safeResults = {
...results,
overview: results.overview || {
excellentItems: 0,
improvementItems: 0,
overallPerformance: 0
},
chartData: results.chartData || {
barChart: [],
pieChart: [],
radarChart: []
},
improvementSuggestions: results.improvementSuggestions || {
overallSuggestions: '暫無改進建議',
maintainStrengths: [],
keyImprovements: [],
actionPlan: []
}
}
const downloadReport = () => {
toast({
title: "報告下載中",
@@ -155,7 +229,7 @@ export default function ResultsPage() {
<div>
<h1 className="text-3xl font-bold text-foreground mb-2 font-[var(--font-playfair)]"></h1>
<p className="text-muted-foreground">
{mockResults.projectTitle} - {mockResults.analysisDate}
{safeResults.projectTitle} - {safeResults.analysisDate}
</p>
</div>
<div className="flex gap-2">
@@ -176,22 +250,24 @@ export default function ResultsPage() {
<CardContent className="pt-6">
<div className="grid md:grid-cols-4 gap-6">
<div className="text-center">
<div className="text-4xl font-bold text-primary mb-2">{mockResults.overallScore}</div>
<div className="text-sm text-muted-foreground"> / {mockResults.totalPossible}</div>
<div className="text-4xl font-bold text-primary mb-2">{safeResults.overallScore}</div>
<div className="text-sm text-muted-foreground"> / {safeResults.totalPossible}</div>
</div>
<div className="text-center">
<Badge className={`text-lg px-4 py-2 ${getGradeColor(mockResults.grade)}`}>{mockResults.grade}</Badge>
<Badge className={`text-lg px-4 py-2 ${getGradeColor(safeResults.grade)}`}>{safeResults.grade}</Badge>
<div className="text-sm text-muted-foreground mt-2"></div>
</div>
<div className="text-center">
<div className="flex items-center justify-center mb-2">
<TrendingUp className="h-6 w-6 text-green-600" />
</div>
<div className="text-sm text-muted-foreground"></div>
<div className="text-sm text-muted-foreground">{safeResults.performanceStatus}</div>
</div>
<div className="text-center">
<div className="flex items-center justify-center mb-2">
<Star className="h-6 w-6 text-yellow-500" />
{Array.from({ length: safeResults.recommendedStars || 0 }, (_, i) => (
<Star key={i} className="h-6 w-6 text-yellow-500 fill-current" />
))}
</div>
<div className="text-sm text-muted-foreground"></div>
</div>
@@ -217,7 +293,7 @@ export default function ResultsPage() {
</CardHeader>
<CardContent>
<div className="space-y-4">
{mockResults.criteria.map((item, index) => (
{safeResults.criteria.map((item, index) => (
<div key={index} className="flex items-center justify-between p-4 bg-muted rounded-lg">
<div className="flex-1">
<div className="flex items-center gap-3 mb-2">
@@ -243,21 +319,21 @@ export default function ResultsPage() {
<Card>
<CardContent className="pt-6 text-center">
<CheckCircle className="h-8 w-8 text-green-600 mx-auto mb-2" />
<div className="text-2xl font-bold text-green-600 mb-1">3</div>
<div className="text-2xl font-bold text-green-600 mb-1">{safeResults.overview.excellentItems}</div>
<div className="text-sm text-muted-foreground"></div>
</CardContent>
</Card>
<Card>
<CardContent className="pt-6 text-center">
<AlertCircle className="h-8 w-8 text-yellow-600 mx-auto mb-2" />
<div className="text-2xl font-bold text-yellow-600 mb-1">2</div>
<div className="text-2xl font-bold text-yellow-600 mb-1">{safeResults.overview.improvementItems}</div>
<div className="text-sm text-muted-foreground"></div>
</CardContent>
</Card>
<Card>
<CardContent className="pt-6 text-center">
<TrendingUp className="h-8 w-8 text-blue-600 mx-auto mb-2" />
<div className="text-2xl font-bold text-blue-600 mb-1">82%</div>
<div className="text-2xl font-bold text-blue-600 mb-1">{safeResults.overview.overallPerformance}%</div>
<div className="text-sm text-muted-foreground"></div>
</CardContent>
</Card>
@@ -265,7 +341,7 @@ export default function ResultsPage() {
</TabsContent>
<TabsContent value="detailed" className="space-y-6">
{mockResults.criteria.map((item, index) => (
{safeResults.criteria.map((item, index) => (
<Card key={index}>
<CardHeader>
<div className="flex items-center justify-between">
@@ -322,7 +398,7 @@ export default function ResultsPage() {
</CardHeader>
<CardContent>
<ResponsiveContainer width="100%" height={300}>
<BarChart data={chartData}>
<BarChart data={safeResults.chartData.barChart}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="name" angle={-45} textAnchor="end" height={80} />
<YAxis domain={[0, 10]} />
@@ -342,7 +418,7 @@ export default function ResultsPage() {
<ResponsiveContainer width="100%" height={300}>
<PieChart>
<Pie
data={pieData}
data={safeResults.chartData.pieChart}
cx="50%"
cy="50%"
labelLine={false}
@@ -351,7 +427,7 @@ export default function ResultsPage() {
fill="#8884d8"
dataKey="value"
>
{pieData.map((entry, index) => (
{safeResults.chartData.pieChart.map((entry, index) => (
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
))}
</Pie>
@@ -370,7 +446,7 @@ export default function ResultsPage() {
</CardHeader>
<CardContent>
<ResponsiveContainer width="100%" height={400}>
<RadarChart data={radarData}>
<RadarChart data={safeResults.chartData.radarChart}>
<PolarGrid />
<PolarAngleAxis dataKey="subject" />
<PolarRadiusAxis domain={[0, 10]} />
@@ -385,86 +461,54 @@ export default function ResultsPage() {
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription> AI </CardDescription>
<CardDescription>{safeResults.improvementSuggestions.overallSuggestions}</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div>
<h4 className="font-medium mb-3 text-green-700"></h4>
<div className="grid md:grid-cols-2 gap-4">
<div className="p-4 bg-green-50 rounded-lg">
<h5 className="font-medium mb-2"></h5>
<p className="text-sm text-muted-foreground">
</p>
</div>
<div className="p-4 bg-green-50 rounded-lg">
<h5 className="font-medium mb-2"></h5>
<p className="text-sm text-muted-foreground">
</p>
</div>
{safeResults.improvementSuggestions.maintainStrengths.map((strength, index) => (
<div key={index} className="p-4 bg-green-50 rounded-lg">
<h5 className="font-medium mb-2">{strength.title}</h5>
<p className="text-sm text-muted-foreground">
{strength.description}
</p>
</div>
))}
</div>
</div>
<div>
<h4 className="font-medium mb-3 text-orange-700"></h4>
<div className="space-y-4">
<div className="p-4 bg-orange-50 rounded-lg">
<h5 className="font-medium mb-2"></h5>
<p className="text-sm text-muted-foreground mb-3"></p>
<ul className="text-sm space-y-1">
<li> </li>
<li> </li>
<li> </li>
<li> </li>
</ul>
</div>
<div className="p-4 bg-orange-50 rounded-lg">
<h5 className="font-medium mb-2"></h5>
<p className="text-sm text-muted-foreground mb-3"></p>
<ul className="text-sm space-y-1">
<li> </li>
<li> </li>
<li> 使</li>
<li> </li>
</ul>
</div>
{safeResults.improvementSuggestions.keyImprovements.map((improvement, index) => (
<div key={index} className="p-4 bg-orange-50 rounded-lg">
<h5 className="font-medium mb-2">{improvement.title}</h5>
<p className="text-sm text-muted-foreground mb-3">{improvement.description}</p>
<ul className="text-sm space-y-1">
{improvement.suggestions.map((suggestion, sIndex) => (
<li key={sIndex}> {suggestion}</li>
))}
</ul>
</div>
))}
</div>
</div>
<div>
<h4 className="font-medium mb-3 text-blue-700"></h4>
<div className="space-y-3">
<div className="flex items-start gap-3 p-3 bg-blue-50 rounded-lg">
<div className="w-6 h-6 bg-blue-600 text-white rounded-full flex items-center justify-center text-sm font-bold">
1
{safeResults.improvementSuggestions.actionPlan.map((action, index) => (
<div key={index} className="flex items-start gap-3 p-3 bg-blue-50 rounded-lg">
<div className="w-6 h-6 bg-blue-600 text-white rounded-full flex items-center justify-center text-sm font-bold">
{index + 1}
</div>
<div>
<h5 className="font-medium">{action.phase}</h5>
<p className="text-sm text-muted-foreground">{action.description}</p>
</div>
</div>
<div>
<h5 className="font-medium">1-2</h5>
<p className="text-sm text-muted-foreground"></p>
</div>
</div>
<div className="flex items-start gap-3 p-3 bg-blue-50 rounded-lg">
<div className="w-6 h-6 bg-blue-600 text-white rounded-full flex items-center justify-center text-sm font-bold">
2
</div>
<div>
<h5 className="font-medium">1</h5>
<p className="text-sm text-muted-foreground">
</p>
</div>
</div>
<div className="flex items-start gap-3 p-3 bg-blue-50 rounded-lg">
<div className="w-6 h-6 bg-blue-600 text-white rounded-full flex items-center justify-center text-sm font-bold">
3
</div>
<div>
<h5 className="font-medium">3</h5>
<p className="text-sm text-muted-foreground"></p>
</div>
</div>
))}
</div>
</div>
</CardContent>