feat: enhance audit page by adding user information retrieval and improving CSV export format, providing better insights and clarity in reports
This commit is contained in:
@@ -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,9 +208,18 @@
|
||||
"Detalhes": atividade.detalhes || "-"
|
||||
}));
|
||||
} else {
|
||||
csvData = dadosParaExportar.map((login: any) => ({
|
||||
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/Email": login.matriculaOuEmail,
|
||||
"Usuário": login.usuarioNome || "-",
|
||||
"Email": login.usuarioEmail || login.matriculaOuEmail || "-",
|
||||
"Status": login.sucesso ? "Sucesso" : "Falhou",
|
||||
"Motivo Falha": login.motivoFalha || "-",
|
||||
"IP": login.ipAddress || "-",
|
||||
@@ -208,7 +227,8 @@
|
||||
"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<HTMLImageElement>((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) => [
|
||||
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),
|
||||
login.matriculaOuEmail,
|
||||
usuarioStr,
|
||||
login.sucesso ? "Sucesso" : "Falhou",
|
||||
login.ipAddress || "-",
|
||||
formatarLocalizacao(login).substring(0, 30),
|
||||
login.device || "-",
|
||||
login.browser || "-"
|
||||
]);
|
||||
];
|
||||
});
|
||||
|
||||
autoTable(doc, {
|
||||
startY: yPos,
|
||||
@@ -700,11 +754,27 @@
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="flex flex-col gap-1">
|
||||
{#if login.usuarioNome}
|
||||
<div class="flex items-center gap-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-base-content/40" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
||||
</svg>
|
||||
<span class="text-sm font-medium">{login.matriculaOuEmail}</span>
|
||||
<span class="text-sm font-semibold">{login.usuarioNome}</span>
|
||||
</div>
|
||||
{/if}
|
||||
{#if login.usuarioEmail || login.matriculaOuEmail}
|
||||
<div class="flex items-center gap-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3 text-base-content/40" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
||||
</svg>
|
||||
<span class="text-xs text-base-content/70 font-mono">
|
||||
{login.usuarioEmail || (login.matriculaOuEmail && typeof login.matriculaOuEmail === 'string' ? login.matriculaOuEmail : "-")}
|
||||
</span>
|
||||
</div>
|
||||
{:else}
|
||||
<span class="text-xs text-base-content/40">-</span>
|
||||
{/if}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user