459 lines
15 KiB
Dart
459 lines
15 KiB
Dart
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}';
|
|
}
|
|
}
|