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:
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user