import { useState } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; import { Label } from "@/components/ui/label"; import { Checkbox } from "@/components/ui/checkbox"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Search, Plus, Layers, Trash2, Loader2 } from "lucide-react"; import AiTipBanner from "@/components/ai/AiTipBanner"; import { examsService } from "@/services/exams.service"; import { useToast } from "@/hooks/use-toast"; import type { ExamStructure } from "@/types"; const MODULE_OPTIONS = ["Reading", "Listening", "Writing", "Speaking"]; export default function ExamStructuresPage() { const { toast } = useToast(); const queryClient = useQueryClient(); const [search, setSearch] = useState(""); const [entityFilter, setEntityFilter] = useState("all"); const [createOpen, setCreateOpen] = useState(false); const [newName, setNewName] = useState(""); const [newIndustry, setNewIndustry] = useState(""); const [newModules, setNewModules] = useState([]); const { data, isLoading, error } = useQuery({ queryKey: ["exam-structures", entityFilter], queryFn: () => examsService.listStructures(entityFilter !== "all" ? { entity_id: Number(entityFilter) } : {}), }); const structures: ExamStructure[] = data?.items ?? []; const createMut = useMutation({ mutationFn: (structureData: Partial) => examsService.createStructure(structureData), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["exam-structures"] }); setCreateOpen(false); setNewName(""); setNewIndustry(""); setNewModules([]); toast({ title: "Structure created" }); }, onError: (err: Error) => toast({ variant: "destructive", title: "Failed", description: err.message }), }); const deleteMut = useMutation({ mutationFn: (id: number) => examsService.deleteStructure(id), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["exam-structures"] }); toast({ title: "Structure deleted" }); }, onError: (err: Error) => toast({ variant: "destructive", title: "Failed", description: err.message }), }); const filtered = structures.filter((s) => { if (search) { const q = search.toLowerCase(); return s.name?.toLowerCase().includes(q) || (s as Record).industry?.toString().toLowerCase().includes(q); } return true; }); return (

Exam Structures

Define exam structure templates by entity and industry.

Create Exam Structure
setNewName(e.target.value)} />
{MODULE_OPTIONS.map((m) => (
{ setNewModules((prev) => checked ? [...prev, m.toLowerCase()] : prev.filter((x) => x !== m.toLowerCase())); }} />
))}
setSearch(e.target.value)} />
{isLoading && (
)} {error && ( Failed to load structures. The backend endpoint may not be available yet. )} {!isLoading && !error && filtered.length === 0 && ( No exam structures found. Create one to get started. )}
{filtered.map((s) => (
{s.name}
{(s as Record).entity_name && Entity: {String((s as Record).entity_name)}} {(s as Record).industry && Industry: {String((s as Record).industry)}}
{(Array.isArray((s as Record).modules) ? (s as Record).modules as string[] : []).map((m) => ( {m} ))}
))}
); }