diff --git a/app/Professor/Alunos/CriarAluno.tsx b/app/Professor/Alunos/CriarAluno.tsx
new file mode 100644
index 0000000..074c802
--- /dev/null
+++ b/app/Professor/Alunos/CriarAluno.tsx
@@ -0,0 +1,227 @@
+// app/Professor/Alunos/CriarAluno.tsx
+import { Ionicons } from '@expo/vector-icons';
+import { useRouter } from 'expo-router';
+import { useMemo, useState } from 'react';
+import {
+ ActivityIndicator,
+ Alert,
+ KeyboardAvoidingView,
+ Platform,
+ ScrollView,
+ StyleSheet,
+ Text,
+ TextInput,
+ TouchableOpacity,
+ View,
+} from 'react-native';
+import { SafeAreaView } from 'react-native-safe-area-context';
+import { useTheme } from '../../../themecontext';
+import { supabase } from '../../lib/supabase';
+
+const CriarAluno = () => {
+ const { isDarkMode } = useTheme();
+ const router = useRouter();
+ const [loading, setLoading] = useState(false);
+
+ // ESTADOS
+ const [email, setEmail] = useState('');
+ const [password, setPassword] = useState('');
+ const [nome, setNome] = useState('');
+ const [residencia, setResidencia] = useState('');
+ const [telefone, setTelefone] = useState('');
+ const [ano, setAno] = useState('');
+ const [nEscola, setNEscola] = useState('');
+ const [curso, setCurso] = useState('');
+ const [tipo, setTipo] = useState<'aluno' | 'professor' | 'empresa'>('aluno');
+
+ const cores = useMemo(() => ({
+ fundo: isDarkMode ? '#0A0A0A' : '#FFFFFF',
+ card: isDarkMode ? '#161618' : '#F8FAFC',
+ texto: isDarkMode ? '#F8FAFC' : '#1A365D',
+ secundario: isDarkMode ? '#94A3B8' : '#718096',
+ azul: '#2390a6',
+ laranja: '#E38E00',
+ borda: isDarkMode ? '#2D2D2D' : '#E2E8F0',
+ }), [isDarkMode]);
+
+ const handleCriar = async () => {
+ if (!email || !password || !nome || (tipo === 'aluno' && (!nEscola || !ano || !curso))) {
+ Alert.alert("Atenção", "Preenche os campos obrigatórios para evitar que isto dê merda.");
+ return;
+ }
+
+ if (password.length < 6) {
+ Alert.alert("Erro", "A password tem de ter pelo menos 6 caracteres.");
+ return;
+ }
+
+ setLoading(true);
+
+ try {
+ // 1. Criar Utilizador no Auth
+ const { data: authData, error: authError } = await supabase.auth.signUp({
+ email,
+ password,
+ options: { data: { nome, tipo } }
+ });
+
+ if (authError) throw authError;
+ const user = authData.user;
+ if (!user) throw new Error("Erro ao gerar ID.");
+
+ // 2. Tabela 'profiles'
+ const { error: profileError } = await supabase
+ .from('profiles')
+ .upsert({
+ id: user.id,
+ nome,
+ email,
+ residencia,
+ telefone,
+ n_escola: tipo === 'aluno' ? nEscola : null,
+ curso: tipo === 'aluno' ? curso.toUpperCase() : null,
+ tipo
+ });
+
+ if (profileError) throw profileError;
+
+ // 3. Tabela 'alunos' (SÓ SE FOR ALUNO)
+ if (tipo === 'aluno') {
+ const { error: alunoError } = await supabase
+ .from('alunos')
+ .insert([{
+ id: user.id,
+ nome,
+ n_escola: nEscola,
+ ano: parseInt(ano),
+ turma_curso: curso.toUpperCase()
+ }]);
+ if (alunoError) throw alunoError;
+ }
+
+ Alert.alert("Sucesso", `${tipo.toUpperCase()} criado com sucesso!`, [
+ { text: "OK", onPress: () => router.back() }
+ ]);
+
+ } catch (err: any) {
+ console.error(err);
+ Alert.alert("Erro no Registo", err.message);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+
+
+
+
+
+
+ router.back()} style={[styles.backBtn, { borderColor: cores.borda }]}>
+
+
+
+ Novo Registo
+ Criar utilizador no sistema
+
+
+
+ {/* SELETOR DE TIPO */}
+ Tipo de Utilizador
+
+ {(['aluno', 'professor', 'empresa'] as const).map((item) => (
+ setTipo(item)}
+ >
+
+ {item.charAt(0).toUpperCase() + item.slice(1)}
+
+
+ ))}
+
+
+
+ Dados de Acesso
+
+
+
+ Informação Geral
+
+
+
+
+ {/* CAMPOS ESPECÍFICOS PARA ALUNO */}
+ {tipo === 'aluno' && (
+ <>
+ Dados Escolares
+
+
+
+
+
+ >
+ )}
+
+
+
+ {loading ? : REGISTAR {tipo.toUpperCase()}}
+
+
+
+
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ scroll: { padding: 24, paddingBottom: 60 },
+ header: { flexDirection: 'row', alignItems: 'center', marginBottom: 25 },
+ backBtn: { width: 45, height: 45, borderRadius: 12, borderWidth: 1, justifyContent: 'center', alignItems: 'center', marginRight: 15 },
+ title: { fontSize: 24, fontWeight: 'bold' },
+ subtitle: { fontSize: 12, fontWeight: '600', textTransform: 'uppercase' },
+ sectionTitle: { fontSize: 13, fontWeight: 'bold', marginTop: 20, marginBottom: 10, opacity: 0.6 },
+ selectorContainer: { flexDirection: 'row', gap: 10, marginBottom: 10 },
+ selectorBtn: { flex: 1, height: 45, borderRadius: 10, borderWidth: 1, justifyContent: 'center', alignItems: 'center' },
+ selectorText: { fontSize: 13, fontWeight: 'bold' },
+ form: { gap: 12 },
+ input: { height: 55, borderRadius: 15, borderWidth: 1, paddingHorizontal: 15, fontSize: 16 },
+ submitBtn: { height: 60, borderRadius: 15, marginTop: 30, justifyContent: 'center', alignItems: 'center' },
+ submitBtnText: { color: '#fff', fontSize: 16, fontWeight: 'bold' },
+});
+
+export default CriarAluno;
\ No newline at end of file
diff --git a/app/Professor/Alunos/ListaAlunos.tsx b/app/Professor/Alunos/ListaAlunos.tsx
index 6bb58ed..2a01e13 100644
--- a/app/Professor/Alunos/ListaAlunos.tsx
+++ b/app/Professor/Alunos/ListaAlunos.tsx
@@ -6,6 +6,7 @@ import {
ActivityIndicator,
Alert,
FlatList,
+ Modal,
RefreshControl,
StatusBar,
StyleSheet,
@@ -42,6 +43,11 @@ const ListaAlunosProfessor = memo(() => {
const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
+ // Estados para o Modal de Eliminação
+ const [showDeleteModal, setShowDeleteModal] = useState(false);
+ const [alunoParaEliminar, setAlunoParaEliminar] = useState(null);
+ const [isDeleting, setIsDeleting] = useState(false);
+
const azulEPVC = '#2390a6';
const laranjaEPVC = '#E38E00';
@@ -55,6 +61,8 @@ const ListaAlunosProfessor = memo(() => {
azulSuave: isDarkMode ? 'rgba(35, 144, 166, 0.12)' : '#F0F9FA',
borda: isDarkMode ? '#2D2D2D' : '#E2E8F0',
verde: '#10B981',
+ vermelho: '#EF4444',
+ vermelhoSuave: isDarkMode ? 'rgba(239, 68, 68, 0.15)' : '#FEE2E2',
}), [isDarkMode]);
const fetchAlunos = async () => {
@@ -62,22 +70,14 @@ const ListaAlunosProfessor = memo(() => {
setLoading(true);
const { data, error } = await supabase
.from('alunos')
- .select(`
- id,
- nome,
- n_escola,
- ano,
- turma_curso,
- estagios(id)
- `)
+ .select(`id, nome, n_escola, ano, turma_curso, estagios(id)`)
.order('ano', { ascending: false })
.order('nome', { ascending: true });
if (error) throw error;
- if (!data) return setTurmas([]);
-
+
const agrupadas: Record = {};
- data.forEach(item => {
+ data?.forEach(item => {
const nomeTurma = `${item.ano}º ${item.turma_curso}`.trim().toUpperCase();
if (!agrupadas[nomeTurma]) agrupadas[nomeTurma] = [];
agrupadas[nomeTurma].push({
@@ -89,18 +89,32 @@ const ListaAlunosProfessor = memo(() => {
});
});
- setTurmas(Object.keys(agrupadas)
- .sort((a, b) => b.localeCompare(a))
- .map(nome => ({ nome, alunos: agrupadas[nome] }))
- );
+ setTurmas(Object.keys(agrupadas).sort((a, b) => b.localeCompare(a)).map(nome => ({ nome, alunos: agrupadas[nome] })));
} catch (err) {
- console.error('Erro:', err);
+ console.error(err);
} finally {
setLoading(false);
setRefreshing(false);
}
};
+ const confirmarEliminacao = async () => {
+ if (!alunoParaEliminar) return;
+ try {
+ setIsDeleting(true);
+ const { error } = await supabase.from('alunos').delete().eq('id', alunoParaEliminar.id);
+ if (error) throw error;
+
+ setShowDeleteModal(false);
+ setAlunoParaEliminar(null);
+ fetchAlunos();
+ } catch (err) {
+ Alert.alert("Erro", "Não foi possível eliminar o aluno.");
+ } finally {
+ setIsDeleting(false);
+ }
+ };
+
useEffect(() => { fetchAlunos(); }, []);
const onRefresh = useCallback(() => {
@@ -113,8 +127,7 @@ const ListaAlunosProfessor = memo(() => {
.map(turma => ({
...turma,
alunos: turma.alunos.filter(a =>
- a.nome.toLowerCase().includes(search.toLowerCase()) ||
- a.n_escola.includes(search)
+ a.nome.toLowerCase().includes(search.toLowerCase()) || a.n_escola.includes(search)
),
}))
.filter(t => t.alunos.length > 0);
@@ -123,38 +136,29 @@ const ListaAlunosProfessor = memo(() => {
return (
-
- {/* HEADER EPVC STYLE */}
+ {/* HEADER */}
- router.back()}
- >
+ router.back()}>
-
Alunos
Gestão de Turmas
-
-
+
- {/* SEARCH MODERNO */}
+ {/* SEARCH */}
{
{item.nome}
-
{item.alunos.map((aluno) => (
router.push({
- pathname: '/Professor/Alunos/DetalhesAluno',
- params: { alunoId: aluno.id }
- })}
+ onPress={() => router.push({ pathname: '/Professor/Alunos/DetalhesAluno', params: { alunoId: aluno.id } })}
+ onLongPress={() => {
+ setAlunoParaEliminar(aluno);
+ setShowDeleteModal(true);
+ }}
>
-
- {aluno.nome.charAt(0)}
-
+ {aluno.nome.charAt(0).toUpperCase()}
-
{aluno.nome}
-
-
- Nº {aluno.n_escola}
-
+ Nº {aluno.n_escola}
-
- {aluno.tem_estagio ? (
-
-
- COLOCADO
-
- ) : (
-
-
- PENDENTE
-
- )}
+
))}
)}
- ListEmptyComponent={() => (
-
-
- Nenhum aluno encontrado.
-
- )}
/>
)}
- {/* FAB */}
+ {/* MODAL DE ELIMINAÇÃO CUSTOMIZADO */}
+
+
+
+
+
+
+
+ Eliminar Aluno?
+
+ Estás prestes a apagar {alunoParaEliminar?.nome}.
+ Esta ação é irreversível e **vai dar merda** se não tiveres a certeza!
+
+
+
+ setShowDeleteModal(false)}
+ >
+ Cancelar
+
+
+
+ {isDeleting ? (
+
+ ) : (
+ Eliminar
+ )}
+
+
+
+
+
+
Alert.alert("EPVC", "Funcionalidade de registo de aluno em desenvolvimento.")}
+ onPress={() => router.push('/Professor/Alunos/CriarAluno')} // Altera esta linha
>
Novo Aluno
@@ -242,29 +260,34 @@ const ListaAlunosProfessor = memo(() => {
const styles = StyleSheet.create({
safe: { flex: 1 },
header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 24, paddingVertical: 15 },
- headerTitle: { fontSize: 22, fontWeight: '900', letterSpacing: -0.5 },
- headerSubtitle: { fontSize: 11, fontWeight: '800', textTransform: 'uppercase', letterSpacing: 0.5 },
+ headerTitle: { fontSize: 22, fontWeight: '900' },
+ headerSubtitle: { fontSize: 11, fontWeight: '800', textTransform: 'uppercase' },
btnAction: { width: 44, height: 44, borderRadius: 14, justifyContent: 'center', alignItems: 'center', borderWidth: 1 },
searchSection: { paddingHorizontal: 24, marginBottom: 10 },
searchBar: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 16, height: 56, borderRadius: 20, borderWidth: 1.5 },
searchInput: { flex: 1, marginLeft: 12, fontSize: 14, fontWeight: '700' },
listPadding: { paddingHorizontal: 24, paddingTop: 10 },
- sectionHeader: { flexDirection: 'row', alignItems: 'center', marginTop: 10, marginBottom: 18 },
+ sectionHeader: { flexDirection: 'row', alignItems: 'center', marginBottom: 18 },
sectionDot: { width: 8, height: 8, borderRadius: 4, marginRight: 10 },
- sectionTitle: { fontSize: 13, fontWeight: '900', textTransform: 'uppercase', letterSpacing: 0.8 },
+ sectionTitle: { fontSize: 13, fontWeight: '900', textTransform: 'uppercase' },
sectionLine: { flex: 1, height: 1, marginLeft: 15, opacity: 0.5 },
- alunoCard: { flexDirection: 'row', alignItems: 'center', padding: 16, borderRadius: 28, marginBottom: 12, borderWidth: 1, elevation: 3, shadowColor: '#000', shadowOpacity: 0.04, shadowRadius: 10 },
+ alunoCard: { flexDirection: 'row', alignItems: 'center', padding: 16, borderRadius: 28, marginBottom: 12, borderWidth: 1 },
avatar: { width: 48, height: 48, borderRadius: 16, justifyContent: 'center', alignItems: 'center' },
avatarText: { fontSize: 18, fontWeight: '900' },
alunoInfo: { flex: 1, marginLeft: 15 },
- alunoNome: { fontSize: 16, fontWeight: '800', letterSpacing: -0.3 },
- idRow: { flexDirection: 'row', alignItems: 'center', gap: 5, marginTop: 3 },
+ alunoNome: { fontSize: 16, fontWeight: '800' },
idText: { fontSize: 13, fontWeight: '600' },
- statusBadge: { flexDirection: 'row', alignItems: 'center', gap: 4, paddingHorizontal: 8, paddingVertical: 4, borderRadius: 10 },
- statusText: { fontSize: 9, fontWeight: '900' },
- emptyContainer: { marginTop: 80, alignItems: 'center' },
- fab: { position: 'absolute', right: 24, flexDirection: 'row', alignItems: 'center', paddingHorizontal: 22, paddingVertical: 16, borderRadius: 22, elevation: 8, shadowColor: '#2390a6', shadowOpacity: 0.3, shadowRadius: 10 },
- fabText: { color: '#fff', fontSize: 15, fontWeight: '900', marginLeft: 10, textTransform: 'uppercase' },
+ fab: { position: 'absolute', right: 24, flexDirection: 'row', alignItems: 'center', paddingHorizontal: 22, paddingVertical: 16, borderRadius: 22 },
+ fabText: { color: '#fff', fontSize: 15, fontWeight: '900', marginLeft: 10 },
+ // Estilos do Modal
+ modalOverlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.7)', justifyContent: 'center', alignItems: 'center', padding: 30 },
+ modalCard: { width: '100%', borderRadius: 32, padding: 24, alignItems: 'center' },
+ warningIconBox: { width: 70, height: 70, borderRadius: 25, justifyContent: 'center', alignItems: 'center', marginBottom: 20 },
+ modalTitle: { fontSize: 22, fontWeight: '900', marginBottom: 10 },
+ modalDesc: { fontSize: 15, textAlign: 'center', lineHeight: 22, marginBottom: 25 },
+ modalButtons: { flexDirection: 'row', gap: 12, width: '100%' },
+ btnModal: { flex: 1, height: 56, borderRadius: 18, justifyContent: 'center', alignItems: 'center' },
+ btnText: { fontSize: 16, fontWeight: '800' }
});
export default ListaAlunosProfessor;
\ No newline at end of file