tela de verificação de turma, possibilidade de alunos entrarem em turmas

This commit is contained in:
2026-05-12 18:59:22 +01:00
parent b7988eb608
commit ad400a9c37
8 changed files with 905 additions and 51 deletions

View File

@@ -7,6 +7,43 @@
## [Unreleased]
### Added
- **Student Classes List (ETAPA 5)** - Students can now view their enrolled classes on the home page
- New `StudentClassesListWidget` at `/lib/features/dashboard/presentation/widgets/student_classes_list_widget.dart`
- Query: `.collection('enrollments').where('studentId', isEqualTo: currentUser.uid).orderBy('joinedAt', descending: true)`
- For each enrollment, fetches corresponding class document from `classes` collection using `classId`
- Layout: Same horizontal 2-row scroll pattern as `TeacherClassesListWidget`
- Cards display: Class name + Class code
- Loading state: `CircularProgressIndicator` (centered in card while loading class data)
- Empty state: Text "Ainda não entraste em nenhuma turma."
- Widget inserted in `StudentDashboardPage` after QuickAccessWidget
- Visual design: White cards (#FFFFFF), teal icon background (#82C9BD with 10% opacity), rounded corners (16px), subtle shadows
- Title: "As Minhas Turmas" (same style as teacher dashboard)
- System now bidirectional: Teachers see students, Students see classes
- **Join Class Feature (ETAPA 4)** - Students can now join classes using class codes
- New `JoinClassPage` screen at `/lib/features/classes/presentation/pages/join_class_page.dart`
- TextField for entering 6-character class code (uppercase, centered, letter-spacing)
- "Entrar na Turma" button with loading state and visual feedback
- Firestore query: `.collection('classes').where('code', isEqualTo: enteredCode).limit(1)`
- Validation: checks if code exists, if student already enrolled
- On success: creates document in `enrollments` collection with `classId`, `studentId`, `studentName`, `joinedAt`
- Success feedback: green SnackBar "Entraste na turma com sucesso!"
- Error feedback: red SnackBar for invalid code, duplicate enrollment, or auth errors
- Auto-returns to student home after successful join
- Visual design: teal AppBar (#82C9BD), centered icon, clean input field with rounded corners
- New "Entrar numa Turma" card in Student Dashboard Quick Access section
- Card design: horizontal layout with `Icons.group_add`, white background, rounded corners
- **Class Students View (ETAPA 3)** - Teachers can now view enrolled students in each class
- New `ClassStudentsPage` screen at `/lib/features/classes/presentation/pages/class_students_page.dart`
- StreamBuilder query on `enrollments` collection with filter by `classId`
- ListTile layout showing student name and join date
- Loading state with `CircularProgressIndicator`
- Empty state message when no students enrolled
- Date formatting using `intl` package (dd/MM/yyyy format)
- Consistent styling with existing app design (teal colors, rounded cards)
- Navigation via `MaterialPageRoute` from class card tap
- **Class Creation Feature (ETAPA 1)** - Teachers can now create classes from the dashboard
- New "Criar Turma" button in Teacher Dashboard Quick Actions
- Simple dialog interface for entering class name

View File

@@ -404,6 +404,74 @@ This document tracks the overall progress of the AI Study Assistant project deve
### **Last 24 Hours:**
-**ETAPA 5: Student Classes List** - Students can now view their enrolled classes on the home page
- New `StudentClassesListWidget` component at `lib/features/dashboard/presentation/widgets/student_classes_list_widget.dart`
- Query: `.collection('enrollments').where('studentId', isEqualTo: currentUser.uid).orderBy('joinedAt', descending: true)`
- For each enrollment document, uses `FutureBuilder` to fetch the corresponding class from `classes` collection
- Layout identical to `TeacherClassesListWidget`:
- Horizontal `ListView.builder` with `scrollDirection: Axis.horizontal`
- 2-row grid layout: Column with cards at index * 2 and index * 2 + 1
- Card size: 200x150 pixels
- Card styling: White background, 16px border radius, subtle shadow
- Card content: Icon (school), class name (bold), class code (grey)
- Title: "As Minhas Turmas" with `textTheme.titleLarge` style
- Loading state: `CircularProgressIndicator` centered in card while loading class data
- Empty state: "Ainda não entraste em nenhuma turma."
- Widget inserted in `StudentDashboardPage` between `QuickAccessWidget` and `ProfileSectionWidget`
- **System is now bidirectional:**
- Professor creates classes → Students can join
- Professor sees list of students in each class (ETAPA 3)
- Students see list of classes they joined (ETAPA 5)
- Both use same visual patterns for consistency
- Testing: After joining a class (ETAPA 4), the class appears immediately in the student's home list
-**ETAPA 4: Join Class Feature** - Students can now join classes using class codes
- New `JoinClassPage` component at `lib/features/classes/presentation/pages/join_class_page.dart`
- Query: `.collection('classes').where('code', isEqualTo: enteredCode).limit(1)`
- Input: TextField with uppercase formatting, 6 character limit, centered text, letter spacing
- Validation flow:
1. Check if code is empty → show error "Insere o código da turma"
2. Query Firestore for class with matching code
3. If no class found → show error "Código de turma inválido"
4. Check if student already enrolled → show error "Já estás inscrito nesta turma"
5. Create enrollment document in `enrollments` collection
- Enrollment document structure:
```dart
{
'classId': classDoc.id,
'studentId': currentUser.uid,
'studentName': currentUser.displayName ?? email.split('@')[0] ?? 'Aluno',
'joinedAt': FieldValue.serverTimestamp(),
}
```
- Success feedback: Green SnackBar "Entraste na turma com sucesso!" (2 seconds)
- Error feedback: Red SnackBar with specific error message (3 seconds)
- Auto-navigates back to Student Dashboard after successful join
- Loading state: CircularProgressIndicator in button while processing
- Visual design: Teal AppBar (#82C9BD), centered group_add icon, clean white input card
- New "Entrar numa Turma" card added to `QuickAccessWidget` in Student Dashboard
- Card design: Horizontal layout with `Icons.group_add`, white background, rounded corners (16px)
- Testing in Firebase Console:
1. Go to Firestore Database → classes collection
2. Copy the `code` field from any class document
3. In app: Student Dashboard → "Entrar numa Turma"
4. Paste code and tap "Entrar na Turma"
5. Check enrollments collection for new document with correct data
- ✅ **ETAPA 3: Class Students View** - Teachers can now view enrolled students in each class
- New `ClassStudentsPage` component at `lib/features/classes/presentation/pages/class_students_page.dart`
- Query: `.collection('enrollments').where('classId', isEqualTo: classId).orderBy('joinedAt', descending: true)`
- StreamBuilder for real-time updates when students join
- ListTile design with student icon, name, and formatted join date
- Empty state: "Nenhum aluno entrou nesta turma ainda."
- Loading state with `CircularProgressIndicator`
- Error state with error icon and message
- Date formatting using `intl` package (Portuguese format: dd/MM/yyyy)
- Navigation via `GestureDetector` onTap in `TeacherClassesListWidget`
- MaterialPageRoute navigation passing `classId` and `className` as parameters
- AppBar with back button and two-line title (class name + subtitle)
- Consistent visual design: teal colors (#82C9BD), white cards, rounded corners (16px), subtle shadows
- ✅ **ETAPA 2: Classes List Display** - Teachers can now view their created classes
- New `TeacherClassesListWidget` component
- "As Minhas Turmas" section added to Teacher Dashboard

View File

@@ -0,0 +1,196 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
/// Página para visualizar os alunos de uma turma específica
class ClassStudentsPage extends StatelessWidget {
final String classId;
final String className;
const ClassStudentsPage({
super.key,
required this.classId,
required this.className,
});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF8F9FA),
appBar: AppBar(
backgroundColor: const Color(0xFF82C9BD),
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.white),
onPressed: () => Navigator.of(context).pop(),
),
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
className,
style: const TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const Text(
'Alunos Matriculados',
style: TextStyle(
color: Colors.white70,
fontSize: 13,
fontWeight: FontWeight.w300,
),
),
],
),
),
body: StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection('enrollments')
.where('classId', isEqualTo: classId)
.orderBy('joinedAt', descending: true)
.snapshots(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(
color: Color(0xFF82C9BD),
),
);
}
if (snapshot.hasError) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.error_outline,
size: 48,
color: Colors.grey[400],
),
const SizedBox(height: 16),
Text(
'Erro ao carregar alunos',
style: TextStyle(
color: Colors.grey[600],
fontSize: 16,
),
),
],
),
);
}
final enrollments = snapshot.data?.docs ?? [];
if (enrollments.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.people_outline,
size: 64,
color: Colors.grey[300],
),
const SizedBox(height: 24),
Text(
'Nenhum aluno entrou nesta turma ainda.',
style: TextStyle(
color: Colors.grey[600],
fontSize: 16,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 32.0),
child: Text(
'Partilha o código da turma para os alunos se juntarem.',
style: TextStyle(
color: Colors.grey[500],
fontSize: 13,
),
textAlign: TextAlign.center,
),
),
],
),
);
}
return ListView.builder(
padding: const EdgeInsets.all(16.0),
itemCount: enrollments.length,
itemBuilder: (context, index) {
final enrollment = enrollments[index].data() as Map<String, dynamic>;
final studentName = enrollment['studentName'] as String? ?? 'Aluno sem nome';
final joinedAt = enrollment['joinedAt'] as Timestamp?;
return Container(
margin: const EdgeInsets.only(bottom: 12.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16.0),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: ListTile(
contentPadding: const EdgeInsets.all(16.0),
leading: Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: const Color(0xFF82C9BD).withOpacity(0.1),
borderRadius: BorderRadius.circular(12.0),
),
child: const Icon(
Icons.person,
color: Color(0xFF82C9BD),
size: 24,
),
),
title: Text(
studentName,
style: const TextStyle(
color: Color(0xFF2D3748),
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 4),
Text(
joinedAt != null
? 'Entrou em ${_formatDate(joinedAt.toDate())}'
: 'Data desconhecida',
style: TextStyle(
color: Colors.grey[600],
fontSize: 13,
),
),
],
),
),
);
},
);
},
),
);
}
String _formatDate(DateTime date) {
return DateFormat('dd/MM/yyyy').format(date);
}
}

