diff --git a/app/Aluno/AlunoHome.tsx b/app/Aluno/AlunoHome.tsx
index bd38df8..a7c9e98 100644
--- a/app/Aluno/AlunoHome.tsx
+++ b/app/Aluno/AlunoHome.tsx
@@ -76,6 +76,8 @@ const AlunoHome = memo(() => {
azul: azulPetroleo,
vermelho: '#EF4444',
verde: '#10B981',
+ aviso: isDarkMode ? '#2D2200' : '#FFF9E6',
+ avisoTexto: isDarkMode ? '#FFD700' : '#856404'
}), [isDarkMode]);
const showAlert = (msg: string, type: 'success' | 'error' | 'info' = 'info') => {
@@ -95,7 +97,13 @@ const AlunoHome = memo(() => {
const { data: { user } } = await supabase.auth.getUser();
if (!user) return;
const { data: estagio } = await supabase.from('estagios').select('data_inicio, data_fim').eq('aluno_id', user.id).single();
- if (estagio) setConfigEstagio({ inicio: estagio.data_inicio, fim: estagio.data_fim });
+
+ if (estagio) {
+ setConfigEstagio({ inicio: estagio.data_inicio, fim: estagio.data_fim });
+ } else {
+ setConfigEstagio({ inicio: '', fim: '' });
+ }
+
const { data, error } = await supabase.from('presencas').select('*').eq('aluno_id', user.id);
if (error) throw error;
const p: any = {}, f: any = {}, s: any = {}, u: any = {};
@@ -104,26 +112,41 @@ const AlunoHome = memo(() => {
else { f[item.data] = true; u[item.data] = item.justificacao_url || ''; }
});
setPresencas(p); setFaltas(f); setSumarios(s); setUrlsJustificacao(u);
- } catch (error) { console.error(error); }
- finally { setIsLoadingDB(false); }
+ } catch (error) {
+ console.error(error);
+ } finally {
+ setIsLoadingDB(false);
+ }
};
const feriadosMap = useMemo(() => getFeriadosMap(new Date(selectedDate).getFullYear()), [selectedDate]);
+ // LOGICA CORRIGIDA: Se as datas forem '', o estágio não existe e bloqueia tudo
const infoData = useMemo(() => {
const data = new Date(selectedDate);
const diaSemana = data.getDay();
const nomeFeriado = feriadosMap[selectedDate];
- const antesDoInicio = configEstagio.inicio && selectedDate < configEstagio.inicio;
- const depoisDoFim = configEstagio.fim && selectedDate > configEstagio.fim;
+
+ const temEstagio = configEstagio.inicio !== '' && configEstagio.fim !== '';
+ const antesDoInicio = temEstagio && selectedDate < configEstagio.inicio;
+ const depoisDoFim = temEstagio && selectedDate > configEstagio.fim;
+
return {
- valida: diaSemana !== 0 && diaSemana !== 6 && !antesDoInicio && !depoisDoFim && !nomeFeriado,
- podeMarcar: selectedDate === hojeStr && !antesDoInicio && !depoisDoFim && !nomeFeriado,
- nomeFeriado, antesDoInicio, depoisDoFim, foraDeRange: antesDoInicio || depoisDoFim
+ valida: temEstagio && diaSemana !== 0 && diaSemana !== 6 && !antesDoInicio && !depoisDoFim && !nomeFeriado,
+ podeMarcar: temEstagio && selectedDate === hojeStr && !antesDoInicio && !depoisDoFim && !nomeFeriado,
+ nomeFeriado,
+ antesDoInicio,
+ depoisDoFim,
+ foraDeRange: !temEstagio || antesDoInicio || depoisDoFim,
+ temEstagio
};
}, [selectedDate, configEstagio, hojeStr, feriadosMap]);
const handlePresencaClick = async () => {
+ if (!infoData.temEstagio) {
+ showAlert("Aguarde pela configuração do estágio.", "error");
+ return;
+ }
const { status } = await Location.getForegroundPermissionsAsync();
if (status === 'granted') {
executarMarcacao();
@@ -143,15 +166,15 @@ const AlunoHome = memo(() => {
await supabase.from('presencas').upsert({
aluno_id: user?.id, data: selectedDate, estado: 'presente', lat: loc.coords.latitude, lng: loc.coords.longitude
});
- showAlert("Presença marcada com sucesso!", "success");
+ showAlert("Presença marcada!", "success");
fetchDadosSupabase();
} catch (e: any) { showAlert(e.message, "error"); }
finally { setIsLocating(false); }
};
const handleFalta = async () => {
- if (infoData.foraDeRange) return showAlert("Fora do período de estágio.", "error");
- if (!infoData.valida) return showAlert("Não podes marcar falta neste dia.", "error");
+ if (infoData.foraDeRange) return showAlert("Data fora do período de estágio.", "error");
+ if (!infoData.valida) return showAlert("Não é possível registar falta hoje.", "error");
try {
const { data: { user } } = await supabase.auth.getUser();
await supabase.from('presencas').upsert({ aluno_id: user?.id, data: selectedDate, estado: 'faltou' });
@@ -177,7 +200,7 @@ const AlunoHome = memo(() => {
const { data: { publicUrl } } = supabase.storage.from('justificacoes').getPublicUrl(fileName);
await supabase.from('presencas').update({ justificacao_url: publicUrl }).match({ aluno_id: user?.id, data: selectedDate });
setPdf(null);
- showAlert("Justificativo enviado!", "success");
+ showAlert("Enviado com sucesso!", "success");
fetchDadosSupabase();
} catch (e) { showAlert("Erro no upload.", "error"); }
finally { setIsUploading(false); }
@@ -188,9 +211,9 @@ const AlunoHome = memo(() => {
const { data: { user } } = await supabase.auth.getUser();
await supabase.from('presencas').update({ sumario: sumarios[selectedDate] }).match({ aluno_id: user?.id, data: selectedDate });
setEditandoSumario(false);
- showAlert("Sumário atualizado!", "success");
+ showAlert("Sumário guardado!", "success");
fetchDadosSupabase();
- } catch (e) { showAlert("Erro ao guardar sumário.", "error"); }
+ } catch (e) { showAlert("Erro ao guardar.", "error"); }
};
return (
@@ -204,15 +227,15 @@ const AlunoHome = memo(() => {
- Validar Localização
+ Confirmar Local
- Para registar a tua presença, precisamos de confirmar que te encontras no local de estágio.
+ Precisamos de validar a tua localização para confirmar que estás no estágio.
Confirmar e Marcar
setShowLocationModal(false)}>
- Agora não
+ Cancelar
@@ -237,16 +260,34 @@ const AlunoHome = memo(() => {
+ {/* AVISO DE FALTA DE ESTÁGIO - Vai dar merda se o aluno não souber por que está bloqueado */}
+ {!infoData.temEstagio && !isLoadingDB && (
+
+
+
+ O teu período de estágio ainda não foi configurado pelo professor.
+
+
+ )}
+
{isLocating ? : Marcar Presença}
@@ -280,7 +321,7 @@ const AlunoHome = memo(() => {
{presencas[selectedDate] && (
- Sumário do Dia
+ Sumário
setEditandoSumario(true)}>
{
multiline editable={editandoSumario}
value={sumarios[selectedDate]}
onChangeText={(txt) => setSumarios({...sumarios, [selectedDate]: txt})}
- placeholder="Descreve o que fizeste..."
+ placeholder="O que fizeste hoje?"
placeholderTextColor="#94A3B8"
/>
{editandoSumario && Guardar Sumário}
@@ -297,11 +338,11 @@ const AlunoHome = memo(() => {
{faltas[selectedDate] && (
- Justificar Falta
+ Justificar
{urlsJustificacao[selectedDate] ? (
- Documento Enviado
+ Justificativo Enviado
) : (
<>
@@ -311,7 +352,7 @@ const AlunoHome = memo(() => {
{pdf && (
- {isUploading ? : Submeter Justificativo}
+ {isUploading ? : Submeter}
)}
>
@@ -331,10 +372,12 @@ const styles = StyleSheet.create({
title: { fontSize: 26, fontWeight: '900' },
alertBar: { position: 'absolute', top: 50, left: 20, right: 20, padding: 15, borderRadius: 15, zIndex: 1000 },
alertText: { color: '#fff', fontWeight: 'bold', textAlign: 'center' },
+ avisoBox: { flexDirection: 'row', alignItems: 'center', gap: 10, padding: 15, borderRadius: 15, marginBottom: 20 },
+ avisoTexto: { fontSize: 13, fontWeight: '700', flex: 1 },
botoesLinha: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 20 },
btn: { padding: 18, borderRadius: 22, width: '48%', alignItems: 'center', elevation: 3 },
txtBtn: { color: '#fff', fontWeight: '800', fontSize: 14 },
- disabled: { opacity: 0.4 },
+ disabled: { opacity: 0.3 },
cardCalendar: { borderRadius: 30, padding: 10, borderWidth: 1, overflow: 'hidden' },
card: { padding: 20, borderRadius: 25, marginTop: 20, borderWidth: 1 },
cardTitulo: { fontSize: 18, fontWeight: '700' },
diff --git a/app/Aluno/perfil.tsx b/app/Aluno/perfil.tsx
index 2ad34b3..dfcd548 100644
--- a/app/Aluno/perfil.tsx
+++ b/app/Aluno/perfil.tsx
@@ -19,9 +19,6 @@ export default function PerfilAluno() {
const [loading, setLoading] = useState(true);
const [isEditing, setIsEditing] = useState(false);
const [perfil, setPerfil] = useState(null);
- const [estagio, setEstagio] = useState(null);
- const [contagemPresencas, setContagemPresencas] = useState(0);
- const [contagemFaltas, setContagemFaltas] = useState(0);
const [alertConfig, setAlertConfig] = useState<{ msg: string, type: 'success' | 'error' | 'info' } | null>(null);
const alertOpacity = useMemo(() => new Animated.Value(0), []);
@@ -48,7 +45,6 @@ export default function PerfilAluno() {
vermelho: '#EF4444',
borda: isDarkMode ? '#2D2D2D' : '#E2E8F0',
verde: '#10B981',
- laranja: '#dd8707',
}), [isDarkMode]);
const formatarParaExibir = (data: string) => {
@@ -71,59 +67,27 @@ export default function PerfilAluno() {
return formatted;
};
- const calcularHorasTotaisUteis = (dataInicio: string, dataFim: string) => {
- if (!dataInicio || !dataFim) return 400;
- let inicio = new Date(dataInicio);
- let fim = new Date(dataFim);
- let diasUteis = 0;
- const feriados = ['2026-01-01', '2026-04-03', '2026-04-25', '2026-05-01', '2026-06-10', '2026-08-15', '2026-10-05', '2026-11-01', '2026-12-01', '2026-12-08', '2026-12-25'];
-
- let dataAtual = new Date(inicio);
- while (dataAtual <= fim) {
- const diaSemana = dataAtual.getDay();
- const dataIso = dataAtual.toISOString().split('T')[0];
- if (diaSemana !== 0 && diaSemana !== 6 && !feriados.includes(dataIso)) diasUteis++;
- dataAtual.setDate(dataAtual.getDate() + 1);
- }
- return diasUteis * 7;
- };
-
const carregarDados = async () => {
try {
setLoading(true);
const { data: { user } } = await supabase.auth.getUser();
if (!user) return;
- const [perfilRes, estagioRes, presencasRes] = await Promise.all([
- supabase.from('profiles').select('*').eq('id', user.id).single(),
- supabase.from('estagios').select('*, empresas(*)').eq('aluno_id', user.id).single(),
- supabase.from('presencas').select('*').eq('aluno_id', user.id)
- ]);
+ const { data: profile, error } = await supabase
+ .from('profiles')
+ .select('*')
+ .eq('id', user.id)
+ .single();
- const dadosPerfil = perfilRes.data;
- if (dadosPerfil?.data_nascimento) {
- dadosPerfil.data_nascimento = formatarParaExibir(dadosPerfil.data_nascimento);
+ if (error) throw error;
+
+ if (profile?.data_nascimento) {
+ profile.data_nascimento = formatarParaExibir(profile.data_nascimento);
}
- setPerfil({ ...dadosPerfil, email: user.email });
- const dadosEstagio = estagioRes.data;
- setEstagio(dadosEstagio);
-
- if (presencasRes.data && dadosEstagio) {
- const dataInicio = new Date(dadosEstagio.data_inicio);
- const dataFim = new Date(dadosEstagio.data_fim);
-
- // Filtrar apenas presenças que ocorrem DENTRO do intervalo do estágio
- const presencasValidas = presencasRes.data.filter((p: any) => {
- const dataP = new Date(p.data);
- return dataP >= dataInicio && dataP <= dataFim;
- });
-
- setContagemPresencas(presencasValidas.filter((p: any) => p.estado === 'presente').length);
- setContagemFaltas(presencasValidas.filter((p: any) => p.estado === 'faltou').length);
- }
+ setPerfil({ ...profile, email: user.email });
} catch (e) {
- showAlert('Erro ao carregar dados.', 'error');
+ showAlert('Erro ao carregar perfil.', 'error');
} finally {
setLoading(false);
}
@@ -138,14 +102,16 @@ export default function PerfilAluno() {
nome: perfil.nome,
telefone: perfil.telefone,
residencia: perfil.residencia,
- data_nascimento: dataBD
+ data_nascimento: dataBD,
+ curso: perfil.curso,
+ n_escola: perfil.n_escola
}).eq('id', perfil.id);
if (error) throw error;
setIsEditing(false);
- showAlert('Perfil atualizado!', 'success');
+ showAlert('Perfil guardado!', 'success');
} catch (e) {
- showAlert('Verifica se a data está correta.', 'error');
+ showAlert('Erro ao salvar. Verifica os campos.', 'error');
}
};
@@ -154,10 +120,6 @@ export default function PerfilAluno() {
router.replace('/');
};
- const horasTotais = calcularHorasTotaisUteis(estagio?.data_inicio, estagio?.data_fim);
- const horasRealizadas = contagemPresencas * 7;
- const progresso = horasTotais > 0 ? Math.min(1, horasRealizadas / horasTotais) : 0;
-
if (loading) return ;
return (
@@ -171,7 +133,7 @@ export default function PerfilAluno() {
)}
-
+
router.back()}>
@@ -194,53 +156,51 @@ export default function PerfilAluno() {
{perfil?.nome}
- Nº Aluno: {perfil?.n_escola || '---'}
+ {perfil?.curso || 'Sem Curso'} • {perfil?.n_escola || '---'}
- {/* PROGRESS CARD */}
-
- Progresso do Estágio
-
-
- {horasRealizadas}h
- Feitas
-
-
- {Math.max(0, horasTotais - horasRealizadas)}h
- Restam
-
-
- {contagemFaltas}
- Faltas
-
+ {/* DADOS ACADÉMICOS */}
+ Informação Académica
+
+
+
+ setPerfil({...perfil, n_escola: v})}
+ cores={cores} keyboardType="numeric"
+ />
+
+
+ setPerfil({...perfil, curso: v})}
+ cores={cores} autoCapitalize="characters"
+ />
+
-
-
-
- {Math.round(progresso * 100)}% das {horasTotais}h úteis concluídas
+
- {/* INFO CARD */}
+ {/* DADOS PESSOAIS */}
+ Dados Pessoais
setPerfil({...perfil, nome: v})} cores={cores} />
-
- {/* Aumentado o flex da data para 1.2 para evitar corte */}
-
+
setPerfil({...perfil, data_nascimento: aplicarMascaraData(v)})}
- cores={cores}
- maxLength={10}
- keyboardType="numeric"
- placeholder="DD-MM-AAAA"
+ cores={cores} maxLength={10} keyboardType="numeric"
/>
-
+
setPerfil({...perfil, telefone: v})} keyboardType="phone-pad" cores={cores} />
@@ -248,31 +208,8 @@ export default function PerfilAluno() {
setPerfil({...perfil, residencia: v})} cores={cores} />
- {/* ESTÁGIO CARD ATUALIZADO */}
-
- Informações do Estágio
-
-
- Início
- {formatarParaExibir(estagio?.data_inicio)}
-
-
- Fim
- {formatarParaExibir(estagio?.data_fim)}
-
-
- Horário
- {estagio?.horario || '09:00-17:00'}
-
-
-
- Empresa e Tutor
- {estagio?.empresas?.nome}
- {estagio?.empresas?.tutor_nome}
-
-
-
-
+ {/* ACÇÕES */}
+
router.push('/Aluno/redefenirsenha')}>
@@ -321,20 +258,14 @@ const styles = StyleSheet.create({
editBtn: { width: 42, height: 42, borderRadius: 12, justifyContent: 'center', alignItems: 'center', elevation: 2 },
topTitle: { fontSize: 18, fontWeight: '800' },
scrollContent: { paddingHorizontal: 20, paddingBottom: 40 },
- profileHeader: { alignItems: 'center', marginVertical: 20 },
+ profileHeader: { alignItems: 'center', marginVertical: 15 },
avatarContainer: { padding: 6, borderRadius: 100, borderWidth: 2, borderStyle: 'dashed' },
- avatar: { width: 70, height: 70, borderRadius: 35, alignItems: 'center', justifyContent: 'center', elevation: 4 },
- avatarLetter: { color: '#fff', fontSize: 28, fontWeight: '800' },
- userName: { fontSize: 20, fontWeight: '800', marginTop: 12 },
- userRole: { fontSize: 13, fontWeight: '500' },
+ avatar: { width: 80, height: 80, borderRadius: 40, alignItems: 'center', justifyContent: 'center', elevation: 4 },
+ avatarLetter: { color: '#fff', fontSize: 32, fontWeight: '800' },
+ userName: { fontSize: 22, fontWeight: '900', marginTop: 12 },
+ userRole: { fontSize: 14, fontWeight: '600' },
+ sectionTitle: { fontSize: 11, fontWeight: '900', textTransform: 'uppercase', letterSpacing: 1.2, marginLeft: 10, marginBottom: 10, marginTop: 10 },
card: { borderRadius: 24, padding: 20, elevation: 2, shadowColor: '#000', shadowOpacity: 0.05, shadowRadius: 10 },
- statsGrid: { flexDirection: 'row', justifyContent: 'space-around' },
- statBox: { alignItems: 'center' },
- statValor: { fontSize: 18, fontWeight: '800' },
- statLabel: { fontSize: 10, fontWeight: '700', textTransform: 'uppercase' },
- progressBarBase: { height: 8, borderRadius: 4, overflow: 'hidden' },
- progressBarFill: { height: '100%', borderRadius: 4 },
- progressText: { fontSize: 10, textAlign: 'center', marginTop: 8, fontWeight: '700' },
inputWrapper: { marginBottom: 15 },
inputLabel: { fontSize: 10, fontWeight: '800', textTransform: 'uppercase', marginBottom: 6, marginLeft: 4 },
inputContainer: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 10, height: 50, borderRadius: 16, borderWidth: 1.5 },
diff --git a/app/Professor/Alunos/CriarAluno.tsx b/app/Professor/Alunos/CriarAluno.tsx
index 074c802..44fd7b8 100644
--- a/app/Professor/Alunos/CriarAluno.tsx
+++ b/app/Professor/Alunos/CriarAluno.tsx
@@ -1,7 +1,7 @@
// app/Professor/Alunos/CriarAluno.tsx
import { Ionicons } from '@expo/vector-icons';
import { useRouter } from 'expo-router';
-import { useMemo, useState } from 'react';
+import { useEffect, useMemo, useState } from 'react';
import {
ActivityIndicator,
Alert,
@@ -18,21 +18,49 @@ import { SafeAreaView } from 'react-native-safe-area-context';
import { useTheme } from '../../../themecontext';
import { supabase } from '../../lib/supabase';
+// Função para calcular idade automaticamente
+const calcularIdade = (data: string): string => {
+ if (!data || data.length < 10) return '';
+ const hoje = new Date();
+ const nascimento = new Date(data);
+ let idade = hoje.getFullYear() - nascimento.getFullYear();
+ const m = hoje.getMonth() - nascimento.getMonth();
+ if (m < 0 || (m === 0 && hoje.getDate() < nascimento.getDate())) idade--;
+ return idade >= 0 ? idade.toString() : '';
+};
+
const CriarAluno = () => {
const { isDarkMode } = useTheme();
const router = useRouter();
const [loading, setLoading] = useState(false);
- // ESTADOS
+ // ESTADOS DE LOGIN
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
+ const [tipo, setTipo] = useState<'aluno' | 'professor' | 'empresa'>('aluno');
+
+ // ESTADOS DE PERFIL (Comuns)
const [nome, setNome] = useState('');
const [residencia, setResidencia] = useState('');
const [telefone, setTelefone] = useState('');
+ const [dataNascimento, setDataNascimento] = useState(''); // Formato AAAA-MM-DD
+ const [idade, setIdade] = useState('');
+
+ // ESTADOS ESPECÍFICOS
const [ano, setAno] = useState('');
- const [nEscola, setNEscola] = useState('');
- const [curso, setCurso] = useState('');
- const [tipo, setTipo] = useState<'aluno' | 'professor' | 'empresa'>('aluno');
+ const [nEscola, setNEscola] = useState('');
+ const [curso, setCurso] = useState('');
+ const [setor, setSetor] = useState('');
+
+ // CAMPOS PARA EMPRESA (Tutor)
+ const [tutorNome, setTutorNome] = useState('');
+ const [tutorTelefone, setTutorTelefone] = useState('');
+
+ // Atualiza idade sempre que a data de nascimento mudar
+ useEffect(() => {
+ const novaIdade = calcularIdade(dataNascimento);
+ if (novaIdade) setIdade(novaIdade);
+ }, [dataNascimento]);
const cores = useMemo(() => ({
fundo: isDarkMode ? '#0A0A0A' : '#FFFFFF',
@@ -42,50 +70,60 @@ const CriarAluno = () => {
azul: '#2390a6',
laranja: '#E38E00',
borda: isDarkMode ? '#2D2D2D' : '#E2E8F0',
+ placeholder: isDarkMode ? '#555' : '#A0AEC0'
}), [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.");
+ const emailLimpo = email.trim();
+
+ if (!emailLimpo || !password || !nome) {
+ Alert.alert("Atenção", "Obrigatório: Email, Password e Nome.");
return;
}
setLoading(true);
try {
- // 1. Criar Utilizador no Auth
+ // 1. Criar Auth User
+ // IMPORTANTE: Se o "Confirm Email" estiver ativo no Supabase,
+ // o signUp não inicia sessão automaticamente.
const { data: authData, error: authError } = await supabase.auth.signUp({
- email,
+ email: emailLimpo,
password,
- options: { data: { nome, tipo } }
+ options: {
+ data: { nome, tipo },
+ emailRedirectTo: undefined
+ }
});
if (authError) throw authError;
const user = authData.user;
- if (!user) throw new Error("Erro ao gerar ID.");
+
+ if (!user) {
+ Alert.alert("Verificação", "Utilizador criado. Verifique o email para ativar a conta.");
+ router.back();
+ return;
+ }
- // 2. Tabela 'profiles'
+ // 2. Inserir em PROFILES
const { error: profileError } = await supabase
.from('profiles')
- .upsert({
+ .insert([{
id: user.id,
nome,
- email,
+ email: emailLimpo,
residencia,
telefone,
- n_escola: tipo === 'aluno' ? nEscola : null,
- curso: tipo === 'aluno' ? curso.toUpperCase() : null,
- tipo
- });
+ idade: idade ? parseInt(idade) : null,
+ data_nascimento: dataNascimento || null,
+ tipo,
+ n_escola: tipo !== 'professor' ? nEscola : null,
+ curso: tipo === 'aluno' ? curso : (tipo === 'professor' ? curso : setor)
+ }]);
if (profileError) throw profileError;
- // 3. Tabela 'alunos' (SÓ SE FOR ALUNO)
+ // 3. Inserir na tabela específica de ALUNOS
if (tipo === 'aluno') {
const { error: alunoError } = await supabase
.from('alunos')
@@ -93,19 +131,32 @@ const CriarAluno = () => {
id: user.id,
nome,
n_escola: nEscola,
- ano: parseInt(ano),
+ ano: ano ? parseInt(ano) : null,
turma_curso: curso.toUpperCase()
}]);
if (alunoError) throw alunoError;
}
- Alert.alert("Sucesso", `${tipo.toUpperCase()} criado com sucesso!`, [
- { text: "OK", onPress: () => router.back() }
- ]);
+ // 4. Se for EMPRESA
+ if (tipo === 'empresa') {
+ const { error: empresaError } = await supabase
+ .from('empresas')
+ .insert([{
+ nome,
+ nif: nEscola,
+ setor,
+ tutor_nome: tutorNome,
+ tutor_telefone: tutorTelefone,
+ user_id: user.id
+ }]);
+ }
+
+ Alert.alert("Sucesso", "Novo registo concluído com sucesso!");
+ router.back();
} catch (err: any) {
+ Alert.alert("Erro ao criar conta", err.message);
console.error(err);
- Alert.alert("Erro no Registo", err.message);
} finally {
setLoading(false);
}
@@ -115,79 +166,118 @@ const CriarAluno = () => {
+
+
+ router.back()} style={[styles.backBtn, { borderColor: cores.borda }]}>
+
+
+ Novo Registo
+
+
-
- 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)}
+
+ {item.toUpperCase()}
))}
- Dados de Acesso
+
- Informação Geral
+
+
+
+
+
+ {idade ? `${idade} anos` : 'Idade'}
+
+
+
+
-
- {/* CAMPOS ESPECÍFICOS PARA ALUNO */}
+ {/* CAMPOS DINÂMICOS */}
{tipo === 'aluno' && (
<>
- Dados Escolares
+
+ >
+ )}
+
+ {tipo === 'professor' && (
+ <>
+
+
+ >
+ )}
+
+ {tipo === 'empresa' && (
+ <>
+
+
+
+
+
+
+
>
)}
@@ -195,10 +285,10 @@ const CriarAluno = () => {
- {loading ? : REGISTAR {tipo.toUpperCase()}}
+ {loading ? : REGISTAR NO SISTEMA}
@@ -208,20 +298,22 @@ const CriarAluno = () => {
);
};
+const SectionHeader = ({ title, cores }: any) => (
+ {title}
+);
+
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 },
+ scroll: { padding: 24, paddingBottom: 80 },
+ header: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 24, paddingVertical: 15, gap: 15 },
+ backBtn: { width: 45, height: 45, borderRadius: 12, borderWidth: 1, justifyContent: 'center', alignItems: 'center' },
+ title: { fontSize: 24, fontWeight: '900', letterSpacing: -0.5 },
+ sectionTitle: { fontSize: 11, fontWeight: '900', marginTop: 25, marginBottom: 10, textTransform: 'uppercase', letterSpacing: 1 },
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' },
+ selectorBtn: { flex: 1, height: 48, borderRadius: 12, borderWidth: 1, justifyContent: 'center', alignItems: 'center' },
+ form: { gap: 10 },
+ input: { height: 55, borderRadius: 16, borderWidth: 1, paddingHorizontal: 18, fontSize: 15, fontWeight: '600' },
+ submitBtn: { height: 62, borderRadius: 20, marginTop: 40, justifyContent: 'center', alignItems: 'center', elevation: 4, shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.2, shadowRadius: 8 },
+ submitBtnText: { color: '#fff', fontSize: 16, fontWeight: '900', letterSpacing: 1 },
});
export default CriarAluno;
\ No newline at end of file
diff --git a/app/Professor/Alunos/DetalhesAluno.tsx b/app/Professor/Alunos/DetalhesAluno.tsx
index 5a17bb1..d5e1ac4 100644
--- a/app/Professor/Alunos/DetalhesAluno.tsx
+++ b/app/Professor/Alunos/DetalhesAluno.tsx
@@ -4,11 +4,14 @@ import { useLocalSearchParams, useRouter } from 'expo-router';
import { memo, useEffect, useMemo, useState } from 'react';
import {
ActivityIndicator,
+ Alert,
Linking,
+ Modal,
ScrollView,
StatusBar,
StyleSheet,
Text,
+ TextInput,
TouchableOpacity,
View
} from 'react-native';
@@ -16,75 +19,146 @@ import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'
import { useTheme } from '../../../themecontext';
import { supabase } from '../../lib/supabase';
+// --- UTILITÁRIOS ---
+const calcularIdade = (dataNascimento: string) => {
+ if (!dataNascimento) return null;
+ const hoje = new Date();
+ const nascimento = new Date(dataNascimento);
+ let idade = hoje.getFullYear() - nascimento.getFullYear();
+ const m = hoje.getMonth() - nascimento.getMonth();
+ if (m < 0 || (m === 0 && hoje.getDate() < nascimento.getDate())) {
+ idade--;
+ }
+ return idade;
+};
+
+// --- TIPAGENS ---
+interface AlunoEditForm {
+ nome: string;
+ n_escola: string;
+ turma_curso: string;
+ telefone: string;
+ residencia: string;
+ data_nascimento: string;
+ email: string;
+}
+
const DetalhesAlunos = memo(() => {
const router = useRouter();
const params = useLocalSearchParams();
const { isDarkMode } = useTheme();
const insets = useSafeAreaInsets();
- const azulEPVC = '#2390a6';
- const laranjaEPVC = '#E38E00';
+ const alunoId = typeof params.alunoId === 'string' ? params.alunoId : null;
+ const [aluno, setAluno] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [modalVisible, setModalVisible] = useState(false);
+ const [saving, setSaving] = useState(false);
+
+ const [editForm, setEditForm] = useState({
+ nome: '',
+ n_escola: '',
+ turma_curso: '',
+ telefone: '',
+ residencia: '',
+ data_nascimento: '',
+ email: ''
+ });
const cores = useMemo(() => ({
fundo: isDarkMode ? '#0A0A0A' : '#FFFFFF',
card: isDarkMode ? '#161618' : '#F8FAFC',
texto: isDarkMode ? '#F8FAFC' : '#1A365D',
secundario: isDarkMode ? '#94A3B8' : '#718096',
- azul: azulEPVC,
- laranja: laranjaEPVC,
+ azul: '#2390a6',
+ laranja: '#E38E00',
azulSuave: isDarkMode ? 'rgba(35, 144, 166, 0.12)' : '#F0F9FA',
borda: isDarkMode ? '#2D2D2D' : '#E2E8F0',
- verde: '#10B981',
+ inputFundo: isDarkMode ? '#252525' : '#EDF2F7'
}), [isDarkMode]);
- const alunoId = typeof params.alunoId === 'string' ? params.alunoId : null;
- const [aluno, setAluno] = useState(null);
- const [loading, setLoading] = useState(true);
-
const fetchAluno = async () => {
+ if (!alunoId) return;
try {
setLoading(true);
- const { data, error } = await supabase
+
+ const { data: alunoData, error: alunoError } = await supabase
.from('alunos')
- .select(`
- id, nome, n_escola, turma_curso,
- profiles!alunos_profile_id_fkey ( email, telefone, residencia, idade ),
- estagios (
- id, data_inicio, data_fim, horas_diarias,
- empresas ( nome, tutor_nome, tutor_telefone )
- )
- `)
+ .select(`*, estagios(*, empresas(*))`)
+ .eq('id', alunoId)
+ .single();
+
+ if (alunoError) throw alunoError;
+
+ const { data: perfilData } = await supabase
+ .from('profiles')
+ .select('*')
.eq('id', alunoId)
.single();
- if (error) throw error;
-
let listaHorarios: string[] = [];
- if (data?.estagios?.[0]?.id) {
+ const estagioAtivo = Array.isArray(alunoData.estagios) ? alunoData.estagios[0] : alunoData.estagios;
+
+ if (estagioAtivo?.id) {
const { data: hData } = await supabase
.from('horarios_estagio')
- .select('hora_inicio, hora_fim')
- .eq('estagio_id', data.estagios[0].id);
-
+ .select('*')
+ .eq('estagio_id', estagioAtivo.id);
if (hData) {
- listaHorarios = hData.map(h => `${h.hora_inicio?.slice(0, 5)} - ${h.hora_fim?.slice(0, 5)}`);
+ listaHorarios = hData.map((h: any) => `${h.hora_inicio?.slice(0, 5)}h - ${h.hora_fim?.slice(0, 5)}h`);
}
}
- setAluno({
- ...data,
- perfil: Array.isArray(data.profiles) ? data.profiles[0] : data.profiles,
- estagio: Array.isArray(data.estagios) ? data.estagios[0] : data.estagios,
- horarios: listaHorarios
+ const infoCompleta = { ...alunoData, perfil: perfilData, estagio: estagioAtivo, horarios: listaHorarios };
+ setAluno(infoCompleta);
+
+ setEditForm({
+ nome: alunoData.nome || '',
+ n_escola: String(alunoData.n_escola || ''),
+ turma_curso: alunoData.turma_curso || '',
+ telefone: perfilData?.telefone || '',
+ residencia: perfilData?.residencia || '',
+ data_nascimento: perfilData?.data_nascimento || '',
+ email: perfilData?.email || ''
});
+
} catch (err: any) {
- console.log('Erro:', err.message);
+ console.error(err);
+ Alert.alert("Erro", "Falha ao carregar dados.");
} finally {
setLoading(false);
}
};
- useEffect(() => { if (alunoId) fetchAluno(); }, [alunoId]);
+ const handleUpdate = async () => {
+ try {
+ setSaving(true);
+ const { error: err1 } = await supabase.from('alunos').update({
+ nome: editForm.nome,
+ n_escola: editForm.n_escola,
+ turma_curso: editForm.turma_curso
+ }).eq('id', alunoId);
+
+ const { error: err2 } = await supabase.from('profiles').update({
+ telefone: editForm.telefone,
+ residencia: editForm.residencia,
+ data_nascimento: editForm.data_nascimento,
+ email: editForm.email
+ }).eq('id', alunoId);
+
+ if (err1 || err2) throw new Error("Erro na gravação dos dados");
+
+ Alert.alert("Sucesso", "Dados atualizados!");
+ setModalVisible(false);
+ fetchAluno();
+ } catch (err: any) {
+ Alert.alert("Erro", "Não foi possível guardar. Verifica a ligação.");
+ } finally {
+ setSaving(false);
+ }
+ };
+
+ useEffect(() => { fetchAluno(); }, [alunoId]);
if (loading) return (
@@ -97,20 +171,19 @@ const DetalhesAlunos = memo(() => {
- {/* HEADER CLEAN */}
+ {/* HEADER */}
router.back()} style={[styles.btnAction, { borderColor: cores.borda }]}>
Ficha do Aluno
-
-
+ setModalVisible(true)} style={[styles.btnAction, { borderColor: cores.borda }]}>
+
- {/* PERFIL MINIMALISTA */}
{aluno?.nome?.charAt(0)}
@@ -121,17 +194,27 @@ const DetalhesAlunos = memo(() => {
- {/* DADOS PESSOAIS CARD */}
+ {/* INFORMAÇÕES PESSOAIS */}
- Linking.openURL(`mailto:${aluno?.perfil?.email}`)} />
- Linking.openURL(`tel:${aluno?.perfil?.telefone}`)} />
+
+ Linking.openURL(`mailto:${aluno.perfil.email}`) : null}
+ />
+ Linking.openURL(`tel:${aluno.perfil.telefone}`) : null}
+ />
- {/* SECÇÃO ESTÁGIO */}
+ {/* ESTÁGIO */}
Plano de Estágio
@@ -143,70 +226,102 @@ const DetalhesAlunos = memo(() => {
ATIVO
-
- {aluno?.estagio?.empresas?.nome || 'Empresa Indefinida'}
+ {aluno?.estagio?.empresas?.nome || 'Empresa'}
- TUTOR NA EMPRESA
- {aluno?.estagio?.empresas?.tutor_nome || 'Não definido'}
+ TUTOR
+ {aluno?.estagio?.empresas?.tutor_nome || 'N/A'}
{aluno?.estagio?.empresas?.tutor_telefone || '-'}
HORÁRIOS
- {aluno?.horarios && aluno.horarios.length > 0 ? (
- aluno.horarios.map((h: string, i: number) => (
- {h}
- ))
- ) : (
- Não registado
- )}
+ {aluno?.horarios?.length > 0 ? (
+ aluno.horarios.map((h: string, i: number) => {h})
+ ) : Não definido}
-
-
- {/* PERÍODO CORRIGIDO: DUAS COLUNAS COM ÍCONE */}
-
-
- INÍCIO
- {aluno?.estagio?.data_inicio}
-
+ INÍCIO
+ {aluno?.estagio?.data_inicio || '-'}
-
- FIM PREVISTO
- {aluno?.estagio?.data_fim}
-
-
+ FIM
+ {aluno?.estagio?.data_fim || '-'}
) : (
-
Sem estágio atribuído
)}
+
+ {/* MODAL DE EDIÇÃO */}
+
+
+
+
+ Editar Aluno
+ setModalVisible(false)}>
+
+
+
+
+
+ setEditForm({...editForm, nome: t})} cores={cores} />
+ setEditForm({...editForm, n_escola: t})} cores={cores} keyboard="numeric" />
+ setEditForm({...editForm, turma_curso: t})} cores={cores} />
+ setEditForm({...editForm, email: t})} cores={cores} keyboard="email-address" />
+ setEditForm({...editForm, telefone: t})} cores={cores} keyboard="phone-pad" />
+ setEditForm({...editForm, data_nascimento: t})} cores={cores} keyboard="numeric" />
+ setEditForm({...editForm, residencia: t})} cores={cores} />
+
+
+ {saving ? : Guardar Alterações}
+
+
+
+
+
+
);
});
+// --- COMPONENTES AUXILIARES ---
+interface EditInputProps {
+ label: string;
+ value: string;
+ onChange: (t: string) => void;
+ cores: any;
+ keyboard?: any;
+}
+
+const EditInput = ({ label, value, onChange, cores, keyboard = "default" }: EditInputProps) => (
+
+ {label}
+
+
+);
+
const DetailRow = ({ icon, label, value, cores, ultimo, onPress }: any) => (
-
-
+
+
{label}
{value || '-'}
- {onPress && }
+ {onPress && }
);
@@ -220,34 +335,37 @@ const styles = StyleSheet.create({
avatar: { width: 65, height: 65, borderRadius: 20, justifyContent: 'center', alignItems: 'center' },
avatarTxt: { fontSize: 26, fontWeight: '900' },
alunoNome: { fontSize: 22, fontWeight: '900', letterSpacing: -0.5 },
- alunoCurso: { fontSize: 13, fontWeight: '800', textTransform: 'uppercase' },
+ alunoCurso: { fontSize: 13, fontWeight: '800' },
infoCard: { borderRadius: 25, borderWidth: 1, paddingHorizontal: 20, paddingVertical: 5, marginBottom: 30 },
- row: { flexDirection: 'row', alignItems: 'center', paddingVertical: 15 },
- rowLabel: { fontSize: 10, fontWeight: '800', textTransform: 'uppercase', marginBottom: 2 },
- rowValue: { fontSize: 15, fontWeight: '700' },
+ row: { flexDirection: 'row', alignItems: 'center', paddingVertical: 12 },
+ rowLabel: { fontSize: 9, fontWeight: '800', textTransform: 'uppercase', marginBottom: 2 },
+ rowValue: { fontSize: 14, fontWeight: '700' },
sectionHeader: { flexDirection: 'row', alignItems: 'center', marginBottom: 20 },
- sectionTitle: { fontSize: 12, fontWeight: '900', textTransform: 'uppercase', letterSpacing: 1, marginRight: 15 },
- sectionLine: { flex: 1, height: 1, opacity: 0.3 },
- estagioCard: { padding: 25, borderRadius: 32, elevation: 8, shadowColor: '#000', shadowOpacity: 0.2, shadowRadius: 10 },
- estagioHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 15 },
- statusBadge: { backgroundColor: 'rgba(255,255,255,0.2)', paddingHorizontal: 10, paddingVertical: 4, borderRadius: 8 },
- statusText: { color: '#fff', fontSize: 10, fontWeight: '900' },
- empresaNome: { color: '#fff', fontSize: 24, fontWeight: '900', marginBottom: 20 },
+ sectionTitle: { fontSize: 11, fontWeight: '900', textTransform: 'uppercase', marginRight: 15 },
+ sectionLine: { flex: 1, height: 1, opacity: 0.2 },
+ estagioCard: { padding: 25, borderRadius: 30 },
+ estagioHeader: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 15 },
+ statusBadge: { backgroundColor: 'rgba(255,255,255,0.2)', paddingHorizontal: 8, paddingVertical: 3, borderRadius: 6 },
+ statusText: { color: '#fff', fontSize: 9, fontWeight: '900' },
+ empresaNome: { color: '#fff', fontSize: 22, fontWeight: '900', marginBottom: 15 },
tutorInfo: { marginBottom: 15 },
- miniLabel: { color: 'rgba(255,255,255,0.5)', fontSize: 9, fontWeight: '900', marginBottom: 4 },
- tutorNome: { color: '#fff', fontSize: 16, fontWeight: '800' },
- tutorTel: { color: 'rgba(255,255,255,0.8)', fontSize: 13, fontWeight: '600' },
- horarioBox: { marginBottom: 20, backgroundColor: 'rgba(255,255,255,0.1)', padding: 12, borderRadius: 15 },
- horarioTxt: { color: '#fff', fontSize: 14, fontWeight: '700' },
- estagioDivider: { height: 1, backgroundColor: 'rgba(255,255,255,0.1)', marginBottom: 20 },
-
- // ESTILOS DO RODAPÉ CORRIGIDOS
+ miniLabel: { color: 'rgba(255,255,255,0.5)', fontSize: 8, fontWeight: '900' },
+ tutorNome: { color: '#fff', fontSize: 15, fontWeight: '800' },
+ tutorTel: { color: 'rgba(255,255,255,0.7)', fontSize: 12 },
+ horarioBox: { backgroundColor: 'rgba(255,255,255,0.1)', padding: 12, borderRadius: 15, marginBottom: 15 },
+ horarioTxt: { color: '#fff', fontSize: 13, fontWeight: '700' },
estagioFooter: { flexDirection: 'row', justifyContent: 'space-between' },
- periodoCol: { flex: 1, flexDirection: 'row', alignItems: 'center' },
- footerVal: { color: '#fff', fontSize: 14, fontWeight: '800' },
-
- noEstagio: { padding: 30, borderRadius: 25, borderWidth: 1, borderStyle: 'dashed', alignItems: 'center', gap: 10 },
- noEstagioTxt: { fontWeight: '700', fontSize: 14 }
+ periodoCol: { flex: 1 },
+ footerVal: { color: '#fff', fontSize: 13, fontWeight: '800' },
+ noEstagio: { padding: 20, borderRadius: 20, borderWidth: 1, borderStyle: 'dashed', alignItems: 'center' },
+ noEstagioTxt: { fontWeight: '700', fontSize: 14 },
+ modalContainer: { flex: 1, backgroundColor: 'rgba(0,0,0,0.6)', justifyContent: 'flex-end' },
+ modalContent: { borderTopLeftRadius: 30, borderTopRightRadius: 30, padding: 25, maxHeight: '90%' },
+ modalHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 20 },
+ modalTitle: { fontSize: 20, fontWeight: '900' },
+ input: { borderRadius: 12, padding: 12, fontSize: 15, fontWeight: '600' },
+ btnSave: { borderRadius: 15, padding: 16, alignItems: 'center', marginTop: 20, marginBottom: 40 },
+ btnSaveTxt: { color: '#fff', fontWeight: '900', fontSize: 16 }
});
export default DetalhesAlunos;
\ No newline at end of file
diff --git a/app/Professor/Alunos/ListaAlunos.tsx b/app/Professor/Alunos/ListaAlunos.tsx
index 2a01e13..6565cc2 100644
--- a/app/Professor/Alunos/ListaAlunos.tsx
+++ b/app/Professor/Alunos/ListaAlunos.tsx
@@ -25,7 +25,6 @@ export interface Aluno {
nome: string;
n_escola: string;
turma: string;
- tem_estagio?: boolean;
}
interface TurmaAgrupada {
@@ -48,19 +47,15 @@ const ListaAlunosProfessor = memo(() => {
const [alunoParaEliminar, setAlunoParaEliminar] = useState(null);
const [isDeleting, setIsDeleting] = useState(false);
- const azulEPVC = '#2390a6';
- const laranjaEPVC = '#E38E00';
-
const cores = useMemo(() => ({
fundo: isDarkMode ? '#0A0A0A' : '#FFFFFF',
card: isDarkMode ? '#161618' : '#F8FAFC',
texto: isDarkMode ? '#F8FAFC' : '#1A365D',
secundario: isDarkMode ? '#94A3B8' : '#718096',
- azul: azulEPVC,
- laranja: laranjaEPVC,
+ azul: '#2390a6',
+ laranja: '#E38E00',
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]);
@@ -70,7 +65,7 @@ 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`)
.order('ano', { ascending: false })
.order('nome', { ascending: true });
@@ -85,13 +80,15 @@ const ListaAlunosProfessor = memo(() => {
nome: item.nome,
n_escola: item.n_escola,
turma: nomeTurma,
- tem_estagio: item.estagios && item.estagios.length > 0
});
});
- 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(err);
+ console.error("Erro ao carregar lista:", err);
} finally {
setLoading(false);
setRefreshing(false);
@@ -100,16 +97,37 @@ const ListaAlunosProfessor = memo(() => {
const confirmarEliminacao = async () => {
if (!alunoParaEliminar) return;
+
try {
setIsDeleting(true);
- const { error } = await supabase.from('alunos').delete().eq('id', alunoParaEliminar.id);
+
+ // APAGAR NO PROFILES (O Cascade do SQL trata das tabelas 'alunos' e 'estagios')
+ const { data, error } = await supabase
+ .from('profiles')
+ .delete()
+ .eq('id', alunoParaEliminar.id)
+ .select();
+
if (error) throw error;
-
+
+ // Se o data vier vazio, o RLS bloqueou o delete no profiles
+ if (!data || data.length === 0) {
+ throw new Error("O servidor recusou apagar o perfil. Verifica se as políticas RLS na tabela 'profiles' permitem DELETE.");
+ }
+
+ // Sucesso no banco -> Atualizar UI local
+ setTurmas(prev => prev.map(turma => ({
+ ...turma,
+ alunos: turma.alunos.filter(a => a.id !== alunoParaEliminar.id)
+ })).filter(t => t.alunos.length > 0));
+
setShowDeleteModal(false);
setAlunoParaEliminar(null);
- fetchAlunos();
- } catch (err) {
- Alert.alert("Erro", "Não foi possível eliminar o aluno.");
+ Alert.alert("Sucesso", "Aluno e todos os dados vinculados foram eliminados.");
+
+ } catch (err: any) {
+ console.error("ERRO AO APAGAR:", err);
+ Alert.alert("Erro Crítico", err.message);
} finally {
setIsDeleting(false);
}
@@ -152,13 +170,13 @@ const ListaAlunosProfessor = memo(() => {
- {/* SEARCH */}
+ {/* PESQUISA */}
{
{aluno.nome}
Nº {aluno.n_escola}
-
+
))}
@@ -207,18 +225,18 @@ const ListaAlunosProfessor = memo(() => {
/>
)}
- {/* MODAL DE ELIMINAÇÃO CUSTOMIZADO */}
+ {/* MODAL DE ELIMINAÇÃO */}
-
+
- Eliminar Aluno?
+ Eliminar Permanentemente?
- Estás prestes a apagar {alunoParaEliminar?.nome}.
- Esta ação é irreversível e **vai dar merda** se não tiveres a certeza!
+ Estás prestes a apagar o perfil de {alunoParaEliminar?.nome}.
+ Isto removerá o acesso à app, dados escolares e estágios. **Vai dar merda** se apagares por engano!
@@ -237,7 +255,7 @@ const ListaAlunosProfessor = memo(() => {
{isDeleting ? (
) : (
- Eliminar
+ Eliminar Tudo
)}
@@ -245,9 +263,10 @@ const ListaAlunosProfessor = memo(() => {
+ {/* BOTÃO FLUTUANTE */}
router.push('/Professor/Alunos/CriarAluno')} // Altera esta linha
+ onPress={() => router.push('/Professor/Alunos/CriarAluno')}
>
Novo Aluno
@@ -277,13 +296,12 @@ const styles = StyleSheet.create({
alunoInfo: { flex: 1, marginLeft: 15 },
alunoNome: { fontSize: 16, fontWeight: '800' },
idText: { fontSize: 13, fontWeight: '600' },
- fab: { position: 'absolute', right: 24, flexDirection: 'row', alignItems: 'center', paddingHorizontal: 22, paddingVertical: 16, borderRadius: 22 },
+ fab: { position: 'absolute', right: 24, flexDirection: 'row', alignItems: 'center', paddingHorizontal: 22, paddingVertical: 16, borderRadius: 22, elevation: 8, shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.3, shadowRadius: 5 },
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 },
+ modalTitle: { fontSize: 20, fontWeight: '900', marginBottom: 10, textAlign: 'center' },
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' },
diff --git a/app/Professor/PerfilProf.tsx b/app/Professor/PerfilProf.tsx
index 23be623..a4b8d3c 100644
--- a/app/Professor/PerfilProf.tsx
+++ b/app/Professor/PerfilProf.tsx
@@ -49,17 +49,13 @@ export default function PerfilProfessor() {
]).start(() => setAlertConfig(null));
}, []);
- // Cores EPVC
- const azulEPVC = '#2390a6';
- const laranjaEPVC = '#E38E00';
-
const cores = useMemo(() => ({
fundo: isDarkMode ? '#0A0A0A' : '#FFFFFF',
card: isDarkMode ? '#161618' : '#F8FAFC',
texto: isDarkMode ? '#F8FAFC' : '#1A365D',
secundario: isDarkMode ? '#94A3B8' : '#718096',
- azul: azulEPVC,
- laranja: laranjaEPVC,
+ azul: '#2390a6',
+ laranja: '#E38E00',
azulSuave: isDarkMode ? 'rgba(35, 144, 166, 0.15)' : '#F0F9FA',
vermelhoSuave: isDarkMode ? 'rgba(239, 68, 68, 0.15)' : '#FFF5F5',
vermelho: '#EF4444',
@@ -72,18 +68,36 @@ export default function PerfilProfessor() {
async function carregarPerfil() {
try {
setLoading(true);
- const { data: { user } } = await supabase.auth.getUser();
- if (user) {
- const { data, error } = await supabase
- .from('profiles')
- .select('*')
- .eq('id', user.id)
- .single();
- if (error) throw error;
- setPerfil(data);
+
+ // 1. Obter a sessão atual de forma limpa
+ const { data: { session }, error: sessionError } = await supabase.auth.getSession();
+
+ if (sessionError || !session) {
+ router.replace('/');
+ return;
}
+
+ // 2. Buscar o perfil filtrando pelo ID da sessão e garantindo que o tipo é PROFESSOR
+ // Isso impede que, se a sessão mudar para aluno, os dados apareçam aqui
+ const { data, error } = await supabase
+ .from('profiles')
+ .select('*')
+ .eq('id', session.user.id)
+ .eq('tipo', 'professor') // Filtro de segurança
+ .single();
+
+ if (error || !data) {
+ // Se for um aluno a tentar aceder a esta página de professor, expulsamos
+ showAlert('Acesso negado ou perfil não encontrado.', 'error');
+ await supabase.auth.signOut();
+ router.replace('/');
+ return;
+ }
+
+ setPerfil(data);
} catch (error: any) {
- showAlert('Não foi possível carregar os dados.', 'error');
+ console.error(error);
+ showAlert('Erro ao carregar dados.', 'error');
} finally {
setLoading(false);
}
@@ -148,7 +162,7 @@ export default function PerfilProfessor() {
router.back()}>
- Perfil
+ O Meu Perfil
editando ? guardarPerfil() : setEditando(true)}
@@ -164,34 +178,29 @@ export default function PerfilProfessor() {
{perfil?.nome?.charAt(0).toUpperCase()}
- {editando && (
-
-
-
- )}
{perfil?.nome}
- {perfil?.curso || 'Professor'}
+ PROFESSOR • {perfil?.curso}
- setPerfil(prev => prev ? { ...prev, nome: v } : null)} cores={cores} />
- setPerfil(prev => prev ? { ...prev, curso: v } : null)} cores={cores} />
-
+
- setPerfil(prev => prev ? { ...prev, n_escola: v } : null)} cores={cores} />
- setPerfil(prev => prev ? { ...prev, telefone: v } : null)} keyboardType="phone-pad" cores={cores} />
@@ -203,7 +212,7 @@ export default function PerfilProfessor() {
- Alterar palavra-passe
+ Segurança da Conta
@@ -212,13 +221,13 @@ export default function PerfilProfessor() {
- Terminar Sessão
+ Sair do Sistema
{editando && (
{ setEditando(false); carregarPerfil(); }}>
- Cancelar Alterações
+ Reverter Alterações
)}
@@ -231,7 +240,7 @@ export default function PerfilProfessor() {
const ModernInput = ({ label, icon, cores, editable, ...props }: any) => (
{label}
-
+
@@ -250,12 +259,11 @@ const styles = StyleSheet.create({
scrollContent: { paddingHorizontal: 24, paddingBottom: 50 },
profileHeader: { alignItems: 'center', marginVertical: 35 },
avatarBorder: { padding: 4, borderRadius: 100, borderWidth: 2, position: 'relative' },
- avatar: { width: 90, height: 90, borderRadius: 45, alignItems: 'center', justifyContent: 'center', elevation: 8, shadowColor: '#000', shadowOpacity: 0.2, shadowRadius: 10 },
+ avatar: { width: 90, height: 90, borderRadius: 45, alignItems: 'center', justifyContent: 'center' },
avatarLetter: { color: '#fff', fontSize: 36, fontWeight: '900' },
- editBadge: { position: 'absolute', bottom: 0, right: 0, width: 28, height: 28, borderRadius: 14, justifyContent: 'center', alignItems: 'center', borderWidth: 3, borderColor: '#fff' },
userName: { fontSize: 24, fontWeight: '900', marginTop: 15, letterSpacing: -0.5 },
roleBadge: { paddingHorizontal: 12, paddingVertical: 4, borderRadius: 8, marginTop: 8 },
- userRole: { fontSize: 12, fontWeight: '800', textTransform: 'uppercase', letterSpacing: 0.5 },
+ userRole: { fontSize: 11, fontWeight: '800', textTransform: 'uppercase', letterSpacing: 1 },
card: { borderRadius: 28, padding: 24, marginBottom: 20, borderWidth: 1 },
inputWrapper: { marginBottom: 18 },
inputLabel: { fontSize: 10, fontWeight: '900', textTransform: 'uppercase', marginBottom: 8, marginLeft: 4, letterSpacing: 0.5 },