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": {