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 jsPDF from "jspdf";
|
||||||
import autoTable from "jspdf-autotable";
|
import autoTable from "jspdf-autotable";
|
||||||
import Papa from "papaparse";
|
import Papa from "papaparse";
|
||||||
|
import logoGovPE from "$lib/assets/logo_governo_PE.png";
|
||||||
|
|
||||||
let abaAtiva = $state<"atividades" | "logins">("atividades");
|
let abaAtiva = $state<"atividades" | "logins">("atividades");
|
||||||
let limite = $state(50);
|
let limite = $state(50);
|
||||||
@@ -96,9 +97,12 @@
|
|||||||
}
|
}
|
||||||
if (filtroUsuario) {
|
if (filtroUsuario) {
|
||||||
const usuarioLower = filtroUsuario.toLowerCase();
|
const usuarioLower = filtroUsuario.toLowerCase();
|
||||||
filtrados = filtrados.filter(l =>
|
filtrados = filtrados.filter(l => {
|
||||||
l.matriculaOuEmail.toLowerCase().includes(usuarioLower)
|
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") {
|
if (filtroStatus !== "todos") {
|
||||||
filtrados = filtrados.filter(l =>
|
filtrados = filtrados.filter(l =>
|
||||||
@@ -125,21 +129,27 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatarLocalizacao(login: { cidade?: string; estado?: string; pais?: string; endereco?: string } | undefined): string {
|
function formatarLocalizacao(login: any): string {
|
||||||
if (!login) return "-";
|
if (!login || typeof login !== 'object') return "-";
|
||||||
|
|
||||||
const partes: string[] = [];
|
const partes: string[] = [];
|
||||||
if (login.cidade) partes.push(login.cidade);
|
if (login.cidade && typeof login.cidade === 'string' && login.cidade.trim()) {
|
||||||
if (login.estado) partes.push(login.estado);
|
partes.push(login.cidade.trim());
|
||||||
if (login.pais) partes.push(login.pais);
|
}
|
||||||
|
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) {
|
if (partes.length > 0) {
|
||||||
return partes.join(", ");
|
return partes.join(", ");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Se não tiver cidade/estado/pais, mas tiver endereco, mostrar endereco
|
// Se não tiver cidade/estado/pais, mas tiver endereco, mostrar endereco
|
||||||
if (login.endereco) {
|
if (login.endereco && typeof login.endereco === 'string' && login.endereco.trim()) {
|
||||||
return login.endereco;
|
return login.endereco.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
return "-";
|
return "-";
|
||||||
@@ -198,17 +208,27 @@
|
|||||||
"Detalhes": atividade.detalhes || "-"
|
"Detalhes": atividade.detalhes || "-"
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
csvData = dadosParaExportar.map((login: any) => ({
|
csvData = dadosParaExportar.map((login: any) => {
|
||||||
"Data/Hora": formatarData(login.timestamp),
|
const usuarioInfo = [];
|
||||||
"Usuário/Email": login.matriculaOuEmail,
|
if (login.usuarioNome) usuarioInfo.push(login.usuarioNome);
|
||||||
"Status": login.sucesso ? "Sucesso" : "Falhou",
|
if (login.usuarioEmail) usuarioInfo.push(login.usuarioEmail);
|
||||||
"Motivo Falha": login.motivoFalha || "-",
|
if (!login.usuarioNome && !login.usuarioEmail && login.matriculaOuEmail) {
|
||||||
"IP": login.ipAddress || "-",
|
usuarioInfo.push(login.matriculaOuEmail);
|
||||||
"Localização": formatarLocalizacao(login),
|
}
|
||||||
"Dispositivo": login.device || "-",
|
|
||||||
"Navegador": login.browser || "-",
|
return {
|
||||||
"Sistema": login.sistema || "-"
|
"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);
|
const csv = Papa.unparse(csvData);
|
||||||
@@ -245,25 +265,49 @@
|
|||||||
|
|
||||||
const doc = new jsPDF();
|
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
|
// Título
|
||||||
doc.setFontSize(20);
|
doc.setFontSize(20);
|
||||||
doc.setTextColor(102, 126, 234);
|
doc.setTextColor(102, 126, 234);
|
||||||
const titulo = abaAtiva === "atividades"
|
const titulo = abaAtiva === "atividades"
|
||||||
? "Relatório de Atividades do Sistema"
|
? "Relatório de Atividades do Sistema"
|
||||||
: "Relatório de Histórico de Logins";
|
: "Relatório de Histórico de Logins";
|
||||||
doc.text(titulo, 14, 20);
|
doc.text(titulo, 14, yPos);
|
||||||
|
|
||||||
// Informações gerais
|
// Informações gerais
|
||||||
doc.setFontSize(12);
|
doc.setFontSize(12);
|
||||||
doc.setTextColor(0, 0, 0);
|
doc.setTextColor(0, 0, 0);
|
||||||
|
yPos += 8;
|
||||||
doc.text(
|
doc.text(
|
||||||
`Gerado em: ${format(new Date(), "dd/MM/yyyy HH:mm", { locale: ptBR })}`,
|
`Gerado em: ${format(new Date(), "dd/MM/yyyy HH:mm", { locale: ptBR })}`,
|
||||||
14,
|
14,
|
||||||
30
|
yPos
|
||||||
);
|
);
|
||||||
doc.text(`Total de registros: ${dadosParaExportar.length}`, 14, 36);
|
yPos += 6;
|
||||||
|
doc.text(`Total de registros: ${dadosParaExportar.length}`, 14, yPos);
|
||||||
let yPos = 50;
|
yPos += 10;
|
||||||
|
|
||||||
if (abaAtiva === "atividades") {
|
if (abaAtiva === "atividades") {
|
||||||
// Tabela de atividades
|
// Tabela de atividades
|
||||||
@@ -285,15 +329,25 @@
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Tabela de logins
|
// Tabela de logins
|
||||||
const loginsData = dadosParaExportar.map((login: any) => [
|
const loginsData = dadosParaExportar.map((login: any) => {
|
||||||
formatarData(login.timestamp),
|
const usuarioInfo = [];
|
||||||
login.matriculaOuEmail,
|
if (login.usuarioNome) usuarioInfo.push(login.usuarioNome);
|
||||||
login.sucesso ? "Sucesso" : "Falhou",
|
if (login.usuarioEmail) usuarioInfo.push(login.usuarioEmail);
|
||||||
login.ipAddress || "-",
|
if (!login.usuarioNome && !login.usuarioEmail && login.matriculaOuEmail) {
|
||||||
formatarLocalizacao(login).substring(0, 30),
|
usuarioInfo.push(login.matriculaOuEmail);
|
||||||
login.device || "-",
|
}
|
||||||
login.browser || "-"
|
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, {
|
autoTable(doc, {
|
||||||
startY: yPos,
|
startY: yPos,
|
||||||
@@ -700,11 +754,27 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex flex-col gap-1">
|
||||||
<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">
|
{#if login.usuarioNome}
|
||||||
<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" />
|
<div class="flex items-center gap-2">
|
||||||
</svg>
|
<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">
|
||||||
<span class="text-sm font-medium">{login.matriculaOuEmail}</span>
|
<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-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>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
|||||||
@@ -421,7 +421,29 @@ export const listarTodosLogins = query({
|
|||||||
.order("desc")
|
.order("desc")
|
||||||
.take(args.limite || 50);
|
.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