fix(ui): change ui theme color

feat(calc): add geogebra based graph calculator for tests
This commit is contained in:
shafin-r
2026-02-20 00:03:23 +06:00
parent 626616c8b5
commit 3c8f945539
18 changed files with 2063 additions and 259 deletions

View File

@ -1,7 +1,5 @@
import { useNavigate } from "react-router-dom";
import { Button } from "../../../components/ui/button";
import { useResults } from "../../../stores/useResults";
import { useSatExam } from "../../../stores/useSatExam";
import { LucideArrowLeft } from "lucide-react";
import {
Card,
@ -11,7 +9,6 @@ import {
CardHeader,
CardTitle,
} from "../../../components/ui/card";
import { Progress } from "../../../components/ui/progress";
import { CircularLevelProgress } from "../../../components/CircularLevelProgress";
import { useEffect, useState } from "react";
import { useExamConfigStore } from "../../../stores/useExamConfigStore";
@ -33,7 +30,7 @@ const XPGainedCard = ({
if (!results?.xp_gained) return;
let startTime: number | null = null;
const duration = 800; // ms
const duration = 800;
const animate = (time: number) => {
if (!startTime) startTime = time;
@ -58,39 +55,139 @@ const XPGainedCard = ({
);
};
// ─── Targeted static results ──────────────────────────────────────────────────
const TARGETED_XP = 15;
const TARGETED_SCORE = 15;
const TargetedResults = ({ onFinish }: { onFinish: () => void }) => {
const { userXp, setUserXp } = useExamConfigStore();
// previousXP is whatever the user had before; we add 15 on top
const previousXP = userXp ?? 0;
const gainedXP = TARGETED_XP;
const totalXP = previousXP;
// Sync updated XP back into the store
useEffect(() => {
setUserXp(totalXP);
}, []);
// Simple level bounds — 0100 per level so progress is visible
// Adjust these to match your real level thresholds if needed
const levelMinXP = Math.floor(previousXP / 100) * 100;
const levelMaxXP = levelMinXP + 100;
const currentLevel = Math.floor(previousXP / 100) + 1;
const [displayXP, setDisplayXP] = useState(0);
useEffect(() => {
let startTime: number | null = null;
const duration = 800;
const animate = (time: number) => {
if (!startTime) startTime = time;
const t = Math.min((time - startTime) / duration, 1);
setDisplayXP(Math.floor(t * gainedXP));
if (t < 1) requestAnimationFrame(animate);
};
requestAnimationFrame(animate);
}, []);
return (
<main className="min-h-screen bg-gray-50 space-y-6 mx-auto px-8 sm:px-6 lg:px-90 py-10">
<header className="flex gap-4">
<button
onClick={onFinish}
className="p-2 rounded-full border border-purple-400 bg-linear-to-br from-purple-400 to-purple-500"
>
<LucideArrowLeft size={20} color="white" />
</button>
<h1 className="text-3xl font-satoshi-bold">Results</h1>
</header>
{/* Targeted mode badge */}
<div className="flex items-center gap-2 bg-purple-50 border border-purple-200 rounded-2xl px-4 py-3">
<span className="text-xl">🎯</span>
<p className="font-satoshi text-purple-700 text-sm">
<strong>Targeted Mode Complete!</strong> You answered all questions
correctly.
</p>
</div>
<section className="w-full flex items-center justify-center">
<CircularLevelProgress
previousXP={previousXP}
gainedXP={gainedXP}
levelMinXP={levelMinXP}
levelMaxXP={levelMaxXP}
level={currentLevel}
/>
</section>
<Card>
<CardHeader>
<CardTitle>XP</CardTitle>
<CardDescription>How much did you improve?</CardDescription>
<CardAction>
<p className="font-satoshi-medium">+{displayXP} XP</p>
</CardAction>
</CardHeader>
</Card>
<Card>
<CardHeader>
<CardTitle>Score</CardTitle>
<CardDescription>Total score you achieved.</CardDescription>
<CardAction>
<p className="font-satoshi-medium">{TARGETED_SCORE}</p>
</CardAction>
</CardHeader>
</Card>
<Card>
<CardHeader>
<CardTitle>Keep it up! 🚀</CardTitle>
<CardDescription>
Great work getting every question right. Keep practicing to level up
faster!
</CardDescription>
</CardHeader>
</Card>
<button
onClick={onFinish}
className="w-full font-satoshi rounded-3xl text-lg py-4 bg-linear-to-br from-purple-500 to-purple-600 text-white"
>
Done
</button>
</main>
);
};
// ─── Main Results ─────────────────────────────────────────────────────────────
export const Results = () => {
const navigate = useNavigate();
const results = useResults((s) => s.results);
const clearResults = useResults((s) => s.clearResults);
const { setUserXp } = useExamConfigStore();
const { setUserXp, payload } = useExamConfigStore();
const isTargeted = payload?.mode === "TARGETED";
useEffect(() => {
if (results) setUserXp(results?.total_xp);
}, [results]);
function handleFinishExam() {
useExamConfigStore.getState().clearPayload();
clearResults();
navigate(`/student/home`);
}
// const [displayXP, setDisplayXP] = useState(0);
// useEffect(() => {
// if (!results?.score) return;
// let start = 0;
// const duration = 600;
// const startTime = performance.now();
// const animate = (time: number) => {
// const t = Math.min((time - startTime) / duration, 1);
// setDisplayXP(Math.floor(t * results.score));
// if (t < 1) requestAnimationFrame(animate);
// };
// requestAnimationFrame(animate);
// }, [results?.score]);
// ── Targeted mode: show static screen ──────────────────────────────────────
if (isTargeted) {
return <TargetedResults onFinish={handleFinishExam} />;
}
// ── Standard mode ──────────────────────────────────────────────────────────
const previousXP = results ? results.total_xp - results.xp_gained : 0;
return (
@ -107,11 +204,6 @@ export const Results = () => {
<section className="w-full flex items-center justify-center">
{results && (
<CircularLevelProgress
// previousXP={505}
// gainedXP={605}
// levelMinXP={500}
// levelMaxXP={1000}
// level={3}
previousXP={previousXP}
gainedXP={results.xp_gained}
levelMinXP={results.current_level_start}