diff --git a/app/Professor/Alunos/CalendarioPresencas.tsx b/app/Professor/Alunos/CalendarioPresencas.tsx new file mode 100644 index 0000000..9381354 --- /dev/null +++ b/app/Professor/Alunos/CalendarioPresencas.tsx @@ -0,0 +1,115 @@ +import { Ionicons } from '@expo/vector-icons'; +import { useLocalSearchParams, useRouter } from 'expo-router'; +import { useMemo } from 'react'; +import { + Linking, + Platform, + SafeAreaView, + ScrollView, + StatusBar, + StyleSheet, + Text, + TouchableOpacity, + View, +} from 'react-native'; +import { useTheme } from '../../../themecontext'; + +interface Presenca { + data: string; + estado: 'presente' | 'faltou'; + localizacao?: { lat: number; lng: number }; +} + +/* DADOS EXEMPLO */ +const presencasData: Presenca[] = [ + { + data: '2024-01-10', + estado: 'presente', + localizacao: { lat: 41.55, lng: -8.42 }, + }, + { data: '2024-01-11', estado: 'faltou' }, +]; + +export default function CalendarioPresencas() { + const router = useRouter(); + const { nome } = useLocalSearchParams<{ nome: string }>(); + const { isDarkMode } = useTheme(); + + const cores = useMemo( + () => ({ + fundo: isDarkMode ? '#121212' : '#f1f3f5', + card: isDarkMode ? '#1e1e1e' : '#fff', + texto: isDarkMode ? '#fff' : '#212529', + verde: '#198754', + vermelho: '#dc3545', + azul: '#0d6efd', + }), + [isDarkMode] + ); + + const abrirMapa = (lat: number, lng: number) => { + const url = + Platform.OS === 'ios' + ? `http://maps.apple.com/?ll=${lat},${lng}` + : `https://www.google.com/maps?q=${lat},${lng}`; + Linking.openURL(url); + }; + + return ( + + + + + {/* HEADER */} + + router.back()}> + + + {nome} + + + + {/* CALENDÁRIO SIMPLIFICADO */} + {presencasData.map((p, i) => ( + + + {new Date(p.data).toLocaleDateString('pt-PT')} + + + {p.estado === 'presente' ? ( + abrirMapa(p.localizacao!.lat, p.localizacao!.lng)}> + Presente (ver localização) + + ) : ( + router.push('/Professor/Alunos/Faltas')}> + + Faltou (ver página faltas) + + + )} + + ))} + + + ); +} + +const styles = StyleSheet.create({ + safe: { + flex: 1, + paddingTop: Platform.OS === 'android' ? StatusBar.currentHeight : 0, + }, + container: { padding: 20 }, + header: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: 20, + }, + title: { fontSize: 20, fontWeight: '700' }, + card: { + padding: 16, + borderRadius: 14, + marginBottom: 12, + }, +}); diff --git a/app/Professor/Alunos/DetalhesAluno.tsx b/app/Professor/Alunos/DetalhesAluno.tsx new file mode 100644 index 0000000..fd03192 --- /dev/null +++ b/app/Professor/Alunos/DetalhesAluno.tsx @@ -0,0 +1,158 @@ +import { Ionicons } from '@expo/vector-icons'; +import { useLocalSearchParams, useRouter } from 'expo-router'; +import { memo, useEffect, useState } from 'react'; +import { Platform, SafeAreaView, ScrollView, StatusBar, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; +import { useTheme } from '../../../themecontext'; + +const DetalhesAluno = memo(() => { + const { isDarkMode } = useTheme(); + const router = useRouter(); + const params = useLocalSearchParams(); + const alunoParam = Array.isArray(params.aluno) ? params.aluno[0] : params.aluno; + const aluno = alunoParam ? JSON.parse(alunoParam) : null; + + const [datas, setDatas] = useState({ inicio: '05/01/2026', fim: '30/05/2026' }); + const [stats, setStats] = useState({ horasConcluidas: 0, faltasTotais: 0, faltasJustificadas: 0, horasFaltam: 300 }); + + const cores = { + fundo: isDarkMode ? '#121212' : '#f1f3f5', + card: isDarkMode ? '#1e1e1e' : '#fff', + texto: isDarkMode ? '#fff' : '#000', + textoSecundario: isDarkMode ? '#adb5bd' : '#6c757d', + borda: isDarkMode ? '#333' : '#f1f3f5', + azul: '#0d6efd', + verde: '#198754', + vermelho: '#dc3545' + }; + + useEffect(() => { + // Simulação de cálculo de horas/faltas para este aluno + const totalHorasEstagio = 300; + const horasConcluidas = Math.floor(Math.random() * 250); // exemplo randomizado + const faltasTotais = Math.floor(Math.random() * 10); + const faltasJustificadas = Math.floor(Math.random() * faltasTotais); + setStats({ + horasConcluidas, + faltasTotais, + faltasJustificadas, + horasFaltam: Math.max(0, totalHorasEstagio - horasConcluidas) + }); + }, []); + + if (!aluno) { + return ( + + Nenhum aluno selecionado. + + ); + } + + return ( + + + + + router.back()}> + + + {aluno.nome} + + + + + + {/* Dados Pessoais */} + + Dados Pessoais + Nome + {aluno.nome} + + Email + {aluno.email} + + Telefone + {aluno.telefone} + + Turma + {aluno.turma} + + + {/* Empresa de Estágio */} + + Empresa de Estágio + Empresa + {aluno.empresa} + + Cargo + {aluno.cargo} + + + {/* Dados do Estágio / Horas */} + + Horas de Estágio + + + Totais + {300}h + + + Concluídas + {stats.horasConcluidas}h + + + Faltam + {stats.horasFaltam}h + + + + Horário Semanal + + + Período + Horário + + + Manhã + 09:30 - 13:00 + + + Almoço + 13:00 - 14:30 + + + Tarde + 14:30 - 17:30 + + + + Total: 7 horas diárias por presença + + + + + ); +}); + +export default DetalhesAluno; + +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 }, + spacer: { width: 40 }, + tituloGeral: { fontSize: 22, fontWeight: 'bold' }, + container: { padding: 20, gap: 20, paddingBottom: 40 }, + card: { padding: 20, borderRadius: 16, elevation: 2, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4 }, + tituloCard: { fontSize: 18, fontWeight: 'bold', textAlign: 'center', marginBottom: 10, borderBottomWidth: 1, paddingBottom: 8 }, + label: { marginTop: 12, fontSize: 13 }, + valor: { fontSize: 16, fontWeight: '600' }, + rowStats: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 5 }, + itemStat: { alignItems: 'center', flex: 1 }, + 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, marginTop: 8, fontWeight: '500' } +}); diff --git a/app/Professor/Alunos/Estagios.tsx b/app/Professor/Alunos/Estagios.tsx new file mode 100644 index 0000000..588d63d --- /dev/null +++ b/app/Professor/Alunos/Estagios.tsx @@ -0,0 +1,225 @@ +// app/Professor/Estagios.tsx +import { Ionicons } from '@expo/vector-icons'; +import { useRouter } from 'expo-router'; +import { useMemo, useState } from 'react'; +import { + Alert, + Modal, + Platform, + SafeAreaView, + ScrollView, + StatusBar, + StyleSheet, + Text, + TextInput, + TouchableOpacity, + View +} from 'react-native'; +import { useTheme } from '../../../themecontext'; // mesmo theme context das definições + +interface Aluno { + id: number; + nome: string; +} + +interface Empresa { + id: number; + nome: string; +} + +interface Estagio { + id: number; + alunoId: number; + empresaId: number; + alunoNome: string; + empresaNome: 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(); + + const cores = useMemo(() => ({ + fundo: isDarkMode ? '#121212' : '#f1f3f5', + card: isDarkMode ? '#1e1e1e' : '#fff', + texto: isDarkMode ? '#fff' : '#212529', + textoSecundario: isDarkMode ? '#adb5bd' : '#6c757d', + border: isDarkMode ? '#343a40' : '#ced4da', + azul: '#0d6efd', + vermelho: '#dc3545', + }), [isDarkMode]); + + // Estados + const [estagios, setEstagios] = useState([]); + const [modalVisible, setModalVisible] = useState(false); + const [alunoSelecionado, setAlunoSelecionado] = useState(null); + const [empresaSelecionada, setEmpresaSelecionada] = useState(null); + const [editandoEstagio, setEditandoEstagio] = useState(null); + const [searchText, setSearchText] = useState(''); + + // Filtrar estágios por busca + const estagiosFiltrados = estagios.filter(e => + e.alunoNome.toLowerCase().includes(searchText.toLowerCase()) || + e.empresaNome.toLowerCase().includes(searchText.toLowerCase()) + ); + + const abrirModalNovo = () => { + setAlunoSelecionado(null); + setEmpresaSelecionada(null); + setEditandoEstagio(null); + setModalVisible(true); + }; + + const salvarEstagio = () => { + if (!alunoSelecionado || !empresaSelecionada) { + Alert.alert('Erro', 'Selecione um aluno e uma empresa existentes.'); + return; + } + + 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]); + } + + setModalVisible(false); + }; + + 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); + }; + + return ( + + + + {/* Header */} + + router.back()}> + + + Estágios + + + + {/* Pesquisa */} + + + {/* Lista de estágios */} + + {estagiosFiltrados.map(e => ( + editarEstagio(e)}> + {e.alunoNome} + {e.empresaNome} + + + ))} + {estagiosFiltrados.length === 0 && Nenhum estágio encontrado.} + + + {/* Botão Novo Estágio */} + + + Novo Estágio + + + {/* Modal de criação/edição */} + + + + {editandoEstagio ? 'Editar Estágio' : 'Novo Estágio'} + + {/* Dropdown Aluno */} + Aluno + {alunosData.map(a => ( + setAlunoSelecionado(a)} + > + {a.nome} + + ))} + + {/* Dropdown Empresa */} + Empresa + {empresasData.map(emp => ( + setEmpresaSelecionada(emp)} + > + {emp.nome} + + ))} + + + setModalVisible(false)} style={[styles.modalButton, { backgroundColor: cores.vermelho }]}> + Cancelar + + + Salvar + + + + + + + + ); +} + +// 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 } +}); diff --git a/app/Professor/Alunos/Faltas.tsx b/app/Professor/Alunos/Faltas.tsx new file mode 100644 index 0000000..1d964ad --- /dev/null +++ b/app/Professor/Alunos/Faltas.tsx @@ -0,0 +1,199 @@ +import { Ionicons } from '@expo/vector-icons'; +import { useRouter } from 'expo-router'; +import { useState } from 'react'; +import { + Modal, + Platform, + SafeAreaView, + ScrollView, + StatusBar, + StyleSheet, + Text, + TouchableOpacity, + View, +} from 'react-native'; +import { WebView } from 'react-native-webview'; +import { useTheme } from '../../../themecontext'; // <- seu contexto de tema + +interface Falta { + dia: string; + motivo?: string; + pdfUrl?: string; +} + +interface Aluno { + id: number; + nome: string; + faltas: Falta[]; +} + +// Dados simulados +const alunosData: Aluno[] = [ + { + id: 1, + nome: 'João Silva', + faltas: [ + { dia: '2026-01-20', motivo: 'Doença', pdfUrl: 'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf' }, + { dia: '2026-01-22', motivo: 'Atraso' }, + ], + }, + { + id: 2, + nome: 'Maria Fernandes', + faltas: [ + { dia: '2026-01-21', motivo: 'Consulta médica' }, + { dia: '2026-01-23', motivo: 'Doença', pdfUrl: 'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf' }, + ], + }, +]; + +export default function FaltasAlunos() { + const router = useRouter(); + const { isDarkMode } = useTheme(); + + const cores = { + fundo: isDarkMode ? '#121212' : '#f1f3f5', + card: isDarkMode ? '#1e1e1e' : '#fff', + texto: isDarkMode ? '#fff' : '#212529', + textoSecundario: isDarkMode ? '#adb5bd' : '#6c757d', + azul: '#0d6efd', + verde: '#198754', + vermelho: '#dc3545', + }; + + const [alunoSelecionado, setAlunoSelecionado] = useState(null); + const [pdfModalVisible, setPdfModalVisible] = useState(false); + const [pdfUrl, setPdfUrl] = useState(null); + + const verPdf = (falta: Falta) => { + if (falta.pdfUrl) { + setPdfUrl(falta.pdfUrl); + setPdfModalVisible(true); + } + }; + + return ( + + + + + {/* Top Bar */} + + { + if (alunoSelecionado) { + setAlunoSelecionado(null); // voltar para lista de alunos + } else { + router.back(); // voltar para menu + } + }}> + + + + {!alunoSelecionado ? 'Faltas dos Alunos' : alunoSelecionado.nome} + + + + + {/* Lista de alunos */} + {!alunoSelecionado && ( + + {alunosData.map(aluno => ( + setAlunoSelecionado(aluno)} + > + + {aluno.nome} + + + ))} + + )} + + {/* Faltas do aluno */} + {alunoSelecionado && ( + + {alunoSelecionado.faltas.map((falta, idx) => ( + + + {falta.dia} + {falta.motivo || 'Sem motivo'} + + {falta.pdfUrl ? 'Justificada com PDF' : 'Não justificada'} + + + {falta.pdfUrl && ( + verPdf(falta)}> + + + )} + + ))} + + )} + + + {/* Modal PDF */} + + + + setPdfModalVisible(false)}> + + + Visualizador de PDF + + + {pdfUrl && } + + + + ); +} + +const styles = StyleSheet.create({ + safe: { flex: 1, paddingTop: Platform.OS === 'android' ? StatusBar.currentHeight : 0 }, + container: { padding: 20, paddingBottom: 40 }, + topBar: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 }, + topTitle: { fontSize: 20, fontWeight: '700', textAlign: 'center', flex: 1 }, + alunoCard: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + padding: 16, + borderRadius: 14, + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 4, + elevation: 2, + }, + alunoName: { fontSize: 16, fontWeight: '600', marginLeft: 12, flex: 1 }, + faltaCard: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + padding: 16, + borderRadius: 14, + marginBottom: 12, + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 4, + elevation: 2, + }, + dia: { fontSize: 14, fontWeight: '700', marginBottom: 4 }, + motivo: { fontSize: 14, marginBottom: 4 }, + status: { fontSize: 13, fontWeight: '600' }, + pdfHeader: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + padding: 16, + borderBottomWidth: 1, + }, + pdfTitle: { fontSize: 18, fontWeight: '700' }, +}); diff --git a/app/Professor/Alunos/ListaAlunos.tsx b/app/Professor/Alunos/ListaAlunos.tsx new file mode 100644 index 0000000..cf7a21f --- /dev/null +++ b/app/Professor/Alunos/ListaAlunos.tsx @@ -0,0 +1,124 @@ +import { Ionicons } from '@expo/vector-icons'; +import { useRouter } from 'expo-router'; +import { memo, useState } from 'react'; +import { FlatList, Platform, SafeAreaView, StatusBar, StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native'; +import { useTheme } from '../../../themecontext'; + +export interface Aluno { + id: number; + nome: string; + estado: string; + email: string; + telefone: string; + turma: string; + empresa: string; + cargo: string; +} + +interface Turma { + nome: string; + alunos: Aluno[]; +} + +const turmasData: Turma[] = [ + { nome: '12ºA', alunos: [{ id: 1, nome: 'João Silva', estado: 'Em estágio', email: 'joao@escola.pt', telefone: '912345678', turma: '12ºA', empresa: 'Empresa X', cargo: 'Dev' }] }, + { nome: '12ºB', alunos: [{ id: 2, nome: 'Maria Fernandes', estado: 'Em estágio', email: 'maria@escola.pt', telefone: '912345681', turma: '12ºB', empresa: 'Empresa W', cargo: 'Design' }] }, +]; + +const ListaAlunosProfessor = memo(() => { + const { isDarkMode } = useTheme(); + const router = useRouter(); + const [search, setSearch] = useState(''); + const [expanded, setExpanded] = useState<{ [key: string]: boolean }>({}); + + const cores = { + fundo: isDarkMode ? '#121212' : '#f1f3f5', + card: isDarkMode ? '#1e1e1e' : '#fff', + texto: isDarkMode ? '#fff' : '#000', + textoSecundario: isDarkMode ? '#adb5bd' : '#6c757d', + azul: '#0d6efd', + }; + + const toggleExpand = (turmaNome: string) => setExpanded(prev => ({ ...prev, [turmaNome]: !prev[turmaNome] })); + + const filteredTurmas = turmasData + .map(turma => ({ + ...turma, + alunos: turma.alunos.filter(a => a.nome.toLowerCase().includes(search.toLowerCase())), + })) + .filter(t => t.alunos.length > 0); + + return ( + + + + {/* Header com botão voltar */} + + router.back()}> + + + Alunos + + + + {/* Input de pesquisa */} + + + {/* Lista de turmas e alunos */} + item.nome} + renderItem={({ item }) => ( + + toggleExpand(item.nome)}> + {item.nome} ({item.alunos.length}) + + + {expanded[item.nome] && ( + + {item.alunos.map(aluno => ( + + router.push({ + pathname: '/Professor/Alunos/DetalhesAluno', + params: { aluno: JSON.stringify(aluno) } + }) + } + > + {aluno.nome} + {aluno.estado} + + ))} + + )} + + )} + /> + + ); +}); + +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, marginBottom: 10, elevation: 2 }, + turmaNome: { fontSize: 18, fontWeight: 'bold' }, + listaAlunos: { marginTop: 10, paddingLeft: 10 }, + alunoItem: { paddingVertical: 8 }, + alunoNome: { fontSize: 16, fontWeight: '600' }, + alunoEstado: { fontSize: 14, marginTop: 2 }, +}); diff --git a/app/Professor/Alunos/Presencas.tsx b/app/Professor/Alunos/Presencas.tsx new file mode 100644 index 0000000..a80d9c8 --- /dev/null +++ b/app/Professor/Alunos/Presencas.tsx @@ -0,0 +1,140 @@ +import { Ionicons } from '@expo/vector-icons'; +import { useRouter } from 'expo-router'; +import { useMemo, useState } from 'react'; +import { + Platform, + SafeAreaView, + ScrollView, + StatusBar, + StyleSheet, + Text, + TextInput, + TouchableOpacity, + View, +} from 'react-native'; +import { useTheme } from '../../../themecontext'; + +interface Aluno { + id: number; + 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 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', + }), + [isDarkMode] + ); + + const alunosFiltrados = alunosData.filter(a => + a.nome.toLowerCase().includes(pesquisa.toLowerCase()) + ); + + const turmas = Array.from(new Set(alunosFiltrados.map(a => a.turma))); + + return ( + + + + + {/* HEADER */} + + router.back()}> + + + 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 }, + }) + } + > + + {aluno.nome} + + + ))} + + ))} + + + ); +} + +const styles = StyleSheet.create({ + safe: { + flex: 1, + paddingTop: Platform.OS === 'android' ? StatusBar.currentHeight : 0, + }, + container: { padding: 20 }, + header: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: 20, + }, + title: { fontSize: 24, fontWeight: '700' }, + searchBox: { + flexDirection: 'row', + alignItems: 'center', + borderWidth: 1, + borderRadius: 14, + paddingHorizontal: 12, + marginBottom: 20, + }, + searchInput: { flex: 1, marginLeft: 8, paddingVertical: 10 }, + turma: { fontSize: 16, fontWeight: '700', marginBottom: 10 }, + card: { + flexDirection: 'row', + alignItems: 'center', + padding: 16, + borderRadius: 14, + marginBottom: 12, + }, + nome: { flex: 1, marginLeft: 12, fontSize: 16, fontWeight: '600' }, +}); diff --git a/app/Professor/Alunos/Sumarios.tsx b/app/Professor/Alunos/Sumarios.tsx new file mode 100644 index 0000000..2ae9c28 --- /dev/null +++ b/app/Professor/Alunos/Sumarios.tsx @@ -0,0 +1,156 @@ +import { Ionicons } from '@expo/vector-icons'; +import { useRouter } from 'expo-router'; +import { memo, useMemo, useState } from 'react'; +import { + Platform, + SafeAreaView, + ScrollView, + StatusBar, + StyleSheet, + Text, + TouchableOpacity, + View, +} from 'react-native'; +import { useTheme } from '../../../themecontext'; // Contexto global do tema + +interface Sumario { + dia: string; + conteudo: string; +} + +interface Aluno { + id: number; + nome: string; + sumarios: Sumario[]; +} + +// Dados simulados +const alunosData: Aluno[] = [ + { + id: 1, + nome: 'João Silva', + sumarios: [ + { dia: '2026-01-20', conteudo: 'Aprendeu sobre React Native e navegação.' }, + { dia: '2026-01-21', conteudo: 'Configurou Supabase e salvou dados.' }, + ], + }, + { + id: 2, + nome: 'Maria Fernandes', + sumarios: [ + { dia: '2026-01-20', conteudo: 'Estudou TypeScript e componentes.' }, + { dia: '2026-01-21', conteudo: 'Criou layout de página de empresas.' }, + ], + }, +]; + +export default memo(function SumariosAlunos() { + const router = useRouter(); + const { isDarkMode } = useTheme(); // pega tema global + const [alunoSelecionado, setAlunoSelecionado] = useState(null); + + // Cores dinamicas + const cores = useMemo( + () => ({ + fundo: isDarkMode ? '#121212' : '#f1f3f5', + card: isDarkMode ? '#1e1e1e' : '#fff', + texto: isDarkMode ? '#fff' : '#212529', + textoSecundario: isDarkMode ? '#adb5bd' : '#6c757d', + azul: '#0d6efd', + }), + [isDarkMode] + ); + + return ( + + + + + {/* Cabeçalho */} + + (alunoSelecionado ? setAlunoSelecionado(null) : router.back())} + style={styles.backButtonHeader} + > + + + + Sumários de Estágio + + + {/* Lista de alunos */} + {!alunoSelecionado && ( + + {alunosData.map(aluno => ( + setAlunoSelecionado(aluno)} + > + + {aluno.nome} + + + ))} + + )} + + {/* Sumários do aluno */} + {alunoSelecionado && ( + + {alunoSelecionado.nome} + + {alunoSelecionado.sumarios.map((s, idx) => ( + + {s.dia} + {s.conteudo} + + ))} + + )} + + + ); +}); + +const styles = StyleSheet.create({ + safe: { flex: 1, paddingTop: Platform.OS === 'android' ? StatusBar.currentHeight : 0 }, + container: { padding: 20, paddingBottom: 40 }, + + header: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: 16, + justifyContent: 'center', + position: 'relative', + }, + backButtonHeader: { position: 'absolute', left: 0, padding: 4 }, + title: { fontSize: 24, fontWeight: '700', textAlign: 'center' }, + + alunosList: { gap: 12 }, + alunoCard: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + padding: 16, + borderRadius: 14, + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 4, + elevation: 2, + }, + alunoName: { fontSize: 16, fontWeight: '600', marginLeft: 12, flex: 1 }, + + sumariosContainer: { marginTop: 10 }, + alunoTitle: { fontSize: 20, fontWeight: '700', marginBottom: 12 }, + sumarioCard: { padding: 16, borderRadius: 14, marginBottom: 12 }, + dia: { fontSize: 14, fontWeight: '700', marginBottom: 6 }, + conteudo: { fontSize: 14, lineHeight: 20 }, +}); diff --git a/app/Professor/Empresas/DetalhesEmpresa.tsx b/app/Professor/Empresas/DetalhesEmpresa.tsx new file mode 100644 index 0000000..1a129bb --- /dev/null +++ b/app/Professor/Empresas/DetalhesEmpresa.tsx @@ -0,0 +1,155 @@ +import { Ionicons } from '@expo/vector-icons'; +import { useLocalSearchParams, useRouter } from 'expo-router'; +import { memo, useMemo, useState } from 'react'; +import { + Alert, + Platform, + SafeAreaView, + ScrollView, + StatusBar, + StyleSheet, + Text, + TextInput, + TouchableOpacity, + View +} from 'react-native'; +import { useTheme } from '../../../themecontext'; +import type { Empresa } from './ListaEmpresas'; + +const DetalhesEmpresa = memo(() => { + const { isDarkMode } = useTheme(); + const router = useRouter(); + const params = useLocalSearchParams(); + + // Parse da empresa recebida + const empresaOriginal: Empresa = useMemo(() => { + if (!params.empresa) return null as any; + const str = Array.isArray(params.empresa) ? params.empresa[0] : params.empresa; + return JSON.parse(str); + }, [params.empresa]); + + const [empresaLocal, setEmpresaLocal] = useState({ ...empresaOriginal }); + const [editando, setEditando] = useState(false); + + const cores = { + fundo: isDarkMode ? '#121212' : '#f1f3f5', + card: isDarkMode ? '#1e1e1e' : '#fff', + texto: isDarkMode ? '#fff' : '#000', + textoSecundario: isDarkMode ? '#adb5bd' : '#6c757d', + azul: '#0d6efd', + verde: '#198754', + vermelho: '#dc3545', + }; + + const handleSave = () => { + setEditando(false); + Alert.alert('Sucesso', 'Empresa atualizada!'); + // Aqui depois substituis por update no Supabase + }; + + const handleDelete = () => { + Alert.alert( + 'Apagar Empresa', + 'Tem a certeza que deseja apagar esta empresa?', + [ + { text: 'Cancelar', style: 'cancel' }, + { text: 'Apagar', style: 'destructive', onPress: () => router.back() } + ] + ); + }; + + return ( + + + + + router.back()}> + + + + {empresaLocal.nome} + + + setEditando(!editando)}> + + + + + + + + + + {/* Dados da Empresa */} + + Dados da Empresa + + {['nome', 'curso', 'morada', 'tutor', 'telefone'].map((campo) => ( + + + {campo.charAt(0).toUpperCase() + campo.slice(1)} + + {editando ? ( + setEmpresaLocal({ ...empresaLocal, [campo]: v })} + /> + ) : ( + + {(empresaLocal as any)[campo]} + + )} + + ))} + + + {/* Botão Guardar alterações */} + {editando && ( + + Guardar alterações + + )} + + {/* Estatísticas */} + + Estatísticas + Total de Alunos + + {empresaLocal.alunos?.length || 0} + + + + {/* Lista de Alunos */} + {empresaLocal.alunos && empresaLocal.alunos.length > 0 && ( + + Alunos na Empresa + {empresaLocal.alunos.map((aluno, index) => ( + + {aluno} + + ))} + + )} + + + + ); +}); + +export default DetalhesEmpresa; + +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 }, + btnAcao: { width: 40, height: 40, borderRadius: 10, justifyContent: 'center', alignItems: 'center' }, + tituloGeral: { fontSize: 22, fontWeight: 'bold' }, + container: { padding: 20, gap: 20, paddingBottom: 40 }, + card: { padding: 20, borderRadius: 16, elevation: 2, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4 }, + tituloCard: { fontSize: 18, fontWeight: 'bold', textAlign: 'center', marginBottom: 10, borderBottomWidth: 1, paddingBottom: 8 }, + label: { marginTop: 12, fontSize: 13 }, + valor: { fontSize: 16, fontWeight: '600' }, + input: { borderWidth: 1, borderRadius: 10, padding: 10, marginTop: 2 }, + saveButton: { padding: 16, borderRadius: 10, marginTop: 16 }, +}); diff --git a/app/Professor/Empresas/ListaEmpresas.tsx b/app/Professor/Empresas/ListaEmpresas.tsx new file mode 100644 index 0000000..e2af789 --- /dev/null +++ b/app/Professor/Empresas/ListaEmpresas.tsx @@ -0,0 +1,136 @@ +import { Ionicons } from '@expo/vector-icons'; +import { useRouter } from 'expo-router'; +import { memo, useMemo, useState } from 'react'; +import { + Alert, + FlatList, Platform, SafeAreaView, StatusBar, + StyleSheet, Text, TextInput, TouchableOpacity, View +} from 'react-native'; +import { useTheme } from '../../../themecontext'; + +export interface Empresa { + id: number; + nome: string; + morada: string; + tutor: string; + telefone: string; + curso: string; + alunos?: string[]; +} + +const initialEmpresasData: Empresa[] = [ + { id: 1, nome: 'Empresa X', morada: 'Rua das Flores, 12', tutor: 'João Santos', telefone: '912345678', curso: 'Técnico de Informática', alunos: ['João Silva'] }, + { id: 2, nome: 'Empresa W', morada: 'Av. Central, 45', tutor: 'Ana Costa', telefone: '912345679', curso: 'Técnico de Design', alunos: ['Maria Fernandes'] }, +]; + +const ListaEmpresasProfessor = memo(() => { + const { isDarkMode } = useTheme(); + const router = useRouter(); + const [search, setSearch] = useState(''); + const [empresas, setEmpresas] = useState(initialEmpresasData); + + const cores = { + fundo: isDarkMode ? '#121212' : '#f1f3f5', + card: isDarkMode ? '#1e1e1e' : '#fff', + texto: isDarkMode ? '#fff' : '#000', + textoSecundario: isDarkMode ? '#adb5bd' : '#6c757d', + azul: '#0d6efd', + }; + + // Filtrar empresas + const filteredEmpresas = useMemo( + () => empresas.filter(e => e.nome.toLowerCase().includes(search.toLowerCase())), + [search, empresas] + ); + + // Criar nova empresa + const criarEmpresa = () => { + Alert.prompt( + 'Nova Empresa', + 'Insira o nome da empresa', + (nome) => { + if (!nome) return; + const nova: Empresa = { + id: Date.now(), + nome, + morada: 'Não definida', + tutor: 'Não definido', + telefone: '000000000', + curso: 'Não definido', + alunos: [] + }; + setEmpresas(prev => [nova, ...prev]); + router.push({ + pathname: '/Professor/Empresas/DetalhesEmpresa', + params: { empresa: JSON.stringify(nova) } + }); + } + ); + }; + + return ( + + + + {/* Header com botão voltar */} + + router.back()}> + + + Empresas + + + + {/* Input de pesquisa */} + + + {/* Botão criar empresa */} + + + Criar Empresa + + + {/* Lista de empresas */} + item.id.toString()} + renderItem={({ item }) => ( + + router.push({ + pathname: '/Professor/Empresas/DetalhesEmpresa', + params: { empresa: JSON.stringify(item) } + }) + } + > + {item.nome} + {item.curso} + {item.tutor} + + )} + /> + + ); +}); + +export default ListaEmpresasProfessor; + +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 }, + btnCriar: { marginHorizontal: 10, padding: 12, borderRadius: 10, alignItems: 'center', marginBottom: 10 }, + card: { borderRadius: 10, padding: 15, marginBottom: 10, elevation: 2 }, + nomeEmpresa: { fontSize: 18, fontWeight: 'bold' }, + curso: { fontSize: 14, marginTop: 2 }, + tutor: { fontSize: 14, marginTop: 2 }, +}); diff --git a/app/Professor/ListaAlunos.tsx b/app/Professor/ListaAlunos.tsx deleted file mode 100644 index 4dd0a12..0000000 --- a/app/Professor/ListaAlunos.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import { - StyleSheet -} from 'react-native'; - -/* DADOS MOCK (SUBSTITUI PELO SUPABASE DEPOIS) */ -const alunos = [ - { - id: '1', - nome: 'Ana Martins', - curso: 'Técnico de Informática', - turma: '12ºTIG', - empresa: 'Tech Solutions', - }, - { - id: '2', - nome: 'Pedro Costa', - curso: 'Técnico de Informática', - turma: '11ºTIG', - empresa: 'SoftDev Lda', - }, - { - id: '3', - nome: 'Rita Fernandes', - curso: 'Técnico de Informática', - turma: '12ºTIG', - empresa: 'WebWorks', - }, -]; - - -/* ESTILOS */ - -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: '#f1f3f5', - }, - content: { - padding: 20, - }, - header: { - flexDirection: 'row', - alignItems: 'center', - marginBottom: 24, - }, - title: { - flex: 1, - textAlign: 'center', - fontSize: 20, - fontWeight: '800', - color: '#212529', - }, - card: { - flexDirection: 'row', - alignItems: 'center', - backgroundColor: '#fff', - borderRadius: 16, - padding: 16, - marginBottom: 12, - shadowColor: '#000', - shadowOpacity: 0.06, - shadowRadius: 8, - elevation: 3, - }, - avatar: { - width: 44, - height: 44, - borderRadius: 22, - backgroundColor: '#0d6efd', - alignItems: 'center', - justifyContent: 'center', - marginRight: 14, - }, - avatarText: { - color: '#fff', - fontWeight: '800', - fontSize: 18, - }, - info: { - flex: 1, - }, - nome: { - fontSize: 16, - fontWeight: '700', - color: '#212529', - }, - sub: { - fontSize: 13, - color: '#6c757d', - marginTop: 2, - }, - empresa: { - fontSize: 13, - color: '#495057', - marginTop: 4, - }, -}); diff --git a/app/Professor/PerfilProf.tsx b/app/Professor/PerfilProf.tsx index e82bf37..a66d6ef 100644 --- a/app/Professor/PerfilProf.tsx +++ b/app/Professor/PerfilProf.tsx @@ -3,21 +3,36 @@ import { useRouter } from 'expo-router'; import { useState } from 'react'; import { Alert, + Platform, SafeAreaView, ScrollView, + StatusBar, StyleSheet, Text, TextInput, TouchableOpacity, View, } from 'react-native'; +import { useTheme } from '../../themecontext'; // Contexto global do tema import { supabase } from '../lib/supabase'; export default function PerfilProfessor() { const router = useRouter(); - + const { isDarkMode } = useTheme(); // pega do contexto global const [editando, setEditando] = useState(false); + // 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', + border: isDarkMode ? '#343a40' : '#ced4da', + azul: '#0d6efd', + vermelho: '#dc3545', + }; + const [perfil, setPerfil] = useState({ nome: 'João Miranda', email: 'joao.miranda@epvc.pt', @@ -35,114 +50,119 @@ export default function PerfilProfessor() { const guardarPerfil = () => { setEditando(false); Alert.alert('Sucesso', 'Perfil atualizado com sucesso!'); - // aqui depois ligas ao Supabase (update) + // depois liga ao Supabase update }; return ( - + + - {/* TOPO COM VOLTAR */} - router.back()}> - + router.push('/Professor/ProfessorHome')}> + - Perfil + Perfil {/* HEADER */} - + - {perfil.nome} - {perfil.funcao} + {perfil.nome} + {perfil.funcao} {/* INFORMAÇÕES */} - - - setPerfil({ ...perfil, nome: v })} + + setPerfil({ ...perfil, nome: v })} + cores={cores} /> - - - - setPerfil({ ...perfil, mecanografico: v })} + + setPerfil({ ...perfil, mecanografico: v })} + cores={cores} /> - - setPerfil({ ...perfil, area: v })} + setPerfil({ ...perfil, area: v })} + cores={cores} /> - - setPerfil({ ...perfil, turmas: v })} + setPerfil({ ...perfil, turmas: v })} + cores={cores} /> - {/* AÇÕES */} - {editando ? ( - + Guardar alterações ) : ( - setEditando(true)} - /> + setEditando(true)} cores={cores} /> )} router.push('/redefenirsenha2')} - /> - - router.push('/Professor/redefenirsenha2')} + cores={cores} /> + - ); } /* COMPONENTES */ - function InfoField({ label, value, editable, onChange, + cores, }: { label: string; value: string; editable: boolean; onChange?: (v: string) => void; + cores: any; }) { return ( - {label} + {label} {editable ? ( ) : ( - {value} + {value} )} ); @@ -153,19 +173,31 @@ function ActionButton({ text, onPress, danger = false, + cores, }: { icon: any; text: string; onPress: () => void; danger?: boolean; + cores: any; }) { return ( - - + + {text} @@ -173,82 +205,28 @@ function ActionButton({ } /* ESTILOS */ - const styles = StyleSheet.create({ - container: { flex: 1, backgroundColor: '#f1f3f5' }, + container: { flex: 1 }, content: { padding: 24 }, - topBar: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - marginBottom: 20, - }, - topTitle: { - fontSize: 18, - fontWeight: '700', - color: '#212529', - }, + topBar: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 20 }, + topTitle: { fontSize: 18, fontWeight: '700' }, header: { alignItems: 'center', marginBottom: 32 }, - avatar: { - width: 90, height: 90, borderRadius: 45, - backgroundColor: '#0d6efd', - alignItems: 'center', justifyContent: 'center', - marginBottom: 12, - }, - name: { fontSize: 22, fontWeight: '800', color: '#212529' }, - role: { fontSize: 14, color: '#6c757d', marginTop: 4 }, + avatar: { width: 90, height: 90, borderRadius: 45, alignItems: 'center', justifyContent: 'center', marginBottom: 12 }, + name: { fontSize: 22, fontWeight: '800' }, + role: { fontSize: 14, marginTop: 4 }, - card: { - backgroundColor: '#fff', - borderRadius: 18, - padding: 20, - marginBottom: 24, - elevation: 4, - }, + card: { borderRadius: 18, padding: 20, marginBottom: 24, elevation: 4 }, infoField: { marginBottom: 16 }, - label: { fontSize: 12, color: '#6c757d', marginBottom: 4 }, - value: { fontSize: 15, fontWeight: '600', color: '#212529' }, - input: { - borderWidth: 1, - borderColor: '#ced4da', - borderRadius: 10, - padding: 10, - fontSize: 15, - backgroundColor: '#f8f9fa', - }, + label: { fontSize: 12, marginBottom: 4 }, + value: { fontSize: 15, fontWeight: '600' }, + input: { borderWidth: 1, borderRadius: 10, padding: 10, fontSize: 15 }, actions: { gap: 12 }, - - actionButton: { - flexDirection: 'row', - alignItems: 'center', - backgroundColor: '#fff', - borderRadius: 14, - padding: 16, - }, - actionText: { - fontSize: 15, - fontWeight: '600', - marginLeft: 12, - color: '#0d6efd', - }, - - primaryButton: { - backgroundColor: '#0d6efd', - borderRadius: 14, - padding: 16, - flexDirection: 'row', - justifyContent: 'center', - alignItems: 'center', - }, + 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 }, - - dangerButton: { - borderWidth: 1, - borderColor: '#dc3545', - }, - dangerText: { color: '#dc3545' }, }); diff --git a/app/Professor/ProfessorHome.tsx b/app/Professor/ProfessorHome.tsx index 414f23c..e001fe5 100644 --- a/app/Professor/ProfessorHome.tsx +++ b/app/Professor/ProfessorHome.tsx @@ -1,26 +1,42 @@ import { Ionicons } from '@expo/vector-icons'; import { useRouter } from 'expo-router'; import { + Platform, SafeAreaView, ScrollView, + StatusBar, StyleSheet, Text, TouchableOpacity, - View, + View } from 'react-native'; +import { useTheme } from '../../themecontext'; // assumindo que tens o ThemeContext export default function ProfessorMenu() { const router = useRouter(); + const { isDarkMode } = useTheme(); + + const cores = { + fundo: isDarkMode ? '#121212' : '#f1f3f5', + card: isDarkMode ? '#1e1e1e' : '#fff', + texto: isDarkMode ? '#fff' : '#212529', + textoSecundario: isDarkMode ? '#adb5bd' : '#6c757d', + azul: '#0d6efd', + }; return ( - + + {/* HEADER */} - Bem-vindo, - Professor 👨‍🏫 - Painel de gestão + Olá, + Professor 👨‍🏫 + Gerencie os estágios facilmente {/* GRID DE OPÇÕES */} @@ -30,35 +46,64 @@ export default function ProfessorMenu() { icon="person-outline" title="Perfil" subtitle="Dados pessoais" - onPress={() => router.push('/perfil')} + onPress={() => router.push('/Professor/PerfilProf')} + cores={cores} /> router.push('/definicoes')} + onPress={() => router.push('/Professor/defenicoes2')} + cores={cores} /> router.push('/professor/sumarios')} + subtitle="Verificar sumários" + onPress={() => router.push('/Professor/Alunos/Sumarios')} + cores={cores} + /> + + router.push('/Professor/Alunos/Faltas')} + cores={cores} + /> + + router.push('/Professor/Alunos/Presencas')} + cores={cores} + /> + + router.push('/Professor/Alunos/Estagios')} + cores={cores} /> router.push('/professor/alunos')} + subtitle="Lista de alunos" + onPress={() => router.push('/Professor/Alunos/ListaAlunos')} + cores={cores} /> router.push('/professor/empresas')} + subtitle="Lista de empresas" + onPress={() => router.push('/Professor/Empresas/ListaEmpresas')} + cores={cores} /> @@ -74,17 +119,19 @@ function MenuCard({ title, subtitle, onPress, + cores, }: { icon: any; title: string; subtitle: string; onPress: () => void; + cores: any; }) { return ( - - - {title} - {subtitle} + + + {title} + {subtitle} ); } @@ -92,7 +139,7 @@ function MenuCard({ const styles = StyleSheet.create({ container: { flex: 1, - backgroundColor: '#f1f3f5', + paddingTop: Platform.OS === 'android' ? StatusBar.currentHeight : 0, }, content: { padding: 24, @@ -102,18 +149,17 @@ const styles = StyleSheet.create({ }, welcome: { fontSize: 16, - color: '#6c757d', + fontWeight: '500', }, name: { fontSize: 28, fontWeight: '800', - color: '#212529', marginTop: 4, }, subtitle: { fontSize: 14, - color: '#6c757d', marginTop: 6, + fontWeight: '400', }, grid: { flexDirection: 'row', @@ -122,7 +168,6 @@ const styles = StyleSheet.create({ }, card: { width: '48%', - backgroundColor: '#fff', borderRadius: 18, padding: 20, marginBottom: 16, @@ -137,11 +182,9 @@ const styles = StyleSheet.create({ fontSize: 16, fontWeight: '700', marginTop: 12, - color: '#212529', }, cardSubtitle: { fontSize: 13, - color: '#6c757d', marginTop: 4, }, }); diff --git a/app/Professor/defenicoes2.tsx b/app/Professor/defenicoes2.tsx new file mode 100644 index 0000000..c2b1817 --- /dev/null +++ b/app/Professor/defenicoes2.tsx @@ -0,0 +1,161 @@ +import { Ionicons } from '@expo/vector-icons'; +import { useRouter } from 'expo-router'; +import { memo, useMemo, useState } from 'react'; // Importado useMemo e memo +import { + Alert, + Linking, + Platform, + SafeAreaView, + StatusBar, + StyleSheet, + Switch, + Text, + TouchableOpacity, + View +} from 'react-native'; +import { useTheme } from '../../themecontext'; + +const Definicoes = memo(() => { + const router = useRouter(); + const [notificacoes, setNotificacoes] = useState(true); + const { isDarkMode, toggleTheme } = useTheme(); + + // Otimização de cores para evitar lag no render + const cores = useMemo(() => ({ + fundo: isDarkMode ? '#121212' : '#f1f3f5', + card: isDarkMode ? '#1e1e1e' : '#fff', + texto: isDarkMode ? '#ffffff' : '#000000', + textoSecundario: isDarkMode ? '#adb5bd' : '#6c757d', + borda: isDarkMode ? '#333' : '#f1f3f5', + sair: '#dc3545', + azul: '#0d6efd' + }), [isDarkMode]); + + const handleLogout = () => { + Alert.alert( + "Terminar Sessão", + "Tem a certeza que deseja sair da aplicação?", + [ + { text: "Cancelar", style: "cancel" }, + { + text: "Sair", + style: "destructive", + onPress: () => router.replace('/') + } + ] + ); + }; + + const abrirEmail = () => Linking.openURL(`mailto:epvc@epvc.pt`); + const abrirEmail2 = () => Linking.openURL(`mailto:secretaria@epvc.pt`); + const abrirTelefone = () => Linking.openURL('tel:252 641 805'); + + return ( + + + + + router.back()} + > + + + Definições + + + + + + + Preferências + + + + + Notificações + + + + + + + + Modo escuro + + + + + Suporte e Contactos + + + + + Direção + + epvc@epvc.pt + + + + + + Secretaria + + secretaria@epvc.pt + + + + + + Ligar para a Escola + + 252 641 805 + + + + + + Versão da app + + 26.1.10 + + + + + + Terminar Sessão + + + + + + + + ); +}); + +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 }, + tituloGeral: { fontSize: 24, fontWeight: 'bold' }, + subtituloSecao: { fontSize: 14, fontWeight: 'bold', textTransform: 'uppercase', marginBottom: 5, marginLeft: 5 }, + container: { padding: 20 }, + card: { paddingHorizontal: 20, paddingVertical: 15, borderRadius: 16, elevation: 2, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4 }, + linha: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingVertical: 15, borderBottomWidth: 1 }, + iconTexto: { flexDirection: 'row', alignItems: 'center' } +}); + +export default Definicoes; \ No newline at end of file diff --git a/app/Professor/redefenirsenha2.tsx b/app/Professor/redefenirsenha2.tsx index f8d4551..7a8ce99 100644 --- a/app/Professor/redefenirsenha2.tsx +++ b/app/Professor/redefenirsenha2.tsx @@ -1,6 +1,6 @@ // app/forgot-password.tsx import { useRouter } from 'expo-router'; -import { useState } from 'react'; +import { useMemo, useState } from 'react'; import { ActivityIndicator, Alert, @@ -13,12 +13,25 @@ import { TouchableOpacity, View } from 'react-native'; +import { useTheme } from '../../themecontext'; // Tema global import { supabase } from '../lib/supabase'; export default function ForgotPassword() { const [email, setEmail] = useState(''); const [loading, setLoading] = useState(false); const router = useRouter(); + const { isDarkMode } = useTheme(); // pega o tema global + + const cores = useMemo(() => ({ + fundo: isDarkMode ? '#121212' : '#f8f9fa', + container: isDarkMode ? '#1e1e1e' : '#fff', + texto: isDarkMode ? '#fff' : '#2d3436', + textoSecundario: isDarkMode ? '#adb5bd' : '#636e72', + inputBg: isDarkMode ? '#2a2a2a' : '#f1f2f6', + button: '#0984e3', + buttonDisabled: '#74b9ff', + backText: '#0984e3', + }), [isDarkMode]); const handleSendResetEmail = async () => { if (!email) { @@ -42,19 +55,22 @@ export default function ForgotPassword() { return ( - + - Recuperar Palavra-passe - Insira seu email para receber o link de redefinição + Recuperar Palavra-passe + + Insira seu email para receber o link de redefinição + {/* INPUT EMAIL */} @@ -72,8 +88,11 @@ export default function ForgotPassword() { {/* BOTÃO VOLTAR */} - router.push('/Professor/PerfilProf')} style={styles.backContainer}> - ← Voltar atrás + router.push('/Professor/PerfilProf')} + style={styles.backContainer} + > + ← Voltar atrás @@ -85,7 +104,6 @@ export default function ForgotPassword() { const styles = StyleSheet.create({ scrollContainer: { flexGrow: 1, justifyContent: 'center', padding: 24 }, container: { - backgroundColor: '#fff', borderRadius: 16, padding: 24, shadowColor: '#000', @@ -94,23 +112,15 @@ const styles = StyleSheet.create({ shadowRadius: 12, elevation: 5, }, - logo: { - width: 120, - height: 120, - alignSelf: 'center', - marginBottom: 20, - }, - title: { fontSize: 24, fontWeight: '700', color: '#2d3436', marginBottom: 8, textAlign: 'center' }, - subtitle: { fontSize: 14, color: '#636e72', marginBottom: 20, textAlign: 'center' }, + title: { fontSize: 24, fontWeight: '700', marginBottom: 8, textAlign: 'center' }, + subtitle: { fontSize: 14, marginBottom: 20, textAlign: 'center' }, input: { - backgroundColor: '#f1f2f6', borderRadius: 12, paddingHorizontal: 16, paddingVertical: 14, fontSize: 16, marginBottom: 20, borderWidth: 0, - color: '#2d3436', }, button: { backgroundColor: '#0984e3', @@ -124,8 +134,7 @@ const styles = StyleSheet.create({ shadowRadius: 6, elevation: 3, }, - buttonDisabled: { backgroundColor: '#74b9ff' }, buttonText: { color: '#fff', fontSize: 17, fontWeight: '700' }, backContainer: { marginTop: 8, alignItems: 'center' }, - backText: { color: '#0984e3', fontSize: 15, fontWeight: '500' }, + backText: { fontSize: 15, fontWeight: '500' }, }); diff --git a/app/_layout.tsx b/app/_layout.tsx index c9fb686..00c0a10 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -1,4 +1,5 @@ // app/_layout.tsx +import { NavigationContainer } from '@react-navigation/native'; import { Stack } from 'expo-router'; import { StatusBar } from 'expo-status-bar'; import { ThemeProvider, useTheme } from '../themecontext'; @@ -22,4 +23,10 @@ export default function RootLayout() { ); -} \ No newline at end of file +} + + + + {/* aluno e professor */} + + diff --git a/package-lock.json b/package-lock.json index d34aaf4..5edacbe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,6 +38,7 @@ "react-native-screens": "~4.16.0", "react-native-url-polyfill": "^3.0.0", "react-native-web": "~0.21.0", + "react-native-webview": "^13.16.0", "react-native-worklets": "0.5.1" }, "devDependencies": { @@ -10874,6 +10875,19 @@ "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", "license": "MIT" }, + "node_modules/react-native-webview": { + "version": "13.16.0", + "resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.16.0.tgz", + "integrity": "sha512-Nh13xKZWW35C0dbOskD7OX01nQQavOzHbCw9XoZmar4eXCo7AvrYJ0jlUfRVVIJzqINxHlpECYLdmAdFsl9xDA==", + "dependencies": { + "escape-string-regexp": "^4.0.0", + "invariant": "2.2.4" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-worklets": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.5.1.tgz", diff --git a/package.json b/package.json index f191b54..621ca15 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "react-native-screens": "~4.16.0", "react-native-url-polyfill": "^3.0.0", "react-native-web": "~0.21.0", + "react-native-webview": "^13.16.0", "react-native-worklets": "0.5.1" }, "devDependencies": {