Download src/components/tuition/support/WeekendPyqTestPage.tsx from asnannp/DocDoeAI: direct link, hf CLI and curl.
- Browser
- Download file 9.93 kB
-
https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/components/tuition/support/WeekendPyqTestPage.tsx
- Command line
-
hf download hf://spaces/asnannp/DocDoeAI/src/components/tuition/support/WeekendPyqTestPage.tsx
-
curl -L -o WeekendPyqTestPage.tsx https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/components/tuition/support/WeekendPyqTestPage.tsx
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'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> | |
| ); | |
| } | |