From 0b544677a837f1885c34a8a7453b7f3e3001216d Mon Sep 17 00:00:00 2001
From: Ricardo Gomes <230413@epvc.pt>
Date: Thu, 23 Apr 2026 16:10:12 +0100
Subject: [PATCH] atualizacao2
---
app.json | 7 +
app/Aluno/perfil.tsx | 305 ++++++++++++++++---------------------------
package-lock.json | 22 ++++
package.json | 1 +
4 files changed, 140 insertions(+), 195 deletions(-)
diff --git a/app.json b/app.json
index ed4b127..4f74539 100644
--- a/app.json
+++ b/app.json
@@ -38,6 +38,13 @@
"backgroundColor": "#000000"
}
}
+ ],
+ [
+ "expo-image-picker",
+ {
+ "photosPermission": "Permitir que a aplicação aceda às tuas fotos para alterar a foto de perfil.",
+ "cameraPermission": "Permitir que a aplicação utilize a câmara para tirar uma foto de perfil."
+ }
]
],
"experiments": {
diff --git a/app/Aluno/perfil.tsx b/app/Aluno/perfil.tsx
index 93627e1..9bf0bc8 100644
--- a/app/Aluno/perfil.tsx
+++ b/app/Aluno/perfil.tsx
@@ -5,9 +5,9 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
import {
ActivityIndicator,
Animated,
- Dimensions,
KeyboardAvoidingView,
Platform,
+ ScrollView,
StatusBar,
StyleSheet,
Text,
@@ -19,8 +19,6 @@ import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'
import { useTheme } from '../../themecontext';
import { supabase } from '../lib/supabase';
-const { width } = Dimensions.get('window');
-
export default function PerfilAluno() {
const { isDarkMode } = useTheme();
const router = useRouter();
@@ -63,7 +61,6 @@ export default function PerfilAluno() {
]).start(() => setAlertConfig(null));
}, []);
- // --- LÓGICA DE FORMATAÇÃO ---
const aplicarMascaraData = (text: string) => {
const cleaned = text.replace(/\D/g, '');
let formatted = cleaned;
@@ -72,46 +69,38 @@ export default function PerfilAluno() {
return formatted;
};
- // --- CARREGAMENTO DE DADOS ---
+ // --- CARREGAMENTO DE DADOS COM JOIN DE HORÁRIOS ---
const buscarDados = async () => {
try {
setLoading(true);
const { data: { user } } = await supabase.auth.getUser();
if (!user) return;
- // Consulta à Tabela Profiles
- const { data: pData, error: pError } = await supabase
- .from('profiles')
- .select('*')
- .eq('id', user.id)
- .single();
+ const { data: pData } = await supabase.from('profiles').select('*').eq('id', user.id).single();
- if (pError) throw pError;
-
- // Consulta à Tabela Alunos
let aData = {};
- if (pData.n_escola) {
- const { data: alunoRes } = await supabase
- .from('alunos')
- .select('ano, n_escola, nome, turma_curso')
- .eq('n_escola', pData.n_escola)
- .single();
+ if (pData?.n_escola) {
+ const { data: alunoRes } = await supabase.from('alunos').select('*').eq('n_escola', pData.n_escola).single();
if (alunoRes) aData = alunoRes;
}
- // Consulta à Tabela Estágios
+ // Join com a tabela horarios_estagio
const { data: eData } = await supabase
.from('estagios')
- .select('*, empresas(nome, tutor_nome)')
+ .select(`
+ *,
+ empresas(nome, tutor_nome),
+ horarios_estagio(periodo, hora_inicio, hora_fim)
+ `)
.eq('aluno_id', user.id)
- .single();
+ .maybeSingle();
setPerfil({ ...pData, ...aData, email: user.email });
setEstagio(eData);
Animated.timing(fadeAnim, { toValue: 1, duration: 600, useNativeDriver: true }).start();
} catch (err) {
- showAlert('Erro ao sincronizar com a base de dados.', 'error');
+ showAlert('Erro ao sincronizar dados.', 'error');
} finally {
setLoading(false);
}
@@ -131,38 +120,24 @@ export default function PerfilAluno() {
}).eq('id', perfil.id);
if (error) throw error;
-
setIsEditing(false);
- showAlert('Perfil atualizado!', 'success');
+ showAlert('Perfil guardado!', 'success');
buscarDados();
} catch (e) {
- showAlert('Falha ao guardar alterações.', 'error');
+ showAlert('Falha ao guardar.', 'error');
} finally {
setSaving(false);
}
};
- if (loading) {
- return (
-
-
-
- );
- }
+ if (loading) return ;
return (
-
+
{alertConfig && (
-
+
{alertConfig.msg}
@@ -170,168 +145,109 @@ export default function PerfilAluno() {
- router.back()}
- >
+ router.back()}>
-
Ficha de Aluno
-
isEditing ? salvarDados() : setIsEditing(true)}
disabled={saving}
>
- {saving ? (
-
- ) : (
-
- )}
+ {saving ? : }
-
- {/* HEADER PERFIL */}
-
-
-
- {perfil?.nome?.charAt(0).toUpperCase()}
-
-
- {perfil?.nome}
-
-
- {perfil?.tipo?.toUpperCase()}
-
-
-
-
- {/* SECÇÃO ACADÉMICA (TABLE ALUNOS) */}
- Registo Escolar
-
-
-
-
-
-
-
-
-
-
-
-
- {/* SECÇÃO PESSOAL (TABLE PROFILES) */}
- Dados de Contacto
-
-
+
+
-
-
- setPerfil({...perfil, idade: v})} cores={cores} keyboardType="numeric" />
-
-
- setPerfil({...perfil, telefone: v})} cores={cores} keyboardType="phone-pad" maxLength={9} />
-
-
-
- setPerfil({...perfil, data_nascimento: aplicarMascaraData(v)})}
- cores={cores}
- placeholder="DD-MM-AAAA"
- />
-
- setPerfil({...perfil, residencia: v})}
- cores={cores}
- />
-
-
- {/* SECÇÃO ESTÁGIO (CORREÇÃO DE LAYOUT) */}
- {estagio && (
- <>
- Informação de Estágio
-
-
-
-
-
-
-
-
-
-
+ {/* CABEÇALHO */}
+
+
+
+ {perfil?.nome?.charAt(0).toUpperCase()}
-
- {estagio.empresas?.tutor_nome && (
-
- )}
- >
- )}
-
- {/* BOTÕES DE ACÇÃO */}
-
- router.push('/Aluno/redefenirsenha')}
- >
-
-
+ {perfil?.nome}
+
+ {perfil?.tipo?.toUpperCase()}
- Alterar Credenciais
-
-
+
- supabase.auth.signOut().then(() => router.replace('/'))}
- >
-
-
+ {/* DADOS ESCOLARES */}
+ Registo Escolar
+
+
+
+
- Terminar Sessão
-
+
+
- {isEditing && (
- { setIsEditing(false); buscarDados(); }}>
- Cancelar edições pendentes
-
+ {/* DADOS PESSOAIS */}
+ Contactos e Pessoal
+
+
+
+ setPerfil({...perfil, idade: v})} cores={cores} keyboardType="numeric" />
+ setPerfil({...perfil, telefone: v})} cores={cores} keyboardType="phone-pad" maxLength={9} />
+
+ setPerfil({...perfil, data_nascimento: aplicarMascaraData(v)})} cores={cores} placeholder="DD-MM-AAAA" />
+
+
+ {/* SECÇÃO ESTÁGIO COM LOGICA DE HORARIOS RELACIONADOS */}
+ {estagio && (
+ <>
+ Informação de Estágio
+
+
+
+
+
+
+
+
+ 0
+ ? estagio.horarios_estagio.map((h: any) =>
+ `${h.periodo}: ${h.hora_inicio.slice(0,5)}-${h.hora_fim.slice(0,5)}`
+ ).join('\n')
+ : "Não definido"
+ }
+ editable={false}
+ cores={cores}
+ multiline={true}
+ />
+
+
+
+ {estagio.empresas?.tutor_nome && }
+
+ >
)}
-
-
+
+ {/* ACÇÕES */}
+
+ router.push('/Aluno/redefenirsenha')}>
+
+ Redefinir Senha
+
+
+ supabase.auth.signOut().then(() => router.replace('/'))}>
+
+ Terminar Sessão
+
+
+
+
);
}
-// --- COMPONENTE DE INPUT CUSTOMIZADO ---
const PerfilInput = ({ label, icon, cores, editable, multiline, ...props }: any) => (
{label}
@@ -339,15 +255,16 @@ const PerfilInput = ({ label, icon, cores, editable, multiline, ...props }: any)
backgroundColor: cores.fundo,
borderColor: editable ? cores.azul : cores.borda,
height: multiline ? undefined : 52,
- minHeight: multiline ? 52 : undefined,
- paddingVertical: multiline ? 10 : 0
+ minHeight: 52,
+ paddingVertical: multiline ? 8 : 0
}]}>
-
+
@@ -358,8 +275,8 @@ const styles = StyleSheet.create({
centered: { flex: 1, justifyContent: 'center', alignItems: 'center' },
headerContainer: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 20, paddingVertical: 15 },
headerTitle: { fontSize: 19, fontWeight: '900' },
- roundBtn: { width: 48, height: 48, borderRadius: 16, justifyContent: 'center', alignItems: 'center', elevation: 2, shadowOpacity: 0.1, shadowRadius: 4 },
- scrollContainer: { paddingHorizontal: 20, paddingBottom: 50 },
+ roundBtn: { width: 48, height: 48, borderRadius: 16, justifyContent: 'center', alignItems: 'center', elevation: 2 },
+ scrollContainer: { paddingHorizontal: 20, paddingBottom: 20 },
avatarSection: { alignItems: 'center', marginVertical: 25 },
avatarFrame: { padding: 6, borderRadius: 100, borderWidth: 2, borderStyle: 'dashed' },
avatarCircle: { width: 95, height: 95, borderRadius: 48, alignItems: 'center', justifyContent: 'center', elevation: 5 },
@@ -367,19 +284,17 @@ const styles = StyleSheet.create({
profileName: { fontSize: 24, fontWeight: '900', marginTop: 15 },
badge: { paddingHorizontal: 12, paddingVertical: 4, borderRadius: 8, marginTop: 8 },
badgeText: { fontSize: 11, fontWeight: '800', letterSpacing: 1 },
- sectionHeader: { fontSize: 12, fontWeight: '900', textTransform: 'uppercase', letterSpacing: 1.5, marginLeft: 8, marginBottom: 12, marginTop: 15 },
- infoCard: { borderRadius: 28, padding: 22, elevation: 3, shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.1, shadowRadius: 10 },
- inputGroup: { marginBottom: 18 },
- inputLabel: { fontSize: 10, fontWeight: '800', textTransform: 'uppercase', marginBottom: 8, marginLeft: 4 },
+ sectionHeader: { fontSize: 11, fontWeight: '900', textTransform: 'uppercase', letterSpacing: 1.5, marginLeft: 8, marginBottom: 12, marginTop: 15 },
+ infoCard: { borderRadius: 25, padding: 20, elevation: 3, shadowOpacity: 0.1 },
+ inputGroup: { marginBottom: 15 },
+ inputLabel: { fontSize: 10, fontWeight: '800', textTransform: 'uppercase', marginBottom: 6, marginLeft: 4 },
inputContainer: { flexDirection: 'row', alignItems: 'center', borderRadius: 16, borderWidth: 1.5 },
- textInput: { flex: 1, fontSize: 15, fontWeight: '700' },
+ textInput: { flex: 1, fontSize: 14, fontWeight: '700' },
inputRow: { flexDirection: 'row', justifyContent: 'space-between' },
footer: { marginTop: 20 },
actionMenuItem: { flexDirection: 'row', alignItems: 'center', padding: 16, borderRadius: 22, elevation: 2 },
actionIcon: { width: 42, height: 42, borderRadius: 14, justifyContent: 'center', alignItems: 'center' },
actionText: { flex: 1, marginLeft: 15, fontSize: 16, fontWeight: '800' },
- cancelLink: { marginTop: 25, alignItems: 'center' },
- cancelLinkText: { fontSize: 14, fontWeight: '700', textDecorationLine: 'underline' },
- alert: { position: 'absolute', left: 20, right: 20, padding: 18, borderRadius: 20, flexDirection: 'row', alignItems: 'center', zIndex: 999, elevation: 10 },
+ alert: { position: 'absolute', left: 20, right: 20, padding: 18, borderRadius: 20, flexDirection: 'row', alignItems: 'center', zIndex: 999 },
alertText: { color: '#fff', fontWeight: '800', marginLeft: 10, flex: 1 }
});
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 3331cbb..bd47bb6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -22,6 +22,7 @@
"expo-font": "~14.0.10",
"expo-haptics": "~15.0.8",
"expo-image": "~3.0.11",
+ "expo-image-picker": "~17.0.10",
"expo-linear-gradient": "~15.0.8",
"expo-linking": "~8.0.10",
"expo-location": "~19.0.8",
@@ -6573,6 +6574,27 @@
}
}
},
+ "node_modules/expo-image-loader": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/expo-image-loader/-/expo-image-loader-6.0.0.tgz",
+ "integrity": "sha512-nKs/xnOGw6ACb4g26xceBD57FKLFkSwEUTDXEDF3Gtcu3MqF3ZIYd3YM+sSb1/z9AKV1dYT7rMSGVNgsveXLIQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "expo": "*"
+ }
+ },
+ "node_modules/expo-image-picker": {
+ "version": "17.0.10",
+ "resolved": "https://registry.npmjs.org/expo-image-picker/-/expo-image-picker-17.0.10.tgz",
+ "integrity": "sha512-a2xrowp2trmvXyUWgX3O6Q2rZaa2C59AqivKI7+bm+wLvMfTEbZgldLX4rEJJhM8xtmEDTNU+lzjtObwzBRGaw==",
+ "license": "MIT",
+ "dependencies": {
+ "expo-image-loader": "~6.0.0"
+ },
+ "peerDependencies": {
+ "expo": "*"
+ }
+ },
"node_modules/expo-keep-awake": {
"version": "15.0.8",
"resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-15.0.8.tgz",
diff --git a/package.json b/package.json
index cb9ce8e..cce23dd 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,7 @@
"expo-font": "~14.0.10",
"expo-haptics": "~15.0.8",
"expo-image": "~3.0.11",
+ "expo-image-picker": "~17.0.10",
"expo-linear-gradient": "~15.0.8",
"expo-linking": "~8.0.10",
"expo-location": "~19.0.8",