26.1.24
This commit is contained in:
115
app/Professor/Alunos/CalendarioPresencas.tsx
Normal file
115
app/Professor/Alunos/CalendarioPresencas.tsx
Normal 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,
|
||||
},
|
||||
});
|
||||
158
app/Professor/Alunos/DetalhesAluno.tsx
Normal file
158
app/Professor/Alunos/DetalhesAluno.tsx
Normal 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' }
|
||||
});
|
||||
225
app/Professor/Alunos/Estagios.tsx
Normal file
225
app/Professor/Alunos/Estagios.tsx
Normal 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 }
|
||||
});
|
||||
199
app/Professor/Alunos/Faltas.tsx
Normal file
199
app/Professor/Alunos/Faltas.tsx
Normal 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' },
|
||||
});
|
||||
124
app/Professor/Alunos/ListaAlunos.tsx
Normal file
124
app/Professor/Alunos/ListaAlunos.tsx
Normal 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 },
|
||||
});
|
||||
140
app/Professor/Alunos/Presencas.tsx
Normal file
140
app/Professor/Alunos/Presencas.tsx
Normal 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' },
|
||||
});
|
||||
156
app/Professor/Alunos/Sumarios.tsx
Normal file
156
app/Professor/Alunos/Sumarios.tsx
Normal 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 },
|
||||
});
|
||||
155
app/Professor/Empresas/DetalhesEmpresa.tsx
Normal file
155
app/Professor/Empresas/DetalhesEmpresa.tsx
Normal 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 },
|
||||
});
|
||||
136
app/Professor/Empresas/ListaEmpresas.tsx
Normal file
136
app/Professor/Empresas/ListaEmpresas.tsx
Normal 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 },
|
||||
});
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
@@ -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' },
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
161
app/Professor/defenicoes2.tsx
Normal file
161
app/Professor/defenicoes2.tsx
Normal 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;
|
||||
@@ -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' },
|
||||
});
|
||||
|
||||
@@ -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
14
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
Reference in New Issue
Block a user