View File

@@ -0,0 +1,247 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import '../../../../core/services/auth_service.dart';
/// Página para o aluno entrar numa turma usando o código
class JoinClassPage extends StatefulWidget {
const JoinClassPage({super.key});
@override
State<JoinClassPage> createState() => _JoinClassPageState();
}
class _JoinClassPageState extends State<JoinClassPage> {
final _codeController = TextEditingController();
bool _isLoading = false;
@override
void dispose() {
_codeController.dispose();
super.dispose();
}
Future<void> _joinClass() async {
final code = _codeController.text.trim().toUpperCase();
if (code.isEmpty) {
_showError('Insere o código da turma');
return;
}
setState(() => _isLoading = true);
try {
// Procurar turma pelo código
final classQuery = await FirebaseFirestore.instance
.collection('classes')
.where('code', isEqualTo: code)
.limit(1)
.get();
if (classQuery.docs.isEmpty) {
setState(() => _isLoading = false);
_showError('Código de turma inválido');
return;
}
final classDoc = classQuery.docs.first;
final classId = classDoc.id;
final currentUser = AuthService.currentUser;
if (currentUser == null) {
setState(() => _isLoading = false);
_showError('Erro: Utilizador não autenticado');
return;
}
// Verificar se já está inscrito nesta turma
final existingEnrollment = await FirebaseFirestore.instance
.collection('enrollments')
.where('classId', isEqualTo: classId)
.where('studentId', isEqualTo: currentUser.uid)
.limit(1)
.get();
if (existingEnrollment.docs.isNotEmpty) {
setState(() => _isLoading = false);
_showError('Já estás inscrito nesta turma');
return;
}
// Criar documento de inscrição
await FirebaseFirestore.instance.collection('enrollments').add({
'classId': classId,
'studentId': currentUser.uid,
'studentName': currentUser.displayName ?? currentUser.email?.split('@')[0] ?? 'Aluno',
'joinedAt': FieldValue.serverTimestamp(),
});
setState(() => _isLoading = false);
// Mostrar sucesso
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Entraste na turma com sucesso!'),
backgroundColor: Color(0xFF10B981),
duration: Duration(seconds: 2),
),
);
// Voltar para a home
Navigator.of(context).pop();
}
} catch (e) {
setState(() => _isLoading = false);
_showError('Erro ao entrar na turma: $e');
}
}
void _showError(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: const Color(0xFFEF4444),
duration: const Duration(seconds: 3),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF8F9FA),
appBar: AppBar(
backgroundColor: const Color(0xFF82C9BD),
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.white),
onPressed: () => Navigator.of(context).pop(),
),
title: const Text(
'Entrar numa Turma',
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
body: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Ícone e descrição
Center(
child: Container(
width: 80,
height: 80,
decoration: BoxDecoration(
color: const Color(0xFF82C9BD).withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
),
child: const Icon(
Icons.group_add,
color: Color(0xFF82C9BD),
size: 40,
),
),
),
const SizedBox(height: 24),
const Center(
child: Text(
'Insere o código da turma',
style: TextStyle(
color: Color(0xFF2D3748),
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
const SizedBox(height: 8),
Center(
child: Text(
'O professor partilhou contigo um código de 6 caracteres.',
style: TextStyle(
color: Colors.grey[600],
fontSize: 14,
),
textAlign: TextAlign.center,
),
),
const SizedBox(height: 32),
// Campo de código
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0xFFE2E8F0), width: 1),
),
child: TextField(
controller: _codeController,
textCapitalization: TextCapitalization.characters,
maxLength: 6,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
letterSpacing: 4,
color: Color(0xFF2D3748),
),
textAlign: TextAlign.center,
decoration: InputDecoration(
hintText: 'XXXXXX',
hintStyle: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
letterSpacing: 4,
color: Colors.grey[400],
),
border: InputBorder.none,
contentPadding: const EdgeInsets.all(20),
counterText: '',
),
),
),
const SizedBox(height: 24),
// Botão de entrar
SizedBox(
width: double.infinity,
height: 56,
child: ElevatedButton(
onPressed: _isLoading ? null : _joinClass,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF82C9BD),
foregroundColor: Colors.white,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
disabledBackgroundColor: const Color(0xFF82C9BD).withOpacity(0.5),
),
child: _isLoading
? const SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
color: Colors.white,
strokeWidth: 2,
),
)
: const Text(
'Entrar na Turma',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
),
],
),
),
);
}
}

