- Restructure: move backend from new_project/ to backend/ - Add full React/TypeScript frontend (37 pages, 17 services, 16 type defs, 11 query hooks) - Add docs/ with SRS specs, user stories, and workflow documentation - Update .gitignore for new directory layout Workflows implemented: WF1 User Signup, WF2 Placement Test, WF3 Exam Configuration, WF4 General English Exam, WF5 Course Generation, WF6 Entity Student Onboarding, AI Course Generation, Adaptive Learning Engine UI, White-Label Branding, Score Release Made-with: Cursor
123 lines
5.6 KiB
TypeScript
123 lines
5.6 KiB
TypeScript
import { useState } from "react";
|
|
import { Card, CardContent } from "@/components/ui/card";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
|
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 AiTipBanner from "@/components/ai/AiTipBanner";
|
|
import AiCreationAssistant from "@/components/ai/AiCreationAssistant";
|
|
|
|
const rubrics = [
|
|
{ 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" },
|
|
];
|
|
|
|
const rubricGroups = [
|
|
{ id: 1, name: "Academic IELTS Full", rubrics: ["IELTS Writing Task 2", "Speaking Fluency", "Reading Comprehension"], created: "2025-02-15" },
|
|
{ id: 2, name: "Business English", rubrics: ["Speaking Fluency"], created: "2025-03-01" },
|
|
];
|
|
|
|
export default function RubricsPage() {
|
|
const [search, setSearch] = useState("");
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h1 className="text-2xl font-bold tracking-tight">Rubrics</h1>
|
|
<p className="text-muted-foreground">Create and manage evaluation rubrics with level-based descriptors.</p>
|
|
</div>
|
|
<div className="flex gap-2">
|
|
<AiCreationAssistant type="rubric" />
|
|
<Dialog>
|
|
<DialogTrigger asChild>
|
|
<Button size="sm"><Plus className="h-4 w-4 mr-1" /> Create Rubric</Button>
|
|
</DialogTrigger>
|
|
<DialogContent className="max-w-2xl">
|
|
<DialogHeader><DialogTitle>Create Rubric</DialogTitle></DialogHeader>
|
|
<div className="space-y-4">
|
|
<div className="space-y-2"><Label>Rubric Name</Label><Input placeholder="e.g. IELTS Writing Task 1" /></div>
|
|
<div className="space-y-3">
|
|
<Label>Level Descriptors</Label>
|
|
{["A1","A2","B1","B2","C1","C2"].map(level => (
|
|
<div key={level} className="space-y-1">
|
|
<Label className="text-xs text-muted-foreground">{level}</Label>
|
|
<Textarea placeholder={`Descriptor for level ${level}...`} className="h-16" />
|
|
</div>
|
|
))}
|
|
</div>
|
|
<Button className="w-full">Create Rubric</Button>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</div>
|
|
</div>
|
|
|
|
<AiTipBanner context="rubrics" variant="recommendation" />
|
|
|
|
<div className="relative max-w-sm">
|
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
<Input placeholder="Search rubrics..." className="pl-9" value={search} onChange={(e) => setSearch(e.target.value)} />
|
|
</div>
|
|
|
|
<Tabs defaultValue="rubrics">
|
|
<TabsList>
|
|
<TabsTrigger value="rubrics">Rubrics List</TabsTrigger>
|
|
<TabsTrigger value="groups">Rubric Groups</TabsTrigger>
|
|
</TabsList>
|
|
<TabsContent value="rubrics" className="mt-4">
|
|
<Card className="border-0 shadow-sm">
|
|
<CardContent className="p-0">
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>Name</TableHead><TableHead>Levels</TableHead><TableHead>Criteria</TableHead><TableHead>Created</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{rubrics.map((r) => (
|
|
<TableRow key={r.id}>
|
|
<TableCell className="font-medium">{r.name}</TableCell>
|
|
<TableCell><div className="flex gap-1">{r.levels.map(l => <Badge key={l} variant="outline" className="text-xs">{l}</Badge>)}</div></TableCell>
|
|
<TableCell>{r.criteria}</TableCell>
|
|
<TableCell>{r.created}</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</CardContent>
|
|
</Card>
|
|
</TabsContent>
|
|
<TabsContent value="groups" className="mt-4">
|
|
<Card className="border-0 shadow-sm">
|
|
<CardContent className="p-0">
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>Group Name</TableHead><TableHead>Rubrics</TableHead><TableHead>Created</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{rubricGroups.map((g) => (
|
|
<TableRow key={g.id}>
|
|
<TableCell className="font-medium">{g.name}</TableCell>
|
|
<TableCell><div className="flex gap-1 flex-wrap">{g.rubrics.map(r => <Badge key={r} variant="secondary" className="text-xs">{r}</Badge>)}</div></TableCell>
|
|
<TableCell>{g.created}</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</CardContent>
|
|
</Card>
|
|
</TabsContent>
|
|
</Tabs>
|
|
</div>
|
|
);
|
|
}
|