修改測驗時間、新增測驗提醒、新增截止測驗功能

This commit is contained in:
2025-10-12 00:18:24 +08:00
parent 967541a492
commit cf40e937a1
7 changed files with 176 additions and 19 deletions

View File

@@ -26,6 +26,7 @@ interface TestResult {
type: "logic" | "creative" | "combined"
score: number
completedAt: string
isTimeout?: boolean
details?: {
logicScore?: number
creativeScore?: number
@@ -437,11 +438,18 @@ function AdminResultDetailContent() {
>
<span className="text-3xl font-bold text-white">{result.score}</span>
</div>
<CardTitle className="text-3xl mb-2"></CardTitle>
<CardTitle className="text-3xl mb-2">
{result.isTimeout ? '(時間到)' : ''}
</CardTitle>
<div className="flex items-center justify-center gap-2 mb-4">
<Badge variant="secondary" className="text-lg px-4 py-1">
{scoreLevel.level}
</Badge>
{result.isTimeout && (
<Badge variant="destructive" className="text-lg px-4 py-1">
</Badge>
)}
</div>
<p className="text-lg text-muted-foreground mb-3">{scoreLevel.description}</p>
<div className="bg-muted/50 rounded-lg p-4 text-sm">
@@ -482,11 +490,18 @@ function AdminResultDetailContent() {
>
<span className="text-3xl font-bold text-white">{result.score}</span>
</div>
<CardTitle className="text-3xl mb-2"></CardTitle>
<CardTitle className="text-3xl mb-2">
{result.isTimeout ? '(時間到)' : ''}
</CardTitle>
<div className="flex items-center justify-center gap-2 mb-4">
<Badge variant="secondary" className="text-lg px-4 py-1">
{scoreLevel.level}
</Badge>
{result.isTimeout && (
<Badge variant="destructive" className="text-lg px-4 py-1">
</Badge>
)}
</div>
<p className="text-lg text-muted-foreground mb-3">{scoreLevel.description}</p>
<div className="bg-muted/50 rounded-lg p-4 text-sm">

View File

@@ -37,6 +37,7 @@ export async function GET(request: NextRequest) {
type: "combined",
score: combinedResult.overall_score,
completedAt: combinedResult.completed_at,
isTimeout: combinedResult.is_timeout || false,
details: {
logicScore: combinedResult.logic_score,
creativeScore: combinedResult.creativity_score,

View File

@@ -15,7 +15,8 @@ export async function POST(request: NextRequest) {
logicBreakdown,
creativityBreakdown,
balanceScore,
completedAt
completedAt,
isTimeout = false
} = body
// 驗證必要欄位
@@ -51,7 +52,8 @@ export async function POST(request: NextRequest) {
logic_breakdown: logicBreakdown || null,
creativity_breakdown: creativityBreakdown || null,
balance_score: balanceScore || 0,
completed_at: mysqlCompletedAt
completed_at: mysqlCompletedAt,
is_timeout: isTimeout
})
if (!testResult) {

View File

@@ -9,7 +9,8 @@ export async function POST(request: NextRequest) {
const {
userId,
answers,
completedAt
completedAt,
isTimeout = false
} = body
// 驗證必要欄位
@@ -75,7 +76,8 @@ export async function POST(request: NextRequest) {
score: score,
total_questions: questions.length,
correct_answers: correctAnswers,
completed_at: mysqlCompletedAt
completed_at: mysqlCompletedAt,
is_timeout: isTimeout
})
console.log('測試結果建立結果:', testResult)

View File

@@ -41,7 +41,8 @@ export default function CombinedTestPage() {
const [currentQuestion, setCurrentQuestion] = useState(0)
const [logicAnswers, setLogicAnswers] = useState<Record<number, string>>({})
const [creativeAnswers, setCreativeAnswers] = useState<Record<number, number>>({})
const [timeRemaining, setTimeRemaining] = useState(45 * 60) // 45 minutes total
const [timeRemaining, setTimeRemaining] = useState(30 * 60) // 30 minutes total
const [hasShownWarning, setHasShownWarning] = useState(false)
const [logicQuestions, setLogicQuestions] = useState<LogicQuestion[]>([])
const [creativeQuestions, setCreativeQuestions] = useState<CreativeQuestion[]>([])
const [isLoading, setIsLoading] = useState(true)
@@ -81,16 +82,27 @@ export default function CombinedTestPage() {
const timer = setInterval(() => {
setTimeRemaining((prev) => {
if (prev <= 1) {
handleSubmit()
const newTime = prev - 1
// 檢查是否剩餘5分鐘300秒且尚未顯示警告
if (newTime <= 300 && !hasShownWarning) {
setHasShownWarning(true)
alert('⚠️ 注意距離測試結束還有5分鐘請盡快完成剩餘題目')
}
// 時間到,強制提交
if (newTime <= 0) {
console.log('⏰ 時間到!強制提交測驗...')
handleTimeoutSubmit()
return 0
}
return prev - 1
return newTime
})
}, 1000)
return () => clearInterval(timer)
}, [logicQuestions, creativeQuestions])
}, [logicQuestions, creativeQuestions, hasShownWarning])
const formatTime = (seconds: number) => {
const mins = Math.floor(seconds / 60)
@@ -151,6 +163,121 @@ export default function CombinedTestPage() {
}
}
const handleTimeoutSubmit = async () => {
console.log('⏰ 時間到!強制提交綜合測試...')
console.log('用戶狀態:', user)
if (!user) {
console.log('❌ 用戶未登入')
alert('⏰ 時間到!但用戶未登入,無法提交結果。')
return
}
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
const processedCreativeAnswers: Record<number, number> = {}
creativeQuestions.forEach((question, index) => {
const answer = creativeAnswers[index] || 1
const processedScore = question.is_reverse ? 6 - answer : answer
creativityTotal += processedScore
processedCreativeAnswers[index] = processedScore
})
const creativityMaxScore = creativeQuestions.length * 5
const creativityScore = Math.round((creativityTotal / creativityMaxScore) * 100)
// Calculate combined score
const combinedResult = calculateCombinedScore(logicScore, creativityScore)
// Store results in localStorage with timeout flag
const results = {
type: "combined",
logicScore,
creativityScore,
overallScore: combinedResult.overallScore,
level: combinedResult.level,
description: combinedResult.description,
breakdown: combinedResult.breakdown,
logicAnswers,
creativeAnswers: processedCreativeAnswers,
logicCorrect,
creativityTotal,
creativityMaxScore,
completedAt: new Date().toISOString(),
isTimeout: true // 標記為時間到強制提交
}
localStorage.setItem("combinedTestResults", JSON.stringify(results))
console.log('✅ 強制提交結果已儲存到 localStorage')
// Upload to database with timeout flag
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: processedCreativeAnswers
},
balanceScore: combinedResult.breakdown.balance,
completedAt: new Date().toISOString(),
isTimeout: true // 標記為時間到強制提交
}
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)
}
// 顯示時間到提示
alert('⏰ 測試時間已到!系統已自動提交您的答案。')
router.push("/results/combined")
} catch (error) {
console.error('❌ 強制提交測驗失敗:', error)
alert('⏰ 時間到!但提交失敗,請聯繫管理員。')
} finally {
setIsSubmitting(false)
}
}
const handleSubmit = async () => {
console.log('🔍 開始提交綜合測試...')
console.log('用戶狀態:', user)
@@ -204,6 +331,7 @@ export default function CombinedTestPage() {
creativityTotal,
creativityMaxScore,
completedAt: new Date().toISOString(),
isTimeout: false // 標記為正常提交
}
localStorage.setItem("combinedTestResults", JSON.stringify(results))
@@ -229,7 +357,8 @@ export default function CombinedTestPage() {
answers: processedCreativeAnswers
},
balanceScore: combinedResult.breakdown.balance,
completedAt: new Date().toISOString()
completedAt: new Date().toISOString(),
isTimeout: false // 標記為正常提交
}
console.log('上傳數據:', uploadData)

