This commit is contained in:
2026-01-24 17:12:54 +00:00
parent cd29acdd23
commit 50db3a0902
17 changed files with 1784 additions and 260 deletions

View File

@@ -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 (
<SafeAreaView style={[styles.safe, { backgroundColor: cores.fundo }]}>
<StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
<ScrollView contentContainerStyle={styles.container}>
{/* HEADER */}
<View style={styles.header}>
<TouchableOpacity onPress={() => router.back()}>
<Ionicons name="arrow-back" size={26} color={cores.texto} />
</TouchableOpacity>
<Text style={[styles.title, { color: cores.texto }]}>{nome}</Text>
<View style={{ width: 26 }} />
</View>
{/* CALENDÁRIO SIMPLIFICADO */}
{presencasData.map((p, i) => (
<View key={i} style={[styles.card, { backgroundColor: cores.card }]}>
<Text style={{ color: cores.texto, fontWeight: '600' }}>
{new Date(p.data).toLocaleDateString('pt-PT')}
</Text>
{p.estado === 'presente' ? (
<TouchableOpacity onPress={() => abrirMapa(p.localizacao!.lat, p.localizacao!.lng)}>
<Text style={{ color: cores.verde }}>Presente (ver localização)</Text>
</TouchableOpacity>
) : (
<TouchableOpacity onPress={() => router.push('/Professor/Alunos/Faltas')}>
<Text style={{ color: cores.vermelho }}>
Faltou (ver página faltas)
</Text>
</TouchableOpacity>
)}
</View>
))}
</ScrollView>
</SafeAreaView>
);
}
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,
},
});

View File

@@ -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 (
<SafeAreaView style={[styles.safe, { backgroundColor: cores.fundo }]}>
<Text style={{ color: cores.texto, textAlign: 'center', marginTop: 50 }}>Nenhum aluno selecionado.</Text>
</SafeAreaView>
);
}
return (
<SafeAreaView style={[styles.safe, { backgroundColor: cores.fundo }]}>
<StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
<View style={styles.header}>
<TouchableOpacity style={[styles.btnVoltar, { backgroundColor: cores.card }]} onPress={() => router.back()}>
<Ionicons name="arrow-back" size={24} color={cores.texto} />
</TouchableOpacity>
<Text style={[styles.tituloGeral, { color: cores.texto }]}>{aluno.nome}</Text>
<View style={styles.spacer} />
</View>
<ScrollView contentContainerStyle={styles.container} showsVerticalScrollIndicator={false}>
{/* Dados Pessoais */}
<View style={[styles.card, { backgroundColor: cores.card }]}>
<Text style={[styles.tituloCard, { color: cores.azul }]}>Dados Pessoais</Text>
<Text style={[styles.label, { color: cores.textoSecundario }]}>Nome</Text>
<Text style={[styles.valor, { color: cores.texto }]}>{aluno.nome}</Text>
<Text style={[styles.label, { color: cores.textoSecundario }]}>Email</Text>
<Text style={[styles.valor, { color: cores.texto }]}>{aluno.email}</Text>
<Text style={[styles.label, { color: cores.textoSecundario }]}>Telefone</Text>
<Text style={[styles.valor, { color: cores.texto }]}>{aluno.telefone}</Text>
<Text style={[styles.label, { color: cores.textoSecundario }]}>Turma</Text>
<Text style={[styles.valor, { color: cores.texto }]}>{aluno.turma}</Text>
</View>
{/* Empresa de Estágio */}
<View style={[styles.card, { backgroundColor: cores.card }]}>
<Text style={[styles.tituloCard, { color: cores.azul }]}>Empresa de Estágio</Text>
<Text style={[styles.label, { color: cores.textoSecundario }]}>Empresa</Text>
<Text style={[styles.valor, { color: cores.texto }]}>{aluno.empresa}</Text>
<Text style={[styles.label, { color: cores.textoSecundario }]}>Cargo</Text>
<Text style={[styles.valor, { color: cores.texto }]}>{aluno.cargo}</Text>
</View>
{/* Dados do Estágio / Horas */}
<View style={[styles.card, { backgroundColor: cores.card }]}>
<Text style={[styles.tituloCard, { color: cores.azul }]}>Horas de Estágio</Text>
<View style={styles.rowStats}>
<View style={styles.itemStat}>
<Text style={[styles.label, { color: cores.textoSecundario }]}>Totais</Text>
<Text style={[styles.valor, { color: cores.texto }]}>{300}h</Text>
</View>
<View style={styles.itemStat}>
<Text style={[styles.label, { color: cores.textoSecundario }]}>Concluídas</Text>
<Text style={[styles.valor, { color: cores.verde }]}>{stats.horasConcluidas}h</Text>
</View>
<View style={styles.itemStat}>
<Text style={[styles.label, { color: cores.textoSecundario }]}>Faltam</Text>
<Text style={[styles.valor, { color: cores.vermelho }]}>{stats.horasFaltam}h</Text>
</View>
</View>
<Text style={[styles.labelHorario, { color: cores.texto }]}>Horário Semanal</Text>
<View style={[styles.tabela, { borderColor: cores.borda }]}>
<View style={[styles.linhaTab, { backgroundColor: isDarkMode ? '#2c2c2c' : '#f8f9fa', borderBottomColor: cores.borda }]}>
<Text style={[styles.celulaHeader, { color: cores.texto }]}>Período</Text>
<Text style={[styles.celulaHeader, { color: cores.texto }]}>Horário</Text>
</View>
<View style={[styles.linhaTab, { borderBottomColor: cores.borda }]}>
<Text style={[styles.celulaLabel, { color: cores.textoSecundario }]}>Manhã</Text>
<Text style={[styles.celulaValor, { color: cores.texto }]}>09:30 - 13:00</Text>
</View>
<View style={[styles.linhaTab, { backgroundColor: isDarkMode ? '#252525' : '#fdfcfe', borderBottomColor: cores.borda }]}>
<Text style={[styles.celulaLabel, { color: cores.textoSecundario }]}>Almoço</Text>
<Text style={[styles.celulaValor, { color: cores.textoSecundario }]}>13:00 - 14:30</Text>
</View>
<View style={[styles.linhaTab, { borderBottomWidth: 0 }]}>
<Text style={[styles.celulaLabel, { color: cores.textoSecundario }]}>Tarde</Text>
<Text style={[styles.celulaValor, { color: cores.texto }]}>14:30 - 17:30</Text>
</View>
</View>
<Text style={[styles.notaTotal, { color: cores.azul }]}>Total: 7 horas diárias por presença</Text>
</View>
</ScrollView>
</SafeAreaView>
);
});
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' }
});

