This commit is contained in:
2026-02-03 17:20:24 +00:00
parent 02478b4cee
commit 249025e860
2 changed files with 154 additions and 279 deletions

View File

@@ -1,158 +1,134 @@
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';
import {
SafeAreaView,
ScrollView,
StatusBar,
StyleSheet,
Text,
TouchableOpacity,
View,
} from 'react-native';
import { supabase } from '../../lib/supabase';
const DetalhesAluno = memo(() => {
const { isDarkMode } = useTheme();
const DetalhesAlunos = memo(() => {
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 { alunoId } = useLocalSearchParams();
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'
};
const [aluno, setAluno] = useState<any>(null);
const [loading, setLoading] = useState(true);
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 (alunoId) fetchAluno();
}, [alunoId]);
const fetchAluno = async () => {
setLoading(true);
const { data: alunoData } = await supabase
.from('alunos')
.select('*')
.eq('id', alunoId)
.single();
if (!alunoData) {
setLoading(false);
return;
}
const { data: perfil } = await supabase
.from('profile')
.select('email, telefone')
.eq('n_escola', alunoData.n_escola)
.single();
const { data: estagio } = await supabase
.from('estagios')
.select('estado')
.eq('aluno_id', alunoId)
.single();
setAluno({
...alunoData,
...perfil,
estagio,
});
}, []);
setLoading(false);
};
if (loading) {
return (
<SafeAreaView style={styles.safe}>
<Text style={styles.center}>A carregar...</Text>
</SafeAreaView>
);
}
if (!aluno) {
return (
<SafeAreaView style={[styles.safe, { backgroundColor: cores.fundo }]}>
<Text style={{ color: cores.texto, textAlign: 'center', marginTop: 50 }}>Nenhum aluno selecionado.</Text>
<SafeAreaView style={styles.safe}>
<Text style={styles.center}>Aluno não encontrado</Text>
</SafeAreaView>
);
}
return (
<SafeAreaView style={[styles.safe, { backgroundColor: cores.fundo }]}>
<StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
<SafeAreaView style={styles.safe}>
<StatusBar barStyle="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 onPress={() => router.back()}>
<Ionicons name="arrow-back" size={24} />
</TouchableOpacity>
<Text style={[styles.tituloGeral, { color: cores.texto }]}>{aluno.nome}</Text>
<View style={styles.spacer} />
<Text style={styles.titulo}>{aluno.nome}</Text>
<View style={{ width: 24 }} />
</View>
<ScrollView contentContainerStyle={styles.container} showsVerticalScrollIndicator={false}>
<ScrollView contentContainerStyle={styles.container}>
<View style={styles.card}>
<Text style={styles.label}>Número Escola</Text>
<Text style={styles.valor}>{aluno.n_escola}</Text>
{/* 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}>Turma</Text>
<Text style={styles.valor}>
{aluno.ano}º {aluno.turma_curso}
</Text>
<Text style={[styles.label, { color: cores.textoSecundario }]}>Email</Text>
<Text style={[styles.valor, { color: cores.texto }]}>{aluno.email}</Text>
<Text style={styles.label}>Email</Text>
<Text style={styles.valor}>{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}>Telefone</Text>
<Text style={styles.valor}>{aluno.telefone || '-'}</Text>
<Text style={[styles.label, { color: cores.textoSecundario }]}>Turma</Text>
<Text style={[styles.valor, { color: cores.texto }]}>{aluno.turma}</Text>
<Text style={styles.label}>Estado Estágio</Text>
<Text style={styles.valor}>{aluno.estagio?.estado || '-'}</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;
export default DetalhesAlunos;
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 },
safe: { flex: 1, backgroundColor: '#f1f3f5' },
center: { marginTop: 50, textAlign: 'center' },
header: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
padding: 16,
},
titulo: { fontSize: 20, fontWeight: 'bold' },
container: { padding: 16 },
card: {
backgroundColor: '#fff',
padding: 16,
borderRadius: 12,
elevation: 2,
},
label: { fontSize: 12, color: '#6c757d', marginTop: 10 },
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

@@ -15,35 +15,19 @@ import {
import { useTheme } from '../../../themecontext';
import { supabase } from '../../lib/supabase';
/* =======================
Interfaces
======================= */
export interface Aluno {
id: string;
nome: string;
email: string;
telefone: string;
estado: string; // virá da tabela estagios
n_escola: string;
turma: string;
}
interface Turma {
nome: string;
alunos: Aluno[];
}
/* =======================
Componente
======================= */
const ListaAlunosProfessor = memo(() => {
const { isDarkMode } = useTheme();
const router = useRouter();
const [search, setSearch] = useState('');
const [expanded, setExpanded] = useState<Record<string, boolean>>({});
const [turmas, setTurmas] = useState<Turma[]>([]);
const [turmas, setTurmas] = useState<{ nome: string; alunos: Aluno[] }[]>([]);
const [loading, setLoading] = useState(true);
const cores = {
@@ -54,10 +38,6 @@ const ListaAlunosProfessor = memo(() => {
azul: '#0d6efd',
};
/* =======================
Fetch Supabase
======================= */
useEffect(() => {
fetchAlunos();
}, []);
@@ -66,59 +46,48 @@ const ListaAlunosProfessor = memo(() => {
setLoading(true);
const { data, error } = await supabase
.from('profiles')
.select(`
id,
nome,
email,
telefone,
turma_curso,
ano,
n_escola
`)
.from('alunos')
.select('id, nome, n_escola, ano, turma_curso')
.order('ano', { ascending: false });
if (error) {
console.error('Erro ao buscar alunos:', error);
console.error('Erro Supabase:', error);
setLoading(false);
return;
}
if (!data || data.length === 0) {
console.log('Nenhum aluno encontrado');
setTurmas([]);
setLoading(false);
return;
}
// Agrupar por ano + turma_curso
const agrupadas: Record<string, Aluno[]> = {};
data.forEach((item: any) => {
const nomeTurma = `${item.ano} ${item.turma_curso}`;
if (!agrupadas[nomeTurma]) {
agrupadas[nomeTurma] = [];
}
data.forEach(item => {
const nomeTurma = `${item.ano}º ${item.turma_curso}`;
if (!agrupadas[nomeTurma]) agrupadas[nomeTurma] = [];
agrupadas[nomeTurma].push({
id: item.id,
nome: item.nome,
email: item.email,
telefone: item.telefone ?? '—',
estado: 'Sem estágio',
n_escola: item.n_escola,
turma: nomeTurma,
});
});
const turmasFormatadas: Turma[] = Object.keys(agrupadas).map(nome => ({
nome,
alunos: agrupadas[nome],
}));
setTurmas(
Object.keys(agrupadas).map(nome => ({
nome,
alunos: agrupadas[nome],
}))
);
setTurmas(turmasFormatadas);
setLoading(false);
};
const toggleExpand = (turmaNome: string) =>
setExpanded(prev => ({ ...prev, [turmaNome]: !prev[turmaNome] }));
/* =======================
Pesquisa
======================= */
const filteredTurmas = turmas
.map(turma => ({
...turma,
@@ -128,15 +97,10 @@ const ListaAlunosProfessor = memo(() => {
}))
.filter(t => t.alunos.length > 0);
/* =======================
Render
======================= */
return (
<SafeAreaView style={[styles.safe, { backgroundColor: cores.fundo }]}>
<StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
{/* Header */}
<View style={styles.header}>
<TouchableOpacity
style={[styles.btnVoltar, { backgroundColor: cores.card }]}
@@ -152,7 +116,6 @@ const ListaAlunosProfessor = memo(() => {
<View style={styles.spacer} />
</View>
{/* Pesquisa */}
<TextInput
style={[styles.search, { backgroundColor: cores.card, color: cores.texto }]}
placeholder="Pesquisar aluno..."
@@ -167,47 +130,33 @@ const ListaAlunosProfessor = memo(() => {
</Text>
)}
{/* Lista */}
<FlatList
data={filteredTurmas}
keyExtractor={item => item.nome}
contentContainerStyle={{ paddingBottom: 20 }}
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>
<Text style={[styles.turmaNome, { color: cores.azul }]}>
{item.nome} ({item.alunos.length})
</Text>
{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 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.n_escola} {aluno.nome}
</Text>
</TouchableOpacity>
))}
</View>
</View>
)}
/>
@@ -217,66 +166,16 @@ const ListaAlunosProfessor = memo(() => {
export default ListaAlunosProfessor;
/* =======================
Styles
======================= */
const styles = StyleSheet.create({
safe: {
flex: 1,
paddingTop: Platform.OS === 'android' ? StatusBar.currentHeight : 0,
},
header: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: 20,
paddingVertical: 10,
},
btnVoltar: {
width: 40,
height: 40,
borderRadius: 20,
justifyContent: 'center',
alignItems: 'center',
elevation: 2,
},
tituloGeral: {
fontSize: 22,
fontWeight: 'bold',
},
spacer: {
width: 40,
},
search: {
borderRadius: 10,
padding: 10,
margin: 10,
},
card: {
borderRadius: 10,
padding: 15,
marginHorizontal: 10,
marginBottom: 10,
elevation: 2,
},
turmaNome: {
fontSize: 18,
fontWeight: 'bold',
},
listaAlunos: {
marginTop: 10,
paddingLeft: 10,
},
alunoItem: {
paddingVertical: 8,
},
alunoNome: {
fontSize: 16,
fontWeight: '600',
},
alunoEstado: {
fontSize: 14,
marginTop: 2,
},
safe: { flex: 1, paddingTop: Platform.OS === 'android' ? StatusBar.currentHeight : 0 },
header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 20, paddingVertical: 10 },
btnVoltar: { width: 40, height: 40, borderRadius: 20, justifyContent: 'center', alignItems: 'center', elevation: 2 },
tituloGeral: { fontSize: 22, fontWeight: 'bold' },
spacer: { width: 40 },
search: { borderRadius: 10, padding: 10, margin: 10 },
card: { borderRadius: 10, padding: 15, marginHorizontal: 10, marginBottom: 10, elevation: 2 },
turmaNome: { fontSize: 18, fontWeight: 'bold' },
listaAlunos: { marginTop: 10, paddingLeft: 10 },
alunoItem: { paddingVertical: 5 },
alunoNome: { fontSize: 16, fontWeight: '600' },
});