import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; import 'package:flutter/services.dart'; import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; import '../../../../core/services/auth_service.dart'; import '../../../../core/theme/app_colors.dart'; import '../../../../core/theme/app_theme_extension.dart'; /// Página para visualizar os alunos de uma turma específica class ClassStudentsPage extends StatefulWidget { final String classId; final String className; const ClassStudentsPage({ super.key, required this.classId, required this.className, }); @override State createState() => _ClassStudentsPageState(); } class _ClassStudentsPageState extends State { bool _isCheckingAccess = true; bool _accessGranted = false; String _currentClassName = ''; String? _classCode; @override void initState() { super.initState(); _currentClassName = widget.className; _verifyOwnership(); } Future _verifyOwnership() async { final currentUser = AuthService.currentUser; if (currentUser == null) { setState(() { _isCheckingAccess = false; _accessGranted = false; }); return; } final role = await AuthService.getUserRole(currentUser.uid); if (role != 'teacher') { setState(() { _isCheckingAccess = false; _accessGranted = false; }); return; } final classDoc = await FirebaseFirestore.instance .collection('classes') .doc(widget.classId) .get(); final teacherId = classDoc.data()?['teacherId'] as String?; final code = classDoc.data()?['code'] as String?; setState(() { _isCheckingAccess = false; _accessGranted = classDoc.exists && teacherId == currentUser.uid; _classCode = code ?? '----'; }); } Future _updateClassName(String newName) async { try { await FirebaseFirestore.instance .collection('classes') .doc(widget.classId) .update({'name': newName}); setState(() { _currentClassName = newName; }); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Row( children: [ const Icon(Icons.check_circle, color: Colors.white), const SizedBox(width: 12), const Text('Nome da turma atualizado com sucesso'), ], ), backgroundColor: Colors.green, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), duration: const Duration(seconds: 3), ), ); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Row( children: [ const Icon(Icons.error_outline, color: Colors.white), const SizedBox(width: 12), Text('Erro ao atualizar nome: $e'), ], ), backgroundColor: Colors.red, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), duration: const Duration(seconds: 4), ), ); } } } Future _deleteClass() async { try { // Delete all enrollments first final enrollmentsSnapshot = await FirebaseFirestore.instance .collection('enrollments') .where('classId', isEqualTo: widget.classId) .get(); final batch = FirebaseFirestore.instance.batch(); for (final doc in enrollmentsSnapshot.docs) { batch.delete(doc.reference); } await batch.commit(); // Delete the class await FirebaseFirestore.instance .collection('classes') .doc(widget.classId) .delete(); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Row( children: [ const Icon(Icons.check_circle, color: Colors.white), const SizedBox(width: 12), const Text('Turma eliminada com sucesso'), ], ), backgroundColor: Colors.green, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), duration: const Duration(seconds: 3), ), ); context.pop(); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Row( children: [ const Icon(Icons.error_outline, color: Colors.white), const SizedBox(width: 12), Text('Erro ao eliminar turma: $e'), ], ), backgroundColor: Colors.red, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), duration: const Duration(seconds: 4), ), ); } } } void _showEditClassNameDialog() { final textController = TextEditingController(text: _currentClassName); showDialog( context: context, builder: (context) => AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), title: Row( children: [ Icon(Icons.edit, color: Theme.of(context).colorScheme.primary), const SizedBox(width: 8), const Text('Editar Nome'), ], ), content: TextField( controller: textController, decoration: InputDecoration( labelText: 'Nome da Turma', border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)), prefixIcon: const Icon(Icons.school), ), autofocus: true, maxLength: 50, ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Cancelar'), ), FilledButton( onPressed: () { final newName = textController.text.trim(); if (newName.isNotEmpty && newName != _currentClassName) { Navigator.of(context).pop(); _updateClassName(newName); } }, child: const Text('Guardar'), ), ], ), ); } void _showDeleteClassDialog() { showDialog( context: context, builder: (context) => AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), title: Row( children: [ Icon(Icons.warning, color: Theme.of(context).colorScheme.error), const SizedBox(width: 8), const Text('Eliminar Turma'), ], ), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Tens a certeza que desejas eliminar a turma "$_currentClassName"?', style: const TextStyle(fontSize: 14), ), const SizedBox(height: 12), Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Theme.of( context, ).colorScheme.error.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), border: Border.all( color: Theme.of( context, ).colorScheme.error.withValues(alpha: 0.3), ), ), child: Row( children: [ Icon( Icons.warning_amber, color: Theme.of(context).colorScheme.error, size: 20, ), const SizedBox(width: 8), Expanded( child: Text( 'Esta ação não pode ser desfeita. Todos os alunos serão removidos.', style: TextStyle( fontSize: 12, color: Theme.of(context).colorScheme.error, ), ), ), ], ), ), ], ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Cancelar'), ), FilledButton( onPressed: () { Navigator.of(context).pop(); _deleteClass(); }, style: FilledButton.styleFrom( backgroundColor: Theme.of(context).colorScheme.error, foregroundColor: Colors.white, ), child: const Text('Eliminar'), ), ], ), ); } @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; final themeExtras = AppThemeExtras.of(context); if (_isCheckingAccess) { return Scaffold( backgroundColor: cs.surface, body: Center(child: CircularProgressIndicator(color: cs.primary)), ); } if (!_accessGranted) { return Scaffold( backgroundColor: cs.surface, body: Center( child: Padding( padding: const EdgeInsets.all(32.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.lock_outline, size: 64, color: cs.primary), const SizedBox(height: 24), Text( 'Sem permissão', style: TextStyle( color: cs.onSurface, fontSize: 20, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 12), Text( 'Só podes ver os alunos das tuas próprias turmas.', style: TextStyle(color: cs.onSurfaceVariant, fontSize: 14), textAlign: TextAlign.center, ), ], ), ), ), ); } return Scaffold( body: Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: themeExtras.dashboardBackgroundGradient, stops: themeExtras.dashboardGradientStops, ), ), child: SafeArea( top: false, child: Column( children: [ // Custom AppBar _buildAppBar(cs), // Main Content Expanded( child: StreamBuilder( stream: FirebaseFirestore.instance .collection('enrollments') .where('classId', isEqualTo: widget.classId) .orderBy('joinedAt', descending: true) .snapshots(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return Center( child: CircularProgressIndicator(color: cs.primary), ); } if (snapshot.hasError) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.error_outline, size: 48, color: cs.onSurfaceVariant, ), const SizedBox(height: 16), Text( 'Erro ao carregar alunos', style: TextStyle( color: cs.onSurfaceVariant, fontSize: 16, ), ), ], ), ); } final enrollments = snapshot.data?.docs ?? []; if (enrollments.isEmpty) { return _buildEmptyState(cs); } return _buildStudentsList(cs, enrollments); }, ), ), ], ), ), ), ); } Widget _buildAppBar(ColorScheme cs) { return Container( padding: const EdgeInsets.only(left: 16, right: 16, top: 52, bottom: 16), child: Column( children: [ // Top Row with Back and Actions Row( children: [ Container( decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(12), ), child: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.white), onPressed: () => context.pop(), tooltip: 'Voltar', ), ), const Spacer(), // Edit Button Container( decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(12), ), child: IconButton( icon: const Icon(Icons.edit, color: Colors.white), onPressed: _showEditClassNameDialog, tooltip: 'Editar nome', ), ), const SizedBox(width: 8), // Delete Button Container( decoration: BoxDecoration( color: (Theme.of(context).brightness == Brightness.dark ? DarkBrandColors.primaryOrange : AppColors.primaryOrange) .withValues(alpha: 0.2), borderRadius: BorderRadius.circular(12), ), child: IconButton( icon: Icon( Icons.delete_outline, color: Theme.of(context).brightness == Brightness.dark ? DarkBrandColors.primaryOrange : AppColors.primaryOrange, ), onPressed: _showDeleteClassDialog, tooltip: 'Eliminar turma', ), ), ], ), const SizedBox(height: 20), // Class Info Card Container( width: double.infinity, padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(20), border: Border.all( color: Colors.white.withValues(alpha: 0.2), width: 1, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(12), ), child: const Icon( Icons.school, color: Colors.white, size: 28, ), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( _currentClassName, style: const TextStyle( color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 4), Text( 'Código: $_classCode', style: TextStyle( color: Colors.white.withValues(alpha: 0.8), fontSize: 14, ), ), ], ), ), ], ), const SizedBox(height: 16), // Stats Row Container( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 12, ), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(12), ), child: StreamBuilder( stream: FirebaseFirestore.instance .collection('enrollments') .where('classId', isEqualTo: widget.classId) .snapshots(), builder: (context, snapshot) { final count = snapshot.data?.docs.length ?? 0; return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon( Icons.people, color: Colors.white, size: 20, ), const SizedBox(width: 8), Text( '$count ${count == 1 ? 'aluno matriculado' : 'alunos matriculados'}', style: const TextStyle( color: Colors.white, fontSize: 14, fontWeight: FontWeight.w500, ), ), ], ); }, ), ), ], ), ).animate().fadeIn( duration: const Duration(milliseconds: 400), curve: Curves.easeOut, ), ], ), ); } Widget _buildEmptyState(ColorScheme cs) { return SingleChildScrollView( padding: const EdgeInsets.all(24), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const SizedBox(height: 40), Container( padding: const EdgeInsets.all(24), decoration: BoxDecoration( color: cs.surface, borderRadius: BorderRadius.circular(24), boxShadow: [ BoxShadow( color: cs.shadow.withValues(alpha: 0.08), blurRadius: 20, offset: const Offset(0, 8), ), ], ), child: Column( children: [ Icon( Icons.people_outline, size: 80, color: cs.primary.withValues(alpha: 0.5), ), const SizedBox(height: 24), Text( 'Nenhum aluno entrou nesta turma ainda.', style: TextStyle( color: cs.onSurface, fontSize: 18, fontWeight: FontWeight.w600, ), textAlign: TextAlign.center, ), const SizedBox(height: 12), Text( 'Partilha o código da turma para os alunos se juntarem.', style: TextStyle( color: cs.onSurfaceVariant, fontSize: 14, ), textAlign: TextAlign.center, ), const SizedBox(height: 24), GestureDetector( onTap: _copyCodeToClipboard, child: Container( padding: const EdgeInsets.symmetric( horizontal: 20, vertical: 12, ), decoration: BoxDecoration( color: cs.primary.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(12), border: Border.all( color: cs.primary.withValues(alpha: 0.3), ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.copy, color: cs.primary, size: 18), const SizedBox(width: 8), Text( 'Código: $_classCode', style: TextStyle( color: cs.primary, fontSize: 16, fontWeight: FontWeight.bold, ), ), ], ), ), ), ], ), ) .animate() .fadeIn( duration: const Duration(milliseconds: 400), curve: Curves.easeOut, ) .then(delay: const Duration(milliseconds: 100)), ], ), ); } Widget _buildStudentsList( ColorScheme cs, List enrollments, ) { return SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Section Title Container( margin: const EdgeInsets.only(bottom: 16, left: 8), child: Row( children: [ Icon(Icons.people, color: cs.onSurface, size: 20), const SizedBox(width: 8), Text( 'Alunos Matriculados', style: TextStyle( color: cs.onSurface, fontSize: 18, fontWeight: FontWeight.bold, ), ), const SizedBox(width: 8), Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4, ), decoration: BoxDecoration( color: cs.primary.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(12), ), child: Text( '${enrollments.length}', style: TextStyle( color: cs.primary, fontSize: 12, fontWeight: FontWeight.bold, ), ), ), ], ), ), // Students List ...enrollments.asMap().entries.map((entry) { final index = entry.key; final enrollment = entry.value.data() as Map; final studentName = enrollment['studentName'] as String? ?? 'Aluno sem nome'; final joinedAt = enrollment['joinedAt'] as Timestamp?; final enrollmentId = entry.value.id; return _buildStudentCard( cs, studentName, joinedAt, enrollmentId, index, ); }), const SizedBox(height: 24), ], ), ); } Widget _buildStudentCard( ColorScheme cs, String studentName, Timestamp? joinedAt, String enrollmentId, int index, ) { return Container( margin: const EdgeInsets.only(bottom: 12), decoration: BoxDecoration( color: cs.surface, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: cs.shadow.withValues(alpha: 0.06), blurRadius: 12, offset: const Offset(0, 4), ), ], ), child: Material( color: Colors.transparent, borderRadius: BorderRadius.circular(16), child: InkWell( borderRadius: BorderRadius.circular(16), onTap: null, child: Padding( padding: const EdgeInsets.all(16), child: Row( children: [ // Avatar Container( width: 52, height: 52, decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ cs.primary.withValues(alpha: 0.8), cs.primary.withValues(alpha: 0.4), ], ), borderRadius: BorderRadius.circular(14), ), child: Center( child: Text( studentName.isNotEmpty ? studentName[0].toUpperCase() : '?', style: const TextStyle( color: Colors.white, fontSize: 22, fontWeight: FontWeight.bold, ), ), ), ), const SizedBox(width: 16), // Info Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( studentName, style: TextStyle( color: cs.onSurface, fontSize: 16, fontWeight: FontWeight.w600, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 4), Text( joinedAt != null ? 'Entrou em ${_formatDate(joinedAt.toDate())}' : 'Data desconhecida', style: TextStyle( color: cs.onSurfaceVariant, fontSize: 13, ), ), ], ), ), // Delete Button Container( decoration: BoxDecoration( color: cs.error.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(10), ), child: IconButton( icon: Icon( Icons.delete_outline, color: cs.error, size: 20, ), onPressed: () => _showRemoveStudentDialog( context, enrollmentId, studentName, ), tooltip: 'Remover aluno', padding: const EdgeInsets.all(10), constraints: const BoxConstraints(), ), ), ], ), ), ), ), ) .animate() .fadeIn( duration: const Duration(milliseconds: 300), curve: Curves.easeOut, ) .then(delay: Duration(milliseconds: index * 50)); } String _formatDate(DateTime date) { return DateFormat('dd/MM/yyyy').format(date); } void _copyCodeToClipboard() { Clipboard.setData(ClipboardData(text: _classCode ?? '')); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Row( children: [ const Icon(Icons.check_circle, color: Colors.white), const SizedBox(width: 12), const Text('Código copiado!'), ], ), backgroundColor: Colors.green, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), duration: const Duration(seconds: 2), ), ); } Future _showRemoveStudentDialog( BuildContext context, String enrollmentId, String studentName, ) async { final confirmed = await showDialog( context: context, builder: (context) => AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), title: Row( children: [ Icon( Icons.person_remove, color: Theme.of(context).colorScheme.error, ), const SizedBox(width: 8), const Text('Remover Aluno'), ], ), content: Text( 'Tens a certeza que desejas remover $studentName desta turma?', style: const TextStyle(fontSize: 14), ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), child: const Text('Cancelar'), ), FilledButton( onPressed: () => Navigator.of(context).pop(true), style: FilledButton.styleFrom( backgroundColor: Theme.of(context).colorScheme.error, foregroundColor: Colors.white, ), child: const Text('Remover'), ), ], ), ); if (confirmed == true && context.mounted) { try { await FirebaseFirestore.instance .collection('enrollments') .doc(enrollmentId) .delete(); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Row( children: [ const Icon(Icons.check_circle, color: Colors.white), const SizedBox(width: 12), const Text('Aluno removido com sucesso'), ], ), backgroundColor: Colors.green, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), duration: const Duration(seconds: 3), ), ); } } catch (e) { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Row( children: [ const Icon(Icons.error_outline, color: Colors.white), const SizedBox(width: 12), Text('Erro ao remover aluno: $e'), ], ), backgroundColor: Colors.red, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), duration: const Duration(seconds: 4), ), ); } } } } }