View File

@@ -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<Estagio[]>([]);
const [modalVisible, setModalVisible] = useState(false);
const [alunoSelecionado, setAlunoSelecionado] = useState<Aluno | null>(null);
const [empresaSelecionada, setEmpresaSelecionada] = useState<Empresa | null>(null);
const [editandoEstagio, setEditandoEstagio] = useState<Estagio | null>(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 (
<SafeAreaView style={[styles.container, { backgroundColor: cores.fundo, paddingTop: Platform.OS === 'android' ? StatusBar.currentHeight : 0 }]}>
<StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} backgroundColor={cores.fundo} />
<ScrollView contentContainerStyle={styles.content}>
{/* Header */}
<View style={styles.header}>
<TouchableOpacity onPress={() => router.back()}>
<Ionicons name="arrow-back-outline" size={26} color={cores.texto} />
</TouchableOpacity>
<Text style={[styles.title, { color: cores.texto }]}>Estágios</Text>
<View style={{ width: 26 }} />
</View>
{/* Pesquisa */}
<TextInput
style={[styles.searchInput, { backgroundColor: cores.card, color: cores.texto, borderColor: cores.border }]}
placeholder="Procurar estágio..."
placeholderTextColor={cores.textoSecundario}
value={searchText}
onChangeText={setSearchText}
/>
{/* Lista de estágios */}
<View style={{ marginTop: 16 }}>
{estagiosFiltrados.map(e => (
<TouchableOpacity key={e.id} style={[styles.card, { backgroundColor: cores.card }]} onPress={() => editarEstagio(e)}>
<Text style={[styles.cardTitle, { color: cores.texto }]}>{e.alunoNome}</Text>
<Text style={[styles.cardSubtitle, { color: cores.textoSecundario }]}>{e.empresaNome}</Text>
<Ionicons name="create-outline" size={20} color={cores.azul} />
</TouchableOpacity>
))}
{estagiosFiltrados.length === 0 && <Text style={{ color: cores.textoSecundario, marginTop: 12 }}>Nenhum estágio encontrado.</Text>}
</View>
{/* Botão Novo Estágio */}
<TouchableOpacity style={[styles.newButton, { backgroundColor: cores.azul }]} onPress={abrirModalNovo}>
<Ionicons name="add" size={20} color="#fff" />
<Text style={styles.newButtonText}>Novo Estágio</Text>
</TouchableOpacity>
{/* Modal de criação/edição */}
<Modal visible={modalVisible} transparent animationType="slide">
<View style={styles.modalOverlay}>
<View style={[styles.modalContent, { backgroundColor: cores.card }]}>
<Text style={[styles.modalTitle, { color: cores.texto }]}>{editandoEstagio ? 'Editar Estágio' : 'Novo Estágio'}</Text>
{/* Dropdown Aluno */}
<Text style={[styles.modalLabel, { color: cores.textoSecundario }]}>Aluno</Text>
{alunosData.map(a => (
<TouchableOpacity
key={a.id}
style={[styles.modalOption, { backgroundColor: alunoSelecionado?.id === a.id ? cores.azul : cores.card }]}
onPress={() => setAlunoSelecionado(a)}
>
<Text style={{ color: alunoSelecionado?.id === a.id ? '#fff' : cores.texto }}>{a.nome}</Text>
</TouchableOpacity>
))}
{/* Dropdown Empresa */}
<Text style={[styles.modalLabel, { color: cores.textoSecundario, marginTop: 12 }]}>Empresa</Text>
{empresasData.map(emp => (
<TouchableOpacity
key={emp.id}
style={[styles.modalOption, { backgroundColor: empresaSelecionada?.id === emp.id ? cores.azul : cores.card }]}
onPress={() => setEmpresaSelecionada(emp)}
>
<Text style={{ color: empresaSelecionada?.id === emp.id ? '#fff' : cores.texto }}>{emp.nome}</Text>
</TouchableOpacity>
))}
<View style={{ flexDirection: 'row', justifyContent: 'space-between', marginTop: 20 }}>
<TouchableOpacity onPress={() => setModalVisible(false)} style={[styles.modalButton, { backgroundColor: cores.vermelho }]}>
<Text style={{ color: '#fff', fontWeight: '600' }}>Cancelar</Text>
</TouchableOpacity>
<TouchableOpacity onPress={salvarEstagio} style={[styles.modalButton, { backgroundColor: cores.azul }]}>
<Text style={{ color: '#fff', fontWeight: '600' }}>Salvar</Text>
</TouchableOpacity>
</View>
</View>
</View>
</Modal>
</ScrollView>
</SafeAreaView>
);
}
// 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 }
});

