feat: implement email notification system for 'Almoxarifado' alerts, enhancing user awareness of stock levels and alert statuses through automated email updates
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
const client = useConvexClient();
|
||||
|
||||
let filtroTipo = $state<string>('');
|
||||
let filtroStatus = $state<string>('ativo');
|
||||
let filtroStatus = $state<string>('');
|
||||
|
||||
const alertasQuery = useQuery(api.almoxarifado.listarAlertas, {
|
||||
status: filtroStatus ? (filtroStatus as AlertaStatus) : undefined,
|
||||
@@ -17,6 +17,16 @@
|
||||
});
|
||||
const materiaisQuery = useQuery(api.almoxarifado.listarMateriais, {});
|
||||
|
||||
// Criar mapa de materiais para lookup eficiente
|
||||
const materiaisMap = $derived.by(() => {
|
||||
if (!materiaisQuery.data) return new Map();
|
||||
const map = new Map();
|
||||
for (const material of materiaisQuery.data) {
|
||||
map.set(material._id, material);
|
||||
}
|
||||
return map;
|
||||
});
|
||||
|
||||
let notice = $state<{ kind: 'success' | 'error'; text: string } | null>(null);
|
||||
|
||||
function mostrarMensagem(kind: 'success' | 'error', text: string) {
|
||||
@@ -145,7 +155,11 @@
|
||||
<!-- Lista de Alertas -->
|
||||
<div class="card bg-base-100 border border-base-300 shadow-xl">
|
||||
<div class="card-body">
|
||||
{#if alertasQuery.data && alertasQuery.data.length > 0}
|
||||
{#if alertasQuery === undefined}
|
||||
<div class="flex items-center justify-center py-12">
|
||||
<span class="loading loading-spinner loading-lg text-primary"></span>
|
||||
</div>
|
||||
{:else if alertasQuery.data && alertasQuery.data.length > 0}
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table table-zebra">
|
||||
<thead>
|
||||
@@ -162,7 +176,7 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each alertasQuery.data as alerta}
|
||||
{@const material = materiaisQuery.data?.find(m => m._id === alerta.materialId)}
|
||||
{@const material = materiaisMap.get(alerta.materialId)}
|
||||
{@const diferenca = alerta.quantidadeMinima - alerta.quantidadeAtual}
|
||||
<tr class="hover:bg-base-200/50 transition-colors">
|
||||
<td>
|
||||
@@ -222,15 +236,29 @@
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-center py-12">
|
||||
<CheckCircle class="mx-auto mb-4 h-20 w-20 text-success" />
|
||||
<AlertTriangle class="mx-auto mb-4 h-20 w-20 text-base-content/30" />
|
||||
<h3 class="text-2xl font-bold mb-2">Nenhum alerta encontrado</h3>
|
||||
<p class="text-base-content/70 text-lg">
|
||||
<p class="text-base-content/70 text-lg mb-4">
|
||||
{#if filtroStatus === 'ativo'}
|
||||
Não há alertas ativos no momento. Todos os materiais estão com estoque adequado!
|
||||
{:else}
|
||||
{:else if filtroStatus || filtroTipo}
|
||||
Não há alertas com os filtros selecionados.
|
||||
{:else}
|
||||
Ainda não há alertas registrados no sistema.
|
||||
{/if}
|
||||
</p>
|
||||
<div class="alert alert-info max-w-2xl mx-auto">
|
||||
<AlertTriangle class="h-6 w-6" />
|
||||
<div class="text-left">
|
||||
<h4 class="font-bold mb-2">Como os alertas funcionam?</h4>
|
||||
<ul class="text-sm space-y-1 list-disc list-inside">
|
||||
<li>Os alertas são criados automaticamente quando o estoque de um material fica abaixo do mínimo configurado</li>
|
||||
<li>O sistema permite apenas <strong>um alerta ativo por material</strong> para evitar duplicações</li>
|
||||
<li>Quando o estoque volta ao normal, você pode resolver o alerta manualmente</li>
|
||||
<li>Alertas são criados durante movimentações de estoque (entradas, saídas, ajustes)</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import { useConvexClient, useQuery } from 'convex-svelte';
|
||||
import { resolve } from '$app/paths';
|
||||
import { ClipboardList, Plus, CheckCircle, XCircle, Package } from 'lucide-svelte';
|
||||
import ErrorModal from '$lib/components/ErrorModal.svelte';
|
||||
|
||||
const client = useConvexClient();
|
||||
|
||||
@@ -34,12 +35,60 @@
|
||||
const setoresQuery = useQuery(api.setores.list, {});
|
||||
|
||||
let notice = $state<{ kind: 'success' | 'error'; text: string } | null>(null);
|
||||
let showErrorModal = $state(false);
|
||||
let errorModalTitle = $state('Erro ao processar requisição');
|
||||
let errorModalMessage = $state('');
|
||||
let errorModalDetails = $state('');
|
||||
|
||||
function mostrarMensagem(kind: 'success' | 'error', text: string) {
|
||||
notice = { kind, text };
|
||||
setTimeout(() => {
|
||||
notice = null;
|
||||
}, 5000);
|
||||
if (kind === 'error') {
|
||||
// Para erros, usar modal
|
||||
errorModalTitle = 'Erro ao processar requisição';
|
||||
|
||||
// Extrair mensagem de erro amigável
|
||||
let mensagemAmigavel = text;
|
||||
let detalhesTecnicos = '';
|
||||
|
||||
// Remover prefixos técnicos do Convex se existirem
|
||||
if (text.includes('[CONVEX') || text.includes('Request ID')) {
|
||||
// Extrair apenas a parte da mensagem de erro após o último ']'
|
||||
const partes = text.split(']');
|
||||
if (partes.length > 1) {
|
||||
mensagemAmigavel = partes[partes.length - 1].trim();
|
||||
}
|
||||
detalhesTecnicos = 'Detalhes técnicos:\n' + text;
|
||||
}
|
||||
|
||||
// Melhorar mensagens específicas
|
||||
if (mensagemAmigavel.includes('Funcionário não encontrado para o usuário')) {
|
||||
errorModalTitle = 'Erro: Funcionário não encontrado';
|
||||
mensagemAmigavel = 'Não foi possível encontrar o funcionário associado ao seu usuário.';
|
||||
detalhesTecnicos = 'Por favor, entre em contato com o suporte técnico.\n\n' +
|
||||
'1. Verifique se o seu usuário está associado a um funcionário no sistema\n' +
|
||||
'2. Solicite a associação do seu usuário a um funcionário\n' +
|
||||
'3. Entre em contato com a equipe de TI se o problema persistir\n\n' +
|
||||
'Detalhes técnicos:\n' + text;
|
||||
} else if (mensagemAmigavel.includes('não encontrado')) {
|
||||
mensagemAmigavel = mensagemAmigavel.replace(/não encontrado/gi, 'não foi encontrado no sistema');
|
||||
}
|
||||
|
||||
errorModalMessage = mensagemAmigavel;
|
||||
errorModalDetails = detalhesTecnicos;
|
||||
|
||||
showErrorModal = true;
|
||||
} else {
|
||||
// Para sucesso, usar banner simples
|
||||
notice = { kind, text };
|
||||
setTimeout(() => {
|
||||
notice = null;
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
function fecharErrorModal() {
|
||||
showErrorModal = false;
|
||||
errorModalMessage = '';
|
||||
errorModalDetails = '';
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
@@ -216,13 +265,22 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notificações -->
|
||||
<!-- Notificações (apenas sucesso) -->
|
||||
{#if notice}
|
||||
<div class="alert alert-{notice.kind} mb-6 shadow-lg">
|
||||
<span>{notice.text}</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Modal de Erro -->
|
||||
<ErrorModal
|
||||
open={showErrorModal}
|
||||
title={errorModalTitle}
|
||||
message={errorModalMessage}
|
||||
details={errorModalDetails}
|
||||
onClose={fecharErrorModal}
|
||||
/>
|
||||
|
||||
<!-- Filtros -->
|
||||
<div class="card bg-base-100 border border-base-300 mb-6 shadow-xl">
|
||||
<div class="card-body">
|
||||
|
||||
Reference in New Issue
Block a user