實作邏輯題結果與資料庫整合

This commit is contained in:
2025-09-29 02:56:23 +08:00
parent 0887347116
commit aa34d2d078
31 changed files with 1839 additions and 88 deletions

27
app/api/test-db/route.ts Normal file
View File

@@ -0,0 +1,27 @@
import { NextResponse } from 'next/server'
import { testConnection } from '@/lib/database/connection'
export async function GET() {
try {
const isConnected = await testConnection()
if (isConnected) {
return NextResponse.json({
success: true,
message: '資料庫連接正常'
})
} else {
return NextResponse.json({
success: false,
message: '資料庫連接失敗'
}, { status: 500 })
}
} catch (error) {
console.error('資料庫測試失敗:', error)
return NextResponse.json({
success: false,
message: '資料庫測試失敗',
error: error instanceof Error ? error.message : '未知錯誤'
}, { status: 500 })
}
}

View File

@@ -0,0 +1,149 @@
import { NextRequest, NextResponse } from 'next/server'
import { createTestResult, getTestResultsByUserId } from '@/lib/database/models/test_result'
import { createLogicTestAnswers } from '@/lib/database/models/logic_test_answer'
import { getAllLogicQuestions } from '@/lib/database/models/logic_question'
export async function POST(request: NextRequest) {
try {
const body = await request.json()
const {
userId,
answers,
completedAt
} = body
// 驗證必要欄位
if (!userId || !answers || !completedAt) {
return NextResponse.json(
{ success: false, error: '缺少必要欄位' },
{ status: 400 }
)
}
// 獲取邏輯題目
const questions = await getAllLogicQuestions()
if (questions.length === 0) {
return NextResponse.json(
{ success: false, error: '無法獲取題目' },
{ status: 500 }
)
}
// 計算分數
let correctAnswers = 0
const answerRecords = []
for (let i = 0; i < questions.length; i++) {
const question = questions[i]
const userAnswer = answers[i] || ''
const isCorrect = userAnswer === question.correct_answer
if (isCorrect) {
correctAnswers++
}
answerRecords.push({
test_result_id: '', // 稍後填入
question_id: question.id,
user_answer: userAnswer as 'A' | 'B' | 'C' | 'D' | 'E',
is_correct: isCorrect
})
}
const score = Math.round((correctAnswers / questions.length) * 100)
// 建立測試結果
console.log('🔄 開始建立測試結果...')
console.log('測試結果數據:', {
user_id: userId,
test_type: 'logic',
score: score,
total_questions: questions.length,
correct_answers: correctAnswers,
completed_at: completedAt
})
const testResult = await createTestResult({
user_id: userId,
test_type: 'logic',
score: score,
total_questions: questions.length,
correct_answers: correctAnswers,
completed_at: completedAt
})
console.log('測試結果建立結果:', testResult)
if (!testResult) {
console.error('❌ 建立測試結果失敗')
return NextResponse.json(
{ success: false, error: '建立測試結果失敗' },
{ status: 500 }
)
}
console.log('✅ 測試結果建立成功:', testResult.id)
// 更新答案記錄的 test_result_id
answerRecords.forEach(record => {
record.test_result_id = testResult.id
})
// 建立答案記錄
const answerResults = await createLogicTestAnswers(answerRecords)
return NextResponse.json({
success: true,
data: {
testResult,
answerCount: answerResults.length
}
})
} catch (error) {
console.error('上傳邏輯測驗結果失敗:', error)
console.error('錯誤詳情:', {
message: error instanceof Error ? error.message : '未知錯誤',
stack: error instanceof Error ? error.stack : undefined,
body: body
})
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 results = await getTestResultsByUserId(userId)
const logicResults = results.filter(result => result.test_type === 'logic')
return NextResponse.json({
success: true,
data: logicResults
})
} catch (error) {
console.error('獲取邏輯測驗結果失敗:', error)
return NextResponse.json(
{ success: false, error: '伺服器錯誤' },
{ status: 500 }
)
}
}

View File

@@ -0,0 +1,123 @@
import { NextRequest, NextResponse } from 'next/server'
import { getTestResultsByUserId } from '@/lib/database/models/test_result'
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 allResults = 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 latestResults = {
logic: allResults.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) =>
new Date(b.completed_at).getTime() - new Date(a.completed_at).getTime()
)[0],
combined: allResults.filter(r => r.test_type === 'combined').sort((a, b) =>
new Date(b.completed_at).getTime() - new Date(a.completed_at).getTime()
)[0]
}
// 只保留有結果的測試類型
const results = Object.values(latestResults).filter(result => result !== undefined)
// 計算統計數據
// 完成測試:基於每種類型測試是否有最新結果
const totalTests = Object.values(latestResults).filter(result => result !== undefined).length
// 平均分數:基於每種類型測試的最新分數計算
const latestScores = Object.values(latestResults)
.filter(result => result !== undefined)
.map(result => result.score)
const averageScore = latestScores.length > 0
? Math.round(latestScores.reduce((sum, score) => sum + score, 0) / latestScores.length)
: 0
// 最高分數:基於每種類型測試的最新分數
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 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
}
// 轉換為前端需要的格式
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
}
}))
// 按完成時間排序(最新的在前)
formattedResults.sort((a, b) => new Date(b.completedAt).getTime() - new Date(a.completedAt).getTime())
return NextResponse.json({
success: true,
data: {
results: formattedResults,
stats: {
totalTests,
averageScore,
bestScore,
lastTestDate,
testCounts
}
}
})
} catch (error) {
console.error('獲取用戶測試結果失敗:', error)
return NextResponse.json(
{ success: false, error: '伺服器錯誤' },
{ status: 500 }
)
}
}

