diff --git a/app/admin/results/page.tsx b/app/admin/results/page.tsx index 68a2435..3ce8992 100644 --- a/app/admin/results/page.tsx +++ b/app/admin/results/page.tsx @@ -7,8 +7,9 @@ import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" +import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog" import { Badge } from "@/components/ui/badge" -import { Brain, Lightbulb, BarChart3, ArrowLeft, Search, Download, Filter, ChevronLeft, ChevronRight, Loader2 } from "lucide-react" +import { Brain, Lightbulb, BarChart3, ArrowLeft, Search, Download, Filter, ChevronLeft, ChevronRight, Loader2, Eye } from "lucide-react" import Link from "next/link" import { useAuth } from "@/lib/hooks/use-auth" @@ -81,6 +82,10 @@ function AdminResultsContent() { const [testTypeFilter, setTestTypeFilter] = useState("all") const [isLoading, setIsLoading] = useState(true) const [error, setError] = useState(null) + const [selectedResult, setSelectedResult] = useState(null) + const [showDetailModal, setShowDetailModal] = useState(false) + const [detailData, setDetailData] = useState(null) + const [isLoadingDetail, setIsLoadingDetail] = useState(false) useEffect(() => { loadData() @@ -232,9 +237,9 @@ function AdminResultsContent() { const link = document.createElement('a') link.href = url link.download = data.filename - document.body.appendChild(link) - link.click() - document.body.removeChild(link) + document.body.appendChild(link) + link.click() + document.body.removeChild(link) window.URL.revokeObjectURL(url) } else { console.error('匯出失敗:', data.message) @@ -246,6 +251,29 @@ function AdminResultsContent() { } } + const handleViewDetail = async (result: TestResult) => { + setSelectedResult(result) + setShowDetailModal(true) + setIsLoadingDetail(true) + + try { + const response = await fetch(`/api/admin/test-results/detail?testResultId=${result.id}&testType=${result.type}`) + const data = await response.json() + + if (data.success) { + setDetailData(data.data) + } else { + console.error('獲取詳細結果失敗:', data.message) + alert('獲取詳細結果失敗,請稍後再試') + } + } catch (error) { + console.error('獲取詳細結果錯誤:', error) + alert('獲取詳細結果時發生錯誤,請稍後再試') + } finally { + setIsLoadingDetail(false) + } + } + return (
{/* Header */} @@ -401,16 +429,17 @@ function AdminResultsContent() { ) : ( <> - - - 用戶 - 部門 - 測試類型 - 分數 - 等級 - 完成時間 - - + + + 用戶 + 部門 + 測試類型 + 分數 + 等級 + 完成時間 + 操作 + + {results.map((result) => { const testTypeInfo = getTestTypeInfo(result.type) @@ -444,10 +473,21 @@ function AdminResultsContent() { {scoreLevel.level} - +
{formatDate(result.completedAt)}
-
- +
+ + + + ) })}
@@ -599,6 +639,273 @@ function AdminResultsContent() { + + {/* 詳細結果模態框 */} + + + + 測驗詳細結果 + + {selectedResult && `${selectedResult.userName} - ${getTestTypeInfo(selectedResult.type).name}`} + + + + {isLoadingDetail ? ( +
+ + 載入詳細結果中... +
+ ) : detailData ? ( +
+ {/* 基本資訊 */} + + + 基本資訊 + + +
+
+ +

{detailData.user.name}

+
+
+ +

{detailData.user.email}

+
+
+ +

{detailData.user.department}

+
+
+ +

{formatDate(detailData.result.completedAt)}

+
+
+ +

{detailData.result.score}

+
+
+ + + {getScoreLevel(detailData.result.score).level} + +
+
+
+
+ + {/* 題目詳情 */} + {detailData.questions && detailData.questions.length > 0 && ( + + + 答題詳情 + + +
+ {/* 邏輯思維題目 */} + {detailData.questions.filter((q: any) => q.type === 'logic').length > 0 && ( +
+

+ + 邏輯思維題目 +

+
+ {detailData.questions + .filter((q: any) => q.type === 'logic') + .map((question: any, index: number) => ( +
+
+

第 {index + 1} 題

+ + {question.isCorrect ? "正確" : "錯誤"} + +
+ +
+
+ +

{question.question}

+
+ +
+
+ +
+ {question.option_a &&

A. {question.option_a}

} + {question.option_b &&

B. {question.option_b}

} + {question.option_c &&

C. {question.option_c}

} + {question.option_d &&

D. {question.option_d}

} + {question.option_e &&

E. {question.option_e}

} +
+
+
+ +
+

用戶答案: {question.userAnswer}

+

正確答案: {question.correctAnswer}

+
+
+
+ {question.explanation && ( +
+ +

{question.explanation}

+
+ )} +
+
+ ))} +
+
+ )} + + {/* 創意能力題目 */} + {detailData.questions.filter((q: any) => q.type === 'creative').length > 0 && ( +
+

+ + 創意能力題目 +

+
+ {detailData.questions + .filter((q: any) => q.type === 'creative') + .map((question: any, index: number) => ( +
+
+

第 {index + 1} 題

+ + {question.score} 分 + +
+ +
+
+ +

{question.statement}

+
+ +
+
+ +

{question.userAnswer}

+
+
+ +

{question.score} 分

+
+
+
+
+ ))} +
+
+ )} + + {/* 單一測試類型的題目(邏輯或創意) */} + {detailData.result.type !== 'combined' && detailData.questions.map((question: any, index: number) => ( +
+
+

第 {index + 1} 題

+ {question.isCorrect !== undefined && ( + + {question.isCorrect ? "正確" : "錯誤"} + + )} + {question.score !== undefined && ( + + {question.score} 分 + + )} +
+ +
+
+ +

{question.question || question.statement}

+
+ + {question.type === 'logic' && ( + <> +
+
+ +
+ {question.option_a &&

A. {question.option_a}

} + {question.option_b &&

B. {question.option_b}

} + {question.option_c &&

C. {question.option_c}

} + {question.option_d &&

D. {question.option_d}

} + {question.option_e &&

E. {question.option_e}

} +
+
+
+ +
+

用戶答案: {question.userAnswer}

+

正確答案: {question.correctAnswer}

+
+
+
+ {question.explanation && ( +
+ +

{question.explanation}

+
+ )} + + )} + + {question.type === 'creative' && ( +
+
+ +

{question.userAnswer}

+
+
+ +

{question.score} 分

+
+
+ )} +
+
+ ))} +
+
+
+ )} + + {/* 綜合測試詳細分析 */} + {detailData.result.type === 'combined' && detailData.result.details && ( + + + 綜合能力分析 + + +
+
+

邏輯思維

+

{detailData.result.details.logicScore}

+
+
+

創意能力

+

{detailData.result.details.creativeScore}

+
+
+

能力平衡

+

{detailData.result.details.abilityBalance}

+
+
+
+
+ )} +
+ ) : ( +
+ 無法載入詳細結果 +
+ )} +
+
) } \ No newline at end of file diff --git a/app/api/admin/test-results/detail/route.ts b/app/api/admin/test-results/detail/route.ts new file mode 100644 index 0000000..5e36bc1 --- /dev/null +++ b/app/api/admin/test-results/detail/route.ts @@ -0,0 +1,157 @@ +import { NextRequest, NextResponse } from "next/server" +import { getTestResultById } from "@/lib/database/models/test_result" +import { getLogicTestAnswersByTestResultId } from "@/lib/database/models/logic_test_answer" +import { getCreativeTestAnswersByTestResultId } from "@/lib/database/models/creative_test_answer" +import { getCombinedTestResultById } from "@/lib/database/models/combined_test_result" +import { findUserById } from "@/lib/database/models/user" +import { findLogicQuestionById } from "@/lib/database/models/logic_question" +import { findCreativeQuestionById } from "@/lib/database/models/creative_question" + +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url) + const testResultId = searchParams.get("testResultId") + const testType = searchParams.get("testType") as "logic" | "creative" | "combined" + + if (!testResultId || !testType) { + return NextResponse.json( + { success: false, message: "缺少必要參數" }, + { status: 400 } + ) + } + + let result: any = null + let user: any = null + let questions: any[] = [] + let answers: any[] = [] + + // 獲取用戶資訊 + if (testType === "combined") { + const combinedResult = await getCombinedTestResultById(testResultId) + if (combinedResult) { + user = await findUserById(combinedResult.user_id) + result = { + id: combinedResult.id, + userId: combinedResult.user_id, + type: "combined", + score: combinedResult.overall_score, + completedAt: combinedResult.completed_at, + details: { + logicScore: combinedResult.logic_score, + creativeScore: combinedResult.creativity_score, + abilityBalance: combinedResult.balance_score, + breakdown: combinedResult.logic_breakdown + } + } + + // 獲取綜合測試的詳細答題資料 + // 從 logic_breakdown 中獲取邏輯題答案 + if (combinedResult.logic_breakdown && typeof combinedResult.logic_breakdown === 'object') { + const logicBreakdown = combinedResult.logic_breakdown as any + if (logicBreakdown.answers && Array.isArray(logicBreakdown.answers)) { + for (const answer of logicBreakdown.answers) { + const question = await findLogicQuestionById(answer.questionId) + if (question) { + questions.push({ + ...question, + type: 'logic', + userAnswer: answer.userAnswer, + isCorrect: answer.isCorrect, + correctAnswer: answer.correctAnswer, + explanation: answer.explanation + }) + } + } + } + } + + // 從 creativity_breakdown 中獲取創意題答案 + if (combinedResult.creativity_breakdown && typeof combinedResult.creativity_breakdown === 'object') { + const creativityBreakdown = combinedResult.creativity_breakdown as any + if (creativityBreakdown.answers && Array.isArray(creativityBreakdown.answers)) { + for (const answer of creativityBreakdown.answers) { + const question = await findCreativeQuestionById(answer.questionId) + if (question) { + questions.push({ + ...question, + type: 'creative', + userAnswer: answer.userAnswer, + score: answer.score, + isReverse: answer.isReverse + }) + } + } + } + } + } + } else { + const testResult = await getTestResultById(testResultId) + if (testResult) { + user = await findUserById(testResult.user_id) + result = { + id: testResult.id, + userId: testResult.user_id, + type: testResult.test_type, + score: testResult.score, + completedAt: testResult.completed_at + } + + // 獲取詳細答案 + if (testType === "logic") { + answers = await getLogicTestAnswersByTestResultId(testResultId) + // 獲取對應的題目 + for (const answer of answers) { + const question = await findLogicQuestionById(answer.question_id) + if (question) { + questions.push({ + ...question, + userAnswer: answer.user_answer, + isCorrect: answer.is_correct, + correctAnswer: answer.correct_answer, + explanation: answer.explanation + }) + } + } + } else if (testType === "creative") { + answers = await getCreativeTestAnswersByTestResultId(testResultId) + // 獲取對應的題目 + for (const answer of answers) { + const question = await findCreativeQuestionById(answer.question_id) + if (question) { + questions.push({ + ...question, + userAnswer: answer.user_answer, + score: answer.score, + isReverse: answer.is_reverse + }) + } + } + } + } + } + + if (!result || !user) { + return NextResponse.json( + { success: false, message: "找不到測試結果" }, + { status: 404 } + ) + } + + return NextResponse.json({ + success: true, + data: { + result, + user, + questions, + answers + } + }) + + } catch (error) { + console.error("獲取詳細測試結果失敗:", error) + return NextResponse.json( + { success: false, message: "獲取詳細結果失敗", error: error instanceof Error ? error.message : String(error) }, + { status: 500 } + ) + } +} diff --git a/scripts/test-detail-api.js b/scripts/test-detail-api.js new file mode 100644 index 0000000..c4d32e4 --- /dev/null +++ b/scripts/test-detail-api.js @@ -0,0 +1,70 @@ +const fetch = require('node-fetch'); + +async function testDetailAPI() { + console.log('🔍 測試詳細結果 API'); + console.log('=============================='); + + try { + // 先獲取測試結果列表 + const listResponse = await fetch('http://localhost:3000/api/admin/test-results'); + const listData = await listResponse.json(); + + if (listData.success && listData.data.results.length > 0) { + const firstResult = listData.data.results[0]; + console.log(`📋 測試結果: ${firstResult.userName} - ${firstResult.type}`); + console.log(` 分數: ${firstResult.score}, 等級: ${firstResult.grade}`); + + // 測試詳細結果 API + const detailResponse = await fetch(`http://localhost:3000/api/admin/test-results/detail?testResultId=${firstResult.id}&testType=${firstResult.type}`); + const detailData = await detailResponse.json(); + + if (detailData.success) { + console.log('✅ 詳細結果獲取成功'); + console.log('📊 用戶資訊:', { + name: detailData.data.user.name, + email: detailData.data.user.email, + department: detailData.data.user.department + }); + console.log('📈 測試結果:', { + type: detailData.data.result.type, + score: detailData.data.result.score, + completedAt: detailData.data.result.completedAt + }); + console.log('📝 題目數量:', detailData.data.questions?.length || 0); + console.log('💡 答案數量:', detailData.data.answers?.length || 0); + + if (detailData.data.questions && detailData.data.questions.length > 0) { + console.log('\n📋 第一題詳情:'); + const firstQuestion = detailData.data.questions[0]; + console.log(' 題目:', firstQuestion.question || firstQuestion.statement); + console.log(' 用戶答案:', firstQuestion.userAnswer); + if (firstQuestion.isCorrect !== undefined) { + console.log(' 是否正確:', firstQuestion.isCorrect ? '是' : '否'); + } + if (firstQuestion.score !== undefined) { + console.log(' 得分:', firstQuestion.score); + } + } + + if (detailData.data.result.type === 'combined' && detailData.data.result.details) { + console.log('\n🎯 綜合能力分析:'); + console.log(' 邏輯思維:', detailData.data.result.details.logicScore); + console.log(' 創意能力:', detailData.data.result.details.creativeScore); + console.log(' 能力平衡:', detailData.data.result.details.abilityBalance); + } + + } else { + console.error('❌ 詳細結果獲取失敗:', detailData.message); + } + } else { + console.log('⚠️ 沒有找到測試結果'); + } + + } catch (error) { + console.error('❌ 測試錯誤:', error.message); + } + + console.log('==============================\n'); +} + +testDetailAPI(); diff --git a/scripts/test-detailed-answers.js b/scripts/test-detailed-answers.js new file mode 100644 index 0000000..6098b45 --- /dev/null +++ b/scripts/test-detailed-answers.js @@ -0,0 +1,113 @@ +const fetch = require('node-fetch'); + +async function testDetailedAnswers() { + console.log('🔍 測試詳細答題結果功能'); + console.log('=============================='); + + try { + // 先獲取測試結果列表 + const listResponse = await fetch('http://localhost:3000/api/admin/test-results'); + const listData = await listResponse.json(); + + if (listData.success && listData.data.results.length > 0) { + // 測試綜合能力測試的詳細結果 + const combinedResult = listData.data.results.find(r => r.type === 'combined'); + if (combinedResult) { + console.log(`📋 測試綜合能力結果: ${combinedResult.userName}`); + console.log(` 分數: ${combinedResult.score}, 等級: ${combinedResult.grade}`); + + const detailResponse = await fetch(`http://localhost:3000/api/admin/test-results/detail?testResultId=${combinedResult.id}&testType=combined`); + const detailData = await detailResponse.json(); + + if (detailData.success) { + console.log('✅ 綜合能力詳細結果獲取成功'); + console.log('📊 用戶資訊:', { + name: detailData.data.user.name, + email: detailData.data.user.email, + department: detailData.data.user.department + }); + console.log('📈 測試結果:', { + type: detailData.data.result.type, + score: detailData.data.result.score, + completedAt: detailData.data.result.completedAt + }); + console.log('🎯 能力分析:', { + logicScore: detailData.data.result.details.logicScore, + creativeScore: detailData.data.result.details.creativeScore, + abilityBalance: detailData.data.result.details.abilityBalance + }); + console.log('📝 題目總數:', detailData.data.questions?.length || 0); + + if (detailData.data.questions && detailData.data.questions.length > 0) { + const logicQuestions = detailData.data.questions.filter(q => q.type === 'logic'); + const creativeQuestions = detailData.data.questions.filter(q => q.type === 'creative'); + + console.log(`\n🧠 邏輯思維題目: ${logicQuestions.length} 題`); + console.log(`💡 創意能力題目: ${creativeQuestions.length} 題`); + + if (logicQuestions.length > 0) { + console.log('\n📋 第一題邏輯題詳情:'); + const firstLogic = logicQuestions[0]; + console.log(' 題目:', firstLogic.question); + console.log(' 用戶答案:', firstLogic.userAnswer); + console.log(' 正確答案:', firstLogic.correctAnswer); + console.log(' 是否正確:', firstLogic.isCorrect ? '是' : '否'); + } + + if (creativeQuestions.length > 0) { + console.log('\n📋 第一題創意題詳情:'); + const firstCreative = creativeQuestions[0]; + console.log(' 題目:', firstCreative.statement); + console.log(' 用戶答案:', firstCreative.userAnswer); + console.log(' 得分:', firstCreative.score); + } + } + + } else { + console.error('❌ 綜合能力詳細結果獲取失敗:', detailData.message); + } + } else { + console.log('⚠️ 沒有找到綜合能力測試結果'); + } + + // 測試單一測試類型的詳細結果 + const singleResult = listData.data.results.find(r => r.type !== 'combined'); + if (singleResult) { + console.log(`\n📋 測試單一類型結果: ${singleResult.userName} - ${singleResult.type}`); + + const detailResponse = await fetch(`http://localhost:3000/api/admin/test-results/detail?testResultId=${singleResult.id}&testType=${singleResult.type}`); + const detailData = await detailResponse.json(); + + if (detailData.success) { + console.log('✅ 單一類型詳細結果獲取成功'); + console.log('📝 題目數量:', detailData.data.questions?.length || 0); + + if (detailData.data.questions && detailData.data.questions.length > 0) { + console.log('\n📋 第一題詳情:'); + const firstQuestion = detailData.data.questions[0]; + console.log(' 題目:', firstQuestion.question || firstQuestion.statement); + console.log(' 用戶答案:', firstQuestion.userAnswer); + if (firstQuestion.isCorrect !== undefined) { + console.log(' 是否正確:', firstQuestion.isCorrect ? '是' : '否'); + } + if (firstQuestion.score !== undefined) { + console.log(' 得分:', firstQuestion.score); + } + } + } else { + console.error('❌ 單一類型詳細結果獲取失敗:', detailData.message); + } + } + + } else { + console.log('⚠️ 沒有找到測試結果'); + } + + } catch (error) { + console.error('❌ 測試錯誤:', error.message); + } + + console.log('==============================\n'); +} + +testDetailedAnswers();