實作綜合題型結果與資料庫整合

This commit is contained in:
2025-09-29 17:24:31 +08:00
parent a3b46b4430
commit f6610013ef
21 changed files with 1341 additions and 92 deletions

View File

@@ -0,0 +1,108 @@
import { NextRequest, NextResponse } from 'next/server'
import { createCombinedTestResult } from '@/lib/database/models/combined_test_result'
export async function POST(request: NextRequest) {
let body: any
try {
body = await request.json()
const {
userId,
logicScore,
creativityScore,
overallScore,
level,
description,
logicBreakdown,
creativityBreakdown,
balanceScore,
completedAt
} = body
// 驗證必要欄位
if (!userId || logicScore === undefined || creativityScore === undefined ||
overallScore === undefined || !level || !completedAt) {
return NextResponse.json(
{ success: false, error: '缺少必要欄位' },
{ status: 400 }
)
}
console.log('🔄 開始建立綜合測試結果...')
console.log('用戶ID:', userId)
console.log('邏輯分數:', logicScore)
console.log('創意分數:', creativityScore)
console.log('總分:', overallScore)
console.log('等級:', level)
// 建立綜合測試結果
const testResult = await createCombinedTestResult({
user_id: userId,
logic_score: logicScore,
creativity_score: creativityScore,
overall_score: overallScore,
level: level,
description: description || null,
logic_breakdown: logicBreakdown || null,
creativity_breakdown: creativityBreakdown || null,
balance_score: balanceScore || 0,
completed_at: completedAt
})
if (!testResult) {
return NextResponse.json(
{ success: false, error: '建立綜合測試結果失敗' },
{ status: 500 }
)
}
console.log('✅ 綜合測試結果建立成功ID:', testResult.id)
return NextResponse.json({
success: true,
data: {
testResult
}
})
} catch (error) {
console.error('上傳綜合測試結果失敗:', error)
return NextResponse.json(
{
success: false,
error: '伺服器錯誤',
details: error instanceof Error ? error.message : '未知錯誤'
},
{ status: 500 }
)
}
}
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url)
const userId = searchParams.get('userId')
if (!userId) {
return NextResponse.json(
{ success: false, error: '缺少用戶ID' },
{ status: 400 }
)
}
// 獲取用戶的綜合測試結果
const { getCombinedTestResultsByUserId } = await import('@/lib/database/models/combined_test_result')
const results = await getCombinedTestResultsByUserId(userId)
return NextResponse.json({
success: true,
data: results
})
} catch (error) {
console.error('獲取綜合測試結果失敗:', error)
return NextResponse.json(
{ success: false, error: '伺服器錯誤' },
{ status: 500 }
)
}
}

View File