View File

@@ -204,7 +204,7 @@ export default function CombinedResultsPage() {
<div>
<h1 className="text-xl font-bold text-foreground"></h1>
<p className="text-sm text-muted-foreground">
{new Date(results.completedAt).toLocaleString("zh-TW")}
{new Date(results.completedAt).toLocaleString("zh-TW", { timeZone: "Asia/Taipei" })}
</p>
</div>
</div>

View File

@@ -113,7 +113,7 @@ export default function CreativeResultsPage() {
<div>
<h1 className="text-xl font-bold text-foreground"></h1>
<p className="text-sm text-muted-foreground">
{new Date(results.completedAt).toLocaleString("zh-TW")}
{new Date(results.completedAt).toLocaleString("zh-TW", { timeZone: "Asia/Taipei" })}
</p>
</div>
</div>

View File

@@ -138,7 +138,7 @@ export default function LogicResultsPage() {
<div>
<h1 className="text-xl font-bold text-foreground"></h1>
<p className="text-sm text-muted-foreground">
{new Date(results.completedAt).toLocaleString("zh-TW")}
{new Date(results.completedAt).toLocaleString("zh-TW", { timeZone: "Asia/Taipei" })}
</p>
</div>
</div>

View File

@@ -14,6 +14,7 @@ interface TestResult {
type: "logic" | "creative" | "combined"
score: number
completedAt: string
testCount?: number
details?: any
}
@@ -33,66 +34,34 @@ function ResultsContent() {
averageScore: 0,
bestScore: 0,
lastTestDate: null as string | null,
testCounts: {
logic: 0,
creative: 0,
combined: 0
}
})
useEffect(() => {
// Load all test results from localStorage
const logicResults = localStorage.getItem("logicTestResults")
const creativeResults = localStorage.getItem("creativeTestResults")
const combinedResults = localStorage.getItem("combinedTestResults")
const loadUserResults = async () => {
if (!user) return
const allResults: TestResult[] = []
try {
const response = await fetch(`/api/user/test-results?userId=${user.id}`)
const data = await response.json()
if (logicResults) {
const data = JSON.parse(logicResults)
allResults.push({
type: "logic",
score: data.score,
completedAt: data.completedAt,
details: data,
})
if (data.success) {
setResults(data.data.results)
setStats(data.data.stats)
} else {
console.error('Failed to load user results:', data.error)
}
} catch (error) {
console.error('Error loading user results:', error)
}
}
if (creativeResults) {
const data = JSON.parse(creativeResults)
allResults.push({
type: "creative",
score: data.score,
completedAt: data.completedAt,
details: data,
})
}
if (combinedResults) {
const data = JSON.parse(combinedResults)
allResults.push({
type: "combined",
score: data.overallScore,
completedAt: data.completedAt,
details: data,
})
}
// Sort by completion date (newest first)
allResults.sort((a, b) => new Date(b.completedAt).getTime() - new Date(a.completedAt).getTime())
setResults(allResults)
// Calculate statistics
if (allResults.length > 0) {
const totalScore = allResults.reduce((sum, result) => sum + result.score, 0)
const averageScore = Math.round(totalScore / allResults.length)
const bestScore = Math.max(...allResults.map((r) => r.score))
const lastTestDate = allResults[0].completedAt
setStats({
totalTests: allResults.length,
averageScore,
bestScore,
lastTestDate,
})
}
}, [])
loadUserResults()
}, [user])
const getTestTypeInfo = (type: string) => {
switch (type) {
@@ -263,7 +232,7 @@ function ResultsContent() {
<Calendar className="w-6 h-6 text-accent" />
</div>
<div className="text-sm font-bold text-foreground mb-1">
{stats.lastTestDate ? new Date(stats.lastTestDate).toLocaleDateString("zh-TW") : "無"}
{stats.lastTestDate ? new Date(stats.lastTestDate).toLocaleDateString("zh-TW", { timeZone: "Asia/Taipei" }) : "無"}
</div>
<div className="text-sm text-muted-foreground"></div>
</CardContent>
@@ -295,8 +264,13 @@ function ResultsContent() {
<div className="min-w-0 flex-1">
<h3 className="font-medium text-foreground">{testInfo.name}</h3>
<p className="text-sm text-muted-foreground">
{new Date(result.completedAt).toLocaleString("zh-TW")}
{new Date(result.completedAt).toLocaleString("zh-TW", { timeZone: "Asia/Taipei" })}
</p>
{result.testCount && result.testCount > 1 && (
<p className="text-xs text-muted-foreground mt-1">
{result.testCount}
</p>
)}
</div>
</div>

View File

@@ -290,11 +290,11 @@ export default function CombinedTestPage() {
{phase === "logic"
? // Logic question options
[
{ value: 'A', text: currentQ.option_a },
{ value: 'B', text: currentQ.option_b },
{ value: 'C', text: currentQ.option_c },
{ value: 'D', text: currentQ.option_d },
{ value: 'E', text: currentQ.option_e }
{ value: 'A', text: (currentQ as LogicQuestion).option_a },
{ value: 'B', text: (currentQ as LogicQuestion).option_b },
{ value: 'C', text: (currentQ as LogicQuestion).option_c },
{ value: 'D', text: (currentQ as LogicQuestion).option_d },
{ value: 'E', text: (currentQ as LogicQuestion).option_e }
].map((option, index) => (
<div
key={index}

View File

@@ -7,6 +7,7 @@ import { Button } from "@/components/ui/button"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
import { Label } from "@/components/ui/label"
import { useRouter } from "next/navigation"
import { useAuth } from "@/lib/hooks/use-auth"
interface LogicQuestion {
id: number
@@ -23,11 +24,13 @@ interface LogicQuestion {
export default function LogicTestPage() {
const router = useRouter()
const { user } = useAuth()
const [questions, setQuestions] = useState<LogicQuestion[]>([])
const [currentQuestion, setCurrentQuestion] = useState(0)
const [answers, setAnswers] = useState<Record<number, string>>({})
const [timeRemaining, setTimeRemaining] = useState(20 * 60) // 20 minutes in seconds
const [isLoading, setIsLoading] = useState(true)
const [isSubmitting, setIsSubmitting] = useState(false)
// Load questions from database
useEffect(() => {
@@ -93,29 +96,88 @@ export default function LogicTestPage() {
}
}
const handleSubmit = () => {
// Calculate score
let correctAnswers = 0
questions.forEach((question, index) => {
if (answers[index] === question.correct_answer) {
correctAnswers++
}
})
const score = Math.round((correctAnswers / questions.length) * 100)
// Store results in localStorage
const results = {
type: "logic",
score,
correctAnswers,
totalQuestions: questions.length,
answers,
completedAt: new Date().toISOString(),
const handleSubmit = async () => {
console.log('🔍 開始提交邏輯測驗...')
console.log('用戶狀態:', user)
if (!user) {
console.log('❌ 用戶未登入')
alert('請先登入')
return
}
localStorage.setItem("logicTestResults", JSON.stringify(results))
router.push("/results/logic")
console.log('✅ 用戶已登入用戶ID:', user.id)
setIsSubmitting(true)
try {
// Calculate score
let correctAnswers = 0
questions.forEach((question, index) => {
if (answers[index] === question.correct_answer) {
correctAnswers++
}
})
const score = Math.round((correctAnswers / questions.length) * 100)
const completedAt = new Date().toISOString().replace('Z', '').replace('T', ' ')
console.log('📊 測驗結果計算:')
console.log('答對題數:', correctAnswers)
console.log('總題數:', questions.length)
console.log('分數:', score)
console.log('完成時間:', completedAt)
// Store results in localStorage (for backward compatibility)
const results = {
type: "logic",
score,
correctAnswers,
totalQuestions: questions.length,
answers,
completedAt,
}
localStorage.setItem("logicTestResults", JSON.stringify(results))
console.log('✅ 結果已儲存到 localStorage')
// Upload to database
console.log('🔄 開始上傳到資料庫...')
const uploadData = {
userId: user.id,
answers: Object.values(answers),
completedAt: completedAt
}
console.log('上傳數據:', uploadData)
const uploadResponse = await fetch('/api/test-results/logic', {
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)
console.log('答案記錄數量:', uploadResult.data.answerCount)
} else {
console.error('❌ 上傳到資料庫失敗:', uploadResult.error)
// 即使上傳失敗,也繼續顯示結果
}
router.push("/results/logic")
} catch (error) {
console.error('❌ 提交測驗失敗:', error)
alert('提交測驗失敗,請重試')
} finally {
setIsSubmitting(false)
}
}
if (isLoading) {
@@ -204,11 +266,11 @@ export default function LogicTestPage() {
{isLastQuestion ? (
<Button
onClick={handleSubmit}
disabled={!hasAnswer}
disabled={!hasAnswer || isSubmitting}
className="bg-green-600 hover:bg-green-700"
size="sm"
>
{isSubmitting ? '提交中...' : '提交測試'}
</Button>
) : (
<Button onClick={handleNext} disabled={!hasAnswer} size="sm">