From 967b238955046fe61c77cf711f1c09eb3f299bb3 Mon Sep 17 00:00:00 2001 From: Ricardo Gomes <230413@epvc.pt> Date: Mon, 15 Jun 2026 16:58:26 +0100 Subject: [PATCH] =?UTF-8?q?update=20-=20apresenta=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Empresas/fichaAvaliacao.tsx | 624 ++++++++++------------------ app/Professor/Alunos/relatorios.tsx | 35 +- 2 files changed, 245 insertions(+), 414 deletions(-) diff --git a/app/Empresas/fichaAvaliacao.tsx b/app/Empresas/fichaAvaliacao.tsx index 630b0ca..3cafc90 100644 --- a/app/Empresas/fichaAvaliacao.tsx +++ b/app/Empresas/fichaAvaliacao.tsx @@ -1,5 +1,4 @@ // app/Empresas/fichaAvaliacao.tsx -// Navegar para esta página passando: { estagio_id, aluno_nome, aluno_turma, n_escola } import { Ionicons } from '@expo/vector-icons'; import { Asset } from 'expo-asset'; @@ -25,44 +24,31 @@ import { SafeAreaView } from 'react-native-safe-area-context'; import { supabase } from '../../lib/supabase'; import { useTheme } from '../../themecontext'; -// ─── Perguntas de Avaliação ──────────────────────────────────────────────────── const PERGUNTAS = [ - { id: 'p1', categoria: 'Competências Técnicas', texto: 'Domínio das tarefas e conhecimentos técnicos exigidos.' }, - { id: 'p2', categoria: 'Competências Técnicas', texto: 'Capacidade de aprendizagem e adaptação a novas situações.' }, - { id: 'p3', categoria: 'Competências Técnicas', texto: 'Qualidade e rigor do trabalho realizado.' }, - { id: 'p4', categoria: 'Atitude Profissional', texto: 'Pontualidade, assiduidade e cumprimento de horários.' }, - { id: 'p5', categoria: 'Atitude Profissional', texto: 'Iniciativa, proatividade e autonomia nas tarefas.' }, - { id: 'p6', categoria: 'Atitude Profissional', texto: 'Responsabilidade e cumprimento das normas da empresa.' }, - { id: 'p7', categoria: 'Relacionamento', texto: 'Relacionamento com colegas e integração na equipa.' }, - { id: 'p8', categoria: 'Relacionamento', texto: 'Comunicação e postura com clientes e superiores.' }, - { id: 'p9', categoria: 'Desenvolvimento Pessoal', texto: 'Capacidade de gerir dificuldades e resolver problemas.' }, - { id: 'p10', categoria: 'Desenvolvimento Pessoal', texto: 'Evolução e progresso ao longo do período de estágio.' }, + { id: 'p1', texto: 'Compreensão das tarefas propostas.' }, + { id: 'p2', texto: 'Grau de colaboração na execução das tarefas propostas.' }, + { id: 'p3', texto: 'Planeamento e organização de atividades.' }, + { id: 'p4', texto: 'Selecção e/ou adaptação dos materiais de acordo com as atividades.' }, + { id: 'p5', texto: 'Cuidado com os materiais e equipamentos.' }, + { id: 'p6', texto: 'Aplicação de normas de higiene e segurança no local de trabalho.' }, + { id: 'p7', texto: 'Rentabilização do tempo.' }, + { id: 'p8', texto: 'Responsabilidade e autonomia no desenvolvimento e execução das atividades.' }, + { id: 'p9', texto: 'Assiduidade e pontualidade.' }, + { id: 'p10', texto: 'Apresentação e higiene pessoal.' }, + { id: 'p11', texto: 'Comunicação e relações interpessoais.' }, + { id: 'p12', texto: 'Capacidade de integração no grupo de trabalho.' }, + { id: 'p13', texto: 'Competências inerentes ao posto de trabalho.' }, + { id: 'p14', texto: 'Desenvolvimento de novas competências.' }, ]; -const ESCALA = [ - { valor: 1, label: 'Insatisfatório' }, - { valor: 2, label: 'A Melhorar' }, - { valor: 3, label: 'Satisfatório' }, - { valor: 4, label: 'Bom' }, +const COLUNAS_ESCALA = [ { valor: 5, label: 'Excelente' }, + { valor: 4, label: 'Muito Bom' }, + { valor: 3, label: 'Bom' }, + { valor: 2, label: 'Médio' }, + { valor: 1, label: 'Insuficiente' }, ]; -// ─── Helper: imagem em base64 (para incluir no PDF) ──────────────────────────── -const getBase64Image = async (imageModule: any) => { - try { - const asset = Asset.fromModule(imageModule); - await asset.downloadAsync(); - const fileUri = asset.localUri || asset.uri; - if (!fileUri) return ''; - const base64 = await FileSystem.readAsStringAsync(fileUri, { encoding: 'base64' }); - return `data:image/png;base64,${base64.replace(/(\r\n|\n|\r)/gm, '')}`; - } catch (e) { - console.error('Erro no Base64:', e); - return ''; - } -}; - -// ─── Componente Principal ────────────────────────────────────────────────────── export default function FichaAvaliacao() { const { isDarkMode } = useTheme(); const router = useRouter(); @@ -73,7 +59,6 @@ export default function FichaAvaliacao() { n_escola: string; }>(); - // Estados locais dinâmicos para contornar o envio incorreto de UUIDs por parâmetro const [alunoNome, setAlunoNome] = useState(params.aluno_nome || 'Aluno'); const [alunoTurma, setAlunoTurma] = useState(params.aluno_turma || '—'); const [numEscola, setNumEscola] = useState(params.n_escola || '—'); @@ -85,6 +70,10 @@ export default function FichaAvaliacao() { const [gerandoPDF, setGerandoPDF] = useState(false); const [loadingDados, setLoadingDados] = useState(true); + const [imgEscolaB64, setImgEscolaB64] = useState(''); + const [imgAppB64, setImgAppB64] = useState(''); + const [imgFundoB64, setImgFundoB64] = useState(''); + const cores = useMemo(() => ({ fundo: isDarkMode ? '#0A0A0A' : '#F4F7FA', card: isDarkMode ? '#161618' : '#FFFFFF', @@ -92,20 +81,39 @@ export default function FichaAvaliacao() { textoSecundario: isDarkMode ? '#94A3B8' : '#64748B', borda: isDarkMode ? '#2D2D2D' : '#E2E8F0', azulMarinho: '#003049', - verdeAgua: '#71BEB3', laranja: '#F18721', vermelho: '#EF4444', verde: '#22C55E', inputFundo: isDarkMode ? '#1E1E20' : '#F8FAFC', }), [isDarkMode]); - // ─── Carregar dados existentes ao entrar na página com Correção de Fallback ─── + const preCarregarImagem = async (modulo: any) => { + try { + const asset = Asset.fromModule(modulo); + await asset.downloadAsync(); + const uri = asset.localUri || asset.uri; + if (!uri) return ''; + const b64 = await FileSystem.readAsStringAsync(uri, { encoding: 'base64' }); + return `data:image/png;base64,${b64.replace(/[\r\n\x0B\x0C\x85]/g, '')}`; + } catch (e) { + console.warn(e); + return ''; + } + }; + + useEffect(() => { + async function carregarImagens() { + setImgEscolaB64(await preCarregarImagem(require('../../assets/images/logoepvc2.png'))); + setImgAppB64(await preCarregarImagem(require('../../assets/images/logo.png'))); + setImgFundoB64(await preCarregarImagem(require('../../assets/images/logoepvc.png'))); + } + carregarImagens(); + }, []); + useEffect(() => { async function carregarDadosCompletos() { if (!params.estagio_id) return; - try { - // 1. Procurar se já existe avaliação gravada const { data: avaliacao, error: errAvaliacao } = await supabase .from('avaliacoes_empresa') .select('respostas, nota_final, observacoes, aluno_nome, aluno_turma, aluno_n_escola') @@ -116,30 +124,19 @@ export default function FichaAvaliacao() { if (avaliacao) { if (avaliacao.respostas) setRespostas(avaliacao.respostas as Record); - if (avaliacao.nota_final !== undefined && avaliacao.nota_final !== null) { - const notaNum = Number(avaliacao.nota_final); - setNotaFinal(isNaN(notaNum) ? '' : notaNum.toString()); + setNotaFinal(Number(avaliacao.nota_final).toString()); } if (avaliacao.observacoes) setObservacoes(avaliacao.observacoes); - if (avaliacao.aluno_nome) setAlunoNome(avaliacao.aluno_nome); if (avaliacao.aluno_turma) setAlunoTurma(avaliacao.aluno_turma); if (avaliacao.aluno_n_escola) setNumEscola(avaliacao.aluno_n_escola.toString()); } - // 2. Fallback robusto olhando o ERD do Supabase: busca real do 'n_escola' e 'turma_curso' - // Executado se o parâmetro recebido estiver em falta ou for detetado um UUID longo (>15 caracteres) if (!params.aluno_turma || !params.n_escola || params.n_escola.length > 15) { const { data: estagio, error: errEstagio } = await supabase .from('estagios') - .select(` - alunos ( - nome, - turma_curso, - n_escola - ) - `) + .select(`alunos ( nome, turma_curso, n_escola )`) .eq('id', params.estagio_id) .maybeSingle(); @@ -153,26 +150,13 @@ export default function FichaAvaliacao() { } } } catch (e) { - console.error('Erro ao carregar dados da avaliação:', e); + console.error(e); } finally { setLoadingDados(false); } } - carregarDadosCompletos(); - }, [params.estagio_id, params.n_escola, params.aluno_turma]); - - const corEscala = (valor: number, selecionado: boolean) => { - if (!selecionado) return { bg: cores.inputFundo, borda: cores.borda, texto: cores.textoSecundario }; - const mapa: Record = { - 1: { bg: '#FEE2E2', borda: '#EF4444', texto: '#B91C1C' }, - 2: { bg: '#FEF3C7', borda: '#F59E0B', texto: '#B45309' }, - 3: { bg: '#FEF9C3', borda: '#EAB308', texto: '#854D0E' }, - 4: { bg: '#DCFCE7', borda: '#22C55E', texto: '#15803D' }, - 5: { bg: '#D1FAE5', borda: '#10B981', texto: '#065F46' }, - }; - return mapa[valor]; - }; + }, [params.estagio_id]); const notaValida = useMemo(() => { if (!notaFinal.trim()) return false; @@ -183,25 +167,14 @@ export default function FichaAvaliacao() { const todasRespondidas = PERGUNTAS.every(p => respostas[p.id] !== undefined); const podeSometer = todasRespondidas && notaValida; - const categorias = useMemo(() => { - const mapa: Record = {}; - PERGUNTAS.forEach(p => { - if (!mapa[p.categoria]) mapa[p.categoria] = []; - mapa[p.categoria].push(p); - }); - return mapa; - }, []); - - // ─── Submeter Avaliação ────────────────────────────────────────────────────── - const handleSubmit = async () => { + const handleSubmit = () => { if (!podeSometer) { - Alert.alert('Atenção', 'Preenche todas as questões e insere uma nota final válida (0–20).'); + Alert.alert('Atenção', 'Responde às 14 perguntas e insere a nota final (0-20).'); return; } - Alert.alert( 'Salvar Avaliação', - `Confirmas a avaliação de ${alunoNome} com nota final de ${notaFinal} valores?`, + `Confirmas a gravação da ficha com nota final de ${notaFinal} valores?`, [ { text: 'Cancelar', style: 'cancel' }, { @@ -209,16 +182,14 @@ export default function FichaAvaliacao() { onPress: async () => { setSubmitting(true); try { - const notaNum = parseFloat(notaFinal.replace(',', '.')); - const { error } = await supabase .from('avaliacoes_empresa') .upsert({ estagio_id: params.estagio_id, aluno_nome: alunoNome, - aluno_n_escola: numEscola && numEscola !== '—' ? parseInt(numEscola, 10) : null, + aluno_n_escola: numEscola !== '—' ? parseInt(numEscola, 10) : null, aluno_turma: alunoTurma, - nota_final: notaNum, + nota_final: parseFloat(notaFinal.replace(',', '.')), respostas: respostas, observacoes: observacoes.trim() || null, atualizado_em: new Date().toISOString(), @@ -226,19 +197,10 @@ export default function FichaAvaliacao() { if (error) throw error; - await supabase - .from('estagios') - .update({ estado: 'Avaliado' }) - .eq('id', params.estagio_id); - - Alert.alert( - 'Avaliação Guardada!', - 'A ficha foi gravada com sucesso. O professor poderá agora consultá-la.', - [{ text: 'OK', onPress: () => router.back() }] - ); + await supabase.from('estagios').update({ estado: 'Avaliado' }).eq('id', params.estagio_id); + Alert.alert('Sucesso', 'Ficha gravada com sucesso.', [{ text: 'OK', onPress: () => router.back() }]); } catch (e) { - console.error(e); - Alert.alert('Erro', 'Não foi possível guardar a avaliação. Tenta novamente.'); + Alert.alert('Erro', 'Erro ao guardar dados.'); } finally { setSubmitting(false); } @@ -248,34 +210,33 @@ export default function FichaAvaliacao() { ); }; - // ─── Gerar PDF da Avaliação ────────────────────────────────────────────────── - const gerarPDFAvaliacao = async () => { +const gerarPDFAvaliacao = async () => { if (!podeSometer) { - Alert.alert('Atenção', 'Preenche todas as questões e insere uma nota válida (0–20) antes de gerar o PDF.'); + Alert.alert('Aviso', 'Preenche todos os campos antes de gerar o PDF.'); return; } setGerandoPDF(true); try { - const b64Escola = await getBase64Image(require('../../assets/images/logoepvc3.png')); - const b64App = await getBase64Image(require('../../assets/images/logo_s/texto.png')); - const b64Final = await getBase64Image(require('../../assets/images/logoepvc.png')); + let linhasTabelaHtml = ''; - let linhasPerguntas = ''; - Object.entries(categorias).forEach(([categoria, perguntas]) => { - linhasPerguntas += `${categoria}`; - perguntas.forEach((p) => { - const valor = respostas[p.id]; - const label = ESCALA.find(e => e.valor === valor)?.label ?? '—'; - linhasPerguntas += ` - - ${p.texto} - ${valor} · ${label} - `; - }); + PERGUNTAS.forEach((p, idx) => { + const valor = respostas[p.id]; + linhasTabelaHtml += ` + + ${idx + 1} + ${p.texto} + ${valor === 5 ? 'X' : ''} + ${valor === 4 ? 'X' : ''} + ${valor === 3 ? 'X' : ''} + ${valor === 2 ? 'X' : ''} + ${valor === 1 ? 'X' : ''} + + `; }); - const notaNum = parseFloat(notaFinal.replace(',', '.')); - const positiva = notaNum >= 9.5; + const dataHoje = new Date().toLocaleDateString('pt-PT'); + // Adicionamos Date.now() para garantir que o nome é sempre único e o sistema não usa cache + const timestamp = Date.now(); const html = ` @@ -283,96 +244,85 @@ export default function FichaAvaliacao() { -
- - - + + + + + -
-

