Files
encoach_frontend_new_v1/src/pages/OfficialExamAccess.tsx
Talal Sharabi 5980451ea8 feat: integrate client requirements (courseware, communication, notifications, FAQ, enhanced approvals)
Add 4 new Odoo modules coverage:
- encoach_courseware: chapters, materials, chapter progress, AI workbench
- encoach_communication: discussion boards, announcements, messaging
- encoach_notification: notification engine, rules, preferences
- encoach_faq: FAQ categories and items, role-filtered

Frontend changes:
- 4 new type files (courseware, communication, notification, faq)
- 5 new service files (courseware, communication, notification, faq, plagiarism)
- 4 new hook files (useCourseware, useCommunication, useNotifications, useFaq)
- 15 new page components (teacher: chapters, chapter detail, AI workbench,
  discussions, announcements; student: chapter view, discussions, announcements,
  messages, journey; admin: FAQ manager, notification rules, approval config;
  public: official exam access, FAQ page)
- Updated App.tsx routing with all new pages
- Updated TeacherLayout, StudentLayout, AdminLmsLayout sidebars

SRS updates:
- ENCOACH_UNIFIED_SRS.md: added Parts IX-XI (Courseware, Communication,
  Notifications/FAQ), enhanced exam management (exercise vs exam, official
  access modes), enhanced approval workflows (timed stages, escalation,
  bypass), enhanced assignments (late submissions, extensions), plagiarism
  detection. Total modules: 35
- ENCOACH_ODOO19_BACKEND_SRS.md v2.0: added sections 26-31, updated module
  inventory to 35, updated endpoint count to 280+, added enhanced assignment
  models with late submission/extension support

Made-with: Cursor
2026-03-27 00:20:30 +04:00

88 lines
4.0 KiB
TypeScript

import { useSearchParams, useNavigate } from "react-router-dom";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Loader2, Clock, AlertCircle, ClipboardList } from "lucide-react";
import { examsService } from "@/services/exams.service";
import { useQuery } from "@tanstack/react-query";
export default function OfficialExamAccess() {
const [searchParams] = useSearchParams();
const navigate = useNavigate();
const token = searchParams.get("token");
const { data: examInfo, isLoading, error } = useQuery({
queryKey: ["exam", "access", token],
queryFn: async () => {
const res = await fetch(`${import.meta.env.VITE_API_BASE_URL}/exam/access/${token}`);
if (!res.ok) throw new Error(res.status === 404 ? "Invalid or expired token" : "Failed to load exam");
return res.json() as Promise<{ id: number; name: string; duration_minutes: number; start_time: string; module: string }>;
},
enabled: !!token,
retry: false,
});
const handleStart = () => {
if (examInfo) {
navigate(`/exam/${examInfo.module}/${examInfo.id}?token=${token}`);
}
};
return (
<div className="min-h-screen flex items-center justify-center bg-background p-4">
<Card className="w-full max-w-md">
{!token ? (
<CardContent className="py-12 text-center">
<AlertCircle className="h-12 w-12 text-destructive mx-auto mb-3" />
<h2 className="text-lg font-semibold">No Access Token</h2>
<p className="text-sm text-muted-foreground mt-1">Please use the link provided by your institution to access the exam.</p>
</CardContent>
) : isLoading ? (
<CardContent className="py-12 text-center">
<Loader2 className="h-8 w-8 animate-spin mx-auto text-primary" />
<p className="text-sm text-muted-foreground mt-3">Loading exam information...</p>
</CardContent>
) : error ? (
<CardContent className="py-12 text-center">
<AlertCircle className="h-12 w-12 text-destructive mx-auto mb-3" />
<h2 className="text-lg font-semibold">Access Denied</h2>
<p className="text-sm text-muted-foreground mt-1">{(error as Error).message}</p>
</CardContent>
) : examInfo ? (
<>
<CardHeader className="text-center">
<div className="h-14 w-14 rounded-xl bg-primary/10 flex items-center justify-center mx-auto mb-2">
<ClipboardList className="h-7 w-7 text-primary" />
</div>
<CardTitle>{examInfo.name}</CardTitle>
<CardDescription>Official Exam Access</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-3 p-4 rounded-lg border bg-muted/30">
<div className="flex items-center justify-between text-sm">
<span className="text-muted-foreground">Duration</span>
<div className="flex items-center gap-1">
<Clock className="h-4 w-4" />
<span className="font-medium">{examInfo.duration_minutes} minutes</span>
</div>
</div>
<div className="flex items-center justify-between text-sm">
<span className="text-muted-foreground">Start Time</span>
<span className="font-medium">{new Date(examInfo.start_time).toLocaleString()}</span>
</div>
<div className="flex items-center justify-between text-sm">
<span className="text-muted-foreground">Module</span>
<Badge variant="outline">{examInfo.module}</Badge>
</div>
</div>
<Button className="w-full" size="lg" onClick={handleStart}>
Start Exam
</Button>
</CardContent>
</>
) : null}
</Card>
</div>
);
}