From f468c926e72091e1fa4e3401e28adda984ed5d74 Mon Sep 17 00:00:00 2001 From: Seu Nome <230413@epvc.pt> Date: Wed, 11 Mar 2026 00:08:38 +0000 Subject: [PATCH] =?UTF-8?q?atualiza=C3=A7=C3=B5es=20-=20noite?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Professor/Alunos/DetalhesAluno.tsx | 242 +++++++++++++--------- app/Professor/Alunos/ListaAlunos.tsx | 268 +++++++++++++++---------- app/Professor/Alunos/Presencas.tsx | 191 +++++++++++------- app/Professor/PerfilProf.tsx | 50 ++--- app/Professor/ProfessorHome.tsx | 183 +++++++++++------ package-lock.json | 117 +++++------ 6 files changed, 642 insertions(+), 409 deletions(-) diff --git a/app/Professor/Alunos/DetalhesAluno.tsx b/app/Professor/Alunos/DetalhesAluno.tsx index bdd2f9e..21ccfad 100644 --- a/app/Professor/Alunos/DetalhesAluno.tsx +++ b/app/Professor/Alunos/DetalhesAluno.tsx @@ -1,8 +1,8 @@ -// app/Professor/Alunos/DetalhesAluno.tsx import { Ionicons } from '@expo/vector-icons'; import { useLocalSearchParams, useRouter } from 'expo-router'; -import { memo, useEffect, useState } from 'react'; +import { memo, useEffect, useMemo, useState } from 'react'; import { + ActivityIndicator, ScrollView, StatusBar, StyleSheet, @@ -14,7 +14,6 @@ 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; @@ -24,6 +23,13 @@ interface AlunoEstado { telefone: string; residencia: string; idade: string; + empresa_nome: string; + tutor_nome: string; + tutor_tel: string; + data_inicio: string; + data_fim: string; + horas_diarias: string; + horarios_detalhados: string[]; } const DetalhesAlunos = memo(() => { @@ -31,130 +37,184 @@ const DetalhesAlunos = memo(() => { const params = useLocalSearchParams(); const { isDarkMode } = useTheme(); - const colors = { - background: isDarkMode ? '#121212' : '#f1f3f5', - card: isDarkMode ? '#1e1e1e' : '#ffffff', - text: isDarkMode ? '#ffffff' : '#000000', - label: isDarkMode ? '#aaaaaa' : '#6c757d', - }; + const cores = useMemo(() => ({ + fundo: isDarkMode ? '#0F0F0F' : '#F8FAFC', + card: isDarkMode ? '#1A1A1A' : '#FFFFFF', + texto: isDarkMode ? '#F8FAFC' : '#1E293B', + secundario: isDarkMode ? '#94A3B8' : '#64748B', + azul: '#3B82F6', + azulSuave: isDarkMode ? 'rgba(59, 130, 246, 0.15)' : 'rgba(59, 130, 246, 0.1)', + borda: isDarkMode ? '#2D2D2D' : '#E2E8F0', + }), [isDarkMode]); - const alunoId = - typeof params.alunoId === 'string' - ? params.alunoId - : Array.isArray(params.alunoId) - ? params.alunoId[0] - : null; + const alunoId = typeof params.alunoId === 'string' ? params.alunoId : null; const [aluno, setAluno] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { - if (!alunoId) { - setLoading(false); - return; - } - fetchAluno(); + if (alunoId) fetchAluno(); }, [alunoId]); const fetchAluno = async () => { try { setLoading(true); - + const { data, error } = await supabase .from('alunos') .select(` - id, - nome, - n_escola, - turma_curso, - profiles!alunos_profile_id_fkey ( - email, - telefone, - residencia, - idade + id, nome, n_escola, turma_curso, + profiles!alunos_profile_id_fkey ( email, telefone, residencia, idade ), + estagios ( + id, data_inicio, data_fim, horas_diarias, + empresas ( nome, tutor_nome, tutor_telefone ) ) `) .eq('id', alunoId) .single(); - if (error) { - console.log('Erro ao buscar:', error.message); - setLoading(false); - return; - } + if (error) throw error; 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; + const d = data as any; + const perfil = d.profiles; + const estagio = Array.isArray(d.estagios) ? d.estagios[0] : d.estagios; + const empresa = estagio?.empresas; + + let listaHorarios: string[] = []; + if (estagio?.id) { + const { data: hData } = await supabase + .from('horarios_estagio') + .select('hora_inicio, hora_fim') + .eq('estagio_id', estagio.id); + + if (hData) { + listaHorarios = hData.map(h => + `${h.hora_inicio.substring(0,5)} até às ${h.hora_fim.substring(0,5)}` + ); + } + } setAluno({ - id: String(data.id), - nome: data.nome || 'Sem nome', - n_escola: String(data.n_escola || '-'), - turma_curso: data.turma_curso || '-', + id: String(d.id), + nome: d.nome || 'Sem nome', + n_escola: String(d.n_escola || '-'), + turma_curso: d.turma_curso || '-', email: perfil?.email ?? '-', telefone: perfil?.telefone ?? '-', residencia: perfil?.residencia ?? '-', idade: perfil?.idade ? String(perfil.idade) : '-', + empresa_nome: empresa?.nome || 'Não atribuída', + tutor_nome: empresa?.tutor_nome || 'Não definido', + tutor_tel: empresa?.tutor_telefone || '-', + data_inicio: estagio?.data_inicio || '-', + data_fim: estagio?.data_fim || '-', + horas_diarias: estagio?.horas_diarias || '0h', + horarios_detalhados: listaHorarios || [], // Garantia de array vazio }); } - - setLoading(false); - } catch (err) { - console.log('Erro inesperado:', err); + } catch (err: any) { + console.log('Erro:', err.message); + } finally { setLoading(false); } }; if (loading) { return ( - - A carregar... - + + + ); } + // Verificação de segurança extra para o objeto aluno if (!aluno) { return ( - - Aluno não encontrado - + + Dados indisponíveis + ); } return ( - - + + - router.back()}> - + router.back()} style={[styles.backBtn, { backgroundColor: cores.card }]}> + - - {aluno.nome} - - + Ficha do Aluno + - - - {renderCampo('Número Escola', aluno.n_escola, colors)} - {renderCampo('Turma', aluno.turma_curso, colors)} - {renderCampo('Email', aluno.email, colors)} - {renderCampo('Telefone', aluno.telefone, colors)} - {renderCampo('Residência', aluno.residencia, colors)} - {renderCampo('Idade', aluno.idade, colors)} + + + + + {aluno.nome.charAt(0).toUpperCase()} + + {aluno.nome} + + {aluno.turma_curso} + + + Dados Pessoais + + + + + + + + Informação de Estágio + + + + + + + + + + Horários Registados + {/* O USO DE OPTIONAL CHAINING AQUI EVITA O ERRO */} + {(aluno.horarios_detalhados?.length ?? 0) > 0 ? ( + aluno.horarios_detalhados.map((h, index) => ( + {h} + )) + ) : ( + Sem horários definidos + )} + + + + + + + + + + + + + ); }); -const renderCampo = (label: string, valor: string, colors: any) => ( - - {label} - {valor} +const InfoRow = ({ icon, label, valor, cores, ultimo }: any) => ( + + + + + + {label} + {valor} + ); @@ -162,25 +222,21 @@ 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 - }, - titulo: { fontSize: 20, fontWeight: 'bold' }, - container: { padding: 16 }, - 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' }, + centered: { flex: 1, justifyContent: 'center', alignItems: 'center' }, + header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 20, paddingVertical: 15 }, + backBtn: { width: 40, height: 40, borderRadius: 12, justifyContent: 'center', alignItems: 'center' }, + headerTitle: { fontSize: 18, fontWeight: '800' }, + scrollContent: { paddingHorizontal: 20, paddingBottom: 40 }, + profileSection: { alignItems: 'center', marginBottom: 25 }, + mainAvatar: { width: 70, height: 70, borderRadius: 35, justifyContent: 'center', alignItems: 'center', marginBottom: 12 }, + avatarLetter: { fontSize: 28, fontWeight: '800' }, + mainName: { fontSize: 22, fontWeight: '800', textAlign: 'center' }, + badge: { marginTop: 6, paddingHorizontal: 12, paddingVertical: 4, borderRadius: 20 }, + badgeText: { fontSize: 11, fontWeight: '700', textTransform: 'uppercase' }, + sectionTitle: { fontSize: 12, fontWeight: '800', textTransform: 'uppercase', letterSpacing: 1, marginTop: 25, marginBottom: 10, marginLeft: 5 }, + card: { borderRadius: 20, padding: 10, elevation: 2, shadowColor: '#000', shadowOpacity: 0.05, shadowRadius: 8, shadowOffset: { width: 0, height: 2 } }, + infoRow: { flexDirection: 'row', alignItems: 'center', paddingVertical: 12, paddingHorizontal: 10 }, + iconBox: { width: 36, height: 36, borderRadius: 10, justifyContent: 'center', alignItems: 'center', marginRight: 15 }, + infoLabel: { fontSize: 10, fontWeight: '600', textTransform: 'uppercase', marginBottom: 2 }, + infoValor: { fontSize: 15, fontWeight: '700' }, }); \ No newline at end of file diff --git a/app/Professor/Alunos/ListaAlunos.tsx b/app/Professor/Alunos/ListaAlunos.tsx index 77b3f6b..2497f99 100644 --- a/app/Professor/Alunos/ListaAlunos.tsx +++ b/app/Professor/Alunos/ListaAlunos.tsx @@ -1,7 +1,8 @@ import { Ionicons } from '@expo/vector-icons'; import { useRouter } from 'expo-router'; -import { memo, useEffect, useState } from 'react'; +import { memo, useEffect, useMemo, useState } from 'react'; import { + ActivityIndicator, FlatList, Platform, SafeAreaView, @@ -30,120 +31,120 @@ const ListaAlunosProfessor = memo(() => { const [turmas, setTurmas] = useState<{ nome: string; alunos: Aluno[] }[]>([]); const [loading, setLoading] = useState(true); - const cores = { - fundo: isDarkMode ? '#121212' : '#f1f3f5', - card: isDarkMode ? '#1e1e1e' : '#fff', - texto: isDarkMode ? '#fff' : '#000', - textoSecundario: isDarkMode ? '#adb5bd' : '#6c757d', - azul: '#0d6efd', - }; + const cores = useMemo(() => ({ + fundo: isDarkMode ? '#0F0F0F' : '#F8FAFC', + card: isDarkMode ? '#1A1A1A' : '#FFFFFF', + texto: isDarkMode ? '#F8FAFC' : '#1E293B', + secundario: isDarkMode ? '#94A3B8' : '#64748B', + azul: '#3B82F6', + azulSuave: isDarkMode ? 'rgba(59, 130, 246, 0.15)' : 'rgba(59, 130, 246, 0.1)', + borda: isDarkMode ? '#2D2D2D' : '#E2E8F0', + }), [isDarkMode]); useEffect(() => { fetchAlunos(); }, []); const fetchAlunos = async () => { - setLoading(true); + try { + setLoading(true); + const { data, error } = await supabase + .from('alunos') + .select('id, nome, n_escola, ano, turma_curso') + .order('ano', { ascending: false }) + .order('nome', { ascending: true }); - const { data, error } = await supabase - .from('alunos') - .select('id, nome, n_escola, ano, turma_curso') - .order('ano', { ascending: false }); + if (error) throw error; - if (error) { - console.error('Erro Supabase:', error); - setLoading(false); - return; - } + if (!data) { + setTurmas([]); + return; + } - if (!data || data.length === 0) { - console.log('Nenhum aluno encontrado'); - setTurmas([]); - setLoading(false); - return; - } - - // Agrupar por ano + turma_curso - const agrupadas: Record = {}; - - data.forEach(item => { - const nomeTurma = `${item.ano}º ${item.turma_curso}`; - if (!agrupadas[nomeTurma]) agrupadas[nomeTurma] = []; - - agrupadas[nomeTurma].push({ - id: item.id, - nome: item.nome, - n_escola: item.n_escola, - turma: nomeTurma, + const agrupadas: Record = {}; + data.forEach(item => { + const nomeTurma = `${item.ano}º ${item.turma_curso}`; + if (!agrupadas[nomeTurma]) agrupadas[nomeTurma] = []; + agrupadas[nomeTurma].push({ + id: item.id, + nome: item.nome, + n_escola: item.n_escola, + turma: nomeTurma, + }); }); - }); - setTurmas( - Object.keys(agrupadas).map(nome => ({ - nome, - alunos: agrupadas[nome], - })) - ); - - setLoading(false); + setTurmas( + Object.keys(agrupadas).map(nome => ({ + nome, + alunos: agrupadas[nome], + })) + ); + } catch (err) { + console.error('Erro ao carregar alunos:', err); + } finally { + setLoading(false); + } }; const filteredTurmas = turmas .map(turma => ({ ...turma, alunos: turma.alunos.filter(a => - a.nome.toLowerCase().includes(search.toLowerCase()) + a.nome.toLowerCase().includes(search.toLowerCase()) || + a.n_escola.includes(search) ), })) .filter(t => t.alunos.length > 0); return ( - + - - router.back()} - > - - + {/* HEADER FIXO ESTILO PREMIUM */} + + + router.back()} style={styles.backBtn}> + + + Alunos + + - - Alunos - - - + + + + - + {loading ? ( + + + + ) : ( + item.nome} + contentContainerStyle={styles.scrollContent} + showsVerticalScrollIndicator={false} + renderItem={({ item }) => ( + + {/* BADGE DA TURMA */} + + + {item.nome} • {item.alunos.length} Alunos + + - {loading && ( - - A carregar alunos... - - )} - - item.nome} - renderItem={({ item }) => ( - - - {item.nome} ({item.alunos.length}) - - - {item.alunos.map(aluno => ( router.push({ pathname: '/Professor/Alunos/DetalhesAluno', @@ -151,15 +152,24 @@ const ListaAlunosProfessor = memo(() => { }) } > - - {aluno.n_escola} – {aluno.nome} - + + + {aluno.nome.charAt(0).toUpperCase()} + + + + + {aluno.nome} + Nº Escola: {aluno.n_escola} + + + ))} - - )} - /> + )} + /> + )} ); }); @@ -167,15 +177,69 @@ const ListaAlunosProfessor = memo(() => { export default ListaAlunosProfessor; 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 }, - tituloGeral: { fontSize: 22, fontWeight: 'bold' }, - spacer: { width: 40 }, - search: { borderRadius: 10, padding: 10, margin: 10 }, - card: { borderRadius: 10, padding: 15, marginHorizontal: 10, marginBottom: 10, elevation: 2 }, - turmaNome: { fontSize: 18, fontWeight: 'bold' }, - listaAlunos: { marginTop: 10, paddingLeft: 10 }, - alunoItem: { paddingVertical: 5 }, - alunoNome: { fontSize: 16, fontWeight: '600' }, -}); + safe: { + flex: 1, + paddingTop: Platform.OS === 'android' ? (StatusBar.currentHeight ?? 0) : 0, + }, + headerFixed: { + paddingHorizontal: 20, + paddingBottom: 15, + }, + topBar: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + height: 60, + }, + backBtn: { width: 40, height: 40, justifyContent: 'center' }, + title: { fontSize: 24, fontWeight: '800' }, + searchBox: { + flexDirection: 'row', + alignItems: 'center', + borderWidth: 1, + borderRadius: 15, + paddingHorizontal: 15, + height: 48, + marginTop: 5, + }, + searchInput: { flex: 1, marginLeft: 10, fontSize: 15 }, + scrollContent: { paddingHorizontal: 20, paddingBottom: 30 }, + section: { marginBottom: 25 }, + turmaBadge: { + paddingHorizontal: 12, + paddingVertical: 6, + borderRadius: 10, + alignSelf: 'flex-start', + marginBottom: 12, + }, + turmaLabel: { + fontSize: 13, + fontWeight: '800', + textTransform: 'uppercase', + letterSpacing: 0.5 + }, + card: { + flexDirection: 'row', + alignItems: 'center', + padding: 14, + borderRadius: 20, + marginBottom: 10, + elevation: 3, + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.05, + shadowRadius: 8, + }, + avatar: { + width: 44, + height: 44, + borderRadius: 22, + justifyContent: 'center', + alignItems: 'center', + }, + avatarText: { fontSize: 17, fontWeight: '700' }, + info: { flex: 1, marginLeft: 15 }, + nome: { fontSize: 16, fontWeight: '700' }, + subText: { fontSize: 12, marginTop: 2, fontWeight: '500' }, + centered: { flex: 1, justifyContent: 'center', alignItems: 'center' }, +}); \ No newline at end of file diff --git a/app/Professor/Alunos/Presencas.tsx b/app/Professor/Alunos/Presencas.tsx index a80d9c8..fcd52f7 100644 --- a/app/Professor/Alunos/Presencas.tsx +++ b/app/Professor/Alunos/Presencas.tsx @@ -1,7 +1,8 @@ import { Ionicons } from '@expo/vector-icons'; import { useRouter } from 'expo-router'; -import { useMemo, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { + ActivityIndicator, Platform, SafeAreaView, ScrollView, @@ -13,59 +14,78 @@ import { View, } from 'react-native'; import { useTheme } from '../../../themecontext'; +import { supabase } from '../../lib/supabase'; interface Aluno { - id: number; + id: string; nome: string; - turma: string; } -const alunosData: Aluno[] = [ - { id: 1, nome: 'João Silva', turma: '12ºINF' }, - { id: 2, nome: 'Maria Fernandes', turma: '12ºINF' }, - { id: 3, nome: 'Pedro Costa', turma: '11ºINF' }, -]; - export default function Presencas() { const router = useRouter(); const { isDarkMode } = useTheme(); const [pesquisa, setPesquisa] = useState(''); + const [alunos, setAlunos] = useState([]); + const [loading, setLoading] = useState(true); const cores = useMemo( () => ({ - fundo: isDarkMode ? '#121212' : '#f1f3f5', - card: isDarkMode ? '#1e1e1e' : '#fff', - texto: isDarkMode ? '#fff' : '#212529', - secundario: isDarkMode ? '#adb5bd' : '#6c757d', - azul: '#0d6efd', - input: isDarkMode ? '#1e1e1e' : '#fff', - borda: isDarkMode ? '#333' : '#dee2e6', + fundo: isDarkMode ? '#0F0F0F' : '#F8FAFC', + card: isDarkMode ? '#1A1A1A' : '#FFFFFF', + texto: isDarkMode ? '#F8FAFC' : '#1E293B', + secundario: isDarkMode ? '#94A3B8' : '#64748B', + azul: '#3B82F6', + azulSuave: isDarkMode ? 'rgba(59, 130, 246, 0.15)' : 'rgba(59, 130, 246, 0.1)', + borda: isDarkMode ? '#2D2D2D' : '#E2E8F0', }), [isDarkMode] ); - const alunosFiltrados = alunosData.filter(a => + useEffect(() => { + fetchAlunos(); + }, []); + + async function fetchAlunos() { + try { + setLoading(true); + + // Busca apenas id e nome, filtrando por tipo aluno + const { data, error } = await supabase + .from('profiles') + .select('id, nome') + .eq('tipo', 'aluno') + .order('nome', { ascending: true }); + + if (error) throw error; + + if (data) { + setAlunos(data as Aluno[]); + } + } catch (error: any) { + console.error("Erro ao carregar alunos:", error.message); + } finally { + setLoading(false); + } + } + + const alunosFiltrados = alunos.filter(a => a.nome.toLowerCase().includes(pesquisa.toLowerCase()) ); - const turmas = Array.from(new Set(alunosFiltrados.map(a => a.turma))); - return ( - + - - {/* HEADER */} - - router.back()}> - + + + router.back()} style={styles.backBtn}> + Presenças - + - {/* PESQUISA */} - + + - {/* TURMAS */} - {turmas.map(turma => ( - - {turma} - - {alunosFiltrados - .filter(a => a.turma === turma) - .map(aluno => ( - - router.push({ - pathname: '/Professor/Alunos/CalendarioPresencas', - params: { alunoId: aluno.id, nome: aluno.nome }, - }) - } - > - + {loading ? ( + + + + ) : ( + + {alunosFiltrados.length === 0 ? ( + + Nenhum aluno encontrado. + + ) : ( + alunosFiltrados.map(aluno => ( + + router.push({ + pathname: '/Professor/Alunos/CalendarioPresencas', + params: { alunoId: aluno.id, nome: aluno.nome }, + }) + } + > + + + {aluno.nome.charAt(0).toUpperCase()} + + + + {aluno.nome} - - - ))} - - ))} - + Ver registo de presenças + + + + + )) + )} + + )} ); } @@ -109,32 +143,53 @@ export default function Presencas() { const styles = StyleSheet.create({ safe: { flex: 1, - paddingTop: Platform.OS === 'android' ? StatusBar.currentHeight : 0, + paddingTop: Platform.OS === 'android' ? (StatusBar.currentHeight ?? 0) : 0, }, - container: { padding: 20 }, - header: { + headerFixed: { + paddingHorizontal: 20, + paddingBottom: 15, + }, + topBar: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', - marginBottom: 20, + height: 60, }, - title: { fontSize: 24, fontWeight: '700' }, + backBtn: { width: 40, height: 40, justifyContent: 'center' }, + title: { fontSize: 22, fontWeight: '800' }, searchBox: { flexDirection: 'row', alignItems: 'center', borderWidth: 1, - borderRadius: 14, - paddingHorizontal: 12, - marginBottom: 20, + borderRadius: 15, + paddingHorizontal: 15, + height: 48, }, - searchInput: { flex: 1, marginLeft: 8, paddingVertical: 10 }, - turma: { fontSize: 16, fontWeight: '700', marginBottom: 10 }, + searchInput: { flex: 1, marginLeft: 10, fontSize: 15 }, + scrollContent: { padding: 20 }, card: { flexDirection: 'row', alignItems: 'center', - padding: 16, - borderRadius: 14, - marginBottom: 12, + padding: 14, + borderRadius: 18, + marginBottom: 10, + elevation: 2, + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.05, + shadowRadius: 5, }, - nome: { flex: 1, marginLeft: 12, fontSize: 16, fontWeight: '600' }, -}); + avatar: { + width: 42, + height: 42, + borderRadius: 21, + justifyContent: 'center', + alignItems: 'center', + }, + avatarText: { fontSize: 16, fontWeight: '700' }, + info: { flex: 1, marginLeft: 15 }, + nome: { fontSize: 16, fontWeight: '700' }, + subText: { fontSize: 12, marginTop: 2 }, + centered: { flex: 1, justifyContent: 'center', alignItems: 'center' }, + empty: { alignItems: 'center', marginTop: 40 }, +}); \ No newline at end of file diff --git a/app/Professor/PerfilProf.tsx b/app/Professor/PerfilProf.tsx index 4d249b1..8896212 100644 --- a/app/Professor/PerfilProf.tsx +++ b/app/Professor/PerfilProf.tsx @@ -4,8 +4,7 @@ import { useEffect, useState } from 'react'; import { ActivityIndicator, Alert, - Platform // Importado para detetar o sistema operativo - , + Platform, SafeAreaView, ScrollView, StatusBar, @@ -26,7 +25,7 @@ interface PerfilData { telefone: string; residencia: string; tipo: string; - area: string; + curso: string; // Sincronizado com a coluna que criaste no Supabase idade?: number; } @@ -82,14 +81,16 @@ export default function PerfilProfessor() { telefone: perfil.telefone, residencia: perfil.residencia, n_escola: perfil.n_escola, - area: perfil.area + curso: perfil.curso // Usando o nome correto da coluna }) .eq('id', perfil.id); + if (error) throw error; setEditando(false); - Alert.alert('Sucesso', 'Perfil atualizado!'); + Alert.alert('Sucesso', 'Perfil atualizado com sucesso!'); } catch (error: any) { - Alert.alert('Erro', error.message); + // Se der erro aqui, vai dar merda porque o nome da coluna pode estar mal escrito no Supabase + Alert.alert('Erro ao gravar', 'Verifica se a coluna se chama exatamente "curso". ' + error.message); } }; @@ -107,7 +108,6 @@ export default function PerfilProfessor() { } return ( - // AJUSTE DE SAFE AREA AQUI: paddingTop dinâmico para Android e iOS - {/* TOPO COM ESPAÇAMENTO PARA NOTIFICAÇÕES */} router.back()}> @@ -128,18 +127,16 @@ export default function PerfilProfessor() { - {/* HEADER PERFIL */} {perfil?.nome} - {perfil?.area || 'Professor Orientador'} + {perfil?.curso || 'Sem curso definido'} - {/* CAMPOS DE INFORMAÇÃO */} setPerfil(prev => prev ? { ...prev, nome: v } : null)} cores={cores} /> + setPerfil(prev => prev ? { ...prev, area: v } : null)} + onChange={(v: string) => setPerfil(prev => prev ? { ...prev, curso: v } : null)} cores={cores} /> - + + + setPerfil(prev => prev ? { ...prev, n_escola: v } : null)} cores={cores} /> + - {/* BOTÕES DE AÇÃO */} {editando ? ( @@ -186,6 +186,14 @@ export default function PerfilProfessor() { )} + router.push('/Professor/redefenirsenha2')} + > + + Alterar Palavra-passe + + Terminar Sessão @@ -214,21 +222,13 @@ function InfoField({ label, value, editable, onChange, cores }: any) { } const styles = StyleSheet.create({ - // 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 - }, + topBar: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 20, marginTop: 10 }, topTitle: { fontSize: 18, fontWeight: '700' }, header: { alignItems: 'center', marginBottom: 32 }, avatar: { width: 90, height: 90, borderRadius: 45, alignItems: 'center', justifyContent: 'center', marginBottom: 12 }, diff --git a/app/Professor/ProfessorHome.tsx b/app/Professor/ProfessorHome.tsx index e001fe5..7707851 100644 --- a/app/Professor/ProfessorHome.tsx +++ b/app/Professor/ProfessorHome.tsx @@ -1,6 +1,9 @@ import { Ionicons } from '@expo/vector-icons'; import { useRouter } from 'expo-router'; +import { useEffect, useState } from 'react'; import { + ActivityIndicator, + Dimensions, Platform, SafeAreaView, ScrollView, @@ -10,38 +13,84 @@ import { TouchableOpacity, View } from 'react-native'; -import { useTheme } from '../../themecontext'; // assumindo que tens o ThemeContext +import { useTheme } from '../../themecontext'; +import { supabase } from '../lib/supabase'; + +const { width } = Dimensions.get('window'); + +// Tipagem para os ícones do Ionicons +type IonIconName = keyof typeof Ionicons.glyphMap; export default function ProfessorMenu() { const router = useRouter(); const { isDarkMode } = useTheme(); + const [nome, setNome] = useState(''); + const [loading, setLoading] = useState(true); const cores = { - fundo: isDarkMode ? '#121212' : '#f1f3f5', - card: isDarkMode ? '#1e1e1e' : '#fff', - texto: isDarkMode ? '#fff' : '#212529', - textoSecundario: isDarkMode ? '#adb5bd' : '#6c757d', - azul: '#0d6efd', + fundo: isDarkMode ? '#0F0F0F' : '#F8FAFC', + card: isDarkMode ? '#1A1A1A' : '#FFFFFF', + texto: isDarkMode ? '#F8FAFC' : '#1E293B', + textoSecundario: isDarkMode ? '#94A3B8' : '#64748B', + azul: '#3B82F6', + azulSuave: isDarkMode ? 'rgba(59, 130, 246, 0.15)' : 'rgba(59, 130, 246, 0.1)', }; + useEffect(() => { + async function obterNome() { + try { + const { data: { user } } = await supabase.auth.getUser(); + if (user) { + const { data, error } = await supabase + .from('profiles') + .select('nome') + .eq('id', user.id) + .single(); + + if (error) throw error; + // Alterado para carregar o nome completo sem divisões + if (data?.nome) setNome(data.nome); + } + } catch (err: any) { + console.error("Erro ao carregar nome:", err.message); + } finally { + setLoading(false); + } + } + obterNome(); + }, []); + return ( - - - - {/* HEADER */} + + + {/* HEADER COM NOME COMPLETO */} - Olá, - Professor 👨‍🏫 - Gerencie os estágios facilmente + Bem-vindo, + {loading ? ( + + ) : ( + + {nome || 'Professor'} + + )} + + + + Painel de Gestão de Estágios + {/* GRID DE OPÇÕES */} - router.push('/Professor/Alunos/Sumarios')} cores={cores} /> @@ -85,7 +134,7 @@ export default function ProfessorMenu() { router.push('/Professor/Alunos/Estagios')} cores={cores} /> @@ -105,33 +154,30 @@ export default function ProfessorMenu() { onPress={() => router.push('/Professor/Empresas/ListaEmpresas')} cores={cores} /> - - ); } -/* CARD REUTILIZÁVEL */ -function MenuCard({ - icon, - title, - subtitle, - onPress, - cores, -}: { - icon: any; - title: string; - subtitle: string; - onPress: () => void; - cores: any; +function MenuCard({ icon, title, subtitle, onPress, cores }: { + icon: IonIconName; + title: string; + subtitle: string; + onPress: () => void; + cores: any }) { return ( - - - {title} - {subtitle} + + + + + {title} + {subtitle} ); } @@ -139,27 +185,43 @@ function MenuCard({ const styles = StyleSheet.create({ container: { flex: 1, - paddingTop: Platform.OS === 'android' ? StatusBar.currentHeight : 0, + paddingTop: Platform.OS === 'android' ? (StatusBar.currentHeight ?? 0) + 10 : 10, }, content: { - padding: 24, + padding: 20, }, header: { marginBottom: 32, }, welcome: { - fontSize: 16, - fontWeight: '500', + fontSize: 14, + fontWeight: '600', + letterSpacing: 0.5, }, name: { - fontSize: 28, + fontSize: 26, // Reduzi ligeiramente o tamanho para nomes completos não quebrarem fontWeight: '800', - marginTop: 4, + letterSpacing: -0.5, + marginBottom: 12, + }, + badge: { + flexDirection: 'row', + alignItems: 'center', + alignSelf: 'flex-start', + paddingHorizontal: 12, + paddingVertical: 6, + borderRadius: 20, + }, + dot: { + width: 6, + height: 6, + borderRadius: 3, + marginRight: 8, }, subtitle: { - fontSize: 14, - marginTop: 6, - fontWeight: '400', + fontSize: 11, + fontWeight: '800', + textTransform: 'uppercase', }, grid: { flexDirection: 'row', @@ -167,24 +229,31 @@ const styles = StyleSheet.create({ justifyContent: 'space-between', }, card: { - width: '48%', - borderRadius: 18, - padding: 20, - marginBottom: 16, - alignItems: 'flex-start', + width: (width - 55) / 2, + borderRadius: 24, + padding: 18, + marginBottom: 15, + elevation: 4, shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.08, shadowRadius: 10, - elevation: 4, + }, + iconBox: { + width: 44, + height: 44, + borderRadius: 14, + justifyContent: 'center', + alignItems: 'center', + marginBottom: 14, }, cardTitle: { - fontSize: 16, + fontSize: 15, fontWeight: '700', - marginTop: 12, }, cardSubtitle: { - fontSize: 13, - marginTop: 4, + fontSize: 12, + marginTop: 3, + fontWeight: '500', }, -}); +}); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 5edacbe..97e28c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1905,12 +1905,11 @@ } }, "node_modules/@expo/fingerprint/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "license": "ISC", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -2049,12 +2048,11 @@ } }, "node_modules/@expo/metro-config/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "license": "ISC", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -2576,27 +2574,6 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", - "license": "MIT", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", - "license": "MIT", - "dependencies": { - "@isaacs/balanced-match": "^4.0.1" - }, - "engines": { - "node": "20 || >=22" - } - }, "node_modules/@isaacs/fs-minipass": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", @@ -3933,13 +3910,12 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, - "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -4367,11 +4343,10 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, - "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -7080,12 +7055,11 @@ } }, "node_modules/expo/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "license": "ISC", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -7568,16 +7542,34 @@ "node": ">=10.13.0" } }, - "node_modules/glob/node_modules/minimatch": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", - "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", - "license": "BlueOak-1.0.0", + "node_modules/glob/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" + "balanced-match": "^4.0.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -9103,10 +9095,9 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "license": "MIT" + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==" }, "node_modules/lodash.debounce": { "version": "4.0.8", @@ -9634,10 +9625,9 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "license": "ISC", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -12147,10 +12137,9 @@ } }, "node_modules/tar": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.4.tgz", - "integrity": "sha512-AN04xbWGrSTDmVwlI4/GTlIIwMFk/XEv7uL8aa57zuvRy6s4hdBed+lVq2fAZ89XDa7Us3ANXcE3Tvqvja1kTA==", - "license": "BlueOak-1.0.0", + "version": "7.5.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.11.tgz", + "integrity": "sha512-ChjMH33/KetonMTAtpYdgUFr0tbz69Fp2v7zWxQfYZX4g5ZN2nOBXm1R2xyA+lMIKrLKIoKAwFj93jE/avX9cQ==", "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0",