View File

@@ -3,6 +3,7 @@ import 'package:go_router/go_router.dart';
import '../../../../core/services/auth_service.dart';
import '../widgets/progress_hero_widget.dart';
import '../widgets/quick_access_widget.dart';
import '../widgets/student_classes_list_widget.dart';
import '../widgets/profile_section_widget.dart';
class StudentDashboardPage extends StatefulWidget {
@@ -146,7 +147,12 @@ class _StudentDashboardPageState extends State<StudentDashboardPage> {
const SizedBox(height: 24),
// Profile Section (Priority 3)
// Classes List Section (Priority 3)
const StudentClassesListWidget(),
const SizedBox(height: 24),
// Profile Section (Priority 4)
const ProfileSectionWidget(),
const SizedBox(height: 40),

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:go_router/go_router.dart';
import '../../../classes/presentation/pages/join_class_page.dart';
/// Quick access cards for Tutor IA and Quiz with fixed overflow
class QuickAccessWidget extends StatelessWidget {
@@ -30,6 +31,9 @@ class QuickAccessWidget extends StatelessWidget {
Expanded(flex: 2, child: _buildQuizCard(context)),
],
),
const SizedBox(height: 16),
// Join Class Card
_buildJoinClassCard(context),
],
)
.animate()
@@ -235,4 +239,93 @@ class QuickAccessWidget extends StatelessWidget {
)
.then(delay: const Duration(milliseconds: 200));
}
Widget _buildJoinClassCard(BuildContext context) {
return Container(
height: 80,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: const Color(0xFFE2E8F0), width: 1),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(16),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const JoinClassPage(),
),
);
},
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: const Color(0xFF82C9BD).withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: const Icon(
Icons.group_add,
color: Color(0xFF82C9BD),
size: 24,
),
),
const SizedBox(width: 16),
const Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Entrar numa Turma',
style: TextStyle(
color: Color(0xFF2D3748),
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 4),
Text(
'Junta-te a uma turma com o código',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Color(0xFF718096),
fontSize: 13,
),
),
],
),
),
const Icon(
Icons.arrow_forward_ios,
color: Color(0xFF82C9BD),
size: 16,
),
],
),
),
),
),
)
.animate()
.scale(
duration: const Duration(milliseconds: 600),
curve: Curves.elasticOut,
)
.then(delay: const Duration(milliseconds: 300));
}
}

