Compare commits

...

1 Commits

Author SHA1 Message Date
34d7ae8afc últimas mudanças 2026-06-11 23:59:58 +01:00
4 changed files with 248 additions and 206 deletions

View File

@@ -93,6 +93,20 @@ class _LoginPageState extends State<LoginPage> {
return;
}
// Se role não existe na Firestore (null), permitir login e criar documento
if (actualRole == null) {
print(
'DEBUG: Role não encontrado na Firestore, criando documento com role selecionado: $selectedRole',
);
try {
await AuthService.createUserRole(uid, selectedRole);
print('DEBUG: Role criado com sucesso');
} catch (e) {
print('DEBUG: Erro ao criar role: $e');
// Continuar mesmo se falhar, pois o usuário já está autenticado
}
}
// Validar se o role selecionado corresponde ao role real
if (actualRole != null && selectedRole != actualRole) {
// Fazer logout imediato antes de mostrar erro
@@ -118,6 +132,9 @@ class _LoginPageState extends State<LoginPage> {
return;
}
// Usar selectedRole se actualRole for null (caso acabamos de criar)
final finalRole = actualRole ?? selectedRole;
// Save session based on remember me preference
await SessionService.saveSession(
rememberMe: _rememberMe,
@@ -137,7 +154,7 @@ class _LoginPageState extends State<LoginPage> {
);
// Redirecionar baseado no role real
if (actualRole == 'teacher') {
if (finalRole == 'teacher') {
context.go('/teacher-dashboard');
} else {
context.go('/student-dashboard');
@@ -523,7 +540,9 @@ class _LoginPageState extends State<LoginPage> {
// Signup link
GestureDetector(
onTap: () {
context.go('/signup');
context.go(
'/signup?role=${widget.selectedRole}',
);
},
child: Text(
'Não tem conta? Criar aqui',

View File

@@ -41,12 +41,14 @@ class _SignupPageState extends State<SignupPage> {
Future<void> _loadAvailableClasses() async {
setState(() => _isLoadingClasses = true);
try {
print('DEBUG: Loading school_classes from Firestore');
final snapshot = await FirebaseFirestore.instance
.collection('school_classes')
.where('active', isEqualTo: true)
.orderBy('year')
.orderBy('section')
.get();
print('DEBUG: Loaded ${snapshot.docs.length} school classes');
setState(() {
_availableClasses = snapshot.docs.map((doc) {
final data = doc.data();
@@ -55,6 +57,7 @@ class _SignupPageState extends State<SignupPage> {
_isLoadingClasses = false;
});
} catch (e) {
print('DEBUG: Error loading school_classes: $e');
setState(() => _isLoadingClasses = false);
}
}

View File

@@ -1,9 +1,11 @@
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
@@ -202,9 +204,7 @@ class _ClassStudentsPageState extends State<ClassStudentsPage> {
controller: textController,
decoration: InputDecoration(
labelText: 'Nome da Turma',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
prefixIcon: const Icon(Icons.school),
),
autofocus: true,
@@ -254,10 +254,14 @@ class _ClassStudentsPageState extends State<ClassStudentsPage> {
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.error.withValues(alpha: 0.1),
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),
color: Theme.of(
context,
).colorScheme.error.withValues(alpha: 0.3),
),
),
child: Row(
@@ -420,12 +424,7 @@ class _ClassStudentsPageState extends State<ClassStudentsPage> {
Widget _buildAppBar(ColorScheme cs) {
return Container(
padding: const EdgeInsets.only(
left: 16,
right: 16,
top: 52,
bottom: 16,
),
padding: const EdgeInsets.only(left: 16, right: 16, top: 52, bottom: 16),
child: Column(
children: [
// Top Row with Back and Actions
@@ -459,11 +458,20 @@ class _ClassStudentsPageState extends State<ClassStudentsPage> {
// Delete Button
Container(
decoration: BoxDecoration(
color: Colors.red.withValues(alpha: 0.2),
color:
(Theme.of(context).brightness == Brightness.dark
? DarkBrandColors.primaryOrange
: AppColors.primaryOrange)
.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(12),
),
child: IconButton(
icon: const Icon(Icons.delete_outline, color: Colors.white),
icon: Icon(
Icons.delete_outline,
color: Theme.of(context).brightness == Brightness.dark
? DarkBrandColors.primaryOrange
: AppColors.primaryOrange,
),
onPressed: _showDeleteClassDialog,
tooltip: 'Eliminar turma',
),
@@ -626,7 +634,9 @@ class _ClassStudentsPageState extends State<ClassStudentsPage> {
textAlign: TextAlign.center,
),
const SizedBox(height: 24),
Container(
GestureDetector(
onTap: _copyCodeToClipboard,
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 12,
@@ -654,12 +664,16 @@ class _ClassStudentsPageState extends State<ClassStudentsPage> {
],
),
),
),
],
),
).animate().fadeIn(
)
.animate()
.fadeIn(
duration: const Duration(milliseconds: 400),
curve: Curves.easeOut,
).then(delay: const Duration(milliseconds: 100)),
)
.then(delay: const Duration(milliseconds: 100)),
],
),
);
@@ -679,11 +693,7 @@ class _ClassStudentsPageState extends State<ClassStudentsPage> {
margin: const EdgeInsets.only(bottom: 16, left: 8),
child: Row(
children: [
Icon(
Icons.people,
color: cs.onSurface,
size: 20,
),
Icon(Icons.people, color: cs.onSurface, size: 20),
const SizedBox(width: 8),
Text(
'Alunos Matriculados',
@@ -832,7 +842,11 @@ class _ClassStudentsPageState extends State<ClassStudentsPage> {
borderRadius: BorderRadius.circular(10),
),
child: IconButton(
icon: Icon(Icons.delete_outline, color: cs.error, size: 20),
icon: Icon(
Icons.delete_outline,
color: cs.error,
size: 20,
),
onPressed: () => _showRemoveStudentDialog(
context,
enrollmentId,
@@ -848,16 +862,38 @@ class _ClassStudentsPageState extends State<ClassStudentsPage> {
),
),
),
).animate().fadeIn(
)
.animate()
.fadeIn(
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
).then(delay: Duration(milliseconds: index * 50));
)
.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<void> _showRemoveStudentDialog(
BuildContext context,
String enrollmentId,
@@ -869,7 +905,10 @@ class _ClassStudentsPageState extends State<ClassStudentsPage> {
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
title: Row(
children: [
Icon(Icons.person_remove, color: Theme.of(context).colorScheme.error),
Icon(
Icons.person_remove,
color: Theme.of(context).colorScheme.error,
),
const SizedBox(width: 8),
const Text('Remover Aluno'),
],

View File

@@ -56,11 +56,6 @@ class _JoinClassPageState extends ConsumerState<JoinClassPage> {
return;
}
// Ler schoolClassId autorizado do aluno (definido no registo)
final studentSchoolClassId = await AuthService.getStudentSchoolClassId(
currentUser.uid,
);
// Procurar disciplina pelo código
final classQuery = await FirebaseFirestore.instance
.collection('classes')
@@ -76,20 +71,6 @@ class _JoinClassPageState extends ConsumerState<JoinClassPage> {
final classDoc = classQuery.docs.first;
final classId = classDoc.id;
final classSchoolClassId = classDoc.data()['schoolClassId'] as String?;
// Verificar se o aluno está autorizado a entrar nesta disciplina
// O schoolClassId do aluno deve corresponder ao schoolClassId da disciplina
if (studentSchoolClassId == null ||
classSchoolClassId == null ||
studentSchoolClassId != classSchoolClassId) {
setState(() => _isLoading = false);
_showError(
'Não tens permissão para entrar nesta disciplina.\n'
'O teu professor ainda não te adicionou a esta disciplina.',
);
return;
}
// Verificar se já está inscrito nesta disciplina
final existingEnrollment = await FirebaseFirestore.instance