first commit
This commit is contained in:
commit
0e854110d3
49 files changed
+2292
No files matched your search
@@ -0,0 +1,127 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { api, type Structure, type QuizCard } from "../lib/api.js";
|
||||
import { useAllStructures } from "../hooks/useStructures.js";
|
||||
import { Scene } from "../components/viewer/Scene.js";
|
||||
import { LayerToggle } from "../components/LayerToggle.js";
|
||||
import { useAtlasStore } from "../store/useAtlasStore.js";
|
||||
|
||||
type Deck = (Structure & { quizCard: QuizCard | null })[];
|
||||
|
||||
export default function QuizPage() {
|
||||
const { bySlug } = useAllStructures();
|
||||
const [deck, setDeck] = useState<Deck>([]);
|
||||
const [index, setIndex] = useState(0);
|
||||
const [feedback, setFeedback] = useState<"correct" | "wrong" | null>(null);
|
||||
const [score, setScore] = useState({ correct: 0, total: 0 });
|
||||
|
||||
const selectedStructureId = useAtlasStore((s) => s.selectedStructureId);
|
||||
const selectStructure = useAtlasStore((s) => s.selectStructure);
|
||||
const setVisibleSystems = useAtlasStore((s) => s.setVisibleSystems);
|
||||
const focusOnStructure = useAtlasStore((s) => s.focusOnStructure);
|
||||
|
||||
async function loadDeck() {
|
||||
const next = await api.quizNext({ limit: 10 });
|
||||
setDeck(next);
|
||||
setIndex(0);
|
||||
setScore({ correct: 0, total: 0 });
|
||||
selectStructure(null);
|
||||
if (next.length > 0) {
|
||||
setVisibleSystems([...new Set(next.map((s) => s.system.slug))]);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
loadDeck();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const current = deck[index];
|
||||
const ignoreNextSelectRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!current || !selectedStructureId || feedback) return;
|
||||
if (ignoreNextSelectRef.current) {
|
||||
ignoreNextSelectRef.current = false;
|
||||
return;
|
||||
}
|
||||
const isCorrect = selectedStructureId === current.id;
|
||||
setFeedback(isCorrect ? "correct" : "wrong");
|
||||
setScore((s) => ({ correct: s.correct + (isCorrect ? 1 : 0), total: s.total + 1 }));
|
||||
api.quizReview(current.id, isCorrect ? 5 : 2).catch(console.error);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedStructureId]);
|
||||
|
||||
function next() {
|
||||
selectStructure(null);
|
||||
setFeedback(null);
|
||||
if (index + 1 < deck.length) setIndex(index + 1);
|
||||
else loadDeck();
|
||||
}
|
||||
|
||||
function giveUp() {
|
||||
if (!current) return;
|
||||
setFeedback("wrong");
|
||||
setScore((s) => ({ ...s, total: s.total + 1 }));
|
||||
api.quizReview(current.id, 2).catch(console.error);
|
||||
ignoreNextSelectRef.current = true;
|
||||
focusOnStructure(current.slug);
|
||||
selectStructure(current.id);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-full grid grid-cols-[280px_1fr]">
|
||||
<aside className="border-r border-white/10 bg-panel p-3 flex flex-col gap-4 min-h-0">
|
||||
<LayerToggle />
|
||||
</aside>
|
||||
|
||||
<div className="relative">
|
||||
<Scene bySlug={bySlug} />
|
||||
|
||||
<div className="absolute top-3 left-1/2 -translate-x-1/2 bg-panel/95 border border-white/10 rounded-lg px-5 py-3 text-center shadow-lg max-w-md">
|
||||
{!current ? (
|
||||
<p className="text-sm text-slate-300">
|
||||
Nenhuma estrutura pendente de revisão agora. 🎉
|
||||
</p>
|
||||
) : (
|
||||
<>
|
||||
<p className="text-xs text-slate-400 uppercase tracking-wide mb-1">
|
||||
Onde fica esta estrutura?
|
||||
</p>
|
||||
<p className="text-lg font-semibold text-white">{current.namePt}</p>
|
||||
<p className="text-xs text-slate-500 mb-2">{current.system.namePt}</p>
|
||||
|
||||
{feedback === "correct" && (
|
||||
<p className="text-sm text-emerald-400 mb-2">Correto! ✅</p>
|
||||
)}
|
||||
{feedback === "wrong" && (
|
||||
<p className="text-sm text-rose-400 mb-2">Não foi essa estrutura.</p>
|
||||
)}
|
||||
|
||||
<div className="flex gap-2 justify-center mt-2">
|
||||
{feedback ? (
|
||||
<button
|
||||
onClick={next}
|
||||
className="text-sm px-3 py-1 rounded bg-accent text-white"
|
||||
>
|
||||
Próxima
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={giveUp}
|
||||
className="text-sm px-3 py-1 rounded bg-white/10 text-slate-200"
|
||||
>
|
||||
Mostrar resposta
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-500 mt-2">
|
||||
{index + 1} / {deck.length} · acertos: {score.correct}/{score.total}
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user