實作完整分享、刪除、下載報告功能
This commit is contained in:
@@ -27,6 +27,7 @@ import {
|
||||
} from "recharts"
|
||||
import { Download, Share2, TrendingUp, AlertCircle, CheckCircle, Star } from "lucide-react"
|
||||
import { useToast } from "@/hooks/use-toast"
|
||||
import { ShareModal } from "@/components/share-modal"
|
||||
|
||||
// 模擬評分結果數據 - 使用您提到的例子:8, 7, 6, 8, 4,平均 = 6.6
|
||||
const mockCriteria = [
|
||||
@@ -129,7 +130,8 @@ 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 [error, setError] = useState<string | null>(null)
|
||||
const [isShareModalOpen, setIsShareModalOpen] = useState(false)
|
||||
const { toast } = useToast()
|
||||
const searchParams = useSearchParams()
|
||||
|
||||
@@ -168,10 +170,10 @@ export default function ResultsPage() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('載入評審結果失敗:', error)
|
||||
setError(error.message)
|
||||
setError((error as Error).message)
|
||||
toast({
|
||||
title: "載入失敗",
|
||||
description: error.message || "無法載入評審結果,請重新進行評審",
|
||||
description: (error as Error).message || "無法載入評審結果,請重新進行評審",
|
||||
variant: "destructive",
|
||||
})
|
||||
} finally {
|
||||
@@ -230,10 +232,10 @@ export default function ResultsPage() {
|
||||
}
|
||||
|
||||
// 使用真實數據或回退到模擬數據
|
||||
const results = evaluationData.evaluation?.fullData || evaluationData || mockResults
|
||||
const results = (evaluationData as any)?.evaluation?.fullData || evaluationData || mockResults
|
||||
|
||||
// 計算統計數據 - 基於 criteria_items 的平均分作為閾值
|
||||
const calculateOverview = (criteria: any[]) => {
|
||||
const calculateOverview = (criteria: any[]): any => {
|
||||
if (!criteria || criteria.length === 0) {
|
||||
return {
|
||||
excellentItems: 0,
|
||||
@@ -278,18 +280,72 @@ export default function ResultsPage() {
|
||||
}
|
||||
}
|
||||
|
||||
const downloadReport = () => {
|
||||
toast({
|
||||
title: "報告下載中",
|
||||
description: "評審報告 PDF 正在生成,請稍候...",
|
||||
})
|
||||
const downloadReport = async () => {
|
||||
try {
|
||||
// 顯示載入提示
|
||||
toast({
|
||||
title: "報告下載中",
|
||||
description: "評審報告 PDF 正在生成,請稍候...",
|
||||
});
|
||||
|
||||
// 獲取評審 ID
|
||||
const evaluationId = searchParams.get('id');
|
||||
|
||||
if (!evaluationId) {
|
||||
throw new Error('無法獲取評審 ID');
|
||||
}
|
||||
|
||||
// 調用下載 API
|
||||
const response = await fetch(`/api/evaluation/${evaluationId}/download`);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
throw new Error(errorData.error || '下載失敗');
|
||||
}
|
||||
|
||||
// 獲取 PDF Blob
|
||||
const pdfBlob = await response.blob();
|
||||
|
||||
// 創建下載連結
|
||||
const url = window.URL.createObjectURL(pdfBlob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
|
||||
// 從 Content-Disposition 標頭獲取檔案名稱,或使用預設名稱
|
||||
const contentDisposition = response.headers.get('Content-Disposition');
|
||||
let fileName = `評審報告_${safeResults.projectTitle}_${safeResults.analysisDate}.pdf`;
|
||||
|
||||
if (contentDisposition) {
|
||||
const fileNameMatch = contentDisposition.match(/filename="(.+)"/);
|
||||
if (fileNameMatch) {
|
||||
fileName = decodeURIComponent(fileNameMatch[1]);
|
||||
}
|
||||
}
|
||||
|
||||
link.download = fileName;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(url);
|
||||
|
||||
// 顯示成功提示
|
||||
toast({
|
||||
title: "下載完成",
|
||||
description: "評審報告已成功下載",
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('下載失敗:', error);
|
||||
toast({
|
||||
title: "下載失敗",
|
||||
description: error instanceof Error ? error.message : '下載過程中發生錯誤',
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const shareResults = () => {
|
||||
toast({
|
||||
title: "分享連結已複製",
|
||||
description: "評審結果分享連結已複製到剪貼板",
|
||||
})
|
||||
setIsShareModalOpen(true)
|
||||
}
|
||||
|
||||
const getScoreColor = (score: number, maxScore: number) => {
|
||||
@@ -384,7 +440,7 @@ export default function ResultsPage() {
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
{safeResults.criteria.map((item, index) => (
|
||||
{safeResults.criteria.map((item: any, index: number) => (
|
||||
<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">
|
||||
@@ -432,7 +488,7 @@ export default function ResultsPage() {
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="detailed" className="space-y-6">
|
||||
{safeResults.criteria.map((item, index) => (
|
||||
{safeResults.criteria.map((item: any, index: number) => (
|
||||
<Card key={index}>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
@@ -455,7 +511,7 @@ export default function ResultsPage() {
|
||||
<div>
|
||||
<h4 className="font-medium mb-2 text-green-700">優點</h4>
|
||||
<ul className="space-y-1">
|
||||
{item.strengths.map((strength, i) => (
|
||||
{item.strengths.map((strength: any, i: number) => (
|
||||
<li key={i} className="flex items-center gap-2 text-sm">
|
||||
<CheckCircle className="h-4 w-4 text-green-600" />
|
||||
{strength}
|
||||
@@ -466,7 +522,7 @@ export default function ResultsPage() {
|
||||
<div>
|
||||
<h4 className="font-medium mb-2 text-orange-700">改進建議</h4>
|
||||
<ul className="space-y-1">
|
||||
{item.improvements.map((improvement, i) => (
|
||||
{item.improvements.map((improvement: any, i: number) => (
|
||||
<li key={i} className="flex items-center gap-2 text-sm">
|
||||
<AlertCircle className="h-4 w-4 text-orange-600" />
|
||||
{improvement}
|
||||
@@ -518,7 +574,7 @@ export default function ResultsPage() {
|
||||
fill="#8884d8"
|
||||
dataKey="value"
|
||||
>
|
||||
{safeResults.chartData.pieChart.map((entry, index) => (
|
||||
{safeResults.chartData.pieChart.map((entry: any, index: number) => (
|
||||
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
|
||||
))}
|
||||
</Pie>
|
||||
@@ -558,7 +614,7 @@ export default function ResultsPage() {
|
||||
<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) => (
|
||||
{safeResults.improvementSuggestions.maintainStrengths.map((strength: any, index: number) => (
|
||||
<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">
|
||||
@@ -572,12 +628,12 @@ export default function ResultsPage() {
|
||||
<div>
|
||||
<h4 className="font-medium mb-3 text-orange-700">重點改進方向</h4>
|
||||
<div className="space-y-4">
|
||||
{safeResults.improvementSuggestions.keyImprovements.map((improvement, index) => (
|
||||
{safeResults.improvementSuggestions.keyImprovements.map((improvement: any, index: number) => (
|
||||
<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) => (
|
||||
{improvement.suggestions.map((suggestion: any, sIndex: number) => (
|
||||
<li key={sIndex}>• {suggestion}</li>
|
||||
))}
|
||||
</ul>
|
||||
@@ -589,7 +645,7 @@ export default function ResultsPage() {
|
||||
<div>
|
||||
<h4 className="font-medium mb-3 text-blue-700">下一步行動計劃</h4>
|
||||
<div className="space-y-3">
|
||||
{safeResults.improvementSuggestions.actionPlan.map((action, index) => (
|
||||
{safeResults.improvementSuggestions.actionPlan.map((action: any, index: number) => (
|
||||
<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}
|
||||
@@ -608,6 +664,14 @@ export default function ResultsPage() {
|
||||
</Tabs>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{/* Share Modal */}
|
||||
<ShareModal
|
||||
isOpen={isShareModalOpen}
|
||||
onClose={() => setIsShareModalOpen(false)}
|
||||
evaluationId={searchParams.get('id') || undefined}
|
||||
projectTitle={safeResults.projectTitle}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
Reference in New Issue
Block a user