updt
This commit is contained in:
@@ -6,7 +6,7 @@ import * as DocumentPicker from 'expo-document-picker';
|
||||
import * as FileSystem from 'expo-file-system/legacy';
|
||||
import * as Location from 'expo-location';
|
||||
import { useRouter } from 'expo-router';
|
||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { memo, useCallback, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Animated,
|
||||
@@ -50,14 +50,12 @@ const AlunoHome = memo(() => {
|
||||
const hojeStr = new Date().toISOString().split('T')[0];
|
||||
const scrollViewRef = useRef<ScrollView>(null);
|
||||
|
||||
// ESTADO DOS SEPARADORES
|
||||
const [activeTab, setActiveTab] = useState<'horas' | 'horario' | 'info'>('horas');
|
||||
|
||||
const [activeTab, setActiveTab] = useState<'assiduidade' | 'horario' | 'info'>('assiduidade');
|
||||
const [selectedDate, setSelectedDate] = useState(hojeStr);
|
||||
|
||||
const [userRole, setUserRole] = useState('aluno');
|
||||
const [estagioDetalhes, setEstagioDetalhes] = useState<any>(null);
|
||||
const [horariosEstagio, setHorariosEstagio] = useState<any[]>([]);
|
||||
|
||||
// 🟢 O NOVO MOTOR CENTRAL (Substitui as 5 variáveis antigas)
|
||||
const [registosDiarios, setRegistosDiarios] = useState<Record<string, any>>({});
|
||||
const [statsFaltas, setStatsFaltas] = useState({ justificadas: 0, injustificadas: 0, totalPresencas: 0 });
|
||||
|
||||
@@ -73,19 +71,16 @@ const AlunoHome = memo(() => {
|
||||
const [alertConfig, setAlertConfig] = useState<{ msg: string, type: 'success' | 'error' | 'info' } | null>(null);
|
||||
const alertOpacity = useMemo(() => new Animated.Value(0), []);
|
||||
|
||||
const azulPetroleo = '#2390a6';
|
||||
const laranjaEPVC = '#dd8707';
|
||||
|
||||
const themeStyles = useMemo(() => ({
|
||||
fundo: isDarkMode ? '#0F0F0F' : '#F8FAFC',
|
||||
card: isDarkMode ? '#1A1A1A' : '#FFFFFF',
|
||||
texto: isDarkMode ? '#F8FAFC' : '#1E293B',
|
||||
textoSecundario: isDarkMode ? '#94A3B8' : '#64748B',
|
||||
borda: isDarkMode ? '#2D2D2D' : '#E2E8F0',
|
||||
azul: azulPetroleo,
|
||||
laranja: laranjaEPVC,
|
||||
amarelo: '#F59E0B', // Adicionado para as faltas por confirmar
|
||||
cinzento: '#94A3B8', // Adicionado para presenças por confirmar
|
||||
azul: '#2390a6',
|
||||
laranja: '#dd8707',
|
||||
amarelo: '#F59E0B',
|
||||
cinzento: '#94A3B8',
|
||||
verde: '#10B981',
|
||||
vermelho: '#EF4444',
|
||||
azulSuave: isDarkMode ? 'rgba(35, 144, 166, 0.15)' : 'rgba(35, 144, 166, 0.1)',
|
||||
@@ -109,9 +104,12 @@ const AlunoHome = memo(() => {
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
if (!user) return;
|
||||
|
||||
const { data: profile } = await supabase.from('profiles').select('tipo').eq('id', user.id).single();
|
||||
if (profile) setUserRole(profile.tipo);
|
||||
|
||||
const { data: eData } = await supabase
|
||||
.from('estagios')
|
||||
.select('id, data_inicio, data_fim, horas_totais, horas_concluidas, horas_diarias, empresas(nome, tutor_nome, tutor_telefone)')
|
||||
.select('id, data_inicio, data_fim, horas_diarias, empresas(nome, tutor_nome, tutor_telefone)')
|
||||
.eq('aluno_id', user.id)
|
||||
.order('data_fim', { ascending: false })
|
||||
.limit(1)
|
||||
@@ -120,11 +118,7 @@ const AlunoHome = memo(() => {
|
||||
setEstagioDetalhes(eData || null);
|
||||
|
||||
if (eData && eData.id) {
|
||||
const { data: hData } = await supabase
|
||||
.from('horarios_estagio')
|
||||
.select('periodo, hora_inicio, hora_fim')
|
||||
.eq('estagio_id', eData.id);
|
||||
|
||||
const { data: hData } = await supabase.from('horarios_estagio').select('periodo, hora_inicio, hora_fim').eq('estagio_id', eData.id);
|
||||
setHorariosEstagio(hData || []);
|
||||
} else {
|
||||
setHorariosEstagio([]);
|
||||
@@ -145,22 +139,17 @@ const AlunoHome = memo(() => {
|
||||
|
||||
data?.forEach(item => {
|
||||
novosRegistos[item.data] = item;
|
||||
|
||||
if (item.estado === 'presente' && item.estado_tutor === 'aprovado') {
|
||||
countPresencasAprovadas++;
|
||||
} else if (item.estado === 'faltou') {
|
||||
if (item.estado_tutor === 'aprovado' && item.justificacao_url) {
|
||||
countJustificadas++;
|
||||
} else if (item.estado_tutor === 'aprovado' || item.estado_tutor === 'rejeitado') {
|
||||
countInjustificadas++;
|
||||
}
|
||||
if (item.estado_tutor === 'aprovado' && item.justificacao_url) countJustificadas++;
|
||||
else if (item.estado_tutor === 'aprovado' || item.estado_tutor === 'rejeitado') countInjustificadas++;
|
||||
}
|
||||
});
|
||||
|
||||
setRegistosDiarios(novosRegistos);
|
||||
setStatsFaltas({ justificadas: countJustificadas, injustificadas: countInjustificadas, totalPresencas: countPresencasAprovadas });
|
||||
|
||||
// Garante que o input do sumário reflete o dia atual
|
||||
if (novosRegistos[selectedDate]) setSumarioInput(novosRegistos[selectedDate].sumario || "");
|
||||
else setSumarioInput("");
|
||||
|
||||
@@ -168,7 +157,6 @@ const AlunoHome = memo(() => {
|
||||
setRegistosDiarios({});
|
||||
setStatsFaltas({ justificadas: 0, injustificadas: 0, totalPresencas: 0 });
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
@@ -178,23 +166,6 @@ const AlunoHome = memo(() => {
|
||||
|
||||
useFocusEffect(useCallback(() => { fetchDadosSupabase(); }, [selectedDate]));
|
||||
|
||||
useEffect(() => {
|
||||
const estagiosSubscription = supabase
|
||||
.channel('estagios_changes')
|
||||
.on('postgres_changes', { event: '*', schema: 'public', table: 'estagios' }, () => fetchDadosSupabase())
|
||||
.subscribe();
|
||||
|
||||
const presencasSubscription = supabase
|
||||
.channel('presencas_changes')
|
||||
.on('postgres_changes', { event: '*', schema: 'public', table: 'presencas' }, () => fetchDadosSupabase())
|
||||
.subscribe();
|
||||
|
||||
return () => {
|
||||
supabase.removeChannel(estagiosSubscription);
|
||||
supabase.removeChannel(presencasSubscription);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const onRefresh = useCallback(async () => {
|
||||
setRefreshing(true);
|
||||
await fetchDadosSupabase(true);
|
||||
@@ -211,24 +182,20 @@ const AlunoHome = memo(() => {
|
||||
}, [estagioDetalhes, hojeStr]);
|
||||
|
||||
const infoData = useMemo(() => {
|
||||
const data = new Date(selectedDate);
|
||||
const diaSemana = data.getDay();
|
||||
const nomeFeriado = feriadosMap[selectedDate];
|
||||
|
||||
const temEstagio = !!estagioDetalhes && estagioDetalhes.data_inicio && estagioDetalhes.data_fim;
|
||||
const antesDoInicio = temEstagio && selectedDate < estagioDetalhes.data_inicio;
|
||||
const depoisDoFim = temEstagio && selectedDate > estagioDetalhes.data_fim;
|
||||
const estagioAtivo = statusEstagio === 'ativo';
|
||||
|
||||
return {
|
||||
valida: estagioAtivo && diaSemana !== 0 && diaSemana !== 6 && !antesDoInicio && !depoisDoFim && !nomeFeriado,
|
||||
valida: estagioAtivo && !antesDoInicio && !depoisDoFim && !nomeFeriado,
|
||||
podeMarcar: estagioAtivo && selectedDate === hojeStr && !antesDoInicio && !depoisDoFim && !nomeFeriado,
|
||||
nomeFeriado, antesDoInicio, depoisDoFim, foraDeRange: !temEstagio || antesDoInicio || depoisDoFim,
|
||||
temEstagio, estagioAtivo
|
||||
};
|
||||
}, [selectedDate, estagioDetalhes, hojeStr, feriadosMap, statusEstagio]);
|
||||
|
||||
// Modificado para ver no "cofre" em vez das antigas variáveis
|
||||
const isDiaMarcado = () => !!registosDiarios[selectedDate];
|
||||
|
||||
const savePresencaData = async (payload: any, successMessage: string) => {
|
||||
@@ -280,7 +247,7 @@ const AlunoHome = memo(() => {
|
||||
lat: loc.coords.latitude,
|
||||
lng: loc.coords.longitude,
|
||||
estado_tutor: 'pendente'
|
||||
}, "Presença marcada! A aguardar aprovação da empresa.");
|
||||
}, "Presença registada! A aguardar aprovação da entidade.");
|
||||
|
||||
} catch (e: any) {
|
||||
showAlert(e.message, "error");
|
||||
@@ -296,7 +263,7 @@ const AlunoHome = memo(() => {
|
||||
await savePresencaData({
|
||||
estado: 'faltou',
|
||||
estado_tutor: 'pendente'
|
||||
}, "Falta registada e enviada para a entidade.");
|
||||
}, "Falta registada e notificada à entidade.");
|
||||
};
|
||||
|
||||
const selecionarDocumento = async () => {
|
||||
@@ -316,11 +283,7 @@ const AlunoHome = memo(() => {
|
||||
|
||||
const { data: { publicUrl } } = supabase.storage.from('justificacoes').getPublicUrl(fileName);
|
||||
|
||||
await savePresencaData({
|
||||
justificacao_url: publicUrl,
|
||||
estado_tutor: 'pendente'
|
||||
}, "Justificativo enviado à entidade com sucesso!");
|
||||
|
||||
await savePresencaData({ justificacao_url: publicUrl, estado_tutor: 'pendente' }, "Documento submetido à entidade com sucesso!");
|
||||
setPdf(null);
|
||||
} catch (e) {
|
||||
showAlert("Erro no upload do documento.", "error");
|
||||
@@ -330,49 +293,37 @@ const AlunoHome = memo(() => {
|
||||
};
|
||||
|
||||
const guardarSumario = async () => {
|
||||
await savePresencaData({
|
||||
sumario: sumarioInput,
|
||||
estado_tutor: 'pendente'
|
||||
}, "Sumário submetido para validação!");
|
||||
await savePresencaData({ sumario: sumarioInput, estado_tutor: 'pendente' }, "Atividades submetidas para validação!");
|
||||
setEditandoSumario(false);
|
||||
};
|
||||
|
||||
// 🟢 AS CORES AGORA REFLETEM AS TUAS REGRAS EXATAS
|
||||
const gerarMarcacoesCalendario = () => {
|
||||
const marcacoes: any = {};
|
||||
|
||||
// Feriados ficam com o azul principal da app
|
||||
Object.keys(feriadosMap).forEach(d => { marcacoes[d] = { marked: true, dotColor: '#000000b7' }; });
|
||||
|
||||
// Pontinhos Azuis: Identificar os dias que já passaram e que estão sem nada
|
||||
if (estagioDetalhes?.data_inicio) {
|
||||
const start = new Date(estagioDetalhes.data_inicio);
|
||||
const limit = new Date(hojeStr) < new Date(estagioDetalhes.data_fim) ? new Date(hojeStr) : new Date(estagioDetalhes.data_fim);
|
||||
|
||||
for (let d = new Date(start); d <= limit; d.setDate(d.getDate() + 1)) {
|
||||
const dateStr = d.toISOString().split('T')[0];
|
||||
const diaSemana = d.getDay();
|
||||
if (diaSemana !== 0 && diaSemana !== 6 && !feriadosMap[dateStr]) {
|
||||
if (!registosDiarios[dateStr]) {
|
||||
marcacoes[dateStr] = { marked: true, dotColor: '#0947f1b7' }; // 🔵 Azul: Sem nada
|
||||
}
|
||||
if (!feriadosMap[dateStr] && !registosDiarios[dateStr]) {
|
||||
marcacoes[dateStr] = { marked: true, dotColor: '#0947f1b7' };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Regras de Cores para dias com Registo
|
||||
Object.values(registosDiarios).forEach(reg => {
|
||||
let cor = themeStyles.azul;
|
||||
const temSumario = reg.sumario && reg.sumario.trim() !== '';
|
||||
|
||||
if (reg.estado === 'presente') {
|
||||
if (reg.estado_tutor === 'aprovado' && temSumario) cor = themeStyles.verde; // 🟢 Verde: Confirmada e com sumário
|
||||
else if (!temSumario) cor = themeStyles.amarelo; // 🟡 Amarelo: Presença sem sumário
|
||||
else cor = themeStyles.azul; // 🔵 Azul: Tem sumário mas falta validar ("Sem nada" da empresa)
|
||||
if (reg.estado_tutor === 'aprovado' && temSumario) cor = themeStyles.verde;
|
||||
else if (!temSumario) cor = themeStyles.amarelo;
|
||||
else cor = themeStyles.azul;
|
||||
} else if (reg.estado === 'faltou') {
|
||||
if (reg.estado_tutor === 'aprovado') cor = themeStyles.cinzento; // 🔘 Cinzento: Falta confirmada
|
||||
else if (reg.justificacao_url) cor = themeStyles.amarelo; // 🟡 Amarelo: Falta justificada (ainda não aprovada)
|
||||
else cor = themeStyles.vermelho; // 🔴 Vermelho: Falta injustificada
|
||||
if (reg.estado_tutor === 'aprovado') cor = themeStyles.cinzento;
|
||||
else if (reg.justificacao_url) cor = themeStyles.amarelo;
|
||||
else cor = themeStyles.vermelho;
|
||||
}
|
||||
marcacoes[reg.data] = { marked: true, dotColor: cor };
|
||||
});
|
||||
@@ -384,42 +335,25 @@ const AlunoHome = memo(() => {
|
||||
const getBadgeStyle = () => {
|
||||
if (statusEstagio === 'concluido') return { bg: '#E2E8F0', text: '#475569', label: 'CONCLUÍDO' };
|
||||
if (statusEstagio === 'agendado') return { bg: '#FEF3C7', text: '#D97706', label: 'AGENDADO' };
|
||||
return { bg: themeStyles.verde + '20', text: themeStyles.verde, label: 'A DECORRER' };
|
||||
return { bg: themeStyles.verde + '20', text: themeStyles.verde, label: 'EM CURSO' };
|
||||
};
|
||||
const badgeObj = getBadgeStyle();
|
||||
const badgeObj = getBadgeStyle();
|
||||
|
||||
const renderAvisoEstadoDia = () => {
|
||||
const reg = registosDiarios[selectedDate];
|
||||
|
||||
// Configuração base (Azul - Sem nada)
|
||||
let config = {
|
||||
icon: 'information-circle',
|
||||
cor: themeStyles.azul,
|
||||
bg: themeStyles.azulSuave,
|
||||
texto: 'Sem Registo (Sem Nada)'
|
||||
};
|
||||
let config = { icon: 'information-circle', cor: themeStyles.azul, bg: themeStyles.azulSuave, texto: 'Sem Registo de Atividade' };
|
||||
|
||||
if (!reg) {
|
||||
// Se o dia não for válido para estágio ou for no futuro, não mostra nada
|
||||
if (infoData.foraDeRange || !infoData.valida || selectedDate > hojeStr) return null;
|
||||
} else if (reg.estado === 'presente') {
|
||||
const temSumario = reg.sumario && reg.sumario.trim() !== '';
|
||||
|
||||
if (reg.estado_tutor === 'aprovado' && temSumario) {
|
||||
config = { icon: 'checkmark-circle', cor: themeStyles.verde, bg: themeStyles.verde + '20', texto: 'Presença Confirmada e com Sumário' };
|
||||
} else if (!temSumario) {
|
||||
config = { icon: 'warning', cor: themeStyles.amarelo, bg: themeStyles.amarelo + '20', texto: 'Presença Sem Sumário' };
|
||||
} else {
|
||||
config = { icon: 'time', cor: themeStyles.azul, bg: themeStyles.azulSuave, texto: 'Presença Pendente de Aprovação' };
|
||||
}
|
||||
if (reg.estado_tutor === 'aprovado' && temSumario) config = { icon: 'checkmark-circle', cor: themeStyles.verde, bg: themeStyles.verde + '20', texto: 'Presença Confirmada e Validada' };
|
||||
else if (!temSumario) config = { icon: 'warning', cor: themeStyles.amarelo, bg: themeStyles.amarelo + '20', texto: 'Presença Requer Submissão de Atividades' };
|
||||
else config = { icon: 'time', cor: themeStyles.azul, bg: themeStyles.azulSuave, texto: 'Presença Pendente de Validação' };
|
||||
} else {
|
||||
if (reg.estado_tutor === 'aprovado') {
|
||||
config = { icon: 'checkmark-done-circle', cor: themeStyles.cinzento, bg: themeStyles.cinzento + '20', texto: 'Falta Confirmada pela Entidade' };
|
||||
} else if (reg.justificacao_url) {
|
||||
config = { icon: 'document-text', cor: themeStyles.amarelo, bg: themeStyles.amarelo + '20', texto: 'Falta Justificada (Em Análise)' };
|
||||
} else {
|
||||
config = { icon: 'close-circle', cor: themeStyles.vermelho, bg: themeStyles.vermelhoSuave, texto: 'Falta Injustificada' };
|
||||
}
|
||||
if (reg.estado_tutor === 'aprovado') config = { icon: 'checkmark-done-circle', cor: themeStyles.cinzento, bg: themeStyles.cinzento + '20', texto: 'Falta Confirmada pela Entidade' };
|
||||
else if (reg.justificacao_url) config = { icon: 'document-text', cor: themeStyles.amarelo, bg: themeStyles.amarelo + '20', texto: 'Documento em Análise pela Entidade' };
|
||||
else config = { icon: 'close-circle', cor: themeStyles.vermelho, bg: themeStyles.vermelhoSuave, texto: 'Falta Injustificada' };
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -430,10 +364,6 @@ const badgeObj = getBadgeStyle();
|
||||
);
|
||||
};
|
||||
|
||||
const horasTotais = Number(estagioDetalhes?.horas_totais) || 0;
|
||||
const horasConcluidas = Number(estagioDetalhes?.horas_concluidas) || 0;
|
||||
const horasEmFalta = Math.max(0, horasTotais - horasConcluidas);
|
||||
|
||||
const regSelecionado = registosDiarios[selectedDate];
|
||||
|
||||
return (
|
||||
@@ -447,12 +377,12 @@ const badgeObj = getBadgeStyle();
|
||||
<View style={[styles.iconCircle, { backgroundColor: themeStyles.azulSuave }]}>
|
||||
<Ionicons name="location" size={40} color={themeStyles.azul} />
|
||||
</View>
|
||||
<Text style={[styles.modalTitle, { color: themeStyles.texto }]}>Confirmar Local</Text>
|
||||
<Text style={[styles.modalTitle, { color: themeStyles.texto }]}>Verificação Geográfica</Text>
|
||||
<Text style={[styles.modalDesc, { color: themeStyles.textoSecundario }]}>
|
||||
Precisamos de validar a tua localização para confirmar que estás no estágio.
|
||||
Para registar a presença, é necessário validar as coordenadas geográficas da entidade de acolhimento.
|
||||
</Text>
|
||||
<TouchableOpacity style={[styles.btnConfirmar, { backgroundColor: themeStyles.azul }]} onPress={executarMarcacao}>
|
||||
<Text style={styles.txtBtn}>Confirmar e Marcar</Text>
|
||||
<Text style={styles.txtBtn}>Autorizar e Registar</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity style={styles.btnFechar} onPress={() => setShowLocationModal(false)}>
|
||||
<Text style={[styles.txtFechar, { color: themeStyles.textoSecundario }]}>Cancelar</Text>
|
||||
@@ -467,175 +397,154 @@ const badgeObj = getBadgeStyle();
|
||||
</Animated.View>
|
||||
)}
|
||||
|
||||
<ScrollView
|
||||
ref={scrollViewRef}
|
||||
contentContainerStyle={styles.container}
|
||||
showsVerticalScrollIndicator={false}
|
||||
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} colors={[themeStyles.azul]} tintColor={themeStyles.azul} />}
|
||||
>
|
||||
<ScrollView ref={scrollViewRef} contentContainerStyle={styles.container} showsVerticalScrollIndicator={false} refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} colors={[themeStyles.azul]} tintColor={themeStyles.azul} />}>
|
||||
|
||||
<View style={styles.topBar}>
|
||||
<Text style={[styles.title, { color: themeStyles.texto }]}>Estágios+</Text>
|
||||
<View style={styles.topIcons}>
|
||||
<TouchableOpacity onPress={() => router.push('/Aluno/definicoes')} style={{ marginRight: 15 }}>
|
||||
<Ionicons name="settings-outline" size={26} color={themeStyles.texto} />
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity onPress={() => router.push('/Aluno/perfil')}>
|
||||
<Ionicons name="person-circle-outline" size={30} color={themeStyles.texto} />
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity onPress={() => router.push('/Aluno/definicoes')} style={{ marginRight: 15 }}><Ionicons name="settings-outline" size={26} color={themeStyles.texto} /></TouchableOpacity>
|
||||
<TouchableOpacity onPress={() => router.push('/Aluno/perfil')}><Ionicons name="person-circle-outline" size={30} color={themeStyles.texto} /></TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={[styles.quickActionsContainer, { backgroundColor: isDarkMode ? '#1E1E1E' : '#F1F5F9' }]}>
|
||||
<TouchableOpacity
|
||||
style={[styles.quickActionBtn, activeTab === 'horas' && styles.quickActionBtnActive, activeTab === 'horas' && { backgroundColor: themeStyles.card }]}
|
||||
onPress={() => setActiveTab('horas')} activeOpacity={0.7}
|
||||
>
|
||||
<Ionicons name={activeTab === 'horas' ? "pie-chart" : "pie-chart-outline"} size={18} color={activeTab === 'horas' ? themeStyles.azul : themeStyles.textoSecundario} />
|
||||
<Text style={[styles.quickActionText, { color: activeTab === 'horas' ? themeStyles.azul : themeStyles.textoSecundario }]}>Horas</Text>
|
||||
<TouchableOpacity style={[styles.quickActionBtn, activeTab === 'assiduidade' && styles.quickActionBtnActive, activeTab === 'assiduidade' && { backgroundColor: themeStyles.card }]} onPress={() => setActiveTab('assiduidade')} activeOpacity={0.7}>
|
||||
<Ionicons name={activeTab === 'assiduidade' ? "calendar" : "calendar-outline"} size={22} color={activeTab === 'assiduidade' ? themeStyles.azul : themeStyles.textoSecundario} />
|
||||
<Text style={[styles.quickActionText, { color: activeTab === 'assiduidade' ? themeStyles.azul : themeStyles.textoSecundario }]}>Assiduidade</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[styles.quickActionBtn, activeTab === 'horario' && styles.quickActionBtnActive, activeTab === 'horario' && { backgroundColor: themeStyles.card }]}
|
||||
onPress={() => setActiveTab('horario')} activeOpacity={0.7}
|
||||
>
|
||||
<Ionicons name={activeTab === 'horario' ? "time" : "time-outline"} size={18} color={activeTab === 'horario' ? themeStyles.laranja : themeStyles.textoSecundario} />
|
||||
<TouchableOpacity style={[styles.quickActionBtn, activeTab === 'horario' && styles.quickActionBtnActive, activeTab === 'horario' && { backgroundColor: themeStyles.card }]} onPress={() => setActiveTab('horario')} activeOpacity={0.7}>
|
||||
<Ionicons name={activeTab === 'horario' ? "time" : "time-outline"} size={22} color={activeTab === 'horario' ? themeStyles.laranja : themeStyles.textoSecundario} />
|
||||
<Text style={[styles.quickActionText, { color: activeTab === 'horario' ? themeStyles.laranja : themeStyles.textoSecundario }]}>Horário</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[styles.quickActionBtn, activeTab === 'info' && styles.quickActionBtnActive, activeTab === 'info' && { backgroundColor: themeStyles.card }]}
|
||||
onPress={() => setActiveTab('info')} activeOpacity={0.7}
|
||||
>
|
||||
<Ionicons name={activeTab === 'info' ? "information-circle" : "information-circle-outline"} size={18} color={activeTab === 'info' ? themeStyles.verde : themeStyles.textoSecundario} />
|
||||
<Text style={[styles.quickActionText, { color: activeTab === 'info' ? themeStyles.verde : themeStyles.textoSecundario }]}>Info</Text>
|
||||
<TouchableOpacity style={[styles.quickActionBtn, activeTab === 'info' && styles.quickActionBtnActive, activeTab === 'info' && { backgroundColor: themeStyles.card }]} onPress={() => setActiveTab('info')} activeOpacity={0.7}>
|
||||
<Ionicons name={activeTab === 'info' ? "information-circle" : "information-circle-outline"} size={22} color={activeTab === 'info' ? themeStyles.verde : themeStyles.textoSecundario} />
|
||||
<Text style={[styles.quickActionText, { color: activeTab === 'info' ? themeStyles.verde : themeStyles.textoSecundario }]}>Detalhes</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{!isLoadingDB && (
|
||||
estagioDetalhes ? (
|
||||
<View style={[styles.dashboardCard, { backgroundColor: themeStyles.card, borderColor: themeStyles.borda, borderLeftColor: statusEstagio === 'concluido' ? '#94A3B8' : themeStyles.azul }]}>
|
||||
// ========================================================
|
||||
// CAIXA MESTRA DO CONTEÚDO DA TAB
|
||||
// Adicionado minHeight: 160 e justifyContent para fixar tamanho!
|
||||
// ========================================================
|
||||
<View style={[styles.dashboardCard, { backgroundColor: themeStyles.card, borderColor: themeStyles.borda, borderLeftColor: userRole !== 'aluno' ? (statusEstagio === 'concluido' ? '#94A3B8' : themeStyles.azul) : undefined }]}>
|
||||
|
||||
<View style={styles.dashHeader}>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', flex: 1, gap: 8 }}>
|
||||
<Ionicons name="business" size={20} color={statusEstagio === 'concluido' ? '#94A3B8' : themeStyles.azul} />
|
||||
<Text style={[styles.dashEmpresa, { color: themeStyles.texto }]} numberOfLines={1}>{estagioDetalhes.empresas?.nome || "Empresa não definida"}</Text>
|
||||
</View>
|
||||
<View style={[styles.statusBadge, { backgroundColor: badgeObj.bg }]}>
|
||||
<Text style={[styles.statusBadgeText, { color: badgeObj.text }]}>{badgeObj.label}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={[styles.dashDividerHorizontal, { backgroundColor: themeStyles.borda, marginTop: 4 }]} />
|
||||
|
||||
{activeTab === 'horas' && (
|
||||
<View>
|
||||
{/* LINHA DE CIMA: CÁLCULO DAS HORAS */}
|
||||
<View style={styles.dashGrid}>
|
||||
<View style={styles.dashGridItem}>
|
||||
<Text style={[styles.dashStatLabel, { color: themeStyles.textoSecundario }]}>REALIZADAS</Text>
|
||||
<Text style={[styles.dashStatValue, { color: themeStyles.azul }]}>{horasConcluidas}h</Text>
|
||||
{/* O Cabeçalho (Empresa) só aparece para Professores e Entidades */}
|
||||
{userRole !== 'aluno' && (
|
||||
<>
|
||||
<View style={styles.dashHeader}>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', flex: 1, gap: 8 }}>
|
||||
<Ionicons name="business" size={20} color={statusEstagio === 'concluido' ? '#94A3B8' : themeStyles.azul} />
|
||||
<Text style={[styles.dashEmpresa, { color: themeStyles.texto }]} numberOfLines={1}>{estagioDetalhes.empresas?.nome || "Entidade Não Atribuída"}</Text>
|
||||
</View>
|
||||
<View style={styles.dashDividerVertical} />
|
||||
<View style={styles.dashGridItem}>
|
||||
<Text style={[styles.dashStatLabel, { color: themeStyles.textoSecundario }]}>EM FALTA</Text>
|
||||
<Text style={[styles.dashStatValue, { color: themeStyles.laranja }]}>{horasEmFalta}h</Text>
|
||||
</View>
|
||||
<View style={styles.dashDividerVertical} />
|
||||
<View style={styles.dashGridItem}>
|
||||
<Text style={[styles.dashStatLabel, { color: themeStyles.textoSecundario }]}>TOTAIS</Text>
|
||||
<Text style={[styles.dashStatValue, { color: themeStyles.texto }]}>{horasTotais}h</Text>
|
||||
<View style={[styles.statusBadge, { backgroundColor: badgeObj.bg }]}>
|
||||
<Text style={[styles.statusBadgeText, { color: badgeObj.text }]}>{badgeObj.label}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={[styles.dashDividerHorizontal, { backgroundColor: themeStyles.borda }]} />
|
||||
|
||||
{/* LINHA DE BAIXO: ESTATÍSTICA DE PRESENÇAS E FALTAS */}
|
||||
<View style={styles.dashGrid}>
|
||||
<View style={styles.dashGridItem}>
|
||||
{/* Corrigido para PRESENÇAS */}
|
||||
<Text style={[styles.dashStatLabel, { color: themeStyles.textoSecundario }]}>PRESENÇAS</Text>
|
||||
<Text style={[styles.dashStatValue, { color: themeStyles.azul }]}>{statsFaltas.totalPresencas}</Text>
|
||||
</View>
|
||||
<View style={styles.dashDividerVertical} />
|
||||
<View style={styles.dashGridItem}>
|
||||
<Text style={[styles.dashStatLabel, { color: themeStyles.textoSecundario }]}>FALTAS JUST.</Text>
|
||||
<Text style={[styles.dashStatValue, { color: themeStyles.verde }]}>{statsFaltas.justificadas}</Text>
|
||||
</View>
|
||||
<View style={styles.dashDividerVertical} />
|
||||
<View style={styles.dashGridItem}>
|
||||
<Text style={[styles.dashStatLabel, { color: themeStyles.textoSecundario }]}>FALTAS INJ.</Text>
|
||||
<Text style={[styles.dashStatValue, { color: themeStyles.vermelho }]}>{statsFaltas.injustificadas}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<View style={[styles.dashDividerHorizontal, { backgroundColor: themeStyles.borda, marginTop: 4, marginBottom: 15 }]} />
|
||||
</>
|
||||
)}
|
||||
|
||||
{activeTab === 'horario' && (
|
||||
<View style={{ alignItems: 'center', paddingVertical: 10 }}>
|
||||
<Ionicons name="time" size={40} color={themeStyles.laranja} style={{ marginBottom: 10 }} />
|
||||
<Text style={{ fontSize: 13, color: themeStyles.textoSecundario, fontWeight: '700', textTransform: 'uppercase' }}>Carga Horária</Text>
|
||||
<Text style={{ fontSize: 24, fontWeight: '900', color: themeStyles.texto, marginTop: 2 }}>
|
||||
{estagioDetalhes.horas_diarias ? estagioDetalhes.horas_diarias + '/dia' : 'Não definido'}
|
||||
</Text>
|
||||
{/* CONTEÚDO DAS TABS COM ALTURA FIXA */}
|
||||
<View style={{ minHeight: 110, justifyContent: 'center' }}>
|
||||
{/* ABA: ASSIDUIDADE */}
|
||||
{activeTab === 'assiduidade' && (
|
||||
<View>
|
||||
{userRole === 'aluno' && (
|
||||
<Text style={{ fontSize: 13, fontWeight: '900', color: themeStyles.textoSecundario, marginBottom: 15, textTransform: 'uppercase', textAlign: 'center', letterSpacing: 1 }}>
|
||||
As Minhas Estatísticas
|
||||
</Text>
|
||||
)}
|
||||
<View style={styles.dashGrid}>
|
||||
<View style={styles.dashGridItem}>
|
||||
<Text style={[styles.dashStatLabel, { color: themeStyles.textoSecundario }]}>PRESENÇAS</Text>
|
||||
<Text style={[styles.dashStatValue, { color: themeStyles.azul }]}>{statsFaltas.totalPresencas}</Text>
|
||||
</View>
|
||||
<View style={styles.dashDividerVertical} />
|
||||
<View style={styles.dashGridItem}>
|
||||
<Text style={[styles.dashStatLabel, { color: themeStyles.textoSecundario }]}>FALTAS JUST.</Text>
|
||||
<Text style={[styles.dashStatValue, { color: themeStyles.verde }]}>{statsFaltas.justificadas}</Text>
|
||||
</View>
|
||||
<View style={styles.dashDividerVertical} />
|
||||
<View style={styles.dashGridItem}>
|
||||
<Text style={[styles.dashStatLabel, { color: themeStyles.textoSecundario }]}>FALTAS INJ.</Text>
|
||||
<Text style={[styles.dashStatValue, { color: themeStyles.vermelho }]}>{statsFaltas.injustificadas}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{horariosEstagio.length > 0 && (
|
||||
<View style={{ width: '100%', marginTop: 20 }}>
|
||||
<View style={[styles.dashDividerHorizontal, { backgroundColor: themeStyles.borda, marginVertical: 8 }]} />
|
||||
{horariosEstagio.map((h, index) => (
|
||||
<View key={index} style={{ flexDirection: 'row', justifyContent: 'space-between', paddingVertical: 8, paddingHorizontal: 10 }}>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
|
||||
<Ionicons name={h.periodo === 'Manhã' ? "partly-sunny-outline" : "sunny-outline"} size={16} color={themeStyles.textoSecundario} />
|
||||
<Text style={{ fontSize: 15, fontWeight: '800', color: themeStyles.textoSecundario }}>{h.periodo}</Text>
|
||||
{/* ABA: HORÁRIO */}
|
||||
{activeTab === 'horario' && (
|
||||
<View style={{ alignItems: 'center' }}>
|
||||
<Ionicons name="time" size={32} color={themeStyles.laranja} style={{ marginBottom: 5 }} />
|
||||
<Text style={{ fontSize: 12, color: themeStyles.textoSecundario, fontWeight: '700', textTransform: 'uppercase' }}>Carga Horária Definida</Text>
|
||||
<Text style={{ fontSize: 20, fontWeight: '900', color: themeStyles.texto, marginTop: 2 }}>
|
||||
{estagioDetalhes.horas_diarias ? estagioDetalhes.horas_diarias + ' Horas/Dia' : 'Não definido'}
|
||||
</Text>
|
||||
|
||||
{horariosEstagio.length > 0 && (
|
||||
<View style={{ width: '100%', marginTop: 15 }}>
|
||||
<View style={[styles.dashDividerHorizontal, { backgroundColor: themeStyles.borda, marginVertical: 8 }]} />
|
||||
{horariosEstagio.map((h, index) => (
|
||||
<View key={index} style={{ flexDirection: 'row', justifyContent: 'space-between', paddingVertical: 4, paddingHorizontal: 10 }}>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
|
||||
<Ionicons name={h.periodo === 'Manhã' ? "partly-sunny-outline" : "sunny-outline"} size={14} color={themeStyles.textoSecundario} />
|
||||
<Text style={{ fontSize: 13, fontWeight: '800', color: themeStyles.textoSecundario }}>{h.periodo}</Text>
|
||||
</View>
|
||||
<Text style={{ fontSize: 14, fontWeight: '900', color: themeStyles.texto }}>
|
||||
{h.hora_inicio?.slice(0, 5)} - {h.hora_fim?.slice(0, 5)}
|
||||
</Text>
|
||||
</View>
|
||||
<Text style={{ fontSize: 15, fontWeight: '900', color: themeStyles.texto }}>
|
||||
{h.hora_inicio?.slice(0, 5)} - {h.hora_fim?.slice(0, 5)}
|
||||
</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
|
||||
{activeTab === 'info' && (
|
||||
<View>
|
||||
<View style={styles.infoRow}>
|
||||
<Ionicons name="person" size={18} color={themeStyles.verde} />
|
||||
<View style={{ marginLeft: 10 }}>
|
||||
<Text style={[styles.infoLabel, { color: themeStyles.textoSecundario }]}>Tutor da Empresa</Text>
|
||||
<Text style={[styles.infoValue, { color: themeStyles.texto }]}>{estagioDetalhes.empresas?.tutor_nome || "N/A"}</Text>
|
||||
{/* ABA: DETALHES DA ENTIDADE */}
|
||||
{activeTab === 'info' && (
|
||||
<View>
|
||||
<View style={styles.infoRow}>
|
||||
<Ionicons name="person" size={18} color={themeStyles.verde} />
|
||||
<View style={{ marginLeft: 10 }}>
|
||||
<Text style={[styles.infoLabel, { color: themeStyles.textoSecundario }]}>Tutor da Entidade</Text>
|
||||
<Text style={[styles.infoValue, { color: themeStyles.texto }]}>{estagioDetalhes.empresas?.tutor_nome || "N/A"}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.infoRow}>
|
||||
<Ionicons name="call" size={18} color={themeStyles.verde} />
|
||||
<View style={{ marginLeft: 10 }}>
|
||||
<Text style={[styles.infoLabel, { color: themeStyles.textoSecundario }]}>Contacto Oficial</Text>
|
||||
<Text style={[styles.infoValue, { color: themeStyles.texto }]}>{estagioDetalhes.empresas?.tutor_telefone || "N/A"}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={[styles.dashDividerHorizontal, { backgroundColor: themeStyles.borda }]} />
|
||||
|
||||
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text style={[styles.infoLabel, { color: themeStyles.textoSecundario }]}>Início do Processo</Text>
|
||||
<Text style={[styles.infoValue, { color: themeStyles.texto }]}>{estagioDetalhes.data_inicio}</Text>
|
||||
</View>
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text style={[styles.infoLabel, { color: themeStyles.textoSecundario }]}>Término Previsto</Text>
|
||||
<Text style={[styles.infoValue, { color: themeStyles.texto }]}>{estagioDetalhes.data_fim}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.infoRow}>
|
||||
<Ionicons name="call" size={18} color={themeStyles.verde} />
|
||||
<View style={{ marginLeft: 10 }}>
|
||||
<Text style={[styles.infoLabel, { color: themeStyles.textoSecundario }]}>Contacto</Text>
|
||||
<Text style={[styles.infoValue, { color: themeStyles.texto }]}>{estagioDetalhes.empresas?.tutor_telefone || "N/A"}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={[styles.dashDividerHorizontal, { backgroundColor: themeStyles.borda }]} />
|
||||
|
||||
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text style={[styles.infoLabel, { color: themeStyles.textoSecundario }]}>Data de Início</Text>
|
||||
<Text style={[styles.infoValue, { color: themeStyles.texto }]}>{estagioDetalhes.data_inicio}</Text>
|
||||
</View>
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text style={[styles.infoLabel, { color: themeStyles.textoSecundario }]}>Data de Fim (Prevista)</Text>
|
||||
<Text style={[styles.infoValue, { color: themeStyles.texto }]}>{estagioDetalhes.data_fim}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
) : (
|
||||
<View style={[styles.avisoBox, { backgroundColor: themeStyles.aviso }]}>
|
||||
<Ionicons name="information-circle" size={24} color={themeStyles.avisoTexto} />
|
||||
<Text style={[styles.avisoTexto, { color: themeStyles.avisoTexto }]}>
|
||||
Sem estágio atribuído no sistema. Aguarda indicação do teu professor.
|
||||
Processo de estágio ainda não atribuído. Aguarda notificação da coordenação.
|
||||
</Text>
|
||||
</View>
|
||||
)
|
||||
@@ -647,14 +556,14 @@ const badgeObj = getBadgeStyle();
|
||||
onPress={handlePresencaClick}
|
||||
disabled={!infoData.podeMarcar || isDiaMarcado() || isLocating}
|
||||
>
|
||||
{isLocating ? <ActivityIndicator color="#fff" /> : <Text style={styles.txtBtn}>Marcar Presença</Text>}
|
||||
{isLocating ? <ActivityIndicator color="#fff" /> : <Text style={styles.txtBtn}>Registar Presença</Text>}
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={[styles.btn, { backgroundColor: themeStyles.vermelho }, (!infoData.valida || isDiaMarcado()) && styles.disabled]}
|
||||
onPress={handleFalta}
|
||||
disabled={!infoData.valida || isDiaMarcado()}
|
||||
>
|
||||
<Text style={styles.txtBtn}>Marcar Falta</Text>
|
||||
<Text style={styles.txtBtn}>Declarar Falta</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
@@ -678,44 +587,41 @@ const badgeObj = getBadgeStyle();
|
||||
<Text style={{ textAlign: 'center', marginTop: 15, fontWeight: '700', color: themeStyles.textoSecundario }}>🎉 {infoData.nomeFeriado}</Text>
|
||||
)}
|
||||
|
||||
{/* 🟢 MOSTRA O AVISO DO ESTADO DO TUTOR */}
|
||||
{renderAvisoEstadoDia()}
|
||||
|
||||
{/* SE O ALUNO ESTIVER PRESENTE, MOSTRA O SUMÁRIO */}
|
||||
{regSelecionado?.estado === 'presente' && (
|
||||
<View style={[styles.card, { backgroundColor: themeStyles.card, borderColor: themeStyles.borda }]}>
|
||||
<View style={styles.rowTitle}>
|
||||
<Text style={[styles.cardTitulo, { color: themeStyles.texto }]}>Sumário</Text>
|
||||
<Text style={[styles.cardTitulo, { color: themeStyles.texto }]}>Relatório de Atividades</Text>
|
||||
<TouchableOpacity onPress={() => setEditandoSumario(true)}><Ionicons name="create-outline" size={22} color={themeStyles.azul} /></TouchableOpacity>
|
||||
</View>
|
||||
<TextInput
|
||||
style={[styles.input, { borderColor: themeStyles.borda, color: themeStyles.texto, backgroundColor: themeStyles.fundo }]}
|
||||
multiline editable={editandoSumario} value={sumarioInput}
|
||||
onChangeText={setSumarioInput}
|
||||
placeholder="O que fizeste hoje?" placeholderTextColor="#94A3B8"
|
||||
placeholder="Descreve as tarefas realizadas e os conhecimentos aplicados..." placeholderTextColor="#94A3B8"
|
||||
/>
|
||||
{editandoSumario && <TouchableOpacity style={[styles.btnSalvar, { backgroundColor: themeStyles.verde }]} onPress={guardarSumario}><Text style={styles.txtBtn}>Submeter para Validação</Text></TouchableOpacity>}
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* SE O ALUNO FALTOU, MOSTRA O UPLOAD DE JUSTIFICAÇÃO */}
|
||||
{regSelecionado?.estado === 'faltou' && (
|
||||
<View style={[styles.card, { backgroundColor: themeStyles.card, borderColor: themeStyles.borda }]}>
|
||||
<Text style={[styles.cardTitulo, { color: themeStyles.texto, marginBottom: 15 }]}>Justificar Falta</Text>
|
||||
<Text style={[styles.cardTitulo, { color: themeStyles.texto, marginBottom: 15 }]}>Documento de Justificação</Text>
|
||||
{regSelecionado.justificacao_url ? (
|
||||
<View style={styles.justificadoBox}>
|
||||
<Ionicons name="checkmark-circle" size={20} color={themeStyles.verde} />
|
||||
<Text style={{ color: themeStyles.verde, fontWeight: '700' }}>Justificativo Enviado à Entidade</Text>
|
||||
<Text style={{ color: themeStyles.verde, fontWeight: '700' }}>Documento Submetido à Entidade</Text>
|
||||
</View>
|
||||
) : (
|
||||
<>
|
||||
<TouchableOpacity style={[styles.btnUpload, { borderColor: themeStyles.azul }]} onPress={selecionarDocumento}>
|
||||
<Ionicons name="document-attach-outline" size={20} color={themeStyles.azul} />
|
||||
<Text style={{ color: themeStyles.azul, fontWeight: '600' }}>{pdf ? pdf.name : "Selecionar Documento PDF"}</Text>
|
||||
<Text style={{ color: themeStyles.azul, fontWeight: '600' }}>{pdf ? pdf.name : "Anexar Documento PDF"}</Text>
|
||||
</TouchableOpacity>
|
||||
{pdf && (
|
||||
<TouchableOpacity style={[styles.btnSalvar, { backgroundColor: themeStyles.azul }]} onPress={enviarJustificativo} disabled={isUploading}>
|
||||
{isUploading ? <ActivityIndicator color="#fff" /> : <Text style={styles.txtBtn}>Submeter Documento</Text>}
|
||||
{isUploading ? <ActivityIndicator color="#fff" /> : <Text style={styles.txtBtn}>Submeter Anexo</Text>}
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</>
|
||||
@@ -734,10 +640,10 @@ const styles = StyleSheet.create({
|
||||
topIcons: { flexDirection: 'row', alignItems: 'center' },
|
||||
title: { fontSize: 26, fontWeight: '900' },
|
||||
|
||||
quickActionsContainer: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 20, borderRadius: 20, padding: 6 },
|
||||
quickActionBtn: { flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', paddingVertical: 12, borderRadius: 16, gap: 6, elevation: 0, shadowOpacity: 0, borderWidth: 0 },
|
||||
quickActionsContainer: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 20, borderRadius: 20, padding: 6, gap: 5 },
|
||||
quickActionBtn: { flex: 1, flexDirection: 'column', alignItems: 'center', justifyContent: 'center', paddingVertical: 10, borderRadius: 16, gap: 4, elevation: 0, shadowOpacity: 0, borderWidth: 0 },
|
||||
quickActionBtnActive: { elevation: 0, shadowOpacity: 0, borderWidth: 0 },
|
||||
quickActionText: { fontSize: 13, fontWeight: '800' },
|
||||
quickActionText: { fontSize: 11, fontWeight: '800', textAlign: 'center' },
|
||||
|
||||
dashboardCard: { padding: 18, borderRadius: 20, borderWidth: 1, borderLeftWidth: 5, marginBottom: 20, elevation: 2, shadowOpacity: 0.05, shadowRadius: 8 },
|
||||
dashHeader: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 },
|
||||
|
||||
@@ -8,16 +8,16 @@ import * as Sharing from 'expo-sharing';
|
||||
import * as WebBrowser from 'expo-web-browser';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Alert,
|
||||
RefreshControl,
|
||||
SafeAreaView,
|
||||
ScrollView,
|
||||
StatusBar,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View
|
||||
ActivityIndicator,
|
||||
Alert,
|
||||
RefreshControl,
|
||||
SafeAreaView,
|
||||
ScrollView,
|
||||
StatusBar,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View
|
||||
} from 'react-native';
|
||||
import { supabase } from '../../../lib/supabase';
|
||||
import { useTheme } from '../../../themecontext';
|
||||
@@ -361,7 +361,7 @@ export default function GestaoRelatorios() {
|
||||
{/* 3. DIÁRIO DE BORDO (PDF) - AGORA COM O ALUNO_ID! */}
|
||||
<View style={[styles.moduloBox, { borderBottomWidth: 0, paddingBottom: 0, marginBottom: 0 }]}>
|
||||
<View style={styles.moduloHeader}>
|
||||
<Text style={[styles.moduloTitle, { color: cores.texto }]}>3. Diário de Bordo</Text>
|
||||
<Text style={[styles.moduloTitle, { color: cores.texto }]}>3. Registos Diários</Text>
|
||||
<Text style={[styles.notaTag, { backgroundColor: cores.verdeAgua + '30', color: '#003049' }]}>{r.horas_concluidas}h Registadas</Text>
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
|
||||
Reference in New Issue
Block a user