diff --git a/app/Aluno/AlunoHome.tsx b/app/Aluno/AlunoHome.tsx index f2b63bd..3162140 100644 --- a/app/Aluno/AlunoHome.tsx +++ b/app/Aluno/AlunoHome.tsx @@ -387,12 +387,6 @@ const AlunoHome = memo(() => { return { bg: themeStyles.verde + '20', text: themeStyles.verde, label: 'A DECORRER' }; }; const badgeObj = getBadgeStyle(); - const horasTotais = estagioDetalhes?.horas_totais || 0; - - const horasPorDia = Number(estagioDetalhes?.horas_diarias || 0); - const horasConcluidas = statsFaltas.totalPresencas * horasPorDia; - - const horasEmFalta = Math.max(0, horasTotais - horasConcluidas); const renderAvisoEstadoDia = () => { const reg = registosDiarios[selectedDate]; @@ -408,7 +402,6 @@ const badgeObj = getBadgeStyle(); if (!reg) { // Se o dia não for válido para estágio ou for no futuro, não mostra nada if (infoData.foraDeRange || !infoData.valida || selectedDate > hojeStr) return null; - // Se for um dia válido que já passou e está vazio, usa a config base (Azul) } else if (reg.estado === 'presente') { const temSumario = reg.sumario && reg.sumario.trim() !== ''; @@ -430,24 +423,18 @@ const badgeObj = getBadgeStyle(); } return ( - + - - {config.texto} - + {config.texto} ); }; + const horasTotais = Number(estagioDetalhes?.horas_totais) || 0; + const horasPorDia = Number(estagioDetalhes?.horas_diarias) || 0; + const horasConcluidas = (statsFaltas?.totalPresencas || 0) * horasPorDia; + const horasEmFalta = Math.max(0, horasTotais - horasConcluidas); + const regSelecionado = registosDiarios[selectedDate]; return ( @@ -542,16 +529,16 @@ const badgeObj = getBadgeStyle(); {activeTab === 'horas' && ( - + + {/* LINHA DE CIMA: CÁLCULO DAS HORAS */} + REALIZADAS - {/* 🟢 AGORA USA A VARIÁVEL CALCULADA */} {horasConcluidas}h EM FALTA - {/* 🟢 AGORA USA A VARIÁVEL CALCULADA */} {horasEmFalta}h @@ -559,12 +546,15 @@ const badgeObj = getBadgeStyle(); TOTAIS {horasTotais}h - + + + {/* LINHA DE BAIXO: ESTATÍSTICA DE PRESENÇAS E FALTAS */} - APROVADAS + {/* Corrigido para PRESENÇAS */} + PRESENÇAS {statsFaltas.totalPresencas} diff --git a/app/Empresa/EmpresaHome.tsx b/app/Empresa/EmpresaHome.tsx deleted file mode 100644 index 0c92942..0000000 --- a/app/Empresa/EmpresaHome.tsx +++ /dev/null @@ -1,482 +0,0 @@ -// app/Empresa/EmpresaHome.tsx -import { Ionicons } from '@expo/vector-icons'; -import { useFocusEffect } from '@react-navigation/native'; -import { useRouter } from 'expo-router'; -import { useCallback, useMemo, useRef, useState } from 'react'; -import { - ActivityIndicator, - Alert, - Animated, - Linking, - Modal, - Platform, - RefreshControl, - SafeAreaView, - ScrollView, - StatusBar, - StyleSheet, - Text, - TouchableOpacity, - View -} from 'react-native'; -import { supabase } from '../../lib/supabase'; -import { useTheme } from '../../themecontext'; - -// Tipos de Ecrã possíveis neste "Tudo-em-Um" -type AppScreen = 'DASHBOARD' | 'ALUNOS' | 'PEDIDOS_LISTA' | 'PEDIDOS_HISTORICO' | 'AVALIACOES' | 'DEFINICOES'; - -export default function EmpresaHome() { - const { isDarkMode } = useTheme(); - const router = useRouter(); - - // ESTADOS DE NAVEGAÇÃO E DADOS - const [currentScreen, setCurrentScreen] = useState('DASHBOARD'); - const [loading, setLoading] = useState(true); - const [refreshing, setRefreshing] = useState(false); - - const [empresaData, setEmpresaData] = useState(null); - const [listaAlunos, setListaAlunos] = useState([]); - const [presencasGerais, setPresencasGerais] = useState([]); - - // ESTADOS PARA MODAIS E SELEÇÕES - const [alunoSelecionado, setAlunoSelecionado] = useState(null); - const [modalDetalhesAluno, setModalDetalhesAluno] = useState(false); - - // TOAST ANIMADO - const [toast, setToast] = useState<{ visible: boolean; message: string; type: 'error' | 'success' | 'info' }>({ visible: false, message: '', type: 'info' }); - const slideAnim = useRef(new Animated.Value(-100)).current; - - const themeStyles = useMemo(() => ({ - fundo: isDarkMode ? '#0A0A0A' : '#F4F7FA', - card: isDarkMode ? '#161618' : '#FFFFFF', - texto: isDarkMode ? '#F8FAFC' : '#1E293B', - secundario: isDarkMode ? '#94A3B8' : '#64748B', - borda: isDarkMode ? '#2D2D2D' : '#E2E8F0', - azul: '#2390a6', - laranja: '#E38E00', - verde: '#10B981', - vermelho: '#EF4444', - azulSuave: isDarkMode ? 'rgba(35, 144, 166, 0.15)' : '#E0F2F4', - vermelhoSuave: isDarkMode ? 'rgba(239, 68, 68, 0.15)' : '#FEE2E2', - aviso: isDarkMode ? '#2D2200' : '#FFF9E6', - avisoTexto: isDarkMode ? '#FFD700' : '#856404' - }), [isDarkMode]); - - const showToast = useCallback((message: string, type: 'error' | 'success' | 'info' = 'info') => { - setToast({ visible: true, message, type }); - Animated.timing(slideAnim, { toValue: Platform.OS === 'ios' ? 50 : 20, duration: 300, useNativeDriver: true }).start(() => { - setTimeout(() => { - Animated.timing(slideAnim, { toValue: -100, duration: 300, useNativeDriver: true }) - .start(() => setToast({ visible: false, message: '', type: 'info' })); - }, 3000); - }); - }, [slideAnim]); - - // 🚀 BUSCAR TUDO DE UMA VEZ - const fetchTudo = async (isManualRefresh = false) => { - if (!isManualRefresh) setLoading(true); - try { - const { data: { user } } = await supabase.auth.getUser(); - if (!user) return; - - // 1. Empresa - const { data: empresa } = await supabase.from('empresas').select('*').eq('user_id', user.id).maybeSingle(); - if (!empresa) { - setLoading(false); - return Alert.alert("Erro", "Conta não associada a nenhuma empresa."); - } - setEmpresaData(empresa); - - // 2. Alunos - const { data: estagios } = await supabase.from('estagios').select('aluno_id').eq('empresa_id', empresa.id); - const alunoIds = estagios?.map(e => e.aluno_id) || []; - - if (alunoIds.length > 0) { - const { data: alunos } = await supabase.from('alunos').select('*').in('id', alunoIds); - setListaAlunos(alunos || []); - - // 3. Presenças e Faltas - const { data: presencas } = await supabase - .from('presencas') - .select('*') - .in('aluno_id', alunoIds) - .order('data', { ascending: false }); - - setPresencasGerais(presencas || []); - } else { - setListaAlunos([]); - setPresencasGerais([]); - } - - } catch (error) { - console.error(error); - showToast("Erro ao carregar dados", "error"); - } finally { - if (!isManualRefresh) setLoading(false); - setRefreshing(false); - } - }; - - useFocusEffect(useCallback(() => { fetchTudo(); }, [])); - - const onRefresh = useCallback(() => { - setRefreshing(true); - fetchTudo(true); - }, []); - - // 🟢 APROVAR OU RECUSAR (COM VERIFICAÇÃO ANTI-MENTIRAS DO SUPABASE) - const lidarComPresenca = async (id: string, decisao: 'aprovado' | 'rejeitado') => { - try { - if (decisao === 'rejeitado') { - // Tenta apagar e pede confirmação de volta (.select) - const { data, error } = await supabase.from('presencas').delete().eq('id', id).select(); - if (error) throw error; - if (!data || data.length === 0) throw new Error("A Base de Dados bloqueou a ação (Verifica se desligaste o RLS no Supabase)!"); - - showToast("Registo recusado e apagado!", "info"); - // Remove da lista local da empresa - setPresencasGerais(prev => prev.filter(p => p.id !== id)); - } else { - // Tenta atualizar e pede confirmação de volta (.select) - const { data, error } = await supabase.from('presencas').update({ estado_tutor: decisao }).eq('id', id).select(); - if (error) throw error; - if (!data || data.length === 0) throw new Error("A Base de Dados bloqueou a ação (Verifica se desligaste o RLS no Supabase)!"); - - showToast("Validado com sucesso!", "success"); - // Atualiza a lista local da empresa - setPresencasGerais(prev => prev.map(p => p.id === id ? { ...p, estado_tutor: decisao } : p)); - } - } catch (e: any) { - Alert.alert("Erro a Validar", e.message || "Não foi possível alterar o registo na Base de Dados."); - } - }; - - const formatarData = (dataStr: string) => { - if (!dataStr) return ''; - const parts = dataStr.split('-'); - return parts.length !== 3 ? dataStr : `${parts[2]}/${parts[1]}/${parts[0]}`; - }; - - // ========================================== - // COMPONENTES DOS DIFERENTES ECRÃS (VIEWS) - // ========================================== - - const renderDashboard = () => ( - - {/* CARD 1: ALUNOS */} - setCurrentScreen('ALUNOS')}> - - - - Alunos - Verificar estagiários e detalhes. - - - {/* CARD 2: PEDIDOS */} - setCurrentScreen('PEDIDOS_LISTA')}> - - - {presencasGerais.filter(p => p.estado_tutor === 'pendente').length > 0 && ( - - )} - - Pedidos - Validar sumários e faltas. - - - {/* CARD 3: AVALIAÇÕES */} - setCurrentScreen('AVALIACOES')}> - - - - Avaliações - Em breve. - - - {/* CARD 4: DEFINIÇÕES */} - setCurrentScreen('DEFINICOES')}> - - - - Definições - Gerir conta da empresa. - - - ); - - const renderAlunos = () => ( - - setCurrentScreen('DASHBOARD')}> - - Voltar ao Menu - - - Estagiários ({listaAlunos.length}) - - {listaAlunos.length === 0 ? ( - Nenhum aluno associado a esta empresa. - ) : ( - listaAlunos.map(aluno => ( - { setAlunoSelecionado(aluno); setModalDetalhesAluno(true); }} - > - - {aluno.nome.charAt(0)} - - - {aluno.nome} - Toque para ver detalhes - - - - )) - )} - - ); - - const renderPedidosLista = () => ( - - setCurrentScreen('DASHBOARD')}> - - Voltar ao Menu - - - Validar Registos - Selecione um aluno para ver o histórico e validar pedidos pendentes. - - {listaAlunos.map(aluno => { - const pendentesDoAluno = presencasGerais.filter(p => p.aluno_id === aluno.id && p.estado_tutor === 'pendente').length; - return ( - { setAlunoSelecionado(aluno); setCurrentScreen('PEDIDOS_HISTORICO'); }} - > - - {aluno.nome} - - {pendentesDoAluno > 0 ? ( - - {pendentesDoAluno} PENDENTES - - ) : ( - - )} - - ); - })} - - ); - - const renderPedidosHistorico = () => { - if (!alunoSelecionado) return null; - const historicoAluno = presencasGerais.filter(p => p.aluno_id === alunoSelecionado.id); - - return ( - - setCurrentScreen('PEDIDOS_LISTA')}> - - Voltar à Lista - - - Histórico: {alunoSelecionado.nome} - - {historicoAluno.length === 0 ? ( - Nenhum registo efetuado por este aluno. - ) : ( - historicoAluno.map(item => ( - - - {formatarData(item.data)} - - - {item.estado === 'faltou' ? 'FALTA' : 'PRESENÇA'} - - - - - - - {item.estado === 'presente' ? (item.sumario || "Sem sumário registado.") : "Aluno marcou ausência."} - - {item.justificacao_url && ( - Linking.openURL(item.justificacao_url)}> - - Ver Justificativo PDF - - )} - - - {/* SÓ MOSTRA BOTÕES SE ESTIVER PENDENTE */} - {item.estado_tutor === 'pendente' ? ( - - lidarComPresenca(item.id, 'rejeitado')}> - - Recusar (Apagar) - - lidarComPresenca(item.id, 'aprovado')}> - - Aprovar - - - ) : ( - - ✅ Aprovado - - )} - - )) - )} - - ); - }; - - const renderAvaliacoes = () => ( - - setCurrentScreen('DASHBOARD')}> - - Voltar - - - BREVEMENTE - A área de avaliações finais está em construção. - - ); - - const renderDefinicoes = () => ( - - setCurrentScreen('DASHBOARD')}> - - Voltar - - - Definições - - - Nome da Empresa - {empresaData?.nome || 'N/A'} - - Tutor - {empresaData?.tutor_nome || 'N/A'} - - - supabase.auth.signOut().then(() => router.replace('/'))} - > - - Terminar Sessão - - - ); - - return ( - - - - - - {toast.message} - - - - Painel Empresa - {empresaData?.nome ? {empresaData.nome} : null} - - - {loading && !refreshing ? ( - - ) : ( - } - > - {currentScreen === 'DASHBOARD' && renderDashboard()} - {currentScreen === 'ALUNOS' && renderAlunos()} - {currentScreen === 'PEDIDOS_LISTA' && renderPedidosLista()} - {currentScreen === 'PEDIDOS_HISTORICO' && renderPedidosHistorico()} - {currentScreen === 'AVALIACOES' && renderAvaliacoes()} - {currentScreen === 'DEFINICOES' && renderDefinicoes()} - - )} - - {/* MODAL DETALHES DO ALUNO */} - - - - - {alunoSelecionado?.nome} - - - Nº ESCOLA - {alunoSelecionado?.n_escola || 'N/A'} - - - TURMA/CURSO - {alunoSelecionado?.turma_curso || 'N/A'} - - - setModalDetalhesAluno(false)}> - Fechar - - - - - - ); -} - -const styles = StyleSheet.create({ - safeArea: { flex: 1, paddingTop: Platform.OS === 'android' ? StatusBar.currentHeight : 0 }, - toastContainer: { position: 'absolute', left: 20, right: 20, zIndex: 9999, flexDirection: 'row', alignItems: 'center', padding: 16, borderRadius: 16, elevation: 6 }, - toastText: { color: '#FFF', fontSize: 14, fontWeight: '700', marginLeft: 12 }, - headerArea: { paddingHorizontal: 20, paddingTop: 20, paddingBottom: 10 }, - appTitle: { fontSize: 28, fontWeight: '900', letterSpacing: -0.5 }, - scroll: { padding: 20, paddingBottom: 60 }, - centerBox: { flex: 1, justifyContent: 'center', alignItems: 'center' }, - - // DASHBOARD GRID - grid: { flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'space-between' }, - dashCard: { width: '48%', padding: 20, borderRadius: 24, borderWidth: 1, marginBottom: 15, elevation: 2, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.05, shadowRadius: 8 }, - dashIcon: { width: 56, height: 56, borderRadius: 18, justifyContent: 'center', alignItems: 'center', marginBottom: 15 }, - dashTitle: { fontSize: 18, fontWeight: '900', marginBottom: 5 }, - dashDesc: { fontSize: 12, fontWeight: '600', lineHeight: 18 }, - badgeNotif: { position: 'absolute', top: -5, right: -5, width: 14, height: 14, borderRadius: 7, backgroundColor: '#EF4444', borderWidth: 2, borderColor: '#fff' }, - - // SUB-PAGES - btnVoltar: { flexDirection: 'row', alignItems: 'center', marginBottom: 20 }, - pageTitle: { fontSize: 24, fontWeight: '900', marginBottom: 20 }, - - // LISTAS - listCard: { flexDirection: 'row', alignItems: 'center', padding: 16, borderRadius: 20, borderWidth: 1, marginBottom: 12 }, - avatar: { width: 44, height: 44, borderRadius: 14, justifyContent: 'center', alignItems: 'center' }, - avatarText: { fontSize: 18, fontWeight: '900' }, - listCardTitle: { fontSize: 16, fontWeight: '800' }, - listCardSub: { fontSize: 12, fontWeight: '600', marginTop: 2 }, - badgeCount: { paddingHorizontal: 10, paddingVertical: 6, borderRadius: 12 }, - - // HISTÓRICO - historyCard: { padding: 20, borderRadius: 24, borderWidth: 1, marginBottom: 15 }, - historyTop: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 15 }, - statusTag: { paddingHorizontal: 10, paddingVertical: 4, borderRadius: 8 }, - statusTagText: { fontSize: 10, fontWeight: '900', letterSpacing: 0.5 }, - historyBody: { padding: 15, borderRadius: 16, marginBottom: 15 }, - actionRow: { flexDirection: 'row', gap: 12 }, - btnAction: { flex: 1, flexDirection: 'row', height: 48, borderRadius: 14, justifyContent: 'center', alignItems: 'center' }, - - // MODAL - modalOverlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.5)', justifyContent: 'flex-end' }, - modalContent: { borderTopLeftRadius: 30, borderTopRightRadius: 30, padding: 30, alignItems: 'center', paddingBottom: 50 }, - modalHandle: { width: 50, height: 6, backgroundColor: '#cbd5e1', borderRadius: 10, marginBottom: 20 }, - infoBox: { width: '100%', padding: 15, borderRadius: 16, alignItems: 'center' }, - btnFecharModal: { width: '100%', padding: 18, borderRadius: 16, alignItems: 'center', marginTop: 20 } -}); \ No newline at end of file diff --git a/app/Empresas/EmpresaHome.tsx b/app/Empresas/EmpresaHome.tsx index 0dee0a2..5339c38 100644 --- a/app/Empresas/EmpresaHome.tsx +++ b/app/Empresas/EmpresaHome.tsx @@ -1,12 +1,11 @@ +// app/Empresas/EmpresaHome.tsx 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, @@ -22,9 +21,7 @@ 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(() => ({ @@ -37,238 +34,150 @@ export default function EmpresaHome() { 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); + const fetchEmpresaInfo = async () => { 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') + .select('nome') .eq('user_id', user.id) .single(); - if (!empresa) { - setPendentes([]); - return; + if (empresa) { + setEmpresaNome(empresa.nome); } - 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); + setLoading(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]}`; - }; + useFocusEffect(useCallback(() => { fetchEmpresaInfo(); }, [])); return ( + {/* CABEÇALHO */} Painel da Entidade - {empresaNome || 'A carregar...'} + {loading ? ( + + ) : ( + {empresaNome || 'A carregar...'} + )} supabase.auth.signOut().then(() => router.replace('/'))}> - - Validações Pendentes - - {pendentes.length} - - - - {loading && !refreshing ? ( - - - - ) : ( - } + + {/* CARTÃO DE DESTAQUE - PEDIDOS PENDENTES */} + AÇÃO IMEDIATA + + router.push('/Empresas/pedidos')} > - {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 - - + + Validações Pendentes + Aprovar presenças e sumários + + + + - - Sumário Submetido: - - {item.sumario ? item.sumario : "O aluno não escreveu sumário para este dia."} - - + {/* SECÇÃO GESTÃO - GRELHA 2 COLUNAS */} + GESTÃO DE ESTÁGIOS + + + router.push('/Empresas/alunos')} + > + + + + Alunos + Gerir estagiários + - - lidarComPresenca(item.aluno_id, item.data, 'rejeitado')} - > - - Rejeitar - + router.push('/Empresas/avaliacoesEmpresa')} + > + + + + Avaliações + Avaliar estágios + + - lidarComPresenca(item.aluno_id, item.data, 'aprovado')} - > - - Aprovar - - + {/* DEFINIÇÕES - CARTÃO LARGO INFERIOR */} + router.push('/Empresas/definicoesEmpresa')} + > + + + + + Definições da Conta + Ajustar dados da empresa e segurança + + + - - )) - )} - - )} + ); } 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 }, + topBar: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: 20, paddingTop: 20, paddingBottom: 15 }, 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' }, + scrollContent: { paddingHorizontal: 20, paddingBottom: 40 }, + sectionLabel: { fontSize: 11, fontWeight: '800', letterSpacing: 1.2, marginBottom: 12, marginLeft: 5 }, - 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 }, + heroCard: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', padding: 20, borderRadius: 24, borderWidth: 1, borderLeftWidth: 6, marginBottom: 25, elevation: 2, shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.05, shadowRadius: 10 }, + heroContent: { flexDirection: 'row', alignItems: 'center', flex: 1 }, + iconWrapperLarge: { width: 65, height: 65, borderRadius: 20, justifyContent: 'center', alignItems: 'center', marginRight: 15 }, + heroTextContainer: { flex: 1, paddingRight: 10 }, + heroTitle: { fontSize: 19, fontWeight: '900', marginBottom: 4 }, + heroDesc: { fontSize: 13, fontWeight: '600' }, - sumarioBox: { padding: 15, borderRadius: 16, marginBottom: 15 }, - sumarioLabel: { fontSize: 10, fontWeight: '800', textTransform: 'uppercase', marginBottom: 6 }, - sumarioText: { fontSize: 14, fontWeight: '600', lineHeight: 20 }, + gridContainer: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 20 }, + gridCard: { width: '48%', padding: 20, borderRadius: 24, borderWidth: 1, alignItems: 'flex-start', elevation: 1, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.03, shadowRadius: 5 }, + iconWrapper: { padding: 12, borderRadius: 16, marginBottom: 16 }, + gridCardTitle: { fontSize: 16, fontWeight: '900', marginBottom: 4 }, + gridCardDesc: { fontSize: 12, fontWeight: '600' }, - 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' } + rowCard: { flexDirection: 'row', alignItems: 'center', padding: 18, borderRadius: 20, borderWidth: 1, marginTop: 5 }, + iconWrapperSmall: { width: 45, height: 45, borderRadius: 14, justifyContent: 'center', alignItems: 'center', marginRight: 15 }, + rowTextContainer: { flex: 1 }, + rowTitle: { fontSize: 15, fontWeight: '800', marginBottom: 2 }, + rowDesc: { fontSize: 12, fontWeight: '600' } }); \ No newline at end of file diff --git a/app/Empresas/alunos.tsx b/app/Empresas/alunos.tsx new file mode 100644 index 0000000..d88cf9c --- /dev/null +++ b/app/Empresas/alunos.tsx @@ -0,0 +1,241 @@ +// app/Empresa/alunos.tsx +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 GestaoAlunos() { + const { isDarkMode } = useTheme(); + const router = useRouter(); + + const [alunosList, setAlunosList] = useState([]); + const [loading, setLoading] = useState(true); + const [refreshing, setRefreshing] = useState(false); + + 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', + verde: '#10B981', + laranja: '#dd8707', + }), [isDarkMode]); + + const fetchAlunos = async (isManualRefresh = false) => { + if (!isManualRefresh) setLoading(true); + try { + const { data: { user } } = await supabase.auth.getUser(); + if (!user) return; + + const { data: empresa } = await supabase + .from('empresas') + .select('id') + .eq('user_id', user.id) + .single(); + + if (!empresa) { + setAlunosList([]); + return; + } + + // 🐅 GRAÇAS AO TIGRE, BASTA LER A COLUNA horas_concluidas DIRETO DA BASE DE DADOS! + const { data: estagios, error } = await supabase + .from('estagios') + .select(` + id, + data_inicio, + data_fim, + horas_totais, + horas_concluidas, + alunos (id, nome) + `) + .eq('empresa_id', empresa.id) + .order('data_fim', { ascending: false }); + + if (error) throw error; + + const formatados = estagios?.map((estagio: any) => { + // 🟢 TRUQUE ANTI-ERRO: Tira o aluno da lista se o Supabase mandar em formato Array + const alunoObj = Array.isArray(estagio.alunos) ? estagio.alunos[0] : estagio.alunos; + + return { + id_estagio: estagio.id, + aluno_id: alunoObj?.id, + aluno_nome: alunoObj?.nome || 'Aluno Desconhecido', + data_inicio: estagio.data_inicio, + data_fim: estagio.data_fim, + horas_totais: estagio.horas_totais || 0, + horas_concluidas: estagio.horas_concluidas || 0, // <-- LIDO DIRETO DA TABELA! + }; + }) || []; + + setAlunosList(formatados); + } catch (error) { + console.error(error); + Alert.alert("Erro", "Não foi possível carregar a lista de alunos."); + } finally { + if (!isManualRefresh) setLoading(false); + setRefreshing(false); + } + }; + + useFocusEffect(useCallback(() => { fetchAlunos(); }, [])); + + const onRefresh = useCallback(() => { + setRefreshing(true); + fetchAlunos(true); + }, []); + + const formatarData = (dataStr: string) => { + if (!dataStr) return ''; + const parts = dataStr.split('-'); + if (parts.length !== 3) return dataStr; + return `${parts[2]}/${parts[1]}/${parts[0]}`; + }; + + const getStatus = (dataFim: string) => { + const hoje = new Date().toISOString().split('T')[0]; + if (hoje > dataFim) return { texto: 'CONCLUÍDO', cor: themeStyles.textoSecundario, bg: themeStyles.textoSecundario + '20' }; + return { texto: 'A DECORRER', cor: themeStyles.verde, bg: themeStyles.verde + '20' }; + }; + + return ( + + + + + router.back()}> + + + Os Meus Alunos + + + + + Estagiários Alocados + + {alunosList.length} + + + + {loading && !refreshing ? ( + + + + ) : ( + } + > + {alunosList.length === 0 ? ( + + + Nenhum estagiário! + + A tua empresa ainda não tem alunos associados pelo professor neste momento. + + + ) : ( + alunosList.map((aluno, index) => { + const status = getStatus(aluno.data_fim); + const progressoPercent = aluno.horas_totais > 0 ? (aluno.horas_concluidas / aluno.horas_totais) * 100 : 0; + + return ( + + + + + + + + {aluno.aluno_nome} + + + + {status.texto} + + + + + + + + INÍCIO + {formatarData(aluno.data_inicio)} + + + FIM PREVISTO + {formatarData(aluno.data_fim)} + + + + + + HORAS REALIZADAS + {aluno.horas_concluidas} / {aluno.horas_totais}h + + + + + + + ); + }) + )} + + )} + + ); +} + +const styles = StyleSheet.create({ + safeArea: { flex: 1, paddingTop: Platform.OS === 'android' ? StatusBar.currentHeight : 0 }, + header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: 20, paddingTop: 20, paddingBottom: 15 }, + btnVoltar: { padding: 5, marginLeft: -5 }, + headerTitle: { fontSize: 20, fontWeight: '900' }, + centerBox: { flex: 1, justifyContent: 'center', alignItems: 'center' }, + headerTitleContainer: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 20, marginBottom: 20, gap: 10 }, + sectionTitle: { fontSize: 16, 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: 20, borderWidth: 1, borderLeftWidth: 5, marginBottom: 15, elevation: 1, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.03, shadowRadius: 5 }, + cardHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }, + avatar: { width: 36, height: 36, borderRadius: 18, justifyContent: 'center', alignItems: 'center' }, + alunoName: { fontSize: 17, fontWeight: '900', flex: 1 }, + statusTag: { paddingHorizontal: 8, paddingVertical: 4, borderRadius: 6 }, + statusTagText: { fontSize: 9, fontWeight: '900', letterSpacing: 0.5 }, + divider: { height: 1, marginVertical: 15, opacity: 0.5 }, + infoRow: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 15 }, + infoCol: { flex: 1 }, + infoLabel: { fontSize: 10, fontWeight: '800', textTransform: 'uppercase', marginBottom: 4 }, + infoValue: { fontSize: 14, fontWeight: '700' }, + progressSection: { marginTop: 5 }, + horasValue: { fontSize: 12, fontWeight: '800' }, + progressBarBg: { height: 8, borderRadius: 4, width: '100%', overflow: 'hidden' }, + progressBarFill: { height: '100%', borderRadius: 4 } +}); \ No newline at end of file diff --git a/app/Empresas/avaliacoesEmpresa.tsx b/app/Empresas/avaliacoesEmpresa.tsx new file mode 100644 index 0000000..e69de29 diff --git a/app/Empresas/definicoesEmpresa.tsx b/app/Empresas/definicoesEmpresa.tsx new file mode 100644 index 0000000..4d4536e --- /dev/null +++ b/app/Empresas/definicoesEmpresa.tsx @@ -0,0 +1,249 @@ +// app/Definicoes.tsx +import { Ionicons } from '@expo/vector-icons'; +import { useRouter } from 'expo-router'; +import { memo, useCallback, useMemo, useRef, useState } from 'react'; +import { + Animated, + Linking, + ScrollView, + StatusBar, + StyleSheet, + Switch, + Text, + TouchableOpacity, + View +} from 'react-native'; +import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'; +import { supabase } from '../../lib/supabase'; +import { useTheme } from '../../themecontext'; + +const Definicoes = memo(() => { + const router = useRouter(); + const insets = useSafeAreaInsets(); + const { isDarkMode, toggleTheme } = useTheme(); + const [notificacoes, setNotificacoes] = useState(true); + + // TOAST ANIMADO UNIVERSAL + const [toast, setToast] = useState<{ visible: boolean; message: string; type: 'error' | 'success' | 'info' }>({ visible: false, message: '', type: 'info' }); + const slideAnim = useRef(new Animated.Value(-100)).current; + + const showToast = useCallback((message: string, type: 'error' | 'success' | 'info' = 'info') => { + setToast({ visible: true, message, type }); + Animated.timing(slideAnim, { toValue: insets.top + 10, duration: 300, useNativeDriver: true }).start(() => { + setTimeout(() => { + Animated.timing(slideAnim, { toValue: -100, duration: 300, useNativeDriver: true }) + .start(() => setToast({ visible: false, message: '', type: 'info' })); + }, 3500); + }); + }, [insets.top, slideAnim]); + + // Cores EPVC + const azulEPVC = '#2390a6'; + const laranjaEPVC = '#E38E00'; + const erroCor = '#EF4444'; + const sucessoCor = '#10B981'; + + const cores = useMemo(() => ({ + fundo: isDarkMode ? '#0A0A0A' : '#F4F7FA', // Novo design Premium + card: isDarkMode ? '#161618' : '#FFFFFF', + texto: isDarkMode ? '#F8FAFC' : '#1E293B', + secundario: isDarkMode ? '#94A3B8' : '#64748B', + azul: azulEPVC, + laranja: laranjaEPVC, + azulSuave: isDarkMode ? 'rgba(35, 144, 166, 0.15)' : '#E0F2F4', + borda: isDarkMode ? '#2D2D2D' : '#E2E8F0', + vermelho: erroCor, + verde: sucessoCor, + vermelhoSuave: isDarkMode ? 'rgba(239, 68, 68, 0.15)' : '#FEE2E2', + }), [isDarkMode]); + + const handleLogout = async () => { + try { + await supabase.auth.signOut(); + router.replace('/'); + } catch (e) { + showToast("Erro ao sair da conta", "error"); + } + }; + + const abrirURL = (url: string) => { + Linking.canOpenURL(url).then(supported => { + if (supported) Linking.openURL(url); + else showToast("Não foi possível abrir o link", "error"); + }); + }; + + return ( + + + + {/* 🟢 TOAST ANIMADO NO TOPO */} + + + {toast.message} + + + + + {/* HEADER MODERNO */} + + router.back()} + > + + + + Definições + Sistema & Suporte + + + + + + + Preferências + + + + + + Notificações + { + setNotificacoes(v); + showToast(v ? "Notificações ligadas" : "Notificações desligadas", "info"); + }} + trackColor={{ false: cores.borda, true: cores.laranja }} + thumbColor="#FFFFFF" + /> + + + + + + + Modo Escuro + + + + + Escola Profissional + + abrirURL('mailto:epvc@epvc.pt')} + cores={cores} + showBorder + /> + abrirURL('mailto:secretaria@epvc.pt')} + cores={cores} + showBorder + /> + abrirURL('tel:252641805')} + cores={cores} + /> + + + + + + + + App Version + 2.6.17 + + + + + + + Terminar Sessão + + + + + + + Estágios+ • Vila do Conde + EPVC + + + + + + ); +}); + +const SettingLink = ({ icon, label, subLabel, onPress, cores, showBorder }: any) => ( + + + + + + {label} + {subLabel} + + + + + +); + +const styles = StyleSheet.create({ + safe: { flex: 1 }, + // TOAST STYLES + toastContainer: { position: 'absolute', left: 20, right: 20, zIndex: 9999, flexDirection: 'row', alignItems: 'center', padding: 16, borderRadius: 16, elevation: 6, shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.2, shadowRadius: 8 }, + toastText: { color: '#FFF', fontSize: 14, fontWeight: '700', marginLeft: 12, flex: 1 }, + + header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 20, paddingVertical: 15 }, + btnVoltar: { width: 44, height: 44, borderRadius: 14, justifyContent: 'center', alignItems: 'center', borderWidth: 1 }, + tituloGeral: { fontSize: 20, fontWeight: '900', letterSpacing: -0.5 }, + tituloSub: { fontSize: 11, fontWeight: '800', textTransform: 'uppercase', letterSpacing: 0.5, marginTop: 2 }, + + scrollContent: { paddingHorizontal: 20, paddingTop: 10, paddingBottom: 60 }, + sectionLabel: { fontSize: 11, fontWeight: '900', textTransform: 'uppercase', marginBottom: 12, marginLeft: 8, letterSpacing: 0.8 }, + + card: { borderRadius: 28, paddingHorizontal: 20, borderWidth: 1, elevation: 2, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.03, shadowRadius: 8 }, + item: { flexDirection: 'row', alignItems: 'center', paddingVertical: 18 }, + iconContainer: { width: 44, height: 44, borderRadius: 14, justifyContent: 'center', alignItems: 'center' }, + itemTexto: { flex: 1, marginLeft: 15, fontSize: 16, fontWeight: '800', letterSpacing: -0.3 }, + versionBadge: { fontSize: 12, fontWeight: '900', paddingHorizontal: 12, paddingVertical: 6, borderRadius: 10 }, + arrowCircle: { width: 34, height: 34, borderRadius: 12, borderWidth: 1, justifyContent: 'center', alignItems: 'center' }, + + footer: { alignItems: 'center', marginTop: 50 }, + footerLine: { width: 40, height: 4, borderRadius: 2, marginBottom: 15 }, + footerText: { fontSize: 11, fontWeight: '800', textTransform: 'uppercase', letterSpacing: 1.5 } +}); + +export default Definicoes; \ No newline at end of file diff --git a/app/Empresas/pedidos.tsx b/app/Empresas/pedidos.tsx new file mode 100644 index 0000000..4dd3808 --- /dev/null +++ b/app/Empresas/pedidos.tsx @@ -0,0 +1,227 @@ +// app/Empresa/pedidos.tsx +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 PedidosPendentes() { + const { isDarkMode } = useTheme(); + const router = useRouter(); + + const [pendentes, setPendentes] = useState([]); + const [loading, setLoading] = useState(true); + const [refreshing, setRefreshing] = useState(false); + + 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', + }), [isDarkMode]); + + const fetchValidaçõesPendentes = async (isManualRefresh = false) => { + if (!isManualRefresh) setLoading(true); + try { + const { data: { user } } = await supabase.auth.getUser(); + if (!user) return; + + const { data: empresa } = await supabase + .from('empresas') + .select('id') + .eq('user_id', user.id) + .single(); + + if (!empresa) { + setPendentes([]); + return; + } + + 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); + + const { data: alunos } = await supabase + .from('alunos') + .select('id, nome') + .in('id', alunoIds); + + const mapaAlunos: Record = {}; + alunos?.forEach(a => { mapaAlunos[a.id] = a.nome; }); + + // Busca presenças OU faltas que estejam pendentes de aprovação da empresa + const { data: presencas } = await supabase + .from('presencas') + .select('*') + .in('aluno_id', alunoIds) + .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 os pedidos."); + } finally { + if (!isManualRefresh) setLoading(false); + setRefreshing(false); + } + }; + + useFocusEffect(useCallback(() => { fetchValidaçõesPendentes(); }, [])); + + const onRefresh = useCallback(() => { + setRefreshing(true); + fetchValidaçõesPendentes(true); + }, []); + + const formatarData = (dataStr: string) => { + if (!dataStr) return ''; + const parts = dataStr.split('-'); + if (parts.length !== 3) return dataStr; + return `${parts[2]}/${parts[1]}/${parts[0]}`; + }; + + const getTipoInfo = (item: any) => { + if (item.estado === 'presente') return { icone: 'checkmark-circle', cor: themeStyles.verde, texto: 'Presença e Sumário' }; + if (item.justificacao_url) return { icone: 'document-text', cor: themeStyles.laranja, texto: 'Falta com Justificação' }; + return { icone: 'close-circle', cor: themeStyles.laranja, texto: 'Falta a Confirmar' }; + }; + + return ( + + + + router.back()}> + + + Pedidos + + + + A Aguardar Validação + + {pendentes.length} + + + {loading && !refreshing ? ( + + + + ) : ( + } + > + {pendentes.length === 0 ? ( + + + Tudo limpo! + + Não tens pedidos pendentes neste momento. + + + ) : ( + pendentes.map((item, index) => { + const tipo = getTipoInfo(item); + return ( + { + router.push({ + pathname: '/Empresa/validar-registo', + params: { + aluno_id: item.aluno_id, + aluno_nome: item.aluno_nome, + data: item.data, + sumario: item.sumario || '', + estado: item.estado, + justificacao_url: item.justificacao_url || '' + } + }); + }} + > + + + + {item.aluno_nome} + + + + {formatarData(item.data)} + + + + {tipo.texto} + + + + + + + ); + }) + )} + + )} + + ); +} + +const styles = StyleSheet.create({ + safeArea: { flex: 1, paddingTop: Platform.OS === 'android' ? StatusBar.currentHeight : 0 }, + header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: 20, paddingTop: 20, paddingBottom: 15 }, + btnVoltar: { padding: 5, marginLeft: -5 }, + headerTitle: { fontSize: 20, fontWeight: '900' }, + + centerBox: { flex: 1, justifyContent: 'center', alignItems: 'center' }, + + headerTitleContainer: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 20, marginBottom: 20, gap: 10 }, + sectionTitle: { fontSize: 16, 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: 20, borderWidth: 1, borderLeftWidth: 5, marginBottom: 15, elevation: 1, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.03, shadowRadius: 5 }, + cardHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }, + alunoName: { fontSize: 17, fontWeight: '900', marginBottom: 2 }, + dataText: { fontSize: 13, fontWeight: '700' } +}); \ No newline at end of file diff --git a/app/index.tsx b/app/index.tsx index 7252c53..71ceb05 100644 --- a/app/index.tsx +++ b/app/index.tsx @@ -51,7 +51,7 @@ export default function LoginScreen() { } else if (data.tipo === 'aluno') { router.replace('/Aluno/AlunoHome'); } else if (data.tipo === 'empresa') { - router.replace('/Empresa/EmpresaHome'); // 🟢 Rota da empresa adicionada! + router.replace('/Empresas/EmpresaHome'); } else { Alert.alert('Erro', 'Tipo de conta inválido'); }