placeholders removidos e todos os dados reais colocados, com conquistas e tudo
This commit is contained in:
@@ -6,6 +6,7 @@ import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import '../../../../core/services/materials_rag_service.dart';
|
||||
import '../../../../core/services/rag_ai_service.dart';
|
||||
import '../../../../core/services/gamification_service.dart';
|
||||
import '../../../../core/utils/logger.dart';
|
||||
|
||||
class QuizListPage extends StatefulWidget {
|
||||
@@ -423,6 +424,7 @@ class _QuizListPageState extends State<QuizListPage>
|
||||
title: name,
|
||||
quizId: quizId,
|
||||
questions: questions,
|
||||
materialName: name,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -1162,10 +1164,12 @@ class _TeacherQuizInteractiveSheet extends StatefulWidget {
|
||||
final String title;
|
||||
final String quizId;
|
||||
final List<_QuizQuestion> questions;
|
||||
final String? materialName;
|
||||
const _TeacherQuizInteractiveSheet({
|
||||
required this.title,
|
||||
required this.quizId,
|
||||
required this.questions,
|
||||
this.materialName,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -1229,6 +1233,14 @@ class _TeacherQuizInteractiveSheetState
|
||||
'total': widget.questions.length,
|
||||
'submittedAt': FieldValue.serverTimestamp(),
|
||||
});
|
||||
|
||||
// Registrar atividade no sistema de gamificação
|
||||
await GamificationService.recordQuizActivity(
|
||||
user.uid,
|
||||
score: _score,
|
||||
totalQuestions: widget.questions.length,
|
||||
materialName: widget.materialName ?? 'Quiz',
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
Logger.error('Error submitting teacher quiz result: $e');
|
||||
|
||||
458
lib/features/quiz/presentation/pages/quiz_management_page.dart
Normal file
458
lib/features/quiz/presentation/pages/quiz_management_page.dart
Normal file
@@ -0,0 +1,458 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../../../core/services/auth_service.dart';
|
||||
import '../../../../core/utils/logger.dart';
|
||||
|
||||
/// Página para gerenciar quizzes (eliminar quizzes feitos)
|
||||
class QuizManagementPage extends StatefulWidget {
|
||||
const QuizManagementPage({super.key});
|
||||
|
||||
@override
|
||||
State<QuizManagementPage> createState() => _QuizManagementPageState();
|
||||
}
|
||||
|
||||
class _QuizManagementPageState extends State<QuizManagementPage> {
|
||||
List<Map<String, dynamic>> _quizHistory = [];
|
||||
bool _loading = true;
|
||||
String _userRole = '';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadUserRole();
|
||||
_loadQuizHistory();
|
||||
}
|
||||
|
||||
Future<void> _loadUserRole() async {
|
||||
final user = AuthService.currentUser;
|
||||
if (user != null) {
|
||||
final userDoc = await FirebaseFirestore.instance
|
||||
.collection('users')
|
||||
.doc(user.uid)
|
||||
.get();
|
||||
|
||||
if (userDoc.exists) {
|
||||
setState(() {
|
||||
_userRole = userDoc.data()?['role'] ?? '';
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadQuizHistory() async {
|
||||
try {
|
||||
final user = AuthService.currentUser;
|
||||
if (user == null) return;
|
||||
|
||||
Query query;
|
||||
|
||||
if (_userRole == 'teacher') {
|
||||
// Professor: ver todos os quizzes criados
|
||||
query = FirebaseFirestore.instance
|
||||
.collection('teacherQuizzes')
|
||||
.where('teacherId', isEqualTo: user.uid)
|
||||
.orderBy('createdAt', descending: true);
|
||||
} else {
|
||||
// Aluno: ver quizzes criados pelo próprio aluno + conceitos dominados
|
||||
final results = await Future.wait([
|
||||
FirebaseFirestore.instance.collection('userStats').doc(user.uid).get(),
|
||||
FirebaseFirestore.instance
|
||||
.collection('quizHistory')
|
||||
.doc(user.uid)
|
||||
.collection('quizzes')
|
||||
.orderBy('createdAt', descending: true)
|
||||
.get(),
|
||||
]);
|
||||
|
||||
final userStatsSnapshot = results[0] as DocumentSnapshot;
|
||||
final studentQuizzesSnapshot = results[1] as QuerySnapshot;
|
||||
|
||||
List<Map<String, dynamic>> quizList = [];
|
||||
|
||||
// Adicionar quizzes criados pelo aluno
|
||||
for (final doc in studentQuizzesSnapshot.docs) {
|
||||
final data = Map<String, dynamic>.from(doc.data() as Map);
|
||||
data['id'] = doc.id;
|
||||
data['title'] = data['materialName'] ?? 'Quiz sem nome';
|
||||
data['type'] = 'created';
|
||||
quizList.add(data);
|
||||
}
|
||||
|
||||
// Adicionar conceitos dominados
|
||||
if (userStatsSnapshot.exists) {
|
||||
final stats = userStatsSnapshot.data() as Map<String, dynamic>?;
|
||||
if (stats != null) {
|
||||
final masteredConcepts = (stats['masteredConcepts'] as List<dynamic>?)
|
||||
?.map((c) => Map<String, dynamic>.from(c as Map))
|
||||
.toList() ?? [];
|
||||
|
||||
for (final concept in masteredConcepts) {
|
||||
quizList.add({
|
||||
'id': concept['conceptName'] ?? '',
|
||||
'title': concept['conceptName'] ?? 'Conceito sem nome',
|
||||
'score': concept['masteryLevel'] ?? 0,
|
||||
'totalQuestions': 100, // Simulado
|
||||
'completedAt': concept['masteredAt'] ?? Timestamp.now(),
|
||||
'type': 'concept',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_quizHistory = quizList;
|
||||
_loading = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
final snapshot = await query.get();
|
||||
|
||||
setState(() {
|
||||
_quizHistory = snapshot.docs.map((doc) {
|
||||
final data = Map<String, dynamic>.from(doc.data() as Map);
|
||||
data['id'] = doc.id;
|
||||
return data;
|
||||
}).cast<Map<String, dynamic>>().toList();
|
||||
_loading = false;
|
||||
});
|
||||
} catch (e) {
|
||||
Logger.error('Error loading quiz history: $e');
|
||||
setState(() {
|
||||
_loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _deleteQuiz(String quizId, String quizTitle, String type) async {
|
||||
try {
|
||||
String confirmMessage;
|
||||
String successMessage;
|
||||
|
||||
if (_userRole == 'teacher') {
|
||||
confirmMessage = 'Tem certeza que deseja eliminar o quiz "$quizTitle"? Esta ação não pode ser desfeita.';
|
||||
successMessage = 'Quiz eliminado com sucesso!';
|
||||
} else {
|
||||
if (type == 'created') {
|
||||
confirmMessage = 'Tem certeza que deseja eliminar seu quiz "$quizTitle"?';
|
||||
successMessage = 'Quiz eliminado com sucesso!';
|
||||
} else {
|
||||
confirmMessage = 'Tem certeza que deseja remover o conceito "$quizTitle" do seu histórico?';
|
||||
successMessage = 'Conceito removido com sucesso!';
|
||||
}
|
||||
}
|
||||
|
||||
final confirmed = await _showDeleteConfirmation(quizTitle, confirmMessage);
|
||||
if (!confirmed) return;
|
||||
|
||||
if (_userRole == 'teacher') {
|
||||
// Professor: eliminar quiz criado
|
||||
await FirebaseFirestore.instance
|
||||
.collection('teacherQuizzes')
|
||||
.doc(quizId)
|
||||
.delete();
|
||||
|
||||
// Também eliminar do histórico de alunos
|
||||
final historySnapshot = await FirebaseFirestore.instance
|
||||
.collection('quizHistory')
|
||||
.where('quizId', isEqualTo: quizId)
|
||||
.get();
|
||||
|
||||
for (final doc in historySnapshot.docs) {
|
||||
await doc.reference.delete();
|
||||
}
|
||||
} else {
|
||||
// Aluno: remover quiz criado ou conceito dominado
|
||||
if (type == 'created') {
|
||||
// Remover quiz criado pelo aluno
|
||||
final user = AuthService.currentUser;
|
||||
if (user != null) {
|
||||
await FirebaseFirestore.instance
|
||||
.collection('quizHistory')
|
||||
.doc(user.uid)
|
||||
.collection('quizzes')
|
||||
.doc(quizId)
|
||||
.delete();
|
||||
}
|
||||
} else {
|
||||
// Remover conceito dominado
|
||||
final user = AuthService.currentUser;
|
||||
if (user != null) {
|
||||
final userStatsDoc = await FirebaseFirestore.instance
|
||||
.collection('userStats')
|
||||
.doc(user.uid)
|
||||
.get();
|
||||
|
||||
if (userStatsDoc.exists) {
|
||||
final userStats = userStatsDoc.data() as Map<String, dynamic>;
|
||||
final masteredConcepts = (userStats['masteredConcepts'] as List<dynamic>?)
|
||||
?.map((c) => Map<String, dynamic>.from(c as Map))
|
||||
.toList() ?? [];
|
||||
|
||||
// Encontrar o conceito específico para remover
|
||||
final conceptToRemove = masteredConcepts.firstWhere(
|
||||
(c) => c['conceptName'] == quizId,
|
||||
orElse: () => {},
|
||||
);
|
||||
|
||||
if (conceptToRemove.isNotEmpty) {
|
||||
await FirebaseFirestore.instance
|
||||
.collection('userStats')
|
||||
.doc(user.uid)
|
||||
.update({
|
||||
'masteredConcepts': FieldValue.arrayRemove([conceptToRemove])
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_loadQuizHistory(); // Recarregar lista
|
||||
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(successMessage),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
Logger.error('Error deleting quiz: $e');
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Erro ao eliminar: $e'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _showDeleteConfirmation(String quizTitle, String message) async {
|
||||
final result = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(_userRole == 'teacher' ? 'Eliminar Quiz' : 'Confirmar Eliminação'),
|
||||
content: Text(message),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
child: const Text('Cancelar'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
|
||||
child: const Text('Eliminar'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
return result ?? false;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: cs.surface,
|
||||
appBar: AppBar(
|
||||
title: Text(_userRole == 'teacher' ? 'Gerenciar Quizzes' : 'Meu Histórico'),
|
||||
backgroundColor: cs.surface,
|
||||
foregroundColor: cs.onSurface,
|
||||
elevation: 0,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () => context.go(_userRole == 'teacher'
|
||||
? '/teacher-dashboard'
|
||||
: '/student-dashboard'),
|
||||
),
|
||||
),
|
||||
body: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
cs.primary.withValues(alpha: 0.05),
|
||||
cs.surface,
|
||||
],
|
||||
),
|
||||
),
|
||||
child: _loading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: _quizHistory.isEmpty
|
||||
? _buildEmptyState()
|
||||
: ListView.builder(
|
||||
padding: const EdgeInsets.all(16),
|
||||
itemCount: _quizHistory.length,
|
||||
itemBuilder: (context, index) {
|
||||
final quiz = _quizHistory[index];
|
||||
return _buildQuizCard(quiz)
|
||||
.animate()
|
||||
.slideX(duration: const Duration(milliseconds: 300))
|
||||
.then(delay: Duration(milliseconds: index * 50));
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEmptyState() {
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
_userRole == 'teacher' ? Icons.quiz_outlined : Icons.history_edu_outlined,
|
||||
size: 64,
|
||||
color: cs.onSurfaceVariant,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
_userRole == 'teacher' ? 'Nenhum quiz criado' : 'Nenhum quiz no histórico',
|
||||
style: TextStyle(
|
||||
color: cs.onSurface,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
_userRole == 'teacher'
|
||||
? 'Crie seu primeiro quiz para começar!'
|
||||
: 'Complete alguns quizzes para ver seu histórico aqui.',
|
||||
style: TextStyle(
|
||||
color: cs.onSurfaceVariant,
|
||||
fontSize: 14,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildQuizCard(Map<String, dynamic> quiz) {
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: cs.surface,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: cs.outline.withValues(alpha: 0.2)),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: cs.shadow.withValues(alpha: 0.1),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
quiz['title'] ?? 'Quiz sem título',
|
||||
style: TextStyle(
|
||||
color: cs.onSurface,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
if (_userRole == 'student' && quiz['score'] != null) ...[
|
||||
Text(
|
||||
'Pontuação: ${quiz['score']}/${quiz['totalQuestions'] ?? '?'}',
|
||||
style: TextStyle(
|
||||
color: cs.primary,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
Text(
|
||||
_userRole == 'teacher' || quiz['type'] == 'created'
|
||||
? 'Criado em: ${_formatDate(quiz['createdAt'])}'
|
||||
: 'Completado em: ${_formatDate(quiz['completedAt'])}',
|
||||
style: TextStyle(
|
||||
color: cs.onSurfaceVariant,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => _deleteQuiz(quiz['id'], quiz['title'] ?? 'Quiz', quiz['type'] ?? 'unknown'),
|
||||
icon: Icon(
|
||||
Icons.delete_outline,
|
||||
color: Colors.red,
|
||||
),
|
||||
tooltip: _userRole == 'teacher' ? 'Eliminar Quiz' : 'Remover',
|
||||
),
|
||||
],
|
||||
),
|
||||
if (_userRole == 'teacher' && quiz['classIds'] != null) ...[
|
||||
const SizedBox(height: 8),
|
||||
Wrap(
|
||||
spacing: 4,
|
||||
children: (quiz['classIds'] as List<dynamic>).map((classId) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: cs.primaryContainer,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
'Turma: $classId',
|
||||
style: TextStyle(
|
||||
color: cs.onPrimaryContainer,
|
||||
fontSize: 10,
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _formatDate(dynamic date) {
|
||||
if (date == null) return 'Data desconhecida';
|
||||
|
||||
DateTime dateTime;
|
||||
if (date is Timestamp) {
|
||||
dateTime = date.toDate();
|
||||
} else if (date is DateTime) {
|
||||
dateTime = date;
|
||||
} else {
|
||||
return 'Data desconhecida';
|
||||
}
|
||||
|
||||
return '${dateTime.day}/${dateTime.month}/${dateTime.year}';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user