feat: enhance email monitoring and management features
- Added a new section for monitoring email status, allowing users to track the email queue and identify sending issues. - Updated the backend to support new internal queries for listing pending emails and retrieving email configurations. - Refactored email-related mutations to improve error handling and streamline the email sending process. - Enhanced the overall email management experience by providing clearer feedback and monitoring capabilities.
This commit is contained in:
@@ -218,6 +218,19 @@
|
||||
palette: "secondary",
|
||||
icon: "envelope",
|
||||
},
|
||||
{
|
||||
title: "Monitoramento de Emails",
|
||||
description:
|
||||
"Acompanhe o status da fila de emails, identifique problemas de envio e processe manualmente quando necessário.",
|
||||
ctaLabel: "Monitorar Emails",
|
||||
href: "/ti/monitoramento-emails",
|
||||
palette: "info",
|
||||
icon: "envelope",
|
||||
highlightBadges: [
|
||||
{ label: "Tempo Real", variant: "solid" },
|
||||
{ label: "Debug", variant: "outline" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Gerenciar Usuários",
|
||||
description:
|
||||
|
||||
@@ -0,0 +1,265 @@
|
||||
<script lang="ts">
|
||||
import { useQuery, useConvexClient } from "convex-svelte";
|
||||
import { api } from "@sgse-app/backend/convex/_generated/api";
|
||||
|
||||
let autoRefresh = $state(true);
|
||||
let refreshInterval: ReturnType<typeof setInterval> | null = null;
|
||||
let processando = $state(false);
|
||||
|
||||
const client = useConvexClient();
|
||||
const estatisticas = useQuery(api.email.obterEstatisticasFilaEmails, {});
|
||||
const filaEmails = useQuery(api.email.listarFilaEmails, { limite: 50 });
|
||||
|
||||
$effect(() => {
|
||||
if (autoRefresh) {
|
||||
refreshInterval = setInterval(() => {
|
||||
// Forçar refresh das queries invalidando o cache
|
||||
// As queries do Convex Svelte atualizam automaticamente
|
||||
}, 5000); // Refresh a cada 5 segundos
|
||||
} else {
|
||||
if (refreshInterval) {
|
||||
clearInterval(refreshInterval);
|
||||
refreshInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (refreshInterval) {
|
||||
clearInterval(refreshInterval);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
async function processarFilaManual() {
|
||||
if (processando) return;
|
||||
|
||||
try {
|
||||
processando = true;
|
||||
const resultado = await client.action(api.email.processarFilaEmailsManual, { limite: 10 });
|
||||
|
||||
if (resultado.sucesso) {
|
||||
alert(`✅ Processados: ${resultado.processados}, Falhas: ${resultado.falhas}`);
|
||||
} else {
|
||||
alert(`❌ Erro: ${resultado.erro || "Erro desconhecido"}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Erro ao processar fila:", error);
|
||||
alert("Erro ao processar fila de emails");
|
||||
} finally {
|
||||
processando = false;
|
||||
}
|
||||
}
|
||||
|
||||
function formatarData(timestamp: number | undefined): string {
|
||||
if (!timestamp) return "-";
|
||||
return new Date(timestamp).toLocaleString("pt-BR");
|
||||
}
|
||||
|
||||
function getStatusBadgeClass(status: string): string {
|
||||
switch (status) {
|
||||
case "pendente":
|
||||
return "badge badge-warning";
|
||||
case "enviando":
|
||||
return "badge badge-info";
|
||||
case "enviado":
|
||||
return "badge badge-success";
|
||||
case "falha":
|
||||
return "badge badge-error";
|
||||
default:
|
||||
return "badge badge-ghost";
|
||||
}
|
||||
}
|
||||
|
||||
function getStatusLabel(status: string): string {
|
||||
switch (status) {
|
||||
case "pendente":
|
||||
return "Pendente";
|
||||
case "enviando":
|
||||
return "Enviando";
|
||||
case "enviado":
|
||||
return "Enviado";
|
||||
case "falha":
|
||||
return "Falha";
|
||||
default:
|
||||
return status;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container mx-auto p-6">
|
||||
<div class="mb-6">
|
||||
<h1 class="text-3xl font-bold mb-2">📧 Monitoramento de Emails</h1>
|
||||
<p class="text-base-content/70">
|
||||
Acompanhe o status da fila de emails e identifique problemas de envio
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Estatísticas -->
|
||||
{#if estatisticas?.data}
|
||||
<div class="grid grid-cols-1 md:grid-cols-5 gap-4 mb-6">
|
||||
<div class="stat bg-base-200 rounded-lg shadow">
|
||||
<div class="stat-title">Total</div>
|
||||
<div class="stat-value text-2xl">{estatisticas.data.total}</div>
|
||||
<div class="stat-desc">Emails na fila</div>
|
||||
</div>
|
||||
|
||||
<div class="stat bg-warning/10 rounded-lg shadow">
|
||||
<div class="stat-title">Pendentes</div>
|
||||
<div class="stat-value text-2xl text-warning">{estatisticas.data.pendentes}</div>
|
||||
<div class="stat-desc">Aguardando envio</div>
|
||||
</div>
|
||||
|
||||
<div class="stat bg-info/10 rounded-lg shadow">
|
||||
<div class="stat-title">Enviando</div>
|
||||
<div class="stat-value text-2xl text-info">{estatisticas.data.enviando}</div>
|
||||
<div class="stat-desc">Em processamento</div>
|
||||
</div>
|
||||
|
||||
<div class="stat bg-success/10 rounded-lg shadow">
|
||||
<div class="stat-title">Enviados</div>
|
||||
<div class="stat-value text-2xl text-success">{estatisticas.data.enviados}</div>
|
||||
<div class="stat-desc">Concluídos</div>
|
||||
</div>
|
||||
|
||||
<div class="stat bg-error/10 rounded-lg shadow">
|
||||
<div class="stat-title">Falhas</div>
|
||||
<div class="stat-value text-2xl text-error">{estatisticas.data.falhas}</div>
|
||||
<div class="stat-desc">Com erro</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else if estatisticas === undefined}
|
||||
<div class="text-center py-8">
|
||||
<span class="loading loading-spinner loading-lg"></span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Controles -->
|
||||
<div class="card bg-base-100 shadow-xl mb-6">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="form-control">
|
||||
<label class="label cursor-pointer gap-4">
|
||||
<span class="label-text">Atualização automática</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="toggle toggle-primary"
|
||||
bind:checked={autoRefresh}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
onclick={processarFilaManual}
|
||||
disabled={processando}
|
||||
>
|
||||
{#if processando}
|
||||
<span class="loading loading-spinner loading-sm"></span>
|
||||
Processando...
|
||||
{:else}
|
||||
🔄 Processar Fila Manualmente
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Lista de Emails -->
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title mb-4">Fila de Emails</h2>
|
||||
|
||||
{#if filaEmails?.data && filaEmails.data.length > 0}
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table table-zebra w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Destinatário</th>
|
||||
<th>Assunto</th>
|
||||
<th>Status</th>
|
||||
<th>Tentativas</th>
|
||||
<th>Criado em</th>
|
||||
<th>Última tentativa</th>
|
||||
<th>Erro</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each filaEmails.data as email}
|
||||
<tr>
|
||||
<td>
|
||||
<div class="font-medium">{email.destinatario}</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="max-w-xs truncate" title={email.assunto}>
|
||||
{email.assunto}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class={getStatusBadgeClass(email.status)}>
|
||||
{getStatusLabel(email.status)}
|
||||
</span>
|
||||
</td>
|
||||
<td>{email.tentativas || 0}</td>
|
||||
<td class="text-sm">{formatarData(email.criadoEm)}</td>
|
||||
<td class="text-sm">
|
||||
{formatarData(email.ultimaTentativa)}
|
||||
</td>
|
||||
<td>
|
||||
{#if email.erroDetalhes}
|
||||
<div
|
||||
class="tooltip tooltip-left"
|
||||
data-tip={email.erroDetalhes}
|
||||
>
|
||||
<span class="text-error text-xs cursor-help">
|
||||
⚠️ Ver erro
|
||||
</span>
|
||||
</div>
|
||||
{:else}
|
||||
<span class="text-base-content/50">-</span>
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{:else if filaEmails?.data !== undefined}
|
||||
<div class="text-center py-8 text-base-content/50">
|
||||
<p>Nenhum email na fila</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-center py-8">
|
||||
<span class="loading loading-spinner loading-lg"></span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dicas de Troubleshooting -->
|
||||
<div class="card bg-base-200 shadow-xl mt-6">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">🔍 Troubleshooting</h2>
|
||||
<div class="space-y-2 text-sm">
|
||||
<p>
|
||||
<strong>Emails pendentes não estão sendo enviados?</strong>
|
||||
</p>
|
||||
<ul class="list-disc list-inside ml-4 space-y-1">
|
||||
<li>Verifique se a configuração SMTP está ativa em Configurações de Email</li>
|
||||
<li>Confirme se o cron job está rodando (verifique logs do Convex)</li>
|
||||
<li>Clique em "Processar Fila Manualmente" para forçar o processamento</li>
|
||||
</ul>
|
||||
|
||||
<p class="mt-4">
|
||||
<strong>Emails com status "Falha"?</strong>
|
||||
</p>
|
||||
<ul class="list-disc list-inside ml-4 space-y-1">
|
||||
<li>Verifique as credenciais SMTP em Configurações de Email</li>
|
||||
<li>Confirme se o servidor SMTP está acessível</li>
|
||||
<li>Verifique os logs do Convex para detalhes do erro</li>
|
||||
<li>Teste a conexão SMTP na página de configurações</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user