updates
This commit is contained in:
@@ -10,6 +10,7 @@ import { memo, useCallback, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Animated,
|
||||
Image,
|
||||
Linking,
|
||||
Modal,
|
||||
Platform,
|
||||
@@ -40,7 +41,7 @@ const getFeriadosMap = (ano: number) => ({
|
||||
[`${ano}-01-01`]: "Ano Novo", [`${ano}-04-25`]: "Dia da Liberdade",
|
||||
[`${ano}-05-01`]: "Dia do Trabalhador", [`${ano}-06-10`]: "Dia de Portugal",
|
||||
[`${ano}-06-24`]: "São João (Vila do Conde)", [`${ano}-08-15`]: "Assunção de Nª Senhora",
|
||||
[`${ano}-10-05`]: "Impl আমের da República", [`${ano}-11-01`]: "Todos os Santos",
|
||||
[`${ano}-10-05`]: "Implantação da República", [`${ano}-11-01`]: "Todos os Santos",
|
||||
[`${ano}-12-01`]: "Restauração da Independência", [`${ano}-12-08`]: "Imaculada Conceição",
|
||||
[`${ano}-12-25`]: "Natal"
|
||||
});
|
||||
@@ -391,10 +392,9 @@ const AlunoHome = memo(() => {
|
||||
<Ionicons name="calendar-outline" size={28} color={themeStyles.textoSecundario} />
|
||||
<Text style={{ flex: 1, marginLeft: 12, fontWeight: '800', fontSize: 14, color: themeStyles.textoSecundario }}>Data fora do período de estágio</Text>
|
||||
</View>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
// 🟢 Correção: MOVIDO PARA CIMA para garantir que mostra em QUALQUER fim de semana ou feriado sem permissão
|
||||
if (infoData.bloqueadoPorRegra) {
|
||||
return (
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', backgroundColor: themeStyles.card, padding: 16, borderRadius: 16, marginTop: 15, borderWidth: 1, borderColor: themeStyles.borda }}>
|
||||
@@ -404,7 +404,7 @@ const AlunoHome = memo(() => {
|
||||
);
|
||||
}
|
||||
|
||||
if (selectedDate > hojeStr) return null; // Apenas dias ÚTEIS no futuro ficam escondidos
|
||||
if (selectedDate > hojeStr) return null;
|
||||
|
||||
let config = { icon: 'information-circle', cor: themeStyles.azul, bg: themeStyles.azulSuave, texto: 'Nenhum Registo Efetuado' };
|
||||
return (
|
||||
@@ -472,7 +472,21 @@ const AlunoHome = memo(() => {
|
||||
<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>
|
||||
{/* 🟢 IMAGEM COM SCALE 1.9 PARA FICAR MAIOR 🟢 */}
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||
<Image
|
||||
source={require('../../assets/images/logo_s/texto.png')}
|
||||
style={{
|
||||
width: 45,
|
||||
height: 45,
|
||||
resizeMode: 'contain',
|
||||
marginRight: 10,
|
||||
transform: [{ scale: 2.5 }]
|
||||
}}
|
||||
/>
|
||||
<Text style={[styles.title, { color: themeStyles.texto }]}>Estágios+</Text>
|
||||
</View>
|
||||
|
||||
<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>
|
||||
@@ -610,6 +624,34 @@ const AlunoHome = memo(() => {
|
||||
<Text style={[styles.infoValue, { color: themeStyles.texto }]}>{estagioDetalhes.data_fim}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* BOTÃO DA AUTOAVALIAÇÃO NA TAB INFO */}
|
||||
<View style={[styles.dashDividerHorizontal, { backgroundColor: themeStyles.borda }]} />
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
backgroundColor: themeStyles.laranja + '15',
|
||||
padding: 15,
|
||||
borderRadius: 15,
|
||||
marginTop: 5
|
||||
}}
|
||||
onPress={() => router.push('/Aluno/autoavaliacao')}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>
|
||||
<View style={{ backgroundColor: themeStyles.card, padding: 8, borderRadius: 10 }}>
|
||||
<Ionicons name="star" size={20} color={themeStyles.laranja} />
|
||||
</View>
|
||||
<View>
|
||||
<Text style={{ fontSize: 14, fontWeight: '800', color: themeStyles.texto }}>Ficha de Autoavaliação</Text>
|
||||
<Text style={{ fontSize: 11, fontWeight: '600', color: themeStyles.textoSecundario }}>Avalia o teu local de estágio</Text>
|
||||
</View>
|
||||
</View>
|
||||
<Ionicons name="chevron-forward" size={20} color={themeStyles.laranja} />
|
||||
</TouchableOpacity>
|
||||
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
226
app/Aluno/autoavaliacao.tsx
Normal file
226
app/Aluno/autoavaliacao.tsx
Normal file
@@ -0,0 +1,226 @@
|
||||
// app/Aluno/autoavaliacao.tsx
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { useRouter } from 'expo-router';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Alert,
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
SafeAreaView,
|
||||
ScrollView,
|
||||
StatusBar,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
View
|
||||
} from 'react-native';
|
||||
import { supabase } from '../../lib/supabase';
|
||||
import { useTheme } from '../../themecontext';
|
||||
|
||||
export default function AutoavaliacaoAluno() {
|
||||
const { isDarkMode } = useTheme();
|
||||
const router = useRouter();
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [submetendo, setSubmetendo] = useState(false);
|
||||
const [estagioId, setEstagioId] = useState<string | null>(null);
|
||||
const [alunoId, setAlunoId] = useState<string | null>(null);
|
||||
const [jaPreencheu, setJaPreencheu] = useState(false);
|
||||
|
||||
// States dos Critérios (1 a 5)
|
||||
const [assiduidade, setAssiduidade] = useState(0);
|
||||
const [responsabilidade, setResponsabilidade] = useState(0);
|
||||
const [relacionamento, setRelacionamento] = useState(0);
|
||||
const [conhecimentos, setConhecimentos] = useState(0);
|
||||
|
||||
const [nota, setNota] = useState('');
|
||||
const [comentarios, setComentarios] = useState('');
|
||||
|
||||
const cores = useMemo(() => ({
|
||||
fundo: isDarkMode ? '#0A0A0A' : '#F4F7FA',
|
||||
card: isDarkMode ? '#161618' : '#FFFFFF',
|
||||
texto: isDarkMode ? '#F8FAFC' : '#0D2235',
|
||||
textoSecundario: isDarkMode ? '#94A3B8' : '#64748B',
|
||||
borda: isDarkMode ? '#2D2D2D' : '#E2E8F0',
|
||||
azulMarinho: '#003049',
|
||||
verdeAgua: '#71BEB3',
|
||||
laranja: '#F18721',
|
||||
}), [isDarkMode]);
|
||||
|
||||
useEffect(() => { carregarDados(); }, []);
|
||||
|
||||
const carregarDados = async () => {
|
||||
try {
|
||||
const { data: userData, error: userError } = await supabase.auth.getUser();
|
||||
if (userError || !userData?.user) throw new Error('Utilizador não autenticado.');
|
||||
const currentUserId = userData.user.id;
|
||||
setAlunoId(currentUserId);
|
||||
|
||||
const { data: estagioData, error: estagioError } = await supabase
|
||||
.from('estagios').select('id').eq('aluno_id', currentUserId).order('data_inicio', { ascending: false }).limit(1).single();
|
||||
|
||||
if (estagioError && estagioError.code !== 'PGRST116') throw estagioError;
|
||||
|
||||
if (estagioData) {
|
||||
setEstagioId(estagioData.id);
|
||||
const { data: avaliacaoData } = await supabase.from('autoavaliacoes').select('id').eq('estagio_id', estagioData.id).single();
|
||||
if (avaliacaoData) setJaPreencheu(true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
Alert.alert('Erro', 'Não foi possível carregar os dados.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const submeterAvaliacao = async () => {
|
||||
if (assiduidade === 0 || responsabilidade === 0 || relacionamento === 0 || conhecimentos === 0) {
|
||||
return Alert.alert('Atenção', 'Por favor, seleciona um número de 1 a 5 para cada critério.');
|
||||
}
|
||||
|
||||
if (!nota || nota.trim() === '') {
|
||||
return Alert.alert('Atenção', 'Por favor, insere a nota final que propões (0 a 20).');
|
||||
}
|
||||
|
||||
const notaNum = parseInt(nota);
|
||||
if (isNaN(notaNum) || notaNum < 0 || notaNum > 20) {
|
||||
return Alert.alert('Nota Inválida', 'A nota tem de ser um número inteiro entre 0 e 20.');
|
||||
}
|
||||
|
||||
setSubmetendo(true);
|
||||
try {
|
||||
const { error } = await supabase.from('autoavaliacoes').insert({
|
||||
estagio_id: estagioId,
|
||||
aluno_id: alunoId,
|
||||
assiduidade,
|
||||
responsabilidade,
|
||||
relacionamento,
|
||||
conhecimentos,
|
||||
nota_desejada: notaNum,
|
||||
comentarios
|
||||
});
|
||||
|
||||
if (error) throw error;
|
||||
Alert.alert('Sucesso', 'A tua autoavaliação foi submetida!');
|
||||
setJaPreencheu(true);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
Alert.alert('Erro', 'Ocorreu um erro ao submeter.');
|
||||
} finally {
|
||||
setSubmetendo(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 🟢 NOVA FUNÇÃO PARA RENDERIZAR NÚMEROS (Substitui as estrelas)
|
||||
const renderNumeros = (valor: number, setValor: (v: number) => void) => (
|
||||
<View style={styles.numerosContainer}>
|
||||
{[1, 2, 3, 4, 5].map((num) => (
|
||||
<TouchableOpacity
|
||||
key={num}
|
||||
style={[styles.numBtn, valor === num && { backgroundColor: cores.laranja }]}
|
||||
onPress={() => setValor(num)}
|
||||
>
|
||||
<Text style={[styles.numText, { color: valor === num ? '#FFF' : cores.texto }]}>{num}</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
|
||||
if (loading) return <View style={[styles.safeArea, { backgroundColor: cores.fundo, justifyContent: 'center' }]}><ActivityIndicator size="large" color={cores.azulMarinho} /></View>;
|
||||
|
||||
return (
|
||||
<SafeAreaView style={[styles.safeArea, { backgroundColor: cores.fundo }]}>
|
||||
<StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
|
||||
<KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : 'height'} style={{ flex: 1 }}>
|
||||
<View style={[styles.header, { paddingTop: Platform.OS === 'android' ? (StatusBar.currentHeight || 0) + 15 : 35 }]}>
|
||||
<TouchableOpacity style={styles.btnVoltar} onPress={() => router.back()}><Ionicons name="arrow-back" size={24} color={cores.texto} /></TouchableOpacity>
|
||||
<Text style={[styles.headerTitle, { color: cores.texto }]}>Autoavaliação</Text>
|
||||
<View style={{ width: 24 }} />
|
||||
</View>
|
||||
|
||||
<ScrollView contentContainerStyle={styles.scrollContent}>
|
||||
<View style={styles.introBox}>
|
||||
<Ionicons name="clipboard-outline" size={32} color={cores.laranja} />
|
||||
<Text style={[styles.introText, { color: cores.texto }]}>Avalia o teu desempenho (1 a 5) e propõe a tua nota final (0 a 20).</Text>
|
||||
</View>
|
||||
|
||||
{jaPreencheu ? (
|
||||
<View style={[styles.sucessoBox, { backgroundColor: cores.verdeAgua + '20', borderColor: cores.verdeAgua }]}>
|
||||
<Ionicons name="checkmark-circle" size={40} color={cores.verdeAgua} />
|
||||
<Text style={[styles.sucessoText, { color: cores.texto }]}>Já submeteste a tua autoavaliação.</Text>
|
||||
</View>
|
||||
) : (
|
||||
<View>
|
||||
<View style={[styles.card, { backgroundColor: cores.card, borderColor: cores.borda }]}>
|
||||
<View style={styles.criterioBox}>
|
||||
<Text style={[styles.criterioLabel, { color: cores.texto }]}>Assiduidade e Pontualidade</Text>
|
||||
{renderNumeros(assiduidade, setAssiduidade)}
|
||||
</View>
|
||||
<View style={styles.criterioBox}>
|
||||
<Text style={[styles.criterioLabel, { color: cores.texto }]}>Responsabilidade e Iniciativa</Text>
|
||||
{renderNumeros(responsabilidade, setResponsabilidade)}
|
||||
</View>
|
||||
<View style={styles.criterioBox}>
|
||||
<Text style={[styles.criterioLabel, { color: cores.texto }]}>Relacionamento Interpessoal</Text>
|
||||
{renderNumeros(relacionamento, setRelacionamento)}
|
||||
</View>
|
||||
<View style={[styles.criterioBox, { borderBottomWidth: 0, paddingBottom: 0, marginBottom: 0 }]}>
|
||||
<Text style={[styles.criterioLabel, { color: cores.texto }]}>Aplicação de Conhecimentos</Text>
|
||||
{renderNumeros(conhecimentos, setConhecimentos)}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={[styles.card, { backgroundColor: cores.card, borderColor: cores.borda, alignItems: 'center', paddingVertical: 30 }]}>
|
||||
<Text style={[styles.criterioLabel, { color: cores.texto, textAlign: 'center', marginBottom: 15 }]}>Nota Final Proposta (0 a 20)</Text>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'flex-end', justifyContent: 'center' }}>
|
||||
<TextInput
|
||||
style={[styles.notaInput, { color: cores.azulMarinho, borderBottomColor: cores.borda }]}
|
||||
keyboardType="number-pad" maxLength={2} value={nota} onChangeText={(val) => setNota(val.replace(/[^0-9]/g, ''))}
|
||||
placeholder="--" placeholderTextColor={cores.textoSecundario + '50'}
|
||||
/>
|
||||
<Text style={{ fontSize: 24, fontWeight: '800', color: cores.textoSecundario, marginBottom: 5, marginLeft: 5 }}>Val.</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<TextInput
|
||||
style={[styles.inputArea, { backgroundColor: cores.card, color: cores.texto, borderColor: cores.borda }]}
|
||||
placeholder="Comentários adicionais..." placeholderTextColor={cores.textoSecundario}
|
||||
multiline numberOfLines={5} value={comentarios} onChangeText={setComentarios} textAlignVertical="top"
|
||||
/>
|
||||
|
||||
<TouchableOpacity style={[styles.btnSubmeter, { backgroundColor: cores.laranja }]} onPress={submeterAvaliacao} disabled={submetendo}>
|
||||
{submetendo ? <ActivityIndicator color="#FFF" /> : <><Ionicons name="send" size={20} color="#FFF" /><Text style={styles.btnSubmeterText}>Submeter Avaliação</Text></>}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)}
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
safeArea: { flex: 1 },
|
||||
header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: 20, paddingBottom: 15 },
|
||||
btnVoltar: { padding: 5, marginLeft: -5 },
|
||||
headerTitle: { fontSize: 18, fontWeight: '900' },
|
||||
scrollContent: { padding: 20, paddingBottom: 40 },
|
||||
introBox: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#F1872115', padding: 15, borderRadius: 15, marginBottom: 25, gap: 12 },
|
||||
introText: { flex: 1, fontSize: 13, fontWeight: '600', lineHeight: 18 },
|
||||
card: { padding: 20, borderRadius: 20, borderWidth: 1, marginBottom: 15 },
|
||||
criterioBox: { borderBottomWidth: 1, borderBottomColor: '#E2E8F0', paddingBottom: 15, marginBottom: 15 },
|
||||
criterioLabel: { fontSize: 15, fontWeight: '800' },
|
||||
// Estilo dos botões numéricos
|
||||
numerosContainer: { flexDirection: 'row', gap: 10, marginTop: 12 },
|
||||
numBtn: { width: 45, height: 45, borderRadius: 12, backgroundColor: '#E2E8F0', justifyContent: 'center', alignItems: 'center' },
|
||||
numText: { fontSize: 16, fontWeight: '900' },
|
||||
notaInput: { fontSize: 50, fontWeight: '900', textAlign: 'center', width: 80, borderBottomWidth: 3, paddingBottom: 0 },
|
||||
inputArea: { borderRadius: 12, borderWidth: 1, padding: 15, fontSize: 14, minHeight: 120, marginBottom: 15 },
|
||||
btnSubmeter: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', padding: 18, borderRadius: 15, marginTop: 10, gap: 10 },
|
||||
btnSubmeterText: { color: '#FFF', fontSize: 15, fontWeight: 'bold' },
|
||||
sucessoBox: { padding: 25, borderRadius: 20, borderWidth: 1, alignItems: 'center', marginTop: 20 },
|
||||
sucessoText: { fontSize: 14, fontWeight: '600', textAlign: 'center', marginTop: 15, lineHeight: 22 }
|
||||
});
|
||||
@@ -46,7 +46,6 @@ export default function GestaoRelatorios() {
|
||||
laranja: '#F18721',
|
||||
}), [isDarkMode]);
|
||||
|
||||
// 🟢 LÊ AS IMAGENS DIRETAMENTE DA TUA APP (OFFLINE)
|
||||
const getBase64Image = async (imageModule: any) => {
|
||||
try {
|
||||
const asset = Asset.fromModule(imageModule);
|
||||
@@ -74,7 +73,8 @@ export default function GestaoRelatorios() {
|
||||
.select(`
|
||||
id, horas_totais, horas_concluidas, horas_diarias, nota_final, avaliacao_url, data_inicio, data_fim,
|
||||
alunos (id, nome, turma_curso, n_escola, ano),
|
||||
empresas (nome, tutor_nome)
|
||||
empresas (nome, tutor_nome),
|
||||
autoavaliacoes (id, assiduidade, responsabilidade, relacionamento, conhecimentos, nota_desejada, comentarios, criado_em)
|
||||
`)
|
||||
.order('data_inicio', { ascending: false });
|
||||
|
||||
@@ -83,6 +83,7 @@ export default function GestaoRelatorios() {
|
||||
const formatados = data?.map((estagio: any) => {
|
||||
const aluno = Array.isArray(estagio.alunos) ? estagio.alunos[0] : estagio.alunos;
|
||||
const empresa = Array.isArray(estagio.empresas) ? estagio.empresas[0] : estagio.empresas;
|
||||
const autoavaliacao = Array.isArray(estagio.autoavaliacoes) ? estagio.autoavaliacoes[0] : estagio.autoavaliacoes;
|
||||
|
||||
const nomeCursoFormatado = formatarTexto(aluno?.turma_curso);
|
||||
const anoAluno = aluno?.ano || 10;
|
||||
@@ -104,7 +105,8 @@ export default function GestaoRelatorios() {
|
||||
horas_totais: estagio.horas_totais || 0,
|
||||
horas_concluidas: estagio.horas_concluidas || 0,
|
||||
nota_empresa: estagio.nota_final,
|
||||
pdf_empresa: estagio.avaliacao_url
|
||||
pdf_empresa: estagio.avaliacao_url,
|
||||
autoavaliacao: autoavaliacao || null
|
||||
};
|
||||
}) || [];
|
||||
|
||||
@@ -141,9 +143,66 @@ export default function GestaoRelatorios() {
|
||||
}, [relatorios, turmaAtiva]);
|
||||
|
||||
// =====================================================================
|
||||
// GERAÇÃO DO MOTOR HTML DA MATRIZ (USADO NO EXCEL E NO PDF)
|
||||
// GERAÇÃO DO CABEÇALHO E RODAPÉ COM OS SÍMBOLOS E MARCA DE ÁGUA
|
||||
// =====================================================================
|
||||
const construirHtmlMatriz = async (logoEPVC_b64: string, logoEstagios_b64: string) => {
|
||||
const gerarCabecalhoERodape = async () => {
|
||||
const b64Escola = await getBase64Image(require('../../../assets/images/logoepvc3.png'));
|
||||
const b64App = await getBase64Image(require('../../../assets/images/logo_s/texto.png'));
|
||||
// A imagem que tem os vários logótipos em baixo
|
||||
const b64LogosFinais = await getBase64Image(require('../../../assets/images/logoepvc.png'));
|
||||
|
||||
return {
|
||||
header: `
|
||||
<table style="width: 100%; border: none; margin-bottom: 20px;">
|
||||
<tr>
|
||||
<td style="width: 25%; text-align: left; vertical-align: top; border: none;">
|
||||
<img src="${b64Escola}" style="width: 130px; height: auto; display: block;" />
|
||||
</td>
|
||||
<td style="width: 50%; text-align: center; border: none;">
|
||||
</td>
|
||||
<td style="width: 25%; text-align: right; vertical-align: top; border: none;">
|
||||
<img src="${b64App}" style="width: 90px; height: auto; display: block; margin-left: auto;" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
`,
|
||||
footer: `
|
||||
<div style="position: absolute; bottom: 10mm; left: 0; right: 0; text-align: center;">
|
||||
<img src="${b64LogosFinais}" style="height: 40px; width: auto; display: inline-block;" />
|
||||
<div style="font-size: 9px; color: #64748B; margin-top: 10px; font-style: italic;">
|
||||
Documento gerado digitalmente via plataforma Estágios+ EPVC • ${new Date().toLocaleDateString('pt-PT')}
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
watermark: `
|
||||
<div style="position: fixed; top: 35%; left: 15%; opacity: 0.04; z-index: -100; transform: rotate(-30deg); pointer-events: none;">
|
||||
<img src="${b64Escola}" style="width: 550px;" />
|
||||
</div>
|
||||
`
|
||||
};
|
||||
};
|
||||
|
||||
const getEstilosHTML = () => `
|
||||
<style>
|
||||
.main-table { border-collapse: collapse; width: 100%; }
|
||||
.th-blue { background-color: #003049; color: #ffffff; font-weight: bold; border: 1px solid #cbd5e1; text-align: center; vertical-align: middle; font-size: 11px; padding: 4px; }
|
||||
.th-orange { background-color: #f18721; color: #ffffff; font-weight: bold; border: 1px solid #cbd5e1; text-align: center; vertical-align: middle; font-size: 11px; padding: 4px; }
|
||||
.th-total { background-color: #0f172a; color: #ffffff; font-weight: bold; border: 1px solid #cbd5e1; text-align: center; vertical-align: middle; font-size: 11px; padding: 4px; }
|
||||
.td-cell { border: 1px solid #cbd5e1; text-align: center; vertical-align: middle; mso-number-format: "0"; font-size: 10px; padding: 4px; }
|
||||
.td-total-cell { border: 1px solid #cbd5e1; text-align: center; vertical-align: middle; font-weight: bold; background-color: #e2e8f0; font-size: 10px; padding: 4px; }
|
||||
.td-name { border: 1px solid #cbd5e1; text-align: left; vertical-align: middle; font-weight: bold; white-space: nowrap; padding: 4px 6px; font-size: 11px; }
|
||||
.zebra { background-color: #F8FAFC; }
|
||||
</style>
|
||||
`;
|
||||
|
||||
// =====================================================================
|
||||
// 1 & 2. GERAÇÃO DA MATRIZ EM HTML (USADA NO EXCEL E NO PDF HORIZONTAL)
|
||||
// =====================================================================
|
||||
const construirHtmlMatriz = async () => {
|
||||
const { header, footer } = await gerarCabecalhoERodape();
|
||||
const tituloMatriz = `<h1 style="color: #003049; margin: 0; font-size: 18px; font-family: sans-serif;">Mapa Oficial de Assiduidade</h1><h2 style="color: #F18721; margin: 5px 0 0; font-size: 12px; font-family: sans-serif;">Formação em Contexto de Trabalho</h2>`;
|
||||
const headerCompleto = header.replace('', tituloMatriz);
|
||||
|
||||
const alunosIds = relatoriosFiltrados.map(r => r.aluno_id);
|
||||
const { data: presencas } = await supabase
|
||||
.from('presencas')
|
||||
@@ -171,7 +230,7 @@ export default function GestaoRelatorios() {
|
||||
const dia = current.getDate();
|
||||
const dw = current.getDay();
|
||||
|
||||
if (dw !== 0 && dw !== 6) { // Ignorar Sábados e Domingos
|
||||
if (dw !== 0 && dw !== 6) {
|
||||
const dataStr = `${ano}-${String(mes+1).padStart(2,'0')}-${String(dia).padStart(2,'0')}`;
|
||||
let mesObj = meses.find(m => m.nome === `${nomeMes} ${ano}`);
|
||||
if (!mesObj) { mesObj = { nome: `${nomeMes} ${ano}`, dias: [] }; meses.push(mesObj); }
|
||||
@@ -180,9 +239,6 @@ export default function GestaoRelatorios() {
|
||||
current.setDate(current.getDate() + 1);
|
||||
}
|
||||
|
||||
let totalColsDias = meses.reduce((acc: number, m: any) => acc + m.dias.length + 1, 0);
|
||||
if (totalColsDias === 0) totalColsDias = 1;
|
||||
|
||||
let mesesHtml = `<tr style="height: 35px;">
|
||||
<td class="th-blue" style="width: 40px;">Nº</td>
|
||||
<td class="th-blue" style="width: 250px;">Nome do Aluno</td>`;
|
||||
@@ -197,9 +253,9 @@ export default function GestaoRelatorios() {
|
||||
diasHtml += `<td class="th-total" style="width: 40px;">Total</td>`;
|
||||
});
|
||||
|
||||
mesesHtml += `<td class="th-orange" style="width: 120px;">Horas Lecionadas (1)</td>
|
||||
<td class="th-orange" style="width: 120px;">Horas por Lecionar (2)</td>
|
||||
<td class="th-orange" style="width: 120px;">Total Contrato (3)</td></tr>`;
|
||||
mesesHtml += `<td class="th-orange" style="width: 120px;">Horas Lecionadas</td>
|
||||
<td class="th-orange" style="width: 120px;">Horas por Lecionar</td>
|
||||
<td class="th-orange" style="width: 120px;">Total Contrato</td></tr>`;
|
||||
diasHtml += `<td class="th-blue"></td><td class="th-blue"></td><td class="th-blue"></td></tr>`;
|
||||
|
||||
let alunosHtml = '';
|
||||
@@ -246,73 +302,33 @@ export default function GestaoRelatorios() {
|
||||
});
|
||||
|
||||
return `
|
||||
<table class="main-table">
|
||||
<tr>
|
||||
<td colspan="4" rowspan="2" style="text-align: center; vertical-align: middle; padding: 10px;">
|
||||
<img src="${logoEPVC_b64}" width="140" style="object-fit: contain; background: transparent; mix-blend-mode: multiply;" />
|
||||
</td>
|
||||
<td colspan="${totalColsDias - 3}" rowspan="2" class="title-escola" style="text-align: center; vertical-align: middle;">
|
||||
Escola Profissional de Vila do Conde<br/>
|
||||
<span style="font-size: 16pt; color: #f18721;">Mapa Oficial de Assiduidade (FCT)</span>
|
||||
</td>
|
||||
<td colspan="4" rowspan="2" style="text-align: center; vertical-align: middle; padding: 10px;">
|
||||
<img src="${logoEstagios_b64}" width="140" style="object-fit: contain; background: transparent; mix-blend-mode: multiply;" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr></tr>
|
||||
<tr><td colspan="${totalColsDias + 5}"></td></tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="4" class="subtitle">Período: ${new Date(minDate).toLocaleDateString('pt-PT')} a ${new Date(maxDate).toLocaleDateString('pt-PT')}</td>
|
||||
<td colspan="${totalColsDias - 3}" class="subtitle" style="text-align: center;">Curso / Turma: ${turmaAtiva || 'Todas as Turmas'}</td>
|
||||
<td colspan="4" class="subtitle" style="text-align: right;">Ano Letivo: 2025/2026</td>
|
||||
</tr>
|
||||
<tr><td colspan="${totalColsDias + 5}"></td></tr>
|
||||
|
||||
${mesesHtml}
|
||||
${diasHtml}
|
||||
${alunosHtml}
|
||||
|
||||
<tr><td colspan="${totalColsDias + 5}"></td></tr>
|
||||
<tr>
|
||||
<td colspan="6" style="font-style: italic; color: #64748b; font-size: 11px;">(1) - Horas Lecionadas no período</td>
|
||||
<td colspan="${Math.max(1, totalColsDias - 7)}" style="font-style: italic; color: #64748b; font-size: 11px;">(2) - Horas em falta para lecionar</td>
|
||||
<td colspan="6" style="font-style: italic; color: #64748b; font-size: 11px;">(3) - Total de Horas do Contrato de Estágio</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="15" style="font-style: italic; color: #ef4444; font-weight: bold; font-size: 11px; padding-top: 5px;">
|
||||
Legenda de Faltas: [ F ] - Falta Justificada | [ FI ] - Falta Injustificada
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div style="font-family: 'Segoe UI', Arial, sans-serif;">
|
||||
${headerCompleto}
|
||||
<table style="width: 100%; margin-bottom: 10px; font-size: 11px;">
|
||||
<tr>
|
||||
<td style="color:#64748b; font-weight:bold;">Período: ${new Date(minDate).toLocaleDateString('pt-PT')} a ${new Date(maxDate).toLocaleDateString('pt-PT')}</td>
|
||||
<td style="text-align: center; color:#64748b; font-weight:bold;">Turma: ${turmaAtiva || 'Geral'}</td>
|
||||
<td style="text-align: right; color:#64748b; font-weight:bold;">Ano Letivo: 2025/2026</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table class="main-table">
|
||||
${mesesHtml}
|
||||
${diasHtml}
|
||||
${alunosHtml}
|
||||
</table>
|
||||
<div style="margin-top: 15px; font-size: 10px; color: #64748B;">
|
||||
<strong>Legenda:</strong> <span style="color: #D97706; font-weight: bold;">[ F ]</span> Falta Justificada • <span style="color: #EF4444; font-weight: bold;">[ FI ]</span> Falta Injustificada
|
||||
</div>
|
||||
${footer}
|
||||
</div>
|
||||
`;
|
||||
};
|
||||
|
||||
const getEstilosHTML = () => `
|
||||
<style>
|
||||
.main-table { border-collapse: collapse; font-family: 'Segoe UI', Arial, sans-serif; width: 100%; }
|
||||
.th-blue { background-color: #003049; color: #ffffff; font-weight: bold; border: 1px solid #cbd5e1; text-align: center; vertical-align: middle; font-size: 11px; padding: 4px; }
|
||||
.th-orange { background-color: #f18721; color: #ffffff; font-weight: bold; border: 1px solid #cbd5e1; text-align: center; vertical-align: middle; font-size: 11px; padding: 4px; }
|
||||
.th-total { background-color: #0f172a; color: #ffffff; font-weight: bold; border: 1px solid #cbd5e1; text-align: center; vertical-align: middle; font-size: 11px; padding: 4px; }
|
||||
.td-cell { border: 1px solid #cbd5e1; text-align: center; vertical-align: middle; mso-number-format: "0"; font-size: 10px; padding: 4px; }
|
||||
.td-total-cell { border: 1px solid #cbd5e1; text-align: center; vertical-align: middle; font-weight: bold; background-color: #e2e8f0; font-size: 10px; padding: 4px; }
|
||||
.td-name { border: 1px solid #cbd5e1; text-align: left; vertical-align: middle; font-weight: bold; white-space: nowrap; padding: 4px 6px; font-size: 11px; }
|
||||
.title-escola { font-size: 20pt; font-weight: bold; color: #003049; }
|
||||
.subtitle { font-size: 12pt; color: #64748b; font-weight: bold; padding: 5px 0; }
|
||||
.zebra { background-color: #F8FAFC; }
|
||||
</style>
|
||||
`;
|
||||
|
||||
// 1. EXPORTAR A MATRIZ EM FORMATO EXCEL NATIVO (.XLS)
|
||||
const gerarExcelGeral = async () => {
|
||||
if (relatoriosFiltrados.length === 0) return Alert.alert("Aviso", "Não há alunos para exportar nesta turma.");
|
||||
setGerandoDocumento("excel");
|
||||
try {
|
||||
const b64Escola = await getBase64Image(require('../../../assets/images/logoepvc3.png'));
|
||||
const b64App = await getBase64Image(require('../../../assets/images/logo_s/texto.png'));
|
||||
|
||||
const htmlCorpo = await construirHtmlMatriz(b64Escola, b64App);
|
||||
|
||||
const htmlCorpo = await construirHtmlMatriz();
|
||||
const docExcel = `
|
||||
<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/html40">
|
||||
<head>
|
||||
@@ -345,15 +361,11 @@ export default function GestaoRelatorios() {
|
||||
}
|
||||
};
|
||||
|
||||
// 2. EXPORTAR A MESMA MATRIZ EM FORMATO PDF
|
||||
const gerarPautaPDF = async () => {
|
||||
if (relatoriosFiltrados.length === 0) return Alert.alert("Aviso", "Não há alunos para exportar nesta turma.");
|
||||
setGerandoDocumento("pdf");
|
||||
try {
|
||||
const b64Escola = await getBase64Image(require('../../../assets/images/logoepvc3.png'));
|
||||
const b64App = await getBase64Image(require('../../../assets/images/logo_s/texto.png'));
|
||||
|
||||
const htmlCorpo = await construirHtmlMatriz(b64Escola, b64App);
|
||||
const htmlCorpo = await construirHtmlMatriz();
|
||||
|
||||
const htmlCompletoPDF = `
|
||||
<!DOCTYPE html>
|
||||
@@ -389,6 +401,95 @@ export default function GestaoRelatorios() {
|
||||
}
|
||||
};
|
||||
|
||||
// =====================================================================
|
||||
// 3. EXPORTAR A AUTOAVALIAÇÃO DO ALUNO
|
||||
// =====================================================================
|
||||
const gerarAutoavaliacaoPDF = async (r: any) => {
|
||||
if (!r.autoavaliacao) return Alert.alert("Aviso", "Este aluno ainda não submeteu a sua autoavaliação.");
|
||||
|
||||
setGerandoDocumento(`auto_${r.id_estagio}`);
|
||||
try {
|
||||
const { header, footer, watermark } = await gerarCabecalhoERodape();
|
||||
const tituloAuto = `<h1 style="color: #003049; margin: 0; font-size: 18px; font-family: sans-serif;">Ficha de Autoavaliação</h1><h2 style="color: #F18721; margin: 5px 0 0; font-size: 12px; font-family: sans-serif;">Formação em Contexto de Trabalho</h2>`;
|
||||
const headerCompleto = header.replace('', tituloAuto);
|
||||
|
||||
const av = r.autoavaliacao;
|
||||
|
||||
const htmlContent = `
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-PT">
|
||||
<head>
|
||||
<style>
|
||||
@page { size: A4; margin: 0; } html, body { height: 99%; overflow: hidden; }
|
||||
body { font-family: sans-serif; margin: 0; padding: 15mm 20mm; color: #1E293B; font-size: 12px; }
|
||||
.info-table { width: 100%; border-collapse: collapse; margin-bottom: 25px; margin-top: 10px; font-size: 12px; border: 1px solid #CBD5E1; }
|
||||
.info-table td { padding: 6px 10px; border: 1px solid #CBD5E1; } .info-table td.label { background-color: #F8FAFC; font-weight: bold; width: 25%; }
|
||||
.section-title { background-color: #003049; color: white; padding: 6px 10px; font-size: 12px; font-weight: bold; margin-bottom: 10px; }
|
||||
.eval-table { width: 100%; border-collapse: collapse; margin-bottom: 25px; font-size: 12px; }
|
||||
.eval-table th { background-color: #F1F5F9; padding: 8px; border: 1px solid #CBD5E1; text-align: left; }
|
||||
.eval-table td { border: 1px solid #CBD5E1; padding: 8px 10px; }
|
||||
.eval-table .score { text-align: center; font-weight: bold; font-size: 14px; width: 25%; }
|
||||
.comments-box { border: 1px solid #CBD5E1; padding: 15px; min-height: 80px; background-color: #F8FAFC; font-style: italic; font-size: 12px; line-height: 1.6; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
${watermark}
|
||||
${headerCompleto}
|
||||
|
||||
<table class="info-table">
|
||||
<tr><td class="label">Estagiário(a):</td><td><strong>${r.aluno_nome}</strong></td></tr>
|
||||
<tr><td class="label">Turma e Curso:</td><td>${r.turma}</td></tr>
|
||||
<tr><td class="label">Entidade de Acolhimento:</td><td><strong>${r.empresa_nome}</strong></td></tr>
|
||||
<tr><td class="label">Data de Submissão:</td><td>${new Date(av.criado_em).toLocaleDateString('pt-PT')}</td></tr>
|
||||
</table>
|
||||
|
||||
<div style="display: flex; flex-direction: row; gap: 20px; align-items: flex-start;">
|
||||
<div style="flex: 2;">
|
||||
<div class="section-title">Critérios Qualitativos</div>
|
||||
<table class="eval-table">
|
||||
<tr><th>Domínio Avaliado</th><th style="text-align: center;">Escala (1 a 5)</th></tr>
|
||||
<tr><td>Assiduidade e Pontualidade</td><td class="score">${av.assiduidade}</td></tr>
|
||||
<tr><td>Responsabilidade e Iniciativa</td><td class="score">${av.responsabilidade}</td></tr>
|
||||
<tr><td>Relacionamento Interpessoal</td><td class="score">${av.relacionamento}</td></tr>
|
||||
<tr><td>Aplicação de Conhecimentos Práticos</td><td class="score">${av.conhecimentos}</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div style="flex: 1;">
|
||||
<div class="section-title">Nota Final</div>
|
||||
<div style="text-align: center; border: 2px solid #E2E8F0; padding: 25px 0; background-color: #F8FAFC;">
|
||||
<div style="font-size: 45px; font-weight: 900; color: #003049;">${av.nota_desejada}</div>
|
||||
<div style="font-size: 14px; color: #64748B; font-weight: bold; margin-top: 5px;">Valores</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-title">Justificação e Comentários do Aluno</div>
|
||||
<div class="comments-box">
|
||||
${av.comentarios ? av.comentarios.replace(/\n/g, '<br/>') : '<em>O aluno não adicionou comentários ou justificação à sua nota.</em>'}
|
||||
</div>
|
||||
|
||||
${footer}
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
const { uri } = await Print.printToFileAsync({ html: htmlContent });
|
||||
const safeName = r.aluno_nome.replace(/[^a-zA-Z0-9]/g, '_');
|
||||
const newFileUri = `${FileSystem.documentDirectory}Autoavaliacao_${safeName}.pdf`;
|
||||
await FileSystem.moveAsync({ from: uri, to: newFileUri });
|
||||
if (await Sharing.isAvailableAsync()) await Sharing.shareAsync(newFileUri, { mimeType: 'application/pdf', UTI: 'com.adobe.pdf' });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
Alert.alert('Erro', 'Não foi possível gerar a autoavaliação.');
|
||||
} finally {
|
||||
setGerandoDocumento(null);
|
||||
}
|
||||
};
|
||||
|
||||
// =====================================================================
|
||||
// 4. EXPORTAR O DIÁRIO DE BORDO INDIVIDUAL
|
||||
// =====================================================================
|
||||
const gerarSumariosPDF = async (r: any) => {
|
||||
setGerandoDocumento(r.id_estagio);
|
||||
try {
|
||||
@@ -413,8 +514,9 @@ export default function GestaoRelatorios() {
|
||||
linhasTabela += `<tr><td style="text-align: center;">${new Date(s.data).toLocaleDateString('pt-PT')}</td><td style="text-align: center;"><strong>${s.tipo}</strong></td><td>${s.sumario || '<em>Sem descrição.</em>'}</td></tr>`;
|
||||
});
|
||||
|
||||
const b64Escola = await getBase64Image(require('../../../assets/images/logoepvc3.png'));
|
||||
const b64App = await getBase64Image(require('../../../assets/images/logo_s/texto.png'));
|
||||
const { header, footer, watermark } = await gerarCabecalhoERodape();
|
||||
const tituloDiario = `<h1 style="color: #003049; margin: 0; font-size: 18px; font-family: sans-serif;">Diário de Bordo</h1><h2 style="color: #F18721; margin: 5px 0 0; font-size: 12px; font-family: sans-serif;">Formação em Contexto de Trabalho</h2>`;
|
||||
const headerCompleto = header.replace('', tituloDiario);
|
||||
|
||||
const htmlContent = `
|
||||
<!DOCTYPE html>
|
||||
@@ -423,30 +525,24 @@ export default function GestaoRelatorios() {
|
||||
<style>
|
||||
@page { size: A4; margin: 0; } html, body { height: 99%; overflow: hidden; }
|
||||
body { font-family: sans-serif; margin: 0; padding: 12mm 15mm; color: #1E293B; font-size: 10px; }
|
||||
.header-table { width: 100%; border-bottom: 2px solid #003049; padding-bottom: 5px; margin-bottom: 8px; }
|
||||
.header-center { text-align: center; } .header-center h1 { color: #003049; margin: 0; font-size: 15px; } .header-center h2 { color: #F18721; margin: 2px 0 0; font-size: 9px; }
|
||||
.info-table { width: 100%; border-collapse: collapse; margin-bottom: 10px; font-size: 9px; border: 1px solid #CBD5E1; }
|
||||
.info-table td { padding: 4px 6px; border: 1px solid #CBD5E1; } .info-table td.label { background-color: #F8FAFC; font-weight: bold; width: 15%; }
|
||||
.section-title { background-color: #003049; color: white; padding: 4px 8px; font-size: 9px; font-weight: bold; margin-bottom: 5px; }
|
||||
.eval-table { width: 100%; border-collapse: collapse; margin-bottom: 8px; font-size: 9px; } .eval-table th { background-color: #F1F5F9; padding: 4px; border: 1px solid #CBD5E1; } .eval-table td { border: 1px solid #CBD5E1; padding: 3px 8px; }
|
||||
.footer { text-align: center; padding-top: 5px; border-top: 1px solid #E2E8F0; position: absolute; bottom: 10mm; left: 0; right: 0; margin: 0 auto; width: calc(100% - 30mm); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<table class="header-table">
|
||||
<tr>
|
||||
<td style="width: 25%;"><img src="${b64Escola}" width="110" style="mix-blend-mode: multiply;" /></td>
|
||||
<td style="width: 50%;" class="header-center"><h1>Diário de Bordo</h1><h2>Formação em Contexto de Trabalho</h2></td>
|
||||
<td style="width: 25%; text-align: right;"><img src="${b64App}" width="110" style="mix-blend-mode: multiply;" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
${watermark}
|
||||
${headerCompleto}
|
||||
|
||||
<table class="info-table">
|
||||
<tr><td class="label">Estagiário:</td><td><strong>${r.aluno_nome}</strong></td><td class="label">Turma:</td><td>${r.turma}</td></tr>
|
||||
<tr><td class="label">Entidade:</td><td><strong>${r.empresa_nome}</strong></td><td class="label">Tutor(a):</td><td>${r.tutor_nome}</td></tr>
|
||||
</table>
|
||||
<div class="section-title">Registo de Atividades Diárias</div>
|
||||
<table class="eval-table"><tr><th style="width: 15%;">Data</th><th style="width: 20%;">Natureza</th><th style="width: 65%;">Sumário</th></tr>${linhasTabela}</table>
|
||||
<div class="footer"><div class="footer-text">Documento gerado digitalmente via plataforma Estágios+ EPVC. Data: ${new Date().toLocaleDateString('pt-PT')}</div></div>
|
||||
|
||||
${footer}
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
@@ -597,6 +693,7 @@ export default function GestaoRelatorios() {
|
||||
|
||||
<View style={[styles.divider, { backgroundColor: cores.borda }]} />
|
||||
|
||||
{/* 1. AVALIAÇÃO DA EMPRESA */}
|
||||
<View style={styles.moduloBox}>
|
||||
<View style={styles.moduloHeader}>
|
||||
<Text style={[styles.moduloTitle, { color: cores.texto }]}>1. Avaliação da Empresa</Text>
|
||||
@@ -607,36 +704,61 @@ export default function GestaoRelatorios() {
|
||||
)}
|
||||
</View>
|
||||
{r.pdf_empresa && (
|
||||
<TouchableOpacity style={styles.btnAcaoLigeiro} onPress={() => WebBrowser.openBrowserAsync(r.pdf_empresa)}>
|
||||
<Ionicons name="document-text" size={16} color={cores.azulMarinho} />
|
||||
<TouchableOpacity
|
||||
style={[styles.btnAcaoLigeiro, { borderColor: cores.borda, backgroundColor: cores.fundo }]}
|
||||
onPress={() => WebBrowser.openBrowserAsync(r.pdf_empresa)}
|
||||
>
|
||||
<Ionicons name="reader-outline" size={20} color={cores.azulMarinho} />
|
||||
<Text style={[styles.textoAcao, { color: cores.azulMarinho }]}>Ver Ficha de Avaliação</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* 2. AUTOAVALIAÇÃO DO ALUNO */}
|
||||
<View style={styles.moduloBox}>
|
||||
<View style={styles.moduloHeader}>
|
||||
<Text style={[styles.moduloTitle, { color: cores.texto }]}>2. Autoavaliação do Aluno</Text>
|
||||
<Text style={[styles.notaTag, { backgroundColor: cores.textoSecundario + '20', color: cores.textoSecundario }]}>Em Breve</Text>
|
||||
{r.autoavaliacao ? (
|
||||
<Text style={[styles.notaTag, { backgroundColor: cores.verdeAgua + '30', color: '#003049' }]}>Concluída</Text>
|
||||
) : (
|
||||
<Text style={[styles.notaTag, { backgroundColor: cores.laranja + '20', color: cores.laranja }]}>Pendente</Text>
|
||||
)}
|
||||
</View>
|
||||
{r.autoavaliacao && (
|
||||
<TouchableOpacity
|
||||
style={[styles.btnAcaoLigeiro, { borderColor: cores.borda, backgroundColor: cores.fundo }]}
|
||||
onPress={() => gerarAutoavaliacaoPDF(r)}
|
||||
disabled={gerandoDocumento === `auto_${r.id_estagio}`}
|
||||
>
|
||||
{gerandoDocumento === `auto_${r.id_estagio}` ? (
|
||||
<ActivityIndicator size="small" color={cores.azulMarinho} />
|
||||
) : (
|
||||
<>
|
||||
<Ionicons name="person-circle-outline" size={20} color={cores.azulMarinho} />
|
||||
<Text style={[styles.textoAcao, { color: cores.azulMarinho }]}>Exportar Autoavaliação (PDF)</Text>
|
||||
</>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* 3. DIÁRIO DE BORDO */}
|
||||
<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.notaTag, { backgroundColor: cores.verdeAgua + '30', color: '#003049' }]}>{r.horas_concluidas}h Registadas</Text>
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
style={[styles.btnAcaoLigeiro, { borderColor: cores.laranja }]}
|
||||
style={[styles.btnAcaoLigeiro, { borderColor: cores.borda, backgroundColor: cores.fundo }]}
|
||||
onPress={() => gerarSumariosPDF(r)}
|
||||
disabled={gerandoDocumento === r.id_estagio}
|
||||
>
|
||||
{gerandoDocumento === r.id_estagio ? (
|
||||
<ActivityIndicator size="small" color={cores.laranja} />
|
||||
<ActivityIndicator size="small" color={cores.azulMarinho} />
|
||||
) : (
|
||||
<>
|
||||
<Ionicons name="calendar-outline" size={16} color={cores.laranja} />
|
||||
<Text style={[styles.textoAcao, { color: cores.laranja }]}>Exportar Diário (PDF)</Text>
|
||||
<Ionicons name="journal-outline" size={20} color={cores.azulMarinho} />
|
||||
<Text style={[styles.textoAcao, { color: cores.azulMarinho }]}>Exportar Diário (PDF)</Text>
|
||||
</>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
@@ -688,6 +810,16 @@ const styles = StyleSheet.create({
|
||||
moduloTitle: { fontSize: 13, fontWeight: '800' },
|
||||
notaTag: { fontSize: 11, fontWeight: '900', paddingHorizontal: 8, paddingVertical: 4, borderRadius: 6 },
|
||||
|
||||
btnAcaoLigeiro: { flexDirection: 'row', alignItems: 'center', alignSelf: 'flex-start', paddingVertical: 6, paddingHorizontal: 12, borderRadius: 8, borderWidth: 1, marginTop: 10, gap: 6 },
|
||||
textoAcao: { fontSize: 12, fontWeight: '800' }
|
||||
btnAcaoLigeiro: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: '100%',
|
||||
paddingVertical: 12,
|
||||
borderRadius: 12,
|
||||
borderWidth: 1,
|
||||
marginTop: 12,
|
||||
gap: 8
|
||||
},
|
||||
textoAcao: { fontSize: 13, fontWeight: '800' }
|
||||
});
|
||||
Reference in New Issue
Block a user