View File

@@ -0,0 +1,192 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import '../../../../core/services/auth_service.dart';
/// Widget para listar as turmas onde o aluno está inscrito
class StudentClassesListWidget extends StatelessWidget {
const StudentClassesListWidget({super.key});
@override
Widget build(BuildContext context) {
final currentUser = AuthService.currentUser;
if (currentUser == null) {
return const SizedBox.shrink();
}
return StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection('enrollments')
.where('studentId', isEqualTo: currentUser.uid)
.orderBy('joinedAt', descending: true)
.snapshots(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: Padding(
padding: EdgeInsets.all(16.0),
child: CircularProgressIndicator(
color: Color(0xFF82C9BD),
),
),
);
}
if (snapshot.hasError) {
return const SizedBox.shrink();
}
final enrollments = snapshot.data?.docs ?? [];
if (enrollments.isEmpty) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Text(
'Ainda não entraste em nenhuma turma.',
style: TextStyle(
color: Colors.grey[600],
fontSize: 14,
),
),
);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'As Minhas Turmas',
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: const Color(0xFF2D3748),
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 16),
SizedBox(
height: 330,
child: ListView.builder(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.only(right: 16),
itemCount: (enrollments.length + 1) ~/ 2,
itemBuilder: (context, index) {
final firstIndex = index * 2;
final secondIndex = firstIndex + 1;
return Padding(
padding: const EdgeInsets.only(right: 12),
child: Column(
children: [
_buildClassCard(enrollments[firstIndex]),
const SizedBox(height: 12),
if (secondIndex < enrollments.length)
_buildClassCard(enrollments[secondIndex]),
],
),
);
},
),
),
],
);
},
);
}
Widget _buildClassCard(DocumentSnapshot enrollmentDoc) {
final enrollmentData = enrollmentDoc.data() as Map<String, dynamic>;
final classId = enrollmentData['classId'] as String? ?? '';
if (classId.isEmpty) {
return const SizedBox.shrink();
}
return FutureBuilder<DocumentSnapshot>(
future: FirebaseFirestore.instance.collection('classes').doc(classId).get(),
builder: (context, snapshot) {
if (!snapshot.hasData || !snapshot.data!.exists) {
return Container(
width: 200,
height: 150,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: const Center(
child: CircularProgressIndicator(
color: Color(0xFF82C9BD),
strokeWidth: 2,
),
),
);
}
final classData = snapshot.data!.data() as Map<String, dynamic>;
final className = classData['name'] as String? ?? 'Sem nome';
final classCode = classData['code'] as String? ?? '----';
return Container(
width: 200,
height: 150,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: const Color(0xFF82C9BD).withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: const Icon(
Icons.school,
color: Color(0xFF82C9BD),
size: 24,
),
),
const SizedBox(height: 12),
Text(
className,
style: const TextStyle(
color: Color(0xFF2D3748),
fontSize: 16,
fontWeight: FontWeight.bold,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(
'Código: $classCode',
style: TextStyle(
color: Colors.grey[600],
fontSize: 13,
),
),
],
),
);
},
);
}
}