View File

@@ -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<Aluno | null>(null);
const [pdfModalVisible, setPdfModalVisible] = useState(false);
const [pdfUrl, setPdfUrl] = useState<string | null>(null);
const verPdf = (falta: Falta) => {
if (falta.pdfUrl) {
setPdfUrl(falta.pdfUrl);
setPdfModalVisible(true);
}
};
return (
<SafeAreaView style={[styles.safe, { backgroundColor: cores.fundo }]}>
<StatusBar
barStyle={isDarkMode ? 'light-content' : 'dark-content'}
backgroundColor={cores.fundo}
/>
<ScrollView contentContainerStyle={styles.container}>
{/* Top Bar */}
<View style={styles.topBar}>
<TouchableOpacity onPress={() => {
if (alunoSelecionado) {
setAlunoSelecionado(null); // voltar para lista de alunos
} else {
router.back(); // voltar para menu
}
}}>
<Ionicons name="arrow-back-outline" size={26} color={cores.azul} />
</TouchableOpacity>
<Text style={[styles.topTitle, { color: cores.texto }]}>
{!alunoSelecionado ? 'Faltas dos Alunos' : alunoSelecionado.nome}
</Text>
<View style={{ width: 26 }} />
</View>
{/* Lista de alunos */}
{!alunoSelecionado && (
<View style={{ gap: 12 }}>
{alunosData.map(aluno => (
<TouchableOpacity
key={aluno.id}
style={[styles.alunoCard, { backgroundColor: cores.card }]}
onPress={() => setAlunoSelecionado(aluno)}
>
<Ionicons name="person-outline" size={28} color={cores.azul} />
<Text style={[styles.alunoName, { color: cores.texto }]}>{aluno.nome}</Text>
<Ionicons name="chevron-forward-outline" size={20} color={cores.textoSecundario} />
</TouchableOpacity>
))}
</View>
)}
{/* Faltas do aluno */}
{alunoSelecionado && (
<View style={{ marginTop: 10 }}>
{alunoSelecionado.faltas.map((falta, idx) => (
<View key={idx} style={[styles.faltaCard, { backgroundColor: cores.card }]}>
<View style={{ flex: 1 }}>
<Text style={[styles.dia, { color: cores.azul }]}>{falta.dia}</Text>
<Text style={[styles.motivo, { color: cores.texto }]}>{falta.motivo || 'Sem motivo'}</Text>
<Text style={[styles.status, { color: falta.pdfUrl ? cores.verde : cores.vermelho }]}>
{falta.pdfUrl ? 'Justificada com PDF' : 'Não justificada'}
</Text>
</View>
{falta.pdfUrl && (
<TouchableOpacity onPress={() => verPdf(falta)}>
<Ionicons name="document-text-outline" size={24} color={cores.azul} />
</TouchableOpacity>
)}
</View>
))}
</View>
)}
</ScrollView>
{/* Modal PDF */}
<Modal visible={pdfModalVisible} animationType="slide">
<SafeAreaView style={{ flex: 1, backgroundColor: cores.fundo }}>
<View style={[styles.pdfHeader, { borderBottomColor: cores.textoSecundario }]}>
<TouchableOpacity onPress={() => setPdfModalVisible(false)}>
<Ionicons name="close-outline" size={28} color={cores.azul} />
</TouchableOpacity>
<Text style={[styles.pdfTitle, { color: cores.texto }]}>Visualizador de PDF</Text>
<View style={{ width: 28 }} />
</View>
{pdfUrl && <WebView source={{ uri: pdfUrl }} style={{ flex: 1 }} />}
</SafeAreaView>
</Modal>
</SafeAreaView>
);
}
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' },
});

View File

