Revert "Feat many fixes"
This commit is contained in:
@@ -1,568 +1,463 @@
|
||||
<script lang="ts">
|
||||
import { useQuery, useConvexClient } from 'convex-svelte';
|
||||
import { api } from '@sgse-app/backend/convex/_generated/api';
|
||||
import type { Doc } from '@sgse-app/backend/convex/_generated/dataModel';
|
||||
import { useQuery, useConvexClient } from "convex-svelte";
|
||||
import { api } from "@sgse-app/backend/convex/_generated/api";
|
||||
import type { Doc } from "@sgse-app/backend/convex/_generated/dataModel";
|
||||
|
||||
type EmailDetalhes = Doc<'notificacoesEmail'> | null;
|
||||
type EmailDetalhes = Doc<"notificacoesEmail"> | null;
|
||||
|
||||
let autoRefresh = $state(true);
|
||||
let refreshInterval: ReturnType<typeof setInterval> | null = null;
|
||||
let processando = $state(false);
|
||||
let modalDetalhesAberto = $state(false);
|
||||
let emailSelecionado = $state<EmailDetalhes>(null);
|
||||
let autoRefresh = $state(true);
|
||||
let refreshInterval: ReturnType<typeof setInterval> | null = null;
|
||||
let processando = $state(false);
|
||||
let modalDetalhesAberto = $state(false);
|
||||
let emailSelecionado = $state<EmailDetalhes>(null);
|
||||
|
||||
const client = useConvexClient();
|
||||
const client = useConvexClient();
|
||||
|
||||
// Criar uma chave reativa para forçar atualização das queries
|
||||
let refreshKey = $state(0);
|
||||
|
||||
// Usar refreshKey nos argumentos para forçar recarregamento quando mudar
|
||||
// O backend ignora esse parâmetro, mas força o Convex Svelte a reexecutar a query
|
||||
const estatisticas = useQuery(api.email.obterEstatisticasFilaEmails, { _refresh: refreshKey });
|
||||
const filaEmails = useQuery(api.email.listarFilaEmails, { limite: 50, _refresh: refreshKey });
|
||||
|
||||
// Criar uma chave reativa para forçar atualização das queries
|
||||
let refreshKey = $state(0);
|
||||
// Função para forçar refresh das queries
|
||||
function refreshQueries() {
|
||||
refreshKey++;
|
||||
}
|
||||
|
||||
// Usar refreshKey nos argumentos para forçar recarregamento quando mudar
|
||||
// O backend ignora esse parâmetro, mas força o Convex Svelte a reexecutar a query
|
||||
const estatisticas = useQuery(api.email.obterEstatisticasFilaEmails, { _refresh: refreshKey });
|
||||
const filaEmails = useQuery(api.email.listarFilaEmails, { limite: 50, _refresh: refreshKey });
|
||||
$effect(() => {
|
||||
if (autoRefresh) {
|
||||
refreshInterval = setInterval(() => {
|
||||
refreshQueries();
|
||||
}, 5000); // Refresh a cada 5 segundos
|
||||
} else {
|
||||
if (refreshInterval) {
|
||||
clearInterval(refreshInterval);
|
||||
refreshInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Função para forçar refresh das queries
|
||||
function refreshQueries() {
|
||||
refreshKey++;
|
||||
}
|
||||
return () => {
|
||||
if (refreshInterval) {
|
||||
clearInterval(refreshInterval);
|
||||
refreshInterval = null;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (autoRefresh) {
|
||||
refreshInterval = setInterval(() => {
|
||||
refreshQueries();
|
||||
}, 5000); // Refresh a cada 5 segundos
|
||||
} else {
|
||||
if (refreshInterval) {
|
||||
clearInterval(refreshInterval);
|
||||
refreshInterval = null;
|
||||
}
|
||||
}
|
||||
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}`);
|
||||
// Forçar atualização após processar
|
||||
refreshQueries();
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (refreshInterval) {
|
||||
clearInterval(refreshInterval);
|
||||
refreshInterval = null;
|
||||
}
|
||||
};
|
||||
});
|
||||
function formatarData(timestamp: number | undefined): string {
|
||||
if (!timestamp) return "-";
|
||||
return new Date(timestamp).toLocaleString("pt-BR");
|
||||
}
|
||||
|
||||
async function processarFilaManual() {
|
||||
if (processando) return;
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
processando = true;
|
||||
const resultado = await client.action(api.email.processarFilaEmailsManual, { limite: 10 });
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (resultado.sucesso) {
|
||||
alert(`✅ Processados: ${resultado.processados}, Falhas: ${resultado.falhas}`);
|
||||
// Forçar atualização após processar
|
||||
refreshQueries();
|
||||
} 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 abrirModalDetalhes(email: Doc<"notificacoesEmail">) {
|
||||
emailSelecionado = email;
|
||||
modalDetalhesAberto = true;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
function abrirModalDetalhes(email: Doc<'notificacoesEmail'>) {
|
||||
emailSelecionado = email;
|
||||
modalDetalhesAberto = true;
|
||||
}
|
||||
|
||||
function fecharModalDetalhes() {
|
||||
modalDetalhesAberto = false;
|
||||
emailSelecionado = null;
|
||||
}
|
||||
function fecharModalDetalhes() {
|
||||
modalDetalhesAberto = false;
|
||||
emailSelecionado = null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container mx-auto p-6">
|
||||
<div class="mb-6">
|
||||
<h1 class="mb-2 text-3xl font-bold">📧 Monitoramento de Emails</h1>
|
||||
<p class="text-base-content/70">
|
||||
Acompanhe o status da fila de emails e identifique problemas de envio
|
||||
</p>
|
||||
</div>
|
||||
<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="mb-6 grid grid-cols-1 gap-4 md:grid-cols-5">
|
||||
<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>
|
||||
<!-- 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-warning text-2xl">{estatisticas.data.pendentes}</div>
|
||||
<div class="stat-desc">Aguardando envio</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-info text-2xl">{estatisticas.data.enviando}</div>
|
||||
<div class="stat-desc">Em processamento</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-success text-2xl">{estatisticas.data.enviados}</div>
|
||||
<div class="stat-desc">Concluídos</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-error text-2xl">{estatisticas.data.falhas}</div>
|
||||
<div class="stat-desc">Com erro</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else if estatisticas === undefined}
|
||||
<div class="py-8 text-center">
|
||||
<span class="loading loading-spinner loading-lg"></span>
|
||||
</div>
|
||||
{/if}
|
||||
<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 mb-6 shadow-xl">
|
||||
<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>
|
||||
<!-- 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>
|
||||
<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>
|
||||
<!-- 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-zebra table 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>
|
||||
<th>Ações</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}
|
||||
<button
|
||||
class="btn btn-xs text-error"
|
||||
onclick={() => abrirModalDetalhes(email)}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
||||
/>
|
||||
</svg>
|
||||
Ver erro
|
||||
</button>
|
||||
{:else}
|
||||
<span class="text-base-content/50">-</span>
|
||||
{/if}
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
class="btn btn-xs"
|
||||
onclick={() => abrirModalDetalhes(email)}
|
||||
title="Ver detalhes"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
||||
/>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{:else if filaEmails?.data !== undefined}
|
||||
<div class="text-base-content/50 py-8 text-center">
|
||||
<p>Nenhum email na fila</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="py-8 text-center">
|
||||
<span class="loading loading-spinner loading-lg"></span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#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>
|
||||
<th>Ações</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}
|
||||
<button
|
||||
class="btn btn-ghost btn-xs text-error"
|
||||
onclick={() => abrirModalDetalhes(email)}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||
</svg>
|
||||
Ver erro
|
||||
</button>
|
||||
{:else}
|
||||
<span class="text-base-content/50">-</span>
|
||||
{/if}
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
class="btn btn-ghost btn-xs"
|
||||
onclick={() => abrirModalDetalhes(email)}
|
||||
title="Ver detalhes"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
||||
</svg>
|
||||
</button>
|
||||
</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 mt-6 shadow-xl">
|
||||
<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="ml-4 list-inside list-disc 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>
|
||||
<!-- 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="ml-4 list-inside list-disc 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>
|
||||
<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>
|
||||
|
||||
<!-- Modal de Detalhes do Email -->
|
||||
{#if modalDetalhesAberto && emailSelecionado}
|
||||
<div class="modal modal-open">
|
||||
<div class="modal-box max-w-4xl">
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<h3 class="flex items-center gap-2 text-2xl font-bold">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="text-primary h-6 w-6"
|
||||
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>
|
||||
Detalhes do Email
|
||||
</h3>
|
||||
<button class="btn btn-sm btn-circle btn-ghost" onclick={fecharModalDetalhes}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal modal-open">
|
||||
<div class="modal-box max-w-4xl">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="font-bold text-2xl flex items-center gap-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-primary" 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>
|
||||
Detalhes do Email
|
||||
</h3>
|
||||
<button class="btn btn-sm btn-circle btn-ghost" onclick={fecharModalDetalhes}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<!-- Status -->
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-semibold">Status:</span>
|
||||
<span class={getStatusBadgeClass(emailSelecionado.status)}>
|
||||
{getStatusLabel(emailSelecionado.status)}
|
||||
</span>
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<!-- Status -->
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-semibold">Status:</span>
|
||||
<span class={getStatusBadgeClass(emailSelecionado.status)}>
|
||||
{getStatusLabel(emailSelecionado.status)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Informações Principais -->
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div>
|
||||
<label class="label">
|
||||
<span class="label-text font-semibold">Destinatário</span>
|
||||
</label>
|
||||
<div class="input input-bordered flex items-center gap-2">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="text-base-content/50 h-4 w-4"
|
||||
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>
|
||||
{emailSelecionado.destinatario}
|
||||
</div>
|
||||
</div>
|
||||
<!-- Informações Principais -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="label">
|
||||
<span class="label-text font-semibold">Destinatário</span>
|
||||
</label>
|
||||
<div class="input input-bordered flex items-center gap-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-base-content/50" 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>
|
||||
{emailSelecionado.destinatario}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="label">
|
||||
<span class="label-text font-semibold">Tentativas</span>
|
||||
</label>
|
||||
<div class="input input-bordered flex items-center gap-2">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="text-base-content/50 h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
|
||||
/>
|
||||
</svg>
|
||||
{emailSelecionado.tentativas || 0}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="label">
|
||||
<span class="label-text font-semibold">Tentativas</span>
|
||||
</label>
|
||||
<div class="input input-bordered flex items-center gap-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-base-content/50" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||
</svg>
|
||||
{emailSelecionado.tentativas || 0}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Assunto -->
|
||||
<div>
|
||||
<label class="label">
|
||||
<span class="label-text font-semibold">Assunto</span>
|
||||
</label>
|
||||
<div class="input input-bordered">
|
||||
{emailSelecionado.assunto}
|
||||
</div>
|
||||
</div>
|
||||
<!-- Assunto -->
|
||||
<div>
|
||||
<label class="label">
|
||||
<span class="label-text font-semibold">Assunto</span>
|
||||
</label>
|
||||
<div class="input input-bordered">
|
||||
{emailSelecionado.assunto}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Datas -->
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div>
|
||||
<label class="label">
|
||||
<span class="label-text font-semibold">Criado em</span>
|
||||
</label>
|
||||
<div class="input input-bordered flex items-center gap-2">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="text-base-content/50 h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
{formatarData(emailSelecionado.criadoEm)}
|
||||
</div>
|
||||
</div>
|
||||
<!-- Datas -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="label">
|
||||
<span class="label-text font-semibold">Criado em</span>
|
||||
</label>
|
||||
<div class="input input-bordered flex items-center gap-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-base-content/50" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
</svg>
|
||||
{formatarData(emailSelecionado.criadoEm)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if emailSelecionado.ultimaTentativa}
|
||||
<div>
|
||||
<label class="label">
|
||||
<span class="label-text font-semibold">Última tentativa</span>
|
||||
</label>
|
||||
<div class="input input-bordered flex items-center gap-2">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="text-base-content/50 h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
{formatarData(emailSelecionado.ultimaTentativa)}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if emailSelecionado.ultimaTentativa}
|
||||
<div>
|
||||
<label class="label">
|
||||
<span class="label-text font-semibold">Última tentativa</span>
|
||||
</label>
|
||||
<div class="input input-bordered flex items-center gap-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-base-content/50" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
{formatarData(emailSelecionado.ultimaTentativa)}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if emailSelecionado.enviadoEm}
|
||||
<div>
|
||||
<label class="label">
|
||||
<span class="label-text font-semibold">Enviado em</span>
|
||||
</label>
|
||||
<div class="input input-bordered flex items-center gap-2">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="text-success h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
{formatarData(emailSelecionado.enviadoEm)}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if emailSelecionado.enviadoEm}
|
||||
<div>
|
||||
<label class="label">
|
||||
<span class="label-text font-semibold">Enviado em</span>
|
||||
</label>
|
||||
<div class="input input-bordered flex items-center gap-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-success" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
{formatarData(emailSelecionado.enviadoEm)}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if emailSelecionado.agendadaPara}
|
||||
<div>
|
||||
<label class="label">
|
||||
<span class="label-text font-semibold">Agendado para</span>
|
||||
</label>
|
||||
<div class="input input-bordered flex items-center gap-2">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="text-info h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
{formatarData(emailSelecionado.agendadaPara)}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if emailSelecionado.agendadaPara}
|
||||
<div>
|
||||
<label class="label">
|
||||
<span class="label-text font-semibold">Agendado para</span>
|
||||
</label>
|
||||
<div class="input input-bordered flex items-center gap-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-info" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
</svg>
|
||||
{formatarData(emailSelecionado.agendadaPara)}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Erro Detalhado -->
|
||||
{#if emailSelecionado.erroDetalhes}
|
||||
<div>
|
||||
<label class="label">
|
||||
<span class="label-text text-error font-semibold">Detalhes do Erro</span>
|
||||
</label>
|
||||
<div class="bg-error/10 border-error/20 rounded-lg border p-4">
|
||||
<pre
|
||||
class="text-error text-sm break-words whitespace-pre-wrap">{emailSelecionado.erroDetalhes}</pre>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<!-- Erro Detalhado -->
|
||||
{#if emailSelecionado.erroDetalhes}
|
||||
<div>
|
||||
<label class="label">
|
||||
<span class="label-text font-semibold text-error">Detalhes do Erro</span>
|
||||
</label>
|
||||
<div class="bg-error/10 border border-error/20 rounded-lg p-4">
|
||||
<pre class="text-sm text-error whitespace-pre-wrap break-words">{emailSelecionado.erroDetalhes}</pre>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Corpo do Email (Preview) -->
|
||||
{#if emailSelecionado.corpo}
|
||||
<div>
|
||||
<label class="label">
|
||||
<span class="label-text font-semibold">Preview do Email</span>
|
||||
</label>
|
||||
<div class="border-base-300 bg-base-200 max-h-64 overflow-y-auto rounded-lg border p-4">
|
||||
{@html emailSelecionado.corpo}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<!-- Corpo do Email (Preview) -->
|
||||
{#if emailSelecionado.corpo}
|
||||
<div>
|
||||
<label class="label">
|
||||
<span class="label-text font-semibold">Preview do Email</span>
|
||||
</label>
|
||||
<div class="border border-base-300 rounded-lg p-4 bg-base-200 max-h-64 overflow-y-auto">
|
||||
{@html emailSelecionado.corpo}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="modal-action">
|
||||
<button class="btn btn-primary" onclick={fecharModalDetalhes}> Fechar </button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-backdrop" onclick={fecharModalDetalhes}></div>
|
||||
</div>
|
||||
<div class="modal-action">
|
||||
<button class="btn btn-primary" onclick={fecharModalDetalhes}>
|
||||
Fechar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-backdrop" onclick={fecharModalDetalhes}></div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<a href={resolve('/ti')} class="btn">
|
||||
<a href={resolve('/ti')} class="btn btn-ghost">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,100 +1,107 @@
|
||||
<script lang="ts">
|
||||
import ProtectedRoute from '$lib/components/ProtectedRoute.svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import ProtectedRoute from "$lib/components/ProtectedRoute.svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
</script>
|
||||
|
||||
<ProtectedRoute allowedRoles={['admin', 'ti']} maxLevel={1}>
|
||||
<!-- Breadcrumb -->
|
||||
<div class="breadcrumbs mb-4 text-sm">
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/" class="text-primary hover:text-primary-focus">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
|
||||
/>
|
||||
</svg>
|
||||
Dashboard
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/ti" class="text-primary hover:text-primary-focus">TI</a>
|
||||
</li>
|
||||
<li class="font-semibold">Personalizar Permissões</li>
|
||||
</ul>
|
||||
</div>
|
||||
<ProtectedRoute allowedRoles={["admin", "ti"]} maxLevel={1}>
|
||||
<!-- Breadcrumb -->
|
||||
<div class="text-sm breadcrumbs mb-4">
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/" class="text-primary hover:text-primary-focus">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
|
||||
/>
|
||||
</svg>
|
||||
Dashboard
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/ti" class="text-primary hover:text-primary-focus">TI</a>
|
||||
</li>
|
||||
<li class="font-semibold">Personalizar Permissões</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Header -->
|
||||
<div class="mb-6">
|
||||
<div class="mb-2 flex items-center gap-3">
|
||||
<div class="bg-info/10 rounded-xl p-3">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="text-info h-8 w-8"
|
||||
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>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h1 class="text-base-content text-3xl font-bold">Funcionalidade descontinuada</h1>
|
||||
<p class="text-base-content/60 mt-1">
|
||||
Agora as permissões são configuradas por ação em cada perfil no painel de permissões.
|
||||
</p>
|
||||
</div>
|
||||
<button class="btn gap-2" onclick={() => goto('/ti')}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M10 19l-7-7m0 0l7-7m-7 7h18"
|
||||
/>
|
||||
</svg>
|
||||
Voltar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-info shadow-lg">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6 shrink-0 stroke-current"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
A personalização por usuário foi substituída por <strong>permissões por ação</strong>
|
||||
por perfil. Utilize o
|
||||
<a href="/ti/painel-permissoes" class="link link-primary">Painel de Permissões</a> para configurar.
|
||||
</span>
|
||||
</div>
|
||||
<!-- Header -->
|
||||
<div class="mb-6">
|
||||
<div class="flex items-center gap-3 mb-2">
|
||||
<div class="p-3 bg-info/10 rounded-xl">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-8 w-8 text-info"
|
||||
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>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h1 class="text-3xl font-bold text-base-content">
|
||||
Funcionalidade descontinuada
|
||||
</h1>
|
||||
<p class="text-base-content/60 mt-1">
|
||||
Agora as permissões são configuradas por ação em cada perfil no painel
|
||||
de permissões.
|
||||
</p>
|
||||
</div>
|
||||
<button class="btn btn-ghost gap-2" onclick={() => goto("/ti")}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M10 19l-7-7m0 0l7-7m-7 7h18"
|
||||
/>
|
||||
</svg>
|
||||
Voltar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-info shadow-lg">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="stroke-current shrink-0 h-6 w-6"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
A personalização por usuário foi substituída por <strong
|
||||
>permissões por ação</strong
|
||||
>
|
||||
por perfil. Utilize o
|
||||
<a href="/ti/painel-permissoes" class="link link-primary"
|
||||
>Painel de Permissões</a
|
||||
> para configurar.
|
||||
</span>
|
||||
</div>
|
||||
</ProtectedRoute>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user