Ficha de Personalidade e Avaliação

-

Formação em Contexto de Trabalho (FCT)

+
EPVC.FI.08/1Aprovado: 01/09/2016
+

Escola Profissional de Vila do Conde

+

CURSO PROFISSIONAL TÉCNICO/A DE INFORMÁTICA DE GESTÃO

+

FICHA DE AVALIAÇÃO FINAL DO ESTÁGIO

- - - - +
+ Estagiário: ${alunoNome}     Ano Letivo: 2025/2026

+ Instituição: __________________________________________________________________________ +
+ +
Estagiário(a):${alunoNome}
Nº Escola / Turma:${numEscola} • ${alunoTurma}
Data de Emissão:${new Date().toLocaleDateString('pt-PT')}
+ + + + + + + + + + + + + ${linhasTabelaHtml} +
#Parâmetros de Avaliação54321
-
Avaliação Qualitativa (1 = Insatisfatório • 5 = Excelente)
- ${linhasPerguntas}
+

+ Pelas competências demonstradas durante o estágio de Formação em Contexto de Trabalho, propomos que seja atribuída ao estagiário a classificação de ${notaFinal} valores. +

-
Classificação Final
-
-
${notaFinal}
-
Valores • ${positiva ? 'APROVADO' : 'REPROVADO'}
-
+

