From f1c2ae0e6b0125d7e3ed6b7bc3ce12e4e5a7ab4b Mon Sep 17 00:00:00 2001 From: deyvisonwanderley Date: Sun, 30 Nov 2025 08:42:21 -0300 Subject: [PATCH] feat: enhance audit page by adding user information retrieval and improving CSV export format, providing better insights and clarity in reports --- .../(dashboard)/ti/auditoria/+page.svelte | 150 +++++++++++++----- packages/backend/convex/logsLogin.ts | 24 ++- 2 files changed, 133 insertions(+), 41 deletions(-) diff --git a/apps/web/src/routes/(dashboard)/ti/auditoria/+page.svelte b/apps/web/src/routes/(dashboard)/ti/auditoria/+page.svelte index 6f345dd..8a42d70 100644 --- a/apps/web/src/routes/(dashboard)/ti/auditoria/+page.svelte +++ b/apps/web/src/routes/(dashboard)/ti/auditoria/+page.svelte @@ -7,6 +7,7 @@ import jsPDF from "jspdf"; import autoTable from "jspdf-autotable"; import Papa from "papaparse"; + import logoGovPE from "$lib/assets/logo_governo_PE.png"; let abaAtiva = $state<"atividades" | "logins">("atividades"); let limite = $state(50); @@ -96,9 +97,12 @@ } if (filtroUsuario) { const usuarioLower = filtroUsuario.toLowerCase(); - filtrados = filtrados.filter(l => - l.matriculaOuEmail.toLowerCase().includes(usuarioLower) - ); + filtrados = filtrados.filter(l => { + const nomeMatch = l.usuarioNome?.toLowerCase().includes(usuarioLower); + const emailMatch = l.usuarioEmail?.toLowerCase().includes(usuarioLower); + const matriculaMatch = l.matriculaOuEmail?.toLowerCase().includes(usuarioLower); + return nomeMatch || emailMatch || matriculaMatch; + }); } if (filtroStatus !== "todos") { filtrados = filtrados.filter(l => @@ -125,21 +129,27 @@ }); } - function formatarLocalizacao(login: { cidade?: string; estado?: string; pais?: string; endereco?: string } | undefined): string { - if (!login) return "-"; + function formatarLocalizacao(login: any): string { + if (!login || typeof login !== 'object') return "-"; const partes: string[] = []; - if (login.cidade) partes.push(login.cidade); - if (login.estado) partes.push(login.estado); - if (login.pais) partes.push(login.pais); + if (login.cidade && typeof login.cidade === 'string' && login.cidade.trim()) { + partes.push(login.cidade.trim()); + } + if (login.estado && typeof login.estado === 'string' && login.estado.trim()) { + partes.push(login.estado.trim()); + } + if (login.pais && typeof login.pais === 'string' && login.pais.trim()) { + partes.push(login.pais.trim()); + } if (partes.length > 0) { return partes.join(", "); } // Se não tiver cidade/estado/pais, mas tiver endereco, mostrar endereco - if (login.endereco) { - return login.endereco; + if (login.endereco && typeof login.endereco === 'string' && login.endereco.trim()) { + return login.endereco.trim(); } return "-"; @@ -198,17 +208,27 @@ "Detalhes": atividade.detalhes || "-" })); } else { - csvData = dadosParaExportar.map((login: any) => ({ - "Data/Hora": formatarData(login.timestamp), - "Usuário/Email": login.matriculaOuEmail, - "Status": login.sucesso ? "Sucesso" : "Falhou", - "Motivo Falha": login.motivoFalha || "-", - "IP": login.ipAddress || "-", - "Localização": formatarLocalizacao(login), - "Dispositivo": login.device || "-", - "Navegador": login.browser || "-", - "Sistema": login.sistema || "-" - })); + csvData = dadosParaExportar.map((login: any) => { + const usuarioInfo = []; + if (login.usuarioNome) usuarioInfo.push(login.usuarioNome); + if (login.usuarioEmail) usuarioInfo.push(login.usuarioEmail); + if (!login.usuarioNome && !login.usuarioEmail && login.matriculaOuEmail) { + usuarioInfo.push(login.matriculaOuEmail); + } + + return { + "Data/Hora": formatarData(login.timestamp), + "Usuário": login.usuarioNome || "-", + "Email": login.usuarioEmail || login.matriculaOuEmail || "-", + "Status": login.sucesso ? "Sucesso" : "Falhou", + "Motivo Falha": login.motivoFalha || "-", + "IP": login.ipAddress || "-", + "Localização": formatarLocalizacao(login), + "Dispositivo": login.device || "-", + "Navegador": login.browser || "-", + "Sistema": login.sistema || "-" + }; + }); } const csv = Papa.unparse(csvData); @@ -245,25 +265,49 @@ const doc = new jsPDF(); + // Adicionar logo no canto superior esquerdo + let yPos = 20; + try { + const logoImg = await new Promise((resolve, reject) => { + const img = new Image(); + img.crossOrigin = 'anonymous'; + img.onload = () => resolve(img); + img.onerror = (err) => reject(err); + setTimeout(() => reject(new Error('Timeout loading logo')), 3000); + img.src = logoGovPE; + }); + + const logoWidth = 25; + const aspectRatio = logoImg.height / logoImg.width; + const logoHeight = logoWidth * aspectRatio; + + doc.addImage(logoImg, 'PNG', 15, 10, logoWidth, logoHeight); + yPos = 10 + logoHeight + 10; + } catch (err) { + console.warn('Erro ao carregar logo:', err); + yPos = 20; + } + // Título doc.setFontSize(20); doc.setTextColor(102, 126, 234); const titulo = abaAtiva === "atividades" ? "Relatório de Atividades do Sistema" : "Relatório de Histórico de Logins"; - doc.text(titulo, 14, 20); + doc.text(titulo, 14, yPos); // Informações gerais doc.setFontSize(12); doc.setTextColor(0, 0, 0); + yPos += 8; doc.text( `Gerado em: ${format(new Date(), "dd/MM/yyyy HH:mm", { locale: ptBR })}`, 14, - 30 + yPos ); - doc.text(`Total de registros: ${dadosParaExportar.length}`, 14, 36); - - let yPos = 50; + yPos += 6; + doc.text(`Total de registros: ${dadosParaExportar.length}`, 14, yPos); + yPos += 10; if (abaAtiva === "atividades") { // Tabela de atividades @@ -285,15 +329,25 @@ }); } else { // Tabela de logins - const loginsData = dadosParaExportar.map((login: any) => [ - formatarData(login.timestamp), - login.matriculaOuEmail, - login.sucesso ? "Sucesso" : "Falhou", - login.ipAddress || "-", - formatarLocalizacao(login).substring(0, 30), - login.device || "-", - login.browser || "-" - ]); + const loginsData = dadosParaExportar.map((login: any) => { + const usuarioInfo = []; + if (login.usuarioNome) usuarioInfo.push(login.usuarioNome); + if (login.usuarioEmail) usuarioInfo.push(login.usuarioEmail); + if (!login.usuarioNome && !login.usuarioEmail && login.matriculaOuEmail) { + usuarioInfo.push(login.matriculaOuEmail); + } + const usuarioStr = usuarioInfo.length > 0 ? usuarioInfo.join(" / ") : "-"; + + return [ + formatarData(login.timestamp), + usuarioStr, + login.sucesso ? "Sucesso" : "Falhou", + login.ipAddress || "-", + formatarLocalizacao(login).substring(0, 30), + login.device || "-", + login.browser || "-" + ]; + }); autoTable(doc, { startY: yPos, @@ -700,11 +754,27 @@ -
- - - - {login.matriculaOuEmail} +
+ {#if login.usuarioNome} +
+ + + + {login.usuarioNome} +
+ {/if} + {#if login.usuarioEmail || login.matriculaOuEmail} +
+ + + + + {login.usuarioEmail || (login.matriculaOuEmail && typeof login.matriculaOuEmail === 'string' ? login.matriculaOuEmail : "-")} + +
+ {:else} + - + {/if}
diff --git a/packages/backend/convex/logsLogin.ts b/packages/backend/convex/logsLogin.ts index 3911716..8219b49 100644 --- a/packages/backend/convex/logsLogin.ts +++ b/packages/backend/convex/logsLogin.ts @@ -421,7 +421,29 @@ export const listarTodosLogins = query({ .order("desc") .take(args.limite || 50); - return logs; + // Buscar informações dos usuários quando disponível + const logsComUsuarios = await Promise.all( + logs.map(async (log) => { + let usuarioNome: string | undefined = undefined; + let usuarioEmail: string | undefined = undefined; + + if (log.usuarioId) { + const usuario = await ctx.db.get(log.usuarioId); + if (usuario) { + usuarioNome = usuario.nome; + usuarioEmail = usuario.email; + } + } + + return { + ...log, + usuarioNome, + usuarioEmail, + }; + }) + ); + + return logsComUsuarios; }, });