/* ================================================================== Online Tests — teacher screen - Type picker (Blitz / Classical) - Paste raw MCQ text → Gemini formats → editable form - Save as draft / Save & make live (publishes to the student portal) ================================================================== */ const OT_ACCENT = "var(--blue-solid)"; const TYPE_PRESETS = [ { id: "blitz", label: "Blitz", tagline: "Per-question timer · no back-tracking", detail: "Best for rapid practice and reflex drills. Each question gets a hard time limit; once it expires, the answer is locked.", accent: "var(--red-solid)", }, { id: "classical", label: "Classical", tagline: "Total timer · free navigation", detail: "Best for full-length tests. One overall timer; students can navigate freely, mark for review, and use the palette.", accent: OT_ACCENT, }, { id: "descriptive", label: "Descriptive", tagline: "Students upload photo answers · teacher grades manually", detail: "Long-form questions. Students attach photo(s) of their handwritten work per question. No options, no auto-grading — every mark comes from your review.", accent: "var(--amber-solid)", }, ]; const OnlineTestsScreen = ({ setScreen }) => { const [testType, setTestType] = React.useState("classical"); const [rawText, setRawText] = React.useState(""); const [parsed, setParsed] = React.useState(null); const [warnings, setWarnings] = React.useState([]); const [formatting, setFormatting] = React.useState(false); const [saving, setSaving] = React.useState(false); const [err, setErr] = React.useState(""); const [successId, setSuccessId] = React.useState(null); const handleFormat = async () => { if (!rawText.trim()) { setErr("Paste some MCQs first."); return; } setErr(""); setFormatting(true); setWarnings([]); try { const r = await window.AppApi.post("/online-tests/format", { raw_text: rawText, type: testType, }); setParsed(r.parsed); setWarnings(Array.isArray(r.parsed?.warnings) ? r.parsed.warnings : []); } catch (e) { setErr(e.message || "Gemini failed to parse the text"); } finally { setFormatting(false); } }; const handleSave = async (publish) => { if (!parsed) { setErr("Format the test before saving."); return; } setErr(""); setSaving(true); try { const r = await window.AppApi.post("/online-tests", { parsed, type: testType, publish: !!publish, // Multi-section: send the chip set. Backend UNIONs students from // every section in the array when auto-enrolling. class_section_ids: (parsed.class_section_ids && parsed.class_section_ids.length) ? parsed.class_section_ids : undefined, subject_id: parsed.subject_id || undefined, }); setSuccessId(r.id); } catch (e) { const errs = e.message?.includes("validation failed") ? e.message : (e.message || "Save failed"); setErr(errs); } finally { setSaving(false); } }; if (successId) { return ( setScreen("library")} onNew={() => { setSuccessId(null); setParsed(null); setRawText(""); setWarnings(""); setErr(""); }}/> ); } return (
{/* Left column — type picker (sticky) */}
Test type
{TYPE_PRESETS.map((t) => ( setTestType(t.id)} disabled={!!parsed}/> ))}
{parsed && (
Type is locked once formatted. Click "Reset" below the form to start over.
)}
{/* Right column — paste + form */}
{!parsed && ( )} {warnings.length > 0 && (
Gemini warnings
{warnings.map((w, i) => (
• {w}
))}
)} {parsed && ( { setParsed(null); setWarnings([]); }}/> )} {err && (
{err}
)} {parsed && (
)}
); }; /* ---------- Shell wrapping for the screen body ---------- */ const OTScreenShell = ({ children }) => (
Assessment · Online tests

Create online test

Paste raw MCQs · Gemini formats them · review · publish to the student portal.
{children}
); const OTLabel = ({ children }) => (
{children}
); const TypeCard = ({ preset, selected, onClick, disabled }) => (
{preset.label}
{preset.tagline}
{preset.detail}
); /* ---------- Paste card ---------- */ const PasteCard = ({ raw, setRaw, formatting, onFormat }) => (
Paste MCQs
Free-text — Gemini will identify questions, options, and the correct answer if marked.