Skip to content

Commit fd208dc

Browse files
committed
melhora a navegação
1 parent 6a90d3b commit fd208dc

File tree

16 files changed

+528
-128
lines changed

16 files changed

+528
-128
lines changed

frontend/package-lock.json

Lines changed: 24 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
"next": "16.0.3",
1313
"react": "19.2.0",
1414
"react-dom": "19.2.0",
15-
"react-icons": "^5.5.0"
15+
"react-icons": "^5.5.0",
16+
"react-toastify": "^11.0.5"
1617
},
1718
"devDependencies": {
1819
"@tailwindcss/postcss": "^4.1.17",

frontend/src/app/(home)/create/page.tsx

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { useQuiz } from "@/hook/useQuiz";
1111
import SetQuiz from "./theme/set";
1212
import { AutomaticModeData } from "./questions/automatico/automatico";
1313
import { QuizPergunta } from "@/util/types/quiz";
14+
import { toast } from "react-toastify";
1415

1516
interface ThemeData {
1617
id?: string;
@@ -48,6 +49,7 @@ export default function CreateQuiz() {
4849
const [automaticData, setAutomaticData] =
4950
useState<AutomaticModeData | null>(null);
5051
const [saving, setSaving] = useState(false);
52+
const [questionModeSelected, setQuestionModeSelected] = useState(false);
5153

5254
// New state for duplicate prevention
5355
const [generatedQuestions, setGeneratedQuestions] = useState<
@@ -63,12 +65,12 @@ export default function CreateQuiz() {
6365

6466
const handleNext = () => {
6567
if (!themeData?.title) {
66-
alert("Preencha o tema!");
68+
toast.warning("Preencha o tema!");
6769
return;
6870
}
6971

7072
if (!subtopicData?.subTopic) {
71-
alert("Preencha o subtópico!");
73+
toast.warning("Preencha o subtópico!");
7274
return;
7375
}
7476

@@ -98,24 +100,24 @@ export default function CreateQuiz() {
98100
// Validação
99101
if (isAutomatic) {
100102
if (!automaticData) {
101-
alert("Preencha os dados para geração automática!");
103+
toast.warning("Preencha os dados para geração automática!");
102104
return;
103105
}
104106
if (automaticData.mode === "text" && !automaticData.text) {
105-
alert("Preencha o texto para geração!");
107+
toast.warning("Preencha o texto para geração!");
106108
return;
107109
}
108110
if (
109111
(automaticData.mode === "audio" ||
110112
automaticData.mode === "document") &&
111113
!automaticData.file
112114
) {
113-
alert("Selecione um arquivo para geração!");
115+
toast.warning("Selecione um arquivo para geração!");
114116
return;
115117
}
116118
} else {
117119
if (questionsData.length === 0) {
118-
alert("Adicione pelo menos uma questão!");
120+
toast.warning("Adicione pelo menos uma questão!");
119121
return;
120122
}
121123
}
@@ -212,11 +214,11 @@ export default function CreateQuiz() {
212214
}
213215
}
214216

215-
alert("Quiz criado com sucesso!");
217+
toast.success("Quiz criado com sucesso!");
216218
window.location.href = "/quiz-lab/home";
217219
} catch (err) {
218220
console.error("Erro ao criar quiz:", err);
219-
alert("Erro ao criar quiz. Tente novamente.");
221+
toast.error("Erro ao criar quiz. Tente novamente.");
220222
} finally {
221223
setSaving(false);
222224
}
@@ -236,9 +238,11 @@ export default function CreateQuiz() {
236238
<Questions
237239
onQuestionsChange={setQuestionsData}
238240
onAutomaticDataChange={setAutomaticData}
239-
onModeChange={(mode) =>
240-
setIsAutomatic(mode === "automatic")
241-
}
241+
onModeChange={(mode) => {
242+
setIsAutomatic(mode === "automatic");
243+
setQuestionModeSelected(true);
244+
}}
245+
onModeReset={() => setQuestionModeSelected(false)}
242246
questions={questionsData}
243247
themeId={themeData?.id || ""}
244248
subTopicId={subtopicData?.id || ""}
@@ -247,7 +251,7 @@ export default function CreateQuiz() {
247251
)}
248252

249253
<nav className="flex justify-between mt-10">
250-
{currentStep === "questions" && (
254+
{currentStep === "questions" && questionModeSelected && (
251255
<Button
252256
variant="subtle"
253257
onClick={handleBack}
@@ -263,7 +267,7 @@ export default function CreateQuiz() {
263267
</Button>
264268
)}
265269

266-
{currentStep === "questions" && (
270+
{currentStep === "questions" && questionModeSelected && (
267271
<Button
268272
onClick={handleFinish}
269273
disabled={saving}

frontend/src/app/(home)/create/questions/automatico/automatico.tsx

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"use client";
22

3+
import { useState, useEffect, useRef } from "react";
34
import { Texto } from "./texto";
45
import { Documento } from "./documento";
56
import { Audio } from "./audio";
@@ -22,6 +23,8 @@ interface AutomaticQuestionsProps {
2223
}
2324

2425
export default function AutomaticQuestions({ mode, onModeSelect, onDataChange, onSubmit }: AutomaticQuestionsProps) {
26+
const [focusedOptionIndex, setFocusedOptionIndex] = useState<number>(0);
27+
const buttonRefs = useRef<(HTMLButtonElement | null)[]>([]);
2528

2629
const handleDataChange = (data: { text?: string; file?: File; num_questions: number; num_alternatives: number } | null) => {
2730
if (data && mode) {
@@ -34,6 +37,39 @@ export default function AutomaticQuestions({ mode, onModeSelect, onDataChange, o
3437
}
3538
};
3639

40+
// Keyboard navigation for mode selection
41+
useEffect(() => {
42+
if (mode !== null) return; // Only active in selection mode
43+
44+
const handleKeyDown = (e: KeyboardEvent) => {
45+
const totalOptions = 3; // text, document, audio
46+
47+
if (e.key === "ArrowDown" || e.key === "ArrowRight") {
48+
e.preventDefault();
49+
setFocusedOptionIndex((prev) => (prev + 1) % totalOptions);
50+
} else if (e.key === "ArrowUp" || e.key === "ArrowLeft") {
51+
e.preventDefault();
52+
setFocusedOptionIndex((prev) => (prev - 1 + totalOptions) % totalOptions);
53+
} else if (e.key === "Enter") {
54+
e.preventDefault();
55+
// Select the focused option
56+
const modes: GenerationMode[] = ["text", "document", "audio"];
57+
const selectedMode = modes[focusedOptionIndex];
58+
onModeSelect(selectedMode);
59+
}
60+
};
61+
62+
document.addEventListener("keydown", handleKeyDown);
63+
return () => document.removeEventListener("keydown", handleKeyDown);
64+
}, [mode, focusedOptionIndex, onModeSelect]);
65+
66+
// Focus the button when index changes
67+
useEffect(() => {
68+
if (mode === null && buttonRefs.current[focusedOptionIndex]) {
69+
buttonRefs.current[focusedOptionIndex]?.focus();
70+
}
71+
}, [focusedOptionIndex, mode]);
72+
3773
if (mode === "text") {
3874
return (
3975
<Texto
@@ -75,8 +111,11 @@ export default function AutomaticQuestions({ mode, onModeSelect, onDataChange, o
75111
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
76112
{/* Opção: Texto */}
77113
<button
114+
ref={(el) => { buttonRefs.current[0] = el; }}
78115
onClick={() => onModeSelect("text")}
79-
className="group bg-layout-card border-2 rounded-xl p-6 hover:border-blue-500 hover:shadow-lg transition-all duration-200 text-left"
116+
className={`group bg-layout-card border-2 rounded-xl p-6 hover:border-blue-500 hover:shadow-lg transition-all duration-200 text-left ${
117+
focusedOptionIndex === 0 && mode === null ? "ring-2 ring-blue-500 border-blue-500" : ""
118+
}`}
80119
>
81120
<div className="w-14 h-14 bg-blue-100 rounded-lg flex items-center justify-center mb-4 group-hover:bg-blue-500 transition-colors">
82121
<svg
@@ -102,8 +141,11 @@ export default function AutomaticQuestions({ mode, onModeSelect, onDataChange, o
102141

103142
{/* Opção: Documento */}
104143
<button
144+
ref={(el) => { buttonRefs.current[1] = el; }}
105145
onClick={() => onModeSelect("document")}
106-
className="group bg-layout-card border-2 rounded-xl p-6 hover:border-green-500 hover:shadow-lg transition-all duration-200 text-left"
146+
className={`group bg-layout-card border-2 rounded-xl p-6 hover:border-green-500 hover:shadow-lg transition-all duration-200 text-left ${
147+
focusedOptionIndex === 1 && mode === null ? "ring-2 ring-green-500 border-green-500" : ""
148+
}`}
107149
>
108150
<div className="w-14 h-14 bg-green-100 rounded-lg flex items-center justify-center mb-4 group-hover:bg-green-500 transition-colors">
109151
<svg
@@ -122,14 +164,17 @@ export default function AutomaticQuestions({ mode, onModeSelect, onDataChange, o
122164
</div>
123165
<h3 className="text-xl font-semibold mb-2">Documento</h3>
124166
<p className="text-sm ">
125-
Faça upload de PDF, DOCX ou TXT para gerar questões
167+
Faça upload de PDF, DOCX, XLSX, PPTX ou TXT para gerar questões
126168
</p>
127169
</button>
128170

129171
{/* Opção: Áudio */}
130172
<button
173+
ref={(el) => { buttonRefs.current[2] = el; }}
131174
onClick={() => onModeSelect("audio")}
132-
className="group bg-layout-card border-2 rounded-xl p-6 hover:border-purple-500 hover:shadow-lg transition-all duration-200 text-left"
175+
className={`group bg-layout-card border-2 rounded-xl p-6 hover:border-purple-500 hover:shadow-lg transition-all duration-200 text-left ${
176+
focusedOptionIndex === 2 && mode === null ? "ring-2 ring-purple-500 border-purple-500" : ""
177+
}`}
133178
>
134179
<div className="w-14 h-14 bg-purple-100 rounded-lg flex items-center justify-center mb-4 group-hover:bg-purple-500 transition-colors">
135180
<svg

frontend/src/app/(home)/create/questions/automatico/documento.tsx

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,17 @@ export function Documento({ onDataChange, onSubmit }: DocumentoProps) {
3232
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
3333
const file = e.target.files?.[0];
3434
if (file) {
35-
const validTypes = ['application/pdf', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'text/plain'];
36-
const validExtensions = /\.(pdf|docx|txt)$/i;
35+
const validTypes = [
36+
'application/pdf',
37+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
38+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
39+
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
40+
'text/plain'
41+
];
42+
const validExtensions = /\.(pdf|docx|xlsx|pptx|txt)$/i;
3743

3844
if (!validTypes.includes(file.type) && !file.name.match(validExtensions)) {
39-
alert("Por favor, selecione um arquivo válido (PDF, DOCX ou TXT).");
45+
alert("Por favor, selecione um arquivo válido (PDF, DOCX, XLSX, PPTX ou TXT).");
4046
return;
4147
}
4248
setDocumentFile(file);
@@ -76,6 +82,18 @@ export function Documento({ onDataChange, onSubmit }: DocumentoProps) {
7682
<path fillRule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4z" clipRule="evenodd" />
7783
</svg>
7884
);
85+
} else if (extension === 'xlsx') {
86+
return (
87+
<svg className="w-12 h-12 text-green-600 mb-3" fill="currentColor" viewBox="0 0 20 20">
88+
<path fillRule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4z" clipRule="evenodd" />
89+
</svg>
90+
);
91+
} else if (extension === 'pptx') {
92+
return (
93+
<svg className="w-12 h-12 text-orange-500 mb-3" fill="currentColor" viewBox="0 0 20 20">
94+
<path fillRule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4z" clipRule="evenodd" />
95+
</svg>
96+
);
7997
} else {
8098
return (
8199
<svg className="w-12 h-12 text-gray-500 mb-3" fill="currentColor" viewBox="0 0 20 20">
@@ -90,7 +108,7 @@ export function Documento({ onDataChange, onSubmit }: DocumentoProps) {
90108
<div className="mb-6">
91109
<h3 className="text-xl font-bold mb-2">Arquivo de Documento</h3>
92110
<p className="">
93-
Envie um arquivo PDF, DOCX ou TXT que será usado como base para gerar as questões automaticamente
111+
Envie um arquivo PDF, DOCX, XLSX, PPTX ou TXT que será usado como base para gerar as questões automaticamente
94112
</p>
95113
</div>
96114

@@ -139,7 +157,7 @@ export function Documento({ onDataChange, onSubmit }: DocumentoProps) {
139157
<input
140158
id="documentInput"
141159
type="file"
142-
accept=".pdf,.docx,.txt,application/pdf,application/vnd.openxmlformats-officedocument.wordprocessingml.document,text/plain"
160+
accept=".pdf,.docx,.xlsx,.pptx,.txt,application/pdf,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.openxmlformats-officedocument.presentationml.presentation,text/plain"
143161
onChange={handleFileChange}
144162
className="hidden"
145163
/>
@@ -162,7 +180,7 @@ export function Documento({ onDataChange, onSubmit }: DocumentoProps) {
162180
Clique para selecionar um documento
163181
</p>
164182
<p className="text-xs text-gray-500 mt-1">
165-
PDF, DOCX ou TXT
183+
PDF, DOCX, XLSX, PPTX ou TXT
166184
</p>
167185
</>
168186
)}

0 commit comments

Comments
 (0)