- 59 pages (52 original + 7 new adaptive learning pages) - 14 AI components wired to real backend endpoints - Real JWT authentication with 7 user roles - 21 API service modules with TanStack Query hooks - 14 TypeScript type definition files - Entity-scoped permission system - Universal adaptive learning engine pages (subjects, diagnostic, proficiency, learning plan, topic) - Admin taxonomy and resource management pages - All mock data removed, all pages connected to Odoo 19 REST API docs/ contains: - ENCOACH_UNIFIED_SRS.md (master product SRS) - ODOO_BACKEND_SRS_v3.md (backend developer handoff) - ENCOACH_PRODUCT_DESCRIPTION.md (stakeholder document) - UTAS_MASTER_PLAN.md (project timeline) - Supporting architecture and analysis documents Made-with: Cursor
93 lines
5.6 KiB
TypeScript
93 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 { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
|
import { Search, MoreHorizontal, Lock, Unlock } from "lucide-react";
|
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
|
|
import AiTipBanner from "@/components/ai/AiTipBanner";
|
|
import AiCreationAssistant from "@/components/ai/AiCreationAssistant";
|
|
|
|
const exams = [
|
|
{ id: "EX-001", title: "IELTS Academic Mock 1", createdBy: "Dr. Smith", modules: 4, rubric: "Linked", difficulty: "Hard", exercises: 40, timer: "180 min", access: "Public", date: "2025-01-10" },
|
|
{ id: "EX-002", title: "Reading Comprehension B2", createdBy: "Prof. Lee", modules: 1, rubric: "Linked", difficulty: "Medium", exercises: 12, timer: "60 min", access: "Private", date: "2025-01-18" },
|
|
{ id: "EX-003", title: "Listening Practice A2", createdBy: "Mr. Kim", modules: 1, rubric: "Pending", difficulty: "Easy", exercises: 10, timer: "30 min", access: "Public", date: "2025-02-05" },
|
|
{ id: "EX-004", title: "Writing Task 1 & 2", createdBy: "Dr. Smith", modules: 1, rubric: "Linked", difficulty: "Hard", exercises: 2, timer: "60 min", access: "Public", date: "2025-02-12" },
|
|
{ id: "EX-005", title: "Speaking Interview Sim", createdBy: "Prof. Lee", modules: 1, rubric: "Linked", difficulty: "Medium", exercises: 3, timer: "15 min", access: "Private", date: "2025-02-20" },
|
|
{ id: "EX-006", title: "Full Mock C1", createdBy: "Mr. Kim", modules: 4, rubric: "Linked", difficulty: "Very Hard", exercises: 48, timer: "180 min", access: "Public", date: "2025-03-01" },
|
|
];
|
|
|
|
export default function ExamsListPage() {
|
|
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">Exams List</h1>
|
|
<p className="text-muted-foreground">Browse and manage all exam content.</p>
|
|
</div>
|
|
<AiCreationAssistant type="exam" />
|
|
</div>
|
|
|
|
<AiTipBanner context="exams-list" variant="insight" />
|
|
|
|
<div className="flex flex-wrap gap-3 items-center">
|
|
<div className="relative flex-1 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 by ID or title..." className="pl-9" value={search} onChange={(e) => setSearch(e.target.value)} />
|
|
</div>
|
|
<Select><SelectTrigger className="w-[140px]"><SelectValue placeholder="Module" /></SelectTrigger>
|
|
<SelectContent>{["All","Reading","Listening","Writing","Speaking"].map(m => <SelectItem key={m} value={m}>{m}</SelectItem>)}</SelectContent>
|
|
</Select>
|
|
<Select><SelectTrigger className="w-[120px]"><SelectValue placeholder="Level" /></SelectTrigger>
|
|
<SelectContent>{["All","A1","A2","B1","B2","C1","C2"].map(l => <SelectItem key={l} value={l}>{l}</SelectItem>)}</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
<Card className="border-0 shadow-sm">
|
|
<CardContent className="p-0">
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>ID</TableHead><TableHead>Title</TableHead><TableHead>Created By</TableHead>
|
|
<TableHead>Modules</TableHead><TableHead>Rubric</TableHead><TableHead>Difficulty</TableHead>
|
|
<TableHead>Exercises</TableHead><TableHead>Timer</TableHead><TableHead>Access</TableHead>
|
|
<TableHead>Date</TableHead><TableHead className="w-10"></TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{exams.map((exam) => (
|
|
<TableRow key={exam.id}>
|
|
<TableCell className="font-mono text-xs">{exam.id}</TableCell>
|
|
<TableCell className="font-medium">{exam.title}</TableCell>
|
|
<TableCell>{exam.createdBy}</TableCell>
|
|
<TableCell><Badge variant="secondary">{exam.modules}</Badge></TableCell>
|
|
<TableCell><Badge variant={exam.rubric === "Linked" ? "default" : "outline"}>{exam.rubric}</Badge></TableCell>
|
|
<TableCell>{exam.difficulty}</TableCell>
|
|
<TableCell>{exam.exercises}</TableCell>
|
|
<TableCell>{exam.timer}</TableCell>
|
|
<TableCell>{exam.access === "Public" ? <Unlock className="h-4 w-4 text-success" /> : <Lock className="h-4 w-4 text-muted-foreground" />}</TableCell>
|
|
<TableCell>{exam.date}</TableCell>
|
|
<TableCell>
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild><Button variant="ghost" size="icon" className="h-8 w-8"><MoreHorizontal className="h-4 w-4" /></Button></DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end">
|
|
<DropdownMenuItem>Edit</DropdownMenuItem>
|
|
<DropdownMenuItem>Load</DropdownMenuItem>
|
|
<DropdownMenuItem>{exam.access === "Public" ? "Set Private" : "Set Public"}</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|