feat: connect academic pages to backend APIs

- Generation page: exam structure dropdown loads from backend
- Generation page: selecting structure auto-selects its modules
- Rubrics page: loads rubrics from backend API with fallback to demo data

Made-with: Cursor
This commit is contained in:
Yamen Ahmad
2026-04-12 11:25:02 +04:00
parent 1e688107fa
commit c3ae379870
2 changed files with 48 additions and 8 deletions

View File

@@ -1,5 +1,5 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { useMutation } from "@tanstack/react-query";
import { useMutation, useQuery } from "@tanstack/react-query";
import { Card, CardContent } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
@@ -52,6 +52,7 @@ import {
import AiTipBanner from "@/components/ai/AiTipBanner";
import { generationService } from "@/services/generation.service";
import { mediaService, type Avatar } from "@/services/media.service";
import { examsService } from "@/services/exams.service";
import { useToast } from "@/hooks/use-toast";
type ModuleKey = "reading" | "listening" | "writing" | "speaking" | "level" | "industry";
@@ -73,6 +74,7 @@ const MODULES: ModuleInfo[] = [
{ key: "industry", label: "Industry", icon: <Briefcase className="h-4 w-4" />, color: "text-amber-700", bgColor: "bg-amber-50 border-amber-200" },
];
const MODULE_KEYS: ModuleKey[] = MODULES.map((m) => m.key);
const CEFR_LEVELS = ["A1", "A2", "B1", "B2", "C1", "C2"];
const READING_EXERCISE_TYPES = [
@@ -197,6 +199,12 @@ export default function GenerationPage() {
const [moduleStates, setModuleStates] = useState<Record<string, ModuleState>>({});
const [previewOpen, setPreviewOpen] = useState(false);
const structuresQ = useQuery({
queryKey: ["exam-structures"],
queryFn: () => examsService.listStructures({}),
});
const structures = structuresQ.data?.items ?? [];
const getModuleState = useCallback((mod: ModuleKey): ModuleState => {
return moduleStates[mod] ?? defaultModuleState(mod);
}, [moduleStates]);
@@ -990,13 +998,27 @@ export default function GenerationPage() {
</div>
<div className="space-y-1">
<Label>Exam Structure</Label>
<Select value={examStructure} onValueChange={setExamStructure}>
<Select value={examStructure} onValueChange={(val) => {
setExamStructure(val);
const s = structures.find((st) => String(st.id) === val);
if (s) {
const mods = (s as Record<string, unknown>).modules as string[] | undefined;
if (Array.isArray(mods) && mods.length) {
const next = new Set<ModuleKey>();
mods.forEach((m) => { if (MODULE_KEYS.includes(m as ModuleKey)) next.add(m as ModuleKey); });
setSelectedModules(next);
if (next.size > 0) setActiveModule([...next][0]);
}
}
}}>
<SelectTrigger><SelectValue placeholder="Select an exam structure" /></SelectTrigger>
<SelectContent>
<SelectItem value="standard_ielts">Standard IELTS Academic</SelectItem>
<SelectItem value="corporate">Corporate English Assessment</SelectItem>
<SelectItem value="hospitality">Hospitality English Test</SelectItem>
<SelectItem value="medical">Medical English Proficiency</SelectItem>
{structures.map((s) => (
<SelectItem key={s.id} value={String(s.id)}>{s.name}</SelectItem>
))}
{structures.length === 0 && (
<SelectItem value="_none" disabled>No structures available</SelectItem>
)}
</SelectContent>
</Select>
</div>

View File

@@ -1,4 +1,5 @@
import { useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { Card, CardContent } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
@@ -8,11 +9,21 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Search, Plus } from "lucide-react";
import { Search, Plus, Loader2 } from "lucide-react";
import AiTipBanner from "@/components/ai/AiTipBanner";
import AiCreationAssistant from "@/components/ai/AiCreationAssistant";
import { examsService } from "@/services/exams.service";
const rubrics = [
interface RubricItem {
id: number;
name: string;
levels: string[];
criteria: number;
created: string;
skill?: string;
}
const FALLBACK_RUBRICS: RubricItem[] = [
{ id: 1, name: "IELTS Writing Task 2", levels: ["A1","A2","B1","B2","C1","C2"], criteria: 4, created: "2025-01-05" },
{ id: 2, name: "Speaking Fluency", levels: ["A1","A2","B1","B2","C1","C2"], criteria: 3, created: "2025-01-10" },
{ id: 3, name: "Reading Comprehension", levels: ["A1","A2","B1","B2","C1"], criteria: 5, created: "2025-02-01" },
@@ -26,6 +37,13 @@ const rubricGroups = [
export default function RubricsPage() {
const [search, setSearch] = useState("");
const rubricsQ = useQuery({
queryKey: ["rubrics"],
queryFn: () => examsService.listRubrics({}),
});
const backendRubrics = (rubricsQ.data?.items ?? []) as RubricItem[];
const rubrics = backendRubrics.length > 0 ? backendRubrics : FALLBACK_RUBRICS;
return (
<div className="space-y-6">
<div className="flex items-center justify-between">