This commit is contained in:
2026-04-12 18:54:59 +07:00
parent 28e866a64e
commit ec3d400e8a
71 changed files with 7888 additions and 333 deletions

View File

@@ -1,25 +1,108 @@
const PARTS = [
{ id: 1, name: "Part 1", desc: "Photographs" },
{ id: 2, name: "Part 2", desc: "Question-Response" },
{ id: 3, name: "Part 3", desc: "Conversations" },
{ id: 4, name: "Part 4", desc: "Short Talks" },
{ id: 5, name: "Part 5", desc: "Incomplete Sentences" },
{ id: 6, name: "Part 6", desc: "Text Completion" },
{ id: 7, name: "Part 7", desc: "Reading Comprehension" },
]
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="space-y-6">
<h1 className="text-2xl font-bold">Luyện đ TOEIC</h1>
<p className="text-gray-600">Chọn Part đ bắt đu luyện tập</p>
<div className="grid gap-3 sm:grid-cols-2">
{PARTS.map((part) => (
<div key={part.id} className="rounded-lg border p-4 cursor-pointer hover:bg-gray-50">
<div className="font-semibold">{part.name}</div>
<div className="text-sm text-gray-500">{part.desc}</div>
</div>
<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"> 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 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ệt.
</p>
</div>
</div>
</div>
)