atualizacoes
This commit is contained in:
@@ -1,585 +1,421 @@
|
||||
// app/Professor/Alunos/relatorios.tsx
|
||||
// app/Empresa/FichaAvaliacao.tsx
|
||||
// Navegar para esta página passando: { estagio_id, aluno_nome, aluno_turma, n_escola }
|
||||
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { useFocusEffect } from '@react-navigation/native';
|
||||
import { Asset } from 'expo-asset';
|
||||
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 { useLocalSearchParams, useRouter } from 'expo-router';
|
||||
import { useMemo, useState } from 'react';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Alert,
|
||||
RefreshControl,
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
SafeAreaView,
|
||||
ScrollView,
|
||||
StatusBar,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
View
|
||||
View,
|
||||
} from 'react-native';
|
||||
import { supabase } from '../../lib/supabase';
|
||||
import { useTheme } from '../../themecontext';
|
||||
|
||||
export default function GestaoRelatorios() {
|
||||
// ─── 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.' },
|
||||
];
|
||||
|
||||
const ESCALA = [
|
||||
{ valor: 1, label: 'Insatisfatório' },
|
||||
{ valor: 2, label: 'A Melhorar' },
|
||||
{ valor: 3, label: 'Satisfatório' },
|
||||
{ valor: 4, label: 'Bom' },
|
||||
{ valor: 5, label: 'Excelente' },
|
||||
];
|
||||
|
||||
// ─── Componente ───────────────────────────────────────────────────────────────
|
||||
export default function FichaAvaliacao() {
|
||||
const { isDarkMode } = useTheme();
|
||||
const router = useRouter();
|
||||
const params = useLocalSearchParams<{
|
||||
estagio_id: string;
|
||||
aluno_nome: string;
|
||||
aluno_turma: string;
|
||||
n_escola: string;
|
||||
}>();
|
||||
|
||||
const [relatorios, setRelatorios] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [gerandoPDF, setGerandoPDF] = useState<string | null>(null);
|
||||
const [respostas, setRespostas] = useState<Record<string, number>>({});
|
||||
const [notaFinal, setNotaFinal] = useState('');
|
||||
const [observacoes, setObservacoes] = useState('');
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
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',
|
||||
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',
|
||||
vermelho: '#EF4444',
|
||||
verde: '#22C55E',
|
||||
inputFundo: isDarkMode ? '#1E1E20' : '#F8FAFC',
|
||||
}), [isDarkMode]);
|
||||
|
||||
// Função para carregar as imagens locais para Base64
|
||||
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}`;
|
||||
} catch (e) {
|
||||
console.error("Erro no Base64:", e);
|
||||
return "";
|
||||
}
|
||||
// Cor do botão de escala consoante o valor selecionado
|
||||
const corEscala = (valor: number, selecionado: boolean) => {
|
||||
if (!selecionado) return { bg: cores.inputFundo, borda: cores.borda, texto: cores.textoSecundario };
|
||||
const mapa: Record<number, { bg: string; borda: string; texto: string }> = {
|
||||
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];
|
||||
};
|
||||
|
||||
const fetchRelatorios = async (isManualRefresh = false) => {
|
||||
if (!isManualRefresh) setLoading(true);
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('estagios')
|
||||
.select(`
|
||||
id, horas_totais, horas_concluidas, nota_final, avaliacao_url, data_inicio, data_fim,
|
||||
alunos (id, nome, turma_curso, n_escola),
|
||||
empresas (nome, tutor_nome)
|
||||
`)
|
||||
.order('data_inicio', { ascending: false });
|
||||
// Validação da nota final
|
||||
const notaValida = useMemo(() => {
|
||||
const n = parseFloat(notaFinal.replace(',', '.'));
|
||||
return !isNaN(n) && n >= 0 && n <= 20;
|
||||
}, [notaFinal]);
|
||||
|
||||
if (error) throw error;
|
||||
const todasRespondidas = PERGUNTAS.every(p => respostas[p.id] !== undefined);
|
||||
const podeSometer = todasRespondidas && notaValida;
|
||||
|
||||
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;
|
||||
|
||||
return {
|
||||
id_estagio: estagio.id,
|
||||
aluno_id: aluno?.id,
|
||||
aluno_nome: aluno?.nome || 'Desconhecido',
|
||||
turma: aluno?.turma_curso || 'N/A',
|
||||
n_escola: aluno?.n_escola || '--',
|
||||
empresa_nome: empresa?.nome || 'N/A',
|
||||
tutor_nome: empresa?.tutor_nome || 'N/A',
|
||||
data_inicio: estagio.data_inicio,
|
||||
data_fim: estagio.data_fim,
|
||||
horas_totais: estagio.horas_totais || 0,
|
||||
horas_concluidas: estagio.horas_concluidas || 0,
|
||||
nota_empresa: estagio.nota_final,
|
||||
pdf_empresa: estagio.avaliacao_url
|
||||
};
|
||||
}) || [];
|
||||
// Agrupa perguntas por categoria
|
||||
const categorias = useMemo(() => {
|
||||
const mapa: Record<string, typeof PERGUNTAS> = {};
|
||||
PERGUNTAS.forEach(p => {
|
||||
if (!mapa[p.categoria]) mapa[p.categoria] = [];
|
||||
mapa[p.categoria].push(p);
|
||||
});
|
||||
return mapa;
|
||||
}, []);
|
||||
|
||||
setRelatorios(formatados);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
Alert.alert('Erro', 'Não foi possível carregar os dados dos estágios.');
|
||||
} finally {
|
||||
if (!isManualRefresh) setLoading(false);
|
||||
setRefreshing(false);
|
||||
const handleSubmit = async () => {
|
||||
if (!podeSometer) {
|
||||
Alert.alert('Atenção', 'Preenche todas as questões e insere uma nota final válida (0–20).');
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
useFocusEffect(useCallback(() => { fetchRelatorios(); }, []));
|
||||
Alert.alert(
|
||||
'Submeter Avaliação',
|
||||
`Confirmas a avaliação de ${params.aluno_nome} com nota final ${notaFinal} valores?`,
|
||||
[
|
||||
{ text: 'Cancelar', style: 'cancel' },
|
||||
{
|
||||
text: 'Confirmar',
|
||||
onPress: async () => {
|
||||
setSubmitting(true);
|
||||
try {
|
||||
const notaNum = parseFloat(notaFinal.replace(',', '.'));
|
||||
|
||||
// 1. GERAR EXCEL (CSV) PERFEITO
|
||||
const gerarExcelGeral = async () => {
|
||||
try {
|
||||
let csvContent = "\uFEFFNumero;Aluno;Turma;Empresa;Horas Feitas;Horas Totais;Nota Empresa;Autoavaliacao\n";
|
||||
|
||||
relatorios.forEach(r => {
|
||||
const nota = r.nota_empresa ? r.nota_empresa : 'Pendente';
|
||||
const autoav = 'Pendente';
|
||||
csvContent += `${r.n_escola};${r.aluno_nome};${r.turma};${r.empresa_nome};${r.horas_concluidas};${r.horas_totais};${nota};${autoav}\n`;
|
||||
});
|
||||
// Guarda as respostas individuais + nota + observações
|
||||
const { error } = await supabase
|
||||
.from('estagios')
|
||||
.update({
|
||||
nota_final: notaNum,
|
||||
avaliacao_observacoes: observacoes.trim() || null,
|
||||
avaliacao_respostas: respostas, // jsonb no Supabase
|
||||
avaliacao_data: new Date().toISOString(),
|
||||
})
|
||||
.eq('id', params.estagio_id);
|
||||
|
||||
const fileName = `Pauta_Estagios_${new Date().toISOString().split('T')[0]}.csv`;
|
||||
const fileUri = FileSystem.documentDirectory + fileName;
|
||||
|
||||
await FileSystem.writeAsStringAsync(fileUri, csvContent, { encoding: FileSystem.EncodingType.UTF8 });
|
||||
|
||||
if (await Sharing.isAvailableAsync()) {
|
||||
await Sharing.shareAsync(fileUri, { mimeType: 'text/csv', dialogTitle: 'Exportar Pauta Excel' });
|
||||
}
|
||||
} catch (e) {
|
||||
Alert.alert('Erro', 'Falha ao gerar o Excel.');
|
||||
}
|
||||
};
|
||||
if (error) throw error;
|
||||
|
||||
// 2. GERAR PAUTA GLOBAL EM PDF COM DESIGN OFICIAL
|
||||
const gerarPautaPDF = async () => {
|
||||
try {
|
||||
const logoEPVC_b64 = await getBase64Image(require('../../../assets/images/logoepvc2.png'));
|
||||
const logoEstagios_b64 = await getBase64Image(require('../../../assets/images/logo.png'));
|
||||
const bannerEU_b64 = await getBase64Image(require('../../../assets/images/logoepvc.png'));
|
||||
|
||||
let linhasTabela = '';
|
||||
relatorios.forEach(r => {
|
||||
const nota = r.nota_empresa ? `<strong>${r.nota_empresa}</strong>` : '<span style="color:#F18721">Pendente</span>';
|
||||
linhasTabela += `
|
||||
<tr>
|
||||
<td style="text-align: center;">${r.n_escola}</td>
|
||||
<td><strong>${r.aluno_nome}</strong></td>
|
||||
<td>${r.turma}</td>
|
||||
<td>${r.empresa_nome}</td>
|
||||
<td style="text-align: center;">${r.horas_concluidas}/${r.horas_totais}h</td>
|
||||
<td style="text-align: center;" class="score">${nota}</td>
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
|
||||
const htmlContent = `
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-PT">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
@page { size: A4; margin: 0; }
|
||||
html, body { height: 99%; overflow: hidden; }
|
||||
|
||||
body {
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 12mm 15mm;
|
||||
color: #1E293B;
|
||||
line-height: 1.2;
|
||||
background-color: #fff;
|
||||
font-size: 10px;
|
||||
box-sizing: border-box;
|
||||
Alert.alert(
|
||||
'Avaliação Submetida!',
|
||||
'A ficha foi gravada com sucesso. O professor será notificado.',
|
||||
[{ text: 'OK', onPress: () => router.back() }]
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
Alert.alert('Erro', 'Não foi possível submeter a avaliação. Tenta novamente.');
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
|
||||
.header-table { width: 100%; border-bottom: 2px solid #003049; padding-bottom: 5px; margin-bottom: 8px; }
|
||||
.header-table td { vertical-align: middle; }
|
||||
|
||||
.logo-epvc { width: 110px; display: block; }
|
||||
.logo-estagios { width: 160px; display: block; float: right; margin-top: -5px; margin-right: -5px; }
|
||||
|
||||
.header-center { text-align: center; }
|
||||
.header-center h1 { color: #003049; margin: 0; font-size: 15px; font-weight: 900; text-transform: uppercase; }
|
||||
.header-center h2 { color: #F18721; margin: 2px 0 0; font-size: 9px; font-weight: 800; text-transform: uppercase; }
|
||||
|
||||
.section-title {
|
||||
background-color: #003049;
|
||||
color: white;
|
||||
padding: 4px 8px;
|
||||
font-size: 9px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 5px;
|
||||
margin-top: 15px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.eval-table { width: 100%; border-collapse: collapse; margin-bottom: 8px; font-size: 9px; }
|
||||
.eval-table th { background-color: #F1F5F9; color: #003049; font-weight: bold; padding: 6px 8px; border: 1px solid #CBD5E1; text-align: left; text-transform: uppercase; }
|
||||
.eval-table td { border: 1px solid #CBD5E1; padding: 6px 8px; vertical-align: middle; color: #334155; }
|
||||
.eval-table td.score { font-size: 11px; color: #003049; background-color: #F8FAFC; }
|
||||
|
||||
.footer { text-align: center; padding-top: 5px; border-top: 1px solid #E2E8F0; width: 100%; position: absolute; bottom: 10mm; left: 0; right: 0; margin: 0 auto; width: calc(100% - 30mm); }
|
||||
.banner-img { max-width: 100%; height: auto; max-height: 30px; margin-bottom: 3px; }
|
||||
.footer-text { font-size: 8px; color: #94A3B8; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<table class="header-table">
|
||||
<tr>
|
||||
<td style="width: 25%;"><img src="${logoEPVC_b64}" class="logo-epvc" /></td>
|
||||
<td style="width: 50%;" class="header-center">
|
||||
<h1>Pauta Geral de Estágios</h1>
|
||||
<h2>Formação em Contexto de Trabalho</h2>
|
||||
</td>
|
||||
<td style="width: 25%; text-align: right;"><img src="${logoEstagios_b64}" class="logo-estagios" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="section-title">Resumo de Processos Individuais</div>
|
||||
<table class="eval-table">
|
||||
<tr>
|
||||
<th style="text-align: center; width: 5%;">Nº</th>
|
||||
<th style="width: 30%;">Nome do Aluno</th>
|
||||
<th style="width: 15%;">Turma/Curso</th>
|
||||
<th style="width: 25%;">Empresa</th>
|
||||
<th style="text-align: center; width: 15%;">Horas</th>
|
||||
<th style="text-align: center; width: 10%;">Nota Final</th>
|
||||
</tr>
|
||||
${linhasTabela}
|
||||
</table>
|
||||
|
||||
<div class="footer">
|
||||
<img src="${bannerEU_b64}" class="banner-img" />
|
||||
<div class="footer-text">
|
||||
Documento gerado digitalmente via plataforma Estágios+ EPVC.<br/>
|
||||
Data de emissão: ${new Date().toLocaleDateString('pt-PT')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
const { uri } = await Print.printToFileAsync({ html: htmlContent });
|
||||
const newFileUri = `${FileSystem.documentDirectory}Pauta_Geral_Estagios.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', 'Falha ao gerar a Pauta em PDF.');
|
||||
}
|
||||
};
|
||||
|
||||
// 3. GERAR PDF DOS SUMÁRIOS DIÁRIOS COM DESIGN OFICIAL
|
||||
const gerarSumariosPDF = async (r: any) => {
|
||||
setGerandoPDF(r.id_estagio);
|
||||
try {
|
||||
let { data: sumarios, error } = await supabase
|
||||
.from('registos_diarios')
|
||||
.select('data, tipo, sumario')
|
||||
.eq('estagio_id', r.id_estagio)
|
||||
.order('data', { ascending: true });
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
if (!sumarios || sumarios.length === 0) {
|
||||
if (!r.aluno_id) throw new Error("ID do aluno não encontrado.");
|
||||
|
||||
const { data: presencas, error: presencasErr } = await supabase
|
||||
.from('presencas')
|
||||
.select('data, estado, sumario')
|
||||
.eq('aluno_id', r.aluno_id)
|
||||
.order('data', { ascending: true });
|
||||
|
||||
if (presencasErr) throw presencasErr;
|
||||
|
||||
if (presencas && presencas.length > 0) {
|
||||
sumarios = presencas.map(p => ({
|
||||
data: p.data,
|
||||
tipo: p.estado || 'Presença',
|
||||
sumario: p.sumario
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
if (!sumarios || sumarios.length === 0) {
|
||||
Alert.alert('Aviso', 'Não foram encontrados registos diários nem presenças para este aluno.');
|
||||
setGerandoPDF(null);
|
||||
return;
|
||||
}
|
||||
|
||||
let linhasTabela = '';
|
||||
sumarios.forEach((s: any) => {
|
||||
const dataFormatada = new Date(s.data).toLocaleDateString('pt-PT');
|
||||
linhasTabela += `
|
||||
<tr>
|
||||
<td style="text-align: center;">${dataFormatada}</td>
|
||||
<td style="text-align: center;"><strong>${s.tipo}</strong></td>
|
||||
<td>${s.sumario || '<em>Sem descrição submetida.</em>'}</td>
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
|
||||
const logoEPVC_b64 = await getBase64Image(require('../../../assets/images/logoepvc2.png'));
|
||||
const logoEstagios_b64 = await getBase64Image(require('../../../assets/images/logo.png'));
|
||||
const bannerEU_b64 = await getBase64Image(require('../../../assets/images/logoepvc.png'));
|
||||
|
||||
const dataInicioFormatada = r.data_inicio ? new Date(r.data_inicio).toLocaleDateString('pt-PT') : 'N/A';
|
||||
const dataFimFormatada = r.data_fim ? new Date(r.data_fim).toLocaleDateString('pt-PT') : 'N/A';
|
||||
|
||||
const htmlContent = `
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-PT">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
@page { size: A4; margin: 0; }
|
||||
html, body { height: 99%; overflow: hidden; }
|
||||
|
||||
body {
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 12mm 15mm;
|
||||
color: #1E293B;
|
||||
line-height: 1.2;
|
||||
background-color: #fff;
|
||||
font-size: 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.header-table { width: 100%; border-bottom: 2px solid #003049; padding-bottom: 5px; margin-bottom: 8px; }
|
||||
.header-table td { vertical-align: middle; }
|
||||
|
||||
.logo-epvc { width: 110px; display: block; }
|
||||
.logo-estagios { width: 160px; display: block; float: right; margin-top: -5px; margin-right: -5px; }
|
||||
|
||||
.header-center { text-align: center; }
|
||||
.header-center h1 { color: #003049; margin: 0; font-size: 15px; font-weight: 900; text-transform: uppercase; }
|
||||
.header-center h2 { color: #F18721; margin: 2px 0 0; font-size: 9px; font-weight: 800; text-transform: uppercase; }
|
||||
|
||||
.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%; color: #003049; }
|
||||
.info-table td.value { width: 35%; color: #334155; }
|
||||
|
||||
.section-title {
|
||||
background-color: #003049;
|
||||
color: white;
|
||||
padding: 4px 8px;
|
||||
font-size: 9px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 5px;
|
||||
margin-top: 5px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.eval-table { width: 100%; border-collapse: collapse; margin-bottom: 8px; font-size: 9px; }
|
||||
.eval-table th { background-color: #F1F5F9; color: #003049; font-weight: bold; padding: 4px 8px; border: 1px solid #CBD5E1; }
|
||||
.eval-table td { border: 1px solid #CBD5E1; padding: 3px 8px; vertical-align: middle; color: #334155; }
|
||||
|
||||
.footer { text-align: center; padding-top: 5px; border-top: 1px solid #E2E8F0; width: 100%; position: absolute; bottom: 10mm; left: 0; right: 0; margin: 0 auto; width: calc(100% - 30mm); }
|
||||
.banner-img { max-width: 100%; height: auto; max-height: 30px; margin-bottom: 3px; }
|
||||
.footer-text { font-size: 8px; color: #94A3B8; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<table class="header-table">
|
||||
<tr>
|
||||
<td style="width: 25%;"><img src="${logoEPVC_b64}" class="logo-epvc" /></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="${logoEstagios_b64}" class="logo-estagios" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table class="info-table">
|
||||
<tr>
|
||||
<td class="label">Estagiário:</td>
|
||||
<td class="value"><strong>${r.aluno_nome}</strong> (Nº ${r.n_escola})</td>
|
||||
<td class="label">Turma/Curso:</td>
|
||||
<td class="value">${r.turma}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Entidade:</td>
|
||||
<td class="value"><strong>${r.empresa_nome}</strong></td>
|
||||
<td class="label">Tutor(a):</td>
|
||||
<td class="value">${r.tutor_nome}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Carga Horária:</td>
|
||||
<td class="value"><strong>${r.horas_totais} Horas</strong></td>
|
||||
<td class="label">Período:</td>
|
||||
<td class="value">${dataInicioFormatada} a ${dataFimFormatada}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="section-title">Registo de Atividades Diárias</div>
|
||||
<table class="eval-table">
|
||||
<tr>
|
||||
<th style="width: 15%; text-align: center;">Data</th>
|
||||
<th style="width: 20%; text-align: center;">Natureza/Estado</th>
|
||||
<th style="width: 65%; text-align: left;">Sumário / Atividades Desenvolvidas</th>
|
||||
</tr>
|
||||
${linhasTabela}
|
||||
</table>
|
||||
|
||||
<div class="footer">
|
||||
<img src="${bannerEU_b64}" class="banner-img" />
|
||||
<div class="footer-text">
|
||||
Documento gerado digitalmente via plataforma Estágios+ EPVC.<br/>
|
||||
Data de emissão: ${new Date().toLocaleDateString('pt-PT')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
const { uri } = await Print.printToFileAsync({ html: htmlContent });
|
||||
const safeName = r.aluno_nome.replace(/[^a-zA-Z0-9]/g, '_');
|
||||
const newFileUri = `${FileSystem.documentDirectory}Diario_Bordo_${safeName}.pdf`;
|
||||
|
||||
await FileSystem.moveAsync({ from: uri, to: newFileUri });
|
||||
|
||||
if (await Sharing.isAvailableAsync()) {
|
||||
await Sharing.shareAsync(newFileUri, {
|
||||
mimeType: 'application/pdf',
|
||||
dialogTitle: `Partilhar Diário de Bordo de ${r.aluno_nome}`,
|
||||
UTI: 'com.adobe.pdf'
|
||||
});
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
console.error("Erro a gerar Sumários PDF:", e);
|
||||
Alert.alert('Erro', 'Não foi possível ler os registos.');
|
||||
} finally {
|
||||
setGerandoPDF(null);
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
};
|
||||
|
||||
// ─── Render ────────────────────────────────────────────────────────────────
|
||||
return (
|
||||
<SafeAreaView style={[styles.safeArea, { backgroundColor: cores.fundo }]}>
|
||||
<SafeAreaView style={[s.safe, { backgroundColor: cores.fundo }]}>
|
||||
<StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
|
||||
|
||||
<View style={styles.header}>
|
||||
<TouchableOpacity style={styles.btnVoltar} onPress={() => router.back()}>
|
||||
|
||||
{/* Header */}
|
||||
<View style={s.header}>
|
||||
<TouchableOpacity onPress={() => router.back()} style={s.btnBack}>
|
||||
<Ionicons name="arrow-back" size={24} color={cores.texto} />
|
||||
</TouchableOpacity>
|
||||
<Text style={[styles.headerTitle, { color: cores.texto }]}>Gestão de Avaliações</Text>
|
||||
<View style={{ width: 24 }} />
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text style={[s.headerTitle, { color: cores.texto }]}>Ficha de Avaliação</Text>
|
||||
<Text style={[s.headerSub, { color: cores.textoSecundario }]}>Formação em Contexto de Trabalho</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={() => { setRefreshing(true); fetchRelatorios(true); }} tintColor={cores.azulMarinho} />}
|
||||
>
|
||||
|
||||
{/* BOTÕES DE EXPORTAÇÃO GLOBAL */}
|
||||
<View style={styles.botoesGlobaisContainer}>
|
||||
<TouchableOpacity style={[styles.btnGlobal, { backgroundColor: cores.verdeAgua + '30', borderColor: cores.verdeAgua }]} onPress={gerarExcelGeral}>
|
||||
<Ionicons name="grid-outline" size={26} color="#003049" />
|
||||
<Text style={[styles.btnGlobalText, { color: '#003049' }]}>Excel (CSV)</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity style={[styles.btnGlobal, { backgroundColor: cores.laranja + '20', borderColor: cores.laranja }]} onPress={gerarPautaPDF}>
|
||||
<Ionicons name="document-text" size={26} color={cores.laranja} />
|
||||
<Text style={[styles.btnGlobalText, { color: cores.laranja }]}>Pauta em PDF</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
<Text style={styles.sectionTitle}>Processos Individuais</Text>
|
||||
|
||||
{loading && !refreshing ? (
|
||||
<ActivityIndicator size="large" color={cores.azulMarinho} style={{ marginTop: 50 }} />
|
||||
) : relatorios.length === 0 ? (
|
||||
<Text style={{ textAlign: 'center', color: cores.textoSecundario, marginTop: 50 }}>Nenhum estágio encontrado.</Text>
|
||||
) : (
|
||||
relatorios.map((r, index) => (
|
||||
<View key={index} style={[styles.card, { backgroundColor: cores.card, borderColor: cores.borda }]}>
|
||||
|
||||
<View style={styles.cardHeader}>
|
||||
<View style={[styles.avatar, { backgroundColor: cores.azulMarinho }]}>
|
||||
<Text style={{ color: '#FFF', fontWeight: 'bold', fontSize: 18 }}>{r.aluno_nome.charAt(0)}</Text>
|
||||
</View>
|
||||
<View style={{ flex: 1, marginLeft: 12 }}>
|
||||
<Text style={[styles.alunoName, { color: cores.texto }]}>{r.aluno_nome}</Text>
|
||||
<Text style={[styles.alunoSub, { color: cores.textoSecundario }]}>{r.empresa_nome} • {r.turma}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<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>
|
||||
{r.nota_empresa ? (
|
||||
<Text style={[styles.notaTag, { backgroundColor: cores.azulMarinho + '20', color: cores.azulMarinho }]}>{r.nota_empresa} Val.</Text>
|
||||
) : (
|
||||
<Text style={[styles.notaTag, { backgroundColor: cores.laranja + '20', color: cores.laranja }]}>Pendente</Text>
|
||||
)}
|
||||
</View>
|
||||
{r.pdf_empresa && (
|
||||
<TouchableOpacity style={styles.btnAcaoLigeiro} onPress={() => WebBrowser.openBrowserAsync(r.pdf_empresa)}>
|
||||
<Ionicons name="document-text" size={16} 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>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 3. DIÁRIO DE BORDO (PDF) */}
|
||||
<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 }]}
|
||||
onPress={() => gerarSumariosPDF(r)} /* PASSEI A VARIÁVEL COMPLETA AQUI! */
|
||||
disabled={gerandoPDF === r.id_estagio}
|
||||
>
|
||||
{gerandoPDF === r.id_estagio ? (
|
||||
<ActivityIndicator size="small" color={cores.laranja} />
|
||||
) : (
|
||||
<>
|
||||
<Ionicons name="calendar-outline" size={16} color={cores.laranja} />
|
||||
<Text style={[styles.textoAcao, { color: cores.laranja }]}>Exportar Histórico (PDF)</Text>
|
||||
</>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<KeyboardAvoidingView style={{ flex: 1 }} behavior={Platform.OS === 'ios' ? 'padding' : undefined}>
|
||||
<ScrollView contentContainerStyle={s.scroll} showsVerticalScrollIndicator={false}>
|
||||
|
||||
{/* Cartão do Aluno */}
|
||||
<View style={[s.alunoCard, { backgroundColor: cores.azulMarinho }]}>
|
||||
<View style={s.alunoAvatar}>
|
||||
<Text style={s.alunoAvatarLetra}>{params.aluno_nome?.charAt(0) ?? '?'}</Text>
|
||||
</View>
|
||||
))
|
||||
)}
|
||||
<View style={{ flex: 1, marginLeft: 14 }}>
|
||||
<Text style={s.alunoNome}>{params.aluno_nome}</Text>
|
||||
<Text style={s.alunoMeta}>Nº {params.n_escola} · {params.aluno_turma}</Text>
|
||||
</View>
|
||||
<View style={[s.badgeFCT, { backgroundColor: cores.laranja }]}>
|
||||
<Text style={s.badgeFCTText}>FCT</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
</ScrollView>
|
||||
{/* Instruções */}
|
||||
<View style={[s.instrucoes, { backgroundColor: cores.verdeAgua + '22', borderColor: cores.verdeAgua }]}>
|
||||
<Ionicons name="information-circle-outline" size={18} color={cores.verdeAgua} style={{ marginRight: 8, marginTop: 1 }} />
|
||||
<Text style={[s.instrucoesTxt, { color: cores.texto }]}>
|
||||
Avalia cada item de <Text style={{ fontWeight: '900' }}>1 (Insatisfatório)</Text> a <Text style={{ fontWeight: '900' }}>5 (Excelente)</Text>. No final, atribui uma nota de 0 a 20.
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* Perguntas agrupadas por categoria */}
|
||||
{Object.entries(categorias).map(([categoria, perguntas], catIdx) => (
|
||||
<View key={catIdx}>
|
||||
{/* Cabeçalho de categoria */}
|
||||
<View style={[s.catHeader, { backgroundColor: cores.azulMarinho }]}>
|
||||
<Text style={s.catHeaderTxt}>{categoria}</Text>
|
||||
</View>
|
||||
|
||||
{perguntas.map((pergunta, idx) => {
|
||||
const selecionado = respostas[pergunta.id];
|
||||
const globalIdx = PERGUNTAS.findIndex(p => p.id === pergunta.id) + 1;
|
||||
|
||||
return (
|
||||
<View key={pergunta.id} style={[s.perguntaCard, { backgroundColor: cores.card, borderColor: cores.borda }]}>
|
||||
{/* Número + Texto */}
|
||||
<View style={s.perguntaTop}>
|
||||
<View style={[s.numCircle, { backgroundColor: selecionado ? cores.laranja : cores.borda }]}>
|
||||
<Text style={[s.numTxt, { color: selecionado ? '#fff' : cores.textoSecundario }]}>{globalIdx}</Text>
|
||||
</View>
|
||||
<Text style={[s.perguntaTxt, { color: cores.texto }]}>{pergunta.texto}</Text>
|
||||
</View>
|
||||
|
||||
{/* Escala */}
|
||||
<View style={s.escalaRow}>
|
||||
{ESCALA.map(e => {
|
||||
const esteSelecionado = selecionado === e.valor;
|
||||
const cor = corEscala(e.valor, esteSelecionado);
|
||||
return (
|
||||
<TouchableOpacity
|
||||
key={e.valor}
|
||||
style={[s.escalaBotao, { backgroundColor: cor.bg, borderColor: cor.borda }]}
|
||||
onPress={() => setRespostas(prev => ({ ...prev, [pergunta.id]: e.valor }))}
|
||||
activeOpacity={0.75}
|
||||
>
|
||||
<Text style={[s.escalaNum, { color: cor.texto }]}>{e.valor}</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
|
||||
{/* Label do selecionado */}
|
||||
{selecionado && (
|
||||
<Text style={[s.escalaLabel, { color: cores.textoSecundario }]}>
|
||||
{ESCALA.find(e => e.valor === selecionado)?.label}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
))}
|
||||
|
||||
{/* Nota Final */}
|
||||
<View style={[s.secaoFinal, { backgroundColor: cores.card, borderColor: cores.borda }]}>
|
||||
<Text style={[s.secaoTitulo, { color: cores.texto }]}>Nota Final</Text>
|
||||
<Text style={[s.secaoDesc, { color: cores.textoSecundario }]}>
|
||||
Atribui uma nota de <Text style={{ fontWeight: '800' }}>0 a 20 valores</Text>. Abaixo de 9,5 é negativa.
|
||||
</Text>
|
||||
|
||||
<View style={s.notaRow}>
|
||||
<TextInput
|
||||
style={[
|
||||
s.notaInput,
|
||||
{
|
||||
backgroundColor: cores.inputFundo,
|
||||
borderColor: notaFinal === '' ? cores.borda : notaValida ? cores.verde : cores.vermelho,
|
||||
color: cores.texto,
|
||||
},
|
||||
]}
|
||||
placeholder="Ex: 16"
|
||||
placeholderTextColor={cores.textoSecundario}
|
||||
keyboardType="decimal-pad"
|
||||
value={notaFinal}
|
||||
onChangeText={setNotaFinal}
|
||||
maxLength={4}
|
||||
/>
|
||||
<Text style={[s.notaSufixo, { color: cores.textoSecundario }]}>/ 20 valores</Text>
|
||||
|
||||
{notaFinal !== '' && (
|
||||
<View style={[
|
||||
s.notaBadge,
|
||||
{ backgroundColor: notaValida ? (parseFloat(notaFinal.replace(',', '.')) >= 9.5 ? '#DCFCE7' : '#FEE2E2') : '#F3F4F6' }
|
||||
]}>
|
||||
<Text style={[
|
||||
s.notaBadgeTxt,
|
||||
{ color: notaValida ? (parseFloat(notaFinal.replace(',', '.')) >= 9.5 ? '#15803D' : '#B91C1C') : cores.textoSecundario }
|
||||
]}>
|
||||
{notaValida
|
||||
? parseFloat(notaFinal.replace(',', '.')) >= 9.5 ? '✓ Positiva' : '✗ Negativa'
|
||||
: 'Inválido'}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Observações */}
|
||||
<View style={[s.secaoFinal, { backgroundColor: cores.card, borderColor: cores.borda }]}>
|
||||
<Text style={[s.secaoTitulo, { color: cores.texto }]}>Observações</Text>
|
||||
<Text style={[s.secaoDesc, { color: cores.textoSecundario }]}>Opcional — comentários adicionais sobre o desempenho.</Text>
|
||||
<TextInput
|
||||
style={[s.obsInput, { backgroundColor: cores.inputFundo, borderColor: cores.borda, color: cores.texto }]}
|
||||
placeholder="Escreve aqui as tuas observações..."
|
||||
placeholderTextColor={cores.textoSecundario}
|
||||
multiline
|
||||
numberOfLines={4}
|
||||
value={observacoes}
|
||||
onChangeText={setObservacoes}
|
||||
textAlignVertical="top"
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Progresso */}
|
||||
<View style={[s.progressoBox, { backgroundColor: cores.card, borderColor: cores.borda }]}>
|
||||
<View style={s.progressoRow}>
|
||||
<Text style={[s.progressoLabel, { color: cores.textoSecundario }]}>
|
||||
{Object.keys(respostas).length} de {PERGUNTAS.length} questões respondidas
|
||||
</Text>
|
||||
<Text style={[s.progressoLabel, { color: todasRespondidas ? cores.verde : cores.laranja, fontWeight: '800' }]}>
|
||||
{todasRespondidas ? '✓ Completo' : 'Incompleto'}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[s.progressoBar, { backgroundColor: cores.borda }]}>
|
||||
<View style={[
|
||||
s.progressoFill,
|
||||
{
|
||||
backgroundColor: todasRespondidas ? cores.verde : cores.laranja,
|
||||
width: `${(Object.keys(respostas).length / PERGUNTAS.length) * 100}%`
|
||||
}
|
||||
]} />
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Botão Submeter */}
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
s.btnSubmeter,
|
||||
{ backgroundColor: podeSometer ? cores.azulMarinho : cores.borda },
|
||||
]}
|
||||
onPress={handleSubmit}
|
||||
disabled={!podeSometer || submitting}
|
||||
activeOpacity={0.85}
|
||||
>
|
||||
{submitting ? (
|
||||
<ActivityIndicator color="#fff" />
|
||||
) : (
|
||||
<>
|
||||
<Ionicons name="checkmark-circle" size={22} color={podeSometer ? '#fff' : cores.textoSecundario} />
|
||||
<Text style={[s.btnSubmeterTxt, { color: podeSometer ? '#fff' : cores.textoSecundario }]}>
|
||||
Submeter Avaliação
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
|
||||
<View style={{ height: 30 }} />
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
safeArea: { flex: 1 },
|
||||
header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: 20, paddingTop: 15, paddingBottom: 10 },
|
||||
btnVoltar: { padding: 5, marginLeft: -5 },
|
||||
headerTitle: { fontSize: 18, fontWeight: '900' },
|
||||
scrollContent: { padding: 20, paddingBottom: 40 },
|
||||
|
||||
botoesGlobaisContainer: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 25, gap: 15 },
|
||||
btnGlobal: { flex: 1, flexDirection: 'column', alignItems: 'center', paddingVertical: 18, borderRadius: 20, borderWidth: 1.5, elevation: 1 },
|
||||
btnGlobalText: { fontSize: 14, fontWeight: '900', marginTop: 8 },
|
||||
|
||||
sectionTitle: { fontSize: 14, fontWeight: '900', textTransform: 'uppercase', color: '#64748B', marginBottom: 15, marginLeft: 5, letterSpacing: 1 },
|
||||
|
||||
card: { padding: 20, borderRadius: 20, borderWidth: 1, marginBottom: 20 },
|
||||
cardHeader: { flexDirection: 'row', alignItems: 'center' },
|
||||
avatar: { width: 44, height: 44, borderRadius: 22, justifyContent: 'center', alignItems: 'center' },
|
||||
alunoName: { fontSize: 16, fontWeight: '900' },
|
||||
alunoSub: { fontSize: 12, fontWeight: '600', marginTop: 2 },
|
||||
divider: { height: 1, marginVertical: 15 },
|
||||
|
||||
moduloBox: { borderBottomWidth: 1, borderBottomColor: '#E2E8F0', paddingBottom: 15, marginBottom: 15 },
|
||||
moduloHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' },
|
||||
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' }
|
||||
// ─── Estilos ──────────────────────────────────────────────────────────────────
|
||||
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 },
|
||||
|
||||
// Aluno card
|
||||
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 },
|
||||
|
||||
// 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' },
|
||||
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' },
|
||||
});
|
||||
Reference in New Issue
Block a user