feat(v3): restructure project + add complete frontend
- 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
This commit is contained in:
122
frontend/src/pages/RubricsPage.tsx
Normal file
122
frontend/src/pages/RubricsPage.tsx
Normal file
@@ -0,0 +1,122 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user