From ae639422f5bf5a09e8062702b1b6fad0763511e0 Mon Sep 17 00:00:00 2001 From: Seu Nome <230413@epvc.pt> Date: Mon, 18 May 2026 11:01:11 +0100 Subject: [PATCH] relatorios --- app/Professor/Alunos/relatorios.tsx | 726 ++++++++++++++++++++-------- package-lock.json | 46 +- 2 files changed, 535 insertions(+), 237 deletions(-) diff --git a/app/Professor/Alunos/relatorios.tsx b/app/Professor/Alunos/relatorios.tsx index 9478003..e408c4a 100644 --- a/app/Professor/Alunos/relatorios.tsx +++ b/app/Professor/Alunos/relatorios.tsx @@ -1,6 +1,7 @@ // app/Professor/Alunos/relatorios.tsx 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'; @@ -10,6 +11,7 @@ import { useCallback, useMemo, useState } from 'react'; import { ActivityIndicator, Alert, + Platform, RefreshControl, SafeAreaView, ScrollView, @@ -27,6 +29,8 @@ export default function GestaoRelatorios() { const router = useRouter(); const [relatorios, setRelatorios] = useState([]); + const [turmaAtiva, setTurmaAtiva] = useState(null); + const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); const [gerandoPDF, setGerandoPDF] = useState(null); @@ -42,15 +46,34 @@ export default function GestaoRelatorios() { laranja: '#F18721', }), [isDarkMode]); + 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 base64.replace(/(\r\n|\n|\r)/gm, ""); + } catch (e) { + console.error("Erro no Base64:", e); + return ""; + } + }; + + const formatarTexto = (texto: string) => { + 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 { const { data, error } = await supabase .from('estagios') .select(` - id, horas_totais, horas_concluidas, nota_final, avaliacao_url, - alunos (id, nome, turma_curso, n_escola), - empresas (nome) + 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) `) .order('data_inicio', { ascending: false }); @@ -60,13 +83,23 @@ 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 nomeCursoFormatado = formatarTexto(aluno?.turma_curso); + const anoAluno = aluno?.ano || 10; + const turmaCompleta = `${anoAluno}º ${nomeCursoFormatado}`; + return { id_estagio: estagio.id, aluno_id: aluno?.id, aluno_nome: aluno?.nome || 'Desconhecido', - turma: aluno?.turma_curso || 'N/A', + curso: nomeCursoFormatado, + ano: anoAluno, + turma: turmaCompleta, 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_diarias: estagio.horas_diarias, horas_totais: estagio.horas_totais || 0, horas_concluidas: estagio.horas_concluidas || 0, nota_empresa: estagio.nota_final, @@ -86,85 +119,294 @@ export default function GestaoRelatorios() { useFocusEffect(useCallback(() => { fetchRelatorios(); }, [])); - // 1. GERAR EXCEL (CSV) PERFEITO - const gerarExcelGeral = async () => { - try { - // O \uFEFF é o BOM (Byte Order Mark) - Obriga o Excel a reconhecer os acentos corretos! - 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`; - }); + const cursosAgrupados = useMemo(() => { + const grupos: Record = {}; + + relatorios.forEach(r => { + 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]); + + const relatoriosFiltrados = useMemo(() => { + if (!turmaAtiva) return []; + return relatorios.filter(r => r.turma === turmaAtiva); + }, [relatorios, turmaAtiva]); + + // 🟢 FUNÇÃO AUXILIAR PARA GERAR O HTML DA MATRIZ (PARTILHADO ENTRE EXCEL E PDF) + const construirHtmlMatriz = async (logoEPVC_b64: string, logoEstagios_b64: string, isPdf = false) => { + const alunosIds = relatoriosFiltrados.map(r => r.aluno_id); + const { data: presencas } = await supabase + .from('presencas') + .select('aluno_id, data, estado, justificacao_url, estado_tutor') + .in('aluno_id', alunosIds); + + let minDateStr = relatoriosFiltrados[0].data_inicio; + let maxDateStr = relatoriosFiltrados[0].data_fim; + relatoriosFiltrados.forEach(r => { + 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(); + if (isNaN(maxDate.getTime())) { maxDate = new Date(); maxDate.setMonth(maxDate.getMonth() + 3); } + + const meses: any[] = []; + let current = new Date(minDate); + while (current <= maxDate) { + const ano = current.getFullYear(); + const mes = current.getMonth(); + 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) { + 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); } + mesObj.dias.push({ dia, dataStr }); + } + current.setDate(current.getDate() + 1); + } + + let totalColsDias = meses.reduce((acc, m) => acc + m.dias.length + 1, 0); + if (totalColsDias === 0) totalColsDias = 1; + + let mesesHtml = ` + Nº + Nome do Aluno`; + let diasHtml = ` + `; + + meses.forEach((m: any) => { + mesesHtml += `${m.nome}`; + m.dias.forEach((d: any) => { + diasHtml += `${d.dia}`; + }); + diasHtml += `Total`; + }); + + mesesHtml += `Horas Feitas (1) + Horas por Lecionar (2) + Total Contrato (3)`; + diasHtml += ``; + + let alunosHtml = ''; + relatoriosFiltrados.forEach((r, idx) => { + 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) { + if (p.estado === 'presente') { + totalMesFeitas += horasDiarias; + alunosHtml += `${horasDiarias}`; + } else if (p.estado === 'faltou') { + const isJustificada = p.justificacao_url || p.estado_tutor === 'aprovado'; + const label = isJustificada ? 'F' : 'FI'; + const color = isJustificada ? '#D97706' : '#EF4444'; + alunosHtml += `${label}`; + } else { + alunosHtml += ``; + } + } else { + alunosHtml += ``; + } + }); + + totalFeitasGlobais += totalMesFeitas; + alunosHtml += `${totalMesFeitas}`; + }); + + const faltam = Math.max(0, (r.horas_totais || 400) - totalFeitasGlobais); + alunosHtml += ` + ${totalFeitasGlobais} + ${faltam} + ${r.horas_totais || 400} + `; + }); + + const imgSrcEscola = isPdf ? `data:image/png;base64,${logoEPVC_b64}` : 'cid:logo_escola'; + const imgSrcApp = isPdf ? `data:image/png;base64,${logoEstagios_b64}` : 'cid:logo_app'; + + return { + totalColsDias, + minDate, + maxDate, + htmlCorpo: ` + + + + + + + + + + + + + + + + + ${mesesHtml} + ${diasHtml} + ${alunosHtml} + + + + + + + + + + +
+ + + Escola Profissional de Vila do Conde
+ Mapa Oficial de Assiduidade (FCT) +
+ +
Período: ${new Date(minDate).toLocaleDateString('pt-PT')} a ${new Date(maxDate).toLocaleDateString('pt-PT')}Curso / Turma: ${turmaAtiva || 'Todas as Turmas'}Ano Letivo: 2025/2026
(1) - Horas Lecionadas no período(2) - Horas em falta para lecionar(3) - Total de Horas do Contrato de Estágio
+ Legenda de Faltas: [ F ] - Falta Justificada | [ FI ] - Falta Injustificada +
+ ` + }; + }; + + // 1. EXPORTAR MATRIZ EM EXCEL (FORMATO MHTML COM IMAGENS FIXAS) + const gerarExcelGeral = async () => { + if (relatoriosFiltrados.length === 0) return Alert.alert("Aviso", "Não há alunos para exportar nesta turma."); + setRefreshing(true); + try { + const b64Escola = await getBase64Image(require('../../../assets/images/logoepvc2.png')); + const b64App = await getBase64Image(require('../../../assets/images/logo.png')); + + const { totalColsDias, htmlCorpo } = await construirHtmlMatriz(b64Escola, b64App, false); + + const mhtmlExcel = `MIME-Version: 1.0 +Content-Type: multipart/related; boundary="----=_NextPart_01D1" + +------=_NextPart_01D1 +Content-Type: text/html; charset="utf-8" +Content-Transfer-Encoding: 7bit + + + + + + + + ${htmlCorpo} + + + +------=_NextPart_01D1 +Content-Type: image/png +Content-ID: +Content-Transfer-Encoding: base64 + +${b64Escola} + +------=_NextPart_01D1 +Content-Type: image/png +Content-ID: +Content-Transfer-Encoding: base64 + +${b64App} + +------=_NextPart_01D1--`; + + const fileNameTurma = turmaAtiva ? turmaAtiva.replace(/[^a-zA-Z0-9]/g, '') : 'Geral'; + const fileUri = FileSystem.documentDirectory + `Mapa_Assiduidade_${fileNameTurma}.xls`; + + await FileSystem.writeAsStringAsync(fileUri, mhtmlExcel, { encoding: FileSystem.EncodingType.UTF8 }); - 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' }); + await Sharing.shareAsync(fileUri, { + mimeType: 'application/vnd.ms-excel', + dialogTitle: 'Exportar Pauta de Assiduidade', + UTI: 'com.microsoft.excel.xls' + }); } } catch (e) { - Alert.alert('Erro', 'Falha ao gerar o Excel.'); + console.error(e); + Alert.alert('Erro', 'Falha ao exportar a Matriz Excel.'); + } finally { + setRefreshing(false); } }; - // 2. GERAR PAUTA GLOBAL EM PDF + // 2. 🟢 EXPORTAR A MESMA MATRIZ EM FORMATO PDF (COMPLETAMENTE CORRIGIDO!) const gerarPautaPDF = async () => { + if (relatoriosFiltrados.length === 0) return Alert.alert("Aviso", "Não há alunos para exportar nesta turma."); + setGerandoPDF("pauta_global"); try { - let linhasTabela = ''; - relatorios.forEach(r => { - const nota = r.nota_empresa ? `${r.nota_empresa}` : 'Pendente'; - linhasTabela += ` - - ${r.n_escola} - ${r.aluno_nome} - ${r.turma} - ${r.empresa_nome} - ${r.horas_concluidas}/${r.horas_totais}h - ${nota} - - `; - }); + const b64Escola = await getBase64Image(require('../../../assets/images/logoepvc2.png')); + const b64App = await getBase64Image(require('../../../assets/images/logo.png')); - const htmlContent = ` + const { htmlCorpo } = await construirHtmlMatriz(b64Escola, b64App, true); + + // Usamos uma folha horizontal (Landscape) e uma tabela responsiva para caber no PDF + const htmlCompletoPDF = ` - + - + -