@@ -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 (
<SafeAreaView style={[styles.safe, { backgroundColor: cores.fundo }]}>
<StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
{/* Header com botão voltar */}
<View style={styles.header}>
<TouchableOpacity style={[styles.btnVoltar, { backgroundColor: cores.card }]} onPress={() => router.back()}>
<Ionicons name="arrow-back" size={24} color={cores.texto} />
</TouchableOpacity>
<Text style={[styles.tituloGeral, { color: cores.texto }]}>Alunos</Text>
<View style={styles.spacer} />
</View>
{/* Input de pesquisa */}
<TextInput
style={[styles.search, { backgroundColor: cores.card, color: cores.texto }]}
placeholder="Pesquisar aluno..."
placeholderTextColor={cores.textoSecundario}
value={search}
onChangeText={setSearch}
/>
{/* Lista de turmas e alunos */}
<FlatList
data={filteredTurmas}
keyExtractor={item => item.nome}
renderItem={({ item }) => (
<View style={[styles.card, { backgroundColor: cores.card }]}>
<TouchableOpacity onPress={() => toggleExpand(item.nome)}>
<Text style={[styles.turmaNome, { color: cores.azul }]}>{item.nome} ({item.alunos.length})</Text>
</TouchableOpacity>
{expanded[item.nome] && (
<View style={styles.listaAlunos}>
{item.alunos.map(aluno => (
<TouchableOpacity
key={aluno.id}
style={styles.alunoItem}
onPress={() =>
router.push({
pathname: '/Professor/Alunos/DetalhesAluno',
params: { aluno: JSON.stringify(aluno) }
})
}
>
<Text style={[styles.alunoNome, { color: cores.texto }]}>{aluno.nome}</Text>
<Text style={[styles.alunoEstado, { color: cores.textoSecundario }]}>{aluno.estado}</Text>
</TouchableOpacity>
))}
</View>
)}
</View>
)}
/>
</SafeAreaView>
);
});
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 },
});

View File

@@ -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 (
<SafeAreaView style={[styles.safe, { backgroundColor: cores.fundo }]}>
<StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
<ScrollView contentContainerStyle={styles.container}>
{/* HEADER */}
<View style={styles.header}>
<TouchableOpacity onPress={() => router.back()}>
<Ionicons name="arrow-back" size={26} color={cores.texto} />
</TouchableOpacity>
<Text style={[styles.title, { color: cores.texto }]}>Presenças</Text>
<View style={{ width: 26 }} />
</View>
{/* PESQUISA */}
<View style={[styles.searchBox, { backgroundColor: cores.input, borderColor: cores.borda }]}>
<Ionicons name="search" size={20} color={cores.secundario} />
<TextInput
placeholder="Pesquisar aluno..."
placeholderTextColor={cores.secundario}
value={pesquisa}
onChangeText={setPesquisa}
style={[styles.searchInput, { color: cores.texto }]}
/>
</View>
{/* TURMAS */}
{turmas.map(turma => (
<View key={turma}>
<Text style={[styles.turma, { color: cores.azul }]}>{turma}</Text>
{alunosFiltrados
.filter(a => a.turma === turma)
.map(aluno => (
<TouchableOpacity
key={aluno.id}
style={[styles.card, { backgroundColor: cores.card }]}
onPress={() =>
router.push({
pathname: '/Professor/Alunos/CalendarioPresencas',
params: { alunoId: aluno.id, nome: aluno.nome },
})
}
>
<Ionicons name="person-outline" size={24} color={cores.azul} />
<Text style={[styles.nome, { color: cores.texto }]}>{aluno.nome}</Text>
<Ionicons name="chevron-forward" size={20} color={cores.secundario} />
</TouchableOpacity>
))}
</View>
))}
</ScrollView>
</SafeAreaView>
);
}
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' },
});

View File

@@ -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<Aluno | null>(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 (
<SafeAreaView style={[styles.safe, { backgroundColor: cores.fundo }]}>
<StatusBar
barStyle={isDarkMode ? 'light-content' : 'dark-content'}
backgroundColor={cores.fundo}
/>
<ScrollView contentContainerStyle={styles.container}>
{/* Cabeçalho */}
<View style={styles.header}>
<TouchableOpacity
onPress={() => (alunoSelecionado ? setAlunoSelecionado(null) : router.back())}
style={styles.backButtonHeader}
>
<Ionicons name="arrow-back" size={24} color={cores.azul} />
</TouchableOpacity>
<Text style={[styles.title, { color: cores.texto }]}>Sumários de Estágio</Text>
</View>
{/* Lista de alunos */}
{!alunoSelecionado && (
<View style={styles.alunosList}>
{alunosData.map(aluno => (
<TouchableOpacity
key={aluno.id}
style={[styles.alunoCard, { backgroundColor: cores.card }]}
onPress={() => setAlunoSelecionado(aluno)}
>
<Ionicons name="person-outline" size={28} color={cores.azul} />
<Text style={[styles.alunoName, { color: cores.texto }]}>{aluno.nome}</Text>
<Ionicons name="chevron-forward-outline" size={20} color={cores.textoSecundario} />
</TouchableOpacity>
))}
</View>
)}
{/* Sumários do aluno */}
{alunoSelecionado && (
<View style={styles.sumariosContainer}>
<Text style={[styles.alunoTitle, { color: cores.texto }]}>{alunoSelecionado.nome}</Text>
{alunoSelecionado.sumarios.map((s, idx) => (
<View
key={idx}
style={[styles.sumarioCard, { backgroundColor: cores.card }]}
>
<Text style={[styles.dia, { color: cores.azul }]}>{s.dia}</Text>
<Text style={[styles.conteudo, { color: cores.texto }]}>{s.conteudo}</Text>
</View>
))}
</View>
)}
</ScrollView>
</SafeAreaView>
);
});
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 },
});

View File

