26.03.10
This commit is contained in:
@@ -20,7 +20,6 @@ LocaleConfig.locales['pt'] = {
|
||||
};
|
||||
LocaleConfig.defaultLocale = 'pt';
|
||||
|
||||
// --- FUNÇÃO PARA CALCULAR FERIADOS (Nacionais + Vila do Conde) ---
|
||||
const getFeriadosMap = (ano: number) => {
|
||||
const f: Record<string, string> = {
|
||||
[`${ano}-01-01`]: "Ano Novo",
|
||||
@@ -107,12 +106,16 @@ const AlunoHome = memo(() => {
|
||||
const diaSemana = data.getDay();
|
||||
const ehFimDeSemana = diaSemana === 0 || diaSemana === 6;
|
||||
const foraDoIntervalo = selectedDate < configEstagio.inicio || selectedDate > configEstagio.fim;
|
||||
|
||||
// CORREÇÃO: Verifica se o dia selecionado é exatamente HOJE
|
||||
const ehHoje = selectedDate === hojeStr;
|
||||
const ehFuturo = selectedDate > hojeStr;
|
||||
const nomeFeriado = feriadosMap[selectedDate];
|
||||
|
||||
return {
|
||||
valida: !ehFimDeSemana && !foraDoIntervalo && !nomeFeriado,
|
||||
podeMarcarPresenca: !ehFimDeSemana && !foraDoIntervalo && !ehFuturo && !nomeFeriado,
|
||||
// Só permite presença se for o dia atual
|
||||
podeMarcarPresenca: ehHoje && !foraDoIntervalo && !nomeFeriado,
|
||||
ehFuturo,
|
||||
nomeFeriado
|
||||
};
|
||||
@@ -133,7 +136,7 @@ const AlunoHome = memo(() => {
|
||||
}, [presencas, faltas, sumarios, faltasJustificadas, selectedDate, listaFeriados]);
|
||||
|
||||
const handlePresenca = async () => {
|
||||
if (!infoData.podeMarcarPresenca) return Alert.alert("Bloqueado", "Data inválida.");
|
||||
if (!infoData.podeMarcarPresenca) return Alert.alert("Bloqueado", "A presença só pode ser marcada no próprio dia.");
|
||||
const novas = { ...presencas, [selectedDate]: true };
|
||||
setPresencas(novas);
|
||||
await AsyncStorage.setItem('@presencas', JSON.stringify(novas));
|
||||
@@ -202,7 +205,7 @@ const AlunoHome = memo(() => {
|
||||
|
||||
<View style={[styles.cardCalendar, { backgroundColor: themeStyles.card }]}>
|
||||
<Calendar
|
||||
key={isDarkMode ? 'dark' : 'light'} // 🔹 Força re-render no tema
|
||||
key={isDarkMode ? 'dark' : 'light'}
|
||||
theme={{ calendarBackground: themeStyles.card, dayTextColor: themeStyles.texto, monthTextColor: themeStyles.texto, todayTextColor: '#0d6efd', arrowColor: '#0d6efd' }}
|
||||
markedDates={diasMarcados}
|
||||
onDayPress={(day) => { setSelectedDate(day.dateString); setEditandoSumario(false); }}
|
||||
|
||||
@@ -1,234 +1,101 @@
|
||||
//PÁGINA PERFIL DO ALUNO
|
||||
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { useRouter } from 'expo-router';
|
||||
import { useEffect, useState } from 'react';
|
||||
import {
|
||||
Platform, SafeAreaView, ScrollView, StatusBar, StyleSheet,
|
||||
Text, TouchableOpacity, View
|
||||
} from 'react-native';
|
||||
import { ActivityIndicator, SafeAreaView, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||
import { useTheme } from '../../themecontext';
|
||||
import { supabase } from '../lib/supabase';
|
||||
|
||||
export default function Perfil() {
|
||||
export default function PerfilAluno() {
|
||||
const { isDarkMode } = useTheme();
|
||||
const router = useRouter();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [perfil, setPerfil] = useState<any>(null);
|
||||
const [estagio, setEstagio] = useState<any>(null);
|
||||
|
||||
// Estados para dados dinâmicos e estatísticas
|
||||
const [datas, setDatas] = useState({ inicio: '05/01/2026', fim: '30/05/2026' });
|
||||
const [stats, setStats] = useState({
|
||||
horasConcluidas: 0,
|
||||
faltasTotais: 0,
|
||||
faltasJustificadas: 0,
|
||||
horasFaltam: 300
|
||||
});
|
||||
useEffect(() => {
|
||||
carregarDados();
|
||||
}, []);
|
||||
|
||||
async function carregarDados() {
|
||||
try {
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
if (!user) return;
|
||||
|
||||
// 1. Dados do Perfil
|
||||
const { data: prof } = await supabase.from('profiles').select('*').eq('id', user.id).single();
|
||||
setPerfil(prof);
|
||||
|
||||
// 2. Dados do Estágio e Empresa (Relacionados)
|
||||
const { data: est } = await supabase
|
||||
.from('estagios')
|
||||
.select('*, empresas(*)')
|
||||
.eq('aluno_id', user.id)
|
||||
.single();
|
||||
setEstagio(est);
|
||||
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
const themeStyles = {
|
||||
fundo: isDarkMode ? '#121212' : '#f1f3f5',
|
||||
card: isDarkMode ? '#1e1e1e' : '#fff',
|
||||
texto: isDarkMode ? '#fff' : '#000',
|
||||
textoSecundario: isDarkMode ? '#adb5bd' : '#6c757d',
|
||||
borda: isDarkMode ? '#333' : '#f1f3f5',
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const carregarECalcular = async () => {
|
||||
try {
|
||||
const [config, presencasRaw, faltasRaw, justRaw] = await Promise.all([
|
||||
AsyncStorage.getItem('@dados_estagio'),
|
||||
AsyncStorage.getItem('@presencas'),
|
||||
AsyncStorage.getItem('@faltas'),
|
||||
AsyncStorage.getItem('@justificacoes')
|
||||
]);
|
||||
|
||||
// 1. Carregar Configurações de Datas
|
||||
if (config) {
|
||||
const p = JSON.parse(config);
|
||||
setDatas({
|
||||
inicio: p.inicio || '05/01/2026',
|
||||
fim: p.fim || '30/05/2026'
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Calcular estatísticas baseadas nos objetos do calendário
|
||||
const presencas = presencasRaw ? JSON.parse(presencasRaw) : {};
|
||||
const faltas = faltasRaw ? JSON.parse(faltasRaw) : {};
|
||||
const justificacoes = justRaw ? JSON.parse(justRaw) : {};
|
||||
|
||||
const totalDiasPresenca = Object.keys(presencas).length;
|
||||
const totalFaltas = Object.keys(faltas).length;
|
||||
const totalJustificadas = Object.keys(justificacoes).length;
|
||||
|
||||
const horasFeitas = totalDiasPresenca * 7; // 7h por dia
|
||||
const totalHorasEstagio = 300;
|
||||
|
||||
setStats({
|
||||
horasConcluidas: horasFeitas,
|
||||
faltasTotais: totalFaltas,
|
||||
faltasJustificadas: totalJustificadas,
|
||||
horasFaltam: Math.max(0, totalHorasEstagio - horasFeitas)
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
console.error("Erro ao sincronizar dados do perfil", e);
|
||||
}
|
||||
};
|
||||
carregarECalcular();
|
||||
}, []);
|
||||
if (loading) return <ActivityIndicator style={{flex:1}} color="#0d6efd" />;
|
||||
|
||||
return (
|
||||
<SafeAreaView style={[styles.safe, { backgroundColor: themeStyles.fundo }]}>
|
||||
<StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
|
||||
|
||||
<View style={styles.header}>
|
||||
<TouchableOpacity
|
||||
style={[styles.btnVoltar, { backgroundColor: themeStyles.card }]}
|
||||
onPress={() => router.back()}
|
||||
>
|
||||
<Ionicons name="arrow-back" size={24} color={themeStyles.texto} />
|
||||
</TouchableOpacity>
|
||||
|
||||
<Text style={[styles.tituloGeral, { color: themeStyles.texto }]}>Perfil do Aluno</Text>
|
||||
|
||||
<View style={styles.spacer} />
|
||||
</View>
|
||||
<ScrollView contentContainerStyle={styles.container}>
|
||||
<Text style={[styles.tituloGeral, { color: themeStyles.texto }]}>O Meu Perfil</Text>
|
||||
|
||||
<ScrollView contentContainerStyle={styles.container} showsVerticalScrollIndicator={false}>
|
||||
|
||||
{/* Dados Pessoais - RESTAURADO TOTALMENTE */}
|
||||
{/* Dados Pessoais vindos da tabela PROFILES */}
|
||||
<View style={[styles.card, { backgroundColor: themeStyles.card }]}>
|
||||
<Text style={styles.tituloCard}>Dados Pessoais</Text>
|
||||
|
||||
<Text style={[styles.label, { color: themeStyles.textoSecundario }]}>Nome</Text>
|
||||
<Text style={[styles.valor, { color: themeStyles.texto }]}>Ricardo Gomes</Text>
|
||||
|
||||
<Text style={[styles.label, { color: themeStyles.textoSecundario }]}>Idade</Text>
|
||||
<Text style={[styles.valor, { color: themeStyles.texto }]}>17 anos</Text>
|
||||
|
||||
<Text style={[styles.label, { color: themeStyles.textoSecundario }]}>Residência</Text>
|
||||
<Text style={[styles.valor, { color: themeStyles.texto }]}>Junqueira, Vila do Conde</Text>
|
||||
|
||||
<Text style={[styles.label, { color: themeStyles.textoSecundario }]}>Telemóvel</Text>
|
||||
<Text style={[styles.valor, { color: themeStyles.texto }]}>915783648</Text>
|
||||
<LabelValor label="Nome" valor={perfil?.nome} theme={themeStyles} />
|
||||
<LabelValor label="Idade" valor={`${perfil?.idade} anos`} theme={themeStyles} />
|
||||
<LabelValor label="Residência" valor={perfil?.residencia} theme={themeStyles} />
|
||||
<LabelValor label="Telemóvel" valor={perfil?.telefone} theme={themeStyles} />
|
||||
</View>
|
||||
|
||||
{/* NOVA SECÇÃO: Assiduidade Dinâmica */}
|
||||
{/* Dados da Empresa vindos da tabela EMPRESAS via Estágio */}
|
||||
<View style={[styles.card, { backgroundColor: themeStyles.card }]}>
|
||||
<Text style={styles.tituloCard}>Assiduidade</Text>
|
||||
<View style={styles.rowStats}>
|
||||
<View style={styles.itemStat}>
|
||||
<Text style={[styles.label, { color: themeStyles.textoSecundario, marginTop: 0 }]}>Faltas Totais</Text>
|
||||
<Text style={[styles.valor, { color: '#dc3545', fontSize: 18 }]}>{stats.faltasTotais}</Text>
|
||||
</View>
|
||||
<View style={styles.itemStat}>
|
||||
<Text style={[styles.label, { color: themeStyles.textoSecundario, marginTop: 0 }]}>Justificadas</Text>
|
||||
<Text style={[styles.valor, { color: '#198754', fontSize: 18 }]}>{stats.faltasJustificadas}</Text>
|
||||
</View>
|
||||
<View style={styles.itemStat}>
|
||||
<Text style={[styles.label, { color: themeStyles.textoSecundario, marginTop: 0 }]}>Ñ Justif.</Text>
|
||||
<Text style={[styles.valor, { color: themeStyles.texto, fontSize: 18 }]}>
|
||||
{stats.faltasTotais - stats.faltasJustificadas}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Empresa de Estágio - RESTAURADO TOTALMENTE */}
|
||||
<View style={[styles.card, { backgroundColor: themeStyles.card }]}>
|
||||
<Text style={styles.tituloCard}>Empresa de Estágio</Text>
|
||||
|
||||
<Text style={[styles.label, { color: themeStyles.textoSecundario }]}>Curso</Text>
|
||||
<Text style={[styles.valor, { color: themeStyles.texto }]}>Técnico de Informática</Text>
|
||||
|
||||
<Text style={[styles.label, { color: themeStyles.textoSecundario }]}>Empresa</Text>
|
||||
<Text style={[styles.valor, { color: themeStyles.texto }]}>Tech Solutions, Lda</Text>
|
||||
|
||||
<Text style={[styles.label, { color: themeStyles.textoSecundario }]}>Morada</Text>
|
||||
<Text style={[styles.valor, { color: themeStyles.texto }]}>Rua das papoilas, nº67</Text>
|
||||
|
||||
<Text style={[styles.label, { color: themeStyles.textoSecundario }]}>Tutor</Text>
|
||||
<Text style={[styles.valor, { color: themeStyles.texto }]}>Nicolau de Sousa</Text>
|
||||
|
||||
<Text style={[styles.label, { color: themeStyles.textoSecundario }]}>Telemóvel</Text>
|
||||
<Text style={[styles.valor, { color: themeStyles.texto }]}>917892748</Text>
|
||||
</View>
|
||||
|
||||
{/* Dados do Estágio - HORAS DINÂMICAS */}
|
||||
<View style={[styles.card, { backgroundColor: themeStyles.card }]}>
|
||||
<Text style={styles.tituloCard}>Dados do Estágio</Text>
|
||||
|
||||
<Text style={[styles.label, { color: themeStyles.textoSecundario }]}>Início do Estágio</Text>
|
||||
<Text style={[styles.valor, { color: themeStyles.texto }]}>{datas.inicio}</Text>
|
||||
|
||||
<Text style={[styles.label, { color: themeStyles.textoSecundario }]}>Fim do Estágio</Text>
|
||||
<Text style={[styles.valor, { color: themeStyles.texto }]}>{datas.fim}</Text>
|
||||
|
||||
<View style={[styles.estatisticasHoras, { borderBottomColor: themeStyles.borda }]}>
|
||||
<View>
|
||||
<Text style={[styles.label, { color: themeStyles.textoSecundario }]}>Totais</Text>
|
||||
<Text style={[styles.valor, { color: themeStyles.texto }]}>300h</Text>
|
||||
</View>
|
||||
<View>
|
||||
<Text style={[styles.label, { color: themeStyles.textoSecundario }]}>Concluídas</Text>
|
||||
<Text style={[styles.valor, {color: '#198754'}]}>{stats.horasConcluidas}h</Text>
|
||||
</View>
|
||||
<View>
|
||||
<Text style={[styles.label, { color: themeStyles.textoSecundario }]}>Faltam</Text>
|
||||
<Text style={[styles.valor, {color: '#dc3545'}]}>{stats.horasFaltam}h</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Text style={[styles.labelHorario, { color: themeStyles.texto }]}>Horário Semanal</Text>
|
||||
|
||||
<View style={[styles.tabela, { borderColor: themeStyles.borda }]}>
|
||||
<View style={[styles.linhaTab, { backgroundColor: isDarkMode ? '#2c2c2c' : '#f8f9fa', borderBottomColor: themeStyles.borda }]}>
|
||||
<Text style={[styles.celulaHeader, { color: themeStyles.texto }]}>Período</Text>
|
||||
<Text style={[styles.celulaHeader, { color: themeStyles.texto }]}>Horário</Text>
|
||||
</View>
|
||||
|
||||
<View style={[styles.linhaTab, { borderBottomColor: themeStyles.borda }]}>
|
||||
<Text style={[styles.celulaLabel, { color: themeStyles.textoSecundario }]}>Manhã</Text>
|
||||
<Text style={[styles.celulaValor, { color: themeStyles.texto }]}>09:30 - 13:00</Text>
|
||||
</View>
|
||||
|
||||
<View style={[styles.linhaTab, { backgroundColor: isDarkMode ? '#252525' : '#fdfcfe', borderBottomColor: themeStyles.borda }]}>
|
||||
<Text style={[styles.celulaLabel, { color: themeStyles.textoSecundario }]}>Almoço</Text>
|
||||
<Text style={[styles.celulaValor, { fontWeight: '400', color: themeStyles.textoSecundario }]}>13:00 - 14:30</Text>
|
||||
</View>
|
||||
|
||||
<View style={[styles.linhaTab, { borderBottomWidth: 0 }]}>
|
||||
<Text style={[styles.celulaLabel, { color: themeStyles.textoSecundario }]}>Tarde</Text>
|
||||
<Text style={[styles.celulaValor, { color: themeStyles.texto }]}>14:30 - 17:30</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Text style={styles.notaTotal}>Total: 7 horas diárias por presença</Text>
|
||||
<Text style={styles.tituloCard}>Local de Estágio</Text>
|
||||
<LabelValor label="Empresa" valor={estagio?.empresas?.nome} theme={themeStyles} />
|
||||
<LabelValor label="Tutor" valor={estagio?.empresas?.tutor_nome} theme={themeStyles} />
|
||||
<LabelValor label="Contacto Tutor" valor={estagio?.empresas?.tutor_telefone} theme={themeStyles} />
|
||||
<LabelValor label="Morada" valor={estagio?.empresas?.morada} theme={themeStyles} />
|
||||
</View>
|
||||
|
||||
<TouchableOpacity
|
||||
style={styles.btnSair}
|
||||
onPress={async () => { await supabase.auth.signOut(); router.replace('/'); }}
|
||||
>
|
||||
<Text style={{color: '#fff', fontWeight: 'bold'}}>Sair da Conta</Text>
|
||||
</TouchableOpacity>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
function LabelValor({ label, valor, theme }: any) {
|
||||
return (
|
||||
<View style={{ marginBottom: 12 }}>
|
||||
<Text style={{ fontSize: 11, color: theme.textoSecundario, textTransform: 'uppercase' }}>{label}</Text>
|
||||
<Text style={{ fontSize: 16, color: theme.texto, fontWeight: '600' }}>{valor || '---'}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
safe: { flex: 1, paddingTop: Platform.OS === 'android' ? StatusBar.currentHeight : 0 },
|
||||
header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 20, paddingVertical: 10 },
|
||||
btnVoltar: { width: 40, height: 40, borderRadius: 20, justifyContent: 'center', alignItems: 'center', elevation: 2, shadowColor: '#000', shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.2, shadowRadius: 2 },
|
||||
spacer: { width: 40 },
|
||||
container: { padding: 20, gap: 20, paddingBottom: 40 },
|
||||
tituloGeral: { fontSize: 22, fontWeight: 'bold' },
|
||||
card: { padding: 20, borderRadius: 16, elevation: 2, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4 },
|
||||
tituloCard: { fontSize: 18, fontWeight: 'bold', color: '#0d6efd', textAlign: 'center', marginBottom: 10, borderBottomWidth: 1, borderBottomColor: '#f1f3f5', paddingBottom: 8 },
|
||||
label: { marginTop: 12, fontSize: 13 },
|
||||
valor: { fontSize: 16, fontWeight: '600' },
|
||||
labelHorario: { fontSize: 16, fontWeight: 'bold', marginTop: 20, marginBottom: 10, textAlign: 'center' },
|
||||
tabela: { borderWidth: 1, borderRadius: 8, overflow: 'hidden' },
|
||||
linhaTab: { flexDirection: 'row', borderBottomWidth: 1, paddingVertical: 8, alignItems: 'center' },
|
||||
celulaHeader: { flex: 1, fontWeight: 'bold', textAlign: 'center', fontSize: 13 },
|
||||
celulaLabel: { flex: 1, paddingLeft: 12, fontSize: 14 },
|
||||
celulaValor: { flex: 1, textAlign: 'center', fontSize: 14, fontWeight: '600' },
|
||||
notaTotal: { textAlign: 'center', fontSize: 12, color: '#0d6efd', marginTop: 8, fontWeight: '500' },
|
||||
estatisticasHoras: { flexDirection: 'row', justifyContent: 'space-between', borderBottomWidth: 1, paddingBottom: 15, marginTop: 5 },
|
||||
rowStats: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 5 },
|
||||
itemStat: { alignItems: 'center', flex: 1 }
|
||||
safe: { flex: 1 },
|
||||
container: { padding: 20, gap: 20 },
|
||||
tituloGeral: { fontSize: 24, fontWeight: 'bold', marginBottom: 10 },
|
||||
card: { padding: 20, borderRadius: 16, elevation: 2 },
|
||||
tituloCard: { fontSize: 14, fontWeight: 'bold', color: '#0d6efd', marginBottom: 15, textTransform: 'uppercase' },
|
||||
btnSair: { backgroundColor: '#dc3545', padding: 18, borderRadius: 15, alignItems: 'center', marginTop: 10 }
|
||||
});
|
||||
@@ -1,3 +1,4 @@
|
||||
// app/Professor/Alunos/DetalhesAluno.tsx
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { useLocalSearchParams, useRouter } from 'expo-router';
|
||||
import { memo, useEffect, useState } from 'react';
|
||||
@@ -13,6 +14,18 @@ import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { useTheme } from '../../../themecontext';
|
||||
import { supabase } from '../../lib/supabase';
|
||||
|
||||
// Definindo a interface para o estado do aluno
|
||||
interface AlunoEstado {
|
||||
id: string;
|
||||
nome: string;
|
||||
n_escola: string;
|
||||
turma_curso: string;
|
||||
email: string;
|
||||
telefone: string;
|
||||
residencia: string;
|
||||
idade: string;
|
||||
}
|
||||
|
||||
const DetalhesAlunos = memo(() => {
|
||||
const router = useRouter();
|
||||
const params = useLocalSearchParams();
|
||||
@@ -32,7 +45,7 @@ const DetalhesAlunos = memo(() => {
|
||||
? params.alunoId[0]
|
||||
: null;
|
||||
|
||||
const [aluno, setAluno] = useState<any>(null);
|
||||
const [aluno, setAluno] = useState<AlunoEstado | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -47,42 +60,45 @@ const DetalhesAlunos = memo(() => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// 1️⃣ Buscar dados do aluno
|
||||
const { data: alunoData, error: alunoError } = await supabase
|
||||
const { data, error } = await supabase
|
||||
.from('alunos')
|
||||
.select('id, nome, n_escola, turma_curso')
|
||||
.select(`
|
||||
id,
|
||||
nome,
|
||||
n_escola,
|
||||
turma_curso,
|
||||
profiles!alunos_profile_id_fkey (
|
||||
email,
|
||||
telefone,
|
||||
residencia,
|
||||
idade
|
||||
)
|
||||
`)
|
||||
.eq('id', alunoId)
|
||||
.single();
|
||||
|
||||
if (alunoError || !alunoData) {
|
||||
console.log('Erro ao buscar aluno:', alunoError);
|
||||
setAluno(null);
|
||||
if (error) {
|
||||
console.log('Erro ao buscar:', error.message);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2️⃣ Buscar dados do profile como array
|
||||
const { data: perfilDataArray, error: perfilError } = await supabase
|
||||
.from('profiles')
|
||||
.select('email, telefone, residencia, idade')
|
||||
.eq('n_escola', alunoData.n_escola);
|
||||
if (data) {
|
||||
// CORREÇÃO AQUI: Forçamos o TypeScript a tratar profiles como um objeto
|
||||
// para que ele permita acessar email, telefone, etc.
|
||||
const perfil = data.profiles as any;
|
||||
|
||||
if (perfilError) console.log('Erro ao buscar profile:', perfilError);
|
||||
|
||||
// Pega o primeiro registro ou undefined
|
||||
const perfilData = perfilDataArray?.[0];
|
||||
|
||||
// 3️⃣ Combinar dados
|
||||
setAluno({
|
||||
id: alunoData.id,
|
||||
nome: alunoData.nome,
|
||||
n_escola: alunoData.n_escola,
|
||||
turma_curso: alunoData.turma_curso,
|
||||
email: perfilData?.email ?? '-',
|
||||
telefone: perfilData?.telefone ?? '-',
|
||||
residencia: perfilData?.residencia ?? '-',
|
||||
idade: perfilData?.idade?.toString() ?? '-',
|
||||
});
|
||||
setAluno({
|
||||
id: String(data.id),
|
||||
nome: data.nome || 'Sem nome',
|
||||
n_escola: String(data.n_escola || '-'),
|
||||
turma_curso: data.turma_curso || '-',
|
||||
email: perfil?.email ?? '-',
|
||||
telefone: perfil?.telefone ?? '-',
|
||||
residencia: perfil?.residencia ?? '-',
|
||||
idade: perfil?.idade ? String(perfil.idade) : '-',
|
||||
});
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
} catch (err) {
|
||||
@@ -136,10 +152,10 @@ const DetalhesAlunos = memo(() => {
|
||||
});
|
||||
|
||||
const renderCampo = (label: string, valor: string, colors: any) => (
|
||||
<>
|
||||
<View key={label} style={{ marginBottom: 15 }}>
|
||||
<Text style={[styles.label, { color: colors.label }]}>{label}</Text>
|
||||
<Text style={[styles.valor, { color: colors.text }]}>{valor}</Text>
|
||||
</>
|
||||
</View>
|
||||
);
|
||||
|
||||
export default DetalhesAlunos;
|
||||
@@ -147,10 +163,24 @@ export default DetalhesAlunos;
|
||||
const styles = StyleSheet.create({
|
||||
safe: { flex: 1 },
|
||||
center: { marginTop: 50, textAlign: 'center', fontSize: 16 },
|
||||
header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 16, paddingVertical: 12 },
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 12
|
||||
},
|
||||
titulo: { fontSize: 20, fontWeight: 'bold' },
|
||||
container: { padding: 16 },
|
||||
card: { padding: 16, borderRadius: 12, elevation: 2 },
|
||||
label: { fontSize: 12, marginTop: 10 },
|
||||
card: {
|
||||
padding: 16,
|
||||
borderRadius: 12,
|
||||
elevation: 2,
|
||||
shadowColor: '#000',
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 4,
|
||||
shadowOffset: { width: 0, height: 2 }
|
||||
},
|
||||
label: { fontSize: 12, marginBottom: 2, textTransform: 'uppercase', letterSpacing: 0.5 },
|
||||
valor: { fontSize: 16, fontWeight: '600' },
|
||||
});
|
||||
@@ -1,51 +1,39 @@
|
||||
// app/Professor/Estagios.tsx
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { useRouter } from 'expo-router';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
Alert,
|
||||
Modal,
|
||||
ActivityIndicator,
|
||||
Alert, Modal,
|
||||
Platform,
|
||||
SafeAreaView,
|
||||
ScrollView,
|
||||
SafeAreaView, ScrollView,
|
||||
StatusBar,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
View
|
||||
StyleSheet, Text, TextInput, TouchableOpacity, View
|
||||
} from 'react-native';
|
||||
import { useTheme } from '../../../themecontext'; // mesmo theme context das definições
|
||||
import { useTheme } from '../../../themecontext';
|
||||
import { supabase } from '../../lib/supabase';
|
||||
|
||||
interface Aluno {
|
||||
id: number;
|
||||
nome: string;
|
||||
// --- Interfaces ---
|
||||
interface Horario {
|
||||
id?: number;
|
||||
periodo: string;
|
||||
hora_inicio: string;
|
||||
hora_fim: string;
|
||||
}
|
||||
|
||||
interface Empresa {
|
||||
id: number;
|
||||
nome: string;
|
||||
}
|
||||
interface Aluno { id: string; nome: string; turma_curso: string; }
|
||||
interface Empresa { id: string; nome: string; morada: string; tutor_nome: string; tutor_telefone: string; curso: string; }
|
||||
|
||||
interface Estagio {
|
||||
id: number;
|
||||
alunoId: number;
|
||||
empresaId: number;
|
||||
alunoNome: string;
|
||||
empresaNome: string;
|
||||
id: string;
|
||||
aluno_id: string;
|
||||
empresa_id: string;
|
||||
data_inicio: string;
|
||||
data_fim: string;
|
||||
horas_diarias?: string;
|
||||
alunos: { nome: string; turma_curso: string };
|
||||
empresas: { id: string; nome: string; morada: string; tutor_nome: string; tutor_telefone: string; curso: string };
|
||||
}
|
||||
|
||||
// Dados simulados
|
||||
const alunosData: Aluno[] = [
|
||||
{ id: 1, nome: 'João Silva' },
|
||||
{ id: 2, nome: 'Maria Fernandes' },
|
||||
];
|
||||
|
||||
const empresasData: Empresa[] = [
|
||||
{ id: 1, nome: 'Empresa A' },
|
||||
{ id: 2, nome: 'Empresa B' },
|
||||
];
|
||||
|
||||
export default function Estagios() {
|
||||
const router = useRouter();
|
||||
const { isDarkMode } = useTheme();
|
||||
@@ -58,168 +46,319 @@ export default function Estagios() {
|
||||
border: isDarkMode ? '#343a40' : '#ced4da',
|
||||
azul: '#0d6efd',
|
||||
vermelho: '#dc3545',
|
||||
inputBg: isDarkMode ? '#2c2c2c' : '#f8f9fa',
|
||||
}), [isDarkMode]);
|
||||
|
||||
// Estados
|
||||
// --- Estados ---
|
||||
const [estagios, setEstagios] = useState<Estagio[]>([]);
|
||||
const [alunos, setAlunos] = useState<Aluno[]>([]);
|
||||
const [empresas, setEmpresas] = useState<Empresa[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [passo, setPasso] = useState(1);
|
||||
const [alunoSelecionado, setAlunoSelecionado] = useState<Aluno | null>(null);
|
||||
const [empresaSelecionada, setEmpresaSelecionada] = useState<Empresa | null>(null);
|
||||
const [editandoEstagio, setEditandoEstagio] = useState<Estagio | null>(null);
|
||||
const [searchText, setSearchText] = useState('');
|
||||
|
||||
const [dataInicio, setDataInicio] = useState('');
|
||||
const [dataFim, setDataFim] = useState('');
|
||||
const [horarios, setHorarios] = useState<Horario[]>([]);
|
||||
|
||||
// Filtrar estágios por busca
|
||||
const estagiosFiltrados = estagios.filter(e =>
|
||||
e.alunoNome.toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
e.empresaNome.toLowerCase().includes(searchText.toLowerCase())
|
||||
);
|
||||
const [searchMain, setSearchMain] = useState('');
|
||||
const [searchAluno, setSearchAluno] = useState('');
|
||||
const [searchEmpresa, setSearchEmpresa] = useState('');
|
||||
|
||||
const abrirModalNovo = () => {
|
||||
// --- Cálculo de Horas Diárias ---
|
||||
const totalHorasDiarias = useMemo(() => {
|
||||
let totalMinutos = 0;
|
||||
horarios.forEach(h => {
|
||||
const [hIni, mIni] = h.hora_inicio.split(':').map(Number);
|
||||
const [hFim, mFim] = h.hora_fim.split(':').map(Number);
|
||||
if (!isNaN(hIni) && !isNaN(hFim)) {
|
||||
const inicio = hIni * 60 + (mIni || 0);
|
||||
const fim = hFim * 60 + (mFim || 0);
|
||||
if (fim > inicio) totalMinutos += (fim - inicio);
|
||||
}
|
||||
});
|
||||
const h = Math.floor(totalMinutos / 60);
|
||||
const m = totalMinutos % 60;
|
||||
return m > 0 ? `${h}h${m}m` : `${h}h`;
|
||||
}, [horarios]);
|
||||
|
||||
// --- Funções de Dados ---
|
||||
useEffect(() => { fetchDados(); }, []);
|
||||
|
||||
const fetchDados = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const [resEstagios, resAlunos, resEmpresas] = await Promise.all([
|
||||
supabase.from('estagios').select('*, alunos(nome, turma_curso), empresas(*)'),
|
||||
supabase.from('alunos').select('id, nome, turma_curso').order('nome'),
|
||||
supabase.from('empresas').select('*').order('nome')
|
||||
]);
|
||||
if (resEstagios.data) setEstagios(resEstagios.data);
|
||||
if (resAlunos.data) setAlunos(resAlunos.data);
|
||||
if (resEmpresas.data) setEmpresas(resEmpresas.data);
|
||||
} catch (e) { console.error(e); } finally { setLoading(false); }
|
||||
};
|
||||
|
||||
const carregarHorarios = async (estagioId: string) => {
|
||||
const { data } = await supabase.from('horarios_estagio').select('*').eq('estagio_id', estagioId);
|
||||
if (data) setHorarios(data);
|
||||
};
|
||||
|
||||
const handleFecharModal = () => {
|
||||
setModalVisible(false);
|
||||
setEditandoEstagio(null);
|
||||
setAlunoSelecionado(null);
|
||||
setEmpresaSelecionada(null);
|
||||
setEditandoEstagio(null);
|
||||
setModalVisible(true);
|
||||
setHorarios([]);
|
||||
setDataInicio('');
|
||||
setDataFim('');
|
||||
setPasso(1);
|
||||
};
|
||||
|
||||
const salvarEstagio = () => {
|
||||
if (!alunoSelecionado || !empresaSelecionada) {
|
||||
Alert.alert('Erro', 'Selecione um aluno e uma empresa existentes.');
|
||||
return;
|
||||
const eliminarEstagio = (id: string) => {
|
||||
Alert.alert("Eliminar Estágio", "Deseja remover este estágio e todos os seus horários?", [
|
||||
{ text: "Cancelar", style: "cancel" },
|
||||
{ text: "Eliminar", style: "destructive", onPress: async () => {
|
||||
await supabase.from('estagios').delete().eq('id', id);
|
||||
fetchDados();
|
||||
}}
|
||||
]);
|
||||
};
|
||||
|
||||
const salvarEstagio = async () => {
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
|
||||
if (empresaSelecionada) {
|
||||
await supabase.from('empresas').update({
|
||||
morada: empresaSelecionada.morada,
|
||||
tutor_nome: empresaSelecionada.tutor_nome,
|
||||
tutor_telefone: empresaSelecionada.tutor_telefone
|
||||
}).eq('id', empresaSelecionada.id);
|
||||
}
|
||||
|
||||
if (editandoEstagio) {
|
||||
// Editar estágio existente
|
||||
setEstagios(estagios.map(e => e.id === editandoEstagio.id ? {
|
||||
...e,
|
||||
alunoId: alunoSelecionado.id,
|
||||
empresaId: empresaSelecionada.id,
|
||||
alunoNome: alunoSelecionado.nome,
|
||||
empresaNome: empresaSelecionada.nome
|
||||
} : e));
|
||||
} else {
|
||||
// Criar novo estágio
|
||||
const novo: Estagio = {
|
||||
id: Date.now(),
|
||||
alunoId: alunoSelecionado.id,
|
||||
empresaId: empresaSelecionada.id,
|
||||
alunoNome: alunoSelecionado.nome,
|
||||
empresaNome: empresaSelecionada.nome
|
||||
};
|
||||
setEstagios([...estagios, novo]);
|
||||
const payloadEstagio = {
|
||||
aluno_id: alunoSelecionado?.id,
|
||||
empresa_id: empresaSelecionada?.id,
|
||||
professor_id: user?.id,
|
||||
data_inicio: dataInicio || new Date().toISOString().split('T')[0],
|
||||
data_fim: dataFim || null,
|
||||
horas_diarias: totalHorasDiarias,
|
||||
estado: 'Ativo',
|
||||
};
|
||||
|
||||
const { data: estData, error: errE } = editandoEstagio
|
||||
? await supabase.from('estagios').update(payloadEstagio).eq('id', editandoEstagio.id).select().single()
|
||||
: await supabase.from('estagios').insert([payloadEstagio]).select().single();
|
||||
|
||||
if (errE) return Alert.alert("Erro", errE.message);
|
||||
|
||||
const currentId = editandoEstagio?.id || estData.id;
|
||||
await supabase.from('horarios_estagio').delete().eq('estagio_id', currentId);
|
||||
|
||||
if (horarios.length > 0) {
|
||||
const payloadH = horarios.map(h => ({
|
||||
estagio_id: currentId,
|
||||
periodo: h.periodo,
|
||||
hora_inicio: h.hora_inicio,
|
||||
hora_fim: h.hora_fim
|
||||
}));
|
||||
await supabase.from('horarios_estagio').insert(payloadH);
|
||||
}
|
||||
|
||||
setModalVisible(false);
|
||||
handleFecharModal();
|
||||
fetchDados();
|
||||
};
|
||||
|
||||
const editarEstagio = (e: Estagio) => {
|
||||
setEditandoEstagio(e);
|
||||
setAlunoSelecionado(alunosData.find(a => a.id === e.alunoId) || null);
|
||||
setEmpresaSelecionada(empresasData.find(emp => emp.id === e.empresaId) || null);
|
||||
setModalVisible(true);
|
||||
};
|
||||
// --- Filtros ---
|
||||
const alunosAgrupados = useMemo(() => {
|
||||
const groups: Record<string, Aluno[]> = {};
|
||||
alunos.filter(a => a.nome.toLowerCase().includes(searchAluno.toLowerCase())).forEach(a => {
|
||||
const k = a.turma_curso || 'Sem Turma';
|
||||
if (!groups[k]) groups[k] = [];
|
||||
groups[k].push(a);
|
||||
});
|
||||
return groups;
|
||||
}, [alunos, searchAluno]);
|
||||
|
||||
const empresasAgrupadas = useMemo(() => {
|
||||
const groups: Record<string, Empresa[]> = {};
|
||||
empresas.filter(e => e.nome.toLowerCase().includes(searchEmpresa.toLowerCase())).forEach(e => {
|
||||
const k = e.curso || 'Geral';
|
||||
if (!groups[k]) groups[k] = [];
|
||||
groups[k].push(e);
|
||||
});
|
||||
return groups;
|
||||
}, [empresas, searchEmpresa]);
|
||||
|
||||
if (loading) return <View style={{flex:1, justifyContent:'center', backgroundColor:cores.fundo}}><ActivityIndicator size="large" color={cores.azul}/></View>;
|
||||
|
||||
return (
|
||||
<SafeAreaView style={[styles.container, { backgroundColor: cores.fundo, paddingTop: Platform.OS === 'android' ? StatusBar.currentHeight : 0 }]}>
|
||||
<StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} backgroundColor={cores.fundo} />
|
||||
<ScrollView contentContainerStyle={styles.content}>
|
||||
{/* Header */}
|
||||
<View style={styles.header}>
|
||||
<TouchableOpacity onPress={() => router.back()}>
|
||||
<Ionicons name="arrow-back-outline" size={26} color={cores.texto} />
|
||||
</TouchableOpacity>
|
||||
<Text style={[styles.title, { color: cores.texto }]}>Estágios</Text>
|
||||
<View style={{ width: 26 }} />
|
||||
</View>
|
||||
<SafeAreaView style={[styles.container, { backgroundColor: cores.fundo }]}>
|
||||
<StatusBar barStyle={isDarkMode ? "light-content" : "dark-content"} />
|
||||
|
||||
<View style={styles.header}>
|
||||
<TouchableOpacity onPress={() => router.back()}><Ionicons name="arrow-back" size={26} color={cores.texto}/></TouchableOpacity>
|
||||
<Text style={[styles.title, { color: cores.texto }]}>Estágios</Text>
|
||||
<TouchableOpacity onPress={fetchDados}><Ionicons name="refresh" size={24} color={cores.azul}/></TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* Pesquisa */}
|
||||
<TextInput
|
||||
style={[styles.searchInput, { backgroundColor: cores.card, color: cores.texto, borderColor: cores.border }]}
|
||||
placeholder="Procurar estágio..."
|
||||
<ScrollView contentContainerStyle={{ padding: 20 }}>
|
||||
<TextInput
|
||||
placeholder="Procurar aluno..."
|
||||
placeholderTextColor={cores.textoSecundario}
|
||||
value={searchText}
|
||||
onChangeText={setSearchText}
|
||||
style={[styles.input, { backgroundColor: cores.card, color: cores.texto, borderColor: cores.border }]}
|
||||
onChangeText={setSearchMain}
|
||||
/>
|
||||
|
||||
{/* Lista de estágios */}
|
||||
<View style={{ marginTop: 16 }}>
|
||||
{estagiosFiltrados.map(e => (
|
||||
<TouchableOpacity key={e.id} style={[styles.card, { backgroundColor: cores.card }]} onPress={() => editarEstagio(e)}>
|
||||
<Text style={[styles.cardTitle, { color: cores.texto }]}>{e.alunoNome}</Text>
|
||||
<Text style={[styles.cardSubtitle, { color: cores.textoSecundario }]}>{e.empresaNome}</Text>
|
||||
<Ionicons name="create-outline" size={20} color={cores.azul} />
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
{estagiosFiltrados.length === 0 && <Text style={{ color: cores.textoSecundario, marginTop: 12 }}>Nenhum estágio encontrado.</Text>}
|
||||
</View>
|
||||
|
||||
{/* Botão Novo Estágio */}
|
||||
<TouchableOpacity style={[styles.newButton, { backgroundColor: cores.azul }]} onPress={abrirModalNovo}>
|
||||
<Ionicons name="add" size={20} color="#fff" />
|
||||
<Text style={styles.newButtonText}>Novo Estágio</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* Modal de criação/edição */}
|
||||
<Modal visible={modalVisible} transparent animationType="slide">
|
||||
<View style={styles.modalOverlay}>
|
||||
<View style={[styles.modalContent, { backgroundColor: cores.card }]}>
|
||||
<Text style={[styles.modalTitle, { color: cores.texto }]}>{editandoEstagio ? 'Editar Estágio' : 'Novo Estágio'}</Text>
|
||||
|
||||
{/* Dropdown Aluno */}
|
||||
<Text style={[styles.modalLabel, { color: cores.textoSecundario }]}>Aluno</Text>
|
||||
{alunosData.map(a => (
|
||||
<TouchableOpacity
|
||||
key={a.id}
|
||||
style={[styles.modalOption, { backgroundColor: alunoSelecionado?.id === a.id ? cores.azul : cores.card }]}
|
||||
onPress={() => setAlunoSelecionado(a)}
|
||||
>
|
||||
<Text style={{ color: alunoSelecionado?.id === a.id ? '#fff' : cores.texto }}>{a.nome}</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
|
||||
{/* Dropdown Empresa */}
|
||||
<Text style={[styles.modalLabel, { color: cores.textoSecundario, marginTop: 12 }]}>Empresa</Text>
|
||||
{empresasData.map(emp => (
|
||||
<TouchableOpacity
|
||||
key={emp.id}
|
||||
style={[styles.modalOption, { backgroundColor: empresaSelecionada?.id === emp.id ? cores.azul : cores.card }]}
|
||||
onPress={() => setEmpresaSelecionada(emp)}
|
||||
>
|
||||
<Text style={{ color: empresaSelecionada?.id === emp.id ? '#fff' : cores.texto }}>{emp.nome}</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
|
||||
<View style={{ flexDirection: 'row', justifyContent: 'space-between', marginTop: 20 }}>
|
||||
<TouchableOpacity onPress={() => setModalVisible(false)} style={[styles.modalButton, { backgroundColor: cores.vermelho }]}>
|
||||
<Text style={{ color: '#fff', fontWeight: '600' }}>Cancelar</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity onPress={salvarEstagio} style={[styles.modalButton, { backgroundColor: cores.azul }]}>
|
||||
<Text style={{ color: '#fff', fontWeight: '600' }}>Salvar</Text>
|
||||
</TouchableOpacity>
|
||||
{estagios.filter(e => e.alunos?.nome?.toLowerCase().includes(searchMain.toLowerCase())).map(e => (
|
||||
<View key={e.id} style={[styles.card, { backgroundColor: cores.card }]}>
|
||||
<TouchableOpacity style={{ flex: 1 }} onPress={() => {
|
||||
setEditandoEstagio(e);
|
||||
setAlunoSelecionado(alunos.find(a => a.id === e.aluno_id) || null);
|
||||
setEmpresaSelecionada(empresas.find(emp => emp.id === e.empresa_id) || null);
|
||||
setDataInicio(e.data_inicio || '');
|
||||
setDataFim(e.data_fim || '');
|
||||
carregarHorarios(e.id);
|
||||
setPasso(2);
|
||||
setModalVisible(true);
|
||||
}}>
|
||||
<Text style={[styles.cardTitle, { color: cores.texto }]}>{e.alunos?.nome}</Text>
|
||||
<View style={{flexDirection: 'row', gap: 10, marginTop: 4}}>
|
||||
<Text style={{ color: cores.azul, fontSize: 11, fontWeight: '700' }}>{e.alunos?.turma_curso}</Text>
|
||||
<Text style={{ color: cores.textoSecundario, fontSize: 11 }}>⏱ {e.horas_diarias || '0h'}/dia</Text>
|
||||
</View>
|
||||
</View>
|
||||
<Text style={{ color: cores.textoSecundario, marginTop: 4, fontSize: 13 }}>🏢 {e.empresas?.nome}</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity onPress={() => eliminarEstagio(e.id)}><Ionicons name="trash-outline" size={20} color={cores.vermelho} /></TouchableOpacity>
|
||||
</View>
|
||||
</Modal>
|
||||
))}
|
||||
|
||||
<TouchableOpacity style={[styles.btnNovo, { backgroundColor: cores.azul }]} onPress={() => {
|
||||
setEditandoEstagio(null); setAlunoSelecionado(null); setEmpresaSelecionada(null);
|
||||
setDataInicio(''); setDataFim(''); setHorarios([]); setPasso(1); setModalVisible(true);
|
||||
}}>
|
||||
<Text style={{ color: '#fff', fontWeight: 'bold' }}>+ Novo Estágio</Text>
|
||||
</TouchableOpacity>
|
||||
</ScrollView>
|
||||
|
||||
<Modal visible={modalVisible} animationType="slide" transparent>
|
||||
<View style={styles.modalOverlay}>
|
||||
<View style={[styles.modalContent, { backgroundColor: cores.card }]}>
|
||||
<ScrollView showsVerticalScrollIndicator={false}>
|
||||
{passo === 1 ? (
|
||||
<View>
|
||||
<Text style={[styles.modalTitle, { color: cores.texto }]}>Passo 1: Seleção</Text>
|
||||
<Text style={styles.label}>Aluno</Text>
|
||||
<View style={styles.selector}><ScrollView nestedScrollEnabled style={{maxHeight: 180}}>
|
||||
{Object.keys(alunosAgrupados).map(t => (
|
||||
<View key={t}><Text style={styles.groupHead}>{t}</Text>
|
||||
{alunosAgrupados[t].map(a => (
|
||||
<TouchableOpacity key={a.id} style={[styles.item, alunoSelecionado?.id === a.id && {backgroundColor: cores.azul}]} onPress={() => setAlunoSelecionado(a)}>
|
||||
<Text style={{color: alunoSelecionado?.id === a.id ? '#fff' : cores.texto}}>{a.nome}</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
))}
|
||||
</ScrollView></View>
|
||||
<Text style={[styles.label, {marginTop: 15}]}>Empresa</Text>
|
||||
<View style={styles.selector}><ScrollView nestedScrollEnabled style={{maxHeight: 180}}>
|
||||
{Object.keys(empresasAgrupadas).map(c => (
|
||||
<View key={c}><Text style={styles.groupHead}>{c}</Text>
|
||||
{empresasAgrupadas[c].map(emp => (
|
||||
<TouchableOpacity key={emp.id} style={[styles.item, empresaSelecionada?.id === emp.id && {backgroundColor: cores.azul}]} onPress={() => setEmpresaSelecionada(emp)}>
|
||||
<Text style={{color: empresaSelecionada?.id === emp.id ? '#fff' : cores.texto}}>{emp.nome}</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
))}
|
||||
</ScrollView></View>
|
||||
<View style={styles.modalFooter}>
|
||||
<TouchableOpacity onPress={handleFecharModal} style={[styles.btnModal, {backgroundColor: cores.vermelho}]}><Text style={{color:'#fff'}}>Sair</Text></TouchableOpacity>
|
||||
<TouchableOpacity onPress={() => { if(alunoSelecionado && empresaSelecionada) setPasso(2); }} style={[styles.btnModal, {backgroundColor: cores.azul}]}><Text style={{color:'#fff'}}>Configurar</Text></TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
) : (
|
||||
<View>
|
||||
<Text style={[styles.modalTitle, { color: cores.texto }]}>Passo 2: Detalhes</Text>
|
||||
<View style={[styles.confirmBox, { borderColor: cores.border }]}>
|
||||
<Text style={styles.confirmTitle}>DURAÇÃO E HORAS</Text>
|
||||
<View style={{flexDirection: 'row', gap: 10, marginBottom: 10}}>
|
||||
<TextInput style={[styles.editInput, {flex:1, color: cores.texto}]} value={dataInicio} onChangeText={setDataInicio} placeholder="Início (AAAA-MM-DD)"/>
|
||||
<TextInput style={[styles.editInput, {flex:1, color: cores.texto}]} value={dataFim} onChangeText={setDataFim} placeholder="Fim (AAAA-MM-DD)"/>
|
||||
</View>
|
||||
<View style={styles.badgeHoras}><Text style={styles.badgeText}>Total Diário: {totalHorasDiarias}</Text></View>
|
||||
</View>
|
||||
|
||||
<View style={[styles.confirmBox, { borderColor: cores.border, marginTop: 15 }]}>
|
||||
<View style={{flexDirection: 'row', justifyContent: 'space-between'}}>
|
||||
<Text style={styles.confirmTitle}>HORÁRIOS</Text>
|
||||
<TouchableOpacity onPress={() => setHorarios([...horarios, { periodo: 'Manhã', hora_inicio: '09:00', hora_fim: '13:00' }])}><Ionicons name="add-circle" size={24} color={cores.azul}/></TouchableOpacity>
|
||||
</View>
|
||||
{horarios.map((h, i) => (
|
||||
<View key={i} style={styles.horarioRow}>
|
||||
<TextInput style={[styles.miniInput, {color: cores.texto, flex: 1.2}]} value={h.periodo} onChangeText={(t) => { const n = [...horarios]; n[i].periodo = t; setHorarios(n); }} />
|
||||
<TextInput style={[styles.miniInput, {color: cores.texto}]} value={h.hora_inicio} onChangeText={(t) => { const n = [...horarios]; n[i].hora_inicio = t; setHorarios(n); }} />
|
||||
<Text style={{color: cores.textoSecundario}}>às</Text>
|
||||
<TextInput style={[styles.miniInput, {color: cores.texto}]} value={h.hora_fim} onChangeText={(t) => { const n = [...horarios]; n[i].hora_fim = t; setHorarios(n); }} />
|
||||
<TouchableOpacity onPress={() => setHorarios(horarios.filter((_, idx) => idx !== i))}><Ionicons name="close-circle" size={20} color={cores.vermelho} /></TouchableOpacity>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
<View style={[styles.confirmBox, { borderColor: cores.border, marginTop: 15 }]}>
|
||||
<Text style={styles.confirmTitle}>TUTOR</Text>
|
||||
<TextInput style={[styles.editInput, {color: cores.texto}]} value={empresaSelecionada?.tutor_nome} onChangeText={(t) => setEmpresaSelecionada(p => p ? {...p, tutor_nome: t}:p)} placeholder="Nome"/>
|
||||
<TextInput style={[styles.editInput, {color: cores.texto, marginTop: 8}]} value={empresaSelecionada?.tutor_telefone} onChangeText={(t) => setEmpresaSelecionada(p => p ? {...p, tutor_telefone: t}:p)} keyboardType="phone-pad" placeholder="Telefone"/>
|
||||
</View>
|
||||
|
||||
<View style={styles.modalFooter}>
|
||||
<TouchableOpacity
|
||||
onPress={() => editandoEstagio ? handleFecharModal() : setPasso(1)}
|
||||
style={[styles.btnModal, {backgroundColor: cores.textoSecundario}]}
|
||||
>
|
||||
<Text style={{color:'#fff'}}>{editandoEstagio ? 'Cancelar' : 'Voltar'}</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity onPress={salvarEstagio} style={[styles.btnModal, {backgroundColor: cores.azul}]}><Text style={{color:'#fff'}}>Gravar</Text></TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</ScrollView>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
// Estilos
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1 },
|
||||
content: { padding: 24 },
|
||||
header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 },
|
||||
title: { fontSize: 24, fontWeight: '700' },
|
||||
searchInput: { borderWidth: 1, borderRadius: 12, padding: 12, fontSize: 16, marginTop: 12 },
|
||||
card: { padding: 16, borderRadius: 14, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12, elevation: 2 },
|
||||
cardTitle: { fontSize: 16, fontWeight: '700' },
|
||||
cardSubtitle: { fontSize: 14 },
|
||||
newButton: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', padding: 16, borderRadius: 14, marginTop: 16 },
|
||||
newButtonText: { color: '#fff', fontWeight: '700', marginLeft: 8 },
|
||||
modalOverlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.4)', justifyContent: 'center', padding: 24 },
|
||||
modalContent: { borderRadius: 16, padding: 20 },
|
||||
modalTitle: { fontSize: 18, fontWeight: '700', marginBottom: 12 },
|
||||
modalLabel: { fontSize: 14, fontWeight: '600', marginBottom: 8 },
|
||||
modalOption: { padding: 12, borderRadius: 10, marginBottom: 6 },
|
||||
modalButton: { padding: 12, borderRadius: 12, flex: 1, alignItems: 'center', marginHorizontal: 4 }
|
||||
});
|
||||
container: {
|
||||
flex: 1,
|
||||
paddingTop: Platform.OS === 'android' ? StatusBar.currentHeight : 0
|
||||
},
|
||||
header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', padding: 20 },
|
||||
title: { fontSize: 22, fontWeight: 'bold' },
|
||||
input: { borderWidth: 1, borderRadius: 12, padding: 12, marginBottom: 15 },
|
||||
card: { padding: 16, borderRadius: 15, flexDirection: 'row', alignItems: 'center', marginBottom: 10, elevation: 2 },
|
||||
cardTitle: { fontSize: 16, fontWeight: 'bold' },
|
||||
btnNovo: { padding: 18, borderRadius: 12, alignItems: 'center', marginTop: 10 },
|
||||
modalOverlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.5)', justifyContent: 'center', padding: 20 },
|
||||
modalContent: { borderRadius: 20, padding: 20, maxHeight: '90%' },
|
||||
modalTitle: { fontSize: 18, fontWeight: 'bold', marginBottom: 20, textAlign: 'center' },
|
||||
label: { fontSize: 14, fontWeight: 'bold', marginBottom: 5 },
|
||||
selector: { borderWidth: 1, borderColor: '#eee', borderRadius: 10, overflow: 'hidden' },
|
||||
groupHead: { fontSize: 11, backgroundColor: 'rgba(0,0,0,0.05)', padding: 6, fontWeight: 'bold', color: '#666' },
|
||||
item: { padding: 12, borderBottomWidth: 0.5, borderColor: '#eee' },
|
||||
modalFooter: { flexDirection: 'row', gap: 10, marginTop: 25 },
|
||||
btnModal: { flex: 1, padding: 15, borderRadius: 12, alignItems: 'center' },
|
||||
confirmBox: { borderWidth: 1, borderRadius: 12, padding: 15, backgroundColor: 'rgba(0,0,0,0.02)' },
|
||||
confirmTitle: { fontSize: 10, fontWeight: '900', color: '#0d6efd', marginBottom: 8, letterSpacing: 1 },
|
||||
editInput: { borderBottomWidth: 1, paddingVertical: 5, fontSize: 14, borderColor: '#eee' },
|
||||
horarioRow: { flexDirection: 'row', alignItems: 'center', gap: 8, marginTop: 10 },
|
||||
miniInput: { borderBottomWidth: 1, borderColor: '#eee', flex: 1, padding: 4, fontSize: 12, textAlign: 'center' },
|
||||
badgeHoras: { backgroundColor: '#0d6efd', padding: 5, borderRadius: 6, alignSelf: 'flex-start' },
|
||||
badgeText: { color: '#fff', fontSize: 11, fontWeight: 'bold' }
|
||||
});
|
||||
@@ -1,9 +1,11 @@
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { useRouter } from 'expo-router';
|
||||
import { useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Alert,
|
||||
Platform,
|
||||
Platform // Importado para detetar o sistema operativo
|
||||
,
|
||||
SafeAreaView,
|
||||
ScrollView,
|
||||
StatusBar,
|
||||
@@ -11,147 +13,190 @@ import {
|
||||
Text,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
View
|
||||
} from 'react-native';
|
||||
import { useTheme } from '../../themecontext'; // Contexto global do tema
|
||||
import { useTheme } from '../../themecontext';
|
||||
import { supabase } from '../lib/supabase';
|
||||
|
||||
interface PerfilData {
|
||||
id: string;
|
||||
nome: string;
|
||||
email: string;
|
||||
n_escola: string;
|
||||
telefone: string;
|
||||
residencia: string;
|
||||
tipo: string;
|
||||
area: string;
|
||||
idade?: number;
|
||||
}
|
||||
|
||||
export default function PerfilProfessor() {
|
||||
const router = useRouter();
|
||||
const { isDarkMode } = useTheme(); // pega do contexto global
|
||||
const { isDarkMode } = useTheme();
|
||||
const [editando, setEditando] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [perfil, setPerfil] = useState<PerfilData | null>(null);
|
||||
|
||||
// Cores dinamicas baseadas no tema
|
||||
const cores = {
|
||||
fundo: isDarkMode ? '#121212' : '#f1f3f5',
|
||||
card: isDarkMode ? '#1e1e1e' : '#fff',
|
||||
texto: isDarkMode ? '#fff' : '#212529',
|
||||
textoSecundario: isDarkMode ? '#adb5bd' : '#6c757d',
|
||||
inputBg: isDarkMode ? '#1e1e1e' : '#f8f9fa',
|
||||
inputBg: isDarkMode ? '#2c2c2c' : '#f8f9fa',
|
||||
border: isDarkMode ? '#343a40' : '#ced4da',
|
||||
azul: '#0d6efd',
|
||||
vermelho: '#dc3545',
|
||||
};
|
||||
|
||||
const [perfil, setPerfil] = useState({
|
||||
nome: 'João Miranda',
|
||||
email: 'joao.miranda@epvc.pt',
|
||||
mecanografico: 'PROF-174',
|
||||
area: 'Informática',
|
||||
turmas: '12ºINF | 11ºINF | 10ºINF',
|
||||
funcao: 'Orientador de Estágio',
|
||||
});
|
||||
useEffect(() => {
|
||||
carregarPerfil();
|
||||
}, []);
|
||||
|
||||
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);
|
||||
}
|
||||
} catch (error: any) {
|
||||
Alert.alert('Erro', 'Não foi possível carregar os dados do perfil.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
const guardarPerfil = async () => {
|
||||
if (!perfil) return;
|
||||
try {
|
||||
const { error } = await supabase
|
||||
.from('profiles')
|
||||
.update({
|
||||
nome: perfil.nome,
|
||||
telefone: perfil.telefone,
|
||||
residencia: perfil.residencia,
|
||||
n_escola: perfil.n_escola,
|
||||
area: perfil.area
|
||||
})
|
||||
.eq('id', perfil.id);
|
||||
if (error) throw error;
|
||||
setEditando(false);
|
||||
Alert.alert('Sucesso', 'Perfil atualizado!');
|
||||
} catch (error: any) {
|
||||
Alert.alert('Erro', error.message);
|
||||
}
|
||||
};
|
||||
|
||||
const terminarSessao = async () => {
|
||||
await supabase.auth.signOut();
|
||||
router.replace('/');
|
||||
};
|
||||
|
||||
const guardarPerfil = () => {
|
||||
setEditando(false);
|
||||
Alert.alert('Sucesso', 'Perfil atualizado com sucesso!');
|
||||
// depois liga ao Supabase update
|
||||
};
|
||||
if (loading) {
|
||||
return (
|
||||
<View style={[styles.centered, { backgroundColor: cores.fundo }]}>
|
||||
<ActivityIndicator size="large" color={cores.azul} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeAreaView
|
||||
style={[
|
||||
styles.container,
|
||||
{ backgroundColor: cores.fundo, paddingTop: Platform.OS === 'android' ? StatusBar.currentHeight : 0 },
|
||||
]}
|
||||
>
|
||||
<StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} backgroundColor={cores.fundo} />
|
||||
<ScrollView contentContainerStyle={styles.content}>
|
||||
{/* TOPO COM VOLTAR */}
|
||||
// AJUSTE DE SAFE AREA AQUI: paddingTop dinâmico para Android e iOS
|
||||
<SafeAreaView style={[styles.safeArea, { backgroundColor: cores.fundo }]}>
|
||||
<StatusBar
|
||||
barStyle={isDarkMode ? 'light-content' : 'dark-content'}
|
||||
backgroundColor="transparent"
|
||||
translucent
|
||||
/>
|
||||
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.content}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
{/* TOPO COM ESPAÇAMENTO PARA NOTIFICAÇÕES */}
|
||||
<View style={styles.topBar}>
|
||||
<TouchableOpacity onPress={() => router.push('/Professor/ProfessorHome')}>
|
||||
<TouchableOpacity onPress={() => router.back()}>
|
||||
<Ionicons name="arrow-back-outline" size={26} color={cores.texto} />
|
||||
</TouchableOpacity>
|
||||
<Text style={[styles.topTitle, { color: cores.texto }]}>Perfil</Text>
|
||||
<Text style={[styles.topTitle, { color: cores.texto }]}>O Meu Perfil</Text>
|
||||
<View style={{ width: 26 }} />
|
||||
</View>
|
||||
|
||||
{/* HEADER */}
|
||||
{/* HEADER PERFIL */}
|
||||
<View style={styles.header}>
|
||||
<View style={[styles.avatar, { backgroundColor: cores.azul }]}>
|
||||
<Ionicons name="person" size={48} color="#fff" />
|
||||
</View>
|
||||
<Text style={[styles.name, { color: cores.texto }]}>{perfil.nome}</Text>
|
||||
<Text style={[styles.role, { color: cores.textoSecundario }]}>{perfil.funcao}</Text>
|
||||
<Text style={[styles.name, { color: cores.texto }]}>{perfil?.nome}</Text>
|
||||
<Text style={[styles.role, { color: cores.textoSecundario }]}>
|
||||
{perfil?.area || 'Professor Orientador'}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* INFORMAÇÕES */}
|
||||
{/* CAMPOS DE INFORMAÇÃO */}
|
||||
<View style={[styles.card, { backgroundColor: cores.card }]}>
|
||||
<InfoField
|
||||
label="Nome"
|
||||
value={perfil.nome}
|
||||
label="Nome Completo"
|
||||
value={perfil?.nome || ''}
|
||||
editable={editando}
|
||||
onChange={(v: string) => setPerfil({ ...perfil, nome: v })}
|
||||
cores={cores}
|
||||
/>
|
||||
<InfoField label="Email" value={perfil.email} editable={false} cores={cores} />
|
||||
<InfoField
|
||||
label="Nº Mecanográfico"
|
||||
value={perfil.mecanografico}
|
||||
editable={editando}
|
||||
onChange={(v: string) => setPerfil({ ...perfil, mecanografico: v })}
|
||||
onChange={(v: string) => setPerfil(prev => prev ? { ...prev, nome: v } : null)}
|
||||
cores={cores}
|
||||
/>
|
||||
<InfoField
|
||||
label="Área"
|
||||
value={perfil.area}
|
||||
label="Área / Departamento"
|
||||
value={perfil?.area || ''}
|
||||
editable={editando}
|
||||
onChange={(v: string) => setPerfil({ ...perfil, area: v })}
|
||||
onChange={(v: string) => setPerfil(prev => prev ? { ...prev, area: v } : null)}
|
||||
cores={cores}
|
||||
/>
|
||||
<InfoField label="Email" value={perfil?.email || ''} editable={false} cores={cores} />
|
||||
<InfoField
|
||||
label="Nº Escola"
|
||||
value={perfil?.n_escola || ''}
|
||||
editable={editando}
|
||||
onChange={(v: string) => setPerfil(prev => prev ? { ...prev, n_escola: v } : null)}
|
||||
cores={cores}
|
||||
/>
|
||||
<InfoField
|
||||
label="Turmas"
|
||||
value={perfil.turmas}
|
||||
label="Telefone"
|
||||
value={perfil?.telefone || ''}
|
||||
editable={editando}
|
||||
onChange={(v: string) => setPerfil({ ...perfil, turmas: v })}
|
||||
onChange={(v: string) => setPerfil(prev => prev ? { ...prev, telefone: v } : null)}
|
||||
cores={cores}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* AÇÕES */}
|
||||
{/* BOTÕES DE AÇÃO */}
|
||||
<View style={styles.actions}>
|
||||
{editando ? (
|
||||
<TouchableOpacity style={[styles.primaryButton, { backgroundColor: cores.azul }]} onPress={guardarPerfil}>
|
||||
<Ionicons name="save-outline" size={20} color="#fff" />
|
||||
<Text style={styles.primaryText}>Guardar alterações</Text>
|
||||
<Text style={styles.primaryText}>Guardar Alterações</Text>
|
||||
</TouchableOpacity>
|
||||
) : (
|
||||
<ActionButton icon="create-outline" text="Editar perfil" onPress={() => setEditando(true)} cores={cores} />
|
||||
<TouchableOpacity style={[styles.actionButton, { backgroundColor: cores.card }]} onPress={() => setEditando(true)}>
|
||||
<Ionicons name="create-outline" size={20} color={cores.azul} />
|
||||
<Text style={[styles.actionText, { color: cores.texto }]}>Editar Perfil</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
|
||||
<ActionButton
|
||||
icon="key-outline"
|
||||
text="Alterar palavra-passe"
|
||||
onPress={() => router.push('/Professor/redefenirsenha2')}
|
||||
cores={cores}
|
||||
/>
|
||||
|
||||
<ActionButton icon="log-out-outline" text="Terminar sessão" danger onPress={terminarSessao} cores={cores} />
|
||||
<TouchableOpacity style={[styles.actionButton, { backgroundColor: cores.card }]} onPress={terminarSessao}>
|
||||
<Ionicons name="log-out-outline" size={20} color={cores.vermelho} />
|
||||
<Text style={[styles.actionText, { color: cores.vermelho }]}>Terminar Sessão</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
/* COMPONENTES */
|
||||
function InfoField({
|
||||
label,
|
||||
value,
|
||||
editable,
|
||||
onChange,
|
||||
cores,
|
||||
}: {
|
||||
label: string;
|
||||
value: string;
|
||||
editable: boolean;
|
||||
onChange?: (v: string) => void;
|
||||
cores: any;
|
||||
}) {
|
||||
function InfoField({ label, value, editable, onChange, cores }: any) {
|
||||
return (
|
||||
<View style={styles.infoField}>
|
||||
<Text style={[styles.label, { color: cores.textoSecundario }]}>{label}</Text>
|
||||
@@ -162,71 +207,41 @@ function InfoField({
|
||||
style={[styles.input, { backgroundColor: cores.inputBg, color: cores.texto, borderColor: cores.border }]}
|
||||
/>
|
||||
) : (
|
||||
<Text style={[styles.value, { color: cores.texto }]}>{value}</Text>
|
||||
<Text style={[styles.value, { color: cores.texto }]}>{value || 'Não definido'}</Text>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
function ActionButton({
|
||||
icon,
|
||||
text,
|
||||
onPress,
|
||||
danger = false,
|
||||
cores,
|
||||
}: {
|
||||
icon: any;
|
||||
text: string;
|
||||
onPress: () => void;
|
||||
danger?: boolean;
|
||||
cores: any;
|
||||
}) {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.actionButton,
|
||||
{ backgroundColor: cores.card },
|
||||
danger && { borderWidth: 1, borderColor: cores.vermelho },
|
||||
]}
|
||||
onPress={onPress}
|
||||
>
|
||||
<Ionicons name={icon} size={20} color={danger ? cores.vermelho : cores.azul} />
|
||||
<Text
|
||||
style={[
|
||||
styles.actionText,
|
||||
danger && { color: cores.vermelho },
|
||||
!danger && cores.texto === '#fff' && { color: '#fff' }, // texto branco no modo escuro
|
||||
]}
|
||||
>
|
||||
{text}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
|
||||
/* ESTILOS */
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1 },
|
||||
content: { padding: 24 },
|
||||
|
||||
topBar: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 20 },
|
||||
// Estilo principal da Safe Area
|
||||
safeArea: {
|
||||
flex: 1,
|
||||
// No Android, a barra de notificações não é respeitada automaticamente pelo SafeAreaView
|
||||
paddingTop: Platform.OS === 'android' ? StatusBar.currentHeight : 0
|
||||
},
|
||||
centered: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
||||
content: { padding: 24, paddingBottom: 40 },
|
||||
topBar: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
marginBottom: 20,
|
||||
marginTop: 10 // Margem extra de segurança
|
||||
},
|
||||
topTitle: { fontSize: 18, fontWeight: '700' },
|
||||
|
||||
header: { alignItems: 'center', marginBottom: 32 },
|
||||
avatar: { width: 90, height: 90, borderRadius: 45, alignItems: 'center', justifyContent: 'center', marginBottom: 12 },
|
||||
name: { fontSize: 22, fontWeight: '800' },
|
||||
role: { fontSize: 14, marginTop: 4 },
|
||||
|
||||
card: { borderRadius: 18, padding: 20, marginBottom: 24, elevation: 4 },
|
||||
|
||||
infoField: { marginBottom: 16 },
|
||||
label: { fontSize: 12, marginBottom: 4 },
|
||||
value: { fontSize: 15, fontWeight: '600' },
|
||||
input: { borderWidth: 1, borderRadius: 10, padding: 10, fontSize: 15 },
|
||||
|
||||
label: { fontSize: 11, marginBottom: 4, textTransform: 'uppercase', fontWeight: '600' },
|
||||
value: { fontSize: 16, fontWeight: '600' },
|
||||
input: { borderWidth: 1, borderRadius: 10, padding: 12, fontSize: 15 },
|
||||
actions: { gap: 12 },
|
||||
actionButton: { flexDirection: 'row', alignItems: 'center', borderRadius: 14, padding: 16 },
|
||||
actionText: { fontSize: 15, fontWeight: '600', marginLeft: 12 },
|
||||
primaryButton: { borderRadius: 14, padding: 16, flexDirection: 'row', justifyContent: 'center', alignItems: 'center' },
|
||||
primaryText: { color: '#fff', fontWeight: '700', marginLeft: 8 },
|
||||
});
|
||||
});
|
||||
@@ -16,51 +16,44 @@ export default function LoginScreen() {
|
||||
|
||||
const handleLoginSuccess = async () => {
|
||||
try {
|
||||
// 1️⃣ Obter utilizador autenticado
|
||||
// Buscar utilizador autenticado
|
||||
const {
|
||||
data: { user },
|
||||
error: userError,
|
||||
} = await supabase.auth.getUser();
|
||||
|
||||
if (userError || !user) {
|
||||
console.log('Erro ao obter utilizador:', userError);
|
||||
Alert.alert('Erro', 'Utilizador não autenticado');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Utilizador autenticado:', user.id);
|
||||
|
||||
// 2️⃣ Buscar tipo do utilizador
|
||||
// Buscar perfil
|
||||
const { data, error } = await supabase
|
||||
.from('profiles')
|
||||
.select('tipo')
|
||||
.eq('id', user.id)
|
||||
.single();
|
||||
.maybeSingle();
|
||||
|
||||
if (error) {
|
||||
console.log('Erro ao buscar perfil:', error);
|
||||
Alert.alert('Erro', error.message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
console.log('Perfil não encontrado');
|
||||
Alert.alert('Erro', 'Perfil não encontrado');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Tipo de utilizador:', data.tipo);
|
||||
|
||||
// 3️⃣ Redirecionar conforme o tipo
|
||||
// Redirecionar
|
||||
if (data.tipo === 'professor') {
|
||||
router.replace('/Professor/ProfessorHome');
|
||||
} else if (data.tipo === 'aluno') {
|
||||
router.replace('/Aluno/AlunoHome');
|
||||
} else {
|
||||
Alert.alert('Erro', 'Tipo de utilizador inválido');
|
||||
Alert.alert('Erro', 'Tipo inválido');
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error('Erro inesperado:', err);
|
||||
Alert.alert('Erro', 'Erro inesperado no login');
|
||||
}
|
||||
};
|
||||
@@ -82,7 +75,6 @@ export default function LoginScreen() {
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* COMPONENTE DE LOGIN */}
|
||||
<Auth onLoginSuccess={handleLoginSuccess} />
|
||||
</View>
|
||||
</ScrollView>
|
||||
@@ -116,4 +108,4 @@ const styles = StyleSheet.create({
|
||||
color: '#636e72',
|
||||
textAlign: 'center',
|
||||
},
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user