import { useState, useMemo } from 'react' import { useNavigate } from '@tanstack/react-router' import { useQuery } from '@tanstack/react-query' import { ArrowRight, Check, Clock, Sparkles, Target } from 'lucide-react' import { fetchTestWithParts } from '@/features/toeic/api/test-list-api' import { fetchQuestionsForTest } from '@/hooks/use-questions' import { useTestStore } from '@/store/test-store' import { useRequireAuth } from '@/hooks/use-require-auth' import type { PartRecord } from '@/types' interface Props { testId: number } // TOEIC part metadata (stable across all tests) const PART_META: Record = { 1: { subtitle: 'Photographs', desc: 'Mô tả hình ảnh', skill: 'listening' }, 2: { subtitle: 'Question-Response', desc: 'Hỏi – đáp', skill: 'listening' }, 3: { subtitle: 'Conversations', desc: 'Hội thoại ngắn', skill: 'listening' }, 4: { subtitle: 'Short Talks', desc: 'Bài nói ngắn', skill: 'listening' }, 5: { subtitle: 'Incomplete Sentences', desc: 'Ngữ pháp câu', skill: 'reading' }, 6: { subtitle: 'Text Completion', desc: 'Điền vào đoạn văn', skill: 'reading' }, 7: { subtitle: 'Reading Comprehension', desc: 'Đọc hiểu', skill: 'reading' }, } const TABS = ['Tất cả', 'Listening', 'Reading', 'Chưa làm', 'Cần ôn'] as const type Tab = (typeof TABS)[number] function Ring({ percent, size = 56, stroke = 5, color }: { percent: number; size?: number; stroke?: number; color: string }) { const cx = size / 2 const r = cx - stroke const c = 2 * Math.PI * r const offset = c - (Math.min(percent, 100) / 100) * c return (
{percent} %
) } function StatusChip({ done, fresh }: { done: boolean; fresh: boolean }) { if (done) { return ( Hoàn thành ) } if (fresh) { return ( Mới ) } return ( Đang làm ) } function PartCard({ part, selected, onToggle, disabled }: { part: PartRecord; selected: boolean; onToggle: () => void; disabled: boolean }) { const meta = PART_META[part.partNumber] ?? { subtitle: part.title, desc: '', skill: 'reading' as const } // Progress data not yet available in API — render as fresh. const done = 0 const score = 0 const pct = part.questionCount > 0 ? Math.round((done / part.questionCount) * 100) : 0 const isDone = done === part.questionCount && part.questionCount > 0 const isFresh = done === 0 const ringColor = isDone ? 'var(--at-good)' : isFresh ? 'var(--at-mute-2)' : 'var(--at-brand)' return ( ) } function AiGeneratedCard({ onClick }: { onClick: () => void }) { return ( ) } export function ToeicTestDetail({ testId }: Props) { const navigate = useNavigate() const { startExam } = useTestStore() const { requireAuth } = useRequireAuth() const [loading, setLoading] = useState(false) const [activeTab, setActiveTab] = useState('Tất cả') const [selectedParts, setSelectedParts] = useState([]) const [durationMinutes, setDurationMinutes] = useState(30) const { data, isLoading } = useQuery({ queryKey: ['test-detail', testId], queryFn: () => fetchTestWithParts(testId), }) function togglePart(partNumber: number) { setSelectedParts(prev => prev.includes(partNumber) ? prev.filter(p => p !== partNumber) : [...prev, partNumber], ) } function clearSelection() { setSelectedParts([]) } async function handleStart( mode: 'full' | 'short' | 'custom', partNumbers?: number[], minutes?: number, ) { if (!requireAuth()) return if (!data) return setLoading(true) try { const parts = await fetchQuestionsForTest(testId, partNumbers) const totalSeconds = mode === 'full' ? data.test.durationMinutes * 60 : mode === 'short' ? 20 * 60 : (minutes ?? 30) * 60 startExam({ testId, testName: data.test.title, parts, totalSeconds }) navigate({ to: '/toeic/session' }) } finally { setLoading(false) } } const filteredParts = useMemo(() => { if (!data) return [] const all = data.parts if (activeTab === 'Tất cả') return all if (activeTab === 'Listening') return all.filter(p => PART_META[p.partNumber]?.skill === 'listening') if (activeTab === 'Reading') return all.filter(p => PART_META[p.partNumber]?.skill === 'reading') // "Chưa làm" / "Cần ôn" — no progress data yet, show all return all }, [data, activeTab]) if (isLoading) { return (
{Array.from({ length: 6 }).map((_, i) => (
))}
) } if (!data) return null const { test, parts } = data return (
{/* Editorial head */}

Chọn phần bạn muốn luyện

{parts.length} phần thi · {test.totalQuestions} câu hỏi · đầy đủ Listening + Reading

{/* Tabs */}
{TABS.map(t => { const active = activeTab === t return ( ) })}
{/* Part grid */}
0 ? 96 : 0, }} > {filteredParts.map(part => ( togglePart(part.partNumber)} disabled={loading} /> ))} {activeTab === 'Tất cả' && ( handleStart('short')} /> )}
{/* Sticky selection bar — full viewport width */} {selectedParts.length > 0 && (
{selectedParts.length}
Đã chọn {selectedParts.length} phần
{data?.parts .filter(p => selectedParts.includes(p.partNumber)) .reduce((sum, p) => sum + p.questionCount, 0)}{' '} câu hỏi
)}
) }