From 0a57a3d8ba6c9e78396617bcbadc1168e28c4816 Mon Sep 17 00:00:00 2001 From: Ricardo Gomes <230413@epvc.pt> Date: Mon, 4 May 2026 15:03:11 +0100 Subject: [PATCH] hjjhjhgj --- app/Empresas/EmpresaHome.tsx | 274 ++++++++++++++++++++++++++ app/Professor/Alunos/CriarRegisto.tsx | 18 +- app/Professor/ProfessorHome.tsx | 2 + 3 files changed, 285 insertions(+), 9 deletions(-) create mode 100644 app/Empresas/EmpresaHome.tsx diff --git a/app/Empresas/EmpresaHome.tsx b/app/Empresas/EmpresaHome.tsx new file mode 100644 index 0000000..0dee0a2 --- /dev/null +++ b/app/Empresas/EmpresaHome.tsx @@ -0,0 +1,274 @@ +import { Ionicons } from '@expo/vector-icons'; +import { useFocusEffect } from '@react-navigation/native'; +import { useRouter } from 'expo-router'; +import { useCallback, useMemo, useState } from 'react'; +import { + ActivityIndicator, + Alert, + Platform, + RefreshControl, + SafeAreaView, + ScrollView, + StatusBar, + StyleSheet, + Text, + TouchableOpacity, + View +} from 'react-native'; +import { supabase } from '../../lib/supabase'; +import { useTheme } from '../../themecontext'; + +export default function EmpresaHome() { + const { isDarkMode } = useTheme(); + const router = useRouter(); + + const [pendentes, setPendentes] = useState([]); + const [loading, setLoading] = useState(true); + const [refreshing, setRefreshing] = useState(false); + const [empresaNome, setEmpresaNome] = useState(''); + + const themeStyles = useMemo(() => ({ + fundo: isDarkMode ? '#0F0F0F' : '#F8FAFC', + card: isDarkMode ? '#1A1A1A' : '#FFFFFF', + texto: isDarkMode ? '#F8FAFC' : '#1E293B', + textoSecundario: isDarkMode ? '#94A3B8' : '#64748B', + borda: isDarkMode ? '#2D2D2D' : '#E2E8F0', + azul: '#2390a6', + laranja: '#dd8707', + verde: '#10B981', + vermelho: '#EF4444', + vermelhoSuave: isDarkMode ? 'rgba(239, 68, 68, 0.15)' : 'rgba(239, 68, 68, 0.1)', + inputFundo: isDarkMode ? '#252525' : '#F1F5F9', + }), [isDarkMode]); + + const fetchValidaçõesPendentes = async (isManualRefresh = false) => { + if (!isManualRefresh) setLoading(true); + try { + const { data: { user } } = await supabase.auth.getUser(); + if (!user) return; + + // 1. Identificar quem é a empresa que tem o login feito + const { data: empresa } = await supabase + .from('empresas') + .select('id, nome') + .eq('user_id', user.id) + .single(); + + if (!empresa) { + setPendentes([]); + return; + } + setEmpresaNome(empresa.nome); + + // 2. Buscar todos os estágios ligados a esta empresa + const { data: estagios } = await supabase + .from('estagios') + .select('aluno_id') + .eq('empresa_id', empresa.id); + + if (!estagios || estagios.length === 0) { + setPendentes([]); + return; + } + + const alunoIds = estagios.map(e => e.aluno_id); + + // 3. Buscar os nomes dos alunos (para o tutor saber quem está a avaliar) + const { data: alunos } = await supabase + .from('alunos') + .select('id, nome') + .in('id', alunoIds); + + const mapaAlunos: Record = {}; + alunos?.forEach(a => { mapaAlunos[a.id] = a.nome; }); + + // 4. Buscar apenas as presenças que estão PENDENTES para estes alunos + const { data: presencas } = await supabase + .from('presencas') + .select('*') + .in('aluno_id', alunoIds) + .eq('estado', 'presente') + .eq('estado_tutor', 'pendente') + .order('data', { ascending: false }); + + const listaFormatada = presencas?.map(p => ({ + ...p, + aluno_nome: mapaAlunos[p.aluno_id] || 'Aluno Desconhecido' + })) || []; + + setPendentes(listaFormatada); + } catch (error) { + console.error(error); + Alert.alert("Erro", "Falha ao carregar as validações pendentes."); + } finally { + if (!isManualRefresh) setLoading(false); + setRefreshing(false); + } + }; + + // Atualiza sempre que o ecrã ganha foco + useFocusEffect(useCallback(() => { fetchValidaçõesPendentes(); }, [])); + + const onRefresh = useCallback(() => { + setRefreshing(true); + fetchValidaçõesPendentes(true); + }, []); + + // 🟢 FUNÇÃO PARA APROVAR OU REJEITAR + const lidarComPresenca = async (aluno_id: string, data: string, decisao: 'aprovado' | 'rejeitado') => { + try { + const { error } = await supabase + .from('presencas') + .update({ estado_tutor: decisao }) + .match({ aluno_id, data }); // Dá match exato ao aluno e àquele dia + + if (error) throw error; + + // Avisa visualmente do sucesso e limpa aquele cartão da lista + if (decisao === 'aprovado') { + Alert.alert("✅ Validado", "Horas e sumário aprovados com sucesso!"); + } else { + Alert.alert("❌ Rejeitado", "O registo foi devolvido ao aluno para correção."); + } + + // Atualiza a lista removendo o que acabou de ser processado + setPendentes(prev => prev.filter(p => !(p.aluno_id === aluno_id && p.data === data))); + + } catch (e: any) { + Alert.alert("Erro ao validar", e.message); + } + }; + + // Formatar data (AAAA-MM-DD -> DD/MM/AAAA) + const formatarData = (dataStr: string) => { + if (!dataStr) return ''; + const parts = dataStr.split('-'); + if (parts.length !== 3) return dataStr; + return `${parts[2]}/${parts[1]}/${parts[0]}`; + }; + + return ( + + + + + + Painel da Entidade + {empresaNome || 'A carregar...'} + + supabase.auth.signOut().then(() => router.replace('/'))}> + + + + + + Validações Pendentes + + {pendentes.length} + + + + {loading && !refreshing ? ( + + + + ) : ( + } + > + {pendentes.length === 0 ? ( + + + Tudo em dia! + + Não tens sumários ou presenças de alunos a aguardar a tua validação neste momento. + + + ) : ( + pendentes.map((item, index) => ( + + + + + + {item.aluno_nome} + + + {formatarData(item.data)} + + + + POR VALIDAR + + + + + Sumário Submetido: + + {item.sumario ? item.sumario : "O aluno não escreveu sumário para este dia."} + + + + + lidarComPresenca(item.aluno_id, item.data, 'rejeitado')} + > + + Rejeitar + + + lidarComPresenca(item.aluno_id, item.data, 'aprovado')} + > + + Aprovar + + + + + )) + )} + + )} + + ); +} + +const styles = StyleSheet.create({ + safeArea: { flex: 1, paddingTop: Platform.OS === 'android' ? StatusBar.currentHeight : 0 }, + centerBox: { flex: 1, justifyContent: 'center', alignItems: 'center' }, + topBar: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: 20, paddingTop: 20, paddingBottom: 10 }, + greeting: { fontSize: 13, fontWeight: '800', textTransform: 'uppercase', letterSpacing: 1 }, + title: { fontSize: 24, fontWeight: '900', marginTop: 2 }, + logoutBtn: { width: 45, height: 45, borderRadius: 14, borderWidth: 1, justifyContent: 'center', alignItems: 'center' }, + + headerTitleContainer: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 20, marginBottom: 15, gap: 10 }, + sectionTitle: { fontSize: 18, fontWeight: '800' }, + badge: { paddingHorizontal: 10, paddingVertical: 4, borderRadius: 12 }, + badgeText: { fontSize: 12, fontWeight: '900' }, + + scroll: { paddingHorizontal: 20, paddingBottom: 40 }, + + emptyBox: { alignItems: 'center', padding: 40, borderRadius: 24, borderWidth: 1, borderStyle: 'dashed', marginTop: 30 }, + emptyTitle: { fontSize: 20, fontWeight: '900', marginBottom: 8 }, + emptyDesc: { fontSize: 14, textAlign: 'center', lineHeight: 22, fontWeight: '500' }, + + card: { padding: 20, borderRadius: 24, borderWidth: 1, marginBottom: 20, elevation: 2, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.05, shadowRadius: 8 }, + cardHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 15 }, + alunoName: { fontSize: 17, fontWeight: '900', marginBottom: 4 }, + dataText: { fontSize: 13, fontWeight: '800' }, + statusTag: { paddingHorizontal: 8, paddingVertical: 4, borderRadius: 6 }, + statusTagText: { fontSize: 9, fontWeight: '900', letterSpacing: 0.5 }, + + sumarioBox: { padding: 15, borderRadius: 16, marginBottom: 15 }, + sumarioLabel: { fontSize: 10, fontWeight: '800', textTransform: 'uppercase', marginBottom: 6 }, + sumarioText: { fontSize: 14, fontWeight: '600', lineHeight: 20 }, + + actionRow: { flexDirection: 'row', gap: 12 }, + btnAction: { flex: 1, flexDirection: 'row', height: 48, borderRadius: 14, justifyContent: 'center', alignItems: 'center', gap: 6 }, + btnActionText: { fontSize: 14, fontWeight: '800' } +}); \ No newline at end of file diff --git a/app/Professor/Alunos/CriarRegisto.tsx b/app/Professor/Alunos/CriarRegisto.tsx index 2f9bfb9..74efb7e 100644 --- a/app/Professor/Alunos/CriarRegisto.tsx +++ b/app/Professor/Alunos/CriarRegisto.tsx @@ -195,7 +195,7 @@ const CriarAluno = () => { const { error: alunoError } = await supabase .from('alunos') .insert([{ - profile_id: user.id, // 🟢 CORREÇÃO AQUI: perfil_id igual ao do teu diagrama + profile_id: user.id, nome, n_escola: nEscola, ano: ano ? parseInt(ano) : null, @@ -214,7 +214,7 @@ const CriarAluno = () => { tutor_nome: nome, tutor_telefone: telefone, curso: curso.toUpperCase() - // 🟢 CORREÇÃO AQUI: Removi user_id e setor que não existem no diagrama + }]); if (empresaError) throw empresaError; } @@ -258,7 +258,7 @@ const CriarAluno = () => { Novo Registo - Sistema Central + Estágios+ @@ -307,7 +307,7 @@ const CriarAluno = () => { {/* DADOS DA EMPRESA */} {tipo === 'empresa' && ( - + { { {tipo !== 'empresa' && ( setDataNascimento(aplicarMascaraData(t))} placeholder="Data de Nascimento (DD-MM-AAAA)" maxLength={10} keyboardType="numeric" placeholderTextColor={cores.placeholder} + value={dataNascimento} onChangeText={(t) => setDataNascimento(aplicarMascaraData(t))} placeholder="Data de Nascimento" maxLength={10} keyboardType="numeric" placeholderTextColor={cores.placeholder} /> @@ -369,7 +369,7 @@ const CriarAluno = () => { { diff --git a/app/Professor/ProfessorHome.tsx b/app/Professor/ProfessorHome.tsx index a7a4813..140923d 100644 --- a/app/Professor/ProfessorHome.tsx +++ b/app/Professor/ProfessorHome.tsx @@ -127,6 +127,8 @@ export default function ProfessorHome() { router.push('/Professor/Alunos/Presencas')} cores={cores} corDestaque={cores.azul} /> router.push('/Professor/Alunos/Sumarios')} cores={cores} corDestaque={cores.laranja} /> router.push('/Professor/Alunos/Faltas')} cores={cores} corDestaque="#EF4444" /> + router.push('')} cores={cores} corDestaque={cores.azul} /> +