fix(dashboard): DPP tags properly dynamic based on user target exam

This commit is contained in:
2026-07-08 12:46:44 +05:30
parent c87b305646
commit 1a6168f724
28 changed files with 853 additions and 304 deletions

View File

@@ -1,6 +1,6 @@
"use client"
import { ReactNode } from "react"
import { ReactNode, useEffect } from "react"
import { LayoutDashboard, Users, Database, Terminal, Settings, FileText } from "lucide-react"
import Link from "next/link"
import { usePathname } from "next/navigation"
@@ -9,6 +9,10 @@ import { Logo } from "@/components/Logo"
export default function AdminLayout({ children }: { children: ReactNode }) {
const pathname = usePathname()
useEffect(() => {
document.documentElement.classList.add('dark');
}, []);
return (
<div className="flex h-screen bg-background overflow-hidden text-foreground selection:bg-[#00F0FF]/20">
{/* Sidebar */}

View File

@@ -1,3 +1,5 @@
"use client"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
import { Search, Download, MoreVertical } from "lucide-react"

View File

@@ -7,6 +7,8 @@ import { Badge } from "@/components/ui/badge"
import { Switch } from "@/components/ui/switch"
import { Input } from "@/components/ui/input"
import { Loader2, Upload, FileText, Clock, CalendarIcon } from "lucide-react"
import DatePicker from "react-datepicker"
import "react-datepicker/dist/react-datepicker.css"
import { useActiveUser } from "@/hooks/useActiveUser"
@@ -15,13 +17,13 @@ export default function AdminTestsPage() {
const [loading, setLoading] = useState(true)
const [uploading, setUploading] = useState(false)
const { userId } = useActiveUser()
const fileInputRef = useRef<HTMLInputElement>(null)
// Upload Form State
const [title, setTitle] = useState("")
const [examPattern, setExamPattern] = useState("JEE_Main")
const [timeLimit, setTimeLimit] = useState("180")
const [scheduledDate, setScheduledDate] = useState("")
const [scheduledDate, setScheduledDate] = useState<Date | null>(null)
const [endDate, setEndDate] = useState<Date | null>(null)
const [questionsJson, setQuestionsJson] = useState("")
const fetchTests = async () => {
try {
@@ -56,37 +58,48 @@ export default function AdminTestsPage() {
const handleUpload = async (e: React.FormEvent) => {
e.preventDefault()
if (!fileInputRef.current?.files?.[0]) return
if (!questionsJson) return
if (!userId) return
setUploading(true)
const formData = new FormData()
formData.append("file", fileInputRef.current.files[0])
formData.append("title", title)
formData.append("exam_pattern", examPattern)
formData.append("time_limit", timeLimit)
formData.append("admin_id", userId)
if (scheduledDate) {
formData.append("scheduled_date", new Date(scheduledDate).toISOString())
let parsedQuestions = [];
try {
parsedQuestions = JSON.parse(questionsJson);
} catch(err) {
alert("Invalid JSON format. Please paste a valid JSON array.");
setUploading(false);
return;
}
const payload = {
title,
exam_pattern: examPattern,
time_limit: parseInt(timeLimit),
admin_id: userId,
questions_json: parsedQuestions,
scheduled_date: scheduledDate ? scheduledDate.toISOString() : null,
end_date: endDate ? endDate.toISOString() : null,
};
try {
const res = await fetch("http://localhost:8000/api/admin/tests/upload", {
const res = await fetch("http://localhost:8000/api/admin/tests/create_manual", {
method: "POST",
body: formData
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
})
if (res.ok) {
alert("Test uploaded and sent to AI for processing!")
alert("Test created successfully!")
setTitle("")
if (fileInputRef.current) fileInputRef.current.value = ""
setQuestionsJson("")
fetchTests()
} else {
alert("Upload failed")
alert("Creation failed")
}
} catch (error) {
console.error(error)
alert("Upload failed")
alert("Creation failed")
} finally {
setUploading(false)
}
@@ -106,9 +119,9 @@ export default function AdminTestsPage() {
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Upload className="w-5 h-5 text-primary" />
Upload New Test
Create Manual Test
</CardTitle>
<CardDescription>Upload a PDF containing questions. AI will automatically extract and format them.</CardDescription>
<CardDescription>Paste the structured JSON block of questions in LaTeX format to create a deterministic test.</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleUpload} className="space-y-4">
@@ -135,20 +148,52 @@ export default function AdminTestsPage() {
<Input required type="number" value={timeLimit} onChange={e => setTimeLimit(e.target.value)} />
</div>
<div className="space-y-2">
<label className="text-sm font-medium">Scheduled Date (Optional)</label>
<Input type="datetime-local" value={scheduledDate} onChange={e => setScheduledDate(e.target.value)} />
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2 flex flex-col">
<label className="text-sm font-medium">Scheduled Start (Optional)</label>
<DatePicker
selected={scheduledDate}
onChange={(date) => setScheduledDate(date)}
showTimeSelect
timeFormat="HH:mm"
timeIntervals={15}
timeCaption="time"
dateFormat="MMMM d, yyyy h:mm aa"
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
placeholderText="Select start date & time"
/>
</div>
<div className="space-y-2 flex flex-col">
<label className="text-sm font-medium">Scheduled End (Optional)</label>
<DatePicker
selected={endDate}
onChange={(date) => setEndDate(date)}
showTimeSelect
timeFormat="HH:mm"
timeIntervals={15}
timeCaption="time"
dateFormat="MMMM d, yyyy h:mm aa"
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
placeholderText="Select end date & time"
/>
</div>
</div>
<div className="space-y-2">
<label className="text-sm font-medium">Test PDF File</label>
<Input required type="file" accept=".pdf" ref={fileInputRef} className="cursor-pointer" />
<p className="text-xs text-muted-foreground mt-1">Make sure it contains clear questions and options.</p>
<label className="text-sm font-medium">Questions JSON (LaTeX Format)</label>
<textarea
required
value={questionsJson}
onChange={e => setQuestionsJson(e.target.value)}
className="w-full min-h-[250px] p-3 text-sm font-mono border rounded-md bg-background focus:ring-primary focus:border-primary"
placeholder='[{"section": "Physics", "text_en": "Question text...", "options_en": ["A", "B", "C", "D"], "correct_answer": "A"}]'
/>
<p className="text-xs text-muted-foreground mt-1">Paste your questions as a JSON array.</p>
</div>
<Button type="submit" className="w-full" disabled={uploading}>
{uploading ? <Loader2 className="w-4 h-4 mr-2 animate-spin" /> : <Upload className="w-4 h-4 mr-2" />}
{uploading ? "Processing PDF via AI..." : "Upload & Parse"}
{uploading ? <Loader2 className="w-4 h-4 mr-2 animate-spin" /> : <FileText className="w-4 h-4 mr-2" />}
{uploading ? "Creating Test..." : "Create Test from JSON"}
</Button>
</form>
</CardContent>