/* ================================================================== Class Reports — class + test picker → class-level summary ================================================================== */ const { Icon: RIcon, StatusPill: RStatusPill } = window.AppUI; const ClassReports = ({ setScreen }) => { const [selection, setSelection] = React.useState(null); // {className, test} if (!selection) { return setSelection(sel)}/>; } return setSelection(null)} setScreen={setScreen}/>; }; /* ---------- Picker ---------- */ const ReportsPicker = ({ onPick }) => { const [cls, setCls] = React.useState("XI"); const [search, setSearch] = React.useState(""); // Only graded / closed tests can have a class report const candidates = window.APP.TESTS .filter(t => t.status === "grading" || t.status === "closed") .filter(t => t.className === cls) .filter(t => !search || t.title.toLowerCase().includes(search.toLowerCase()) || t.id.toLowerCase().includes(search.toLowerCase())); return (
Class Reports

Open a class report

Pick the class and test you'd like to review. You'll see how the cohort performed and get a Gemini-drafted lesson plan for the next session.

1 · Class
{["IX", "X", "XI", "XII"].map(c => )}
2 · Test
setSearch(e.target.value)} style={{ paddingLeft: 28, padding:"5px 10px 5px 28px", fontSize: 12.5, width:"100%", background:"var(--bg-2)" }}/>
{candidates.length} graded
{candidates.length === 0 && (
No graded tests for class {cls} yet. Finish evaluating a test first.
)} {candidates.map((t) => { const pct = t.students ? Math.round(t.reviewed / t.students * 100) : 0; return (
onPick({ className: cls, test: t })} onMouseEnter={(e)=>e.currentTarget.style.background="var(--bg-2)"} onMouseLeave={(e)=>e.currentTarget.style.background="transparent"} className="cx-datarow r5" style={{ padding:"12px 16px", borderBottom:"1px solid var(--line-soft)", gap: 12, alignItems:"center", cursor:"pointer", }}> {t.id}
{t.title}
{t.subject} · {t.chapter}
{t.reviewed}/{t.students} reviewed
); })}
); }; /* ---------- Class report view ---------- */ const ClassReportView = ({ selection, onChange, setScreen }) => { const { className, test } = selection; const totalStudents = test.students || 0; const reviewed = test.reviewed || 0; // All three lists below are populated from backend aggregates (schema.sql: // v_question_accuracy, v_misconception_frequency, lesson_plans + steps). const perQ = window.APP.PER_QUESTION_PCT || []; const avg = perQ.length ? Math.round(perQ.reduce((a, r) => a + (r.pct || 0), 0) / perQ.length) : null; const buckets = window.APP.SCORE_BUCKETS || []; const maxBucket = buckets.length ? Math.max(...buckets.map(b => b.count)) : 0; const misconceptions = window.APP.TOP_MISCONCEPTIONS || []; const lessonPlan = window.APP.LESSON_PLAN || []; return (
{test.id} · Class {className} · {test.subject}

{test.title}

{test.chapter} · {test.date} · {reviewed}/{totalStudents} reviewed
{/* Summary stats — backend supplies avg/high/low/above/need on test.stats */}
{/* Per-question accuracy */}
Per-question accuracyfull · partial · missed
{perQ.length === 0 &&
No graded questions yet.
} {perQ.map(({q, pct, fullMarks, partial, missed}) => (
Q{q.num}
{q.english}
75 ? "var(--green)" : pct > 50 ? "var(--accent)" : "var(--red)", fontSize: 13, fontWeight: 600 }}>{pct}%
))}
{/* Score distribution */}
Score distribution
{buckets.length === 0 &&
No distribution data yet.
}
{buckets.map(b => (
{b.count}
{b.label}
))}
Percentage band · {totalStudents} students
{/* Misconceptions */}
Top misconceptions across the cohortdetected by Gemini · ranked by student count
{misconceptions.length === 0 &&
No misconceptions detected yet.
} {misconceptions.map(m => (
{m.code} {m.concept} {m.trend} {m.n} students
))}
{/* Lesson plan */}
Suggested lesson plan · next session gemini-2.5-flash targets the top 3 gaps · 50 min
{lessonPlan.length === 0 &&
Lesson plan will be generated once evaluations are confirmed.
} {lessonPlan.map((row, i) => (
{row.time}
{row.activity}
{row.focus}
))}
Regenerate with different constraints (time, mode, group size).
); }; const StatCard = ({ label, value, color }) => (
{label}
{value}
); window.ClassReports = ClassReports;