@@ -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<Empresa>({ ...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 (
<SafeAreaView style={[styles.safe, { backgroundColor: cores.fundo }]}>
<StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
<View style={styles.header}>
<TouchableOpacity style={[styles.btnVoltar, { backgroundColor: cores.card }]} onPress={() => router.back()}>
<Ionicons name="arrow-back" size={24} color={cores.texto} />
</TouchableOpacity>
<Text style={[styles.tituloGeral, { color: cores.texto }]}>{empresaLocal.nome}</Text>
<View style={{ flexDirection: 'row', gap: 8 }}>
<TouchableOpacity style={[styles.btnAcao, { backgroundColor: '#ffc107' }]} onPress={() => setEditando(!editando)}>
<Ionicons name="pencil" size={20} color="#000" />
</TouchableOpacity>
<TouchableOpacity style={[styles.btnAcao, { backgroundColor: cores.vermelho }]} onPress={handleDelete}>
<Ionicons name="trash" size={20} color="#fff" />
</TouchableOpacity>
</View>
</View>
<ScrollView contentContainerStyle={styles.container}>
{/* Dados da Empresa */}
<View style={[styles.card, { backgroundColor: cores.card }]}>
<Text style={[styles.tituloCard, { color: cores.azul }]}>Dados da Empresa</Text>
{['nome', 'curso', 'morada', 'tutor', 'telefone'].map((campo) => (
<View key={campo}>
<Text style={[styles.label, { color: cores.textoSecundario }]}>
{campo.charAt(0).toUpperCase() + campo.slice(1)}
</Text>
{editando ? (
<TextInput
style={[styles.input, { color: cores.texto, borderColor: cores.textoSecundario }]}
value={(empresaLocal as any)[campo]}
onChangeText={(v) => setEmpresaLocal({ ...empresaLocal, [campo]: v })}
/>
) : (
<Text style={[styles.valor, { color: cores.texto }]}>
{(empresaLocal as any)[campo]}
</Text>
)}
</View>
))}
</View>
{/* Botão Guardar alterações */}
{editando && (
<TouchableOpacity style={[styles.saveButton, { backgroundColor: cores.azul }]} onPress={handleSave}>
<Text style={{ color: '#fff', fontWeight: 'bold', textAlign: 'center' }}>Guardar alterações</Text>
</TouchableOpacity>
)}
{/* Estatísticas */}
<View style={[styles.card, { backgroundColor: cores.card }]}>
<Text style={[styles.tituloCard, { color: cores.azul }]}>Estatísticas</Text>
<Text style={[styles.label, { color: cores.textoSecundario }]}>Total de Alunos</Text>
<Text style={[styles.valor, { color: cores.verde, fontSize: 18 }]}>
{empresaLocal.alunos?.length || 0}
</Text>
</View>
{/* Lista de Alunos */}
{empresaLocal.alunos && empresaLocal.alunos.length > 0 && (
<View style={[styles.card, { backgroundColor: cores.card }]}>
<Text style={[styles.tituloCard, { color: cores.azul }]}>Alunos na Empresa</Text>
{empresaLocal.alunos.map((aluno, index) => (
<Text key={index} style={[styles.valor, { color: cores.texto, marginVertical: 2 }]}>
{aluno}
</Text>
))}
</View>
)}
</ScrollView>
</SafeAreaView>
);
});
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 },
});

View File

@@ -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<Empresa[]>(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 (
<SafeAreaView style={[styles.safe, { backgroundColor: cores.fundo }]}>
<StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
{/* Header com botão voltar */}
<View style={styles.header}>
<TouchableOpacity style={[styles.btnVoltar, { backgroundColor: cores.card }]} onPress={() => router.back()}>
<Ionicons name="arrow-back" size={24} color={cores.texto} />
</TouchableOpacity>
<Text style={[styles.tituloGeral, { color: cores.texto }]}>Empresas</Text>
<View style={styles.spacer} />
</View>
{/* Input de pesquisa */}
<TextInput
style={[styles.search, { backgroundColor: cores.card, color: cores.texto }]}
placeholder="Pesquisar empresa..."
placeholderTextColor={cores.textoSecundario}
value={search}
onChangeText={setSearch}
/>
{/* Botão criar empresa */}
<TouchableOpacity style={[styles.btnCriar, { backgroundColor: cores.azul }]} onPress={criarEmpresa}>
<Text style={{ color: '#fff', fontWeight: 'bold' }}>+ Criar Empresa</Text>
</TouchableOpacity>
{/* Lista de empresas */}
<FlatList
data={filteredEmpresas}
keyExtractor={item => item.id.toString()}
renderItem={({ item }) => (
<TouchableOpacity
style={[styles.card, { backgroundColor: cores.card }]}
onPress={() =>
router.push({
pathname: '/Professor/Empresas/DetalhesEmpresa',
params: { empresa: JSON.stringify(item) }
})
}
>
<Text style={[styles.nomeEmpresa, { color: cores.azul }]}>{item.nome}</Text>
<Text style={[styles.curso, { color: cores.textoSecundario }]}>{item.curso}</Text>
<Text style={[styles.tutor, { color: cores.textoSecundario }]}>{item.tutor}</Text>
</TouchableOpacity>
)}
/>
</SafeAreaView>
);
});
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 },
});

View File

@@ -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,
},
});

View File