Observações:

+
${observacoes}
-
Observações da Entidade Acolhedora
-
${observacoes.trim() ? observacoes.replace(/\n/g, '
') : 'Sem observações adicionais registadas.'}
+

+ Escola Profissional de Vila do Conde, ${dataHoje} +

- + + + + + +
O Formador acompanhante
O Monitor de estágio
`; const { uri } = await Print.printToFileAsync({ html }); - const safeName = (alunoNome || 'aluno').replace(/[^a-zA-Z0-9]/g, '_'); - const newUri = `${FileSystem.documentDirectory}Avaliacao_Empresa_${safeName}.pdf`; - await FileSystem.moveAsync({ from: uri, to: newUri }); - if (await Sharing.isAvailableAsync()) { - await Sharing.shareAsync(newUri, { mimeType: 'application/pdf', UTI: 'com.adobe.pdf' }); - } + + // O truque está aqui: incluímos o timestamp no nome do ficheiro final + const safeName = alunoNome.replace(/\s/g, '_'); + const finalPath = `${FileSystem.documentDirectory}Avaliacao_${safeName}_${timestamp}.pdf`; + + await FileSystem.moveAsync({ from: uri, to: finalPath }); + await Sharing.shareAsync(finalPath); } catch (e) { - console.error(e); - Alert.alert('Erro', 'Não foi possível gerar o PDF da avaliação.'); + Alert.alert('Erro', 'Falha ao gerar o documento.'); } finally { setGerandoPDF(false); } @@ -380,9 +330,8 @@ export default function FichaAvaliacao() { if (loadingDados) { return ( - + - A carregar avaliação existente... ); } @@ -392,264 +341,139 @@ export default function FichaAvaliacao() { - router.back()} style={s.btnBack}> + router.back()} style={s.btnVoltar}> - + Ficha de Avaliação - Formação em Contexto de Trabalho + Preenchimento da Grelha Oficial - + - - - {alunoNome?.charAt(0) ?? '?'} + + + {alunoNome.charAt(0).toUpperCase()} - - {alunoNome} - Nº {numEscola} · {alunoTurma} - - - FCT + + {alunoNome} + Nº {numEscola} • Turma {alunoTurma} - - - - Avalia cada item de 1 (Insatisfatório) a 5 (Excelente). No final, atribui uma nota de 0 a 20. - - + {PERGUNTAS.map((p, index) => { + const atual = respostas[p.id]; + return ( + + + {index + 1}. {p.texto} + - {Object.entries(categorias).map(([categoria, perguntas], catIdx) => ( - - - {categoria} + + {COLUNAS_ESCALA.map(col => { + const ativo = atual === col.valor; + return ( + setRespostas(pvs => ({ ...pvs, [p.id]: col.valor }))} + > + {col.valor} + {col.label} + + ); + })} + + ); + })} - {perguntas.map((pergunta) => { - const selecionado = respostas[pergunta.id]; - const globalIdx = PERGUNTAS.findIndex(p => p.id === pergunta.id) + 1; - - return ( - - - - {globalIdx} - - {pergunta.texto} - - - - {ESCALA.map(e => { - const esteSelecionado = selecionado === e.valor; - const cor = corEscala(e.valor, esteSelecionado); - return ( - setRespostas(prev => ({ ...prev, [pergunta.id]: e.valor }))} - activeOpacity={0.75} - > - {e.valor} - - ); - })} - - - {selecionado && ( - - {ESCALA.find(e => e.valor === selecionado)?.label} - - )} - - ); - })} - - ))} - - - Nota Final - - Insira a nota obtida na escala de 0 a 20 valores. - - - + + Classificação Final (0 a 20) + - valores - - {notaFinal !== '' && ( - = 9.5 ? '#DCFCE7' : '#FEE2E2') : '#F3F4F6' } - ]}> - = 9.5 ? '#15803D' : '#B91C1C') : cores.textoSecundario } - ]}> - {notaValida - ? parseFloat(notaFinal.replace(',', '.')) >= 9.5 ? '✓ Positiva' : '✗ Negativa' - : 'Inválido'} - - - )} + valores - - Observações - Opcional — comentários adicionais sobre o desempenho. + + Observações Adicionais - - - - {Object.keys(respostas).length} de {PERGUNTAS.length} questões respondidas - - - {todasRespondidas ? '✓ Completo' : 'Incompleto'} - - - - - - - - + - {submitting ? ( - - ) : ( - <> - - - Salvar Avaliação - - - )} + {submitting ? : Gravar Avaliação} - {gerandoPDF ? ( - - ) : ( - <> - - - Exportar PDF - - - )} + {gerandoPDF ? : Gerar PDF Oficial} - ); } -// ─── Estilos Customizados ────────────────────────────────────────────────────── const s = StyleSheet.create({ - safe: { flex: 1 }, - header: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 20, paddingTop: 15, paddingBottom: 12, gap: 14 }, - btnBack: { padding: 4 }, - headerTitle: { fontSize: 18, fontWeight: '900' }, - headerSub: { fontSize: 12, fontWeight: '500', marginTop: 1 }, - scroll: { paddingHorizontal: 16, paddingBottom: 20 }, - - loadingContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', gap: 12 }, - loadingText: { fontSize: 14, fontWeight: '600' }, - - alunoCard: { flexDirection: 'row', alignItems: 'center', borderRadius: 18, padding: 18, marginBottom: 16 }, - alunoAvatar: { width: 46, height: 46, borderRadius: 23, backgroundColor: 'rgba(255,255,255,0.2)', justifyContent: 'center', alignItems: 'center' }, - alunoAvatarLetra: { color: '#fff', fontSize: 20, fontWeight: '900' }, - alunoNome: { color: '#fff', fontSize: 16, fontWeight: '900' }, - alunoMeta: { color: 'rgba(255,255,255,0.7)', fontSize: 12, fontWeight: '600', marginTop: 3 }, - badgeFCT: { paddingHorizontal: 10, paddingVertical: 5, borderRadius: 8 }, - badgeFCTText: { color: '#fff', fontSize: 11, fontWeight: '900', letterSpacing: 1 }, - - instrucoes: { flexDirection: 'row', alignItems: 'flex-start', borderRadius: 12, borderWidth: 1, padding: 12, marginBottom: 20 }, - instrucoesTxt: { flex: 1, fontSize: 13, lineHeight: 19 }, - - catHeader: { borderRadius: 8, paddingVertical: 7, paddingHorizontal: 14, marginBottom: 10, marginTop: 6 }, - catHeaderTxt: { color: '#fff', fontSize: 11, fontWeight: '800', letterSpacing: 1, textTransform: 'uppercase' }, - - perguntaCard: { borderRadius: 16, borderWidth: 1, padding: 16, marginBottom: 10 }, - perguntaTop: { flexDirection: 'row', alignItems: 'flex-start', marginBottom: 14, gap: 12 }, - numCircle: { width: 28, height: 28, borderRadius: 14, justifyContent: 'center', alignItems: 'center', flexShrink: 0, marginTop: 1 }, - numTxt: { fontSize: 13, fontWeight: '900' }, - perguntaTxt: { flex: 1, fontSize: 14, fontWeight: '600', lineHeight: 20 }, - - escalaRow: { flexDirection: 'row', gap: 8, justifyContent: 'space-between' }, - escalaBotao: { flex: 1, aspectRatio: 1, borderRadius: 12, borderWidth: 1.5, justifyContent: 'center', alignItems: 'center', maxWidth: 54 }, - escalaNum: { fontSize: 17, fontWeight: '900' }, - escalaLabel: { fontSize: 11, fontWeight: '600', marginTop: 8, textAlign: 'center' }, - - secaoFinal: { borderRadius: 18, borderWidth: 1, padding: 18, marginBottom: 14 }, - secaoTitulo: { fontSize: 16, fontWeight: '900', marginBottom: 4 }, - secaoDesc: { fontSize: 13, marginBottom: 14, lineHeight: 18 }, - notaRow: { flexDirection: 'row', alignItems: 'center', gap: 12 }, - notaInput: { borderWidth: 2, borderRadius: 12, paddingHorizontal: 16, paddingVertical: 12, fontSize: 22, fontWeight: '900', width: 95, textAlign: 'center' }, - notaSufixo: { fontSize: 15, fontWeight: '700' }, - notaBadge: { paddingHorizontal: 10, paddingVertical: 6, borderRadius: 8 }, - notaBadgeTxt: { fontSize: 13, fontWeight: '800' }, - - obsInput: { borderWidth: 1.5, borderRadius: 12, padding: 14, fontSize: 14, minHeight: 100, lineHeight: 21 }, - - progressoBox: { borderRadius: 14, borderWidth: 1, padding: 14, marginBottom: 16 }, - progressoRow: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 8 }, - progressoLabel: { fontSize: 12, fontWeight: '600' }, - progressoBar: { height: 6, borderRadius: 3, overflow: 'hidden' }, - progressoFill: { height: '100%', borderRadius: 3 }, - - botoesRow: { flexDirection: 'row', gap: 10, marginTop: 4 }, - btnSalvar: { flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 8, paddingVertical: 16, borderRadius: 18 }, - btnPDF: { flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 8, paddingVertical: 16, borderRadius: 18 }, - btnTxt: { fontSize: 14, fontWeight: '900' }, + safe: { flex: 1 }, + loadingBox: { flex: 1, justifyContent: 'center', alignItems: 'center' }, + header: { flexDirection: 'row', alignItems: 'center', padding: 16, gap: 12 }, + btnVoltar: { padding: 4 }, + headerTitle: { fontSize: 18, fontWeight: 'bold' }, + headerSub: { fontSize: 12, marginTop: 2 }, + container: { padding: 16, gap: 14, paddingBottom: 40 }, + cardAluno: { flexDirection: 'row', alignItems: 'center', padding: 16, borderRadius: 12 }, + avatar: { width: 40, height: 40, borderRadius: 20, backgroundColor: 'rgba(255,255,255,0.2)', justifyContent: 'center', alignItems: 'center' }, + avatarLetra: { color: '#fff', fontSize: 18, fontWeight: 'bold' }, + nomeAluno: { color: '#fff', fontSize: 15, fontWeight: 'bold' }, + metaAluno: { color: 'rgba(255,255,255,0.7)', fontSize: 12, marginTop: 2 }, + cardPergunta: { padding: 16, borderRadius: 12, borderWidth: 1, gap: 12 }, + txtPergunta: { fontSize: 14, fontWeight: '500', lineHeight: 20 }, + rowEscala: { flexDirection: 'row', gap: 6 }, + btnEscala: { flex: 1, paddingVertical: 8, borderRadius: 8, borderWidth: 1, alignItems: 'center', justifyContent: 'center' }, + txtEscalaNum: { fontSize: 15, fontWeight: 'bold' }, + txtEscalaLabel: { fontSize: 8, marginTop: 2, textAlign: 'center' }, + cardSeccao: { padding: 16, borderRadius: 12, borderWidth: 1 }, + tituloSeccao: { fontSize: 14, fontWeight: 'bold', marginBottom: 10 }, + rowNota: { flexDirection: 'row', alignItems: 'center', gap: 12 }, + inputNota: { width: 80, padding: 12, borderRadius: 8, borderWidth: 1, fontSize: 16, fontWeight: 'bold', textAlign: 'center' }, + sufixoValores: { fontSize: 14, fontWeight: '500' }, + inputObs: { minHeight: 80, borderRadius: 8, borderWidth: 1, padding: 12, fontSize: 14, textAlignVertical: 'top' }, + botoesContainer: { flexDirection: 'row', gap: 10, marginTop: 10 }, + btnAcao: { flex: 1, height: 50, borderRadius: 10, justifyContent: 'center', alignItems: 'center' }, + btnAcaoTxt: { color: '#fff', fontSize: 14, fontWeight: 'bold' }, }); \ No newline at end of file diff --git a/app/Professor/Alunos/relatorios.tsx b/app/Professor/Alunos/relatorios.tsx index 14c0870..281eb97 100644 --- a/app/Professor/Alunos/relatorios.tsx +++ b/app/Professor/Alunos/relatorios.tsx @@ -161,24 +161,31 @@ export default function GestaoRelatorios() { }, [relatorios, turmaAtiva]); // ─── Cabeçalho/Rodapé/Marca de água partilhados ──────────────────────────── - const gerarCabecalhoERodape = async () => { -const logoEscola = require('../../assets/logoepvc3.png'); -const logoEstagiosPlus = require('../../assets/logo_estagios_plus.png'); -const logoRodape = require('../../assets/logoepvc.png'); + +const gerarCabecalhoERodape = async () => { + // CORREÇÃO: Importamos o módulo completo e guardamos numa constante limpa + const reactNative = require('react-native'); + const RNImage = reactNative.Image; + + // Agora a resolução dos caminhos vai funcionar sem erros de sintaxe + const logoEscolaUri = RNImage.resolveAssetSource(require('../../../assets/images/logoepvc2.png')).uri; + const logoAppUri = RNImage.resolveAssetSource(require('../../../assets/images/logo.png')).uri; + const logoRodapeUri = RNImage.resolveAssetSource(require('../../../assets/images/logoepvc.png')).uri; const header = ` -
+
- - -
- + + + + -
Plataforma Oficial
- + +
+
@@ -190,8 +197,8 @@ const logoRodape = require('../../assets/logoepvc.png'); -
Documento gerado automaticamente pela Plataforma Estágios+ - + +
@@ -200,7 +207,7 @@ const logoRodape = require('../../assets/logoepvc.png'); const watermark = `
- +
`;