Pauta Geral de Avaliações - Estágios

-
Data de Emissão: ${new Date().toLocaleDateString('pt-PT')}
- - - - - - - - - - ${linhasTabela} -
Nome do AlunoTurma/CursoEmpresaHorasNota Final
+ ${htmlCorpo} `; - const { uri } = await Print.printToFileAsync({ html: htmlContent }); - const newFileUri = `${FileSystem.documentDirectory}Pauta_Geral_Estagios.pdf`; + const { uri } = await Print.printToFileAsync({ html: htmlCompletoPDF }); + const fileNameTurma = turmaAtiva ? turmaAtiva.replace(/[^a-zA-Z0-9]/g, '') : 'Geral'; + const newFileUri = `${FileSystem.documentDirectory}Pauta_Oficial_${fileNameTurma}.pdf`; await FileSystem.moveAsync({ from: uri, to: newFileUri }); @@ -173,107 +415,83 @@ export default function GestaoRelatorios() { } } catch (e) { console.error(e); - Alert.alert('Erro', 'Falha ao gerar a Pauta em PDF.'); + Alert.alert('Erro', 'Falha ao gerar o PDF Oficial.'); + } finally { + setGerandoPDF(null); } }; - // 3. GERAR PDF DOS SUMÁRIOS DIÁRIOS - const gerarSumariosPDF = async (estagio_id: string, aluno_id: string, aluno_nome: string) => { - setGerandoPDF(estagio_id); + 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', estagio_id) - .order('data', { ascending: true }); - + 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 (!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', aluno_id) - .order('data', { ascending: true }); - + 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 (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.'); + Alert.alert('Aviso', 'Não foram encontrados registos para este aluno.'); setGerandoPDF(null); return; } let linhasTabela = ''; sumarios.forEach((s: any) => { - const dataFormatada = new Date(s.data).toLocaleDateString('pt-PT'); - linhasTabela += ` - - ${dataFormatada} - ${s.tipo} - ${s.sumario || 'Sem descrição submetida.'} - - `; + linhasTabela += `${new Date(s.data).toLocaleDateString('pt-PT')}${s.tipo}${s.sumario || 'Sem descrição.'}`; }); + const logoEPVC_b64 = await getBase64Image(require('../../../assets/images/logoepvc2.png')); + const logoEstagios_b64 = await getBase64Image(require('../../../assets/images/logo.png')); + const htmlContent = ` - + - -