View File

@@ -13,6 +13,7 @@ export interface CombinedTestResult {
creativity_breakdown: any | null // JSON 格式
balance_score: number
completed_at: string
is_timeout: boolean
created_at?: string
}
@@ -27,6 +28,7 @@ export interface CreateCombinedTestResultData {
creativity_breakdown: any | null
balance_score: number
completed_at: string
is_timeout: boolean
}
// 建立綜合測試結果表(如果不存在)
@@ -44,6 +46,7 @@ export async function createCombinedTestResultsTable(): Promise<void> {
creativity_breakdown JSON NULL,
balance_score INT NOT NULL,
completed_at TIMESTAMP NOT NULL,
is_timeout BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_user_id (user_id),
INDEX idx_completed_at (completed_at)
@@ -62,8 +65,8 @@ export async function createCombinedTestResult(resultData: CreateCombinedTestRes
INSERT INTO combined_test_results (
id, user_id, logic_score, creativity_score, overall_score,
level, description, logic_breakdown, creativity_breakdown,
balance_score, completed_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
balance_score, completed_at, is_timeout
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`
const values = [
@@ -77,7 +80,8 @@ export async function createCombinedTestResult(resultData: CreateCombinedTestRes
resultData.logic_breakdown ? JSON.stringify(resultData.logic_breakdown) : null,
resultData.creativity_breakdown ? JSON.stringify(resultData.creativity_breakdown) : null,
resultData.balance_score,
resultData.completed_at
resultData.completed_at,
resultData.is_timeout
]
try {

View File

@@ -8,6 +8,7 @@ export interface TestResult {
total_questions: number
correct_answers: number
completed_at: string
is_timeout: boolean
created_at?: string
}
@@ -18,6 +19,7 @@ export interface CreateTestResultData {
total_questions: number
correct_answers: number
completed_at: string
is_timeout: boolean
}
// 建立測試結果表(如果不存在)
@@ -31,6 +33,7 @@ export async function createTestResultsTable(): Promise<void> {
total_questions INT NOT NULL,
correct_answers INT NOT NULL,
completed_at TIMESTAMP NOT NULL,
is_timeout BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_user_id (user_id),
INDEX idx_test_type (test_type),
@@ -50,8 +53,8 @@ export async function createTestResult(resultData: CreateTestResultData): Promis
const insertQuery = `
INSERT INTO test_results (
id, user_id, test_type, score, total_questions,
correct_answers, completed_at
) VALUES (?, ?, ?, ?, ?, ?, ?)
correct_answers, completed_at, is_timeout
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`
await executeQuery(insertQuery, [
@@ -61,7 +64,8 @@ export async function createTestResult(resultData: CreateTestResultData): Promis
resultData.score,
resultData.total_questions,
resultData.correct_answers,
resultData.completed_at
resultData.completed_at,
resultData.is_timeout
])
const result = await getTestResultById(id)