DocDoeAI / src /components /tuition /support /WeekendPyqTestPage.tsx
asnannp's picture
deploy: sync backend to Space root (learn-lesson HF cache fix)
65d68f5
Raw History Blame Contribute Delete
9.93 kB
"use client";
import { useMemo, useState } from "react";
import Link from "next/link";
import { CheckCircle2, Clock, Flag, Send } from "lucide-react";
import { buildWeekendPyqTest, evaluatePyqAnswers, updatePhase3Activity, type WeekendPyqResult } from "@/lib/phase3-learning";
import { cn } from "@/lib/utils";
import { getDefaultTuitionSupportInput } from "./tuitionSupportData";
export function WeekendPyqTestPage() {
const input = useMemo(() => getDefaultTuitionSupportInput(), []);
const test = useMemo(() => buildWeekendPyqTest(input), [input]);
const [activeQuestionId, setActiveQuestionId] = useState(test.questions[0]?.id ?? "");
const [answers, setAnswers] = useState<Record<string, string>>({});
const [marked, setMarked] = useState<Set<string>>(() => new Set());
const [result, setResult] = useState<WeekendPyqResult | null>(null);
const activeQuestion = test.questions.find((question) => question.id === activeQuestionId) ?? test.questions[0];
function updateAnswer(questionId: string, answer: string) {
setAnswers((current) => ({ ...current, [questionId]: answer }));
}
function toggleMarked(questionId: string) {
setMarked((current) => {
const next = new Set(current);
if (next.has(questionId)) next.delete(questionId);
else next.add(questionId);
return next;
});
}
function submitTest() {
const nextResult = evaluatePyqAnswers(test, answers);
setResult(nextResult);
updatePhase3Activity({
pyqSubmittedAt: new Date().toISOString(),
pyqScore: nextResult.score,
pyqMaxScore: nextResult.maxScore,
weakTopicsAdded: nextResult.weakAreas,
});
}
return (
<section className="mx-auto grid w-full max-w-7xl gap-5 px-4 py-6 text-white sm:px-6 lg:grid-cols-[220px_minmax(0,1fr)_280px] lg:px-8">
<aside className="rounded-xl border border-white/[0.08] bg-[#0B0B0B] p-4 lg:sticky lg:top-5 lg:self-start">
<p className="text-[10px] font-bold uppercase tracking-[0.16em] text-white/34">Navigator</p>
<div className="mt-4 grid grid-cols-4 gap-2 lg:grid-cols-2">
{test.questions.map((question, index) => {
const active = question.id === activeQuestion.id;
const answered = Boolean(answers[question.id]?.trim());
return (
<button
key={question.id}
type="button"
onClick={() => setActiveQuestionId(question.id)}
className={cn(
"relative flex min-h-12 items-center justify-center rounded-lg border text-sm font-bold transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/30",
active ? "border-white bg-white text-black" : "border-white/[0.08] bg-white/[0.03] text-white/62 hover:text-white",
)}
>
{index + 1}
{answered ? <CheckCircle2 className="absolute right-1.5 top-1.5 size-3" /> : null}
{marked.has(question.id) ? <Flag className="absolute bottom-1.5 right-1.5 size-3" /> : null}
</button>
);
})}
</div>
<Link href="/tuition" className="mt-4 inline-flex min-h-11 w-full items-center justify-center rounded-lg border border-white/[0.1] px-3 text-sm font-semibold text-white/66 hover:border-white/25 hover:text-white">
Back to Today&apos;s Tuition
</Link>
</aside>
<main className="min-w-0 space-y-5">
<header className="border-b border-white/[0.1] pb-5">
<div className="flex flex-wrap items-start justify-between gap-4">
<div>
<p className="text-[10px] font-bold uppercase tracking-[0.16em] text-white/34">{test.weekLabel}</p>
<h1 className="mt-2 text-2xl font-semibold text-white sm:text-3xl">Previous Questions Practice</h1>
<p className="mt-2 text-sm leading-6 text-white/56">
Sample exam practice for {test.subject} - {test.chapter}. More previous-question practice is coming as the beta improves.
</p>
</div>
<span className="inline-flex items-center gap-2 rounded-full border border-white/[0.1] bg-white/[0.04] px-3 py-1.5 text-xs font-semibold text-white/60">
<Clock className="size-4" />
{test.estimatedMinutes} min
</span>
</div>
<p className="mt-4 border-l-2 border-white/15 pl-3 text-xs leading-5 text-white/48">{test.safePatternNote}</p>
</header>
<article className="rounded-xl border border-white/[0.08] bg-[#0B0B0B] p-5 sm:p-6">
<div className="flex flex-wrap items-center justify-between gap-3">
<div>
<p className="text-[11px] font-bold uppercase tracking-[0.16em] text-white/34">{activeQuestion.label}</p>
<h2 className="mt-2 text-lg font-semibold leading-7 text-white">{activeQuestion.question}</h2>
</div>
<span className="rounded-full border border-white/[0.1] px-2.5 py-1 text-xs font-semibold text-white/56">
{activeQuestion.marks} marks
</span>
</div>
<textarea
aria-label="Write your board-style answer"
value={answers[activeQuestion.id] ?? ""}
onChange={(event) => updateAnswer(activeQuestion.id, event.target.value)}
rows={10}
placeholder="Write your board-style answer here. Include exam keywords."
className="mt-5 min-h-56 w-full resize-none rounded-lg border border-white/[0.12] bg-[#050505] p-4 text-base leading-7 text-white placeholder:text-white/34 focus:border-white/30 focus:outline-none focus-visible:ring-2 focus-visible:ring-white/30"
/>
<div className="mt-4 flex flex-wrap items-center justify-between gap-3">
<button
type="button"
onClick={() => toggleMarked(activeQuestion.id)}
className="inline-flex min-h-11 items-center gap-2 rounded-lg border border-white/[0.1] px-3 text-sm font-semibold text-white/66 transition hover:border-white/25 hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/30"
>
<Flag className="size-4" />
{marked.has(activeQuestion.id) ? "Marked for review" : "Mark for review"}
</button>
<button
type="button"
onClick={submitTest}
className="inline-flex min-h-11 items-center gap-2 rounded-lg bg-white px-4 text-sm font-bold text-black transition hover:bg-white/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/40 focus-visible:ring-offset-2 focus-visible:ring-offset-black"
>
<Send className="size-4" />
Submit practice
</button>
</div>
</article>
</main>
<aside className="space-y-5 lg:sticky lg:top-5 lg:self-start">
<section className="rounded-xl border border-white/[0.08] bg-[#0B0B0B] p-4">
<p className="text-[10px] font-bold uppercase tracking-[0.16em] text-white/34">Sections</p>
<div className="mt-3 divide-y divide-white/[0.08] border-y border-white/[0.08]">
{["A", "B"].map((section) => (
<div key={section} className="py-3">
<p className="text-sm font-semibold text-white">Section {section}</p>
<p className="mt-1 text-xs text-white/44">
{section === "A" ? "Previous-question style" : "Exam practice"}
</p>
</div>
))}
</div>
</section>
{result ? (
<section className="rounded-xl border border-white/[0.08] bg-white p-4 text-black">
<p className="text-[10px] font-bold uppercase tracking-[0.16em] text-black/45">Answer review</p>
<p className="mt-2 text-3xl font-black">{result.score}/{result.maxScore}</p>
<div className="mt-4 space-y-3 text-sm">
<ReviewBlock title="Board-style answer" items={[result.correctAnswer]} />
<ReviewBlock title="Missing keywords" items={result.missingKeywords.length ? result.missingKeywords : ["No major keyword gap detected."]} />
<ReviewBlock title="Marks lost" items={[`${result.maxScore - result.score} mark${result.maxScore - result.score === 1 ? "" : "s"} lost from missing keywords or unclear points.`]} />
<ReviewBlock title="Topics to revise" items={result.weakAreas.length ? result.weakAreas : ["No weak area added."]} />
<p className="rounded-lg bg-black/[0.06] p-3 text-xs leading-5 text-black/70">{result.suggestedRevision}</p>
</div>
<Link href="/tuition/revision" className="mt-4 inline-flex min-h-11 w-full items-center justify-center rounded-lg bg-black px-3 text-sm font-bold text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-black/40">
Add weak topics to revision
</Link>
</section>
) : (
<section className="border-y border-white/[0.08] py-4">
<p className="text-[10px] font-bold uppercase tracking-[0.16em] text-white/34">After submit</p>
<p className="mt-2 text-sm leading-6 text-white/52">
You will see score, board-style answer, missing keywords, marks lost, and revision suggestions.
</p>
</section>
)}
</aside>
</section>
);
}
function ReviewBlock({ title, items }: { title: string; items: string[] }) {
return (
<div>
<p className="text-[10px] font-bold uppercase tracking-[0.14em] text-black/45">{title}</p>
<ul className="mt-1 space-y-1">
{items.map((item) => (
<li key={item} className="text-xs leading-5 text-black/70">{item}</li>
))}
</ul>
</div>
);
}