Ajustes Gerais
This commit is contained in:
@@ -330,6 +330,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="ml-auto flex flex-none items-center gap-4">
|
<div class="ml-auto flex flex-none items-center gap-4">
|
||||||
{#if currentUser.data}
|
{#if currentUser.data}
|
||||||
|
<!-- Nome e Perfil à esquerda do avatar -->
|
||||||
|
<div class="hidden flex-col items-end lg:flex">
|
||||||
|
<span class="text-primary text-sm font-semibold">{currentUser.data.nome}</span>
|
||||||
|
<span class="text-base-content/60 text-xs">{currentUser.data.role?.nome}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="dropdown dropdown-end">
|
<div class="dropdown dropdown-end">
|
||||||
<!-- Botão de Perfil ULTRA MODERNO -->
|
<!-- Botão de Perfil ULTRA MODERNO -->
|
||||||
<button
|
<button
|
||||||
@@ -387,11 +393,6 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mr-2 hidden flex-col items-end lg:flex">
|
|
||||||
<span class="text-primary text-sm font-semibold">{currentUser.data.nome}</span>
|
|
||||||
<span class="text-base-content/60 text-xs">{currentUser.data.role?.nome}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Sino de notificações no canto superior direito -->
|
<!-- Sino de notificações no canto superior direito -->
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<NotificationBell />
|
<NotificationBell />
|
||||||
|
|||||||
@@ -424,15 +424,16 @@
|
|||||||
|
|
||||||
<section class="grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-3">
|
<section class="grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-3">
|
||||||
{#each featureCards as card (card.title)}
|
{#each featureCards as card (card.title)}
|
||||||
<article
|
{#if card.href && !card.disabled}
|
||||||
class={`group relative overflow-hidden rounded-2xl border ${paletteStyles[card.palette].cardBorder} bg-base-100/90 p-6 shadow-lg transition-all duration-300`}
|
<a
|
||||||
|
href={resolve(card.href)}
|
||||||
|
class={`group relative flex cursor-pointer items-center gap-4 overflow-hidden rounded-2xl border ${paletteStyles[card.palette].cardBorder} bg-base-100/90 p-6 shadow-lg transition-all duration-300 hover:shadow-xl hover:scale-[1.02]`}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="from-base-200/40 absolute inset-x-6 top-0 h-24 rounded-b-full bg-linear-to-b to-transparent opacity-0 transition-opacity duration-300 group-hover:opacity-100"
|
class="from-base-200/40 absolute inset-x-6 top-0 h-24 rounded-b-full bg-linear-to-b to-transparent opacity-0 transition-opacity duration-300 group-hover:opacity-100"
|
||||||
></div>
|
></div>
|
||||||
<div class="relative flex items-start gap-4">
|
|
||||||
<div
|
<div
|
||||||
class={`flex h-14 w-14 items-center justify-center rounded-2xl ${paletteStyles[card.palette].iconBg} ${paletteStyles[card.palette].iconRing}`}
|
class={`relative flex h-14 w-14 shrink-0 items-center justify-center rounded-2xl ${paletteStyles[card.palette].iconBg} ${paletteStyles[card.palette].iconRing}`}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
@@ -455,47 +456,39 @@
|
|||||||
<h2 class="text-base-content text-xl font-semibold">
|
<h2 class="text-base-content text-xl font-semibold">
|
||||||
{card.title}
|
{card.title}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-base-content/70 mt-2 text-sm leading-relaxed">
|
|
||||||
{card.description}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if card.highlightBadges}
|
|
||||||
<div class="mt-4 flex flex-wrap gap-2">
|
|
||||||
{#each card.highlightBadges as badge (badge.label)}
|
|
||||||
{#if badge.variant === 'solid'}
|
|
||||||
<span class={`badge ${paletteStyles[card.palette].badgeSolid}`}>{badge.label}</span>
|
|
||||||
{:else}
|
|
||||||
<span
|
|
||||||
class={`badge ${paletteStyles[card.palette].badgeOutline} ${paletteStyles[card.palette].iconColor}`}
|
|
||||||
>
|
|
||||||
{badge.label}
|
|
||||||
</span>
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class="mt-6 flex justify-end">
|
|
||||||
{#if card.href && !card.disabled}
|
|
||||||
<a
|
|
||||||
class={`btn ${paletteStyles[card.palette].button} btn-sm sm:btn-md shadow-md transition-all duration-200 hover:shadow-lg`}
|
|
||||||
href={resolve(card.href)}
|
|
||||||
>
|
|
||||||
{card.ctaLabel}
|
|
||||||
</a>
|
</a>
|
||||||
{:else}
|
{:else}
|
||||||
<button
|
<article
|
||||||
type="button"
|
class={`group relative flex cursor-not-allowed items-center gap-4 overflow-hidden rounded-2xl border ${paletteStyles[card.palette].cardBorder} bg-base-100/50 p-6 shadow-lg opacity-60`}
|
||||||
class={`btn ${paletteStyles[card.palette].button} btn-sm sm:btn-md shadow-md`}
|
|
||||||
disabled
|
|
||||||
>
|
>
|
||||||
{card.ctaLabel}
|
<div
|
||||||
</button>
|
class={`relative flex h-14 w-14 shrink-0 items-center justify-center rounded-2xl ${paletteStyles[card.palette].iconBg} ${paletteStyles[card.palette].iconRing}`}
|
||||||
{/if}
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
class={`h-7 w-7 ${paletteStyles[card.palette].iconColor}`}
|
||||||
|
>
|
||||||
|
{#each iconPaths[card.icon] as path (path.d)}
|
||||||
|
<path
|
||||||
|
d={path.d}
|
||||||
|
stroke-linecap={path.strokeLinecap ?? 'round'}
|
||||||
|
stroke-linejoin={path.strokeLinejoin ?? 'round'}
|
||||||
|
stroke-width={path.strokeWidth ?? 2}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="relative flex-1">
|
||||||
|
<h2 class="text-base-content text-xl font-semibold">
|
||||||
|
{card.title}
|
||||||
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
enviadoPor: Id<'usuarios'>;
|
enviadoPor: Id<'usuarios'>;
|
||||||
criadoEm: number;
|
criadoEm: number;
|
||||||
enviadoEm: number | undefined;
|
enviadoEm: number | undefined;
|
||||||
|
erroDetalhes?: string;
|
||||||
destinatarioInfo: Doc<'usuarios'> | null;
|
destinatarioInfo: Doc<'usuarios'> | null;
|
||||||
templateInfo: Doc<'templatesMensagens'> | null;
|
templateInfo: Doc<'templatesMensagens'> | null;
|
||||||
}
|
}
|
||||||
@@ -55,10 +56,11 @@
|
|||||||
const emailIdsArray = $derived(
|
const emailIdsArray = $derived(
|
||||||
Array.from(emailIdsRastreados).map((id) => id as Id<'notificacoesEmail'>)
|
Array.from(emailIdsRastreados).map((id) => id as Id<'notificacoesEmail'>)
|
||||||
);
|
);
|
||||||
// Usar função para evitar execução quando array está vazio
|
// Usar $derived para calcular argumentos da query condicionalmente
|
||||||
const emailsStatusQuery = useQuery(api.email.buscarEmailsPorIds, () =>
|
const emailsStatusArgs = $derived(
|
||||||
emailIdsArray.length === 0 ? 'skip' : { emailIds: emailIdsArray }
|
emailIdsArray.length === 0 ? 'skip' : { emailIds: emailIdsArray }
|
||||||
);
|
);
|
||||||
|
const emailsStatusQuery = useQuery(api.email.buscarEmailsPorIds, emailsStatusArgs);
|
||||||
|
|
||||||
// Queries para agendamentos
|
// Queries para agendamentos
|
||||||
const agendamentosEmailQuery = useQuery(api.email.listarAgendamentosEmail, {});
|
const agendamentosEmailQuery = useQuery(api.email.listarAgendamentosEmail, {});
|
||||||
@@ -248,9 +250,21 @@
|
|||||||
variaveis: Record<string, string>
|
variaveis: Record<string, string>
|
||||||
): string {
|
): string {
|
||||||
const textoComVariaveis = renderizarTemplate(template, variaveis);
|
const textoComVariaveis = renderizarTemplate(template, variaveis);
|
||||||
// Remove quaisquer tags HTML que possam ter sido inseridas por engano
|
// Remove todas as tags HTML (incluindo quebras de linha HTML)
|
||||||
const textoPuro = textoComVariaveis.replace(/<[^>]*>/g, '');
|
let textoPuro = textoComVariaveis.replace(/<[^>]*>/g, '');
|
||||||
return textoPuro;
|
// Converte entidades HTML comuns para texto normal
|
||||||
|
textoPuro = textoPuro
|
||||||
|
.replace(/ /g, ' ')
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, "'")
|
||||||
|
.replace(/'/g, "'")
|
||||||
|
.replace(/&[a-zA-Z0-9#]+;/g, ''); // Remove outras entidades HTML
|
||||||
|
// Normaliza espaços múltiplos (mas preserva quebras de linha reais)
|
||||||
|
textoPuro = textoPuro.replace(/[ \t]+/g, ' ').replace(/[ \t]*\n[ \t]*/g, '\n');
|
||||||
|
return textoPuro.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Função para mostrar mensagens
|
// Função para mostrar mensagens
|
||||||
@@ -726,9 +740,10 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (conversaId) {
|
if (conversaId) {
|
||||||
|
// Para chat, sempre remover HTML dos templates
|
||||||
const mensagem =
|
const mensagem =
|
||||||
usarTemplate && templateSelecionado
|
usarTemplate && templateSelecionado
|
||||||
? renderizarTemplate(templateSelecionado.corpo, {
|
? renderizarTemplateChatLocal(templateSelecionado.corpo, {
|
||||||
nome: destinatario.nome,
|
nome: destinatario.nome,
|
||||||
matricula: destinatario.matricula || ''
|
matricula: destinatario.matricula || ''
|
||||||
})
|
})
|
||||||
@@ -2000,6 +2015,7 @@
|
|||||||
<span>Para enviar emails, certifique-se de configurar o SMTP em Configurações de Email.</span>
|
<span>Para enviar emails, certifique-se de configurar o SMTP em Configurações de Email.</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Modal Novo Template -->
|
<!-- Modal Novo Template -->
|
||||||
{#if modalNovoTemplateAberto}
|
{#if modalNovoTemplateAberto}
|
||||||
|
|||||||
@@ -402,18 +402,13 @@ export const buscarEmailsPorIds = query({
|
|||||||
export const listarAgendamentosEmail = query({
|
export const listarAgendamentosEmail = query({
|
||||||
args: {},
|
args: {},
|
||||||
handler: async (ctx) => {
|
handler: async (ctx) => {
|
||||||
// Buscar todos os emails agendados (pendentes ou enviando)
|
// Buscar todos os emails agendados (pendentes, enviando ou já enviados que tinham agendamento)
|
||||||
const emailsAgendados = await ctx.db
|
const emailsAgendados = await ctx.db
|
||||||
.query("notificacoesEmail")
|
.query("notificacoesEmail")
|
||||||
.filter((q) => {
|
.filter((q) => {
|
||||||
const temAgendamento = q.neq(q.field("agendadaPara"), undefined);
|
// Apenas emails que têm agendadaPara definido
|
||||||
const statusValido = q.or(
|
return q.neq(q.field("agendadaPara"), undefined);
|
||||||
q.eq(q.field("status"), "pendente"),
|
|
||||||
q.eq(q.field("status"), "enviando")
|
|
||||||
);
|
|
||||||
return q.and(temAgendamento, statusValido);
|
|
||||||
})
|
})
|
||||||
.order("asc")
|
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Enriquecer com informações de destinatário e template
|
// Enriquecer com informações de destinatário e template
|
||||||
|
|||||||
Reference in New Issue
Block a user