@@ -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 (
<SafeAreaView style={styles.container}>
<SafeAreaView
style={[
styles.container,
{ backgroundColor: cores.fundo, paddingTop: Platform.OS === 'android' ? StatusBar.currentHeight : 0 },
]}
>
<StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} backgroundColor={cores.fundo} />
<ScrollView contentContainerStyle={styles.content}>
{/* TOPO COM VOLTAR */}
<View style={styles.topBar}>
<TouchableOpacity onPress={() => router.back()}>
<Ionicons name="arrow-back-outline" size={26} color="#212529" />
<TouchableOpacity onPress={() => router.push('/Professor/ProfessorHome')}>
<Ionicons name="arrow-back-outline" size={26} color={cores.texto} />
</TouchableOpacity>
<Text style={styles.topTitle}>Perfil</Text>
<Text style={[styles.topTitle, { color: cores.texto }]}>Perfil</Text>
<View style={{ width: 26 }} />
</View>
{/* HEADER */}
<View style={styles.header}>
<View style={styles.avatar}>
<View style={[styles.avatar, { backgroundColor: cores.azul }]}>
<Ionicons name="person" size={48} color="#fff" />
</View>
<Text style={styles.name}>{perfil.nome}</Text>
<Text style={styles.role}>{perfil.funcao}</Text>
<Text style={[styles.name, { color: cores.texto }]}>{perfil.nome}</Text>
<Text style={[styles.role, { color: cores.textoSecundario }]}>{perfil.funcao}</Text>
</View>
{/* INFORMAÇÕES */}
<View style={styles.card}>
<InfoField label="Nome" value={perfil.nome} editable={editando}
onChange={(v) => setPerfil({ ...perfil, nome: v })}
<View style={[styles.card, { backgroundColor: cores.card }]}>
<InfoField
label="Nome"
value={perfil.nome}
editable={editando}
onChange={(v: string) => setPerfil({ ...perfil, nome: v })}
cores={cores}
/>
<InfoField label="Email" value={perfil.email} editable={false} />
<InfoField label="Nº Mecanográfico" value={perfil.mecanografico} editable={editando}
onChange={(v) => setPerfil({ ...perfil, mecanografico: v })}
<InfoField label="Email" value={perfil.email} editable={false} cores={cores} />
<InfoField
label="Nº Mecanográfico"
value={perfil.mecanografico}
editable={editando}
onChange={(v: string) => setPerfil({ ...perfil, mecanografico: v })}
cores={cores}
/>
<InfoField label="Área" value={perfil.area} editable={editando}
onChange={(v) => setPerfil({ ...perfil, area: v })}
<InfoField
label="Área"
value={perfil.area}
editable={editando}
onChange={(v: string) => setPerfil({ ...perfil, area: v })}
cores={cores}
/>
<InfoField label="Turmas" value={perfil.turmas} editable={editando}
onChange={(v) => setPerfil({ ...perfil, turmas: v })}
<InfoField
label="Turmas"
value={perfil.turmas}
editable={editando}
onChange={(v: string) => setPerfil({ ...perfil, turmas: v })}
cores={cores}
/>
</View>
{/* AÇÕES */}
<View style={styles.actions}>
{editando ? (
<TouchableOpacity style={styles.primaryButton} onPress={guardarPerfil}>
<TouchableOpacity style={[styles.primaryButton, { backgroundColor: cores.azul }]} onPress={guardarPerfil}>
<Ionicons name="save-outline" size={20} color="#fff" />
<Text style={styles.primaryText}>Guardar alterações</Text>
</TouchableOpacity>
) : (
<ActionButton
icon="create-outline"
text="Editar perfil"
onPress={() => setEditando(true)}
/>
<ActionButton icon="create-outline" text="Editar perfil" onPress={() => setEditando(true)} cores={cores} />
)}
<ActionButton
icon="key-outline"
text="Alterar palavra-passe"
onPress={() => router.push('/redefenirsenha2')}
/>
<ActionButton
icon="log-out-outline"
text="Terminar sessão"
danger
onPress={terminarSessao}
onPress={() => router.push('/Professor/redefenirsenha2')}
cores={cores}
/>
<ActionButton icon="log-out-outline" text="Terminar sessão" danger onPress={terminarSessao} cores={cores} />
</View>
</ScrollView>
</SafeAreaView>
);
}
/* COMPONENTES */
function InfoField({
label,
value,
editable,
onChange,
cores,
}: {
label: string;
value: string;
editable: boolean;
onChange?: (v: string) => void;
cores: any;
}) {
return (
<View style={styles.infoField}>
<Text style={styles.label}>{label}</Text>
<Text style={[styles.label, { color: cores.textoSecundario }]}>{label}</Text>
{editable ? (
<TextInput
value={value}
onChangeText={onChange}
style={styles.input}
style={[styles.input, { backgroundColor: cores.inputBg, color: cores.texto, borderColor: cores.border }]}
/>
) : (
<Text style={styles.value}>{value}</Text>
<Text style={[styles.value, { color: cores.texto }]}>{value}</Text>
)}
</View>
);
@@ -153,19 +173,31 @@ function ActionButton({
text,
onPress,
danger = false,
cores,
}: {
icon: any;
text: string;
onPress: () => void;
danger?: boolean;
cores: any;
}) {
return (
<TouchableOpacity
style={[styles.actionButton, danger && styles.dangerButton]}
style={[
styles.actionButton,
{ backgroundColor: cores.card },
danger && { borderWidth: 1, borderColor: cores.vermelho },
]}
onPress={onPress}
>
<Ionicons name={icon} size={20} color={danger ? '#dc3545' : '#0d6efd'} />
<Text style={[styles.actionText, danger && styles.dangerText]}>
<Ionicons name={icon} size={20} color={danger ? cores.vermelho : cores.azul} />
<Text
style={[
styles.actionText,
danger && { color: cores.vermelho },
!danger && cores.texto === '#fff' && { color: '#fff' }, // texto branco no modo escuro
]}
>
{text}
</Text>
</TouchableOpacity>
@@ -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' },
});

View File

@@ -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 (
<SafeAreaView style={styles.container}>
<SafeAreaView style={[styles.container, { backgroundColor: cores.fundo }]}>
<StatusBar
barStyle={isDarkMode ? 'light-content' : 'dark-content'}
backgroundColor={cores.fundo}
/>
<ScrollView contentContainerStyle={styles.content}>
{/* HEADER */}
<View style={styles.header}>
<Text style={styles.welcome}>Bem-vindo,</Text>
<Text style={styles.name}>Professor 👨🏫</Text>
<Text style={styles.subtitle}>Painel de gestão</Text>
<Text style={[styles.welcome, { color: cores.textoSecundario }]}>Olá,</Text>
<Text style={[styles.name, { color: cores.texto }]}>Professor 👨🏫</Text>
<Text style={[styles.subtitle, { color: cores.textoSecundario }]}>Gerencie os estágios facilmente</Text>
</View>
{/* 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}
/>
<MenuCard
icon="settings-outline"
title="Definições"
subtitle="Configurações"
onPress={() => router.push('/definicoes')}
onPress={() => router.push('/Professor/defenicoes2')}
cores={cores}
/>
<MenuCard
icon="document-text-outline"
title="Sumários"
subtitle="Registos diários"
onPress={() => router.push('/professor/sumarios')}
subtitle="Verificar sumários"
onPress={() => router.push('/Professor/Alunos/Sumarios')}
cores={cores}
/>
<MenuCard
icon="close-circle-outline"
title="Faltas"
subtitle="Verificar faltas"
onPress={() => router.push('/Professor/Alunos/Faltas')}
cores={cores}
/>
<MenuCard
icon="checkmark-circle-outline"
title="Presenças"
subtitle="Verificar presenças"
onPress={() => router.push('/Professor/Alunos/Presencas')}
cores={cores}
/>
<MenuCard
icon="briefcase-outline"
title="Estágios"
subtitle="Criar / Editar estágios"
onPress={() => router.push('/Professor/Alunos/Estagios')}
cores={cores}
/>
<MenuCard
icon="people-outline"
title="Alunos"
subtitle="Gerir alunos"
onPress={() => router.push('/professor/alunos')}
subtitle="Lista de alunos"
onPress={() => router.push('/Professor/Alunos/ListaAlunos')}
cores={cores}
/>
<MenuCard
icon="business-outline"
title="Empresas"
subtitle="Entidades de estágio"
onPress={() => router.push('/professor/empresas')}
subtitle="Lista de empresas"
onPress={() => router.push('/Professor/Empresas/ListaEmpresas')}
cores={cores}
/>
</View>
@@ -74,17 +119,19 @@ function MenuCard({
title,
subtitle,
onPress,
cores,
}: {
icon: any;
title: string;
subtitle: string;
onPress: () => void;
cores: any;
}) {
return (
<TouchableOpacity style={styles.card} onPress={onPress}>
<Ionicons name={icon} size={28} color="#0d6efd" />
<Text style={styles.cardTitle}>{title}</Text>
<Text style={styles.cardSubtitle}>{subtitle}</Text>
<TouchableOpacity style={[styles.card, { backgroundColor: cores.card }]} onPress={onPress}>
<Ionicons name={icon} size={28} color={cores.azul} />
<Text style={[styles.cardTitle, { color: cores.texto }]}>{title}</Text>
<Text style={[styles.cardSubtitle, { color: cores.textoSecundario }]}>{subtitle}</Text>
</TouchableOpacity>
);
}
@@ -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,
},
});

View File

@@ -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 (
<SafeAreaView style={[styles.safe, { backgroundColor: cores.fundo }]}>
<StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
<View style={styles.header}>
<TouchableOpacity
style={[styles.btnVoltar, { backgroundColor: cores.card }]}
onPress={() => router.back()}
>
<Ionicons name="arrow-back" size={24} color={cores.texto} />
</TouchableOpacity>
<Text style={[styles.tituloGeral, { color: cores.texto }]}>Definições</Text>
<View style={{ width: 40 }} />
</View>
<View style={styles.container}>
<View style={[styles.card, { backgroundColor: cores.card }]}>
<Text style={[styles.subtituloSecao, { color: cores.azul }]}>Preferências</Text>
<View style={[styles.linha, { borderBottomColor: cores.borda }]}>
<View style={styles.iconTexto}>
<Ionicons name="notifications-outline" size={20} color={cores.texto} style={{marginRight: 10}} />
<Text style={{ color: cores.texto }}>Notificações</Text>
</View>
<Switch
value={notificacoes}
onValueChange={setNotificacoes}
trackColor={{ false: '#767577', true: cores.azul }}
/>
</View>
<View style={[styles.linha, { borderBottomColor: cores.borda }]}>
<View style={styles.iconTexto}>
<Ionicons name={isDarkMode ? "moon" : "sunny-outline"} size={20} color={cores.texto} style={{marginRight: 10}} />
<Text style={{ color: cores.texto }}>Modo escuro</Text>
</View>
<Switch
value={isDarkMode}
onValueChange={toggleTheme} // Chamada direta otimizada pelo ThemeContext
trackColor={{ false: '#767577', true: cores.azul }}
thumbColor={isDarkMode ? '#fff' : '#f4f3f4'}
/>
</View>
<Text style={[styles.subtituloSecao, { color: cores.azul, marginTop: 25 }]}>Suporte e Contactos</Text>
<TouchableOpacity style={[styles.linha, { borderBottomColor: cores.borda }]} onPress={abrirEmail}>
<View style={styles.iconTexto}>
<Ionicons name="mail-outline" size={20} color={cores.texto} style={{marginRight: 10}} />
<Text style={{ color: cores.texto }}>Direção</Text>
</View>
<Text style={{ color: cores.textoSecundario, fontSize: 12 }}>epvc@epvc.pt</Text>
</TouchableOpacity>
<TouchableOpacity style={[styles.linha, { borderBottomColor: cores.borda }]} onPress={abrirEmail2}>
<View style={styles.iconTexto}>
<Ionicons name="mail-outline" size={20} color={cores.texto} style={{marginRight: 10}} />
<Text style={{ color: cores.texto }}>Secretaria</Text>
</View>
<Text style={{ color: cores.textoSecundario, fontSize: 12 }}>secretaria@epvc.pt</Text>
</TouchableOpacity>
<TouchableOpacity style={[styles.linha, { borderBottomColor: cores.borda }]} onPress={abrirTelefone}>
<View style={styles.iconTexto}>
<Ionicons name="call-outline" size={20} color={cores.texto} style={{marginRight: 10}} />
<Text style={{ color: cores.texto }}>Ligar para a Escola</Text>
</View>
<Text style={{ color: cores.textoSecundario, fontSize: 12 }}>252 641 805</Text>
</TouchableOpacity>
<View style={[styles.linha, { borderBottomColor: cores.borda }]}>
<View style={styles.iconTexto}>
<Ionicons name="information-circle-outline" size={20} color={cores.texto} style={{marginRight: 10}} />
<Text style={{ color: cores.texto }}>Versão da app</Text>
</View>
<Text style={{ color: cores.textoSecundario }}>26.1.10</Text>
</View>
<TouchableOpacity
style={[styles.linha, { borderBottomWidth: 0 }]}
onPress={handleLogout}
>
<View style={styles.iconTexto}>
<Ionicons name="log-out-outline" size={22} color={cores.sair} style={{marginRight: 10}} />
<Text style={{ color: cores.sair, fontWeight: '600' }}>Terminar Sessão</Text>
</View>
<Ionicons name="chevron-forward" size={18} color={cores.textoSecundario} />
</TouchableOpacity>
</View>
</View>
</SafeAreaView>
);
});
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;

View File

@@ -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 (
<KeyboardAvoidingView
style={{ flex: 1, backgroundColor: '#f8f9fa' }}
style={{ flex: 1, backgroundColor: cores.fundo }}
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
>
<ScrollView contentContainerStyle={styles.scrollContainer} keyboardShouldPersistTaps="handled">
<View style={styles.container}>
<View style={[styles.container, { backgroundColor: cores.container }]}>
<Text style={styles.title}>Recuperar Palavra-passe</Text>
<Text style={styles.subtitle}>Insira seu email para receber o link de redefinição</Text>
<Text style={[styles.title, { color: cores.texto }]}>Recuperar Palavra-passe</Text>
<Text style={[styles.subtitle, { color: cores.textoSecundario }]}>
Insira seu email para receber o link de redefinição
</Text>
{/* INPUT EMAIL */}
<TextInput
style={styles.input}
style={[styles.input, { backgroundColor: cores.inputBg, color: cores.texto }]}
placeholder="email@address.com"
placeholderTextColor={isDarkMode ? '#888' : '#a1a1a1'}
value={email}
onChangeText={setEmail}
keyboardType="email-address"
@@ -64,7 +80,7 @@ export default function ForgotPassword() {
{/* BOTÃO ENVIAR LINK */}
<TouchableOpacity
style={[styles.button, loading && styles.buttonDisabled]}
style={[styles.button, loading && { backgroundColor: cores.buttonDisabled }]}
onPress={handleSendResetEmail}
disabled={loading}
>
@@ -72,8 +88,11 @@ export default function ForgotPassword() {
</TouchableOpacity>
{/* BOTÃO VOLTAR */}
<TouchableOpacity onPress={() => router.push('/Professor/PerfilProf')} style={styles.backContainer}>
<Text style={styles.backText}> Voltar atrás</Text>
<TouchableOpacity
onPress={() => router.push('/Professor/PerfilProf')}
style={styles.backContainer}
>
<Text style={[styles.backText, { color: cores.backText }]}> Voltar atrás</Text>
</TouchableOpacity>
</View>
@@ -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' },
});

View File

@@ -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() {
<RootLayoutContent />
</ThemeProvider>
);
}
}
<ThemeProvider>
<NavigationContainer children={undefined}>
{/* aluno e professor */}
</NavigationContainer>
</ThemeProvider>

14
package-lock.json generated
View File

@@ -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",

View File

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