refactor: improve Svelte components and enhance user experience
- Updated various Svelte components to improve code readability and maintainability. - Standardized button classes across components for a consistent user interface. - Enhanced error handling and user feedback in modals and forms. - Cleaned up unnecessary imports and optimized component structure for better performance.
This commit is contained in:
@@ -1,463 +1,568 @@
|
||||
<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();
|
||||
|
||||
// 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 });
|
||||
const client = useConvexClient();
|
||||
|
||||
// Função para forçar refresh das queries
|
||||
function refreshQueries() {
|
||||
refreshKey++;
|
||||
}
|
||||
// Criar uma chave reativa para forçar atualização das queries
|
||||
let refreshKey = $state(0);
|
||||
|
||||
$effect(() => {
|
||||
if (autoRefresh) {
|
||||
refreshInterval = setInterval(() => {
|
||||
refreshQueries();
|
||||
}, 5000); // Refresh a cada 5 segundos
|
||||
} else {
|
||||
if (refreshInterval) {
|
||||
clearInterval(refreshInterval);
|
||||
refreshInterval = null;
|
||||
}
|
||||
}
|
||||
// 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 });
|
||||
|
||||
return () => {
|
||||
if (refreshInterval) {
|
||||
clearInterval(refreshInterval);
|
||||
refreshInterval = null;
|
||||
}
|
||||
};
|
||||
});
|
||||
// Função para forçar refresh das queries
|
||||
function refreshQueries() {
|
||||
refreshKey++;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
$effect(() => {
|
||||
if (autoRefresh) {
|
||||
refreshInterval = setInterval(() => {
|
||||
refreshQueries();
|
||||
}, 5000); // Refresh a cada 5 segundos
|
||||
} else {
|
||||
if (refreshInterval) {
|
||||
clearInterval(refreshInterval);
|
||||
refreshInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
function formatarData(timestamp: number | undefined): string {
|
||||
if (!timestamp) return "-";
|
||||
return new Date(timestamp).toLocaleString("pt-BR");
|
||||
}
|
||||
return () => {
|
||||
if (refreshInterval) {
|
||||
clearInterval(refreshInterval);
|
||||
refreshInterval = null;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
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";
|
||||
}
|
||||
}
|
||||
async function processarFilaManual() {
|
||||
if (processando) return;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
try {
|
||||
processando = true;
|
||||
const resultado = await client.action(api.email.processarFilaEmailsManual, { limite: 10 });
|
||||
|
||||
function abrirModalDetalhes(email: Doc<"notificacoesEmail">) {
|
||||
emailSelecionado = email;
|
||||
modalDetalhesAberto = true;
|
||||
}
|
||||
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 fecharModalDetalhes() {
|
||||
modalDetalhesAberto = false;
|
||||
emailSelecionado = null;
|
||||
}
|
||||
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;
|
||||
}
|
||||
</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>
|
||||
<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>
|
||||
|
||||
<!-- 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>
|
||||
<!-- 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>
|
||||
|
||||
<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-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-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-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-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-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-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}
|
||||
<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}
|
||||
|
||||
<!-- 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>
|
||||
<!-- 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>
|
||||
|
||||
<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 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>
|
||||
{#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>
|
||||
|
||||
<!-- 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>
|
||||
<!-- 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>
|
||||
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<!-- Modal de Detalhes do Email -->
|
||||
{#if modalDetalhesAberto && emailSelecionado}
|
||||
<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="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="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 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>
|
||||
<!-- 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>
|
||||
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<!-- 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 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>
|
||||
<!-- 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>
|
||||
|
||||
{#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.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.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.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.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>
|
||||
{#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>
|
||||
|
||||
<!-- 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}
|
||||
<!-- 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}
|
||||
|
||||
<!-- 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>
|
||||
<!-- 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>
|
||||
|
||||
<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 btn-ghost">
|
||||
<a href={resolve('/ti')} class="btn">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -284,7 +284,7 @@
|
||||
</svg>
|
||||
Criar novo perfil
|
||||
</button>
|
||||
<button class="btn btn-ghost gap-2" onclick={() => goto(resolve('/ti'))}>
|
||||
<button class="btn gap-2" onclick={() => goto(resolve('/ti'))}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5"
|
||||
@@ -402,11 +402,7 @@
|
||||
{#if busca}
|
||||
<div class="badge badge-primary gap-2">
|
||||
Busca: {busca}
|
||||
<button
|
||||
class="btn btn-ghost btn-xs"
|
||||
onclick={() => (busca = '')}
|
||||
aria-label="Limpar busca"
|
||||
>
|
||||
<button class="btn btn-xs" onclick={() => (busca = '')} aria-label="Limpar busca">
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
@@ -415,7 +411,7 @@
|
||||
<div class="badge badge-secondary gap-2">
|
||||
Perfil filtrado
|
||||
<button
|
||||
class="btn btn-ghost btn-xs"
|
||||
class="btn btn-xs"
|
||||
onclick={() => (filtroRole = '')}
|
||||
aria-label="Limpar filtro"
|
||||
>
|
||||
@@ -761,7 +757,7 @@
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-ghost"
|
||||
class="btn"
|
||||
onclick={fecharModalNovoPerfil}
|
||||
disabled={criandoNovoPerfil}
|
||||
>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,107 +1,100 @@
|
||||
<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="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>
|
||||
<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>
|
||||
|
||||
<!-- 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>
|
||||
<!-- 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>
|
||||
</ProtectedRoute>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -318,7 +318,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button class="btn btn-ghost gap-2" onclick={() => goto(resolve('/ti'))}>
|
||||
<button class="btn gap-2" onclick={() => goto(resolve('/ti'))}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5"
|
||||
@@ -424,9 +424,7 @@
|
||||
</div>
|
||||
|
||||
<div class="card-actions mt-6 justify-end">
|
||||
<button class="btn btn-ghost" onclick={cancelarEdicao} disabled={processando}>
|
||||
Cancelar
|
||||
</button>
|
||||
<button class="btn" onclick={cancelarEdicao} disabled={processando}> Cancelar </button>
|
||||
<button class="btn btn-primary" onclick={salvarTime} disabled={processando}>
|
||||
{processando ? 'Salvando...' : 'Salvar'}
|
||||
</button>
|
||||
@@ -457,7 +455,7 @@
|
||||
<h2 class="card-title text-lg">{time.nome}</h2>
|
||||
</div>
|
||||
<div class="dropdown dropdown-end">
|
||||
<button type="button" class="btn btn-ghost btn-sm" aria-label="Menu do time">
|
||||
<button type="button" class="btn btn-sm" aria-label="Menu do time">
|
||||
<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
Reference in New Issue
Block a user