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

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" type: "logic" | "creative" | "combined"
score: number score: number
completedAt: string completedAt: string
isTimeout?: boolean
details?: { details?: {
logicScore?: number logicScore?: number
creativeScore?: number creativeScore?: number
@@ -437,11 +438,18 @@ function AdminResultDetailContent() {
> >
<span className="text-3xl font-bold text-white">{result.score}</span> <span className="text-3xl font-bold text-white">{result.score}</span>
</div> </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"> <div className="flex items-center justify-center gap-2 mb-4">
<Badge variant="secondary" className="text-lg px-4 py-1"> <Badge variant="secondary" className="text-lg px-4 py-1">
{scoreLevel.level} {scoreLevel.level}
</Badge> </Badge>
{result.isTimeout && (
<Badge variant="destructive" className="text-lg px-4 py-1">
</Badge>
)}
</div> </div>
<p className="text-lg text-muted-foreground mb-3">{scoreLevel.description}</p> <p className="text-lg text-muted-foreground mb-3">{scoreLevel.description}</p>
<div className="bg-muted/50 rounded-lg p-4 text-sm"> <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> <span className="text-3xl font-bold text-white">{result.score}</span>
</div> </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"> <div className="flex items-center justify-center gap-2 mb-4">
<Badge variant="secondary" className="text-lg px-4 py-1"> <Badge variant="secondary" className="text-lg px-4 py-1">
{scoreLevel.level} {scoreLevel.level}
</Badge> </Badge>
{result.isTimeout && (
<Badge variant="destructive" className="text-lg px-4 py-1">
</Badge>
)}
</div> </div>
<p className="text-lg text-muted-foreground mb-3">{scoreLevel.description}</p> <p className="text-lg text-muted-foreground mb-3">{scoreLevel.description}</p>
<div className="bg-muted/50 rounded-lg p-4 text-sm"> <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", type: "combined",
score: combinedResult.overall_score, score: combinedResult.overall_score,
completedAt: combinedResult.completed_at, completedAt: combinedResult.completed_at,
isTimeout: combinedResult.is_timeout || false,
details: { details: {
logicScore: combinedResult.logic_score, logicScore: combinedResult.logic_score,
creativeScore: combinedResult.creativity_score, creativeScore: combinedResult.creativity_score,

View File

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

View File

@@ -6,10 +6,11 @@ import { getAllLogicQuestions } from '@/lib/database/models/logic_question'
export async function POST(request: NextRequest) { export async function POST(request: NextRequest) {
try { try {
const body = await request.json() const body = await request.json()
const { const {
userId, userId,
answers, answers,
completedAt completedAt,
isTimeout = false
} = body } = body
// 驗證必要欄位 // 驗證必要欄位
@@ -75,7 +76,8 @@ export async function POST(request: NextRequest) {
score: score, score: score,
total_questions: questions.length, total_questions: questions.length,
correct_answers: correctAnswers, correct_answers: correctAnswers,
completed_at: mysqlCompletedAt completed_at: mysqlCompletedAt,
is_timeout: isTimeout
}) })
console.log('測試結果建立結果:', testResult) console.log('測試結果建立結果:', testResult)

View File

@@ -41,7 +41,8 @@ export default function CombinedTestPage() {
const [currentQuestion, setCurrentQuestion] = useState(0) const [currentQuestion, setCurrentQuestion] = useState(0)
const [logicAnswers, setLogicAnswers] = useState<Record<number, string>>({}) const [logicAnswers, setLogicAnswers] = useState<Record<number, string>>({})
const [creativeAnswers, setCreativeAnswers] = useState<Record<number, number>>({}) 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 [logicQuestions, setLogicQuestions] = useState<LogicQuestion[]>([])
const [creativeQuestions, setCreativeQuestions] = useState<CreativeQuestion[]>([]) const [creativeQuestions, setCreativeQuestions] = useState<CreativeQuestion[]>([])
const [isLoading, setIsLoading] = useState(true) const [isLoading, setIsLoading] = useState(true)
@@ -81,16 +82,27 @@ export default function CombinedTestPage() {
const timer = setInterval(() => { const timer = setInterval(() => {
setTimeRemaining((prev) => { setTimeRemaining((prev) => {
if (prev <= 1) { const newTime = prev - 1
handleSubmit()
// 檢查是否剩餘5分鐘300秒且尚未顯示警告
if (newTime <= 300 && !hasShownWarning) {
setHasShownWarning(true)
alert('⚠️ 注意距離測試結束還有5分鐘請盡快完成剩餘題目')
}
// 時間到,強制提交
if (newTime <= 0) {
console.log('⏰ 時間到!強制提交測驗...')
handleTimeoutSubmit()
return 0 return 0
} }
return prev - 1
return newTime
}) })
}, 1000) }, 1000)
return () => clearInterval(timer) return () => clearInterval(timer)
}, [logicQuestions, creativeQuestions]) }, [logicQuestions, creativeQuestions, hasShownWarning])
const formatTime = (seconds: number) => { const formatTime = (seconds: number) => {
const mins = Math.floor(seconds / 60) 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 () => { const handleSubmit = async () => {
console.log('🔍 開始提交綜合測試...') console.log('🔍 開始提交綜合測試...')
console.log('用戶狀態:', user) console.log('用戶狀態:', user)
@@ -204,6 +331,7 @@ export default function CombinedTestPage() {
creativityTotal, creativityTotal,
creativityMaxScore, creativityMaxScore,
completedAt: new Date().toISOString(), completedAt: new Date().toISOString(),
isTimeout: false // 標記為正常提交
} }
localStorage.setItem("combinedTestResults", JSON.stringify(results)) localStorage.setItem("combinedTestResults", JSON.stringify(results))
@@ -229,7 +357,8 @@ export default function CombinedTestPage() {
answers: processedCreativeAnswers answers: processedCreativeAnswers
}, },
balanceScore: combinedResult.breakdown.balance, balanceScore: combinedResult.breakdown.balance,
completedAt: new Date().toISOString() completedAt: new Date().toISOString(),
isTimeout: false // 標記為正常提交
} }
console.log('上傳數據:', uploadData) console.log('上傳數據:', uploadData)

View File

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

View File

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