fix(dashboard): DPP tags properly dynamic based on user target exam
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user