@@ -1,5 +1,7 @@
import { NextRequest, NextResponse } from 'next/server'
import { getTestResultsByUserId } from '@/lib/database/models/test_result'
import { getTestResultsByUserId, TestResult as DBTestResult } from '@/lib/database/models/test_result'
import { getCombinedTestResultsByUserId, CombinedTestResult } from '@/lib/database/models/combined_test_result'
import { findUserById } from '@/lib/database/models/user'
export async function GET(request: NextRequest) {
try {
@@ -13,38 +15,21 @@ export async function GET(request: NextRequest) {
)
}
// 獲取用戶的所有測試結果
const allResults = await getTestResultsByUserId(userId)
// 獲取邏輯和創意測試結果
const testResults = await getTestResultsByUserId(userId)
if (allResults.length === 0) {
return NextResponse.json({
success: true,
data: {
results: [],
stats: {
totalTests: 0,
averageScore: 0,
bestScore: 0,
lastTestDate: null,
testCounts: {
logic: 0,
creative: 0,
combined: 0
}
}
}
})
}
// 獲取綜合測試結果
const combinedResults = await getCombinedTestResultsByUserId(userId)
// 按測試類型分組,只保留每種類型的最新結果
const latestResults = {
logic: allResults.filter(r => r.test_type === 'logic').sort((a, b) =>
logic: testResults.filter(r => r.test_type === 'logic').sort((a, b) =>
new Date(b.completed_at).getTime() - new Date(a.completed_at).getTime()
)[0],
creative: allResults.filter(r => r.test_type === 'creative').sort((a, b) =>
creative: testResults.filter(r => r.test_type === 'creative').sort((a, b) =>
new Date(b.completed_at).getTime() - new Date(a.completed_at).getTime()
)[0],
combined: allResults.filter(r => r.test_type === 'combined').sort((a, b) =>
combined: combinedResults.sort((a, b) =>
new Date(b.completed_at).getTime() - new Date(a.completed_at).getTime()
)[0]
}
@@ -59,7 +44,10 @@ export async function GET(request: NextRequest) {
// 平均分數:基於每種類型測試的最新分數計算
const latestScores = Object.values(latestResults)
.filter(result => result !== undefined)
.map(result => result.score)
.map(result => {
// 綜合測試結果使用 overall_score其他使用 score
return 'overall_score' in result ? result.overall_score : result.score
})
const averageScore = latestScores.length > 0
? Math.round(latestScores.reduce((sum, score) => sum + score, 0) / latestScores.length)
@@ -69,32 +57,58 @@ export async function GET(request: NextRequest) {
const bestScore = latestScores.length > 0 ? Math.max(...latestScores) : 0
// 最近測試日期:基於所有測試結果
const lastTestDate = allResults.length > 0
? allResults.sort((a, b) =>
new Date(b.completed_at).getTime() - new Date(a.completed_at).getTime()
)[0]?.completed_at || null
: null
const allTestDates = [
...testResults.map(r => r.completed_at),
...combinedResults.map(r => r.completed_at)
].sort((a, b) => new Date(b).getTime() - new Date(a).getTime())
const lastTestDate = allTestDates.length > 0 ? allTestDates[0] : null
// 計算各類型測試次數
const testCounts = {
logic: allResults.filter(r => r.test_type === 'logic').length,
creative: allResults.filter(r => r.test_type === 'creative').length,
combined: allResults.filter(r => r.test_type === 'combined').length
logic: testResults.filter(r => r.test_type === 'logic').length,
creative: testResults.filter(r => r.test_type === 'creative').length,
combined: combinedResults.length
}
// 轉換為前端需要的格式
const formattedResults = results.map(result => ({
type: result.test_type,
score: result.score,
completedAt: result.completed_at,
testCount: testCounts[result.test_type as keyof typeof testCounts],
details: {
id: result.id,
total_questions: result.total_questions,
correct_answers: result.correct_answers,
created_at: result.created_at
const formattedResults = results.map(result => {
// 檢查是否為綜合測試結果
if ('overall_score' in result) {
// 綜合測試結果
const combinedResult = result as CombinedTestResult
return {
type: 'combined' as const,
score: combinedResult.overall_score,
completedAt: combinedResult.completed_at,
testCount: testCounts.combined,
details: {
id: combinedResult.id,
logic_score: combinedResult.logic_score,
creativity_score: combinedResult.creativity_score,
level: combinedResult.level,
description: combinedResult.description,
balance_score: combinedResult.balance_score,
created_at: combinedResult.created_at
}
}
} else {
// 邏輯或創意測試結果
const testResult = result as DBTestResult
return {
type: testResult.test_type,
score: testResult.score,
completedAt: testResult.completed_at,
testCount: testCounts[testResult.test_type as keyof typeof testCounts],
details: {
id: testResult.id,
total_questions: testResult.total_questions,
correct_answers: testResult.correct_answers,
created_at: testResult.created_at
}
}
}
}))
})
// 按完成時間排序(最新的在前)
formattedResults.sort((a, b) => new Date(b.completedAt).getTime() - new Date(a.completedAt).getTime())

View File

@@ -8,6 +8,7 @@ import { Progress } from "@/components/ui/progress"
import { Brain, Lightbulb, BarChart3, Home, RotateCcw, TrendingUp, Target, Award } from "lucide-react"
import Link from "next/link"
import { getRecommendations } from "@/lib/utils/score-calculator"
import { useAuth } from "@/lib/hooks/use-auth"
interface LogicQuestion {
id: number
@@ -51,6 +52,7 @@ interface CombinedTestResults {
}
export default function CombinedResultsPage() {
const { user } = useAuth()
const [results, setResults] = useState<CombinedTestResults | null>(null)
const [logicQuestions, setLogicQuestions] = useState<LogicQuestion[]>([])
const [creativeQuestions, setCreativeQuestions] = useState<CreativeQuestion[]>([])
@@ -58,11 +60,43 @@ export default function CombinedResultsPage() {
useEffect(() => {
const loadData = async () => {
if (!user) return
try {
// Load test results
const savedResults = localStorage.getItem("combinedTestResults")
if (savedResults) {
setResults(JSON.parse(savedResults))
// 從資料庫獲取最新的綜合測試結果
const response = await fetch(`/api/test-results/combined?userId=${user.id}`)
const data = await response.json()
if (data.success && data.data.length > 0) {
// 取最新的結果
const latestResult = data.data[0]
// 轉換為前端需要的格式
setResults({
type: "combined",
logicScore: latestResult.logic_score,
creativityScore: latestResult.creativity_score,
overallScore: latestResult.overall_score,
level: latestResult.level,
description: latestResult.description || "",
breakdown: {
logic: latestResult.logic_score,
creativity: latestResult.creativity_score,
balance: latestResult.balance_score
},
logicAnswers: latestResult.logic_breakdown?.answers || {},
creativeAnswers: latestResult.creativity_breakdown?.answers || {},
logicCorrect: latestResult.logic_breakdown?.correct || 0,
creativityTotal: latestResult.creativity_breakdown?.total || 0,
creativityMaxScore: latestResult.creativity_breakdown?.maxScore || 0,
completedAt: latestResult.completed_at
})
} else {
// 如果沒有資料庫結果,回退到 localStorage
const savedResults = localStorage.getItem("combinedTestResults")
if (savedResults) {
setResults(JSON.parse(savedResults))
}
}
// Load questions from database
@@ -80,13 +114,18 @@ export default function CombinedResultsPage() {
}
} catch (error) {
console.error('Error loading data:', error)
// 回退到 localStorage
const savedResults = localStorage.getItem("combinedTestResults")
if (savedResults) {
setResults(JSON.parse(savedResults))
}
} finally {
setIsLoading(false)
}
}
loadData()
}, [])
}, [user])
if (isLoading) {
return (

View File

@@ -9,6 +9,7 @@ import { Label } from "@/components/ui/label"
import { Progress } from "@/components/ui/progress"
import { useRouter } from "next/navigation"
import { calculateCombinedScore } from "@/lib/utils/score-calculator"
import { useAuth } from "@/lib/hooks/use-auth"
interface LogicQuestion {
id: number
@@ -35,6 +36,7 @@ type TestPhase = "logic" | "creative" | "completed"
export default function CombinedTestPage() {
const router = useRouter()
const { user } = useAuth()
const [phase, setPhase] = useState<TestPhase>("logic")
const [currentQuestion, setCurrentQuestion] = useState(0)
const [logicAnswers, setLogicAnswers] = useState<Record<number, string>>({})
@@ -43,6 +45,7 @@ export default function CombinedTestPage() {
const [logicQuestions, setLogicQuestions] = useState<LogicQuestion[]>([])
const [creativeQuestions, setCreativeQuestions] = useState<CreativeQuestion[]>([])
const [isLoading, setIsLoading] = useState(true)
const [isSubmitting, setIsSubmitting] = useState(false)
// Load questions from database
useEffect(() => {
@@ -148,47 +151,113 @@ export default function CombinedTestPage() {
}
}
const handleSubmit = () => {
// Calculate logic score
let logicCorrect = 0
logicQuestions.forEach((question, index) => {
if (logicAnswers[index] === question.correct_answer) {
logicCorrect++
}
})
const logicScore = Math.round((logicCorrect / logicQuestions.length) * 100)
const handleSubmit = async () => {
console.log('🔍 開始提交綜合測試...')
console.log('用戶狀態:', user)
// Calculate creativity score
let creativityTotal = 0
creativeQuestions.forEach((question, index) => {
const answer = creativeAnswers[index] || 1
creativityTotal += question.is_reverse ? 6 - answer : answer
})
const creativityMaxScore = creativeQuestions.length * 5
const creativityScore = Math.round((creativityTotal / creativityMaxScore) * 100)
// Calculate combined score
const combinedResult = calculateCombinedScore(logicScore, creativityScore)
// Store results
const results = {
type: "combined",
logicScore,
creativityScore,
overallScore: combinedResult.overallScore,
level: combinedResult.level,
description: combinedResult.description,
breakdown: combinedResult.breakdown,
logicAnswers,
creativeAnswers,
logicCorrect,
creativityTotal,
creativityMaxScore,
completedAt: new Date().toISOString(),
if (!user) {
console.log('❌ 用戶未登入')
alert('請先登入')
return
}
localStorage.setItem("combinedTestResults", JSON.stringify(results))
router.push("/results/combined")
console.log('✅ 用戶已登入用戶ID:', user.id)
setIsSubmitting(true)
try {
// Calculate logic score
let logicCorrect = 0
logicQuestions.forEach((question, index) => {
if (logicAnswers[index] === question.correct_answer) {
logicCorrect++
}
})
const logicScore = Math.round((logicCorrect / logicQuestions.length) * 100)
// Calculate creativity score
let creativityTotal = 0
creativeQuestions.forEach((question, index) => {
const answer = creativeAnswers[index] || 1
creativityTotal += question.is_reverse ? 6 - answer : answer
})
const creativityMaxScore = creativeQuestions.length * 5
const creativityScore = Math.round((creativityTotal / creativityMaxScore) * 100)
// Calculate combined score
const combinedResult = calculateCombinedScore(logicScore, creativityScore)
// Store results in localStorage (for backward compatibility)
const results = {
type: "combined",
logicScore,
creativityScore,
overallScore: combinedResult.overallScore,
level: combinedResult.level,
description: combinedResult.description,
breakdown: combinedResult.breakdown,
logicAnswers,
creativeAnswers,
logicCorrect,
creativityTotal,
creativityMaxScore,
completedAt: new Date().toISOString(),
}
localStorage.setItem("combinedTestResults", JSON.stringify(results))
console.log('✅ 結果已儲存到 localStorage')
// Upload to database
console.log('🔄 開始上傳到資料庫...')
const uploadData = {
userId: user.id,
logicScore,
creativityScore,
overallScore: combinedResult.overallScore,
level: combinedResult.level,
description: combinedResult.description,
logicBreakdown: {
correct: logicCorrect,
total: logicQuestions.length,
answers: logicAnswers
},
creativityBreakdown: {
total: creativityTotal,
maxScore: creativityMaxScore,
answers: creativeAnswers
},
balanceScore: combinedResult.breakdown.balance,
completedAt: new Date().toISOString()
}
console.log('上傳數據:', uploadData)
const uploadResponse = await fetch('/api/test-results/combined', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(uploadData)
})
console.log('📡 API 響應狀態:', uploadResponse.status)
const uploadResult = await uploadResponse.json()
console.log('📡 API 響應內容:', uploadResult)
if (uploadResult.success) {
console.log('✅ 綜合測試結果已上傳到資料庫')
console.log('測試結果ID:', uploadResult.data.testResult.id)
} else {
console.error('❌ 上傳到資料庫失敗:', uploadResult.error)
// 即使上傳失敗,也繼續顯示結果
}
router.push("/results/combined")
} catch (error) {
console.error('❌ 提交測驗失敗:', error)
alert('提交測驗失敗,請重試')
} finally {
setIsSubmitting(false)
}
}
const currentQuestions = getCurrentQuestions()
@@ -389,8 +458,8 @@ export default function CombinedTestPage() {
</div>
{isLastQuestion ? (
<Button onClick={handleSubmit} disabled={!hasAnswer} className="bg-green-600 hover:bg-green-700">
<Button onClick={handleSubmit} disabled={!hasAnswer || isSubmitting} className="bg-green-600 hover:bg-green-700">
{isSubmitting ? '提交中...' : '提交測試'}
</Button>
) : (
<Button onClick={handleNext} disabled={!hasAnswer}>

View File

@@ -135,7 +135,7 @@ export default function CreativeTestPage() {
const uploadData = {
userId: user.id,
answers: Object.values(answers),
completedAt: new Date().toISOString().replace('Z', '').replace('T', ' ')
completedAt: new Date().toISOString()
}
console.log('上傳數據:', uploadData)

View File

@@ -119,7 +119,7 @@ export default function LogicTestPage() {
})
const score = Math.round((correctAnswers / questions.length) * 100)
const completedAt = new Date().toISOString().replace('Z', '').replace('T', ' ')
const completedAt = new Date().toISOString()
console.log('📊 測驗結果計算:')
console.log('答對題數:', correctAnswers)