110 lines
5.0 KiB
TypeScript
110 lines
5.0 KiB
TypeScript
import { useState } from 'react'
|
|
import { useNavigate } from '@tanstack/react-router'
|
|
import { CircularProgress } from '@/components/CircularProgress'
|
|
import { useTestStore } from '@/store/test-store'
|
|
import { TOEIC_PARTS } from '@/data/mock-data'
|
|
import { fetchQuestions } from '@/hooks/use-questions'
|
|
import { useRequireAuth } from '@/hooks/use-require-auth'
|
|
|
|
export function ToeicPractice() {
|
|
const navigate = useNavigate()
|
|
const { startExam } = useTestStore()
|
|
const [loadingPartId, setLoadingPartId] = useState<number | null>(null)
|
|
const { requireAuth } = useRequireAuth()
|
|
|
|
async function handleSelectPart(partId: number, partName: string) {
|
|
if (!requireAuth()) return
|
|
setLoadingPartId(partId)
|
|
try {
|
|
const questions = await fetchQuestions(partId, 10)
|
|
startExam(partId, partName, questions)
|
|
navigate({ to: '/toeic/session' })
|
|
} catch (err) {
|
|
console.error('Failed to load questions:', err)
|
|
} finally {
|
|
setLoadingPartId(null)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="px-6 py-8 max-w-6xl mx-auto page-enter">
|
|
<div className="mb-8">
|
|
<h1 className="text-3xl font-extrabold text-slate-800 mb-2">Chọn Part TOEIC</h1>
|
|
<p className="text-slate-500">
|
|
Hệ thống ôn luyện theo cấu trúc bài thi TOEIC thực tế. Chọn phần cụ thể để bắt đầu.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 lg:grid-cols-4 gap-5">
|
|
{TOEIC_PARTS.map((part) => (
|
|
<button
|
|
key={part.id}
|
|
onClick={() => handleSelectPart(part.id, part.nameVi)}
|
|
disabled={loadingPartId !== null}
|
|
className="bg-white rounded-2xl p-5 border border-slate-200 text-left hover:-translate-y-1 hover:shadow-md transition-all duration-200 group disabled:opacity-70 disabled:cursor-wait"
|
|
>
|
|
<div className="flex justify-between items-start mb-5">
|
|
<div className="w-10 h-10 bg-blue-50 rounded-xl flex items-center justify-center group-hover:bg-blue-600 transition-colors">
|
|
{loadingPartId === part.id ? (
|
|
<span className="w-4 h-4 border-2 border-blue-300 border-t-blue-600 rounded-full animate-spin" />
|
|
) : (
|
|
<span
|
|
className="material-symbols-outlined text-blue-600 group-hover:text-white transition-colors"
|
|
style={{ fontSize: 18 }}
|
|
>
|
|
{part.icon}
|
|
</span>
|
|
)}
|
|
</div>
|
|
<CircularProgress percent={part.progressPercent} size={44} />
|
|
</div>
|
|
<div className="font-extrabold text-lg text-slate-800 mb-0.5">{part.name}</div>
|
|
<div className="text-sm font-semibold text-slate-700 mb-2">{part.nameVi}</div>
|
|
<div className="flex items-center gap-1.5 text-xs text-slate-400">
|
|
<span className="material-symbols-outlined" style={{ fontSize: 14 }}>list_alt</span>
|
|
{part.questionCount} câu hỏi
|
|
</div>
|
|
</button>
|
|
))}
|
|
|
|
{/* Full Test card */}
|
|
<button
|
|
onClick={() => handleSelectPart(0, 'Full Test')}
|
|
className="relative rounded-2xl p-5 text-left overflow-hidden hover:-translate-y-1 hover:shadow-xl transition-all duration-200"
|
|
style={{ background: 'linear-gradient(135deg, #f59e0b, #d97706)' }}
|
|
>
|
|
<div className="absolute top-0 right-0 opacity-10">
|
|
<span className="material-symbols-outlined text-white" style={{ fontSize: 80 }}>
|
|
workspace_premium
|
|
</span>
|
|
</div>
|
|
<div className="relative z-10">
|
|
<div className="w-10 h-10 bg-white/20 rounded-xl flex items-center justify-center mb-5">
|
|
<span className="material-symbols-outlined text-white" style={{ fontSize: 18 }}>
|
|
military_tech
|
|
</span>
|
|
</div>
|
|
<div className="font-extrabold text-2xl text-white mb-0.5">Full Test</div>
|
|
<div className="text-sm font-semibold text-amber-50 mb-2">Mô phỏng thi thật 2h</div>
|
|
<div className="flex items-center gap-1.5 text-xs text-amber-100">
|
|
<span className="material-symbols-outlined" style={{ fontSize: 14 }}>timer</span>
|
|
120 phút · 200 câu
|
|
</div>
|
|
</div>
|
|
</button>
|
|
</div>
|
|
|
|
{/* Tip */}
|
|
<div className="mt-8 bg-blue-50 border border-blue-100 rounded-2xl p-5 flex items-start gap-4">
|
|
<span className="material-symbols-outlined text-blue-600 flex-shrink-0 mt-0.5">tips_and_updates</span>
|
|
<div>
|
|
<div className="font-semibold text-blue-700 text-sm mb-1">Mẹo luyện thi</div>
|
|
<p className="text-slate-500 text-sm">
|
|
Bắt đầu từ <strong>Part 5 (Điền từ)</strong> — phần mang lại điểm nhanh nhất vì không phụ thuộc kỹ năng nghe. Mỗi ngày 20 câu, sau 2 tuần bạn sẽ thấy cải thiện rõ rệt.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|