first commit

This commit is contained in:
Rodrigo Bertoldo de Sousa Martins committed 2026-09-07 20:46:08 -03:00
commit 0e854110d3
49 files changed
+2292

No files matched your search

+127
View File
@@ -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>
);
}