Files
ai-scoring-application/app/results/page.tsx

523 lines
21 KiB
TypeScript

"use client"
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"
import { Badge } from "@/components/ui/badge"
import { Progress } from "@/components/ui/progress"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import {
BarChart,
Bar,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
PieChart,
Pie,
Cell,
RadarChart,
PolarGrid,
PolarAngleAxis,
PolarRadiusAxis,
Radar,
} from "recharts"
import { Download, Share2, TrendingUp, AlertCircle, CheckCircle, Star } from "lucide-react"
import { useToast } from "@/hooks/use-toast"
// 模擬評分結果數據
const mockResults = {
projectTitle: "產品介紹簡報",
overallScore: 82,
totalPossible: 100,
grade: "B+",
analysisDate: "2024-01-15",
criteria: [
{
name: "內容品質",
score: 8.5,
maxScore: 10,
weight: 25,
weightedScore: 21.25,
feedback: "內容結構清晰,資訊豐富且準確。建議增加更多實際案例來支撐論點。",
strengths: ["邏輯清晰", "資料準確", "結構完整"],
improvements: ["增加案例", "深化分析"],
},
{
name: "視覺設計",
score: 7.8,
maxScore: 10,
weight: 20,
weightedScore: 15.6,
feedback: "整體設計風格統一,色彩搭配合理。部分頁面文字密度過高,影響閱讀體驗。",
strengths: ["風格統一", "色彩協調", "版面整潔"],
improvements: ["減少文字密度", "增加視覺元素"],
},
{
name: "邏輯結構",
score: 8.8,
maxScore: 10,
weight: 20,
weightedScore: 17.6,
feedback: "邏輯架構非常清晰,各章節銜接自然,論述層次分明。",
strengths: ["邏輯清晰", "結構完整", "銜接自然"],
improvements: ["可增加總結回顧"],
},
{
name: "創新性",
score: 7.2,
maxScore: 10,
weight: 15,
weightedScore: 10.8,
feedback: "內容具有一定創新性,但可以更大膽地提出獨特觀點和解決方案。",
strengths: ["思路新穎", "角度獨特"],
improvements: ["增加創新元素", "提出獨特見解"],
},
{
name: "實用性",
score: 8.3,
maxScore: 10,
weight: 20,
weightedScore: 16.6,
feedback: "內容實用性強,提供的解決方案具有可操作性,對目標受眾有實際價值。",
strengths: ["實用性強", "可操作性好", "價值明確"],
improvements: ["增加實施步驟"],
},
],
}
// 圖表數據將在組件內部動態生成
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: "報告下載中",
description: "評審報告 PDF 正在生成,請稍候...",
})
}
const shareResults = () => {
toast({
title: "分享連結已複製",
description: "評審結果分享連結已複製到剪貼板",
})
}
const getScoreColor = (score: number, maxScore: number) => {
const percentage = (score / maxScore) * 100
if (percentage >= 90) return "text-green-600"
if (percentage >= 80) return "text-blue-600"
if (percentage >= 70) return "text-yellow-600"
if (percentage >= 60) return "text-orange-600"
return "text-red-600"
}
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-red-100 text-red-800"
}
return (
<div className="min-h-screen bg-background">
<Sidebar />
<main className="md:ml-64 p-6">
<div className="max-w-6xl mx-auto">
{/* Header */}
<div className="mb-8 pt-8 md:pt-0">
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div>
<h1 className="text-3xl font-bold text-foreground mb-2 font-[var(--font-playfair)]"></h1>
<p className="text-muted-foreground">
{safeResults.projectTitle} - {safeResults.analysisDate}
</p>
</div>
<div className="flex gap-2">
<Button onClick={shareResults} variant="outline">
<Share2 className="h-4 w-4 mr-2" />
</Button>
<Button onClick={downloadReport}>
<Download className="h-4 w-4 mr-2" />
</Button>
</div>
</div>
</div>
{/* Overall Score */}
<Card className="mb-8">
<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">{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(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">{safeResults.performanceStatus}</div>
</div>
<div className="text-center">
<div className="flex items-center justify-center mb-2">
{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>
</div>
</CardContent>
</Card>
{/* Detailed Results */}
<Tabs value={activeTab} onValueChange={setActiveTab}>
<TabsList className="grid w-full grid-cols-4">
<TabsTrigger value="overview"></TabsTrigger>
<TabsTrigger value="detailed"></TabsTrigger>
<TabsTrigger value="charts"></TabsTrigger>
<TabsTrigger value="suggestions"></TabsTrigger>
</TabsList>
<TabsContent value="overview" className="space-y-6">
{/* Score Breakdown */}
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
{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">
<h4 className="font-medium">{item.name}</h4>
<Badge variant="outline">{item.weight}% </Badge>
</div>
<Progress value={(item.score / item.maxScore) * 100} className="h-2" />
</div>
<div className="text-right ml-4">
<div className={`text-2xl font-bold ${getScoreColor(item.score, item.maxScore)}`}>
{item.score}
</div>
<div className="text-sm text-muted-foreground">/ {item.maxScore}</div>
</div>
</div>
))}
</div>
</CardContent>
</Card>
{/* Quick Stats */}
<div className="grid md:grid-cols-3 gap-6">
<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">{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">{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">{safeResults.overview.overallPerformance}%</div>
<div className="text-sm text-muted-foreground"></div>
</CardContent>
</Card>
</div>
</TabsContent>
<TabsContent value="detailed" className="space-y-6">
{safeResults.criteria.map((item, index) => (
<Card key={index}>
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle className="flex items-center gap-2">
{item.name}
<Badge variant="outline">{item.weight}% </Badge>
</CardTitle>
<div className={`text-2xl font-bold ${getScoreColor(item.score, item.maxScore)}`}>
{item.score}/{item.maxScore}
</div>
</div>
</CardHeader>
<CardContent className="space-y-4">
<div>
<h4 className="font-medium mb-2">AI </h4>
<p className="text-muted-foreground leading-relaxed">{item.feedback}</p>
</div>
<div className="grid md:grid-cols-2 gap-4">
<div>
<h4 className="font-medium mb-2 text-green-700"></h4>
<ul className="space-y-1">
{item.strengths.map((strength, i) => (
<li key={i} className="flex items-center gap-2 text-sm">
<CheckCircle className="h-4 w-4 text-green-600" />
{strength}
</li>
))}
</ul>
</div>
<div>
<h4 className="font-medium mb-2 text-orange-700"></h4>
<ul className="space-y-1">
{item.improvements.map((improvement, i) => (
<li key={i} className="flex items-center gap-2 text-sm">
<AlertCircle className="h-4 w-4 text-orange-600" />
{improvement}
</li>
))}
</ul>
</div>
</div>
</CardContent>
</Card>
))}
</TabsContent>
<TabsContent value="charts" className="space-y-6">
<div className="grid lg:grid-cols-2 gap-6">
{/* Bar Chart */}
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent>
<ResponsiveContainer width="100%" height={300}>
<BarChart data={safeResults.chartData.barChart}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="name" angle={-45} textAnchor="end" height={80} />
<YAxis domain={[0, 10]} />
<Tooltip />
<Bar dataKey="score" fill="#0891b2" />
</BarChart>
</ResponsiveContainer>
</CardContent>
</Card>
{/* Pie Chart */}
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent>
<ResponsiveContainer width="100%" height={300}>
<PieChart>
<Pie
data={safeResults.chartData.pieChart}
cx="50%"
cy="50%"
labelLine={false}
label={({ name, weight }) => `${name} (${weight}%)`}
outerRadius={80}
fill="#8884d8"
dataKey="value"
>
{safeResults.chartData.pieChart.map((entry, index) => (
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
))}
</Pie>
<Tooltip />
</PieChart>
</ResponsiveContainer>
</CardContent>
</Card>
</div>
{/* Radar Chart */}
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<ResponsiveContainer width="100%" height={400}>
<RadarChart data={safeResults.chartData.radarChart}>
<PolarGrid />
<PolarAngleAxis dataKey="subject" />
<PolarRadiusAxis domain={[0, 10]} />
<Radar name="得分" dataKey="score" stroke="#0891b2" fill="#0891b2" fillOpacity={0.3} />
</RadarChart>
</ResponsiveContainer>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="suggestions" className="space-y-6">
<Card>
<CardHeader>
<CardTitle></CardTitle>
<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">
{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">
{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">
{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>
</div>
</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
</main>
</div>
)
}