= {};
PERGUNTAS.forEach(p => {
@@ -105,6 +192,7 @@ export default function FichaAvaliacao() {
return mapa;
}, []);
+ // ─── Submeter Avaliação ──────────────────────────────────────────────────────
const handleSubmit = async () => {
if (!podeSometer) {
Alert.alert('Atenção', 'Preenche todas as questões e insere uma nota final válida (0–20).');
@@ -112,8 +200,8 @@ export default function FichaAvaliacao() {
}
Alert.alert(
- 'Submeter Avaliação',
- `Confirmas a avaliação de ${params.aluno_nome} com nota final ${notaFinal} valores?`,
+ 'Salvar Avaliação',
+ `Confirmas a avaliação de ${alunoNome} com nota final de ${notaFinal} valores?`,
[
{ text: 'Cancelar', style: 'cancel' },
{
@@ -123,27 +211,34 @@ export default function FichaAvaliacao() {
try {
const notaNum = parseFloat(notaFinal.replace(',', '.'));
- // Guarda as respostas individuais + nota + observações
const { error } = await supabase
- .from('estagios')
- .update({
+ .from('avaliacoes_empresa')
+ .upsert({
+ estagio_id: params.estagio_id,
+ aluno_nome: alunoNome,
+ aluno_n_escola: numEscola && numEscola !== '—' ? parseInt(numEscola, 10) : null,
+ aluno_turma: alunoTurma,
nota_final: notaNum,
- avaliacao_observacoes: observacoes.trim() || null,
- avaliacao_respostas: respostas, // jsonb no Supabase
- avaliacao_data: new Date().toISOString(),
- })
- .eq('id', params.estagio_id);
+ respostas: respostas,
+ observacoes: observacoes.trim() || null,
+ atualizado_em: new Date().toISOString(),
+ }, { onConflict: 'estagio_id' });
if (error) throw error;
+ await supabase
+ .from('estagios')
+ .update({ estado: 'Avaliado' })
+ .eq('id', params.estagio_id);
+
Alert.alert(
- 'Avaliação Submetida!',
- 'A ficha foi gravada com sucesso. O professor será notificado.',
+ 'Avaliação Guardada!',
+ 'A ficha foi gravada com sucesso. O professor poderá agora consultá-la.',
[{ text: 'OK', onPress: () => router.back() }]
);
} catch (e) {
console.error(e);
- Alert.alert('Erro', 'Não foi possível submeter a avaliação. Tenta novamente.');
+ Alert.alert('Erro', 'Não foi possível guardar a avaliação. Tenta novamente.');
} finally {
setSubmitting(false);
}
@@ -153,12 +248,149 @@ export default function FichaAvaliacao() {
);
};
- // ─── Render ────────────────────────────────────────────────────────────────
+ // ─── Gerar PDF da Avaliação ──────────────────────────────────────────────────
+ 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.');
+ 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 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} |
+
`;
+ });
+ });
+
+ const notaNum = parseFloat(notaFinal.replace(',', '.'));
+ const positiva = notaNum >= 9.5;
+
+ const html = `
+
+
+
+
+
+
+
+
+
+
+
+
+ | Estagiário(a): | ${alunoNome} |
+ | Nº Escola / Turma: | ${numEscola} • ${alunoTurma} |
+ | Data de Emissão: | ${new Date().toLocaleDateString('pt-PT')} |
+
+
+ Avaliação Qualitativa (1 = Insatisfatório • 5 = Excelente)
+
+
+ Classificação Final
+
+
${notaFinal}
+
Valores • ${positiva ? 'APROVADO' : 'REPROVADO'}
+
+
+ Observações da Entidade Acolhedora
+ ${observacoes.trim() ? observacoes.replace(/\n/g, '
') : 'Sem observações adicionais registadas.'}
+
+
+
+ `;
+
+ 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' });
+ }
+ } catch (e) {
+ console.error(e);
+ Alert.alert('Erro', 'Não foi possível gerar o PDF da avaliação.');
+ } finally {
+ setGerandoPDF(false);
+ }
+ };
+
+ if (loadingDados) {
+ return (
+
+
+ A carregar avaliação existente...
+
+ );
+ }
+
return (
-
+
- {/* Header */}
router.back()} style={s.btnBack}>
@@ -172,21 +404,19 @@ export default function FichaAvaliacao() {
- {/* Cartão do Aluno */}
- {params.aluno_nome?.charAt(0) ?? '?'}
+ {alunoNome?.charAt(0) ?? '?'}
- {params.aluno_nome}
- Nº {params.n_escola} · {params.aluno_turma}
+ {alunoNome}
+ Nº {numEscola} · {alunoTurma}
FCT
- {/* Instruções */}
@@ -194,21 +424,18 @@ export default function FichaAvaliacao() {
- {/* Perguntas agrupadas por categoria */}
{Object.entries(categorias).map(([categoria, perguntas], catIdx) => (
- {/* Cabeçalho de categoria */}
{categoria}
- {perguntas.map((pergunta, idx) => {
+ {perguntas.map((pergunta) => {
const selecionado = respostas[pergunta.id];
const globalIdx = PERGUNTAS.findIndex(p => p.id === pergunta.id) + 1;
return (
- {/* Número + Texto */}
{globalIdx}
@@ -216,7 +443,6 @@ export default function FichaAvaliacao() {
{pergunta.texto}
- {/* Escala */}
{ESCALA.map(e => {
const esteSelecionado = selecionado === e.valor;
@@ -234,7 +460,6 @@ export default function FichaAvaliacao() {
})}
- {/* Label do selecionado */}
{selecionado && (
{ESCALA.find(e => e.valor === selecionado)?.label}
@@ -246,11 +471,10 @@ export default function FichaAvaliacao() {
))}
- {/* Nota Final */}
Nota Final
- Atribui uma nota de 0 a 20 valores. Abaixo de 9,5 é negativa.
+ Insira a nota obtida na escala de 0 a 20 valores.
@@ -263,14 +487,14 @@ export default function FichaAvaliacao() {
color: cores.texto,
},
]}
- placeholder="Ex: 16"
+ placeholder="0-20"
placeholderTextColor={cores.textoSecundario}
keyboardType="decimal-pad"
value={notaFinal}
onChangeText={setNotaFinal}
maxLength={4}
/>
- / 20 valores
+ valores
{notaFinal !== '' && (
- {/* Observações */}
Observações
Opcional — comentários adicionais sobre o desempenho.
@@ -306,7 +529,6 @@ export default function FichaAvaliacao() {
/>
- {/* Progresso */}
@@ -327,27 +549,43 @@ export default function FichaAvaliacao() {
- {/* Botão Submeter */}
-
- {submitting ? (
-
- ) : (
- <>
-
-
- Submeter Avaliação
-
- >
- )}
-
+
+
+ {submitting ? (
+
+ ) : (
+ <>
+
+
+ Salvar Avaliação
+
+ >
+ )}
+
+
+
+ {gerandoPDF ? (
+
+ ) : (
+ <>
+
+
+ Exportar PDF
+
+ >
+ )}
+
+
@@ -356,7 +594,7 @@ export default function FichaAvaliacao() {
);
}
-// ─── Estilos ──────────────────────────────────────────────────────────────────
+// ─── Estilos Customizados ──────────────────────────────────────────────────────
const s = StyleSheet.create({
safe: { flex: 1 },
header: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 20, paddingTop: 15, paddingBottom: 12, gap: 14 },
@@ -365,7 +603,9 @@ const s = StyleSheet.create({
headerSub: { fontSize: 12, fontWeight: '500', marginTop: 1 },
scroll: { paddingHorizontal: 16, paddingBottom: 20 },
- // Aluno card
+ 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' },
@@ -374,48 +614,42 @@ const s = StyleSheet.create({
badgeFCT: { paddingHorizontal: 10, paddingVertical: 5, borderRadius: 8 },
badgeFCTText: { color: '#fff', fontSize: 11, fontWeight: '900', letterSpacing: 1 },
- // Instruções
instrucoes: { flexDirection: 'row', alignItems: 'flex-start', borderRadius: 12, borderWidth: 1, padding: 12, marginBottom: 20 },
instrucoesTxt: { flex: 1, fontSize: 13, lineHeight: 19 },
- // Categoria
catHeader: { borderRadius: 8, paddingVertical: 7, paddingHorizontal: 14, marginBottom: 10, marginTop: 6 },
catHeaderTxt: { color: '#fff', fontSize: 11, fontWeight: '800', letterSpacing: 1, textTransform: 'uppercase' },
- // Pergunta
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 },
- // Escala
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' },
- // Nota final
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: 90, textAlign: 'center' },
+ 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' },
- // Observações
obsInput: { borderWidth: 1.5, borderRadius: 12, padding: 14, fontSize: 14, minHeight: 100, lineHeight: 21 },
- // Progresso
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 },
- // Botão submeter
- btnSubmeter: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 10, paddingVertical: 17, borderRadius: 18, marginTop: 4 },
- btnSubmeterTxt: { fontSize: 16, fontWeight: '900' },
+ 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' },
});
\ No newline at end of file
diff --git a/app/Professor/Alunos/relatorios.tsx b/app/Professor/Alunos/relatorios.tsx
index 3ab4f78..14c0870 100644
--- a/app/Professor/Alunos/relatorios.tsx
+++ b/app/Professor/Alunos/relatorios.tsx
@@ -1,4 +1,3 @@
-// app/Professor/Alunos/relatorios.tsx
import { Ionicons } from '@expo/vector-icons';
import { useFocusEffect } from '@react-navigation/native';
import { Asset } from 'expo-asset';
@@ -6,7 +5,6 @@ import * as FileSystem from 'expo-file-system/legacy';
import * as Print from 'expo-print';
import { useRouter } from 'expo-router';
import * as Sharing from 'expo-sharing';
-import * as WebBrowser from 'expo-web-browser';
import { useCallback, useMemo, useState } from 'react';
import {
ActivityIndicator,
@@ -24,6 +22,28 @@ import {
import { supabase } from '../../../lib/supabase';
import { useTheme } from '../../../themecontext';
+// ─── Perguntas/Escala (idênticas às usadas pela empresa) ──────────────────────
+const PERGUNTAS_EMP = [
+ { 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.' },
+];
+
+const ESCALA_EMP = [
+ { valor: 1, label: 'Insatisfatório' },
+ { valor: 2, label: 'A Melhorar' },
+ { valor: 3, label: 'Satisfatório' },
+ { valor: 4, label: 'Bom' },
+ { valor: 5, label: 'Excelente' },
+];
+
export default function GestaoRelatorios() {
const { isDarkMode } = useTheme();
const router = useRouter();
@@ -56,7 +76,7 @@ export default function GestaoRelatorios() {
return `data:image/png;base64,${base64.replace(/(\r\n|\n|\r)/gm, "")}`;
} catch (e) {
console.error("Erro no Base64:", e);
- return "";
+ return "";
}
};
@@ -64,7 +84,7 @@ export default function GestaoRelatorios() {
if (!texto || texto === 'N/A') return 'N/A';
return texto.charAt(0).toUpperCase() + texto.slice(1).toLowerCase();
};
-
+
const fetchRelatorios = async (isManualRefresh = false) => {
if (!isManualRefresh) setLoading(true);
try {
@@ -72,9 +92,10 @@ export default function GestaoRelatorios() {
.from('estagios')
.select(`
id, horas_totais, horas_concluidas, horas_diarias, nota_final, avaliacao_url, data_inicio, data_fim,
- alunos (id, nome, turma_curso, n_escola, ano),
+ alunos (id, nome, turma_curso, n_escola, ano),
empresas (nome, tutor_nome),
- autoavaliacoes (id, assiduidade, responsabilidade, relacionamento, conhecimentos, nota_desejada, comentarios, criado_em)
+ autoavaliacoes (id, assiduidade, responsabilidade, relacionamento, conhecimentos, nota_desejada, comentarios, criado_em),
+ avaliacoes_empresa (respostas, observacoes, atualizado_em)
`)
.order('data_inicio', { ascending: false });
@@ -84,7 +105,8 @@ export default function GestaoRelatorios() {
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 avaliacaoEmp = Array.isArray(estagio.avaliacoes_empresa) ? estagio.avaliacoes_empresa[0] : estagio.avaliacoes_empresa;
+
const nomeCursoFormatado = formatarTexto(aluno?.turma_curso);
const anoAluno = aluno?.ano || 10;
const turmaCompleta = `${anoAluno}º ${nomeCursoFormatado}`;
@@ -95,7 +117,7 @@ export default function GestaoRelatorios() {
aluno_nome: aluno?.nome || 'Desconhecido',
curso: nomeCursoFormatado,
ano: anoAluno,
- turma: turmaCompleta,
+ turma: turmaCompleta,
n_escola: aluno?.n_escola || '--',
empresa_nome: empresa?.nome || 'N/A',
tutor_nome: empresa?.tutor_nome || 'N/A',
@@ -105,8 +127,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,
- autoavaliacao: autoavaliacao || null
+ autoavaliacao: autoavaliacao || null,
+ avaliacao_empresa_detalhes: avaliacaoEmp || null
};
}) || [];
@@ -124,16 +146,12 @@ export default function GestaoRelatorios() {
const cursosAgrupados = useMemo(() => {
const grupos: Record = {};
-
relatorios.forEach(r => {
- if (!grupos[r.curso]) {
- grupos[r.curso] = { count10: 0, count11: 0, count12: 0 };
- }
+ if (!grupos[r.curso]) grupos[r.curso] = { count10: 0, count11: 0, count12: 0 };
if (r.ano === 10 || r.ano === 1) grupos[r.curso].count10++;
else if (r.ano === 11 || r.ano === 2) grupos[r.curso].count11++;
else if (r.ano === 12 || r.ano === 3) grupos[r.curso].count12++;
});
-
return Object.keys(grupos).map(curso => ({ curso, ...grupos[curso] })).sort((a, b) => a.curso.localeCompare(b.curso));
}, [relatorios]);
@@ -142,45 +160,52 @@ export default function GestaoRelatorios() {
return relatorios.filter(r => r.turma === turmaAtiva);
}, [relatorios, turmaAtiva]);
- // =====================================================================
- // GERAÇÃO DO CABEÇALHO E RODAPÉ COM OS SÍMBOLOS E MARCA DE ÁGUA
- // =====================================================================
+ // ─── Cabeçalho/Rodapé/Marca de água partilhados ────────────────────────────
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'));
+const logoEscola = require('../../assets/logoepvc3.png');
+const logoEstagiosPlus = require('../../assets/logo_estagios_plus.png');
+const logoRodape = require('../../assets/logoepvc.png');
- return {
- header: `
-
-
-
-
- |
-
- |
-
-
- |
-
-
- `,
- footer: `
-
-

-
- Documento gerado digitalmente via plataforma Estágios+ EPVC • ${new Date().toLocaleDateString('pt-PT')}
-
-
- `,
- watermark: `
-
-

-
- `
- };
- };
+ const header = `
+
+
+
+
+
+ |
+
+
+ |
+
+ Plataforma Oficial
+
+ |
+
+
+
+ `;
+
+ const footer = `
+
+
+
+ | Documento gerado automaticamente pela Plataforma Estágios+ |
+
+
+ |
+
+
+
+ `;
+
+ const watermark = `
+
+

+
+ `;
+
+ return { header, footer, watermark };
+};
const getEstilosHTML = () => `
`;
- // =====================================================================
- // 1 & 2. GERAÇÃO DA MATRIZ EM HTML (USADA NO EXCEL E NO PDF HORIZONTAL)
- // =====================================================================
+ // ─── Matriz de Assiduidade (mantida) ───────────────────────────────────────
const construirHtmlMatriz = async () => {
const { header, footer } = await gerarCabecalhoERodape();
const tituloMatriz = `Mapa Oficial de Assiduidade
Formação em Contexto de Trabalho
`;
@@ -215,7 +238,7 @@ export default function GestaoRelatorios() {
if(r.data_inicio < minDateStr) minDateStr = r.data_inicio;
if(r.data_fim > maxDateStr) maxDateStr = r.data_fim;
});
-
+
let minDate = new Date(minDateStr);
let maxDate = new Date(maxDateStr);
if (isNaN(minDate.getTime())) minDate = new Date();
@@ -229,8 +252,7 @@ export default function GestaoRelatorios() {
const nomeMes = ['Janeiro','Fevereiro','Março','Abril','Maio','Junho','Julho','Agosto','Setembro','Outubro','Novembro','Dezembro'][mes];
const dia = current.getDate();
const dw = current.getDay();
-
- if (dw !== 0 && dw !== 6) {
+ 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); }
@@ -242,14 +264,11 @@ export default function GestaoRelatorios() {
let mesesHtml = `
| Nº |
Nome do Aluno | `;
- let diasHtml = `
- | | `;
+ let diasHtml = `
| | `;
meses.forEach((m: any) => {
mesesHtml += `${m.nome} | `;
- m.dias.forEach((d: any) => {
- diasHtml += `${d.dia} | `;
- });
+ m.dias.forEach((d: any) => { diasHtml += `${d.dia} | `; });
diasHtml += `Total | `;
});
@@ -263,13 +282,12 @@ export default function GestaoRelatorios() {
alunosHtml += `
| ${r.n_escola} |
${r.aluno_nome} | `;
-
+
let totalFeitasGlobais = 0;
const horasDiarias = parseInt(String(r.horas_diarias || '8').match(/\d+/)?.[0] || '8');
-
+
meses.forEach((m: any) => {
let totalMesFeitas = 0;
-
m.dias.forEach((d: any) => {
const p = presencas?.find(x => x.aluno_id === r.aluno_id && x.data === d.dataStr);
if (p) {
@@ -288,11 +306,10 @@ export default function GestaoRelatorios() {
alunosHtml += ` | `;
}
});
-
totalFeitasGlobais += totalMesFeitas;
alunosHtml += `${totalMesFeitas} | `;
});
-
+
const faltam = Math.max(0, (r.horas_totais || 400) - totalFeitasGlobais);
alunosHtml += `
${totalFeitasGlobais} |
@@ -311,11 +328,7 @@ export default function GestaoRelatorios() {
Ano Letivo: 2025/2026 |
-
- ${mesesHtml}
- ${diasHtml}
- ${alunosHtml}
-
+ ${mesesHtml}${diasHtml}${alunosHtml}
Legenda: [ F ] Falta Justificada • [ FI ] Falta Injustificada
@@ -326,39 +339,21 @@ export default function GestaoRelatorios() {
const gerarExcelGeral = async () => {
if (relatoriosFiltrados.length === 0) return Alert.alert("Aviso", "Não há alunos para exportar nesta turma.");
- setGerandoDocumento("excel");
+ setGerandoDocumento("excel");
try {
const htmlCorpo = await construirHtmlMatriz();
const docExcel = `
-
-
- ${getEstilosHTML()}
-
-
- ${htmlCorpo}
-
-
- `;
-
+ ${getEstilosHTML()}
+ ${htmlCorpo}