import { useMemo } from "react"; import { useNavigate, useParams } from "react-router-dom"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Badge } from "@/components/ui/badge"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Skeleton } from "@/components/ui/skeleton"; import { useIeltsSkills } from "@/hooks/queries/useExamTemplates"; import type { IELTSPartConfig, IELTSSkill, IELTSSkillConfig } from "@/types"; const SKILL_LABEL: Record = { listening: "Listening", reading: "Reading", writing: "Writing", speaking: "Speaking", }; const STATIC_PARTS: Record[]> = { listening: [ { question_count: 10, content_type: "Audio + MCQ", time_limit_min: 7, description: "Part 1 — everyday social" }, { question_count: 10, content_type: "Audio + MCQ", time_limit_min: 7, description: "Part 2 — monologue" }, { question_count: 10, content_type: "Audio + MCQ", time_limit_min: 8, description: "Part 3 — conversation" }, { question_count: 10, content_type: "Audio + MCQ", time_limit_min: 8, description: "Part 4 — lecture" }, ], reading: [ { question_count: 13, content_type: "Passage + items", time_limit_min: 20, description: "Passage 1" }, { question_count: 13, content_type: "Passage + items", time_limit_min: 20, description: "Passage 2" }, { question_count: 14, content_type: "Passage + items", time_limit_min: 20, description: "Passage 3" }, ], writing: [ { question_count: 1, content_type: "Task response", time_limit_min: 20, description: "Task 1" }, { question_count: 1, content_type: "Essay", time_limit_min: 40, description: "Task 2" }, ], speaking: [ { question_count: 1, content_type: "Interview", time_limit_min: 5, description: "Part 1 — introduction" }, { question_count: 1, content_type: "Long turn", time_limit_min: 4, description: "Part 2 — cue card" }, { question_count: 1, content_type: "Discussion", time_limit_min: 5, description: "Part 3 — abstract" }, ], }; function resolveParts(skill: IELTSSkill, cfg?: IELTSSkillConfig): IELTSPartConfig[] { const fromApi = cfg?.parts?.length ? cfg.parts : []; if (fromApi.length) return fromApi; return STATIC_PARTS[skill].map((p, i) => ({ part_number: i + 1, ...p, })); } function requiredQuestions(skill: IELTSSkill, parts: IELTSPartConfig[]): number { const sum = parts.reduce((a, p) => a + p.question_count, 0); if (sum > 0) return sum; if (skill === "listening") return 40; if (skill === "reading") return 40; if (skill === "writing") return 2; return 3; } export default function IeltsSkillConfig() { const { examId: examIdParam } = useParams<{ examId: string }>(); const navigate = useNavigate(); const examId = Number(examIdParam); const { data, isLoading, isError } = useIeltsSkills(Number.isFinite(examId) ? examId : 0); const skills = useMemo(() => { if (!data) return [] as IELTSSkillConfig[]; const arr = Array.isArray(data) ? data : (data as { data?: IELTSSkillConfig[] }).data ?? []; return arr; }, [data]); const defaultTab = (skills.length > 0 ? skills[0]?.skill : "listening") ?? "listening"; if (!Number.isFinite(examId) || examId <= 0) { return

Invalid exam.

; } if (isLoading) { return (
); } const displaySkills: IELTSSkillConfig[] = skills.length > 0 ? skills : (["listening", "reading", "writing", "speaking"] as IELTSSkill[]).map((s) => ({ skill: s, parts: STATIC_PARTS[s].map((p, i) => ({ part_number: i + 1, ...p })), available_count: 0, })); return (

Skill layout

Review the official structure before choosing content.

{displaySkills.map((s) => ( {SKILL_LABEL[s.skill]} ))} {displaySkills.map((s) => { const parts = resolveParts(s.skill, s); const required = requiredQuestions(s.skill, parts); const pool = s.available_count ?? 0; const pct = required > 0 ? Math.min(100, Math.round((pool / required) * 100)) : 0; return (
{SKILL_LABEL[s.skill]} Read-only structure. Pool coverage: {pool} items available for ~{required} required questions.
= 100 ? "default" : "secondary"}>{pct}% pool coverage
Part Questions Content type Time (min) Pool {parts.map((p) => ( {p.part_number} {p.question_count} {p.content_type} {p.time_limit_min} {Math.max(0, Math.floor(pool / parts.length))} ))}
); })}
); }