Fix usuarios page #6
@@ -1,25 +1,193 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { useQuery, useConvexClient } from "convex-svelte";
|
import { useQuery, useConvexClient } from "convex-svelte";
|
||||||
import { api } from "@sgse-app/backend/convex/_generated/api";
|
import { api } from "@sgse-app/backend/convex/_generated/api";
|
||||||
|
import { authStore } from "$lib/stores/auth.svelte";
|
||||||
|
import type { Id } from "@sgse-app/backend/convex/_generated/dataModel";
|
||||||
|
|
||||||
const client = useConvexClient();
|
const client = useConvexClient();
|
||||||
const templates = useQuery(api.templatesMensagens.listarTemplates, {});
|
|
||||||
const usuarios = useQuery(api.usuarios.listar, {});
|
// Queries
|
||||||
|
const templatesQuery = useQuery(api.templatesMensagens.listarTemplates, {});
|
||||||
|
const usuariosQuery = useQuery(api.usuarios.listar, {});
|
||||||
|
|
||||||
|
// Extrair dados das queries de forma robusta
|
||||||
|
const templates = $derived.by(() => {
|
||||||
|
if (templatesQuery === undefined || templatesQuery === null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if ("data" in templatesQuery && templatesQuery.data !== undefined) {
|
||||||
|
return Array.isArray(templatesQuery.data) ? templatesQuery.data : [];
|
||||||
|
}
|
||||||
|
if (Array.isArray(templatesQuery)) {
|
||||||
|
return templatesQuery;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
|
||||||
|
const usuarios = $derived.by(() => {
|
||||||
|
if (usuariosQuery === undefined || usuariosQuery === null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if ("data" in usuariosQuery && usuariosQuery.data !== undefined) {
|
||||||
|
return Array.isArray(usuariosQuery.data) ? usuariosQuery.data : [];
|
||||||
|
}
|
||||||
|
if (Array.isArray(usuariosQuery)) {
|
||||||
|
return usuariosQuery;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
|
||||||
|
// Estados de carregamento e erro
|
||||||
|
const carregandoTemplates = $derived(
|
||||||
|
templatesQuery === undefined || templatesQuery === null
|
||||||
|
);
|
||||||
|
const carregandoUsuarios = $derived(
|
||||||
|
usuariosQuery === undefined || usuariosQuery === null
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verificar erros de forma mais robusta
|
||||||
|
const erroTemplates = $derived.by(() => {
|
||||||
|
if (templatesQuery === undefined || templatesQuery === null) return false;
|
||||||
|
// Verificar se é um objeto com propriedade error
|
||||||
|
if (typeof templatesQuery === 'object' && 'error' in templatesQuery) {
|
||||||
|
return templatesQuery.error !== undefined && templatesQuery.error !== null;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
const erroUsuarios = $derived.by(() => {
|
||||||
|
if (usuariosQuery === undefined || usuariosQuery === null) return false;
|
||||||
|
if (typeof usuariosQuery === 'object' && 'error' in usuariosQuery) {
|
||||||
|
return usuariosQuery.error !== undefined && usuariosQuery.error !== null;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Log para debug (remover depois se necessário)
|
||||||
|
$effect(() => {
|
||||||
|
if (templatesQuery !== undefined && templatesQuery !== null) {
|
||||||
|
console.log("Templates Query:", templatesQuery);
|
||||||
|
console.log("Templates Extraídos:", templates);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let destinatarioId = $state("");
|
let destinatarioId = $state("");
|
||||||
|
let enviarParaTodos = $state(false);
|
||||||
let canal = $state<"chat" | "email" | "ambos">("chat");
|
let canal = $state<"chat" | "email" | "ambos">("chat");
|
||||||
let templateId = $state("");
|
let templateId = $state("");
|
||||||
let mensagemPersonalizada = $state("");
|
let mensagemPersonalizada = $state("");
|
||||||
let usarTemplate = $state(true);
|
let usarTemplate = $state(true);
|
||||||
let processando = $state(false);
|
let processando = $state(false);
|
||||||
|
let criandoTemplates = $state(false);
|
||||||
|
let progressoEnvio = $state({ total: 0, enviados: 0, falhas: 0 });
|
||||||
|
|
||||||
|
// Estados para modal de novo template
|
||||||
|
let modalNovoTemplateAberto = $state(false);
|
||||||
|
let codigoTemplate = $state("");
|
||||||
|
let nomeTemplate = $state("");
|
||||||
|
let tituloTemplate = $state("");
|
||||||
|
let corpoTemplate = $state("");
|
||||||
|
let variaveisTemplate = $state("");
|
||||||
|
let criandoNovoTemplate = $state(false);
|
||||||
|
|
||||||
const templateSelecionado = $derived(
|
const templateSelecionado = $derived(
|
||||||
templates?.data?.find(t => t._id === templateId)
|
templates.find(t => t._id === templateId)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
async function criarTemplatesPadrao() {
|
||||||
|
if (criandoTemplates) return;
|
||||||
|
|
||||||
|
criandoTemplates = true;
|
||||||
|
try {
|
||||||
|
const resultado = await client.mutation(api.templatesMensagens.criarTemplatesPadrao, {});
|
||||||
|
if (resultado.sucesso) {
|
||||||
|
alert("✅ Templates padrão criados com sucesso! A página será recarregada.");
|
||||||
|
window.location.reload();
|
||||||
|
} else {
|
||||||
|
alert("❌ Erro ao criar templates padrão.");
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("Erro ao criar templates:", error);
|
||||||
|
alert("❌ Erro ao criar templates: " + (error.message || "Erro desconhecido"));
|
||||||
|
} finally {
|
||||||
|
criandoTemplates = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function abrirModalNovoTemplate() {
|
||||||
|
modalNovoTemplateAberto = true;
|
||||||
|
// Limpar campos
|
||||||
|
codigoTemplate = "";
|
||||||
|
nomeTemplate = "";
|
||||||
|
tituloTemplate = "";
|
||||||
|
corpoTemplate = "";
|
||||||
|
variaveisTemplate = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function fecharModalNovoTemplate() {
|
||||||
|
modalNovoTemplateAberto = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function salvarNovoTemplate() {
|
||||||
|
if (!authStore.usuario) {
|
||||||
|
alert("❌ Você precisa estar autenticado para criar templates.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validações
|
||||||
|
if (!codigoTemplate.trim()) {
|
||||||
|
alert("❌ O código do template é obrigatório.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!nomeTemplate.trim()) {
|
||||||
|
alert("❌ O nome do template é obrigatório.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!tituloTemplate.trim()) {
|
||||||
|
alert("❌ O título do template é obrigatório.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!corpoTemplate.trim()) {
|
||||||
|
alert("❌ O corpo do template é obrigatório.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Processar variáveis (separadas por vírgula ou espaço)
|
||||||
|
const variaveis = variaveisTemplate
|
||||||
|
.split(/[,;\s]+/)
|
||||||
|
.map(v => v.trim())
|
||||||
|
.filter(v => v.length > 0);
|
||||||
|
|
||||||
|
criandoNovoTemplate = true;
|
||||||
|
try {
|
||||||
|
const resultado = await client.mutation(api.templatesMensagens.criarTemplate, {
|
||||||
|
codigo: codigoTemplate.trim().toUpperCase().replace(/\s+/g, "_"),
|
||||||
|
nome: nomeTemplate.trim(),
|
||||||
|
titulo: tituloTemplate.trim(),
|
||||||
|
corpo: corpoTemplate.trim(),
|
||||||
|
variaveis: variaveis.length > 0 ? variaveis : undefined,
|
||||||
|
criadoPorId: authStore.usuario._id as Id<"usuarios">,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (resultado.sucesso) {
|
||||||
|
alert("✅ Template criado com sucesso!");
|
||||||
|
fecharModalNovoTemplate();
|
||||||
|
// Recarregar a página para atualizar a lista
|
||||||
|
window.location.reload();
|
||||||
|
} else {
|
||||||
|
alert("❌ Erro ao criar template: " + (resultado.erro || "Erro desconhecido"));
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("Erro ao criar template:", error);
|
||||||
|
alert("❌ Erro ao criar template: " + (error.message || "Erro desconhecido"));
|
||||||
|
} finally {
|
||||||
|
criandoNovoTemplate = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function enviarNotificacao() {
|
async function enviarNotificacao() {
|
||||||
if (!destinatarioId) {
|
if (!enviarParaTodos && !destinatarioId) {
|
||||||
alert("Selecione um destinatário");
|
alert("Selecione um destinatário ou marque 'Enviar para todos'");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,22 +202,33 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
processando = true;
|
processando = true;
|
||||||
try {
|
progressoEnvio = { total: 0, enviados: 0, falhas: 0 };
|
||||||
const destinatario = usuarios?.data?.find(u => u._id === destinatarioId);
|
|
||||||
|
|
||||||
if (!destinatario) {
|
try {
|
||||||
alert("Destinatário não encontrado");
|
// Obter lista de destinatários
|
||||||
|
const destinatarios: typeof usuarios = enviarParaTodos
|
||||||
|
? usuarios
|
||||||
|
: usuarios.filter(u => u._id === destinatarioId);
|
||||||
|
|
||||||
|
if (destinatarios.length === 0) {
|
||||||
|
alert("Nenhum destinatário encontrado");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
progressoEnvio.total = destinatarios.length;
|
||||||
|
|
||||||
|
// Se for envio para um único usuário
|
||||||
|
if (destinatarios.length === 1) {
|
||||||
|
const destinatario = destinatarios[0];
|
||||||
let resultadoChat = null;
|
let resultadoChat = null;
|
||||||
let resultadoEmail = null;
|
let resultadoEmail = null;
|
||||||
|
|
||||||
// ENVIAR PARA CHAT
|
// ENVIAR PARA CHAT
|
||||||
if (canal === "chat" || canal === "ambos") {
|
if (canal === "chat" || canal === "ambos") {
|
||||||
|
try {
|
||||||
const conversaResult = await client.mutation(
|
const conversaResult = await client.mutation(
|
||||||
api.chat.criarOuBuscarConversaIndividual,
|
api.chat.criarOuBuscarConversaIndividual,
|
||||||
{ outroUsuarioId: destinatarioId as any }
|
{ outroUsuarioId: destinatario._id as any }
|
||||||
);
|
);
|
||||||
|
|
||||||
if (conversaResult.conversaId) {
|
if (conversaResult.conversaId) {
|
||||||
@@ -60,22 +239,20 @@
|
|||||||
resultadoChat = await client.mutation(api.chat.enviarMensagem, {
|
resultadoChat = await client.mutation(api.chat.enviarMensagem, {
|
||||||
conversaId: conversaResult.conversaId,
|
conversaId: conversaResult.conversaId,
|
||||||
conteudo: mensagem,
|
conteudo: mensagem,
|
||||||
tipo: "texto", // Tipo de mensagem
|
tipo: "texto",
|
||||||
permitirNotificacaoParaSiMesmo: true, // ✅ Permite notificação para si mesmo via painel admin
|
permitirNotificacaoParaSiMesmo: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erro ao enviar chat:", error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ENVIAR PARA EMAIL
|
// ENVIAR PARA EMAIL
|
||||||
if (canal === "email" || canal === "ambos") {
|
if (canal === "email" || canal === "ambos") {
|
||||||
if (!destinatario.email) {
|
if (destinatario.email) {
|
||||||
alert("Destinatário não possui email cadastrado");
|
try {
|
||||||
processando = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (usarTemplate && templateId) {
|
if (usarTemplate && templateId) {
|
||||||
// Usar template
|
|
||||||
const template = templateSelecionado;
|
const template = templateSelecionado;
|
||||||
if (template) {
|
if (template) {
|
||||||
resultadoEmail = await client.mutation(api.email.enviarEmailComTemplate, {
|
resultadoEmail = await client.mutation(api.email.enviarEmailComTemplate, {
|
||||||
@@ -86,19 +263,22 @@
|
|||||||
nome: destinatario.nome,
|
nome: destinatario.nome,
|
||||||
matricula: destinatario.matricula,
|
matricula: destinatario.matricula,
|
||||||
},
|
},
|
||||||
enviadoPorId: destinatario._id as any, // TODO: Pegar usuário logado
|
enviadoPorId: destinatario._id as any,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Mensagem personalizada
|
|
||||||
resultadoEmail = await client.mutation(api.email.enfileirarEmail, {
|
resultadoEmail = await client.mutation(api.email.enfileirarEmail, {
|
||||||
destinatario: destinatario.email,
|
destinatario: destinatario.email,
|
||||||
destinatarioId: destinatario._id as any,
|
destinatarioId: destinatario._id as any,
|
||||||
assunto: "Notificação do Sistema",
|
assunto: "Notificação do Sistema",
|
||||||
corpo: mensagemPersonalizada,
|
corpo: mensagemPersonalizada,
|
||||||
enviadoPorId: destinatario._id as any, // TODO: Pegar usuário logado
|
enviadoPorId: destinatario._id as any,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erro ao enviar email:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Feedback de sucesso
|
// Feedback de sucesso
|
||||||
@@ -118,9 +298,111 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
alert(mensagem);
|
alert(mensagem);
|
||||||
|
progressoEnvio.enviados = 1;
|
||||||
|
} else {
|
||||||
|
// ENVIO EM MASSA
|
||||||
|
let sucessosChat = 0;
|
||||||
|
let sucessosEmail = 0;
|
||||||
|
let falhasChat = 0;
|
||||||
|
let falhasEmail = 0;
|
||||||
|
|
||||||
|
for (const destinatario of destinatarios) {
|
||||||
|
try {
|
||||||
|
// ENVIAR PARA CHAT
|
||||||
|
if (canal === "chat" || canal === "ambos") {
|
||||||
|
try {
|
||||||
|
const conversaResult = await client.mutation(
|
||||||
|
api.chat.criarOuBuscarConversaIndividual,
|
||||||
|
{ outroUsuarioId: destinatario._id as any }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (conversaResult.conversaId) {
|
||||||
|
// Para templates, usar corpo direto (o backend já faz substituição via email)
|
||||||
|
// Para mensagem personalizada, usar diretamente
|
||||||
|
const mensagem = usarTemplate
|
||||||
|
? templateSelecionado?.corpo || ""
|
||||||
|
: mensagemPersonalizada;
|
||||||
|
|
||||||
|
await client.mutation(api.chat.enviarMensagem, {
|
||||||
|
conversaId: conversaResult.conversaId,
|
||||||
|
conteudo: mensagem,
|
||||||
|
tipo: "texto",
|
||||||
|
permitirNotificacaoParaSiMesmo: true,
|
||||||
|
});
|
||||||
|
sucessosChat++;
|
||||||
|
} else {
|
||||||
|
falhasChat++;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Erro ao enviar chat para ${destinatario.nome}:`, error);
|
||||||
|
falhasChat++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ENVIAR PARA EMAIL
|
||||||
|
if (canal === "email" || canal === "ambos") {
|
||||||
|
if (destinatario.email) {
|
||||||
|
try {
|
||||||
|
if (usarTemplate && templateId) {
|
||||||
|
const template = templateSelecionado;
|
||||||
|
if (template) {
|
||||||
|
await client.mutation(api.email.enviarEmailComTemplate, {
|
||||||
|
destinatario: destinatario.email,
|
||||||
|
destinatarioId: destinatario._id as any,
|
||||||
|
templateCodigo: template.codigo,
|
||||||
|
variaveis: {
|
||||||
|
nome: destinatario.nome,
|
||||||
|
matricula: destinatario.matricula || "",
|
||||||
|
},
|
||||||
|
enviadoPorId: destinatario._id as any,
|
||||||
|
});
|
||||||
|
sucessosEmail++;
|
||||||
|
} else {
|
||||||
|
falhasEmail++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await client.mutation(api.email.enfileirarEmail, {
|
||||||
|
destinatario: destinatario.email,
|
||||||
|
destinatarioId: destinatario._id as any,
|
||||||
|
assunto: "Notificação do Sistema",
|
||||||
|
corpo: mensagemPersonalizada,
|
||||||
|
enviadoPorId: destinatario._id as any,
|
||||||
|
});
|
||||||
|
sucessosEmail++;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Erro ao enviar email para ${destinatario.nome}:`, error);
|
||||||
|
falhasEmail++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
falhasEmail++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
progressoEnvio.enviados++;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Erro geral ao enviar para ${destinatario.nome}:`, error);
|
||||||
|
progressoEnvio.falhas++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Feedback de envio em massa
|
||||||
|
let mensagem = `✅ Envio em massa concluído!\n\n`;
|
||||||
|
if (canal === "ambos") {
|
||||||
|
mensagem += `Chat: ${sucessosChat} enviados, ${falhasChat} falhas\n`;
|
||||||
|
mensagem += `Email: ${sucessosEmail} enviados, ${falhasEmail} falhas`;
|
||||||
|
} else if (canal === "chat") {
|
||||||
|
mensagem += `Chat: ${sucessosChat} enviados, ${falhasChat} falhas`;
|
||||||
|
} else if (canal === "email") {
|
||||||
|
mensagem += `Email: ${sucessosEmail} enviados, ${falhasEmail} falhas`;
|
||||||
|
}
|
||||||
|
|
||||||
|
alert(mensagem);
|
||||||
|
}
|
||||||
|
|
||||||
// Limpar form
|
// Limpar form
|
||||||
destinatarioId = "";
|
destinatarioId = "";
|
||||||
|
enviarParaTodos = false;
|
||||||
templateId = "";
|
templateId = "";
|
||||||
mensagemPersonalizada = "";
|
mensagemPersonalizada = "";
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -128,6 +410,7 @@
|
|||||||
alert("Erro ao enviar notificação: " + (error.message || "Erro desconhecido"));
|
alert("Erro ao enviar notificação: " + (error.message || "Erro desconhecido"));
|
||||||
} finally {
|
} finally {
|
||||||
processando = false;
|
processando = false;
|
||||||
|
progressoEnvio = { total: 0, enviados: 0, falhas: 0 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -156,19 +439,46 @@
|
|||||||
|
|
||||||
<!-- Destinatário -->
|
<!-- Destinatário -->
|
||||||
<div class="form-control mb-4">
|
<div class="form-control mb-4">
|
||||||
|
<div class="flex items-center justify-between mb-2">
|
||||||
<label class="label" for="destinatario-select">
|
<label class="label" for="destinatario-select">
|
||||||
<span class="label-text font-medium">Destinatário *</span>
|
<span class="label-text font-medium">Destinatário *</span>
|
||||||
</label>
|
</label>
|
||||||
<select id="destinatario-select" bind:value={destinatarioId} class="select select-bordered">
|
<label class="label cursor-pointer gap-2">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
bind:checked={enviarParaTodos}
|
||||||
|
class="checkbox checkbox-primary checkbox-sm"
|
||||||
|
disabled={processando}
|
||||||
|
/>
|
||||||
|
<span class="label-text text-sm">Enviar para todos ({usuarios.length} usuários)</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<select
|
||||||
|
id="destinatario-select"
|
||||||
|
bind:value={destinatarioId}
|
||||||
|
class="select select-bordered"
|
||||||
|
disabled={enviarParaTodos || processando}
|
||||||
|
>
|
||||||
<option value="">Selecione um usuário</option>
|
<option value="">Selecione um usuário</option>
|
||||||
{#if usuarios?.data}
|
{#if carregandoUsuarios}
|
||||||
{#each usuarios.data as usuario}
|
<option disabled>Carregando usuários...</option>
|
||||||
|
{:else if usuarios.length > 0}
|
||||||
|
{#each usuarios as usuario}
|
||||||
<option value={usuario._id}>
|
<option value={usuario._id}>
|
||||||
{usuario.nome} ({usuario.matricula})
|
{usuario.nome} ({usuario.matricula})
|
||||||
</option>
|
</option>
|
||||||
{/each}
|
{/each}
|
||||||
|
{:else}
|
||||||
|
<option disabled>Nenhum usuário disponível</option>
|
||||||
{/if}
|
{/if}
|
||||||
</select>
|
</select>
|
||||||
|
{#if enviarParaTodos}
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text-alt text-warning">
|
||||||
|
⚠️ A notificação será enviada para todos os {usuarios.length} usuários do sistema
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Canal -->
|
<!-- Canal -->
|
||||||
@@ -242,12 +552,16 @@
|
|||||||
</label>
|
</label>
|
||||||
<select id="template-select" bind:value={templateId} class="select select-bordered">
|
<select id="template-select" bind:value={templateId} class="select select-bordered">
|
||||||
<option value="">Selecione um template</option>
|
<option value="">Selecione um template</option>
|
||||||
{#if templates?.data}
|
{#if carregandoTemplates}
|
||||||
{#each templates.data as template}
|
<option disabled>Carregando templates...</option>
|
||||||
|
{:else if templates.length > 0}
|
||||||
|
{#each templates as template}
|
||||||
<option value={template._id}>
|
<option value={template._id}>
|
||||||
{template.nome}
|
{template.nome}
|
||||||
</option>
|
</option>
|
||||||
{/each}
|
{/each}
|
||||||
|
{:else}
|
||||||
|
<option disabled>Nenhum template disponível</option>
|
||||||
{/if}
|
{/if}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -282,17 +596,28 @@
|
|||||||
<div class="card-actions justify-end mt-4">
|
<div class="card-actions justify-end mt-4">
|
||||||
<button
|
<button
|
||||||
class="btn btn-primary btn-block"
|
class="btn btn-primary btn-block"
|
||||||
onclick={enviarNotificacao}
|
onclick={() => enviarNotificacao()}
|
||||||
disabled={processando}
|
disabled={processando}
|
||||||
>
|
>
|
||||||
{#if processando}
|
{#if processando}
|
||||||
<span class="loading loading-spinner loading-sm"></span>
|
<span class="loading loading-spinner loading-sm"></span>
|
||||||
|
{#if progressoEnvio.total > 1}
|
||||||
|
<span class="ml-2">
|
||||||
|
Enviando... ({progressoEnvio.enviados}/{progressoEnvio.total})
|
||||||
|
</span>
|
||||||
|
{:else}
|
||||||
|
<span class="ml-2">Enviando...</span>
|
||||||
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<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="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" />
|
||||||
</svg>
|
</svg>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if enviarParaTodos && !processando}
|
||||||
|
Enviar para Todos ({usuarios.length})
|
||||||
|
{:else if !processando}
|
||||||
Enviar Notificação
|
Enviar Notificação
|
||||||
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -303,7 +628,11 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<h2 class="card-title">Templates Disponíveis</h2>
|
<h2 class="card-title">Templates Disponíveis</h2>
|
||||||
<button class="btn btn-sm btn-outline btn-primary">
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-sm btn-outline btn-primary"
|
||||||
|
onclick={() => abrirModalNovoTemplate()}
|
||||||
|
>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<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 4v16m8-8H4" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
||||||
</svg>
|
</svg>
|
||||||
@@ -311,17 +640,15 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if !templates?.data}
|
{#if carregandoTemplates}
|
||||||
<div class="flex justify-center py-10">
|
<div class="flex flex-col items-center justify-center py-10 gap-4">
|
||||||
<span class="loading loading-spinner loading-lg"></span>
|
<span class="loading loading-spinner loading-lg"></span>
|
||||||
|
<p class="text-sm text-base-content/60">Carregando templates...</p>
|
||||||
</div>
|
</div>
|
||||||
{:else if templates.data.length === 0}
|
{:else if templates.length > 0}
|
||||||
<div class="text-center py-10 text-base-content/60">
|
<!-- Mostrar templates se existirem -->
|
||||||
Nenhum template disponível
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<div class="space-y-3 max-h-[600px] overflow-y-auto">
|
<div class="space-y-3 max-h-[600px] overflow-y-auto">
|
||||||
{#each templates.data as template}
|
{#each templates as template}
|
||||||
<div class="card bg-base-200 compact">
|
<div class="card bg-base-200 compact">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="flex items-start justify-between">
|
<div class="flex items-start justify-between">
|
||||||
@@ -359,6 +686,30 @@
|
|||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
{:else}
|
||||||
|
<!-- Nenhum template disponível - mostrar botão para criar -->
|
||||||
|
<div class="text-center py-10">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 mx-auto mb-4 opacity-50" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||||
|
</svg>
|
||||||
|
<p class="font-medium text-base-content mb-2">Nenhum template disponível</p>
|
||||||
|
<p class="text-sm text-base-content/60 mb-4">Clique no botão abaixo para criar os templates padrão do sistema.</p>
|
||||||
|
<button
|
||||||
|
class="btn btn-primary btn-sm"
|
||||||
|
onclick={criarTemplatesPadrao}
|
||||||
|
disabled={criandoTemplates}
|
||||||
|
>
|
||||||
|
{#if criandoTemplates}
|
||||||
|
<span class="loading loading-spinner loading-xs"></span>
|
||||||
|
Criando templates...
|
||||||
|
{:else}
|
||||||
|
<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 4v16m8-8H4" />
|
||||||
|
</svg>
|
||||||
|
Criar Templates Padrão
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -373,3 +724,124 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal Novo Template -->
|
||||||
|
{#if modalNovoTemplateAberto}
|
||||||
|
<div class="modal modal-open">
|
||||||
|
<div class="modal-box max-w-2xl">
|
||||||
|
<h3 class="font-bold text-lg mb-4">Criar Novo Template</h3>
|
||||||
|
|
||||||
|
<div class="space-y-4">
|
||||||
|
<!-- Código -->
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text font-medium">Código *</span>
|
||||||
|
<span class="label-text-alt">Ex: AVISO_IMPORTANTE</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
bind:value={codigoTemplate}
|
||||||
|
placeholder="CODIGO_TEMPLATE"
|
||||||
|
class="input input-bordered"
|
||||||
|
maxlength="50"
|
||||||
|
/>
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text-alt">Código único para identificar o template (será convertido para MAIÚSCULAS)</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Nome -->
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text font-medium">Nome *</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
bind:value={nomeTemplate}
|
||||||
|
placeholder="Nome do Template"
|
||||||
|
class="input input-bordered"
|
||||||
|
maxlength="100"
|
||||||
|
/>
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text-alt">Nome exibido na lista de templates</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Título -->
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text font-medium">Título *</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
bind:value={tituloTemplate}
|
||||||
|
placeholder="Título da Mensagem"
|
||||||
|
class="input input-bordered"
|
||||||
|
maxlength="200"
|
||||||
|
/>
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text-alt">Título usado no assunto do email ou cabeçalho da mensagem</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Corpo -->
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text font-medium">Corpo da Mensagem *</span>
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
bind:value={corpoTemplate}
|
||||||
|
placeholder="Digite o conteúdo da mensagem..."
|
||||||
|
class="textarea textarea-bordered h-32"
|
||||||
|
maxlength="2000"
|
||||||
|
></textarea>
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text-alt">Use {'{{'}variavel{'}}'} para variáveis dinâmicas (ex: {'{{'}nome{'}}'}, {'{{'}data{'}}'})</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Variáveis -->
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text font-medium">Variáveis (opcional)</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
bind:value={variaveisTemplate}
|
||||||
|
placeholder="nome, data, valor (separadas por vírgula)"
|
||||||
|
class="input input-bordered"
|
||||||
|
/>
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text-alt">Liste as variáveis que podem ser substituídas no template (separadas por vírgula ou espaço)</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-action">
|
||||||
|
<button
|
||||||
|
class="btn btn-ghost"
|
||||||
|
onclick={fecharModalNovoTemplate}
|
||||||
|
disabled={criandoNovoTemplate}
|
||||||
|
>
|
||||||
|
Cancelar
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn btn-primary"
|
||||||
|
onclick={salvarNovoTemplate}
|
||||||
|
disabled={criandoNovoTemplate}
|
||||||
|
>
|
||||||
|
{#if criandoNovoTemplate}
|
||||||
|
<span class="loading loading-spinner loading-sm"></span>
|
||||||
|
Criando...
|
||||||
|
{:else}
|
||||||
|
<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="M5 13l4 4L19 7" />
|
||||||
|
</svg>
|
||||||
|
Criar Template
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-backdrop" onclick={fecharModalNovoTemplate}></div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user