ui fixes
This commit is contained in:
@@ -5,10 +5,7 @@ import {
|
|||||||
type FlashcardLibrarySummary,
|
type FlashcardLibrarySummary,
|
||||||
} from '../services/MindforgeApiService';
|
} from '../services/MindforgeApiService';
|
||||||
import { Button } from './Button';
|
import { Button } from './Button';
|
||||||
import {
|
import { FlashcardStudySession } from './FlashcardStudySession';
|
||||||
FlashcardStudySession,
|
|
||||||
type FlashcardStudySessionLibraryMeta,
|
|
||||||
} from './FlashcardStudySession';
|
|
||||||
import './FlashcardReviewComponent.css';
|
import './FlashcardReviewComponent.css';
|
||||||
|
|
||||||
function groupLibrariesBySubject(libraries: FlashcardLibrarySummary[]) {
|
function groupLibrariesBySubject(libraries: FlashcardLibrarySummary[]) {
|
||||||
@@ -76,18 +73,6 @@ export function FlashcardReviewComponent() {
|
|||||||
|
|
||||||
const groupedLibraries = useMemo(() => groupLibrariesBySubject(libraries), [libraries]);
|
const groupedLibraries = useMemo(() => groupLibrariesBySubject(libraries), [libraries]);
|
||||||
|
|
||||||
const libraryMetaById = useMemo(() => {
|
|
||||||
return new Map<number, FlashcardStudySessionLibraryMeta>(
|
|
||||||
libraries.map((library) => [
|
|
||||||
library.id,
|
|
||||||
{
|
|
||||||
fileName: library.fileName,
|
|
||||||
difficultyLabel: difficultyLabel(library.difficulty),
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
}, [libraries]);
|
|
||||||
|
|
||||||
const toggleLibrary = (libraryId: number) => {
|
const toggleLibrary = (libraryId: number) => {
|
||||||
if (selectedLibraryIds.includes(libraryId)) {
|
if (selectedLibraryIds.includes(libraryId)) {
|
||||||
setSelectedLibraryIds(selectedLibraryIds.filter((id) => id !== libraryId));
|
setSelectedLibraryIds(selectedLibraryIds.filter((id) => id !== libraryId));
|
||||||
@@ -189,7 +174,6 @@ export function FlashcardReviewComponent() {
|
|||||||
{sessionCards.length > 0 && (
|
{sessionCards.length > 0 && (
|
||||||
<FlashcardStudySession
|
<FlashcardStudySession
|
||||||
cards={sessionCards}
|
cards={sessionCards}
|
||||||
libraryMetaById={libraryMetaById}
|
|
||||||
onAnswer={recordReviewAnswer}
|
onAnswer={recordReviewAnswer}
|
||||||
onEnd={endSession}
|
onEnd={endSession}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -147,7 +147,6 @@
|
|||||||
min-height: 355px;
|
min-height: 355px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
|
||||||
padding: clamp(24px, 5vw, 42px);
|
padding: clamp(24px, 5vw, 42px);
|
||||||
border-radius: 30px;
|
border-radius: 30px;
|
||||||
backface-visibility: hidden;
|
backface-visibility: hidden;
|
||||||
@@ -220,7 +219,8 @@
|
|||||||
display: grid;
|
display: grid;
|
||||||
align-content: center;
|
align-content: center;
|
||||||
gap: 18px;
|
gap: 18px;
|
||||||
min-height: 190px;
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
font-family: "Segoe UI", Inter, Avenir, system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
|
font-family: "Segoe UI", Inter, Avenir, system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
|
||||||
}
|
}
|
||||||
@@ -262,18 +262,6 @@
|
|||||||
line-height: 1.45;
|
line-height: 1.45;
|
||||||
}
|
}
|
||||||
|
|
||||||
.study-card-footer {
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 14px;
|
|
||||||
align-items: center;
|
|
||||||
color: var(--muted);
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 800;
|
|
||||||
}
|
|
||||||
|
|
||||||
.study-controls {
|
.study-controls {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|||||||
@@ -3,14 +3,8 @@ import type { FlashcardCard } from '../services/MindforgeApiService';
|
|||||||
import { Button } from './Button';
|
import { Button } from './Button';
|
||||||
import './FlashcardStudySession.css';
|
import './FlashcardStudySession.css';
|
||||||
|
|
||||||
export interface FlashcardStudySessionLibraryMeta {
|
|
||||||
fileName?: string;
|
|
||||||
difficultyLabel?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface FlashcardStudySessionProps {
|
interface FlashcardStudySessionProps {
|
||||||
cards: FlashcardCard[];
|
cards: FlashcardCard[];
|
||||||
libraryMetaById: Map<number, FlashcardStudySessionLibraryMeta>;
|
|
||||||
onAnswer: (card: FlashcardCard, correct: boolean) => Promise<void>;
|
onAnswer: (card: FlashcardCard, correct: boolean) => Promise<void>;
|
||||||
onEnd: () => void;
|
onEnd: () => void;
|
||||||
}
|
}
|
||||||
@@ -98,14 +92,9 @@ function resetTimeout(timeoutRef: { current: number | null }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatFooter(meta: FlashcardStudySessionLibraryMeta | undefined) {
|
export function FlashcardStudySession({ cards, onAnswer, onEnd }: FlashcardStudySessionProps) {
|
||||||
return meta?.fileName || 'Arquivo';
|
|
||||||
}
|
|
||||||
|
|
||||||
export function FlashcardStudySession({ cards, libraryMetaById, onAnswer, onEnd }: FlashcardStudySessionProps) {
|
|
||||||
const [currentIndex, setCurrentIndex] = useState(0);
|
const [currentIndex, setCurrentIndex] = useState(0);
|
||||||
const [showAnswer, setShowAnswer] = useState(false);
|
const [showAnswer, setShowAnswer] = useState(false);
|
||||||
const [showBackQuestion, setShowBackQuestion] = useState(false);
|
|
||||||
const [submittingAnswer, setSubmittingAnswer] = useState(false);
|
const [submittingAnswer, setSubmittingAnswer] = useState(false);
|
||||||
const [cardExiting, setCardExiting] = useState(false);
|
const [cardExiting, setCardExiting] = useState(false);
|
||||||
const [stampState, setStampState] = useState<'correct' | 'wrong' | null>(null);
|
const [stampState, setStampState] = useState<'correct' | 'wrong' | null>(null);
|
||||||
@@ -117,7 +106,6 @@ export function FlashcardStudySession({ cards, libraryMetaById, onAnswer, onEnd
|
|||||||
const advanceTimerRef = useRef<number | null>(null);
|
const advanceTimerRef = useRef<number | null>(null);
|
||||||
|
|
||||||
const currentCard = cards[currentIndex];
|
const currentCard = cards[currentIndex];
|
||||||
const currentMeta = currentCard ? libraryMetaById.get(currentCard.libraryId) : undefined;
|
|
||||||
const correctCount = Object.values(sessionAnswers).filter(Boolean).length;
|
const correctCount = Object.values(sessionAnswers).filter(Boolean).length;
|
||||||
const remainingCount = cards.length - currentIndex;
|
const remainingCount = cards.length - currentIndex;
|
||||||
const progressPercent = cards.length > 0
|
const progressPercent = cards.length > 0
|
||||||
@@ -127,7 +115,6 @@ export function FlashcardStudySession({ cards, libraryMetaById, onAnswer, onEnd
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCurrentIndex(0);
|
setCurrentIndex(0);
|
||||||
setShowAnswer(false);
|
setShowAnswer(false);
|
||||||
setShowBackQuestion(false);
|
|
||||||
setSubmittingAnswer(false);
|
setSubmittingAnswer(false);
|
||||||
setCardExiting(false);
|
setCardExiting(false);
|
||||||
setStampState(null);
|
setStampState(null);
|
||||||
@@ -149,13 +136,12 @@ export function FlashcardStudySession({ cards, libraryMetaById, onAnswer, onEnd
|
|||||||
|
|
||||||
if (e.code === 'Space' || e.code === 'Enter') {
|
if (e.code === 'Space' || e.code === 'Enter') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!showAnswer) {
|
if (submittingAnswer) return;
|
||||||
setShowAnswer(true);
|
setShowAnswer((current) => !current);
|
||||||
}
|
} else if (e.code === 'KeyC' && showAnswer && !submittingAnswer) {
|
||||||
} else if (e.code === 'KeyC' && showAnswer && !showBackQuestion && !submittingAnswer) {
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
void registerReviewAnswer(true);
|
void registerReviewAnswer(true);
|
||||||
} else if (e.code === 'KeyW' && showAnswer && !showBackQuestion && !submittingAnswer) {
|
} else if (e.code === 'KeyW' && showAnswer && !submittingAnswer) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
void registerReviewAnswer(false);
|
void registerReviewAnswer(false);
|
||||||
}
|
}
|
||||||
@@ -163,13 +149,12 @@ export function FlashcardStudySession({ cards, libraryMetaById, onAnswer, onEnd
|
|||||||
|
|
||||||
window.addEventListener('keydown', handleKeyDown);
|
window.addEventListener('keydown', handleKeyDown);
|
||||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||||
}, [cards.length, currentCard, showAnswer, showBackQuestion, submittingAnswer]);
|
}, [cards.length, currentCard, showAnswer, submittingAnswer]);
|
||||||
|
|
||||||
const goToPrevious = () => {
|
const goToPrevious = () => {
|
||||||
if (currentIndex === 0 || submittingAnswer) return;
|
if (currentIndex === 0 || submittingAnswer) return;
|
||||||
setCurrentIndex(currentIndex - 1);
|
setCurrentIndex(currentIndex - 1);
|
||||||
setShowAnswer(false);
|
setShowAnswer(false);
|
||||||
setShowBackQuestion(false);
|
|
||||||
setStampState(null);
|
setStampState(null);
|
||||||
setCardExiting(false);
|
setCardExiting(false);
|
||||||
setSubmissionError(null);
|
setSubmissionError(null);
|
||||||
@@ -183,7 +168,6 @@ export function FlashcardStudySession({ cards, libraryMetaById, onAnswer, onEnd
|
|||||||
|
|
||||||
setCurrentIndex(currentIndex + 1);
|
setCurrentIndex(currentIndex + 1);
|
||||||
setShowAnswer(false);
|
setShowAnswer(false);
|
||||||
setShowBackQuestion(false);
|
|
||||||
setStampState(null);
|
setStampState(null);
|
||||||
setCardExiting(false);
|
setCardExiting(false);
|
||||||
setSubmittingAnswer(false);
|
setSubmittingAnswer(false);
|
||||||
@@ -191,7 +175,7 @@ export function FlashcardStudySession({ cards, libraryMetaById, onAnswer, onEnd
|
|||||||
};
|
};
|
||||||
|
|
||||||
const registerReviewAnswer = async (correct: boolean) => {
|
const registerReviewAnswer = async (correct: boolean) => {
|
||||||
if (!currentCard || !showAnswer || showBackQuestion || submittingAnswer) return;
|
if (!currentCard || !showAnswer || submittingAnswer) return;
|
||||||
|
|
||||||
resetTimeout(stampTimerRef);
|
resetTimeout(stampTimerRef);
|
||||||
resetTimeout(advanceTimerRef);
|
resetTimeout(advanceTimerRef);
|
||||||
@@ -266,37 +250,20 @@ export function FlashcardStudySession({ cards, libraryMetaById, onAnswer, onEnd
|
|||||||
class={`study-flashcard${showAnswer ? ' is-flipped' : ''}${cardExiting ? ' is-reviewed' : ''}`}
|
class={`study-flashcard${showAnswer ? ' is-flipped' : ''}${cardExiting ? ' is-reviewed' : ''}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (submittingAnswer) return;
|
if (submittingAnswer) return;
|
||||||
if (!showAnswer) {
|
setShowAnswer((current) => !current);
|
||||||
setShowAnswer(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!showBackQuestion) {
|
|
||||||
setShowBackQuestion(true);
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
role="button"
|
role="button"
|
||||||
aria-label={
|
aria-label={
|
||||||
!showAnswer
|
!showAnswer
|
||||||
? 'Clique ou pressione Espaço para revelar'
|
? 'Clique ou pressione Espaço para revelar a resposta'
|
||||||
: showBackQuestion
|
: 'Clique ou pressione Espaço para voltar à pergunta'
|
||||||
? 'Flashcard com pergunta e resposta visíveis'
|
|
||||||
: 'Clique para mostrar a pergunta junto da resposta'
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div class="study-card-face">
|
<div class="study-card-face">
|
||||||
{currentMeta?.difficultyLabel && (
|
|
||||||
<div class="study-card-meta">
|
|
||||||
<span class="study-tag">{currentMeta.difficultyLabel}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div class="study-card-question">
|
<div class="study-card-question">
|
||||||
{currentCard.front}
|
{currentCard.front}
|
||||||
</div>
|
</div>
|
||||||
<div class="study-card-footer">
|
|
||||||
<span>{formatFooter(currentMeta)}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="study-card-face study-card-back">
|
<div class="study-card-face study-card-back">
|
||||||
@@ -307,22 +274,16 @@ export function FlashcardStudySession({ cards, libraryMetaById, onAnswer, onEnd
|
|||||||
)}
|
)}
|
||||||
<div class="study-card-meta">
|
<div class="study-card-meta">
|
||||||
<span class="study-tag">Resposta</span>
|
<span class="study-tag">Resposta</span>
|
||||||
{currentMeta?.difficultyLabel && <span>{currentMeta.difficultyLabel}</span>}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="study-card-answer">
|
<div class="study-card-answer">
|
||||||
<div class="study-back-answer">
|
<div class="study-back-answer">
|
||||||
<span>Resposta</span>
|
<span>Resposta</span>
|
||||||
<p>{currentCard.back}</p>
|
<p>{currentCard.back}</p>
|
||||||
</div>
|
</div>
|
||||||
{showBackQuestion && (
|
|
||||||
<div class="study-back-question">
|
<div class="study-back-question">
|
||||||
<span>Pergunta</span>
|
<span>Pergunta</span>
|
||||||
<p>{currentCard.front}</p>
|
<p>{currentCard.front}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="study-card-footer">
|
|
||||||
<span>{formatFooter(currentMeta)}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -332,14 +293,14 @@ export function FlashcardStudySession({ cards, libraryMetaById, onAnswer, onEnd
|
|||||||
<button
|
<button
|
||||||
class="study-review-button correct"
|
class="study-review-button correct"
|
||||||
onClick={() => void registerReviewAnswer(true)}
|
onClick={() => void registerReviewAnswer(true)}
|
||||||
disabled={!showAnswer || showBackQuestion || submittingAnswer}
|
disabled={!showAnswer || submittingAnswer}
|
||||||
>
|
>
|
||||||
Correto
|
Correto
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="study-review-button wrong"
|
class="study-review-button wrong"
|
||||||
onClick={() => void registerReviewAnswer(false)}
|
onClick={() => void registerReviewAnswer(false)}
|
||||||
disabled={!showAnswer || showBackQuestion || submittingAnswer}
|
disabled={!showAnswer || submittingAnswer}
|
||||||
>
|
>
|
||||||
Incorreto
|
Incorreto
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -7,10 +7,7 @@ import {
|
|||||||
type FlashcardRagStatus,
|
type FlashcardRagStatus,
|
||||||
} from '../services/MindforgeApiService';
|
} from '../services/MindforgeApiService';
|
||||||
import { Button } from './Button';
|
import { Button } from './Button';
|
||||||
import {
|
import { FlashcardStudySession } from './FlashcardStudySession';
|
||||||
FlashcardStudySession,
|
|
||||||
type FlashcardStudySessionLibraryMeta,
|
|
||||||
} from './FlashcardStudySession';
|
|
||||||
import './SpacedReviewComponent.css';
|
import './SpacedReviewComponent.css';
|
||||||
|
|
||||||
interface RagStatusOption {
|
interface RagStatusOption {
|
||||||
@@ -102,13 +99,12 @@ export function SpacedReviewComponent() {
|
|||||||
const [dashboard, setDashboard] = useState<FlashcardRagDashboardResponse | null>(null);
|
const [dashboard, setDashboard] = useState<FlashcardRagDashboardResponse | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [selectedStatuses, setSelectedStatuses] = useState<FlashcardRagStatus[]>(['Red', 'Amber', 'Green', 'Grey']);
|
const [selectedStatuses, setSelectedStatuses] = useState<FlashcardRagStatus[]>(['Red', 'Amber']);
|
||||||
const [selectedLibraryIds, setSelectedLibraryIds] = useState<number[]>([]);
|
const [selectedLibraryIds, setSelectedLibraryIds] = useState<number[]>([]);
|
||||||
const [selectedSubjects, setSelectedSubjects] = useState<string[]>([]);
|
const [selectedSubjects, setSelectedSubjects] = useState<string[]>([]);
|
||||||
const [selectedSubSubjects, setSelectedSubSubjects] = useState<string[]>([]);
|
const [selectedSubSubjects, setSelectedSubSubjects] = useState<string[]>([]);
|
||||||
const [startingSession, setStartingSession] = useState(false);
|
const [startingSession, setStartingSession] = useState(false);
|
||||||
const [sessionCards, setSessionCards] = useState<FlashcardCard[]>([]);
|
const [sessionCards, setSessionCards] = useState<FlashcardCard[]>([]);
|
||||||
const [sessionLibraries, setSessionLibraries] = useState<FlashcardRagLibrary[]>([]);
|
|
||||||
|
|
||||||
const loadDashboard = async (preserveSelection: boolean) => {
|
const loadDashboard = async (preserveSelection: boolean) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -138,7 +134,7 @@ export function SpacedReviewComponent() {
|
|||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ['Red', 'Amber', 'Green', 'Grey'];
|
return ['Red', 'Amber'];
|
||||||
});
|
});
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setError(err?.message || 'Falha ao carregar status de revisão espaçada.');
|
setError(err?.message || 'Falha ao carregar status de revisão espaçada.');
|
||||||
@@ -183,7 +179,6 @@ export function SpacedReviewComponent() {
|
|||||||
setStartingSession(true);
|
setStartingSession(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
setSessionCards([]);
|
setSessionCards([]);
|
||||||
setSessionLibraries([]);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const ragByLibraryId = new Map(selectedRagLibraries.map((library) => [library.libraryId, library]));
|
const ragByLibraryId = new Map(selectedRagLibraries.map((library) => [library.libraryId, library]));
|
||||||
@@ -198,7 +193,6 @@ export function SpacedReviewComponent() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setSessionLibraries(selectedRagLibraries);
|
|
||||||
setSessionCards(orderedCards);
|
setSessionCards(orderedCards);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setError(err?.message || 'Falha ao iniciar revisão espaçada.');
|
setError(err?.message || 'Falha ao iniciar revisão espaçada.');
|
||||||
@@ -224,24 +218,14 @@ export function SpacedReviewComponent() {
|
|||||||
libraryIdSet.has(library.libraryId) && statusSet.has(library.ragStatus));
|
libraryIdSet.has(library.libraryId) && statusSet.has(library.ragStatus));
|
||||||
}, [allRagLibraries, selectedLibraryIds, selectedStatuses]);
|
}, [allRagLibraries, selectedLibraryIds, selectedStatuses]);
|
||||||
|
|
||||||
const sessionLibraryMetaById = useMemo(() => {
|
|
||||||
return new Map<number, FlashcardStudySessionLibraryMeta>(
|
|
||||||
sessionLibraries.map((library) => [
|
|
||||||
library.libraryId,
|
|
||||||
{
|
|
||||||
fileName: library.fileName,
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
}, [sessionLibraries]);
|
|
||||||
|
|
||||||
const toggleStatus = (status: FlashcardRagStatus) => {
|
const toggleStatus = (status: FlashcardRagStatus) => {
|
||||||
if (selectedStatuses.includes(status)) {
|
if (selectedStatuses.includes(status)) {
|
||||||
setSelectedStatuses([]);
|
const next = selectedStatuses.filter((s) => s !== status);
|
||||||
|
setSelectedStatuses(next.length > 0 ? next : ['Red']);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setSelectedStatuses(['Red', 'Amber', 'Green', 'Grey']);
|
setSelectedStatuses([...selectedStatuses, status]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleLibrary = (libraryId: number) => {
|
const toggleLibrary = (libraryId: number) => {
|
||||||
@@ -313,7 +297,6 @@ export function SpacedReviewComponent() {
|
|||||||
|
|
||||||
const endSession = () => {
|
const endSession = () => {
|
||||||
setSessionCards([]);
|
setSessionCards([]);
|
||||||
setSessionLibraries([]);
|
|
||||||
void loadDashboard(true);
|
void loadDashboard(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -460,7 +443,6 @@ export function SpacedReviewComponent() {
|
|||||||
{sessionCards.length > 0 && (
|
{sessionCards.length > 0 && (
|
||||||
<FlashcardStudySession
|
<FlashcardStudySession
|
||||||
cards={sessionCards}
|
cards={sessionCards}
|
||||||
libraryMetaById={sessionLibraryMetaById}
|
|
||||||
onAnswer={registerAnswer}
|
onAnswer={registerAnswer}
|
||||||
onEnd={endSession}
|
onEnd={endSession}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -141,13 +141,14 @@ Formas de requisição principais:
|
|||||||
- **Idioma**: Todo texto em **português brasileiro**, com acentuação e grafia corretas nas telas e mensagens.
|
- **Idioma**: Todo texto em **português brasileiro**, com acentuação e grafia corretas nas telas e mensagens.
|
||||||
- **Navegação**: Os rótulos visíveis da revisão usam "Revisão de Flashcards" e "Revisão espaçada" como nomes canônicos. A marca Mindforge na sidebar navega para a página inicial.
|
- **Navegação**: Os rótulos visíveis da revisão usam "Revisão de Flashcards" e "Revisão espaçada" como nomes canônicos. A marca Mindforge na sidebar navega para a página inicial.
|
||||||
- **Revisão de flashcards**: "Revisão de Flashcards" e "Revisão espaçada" mantêm seleção/filtros próprios, mas compartilham a mesma sessão visual (`FlashcardStudySession`) com título "Sessão de Revisão", flip 3D, atalhos, fila/progresso, carimbo e confete.
|
- **Revisão de flashcards**: "Revisão de Flashcards" e "Revisão espaçada" mantêm seleção/filtros próprios, mas compartilham a mesma sessão visual (`FlashcardStudySession`) com título "Sessão de Revisão", flip 3D, atalhos, fila/progresso, carimbo e confete.
|
||||||
|
- **Filtro RAG da "Revisão espaçada"**: o painel inicia com apenas Vermelho e Amarelo selecionados. Os checkboxes de status funcionam como multi-select comum (clicar num selecionado o remove, clicar num não selecionado o adiciona); se o usuário chegar a uma seleção vazia, o sistema força `['Red']` automaticamente para impedir revisão sem status. A lista de arquivos é filtrada pela união dos status marcados.
|
||||||
- **Tema**: Claro quente vintage ("mesa de estudo pessoal"). Fundos creme/pergaminho/dourado com textura de papel (grid via `body::before`). Vidro translúcido com `backdrop-filter` em tons quentes.
|
- **Tema**: Claro quente vintage ("mesa de estudo pessoal"). Fundos creme/pergaminho/dourado com textura de papel (grid via `body::before`). Vidro translúcido com `backdrop-filter` em tons quentes.
|
||||||
- **Botões**: Estilo pill quente com sombras marrons. Variantes `primary` (gradiente azul, com brilho hover) e `secondary` (translúcido pergaminho).
|
- **Botões**: Estilo pill quente com sombras marrons. Variantes `primary` (gradiente azul, com brilho hover) e `secondary` (translúcido pergaminho).
|
||||||
- **Tipografia**: Inter (UI global), Georgia/Times New Roman (marca e cabeçalhos), Segoe UI/Inter (conteúdo do flashcard).
|
- **Tipografia**: Inter (UI global), Georgia/Times New Roman (marca e cabeçalhos), Segoe UI/Inter (conteúdo do flashcard).
|
||||||
- **Background**: Gradiente radial quente (dourado/azul) sobre base `#fff8e6` com overlay de grid de papel 24px.
|
- **Background**: Gradiente radial quente (dourado/azul) sobre base `#fff8e6` com overlay de grid de papel 24px.
|
||||||
- **Layout**: CSS Grid (`grid-template-columns: 288px minmax(0, 1fr)`). Sidebar sticky com textura diagonal. Topbar integrada ao fluxo (não fixa).
|
- **Layout**: CSS Grid (`grid-template-columns: 288px minmax(0, 1fr)`). Sidebar sticky com textura diagonal. Topbar integrada ao fluxo (não fixa).
|
||||||
- **Animações**: Apenas sob ação do usuário (flip 3D do flashcard, carimbo, saída do cartão, confete canvas). Sem animações infinitas ou spinners.
|
- **Animações**: Apenas sob ação do usuário (flip 3D do flashcard, carimbo, saída do cartão, confete canvas). Sem animações infinitas ou spinners.
|
||||||
- **Flashcard**: Cartão 3D com efeito `rotateY(180deg)`, frente papel pautado com borda tracejada e verso azulado focado na resposta. No verso, um clique extra revela a pergunta; enquanto essa pergunta auxiliar estiver visível, os botões "Correto" e "Incorreto" ficam bloqueados. O card não exibe status RAG nem metadados de assunto/subassunto/desempenho/última revisão. O carimbo de feedback ("Correto"/"Incorreto") é renderizado no verso visível antes da saída do cartão, e há confete canvas ao acertar.
|
- **Flashcard**: Cartão 3D com efeito `rotateY(180deg)`, frente papel pautado com borda tracejada e verso azulado. O verso exibe a resposta em cima e a pergunta original embaixo, ambos visíveis ao virar o cartão. Clicar no verso retorna à frente. Os botões "Correto" e "Incorreto" ficam desabilitados enquanto a frente está visível. O card não exibe status RAG, metadados de assunto/subassunto/desempenho/última revisão, dificuldade nem nome do arquivo de origem. O carimbo de feedback ("Correto"/"Incorreto") é renderizado no verso visível antes da saída do cartão, e há confete canvas ao acertar.
|
||||||
- **Responsivo**: Breakpoints em 1120px (sidebar colapsada) e 760px (layout single-column).
|
- **Responsivo**: Breakpoints em 1120px (sidebar colapsada) e 760px (layout single-column).
|
||||||
|
|
||||||
### Variáveis CSS (definidas em `index.css`)
|
### Variáveis CSS (definidas em `index.css`)
|
||||||
|
|||||||
Reference in New Issue
Block a user