View File

@@ -2,6 +2,7 @@ import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import '../../../../core/services/auth_service.dart';
import '../../../classes/presentation/pages/class_students_page.dart';
/// Widget para listar as turmas criadas pelo professor
class TeacherClassesListWidget extends StatelessWidget {
@@ -77,10 +78,10 @@ class TeacherClassesListWidget extends StatelessWidget {
padding: const EdgeInsets.only(right: 12),
child: Column(
children: [
_buildClassCard(classes[firstIndex]),
_buildClassCard(classes[firstIndex], context),
const SizedBox(height: 12),
if (secondIndex < classes.length)
_buildClassCard(classes[secondIndex]),
_buildClassCard(classes[secondIndex], context),
],
),
);
@@ -93,12 +94,25 @@ class TeacherClassesListWidget extends StatelessWidget {
);
}
Widget _buildClassCard(DocumentSnapshot doc) {
Widget _buildClassCard(DocumentSnapshot doc, BuildContext context) {
final data = doc.data() as Map<String, dynamic>;
final classId = doc.id;
final className = data['name'] as String? ?? 'Sem nome';
final classCode = data['code'] as String? ?? '----';
return Container(
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => ClassStudentsPage(
classId: classId,
className: className,
),
),
);
},
child: Container(
width: 200,
height: 150,
padding: const EdgeInsets.all(16),
@@ -150,6 +164,7 @@ class TeacherClassesListWidget extends StatelessWidget {
),
],
),
),
);
}
}