Diário de Bordo

-

Estagiário: ${aluno_nome}

- +
- - - + + + - ${linhasTabela}
DataNatureza/EstadoAtividades Desenvolvidas

Diário de Bordo

Formação em Contexto de Trabalho

+ + + +
Estagiário:${r.aluno_nome}Turma:${r.turma}
Entidade:${r.empresa_nome}Tutor(a):${r.tutor_nome}
+
Registo de Atividades Diárias
+ ${linhasTabela}
DataNaturezaSumário
+ `; const { uri } = await Print.printToFileAsync({ html: htmlContent }); - const safeName = aluno_nome.replace(/[^a-zA-Z0-9]/g, '_'); + 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 ${aluno_nome}`, - UTI: 'com.adobe.pdf' - }); - } - + if (await Sharing.isAvailableAsync()) await Sharing.shareAsync(newFileUri, { mimeType: 'application/pdf', UTI: 'com.adobe.pdf' }); } catch (e) { - console.error("Erro a gerar Sumários PDF:", e); + console.error(e); Alert.alert('Erro', 'Não foi possível ler os registos.'); } finally { setGerandoPDF(null); @@ -284,11 +502,16 @@ export default function GestaoRelatorios() { - - router.back()}> + + { + if (turmaAtiva) setTurmaAtiva(null); + else router.back(); + }}> - Gestão de Avaliações + + {turmaAtiva ? turmaAtiva : 'Relatórios por Turma'} + @@ -297,91 +520,156 @@ export default function GestaoRelatorios() { refreshControl={ { setRefreshing(true); fetchRelatorios(true); }} tintColor={cores.azulMarinho} />} > - {/* BOTÕES DE EXPORTAÇÃO GLOBAL */} - - - - Excel (CSV) - - - - - Pauta em PDF - - - - Processos Individuais - {loading && !refreshing ? ( - ) : relatorios.length === 0 ? ( - Nenhum estágio encontrado. - ) : ( - relatorios.map((r, index) => ( - - - - - {r.aluno_nome.charAt(0)} + ) : !turmaAtiva ? ( + + {cursosAgrupados.map((cursoGrupo, index) => ( + + + + + {cursoGrupo.curso} - - {r.aluno_nome} - {r.empresa_nome} • {r.turma} - - - - - {/* 1. AVALIAÇÃO DA EMPRESA */} - - - 1. Avaliação da Empresa - {r.nota_empresa ? ( - {r.nota_empresa} Val. - ) : ( - Pendente - )} - - {r.pdf_empresa && ( - WebBrowser.openBrowserAsync(r.pdf_empresa)}> - - Ver Ficha de Avaliação + + 0 ? 1 : 0.5 }]} + activeOpacity={0.7} + disabled={cursoGrupo.count10 === 0} + onPress={() => setTurmaAtiva(`10º ${cursoGrupo.curso}`)} + > + 10º + {cursoGrupo.count10} Aluno(s) + + 0 ? 1 : 0.5 }]} + activeOpacity={0.7} + disabled={cursoGrupo.count11 === 0} + onPress={() => setTurmaAtiva(`11º ${cursoGrupo.curso}`)} + > + 11º + {cursoGrupo.count11} Aluno(s) + + + 0 ? 1 : 0.5 }]} + activeOpacity={0.7} + disabled={cursoGrupo.count12 === 0} + onPress={() => setTurmaAtiva(`12º ${cursoGrupo.curso}`)} + > + 12º + {cursoGrupo.count12} Aluno(s) + + + + + ))} + + {cursosAgrupados.length === 0 && ( + Nenhum estágio em sistema. + )} + + ) : ( + + + + + Matriz de Assiduidade + + + + {gerandoPDF === "pauta_global" ? ( + + ) : ( + <> + + Pauta em PDF + )} - - - {/* 2. AUTOAVALIAÇÃO DO ALUNO */} - - - 2. Autoavaliação do Aluno - Em Breve - - - - {/* 3. DIÁRIO DE BORDO (PDF) - AGORA COM O ALUNO_ID! */} - - - 3. Registos Diários - {r.horas_concluidas}h Registadas - - gerarSumariosPDF(r.id_estagio, r.aluno_id, r.aluno_nome)} - disabled={gerandoPDF === r.id_estagio} - > - {gerandoPDF === r.id_estagio ? ( - - ) : ( - <> - - Exportar Histórico (PDF) - - )} - - - + - )) + + + Processos de {turmaAtiva} + {relatoriosFiltrados.length} Aluno(s) + + + {relatoriosFiltrados.length === 0 ? ( + + + Nenhum estágio para esta turma. + + ) : ( + relatoriosFiltrados.map((r, index) => ( + + + + + {r.aluno_nome.charAt(0)} + + + {r.aluno_nome} + {r.empresa_nome} + + + + + + + + 1. Avaliação da Empresa + {r.nota_empresa ? ( + {r.nota_empresa} Val. + ) : ( + Pendente + )} + + {r.pdf_empresa && ( + WebBrowser.openBrowserAsync(r.pdf_empresa)}> + + Ver Ficha de Avaliação + + )} + + + + + 2. Autoavaliação do Aluno + Em Breve + + + + + + 3. Diário de Bordo + {r.horas_concluidas}h Registadas + + gerarSumariosPDF(r)} + disabled={gerandoPDF === r.id_estagio} + > + {gerandoPDF === r.id_estagio ? ( + + ) : ( + <> + + Exportar Diário (PDF) + + )} + + + + + )) + )} + )} @@ -391,21 +679,31 @@ export default function GestaoRelatorios() { const styles = StyleSheet.create({ safeArea: { flex: 1 }, - header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: 20, paddingTop: 15, paddingBottom: 10 }, + 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 }, + + cursoSection: { marginBottom: 30 }, + cursoHeader: { flexDirection: 'row', alignItems: 'center', marginBottom: 15, gap: 8 }, + cursoTitle: { fontSize: 16, fontWeight: '900', textTransform: 'uppercase', letterSpacing: -0.5 }, + + cardsRow: { flexDirection: 'row', justifyContent: 'space-between', gap: 10 }, + turmaCard: { flex: 1, paddingVertical: 20, borderRadius: 20, borderWidth: 1, alignItems: 'center', justifyContent: 'center', elevation: 1, shadowOpacity: 0.05, shadowRadius: 5 }, + turmaAno: { fontSize: 24, fontWeight: '900', marginBottom: 4 }, + turmaCount: { fontSize: 11, fontWeight: '700' }, 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 }, + btnGlobal: { flex: 1, flexDirection: 'column', alignItems: 'center', paddingVertical: 18, borderRadius: 20, borderWidth: 1.5, elevation: 0 }, + btnGlobalText: { fontSize: 13, fontWeight: '900', marginTop: 8 }, - sectionTitle: { fontSize: 14, fontWeight: '900', textTransform: 'uppercase', color: '#64748B', marginBottom: 15, marginLeft: 5, letterSpacing: 1 }, + sectionTitle: { fontSize: 13, fontWeight: '900', textTransform: 'uppercase', color: '#64748B', letterSpacing: 1 }, - card: { padding: 20, borderRadius: 20, borderWidth: 1, marginBottom: 20 }, + card: { padding: 20, borderRadius: 20, borderWidth: 1, marginBottom: 20, elevation: 1, shadowOpacity: 0.05, shadowRadius: 10 }, cardHeader: { flexDirection: 'row', alignItems: 'center' }, avatar: { width: 44, height: 44, borderRadius: 22, justifyContent: 'center', alignItems: 'center' }, - alunoName: { fontSize: 16, fontWeight: '900' }, + alunoName: { fontSize: 16, fontWeight: '900', letterSpacing: -0.5 }, alunoSub: { fontSize: 12, fontWeight: '600', marginTop: 2 }, divider: { height: 1, marginVertical: 15 }, diff --git a/package-lock.json b/package-lock.json index f09d608..9788081 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4446,29 +4446,6 @@ "node-int64": "^0.4.0" } }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -11934,6 +11911,29 @@ "node": ">=10" } }, + "node_modules/whatwg-url-without-unicode/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/whatwg-url-without-unicode/node_modules/webidl-conversions": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz",