feat: add chart for displaying the last 10 registered products in 'Almoxarifado', enhancing inventory visibility and user engagement
This commit is contained in:
@@ -10,11 +10,15 @@
|
||||
BarChart3,
|
||||
CheckCircle2
|
||||
} from 'lucide-svelte';
|
||||
import BarChart3D from '$lib/components/ti/charts/BarChart3D.svelte';
|
||||
|
||||
const client = useConvexClient();
|
||||
const statsQuery = useQuery(api.almoxarifado.obterEstatisticas, {});
|
||||
const alertasQuery = useQuery(api.almoxarifado.listarAlertas, { status: 'ativo' });
|
||||
const materiaisQuery = useQuery(api.almoxarifado.listarMateriais, {});
|
||||
const ultimosProdutosQuery = useQuery(api.almoxarifado.obterUltimosProdutosCadastrados, {
|
||||
limit: 10
|
||||
});
|
||||
|
||||
// Criar mapa de materiais para lookup eficiente
|
||||
const materiaisMap = $derived.by(() => {
|
||||
@@ -25,6 +29,51 @@
|
||||
}
|
||||
return map;
|
||||
});
|
||||
|
||||
// Dados formatados para o gráfico
|
||||
const chartData = $derived.by(() => {
|
||||
if (!ultimosProdutosQuery?.data || ultimosProdutosQuery.data.length === 0) {
|
||||
return {
|
||||
labels: [],
|
||||
datasets: []
|
||||
};
|
||||
}
|
||||
|
||||
const produtos = ultimosProdutosQuery.data;
|
||||
|
||||
// Ordenar do mais antigo para o mais recente (para o gráfico)
|
||||
const produtosOrdenados = [...produtos].reverse();
|
||||
|
||||
// Criar cores gradientes para cada barra (efeito 3D)
|
||||
const cores = [
|
||||
'#6366f1', // indigo
|
||||
'#8b5cf6', // violet
|
||||
'#a855f7', // purple
|
||||
'#c084fc', // fuchsia
|
||||
'#d946ef', // pink
|
||||
'#ec4899', // rose
|
||||
'#f43f5e', // red
|
||||
'#fb7185', // pink-400
|
||||
'#f87171', // red-400
|
||||
'#fb923c' // orange-400
|
||||
];
|
||||
|
||||
return {
|
||||
labels: produtosOrdenados.map((p) => {
|
||||
// Truncar nome se muito longo
|
||||
return p.nome.length > 20 ? p.nome.substring(0, 20) + '...' : p.nome;
|
||||
}),
|
||||
datasets: [
|
||||
{
|
||||
label: 'Quantidade em Estoque',
|
||||
data: produtosOrdenados.map((p) => p.estoqueAtual),
|
||||
backgroundColor: produtosOrdenados.map((_, index) => cores[index % cores.length]),
|
||||
borderColor: produtosOrdenados.map((_, index) => cores[index % cores.length]),
|
||||
borderWidth: 2
|
||||
}
|
||||
]
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<main class="container mx-auto px-4 py-6">
|
||||
@@ -128,6 +177,37 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Gráfico de Produtos x Quantidades -->
|
||||
<div class="mb-8">
|
||||
<div class="card bg-base-100 border border-base-300 shadow-xl">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-2xl mb-4">
|
||||
<div class="rounded-lg bg-primary/20 p-2">
|
||||
<BarChart3 class="h-6 w-6 text-primary" />
|
||||
</div>
|
||||
<span>Últimos 10 Produtos Cadastrados</span>
|
||||
</h2>
|
||||
{#if ultimosProdutosQuery === undefined}
|
||||
<div class="flex items-center justify-center py-8">
|
||||
<span class="loading loading-spinner loading-lg text-primary"></span>
|
||||
</div>
|
||||
{:else if ultimosProdutosQuery.data && ultimosProdutosQuery.data.length > 0}
|
||||
<div class="w-full">
|
||||
<BarChart3D data={chartData} height={400} />
|
||||
</div>
|
||||
<div class="mt-4 text-sm text-base-content/60">
|
||||
<p>Mostrando os últimos 10 produtos cadastrados ordenados por data de criação</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="alert alert-info shadow-lg">
|
||||
<Package class="h-6 w-6" />
|
||||
<span class="font-medium">Nenhum produto cadastrado ainda</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Alertas Recentes -->
|
||||
<div class="mb-8">
|
||||
<div class="card bg-base-100 border border-base-300 shadow-xl">
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import { useConvexClient, useQuery } from 'convex-svelte';
|
||||
import { resolve } from '$app/paths';
|
||||
import { AlertTriangle, CheckCircle, XCircle, Package } from 'lucide-svelte';
|
||||
import ConfirmModal from '$lib/components/ConfirmModal.svelte';
|
||||
|
||||
const client = useConvexClient();
|
||||
|
||||
@@ -28,6 +29,8 @@
|
||||
});
|
||||
|
||||
let notice = $state<{ kind: 'success' | 'error'; text: string } | null>(null);
|
||||
let showConfirmIgnorar = $state(false);
|
||||
let alertaParaIgnorar = $state<Id<'alertasEstoque'> | null>(null);
|
||||
|
||||
function mostrarMensagem(kind: 'success' | 'error', text: string) {
|
||||
notice = { kind, text };
|
||||
@@ -36,6 +39,16 @@
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
function abrirModalIgnorar(id: Id<'alertasEstoque'>) {
|
||||
alertaParaIgnorar = id;
|
||||
showConfirmIgnorar = true;
|
||||
}
|
||||
|
||||
function fecharModalIgnorar() {
|
||||
showConfirmIgnorar = false;
|
||||
alertaParaIgnorar = null;
|
||||
}
|
||||
|
||||
async function resolverAlerta(id: Id<'alertasEstoque'>) {
|
||||
try {
|
||||
await client.mutation(api.almoxarifado.resolverAlerta, { id });
|
||||
@@ -47,10 +60,8 @@
|
||||
}
|
||||
|
||||
async function ignorarAlerta(id: Id<'alertasEstoque'>) {
|
||||
if (!confirm('Tem certeza que deseja ignorar este alerta?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
fecharModalIgnorar();
|
||||
|
||||
try {
|
||||
await client.mutation(api.almoxarifado.ignorarAlerta, { id });
|
||||
mostrarMensagem('success', 'Alerta ignorado');
|
||||
@@ -221,7 +232,7 @@
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm btn-ghost hover:btn-error"
|
||||
onclick={() => ignorarAlerta(alerta._id)}
|
||||
onclick={() => abrirModalIgnorar(alerta._id)}
|
||||
>
|
||||
<XCircle class="h-4 w-4" />
|
||||
Ignorar
|
||||
@@ -263,6 +274,17 @@
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal de Confirmação para Ignorar Alerta -->
|
||||
<ConfirmModal
|
||||
open={showConfirmIgnorar}
|
||||
title="Ignorar Alerta"
|
||||
message="Tem certeza que deseja ignorar este alerta? O estoque ainda estará abaixo do mínimo configurado."
|
||||
confirmText="Sim, ignorar"
|
||||
cancelText="Cancelar"
|
||||
onConfirm={() => alertaParaIgnorar && ignorarAlerta(alertaParaIgnorar)}
|
||||
onCancel={fecharModalIgnorar}
|
||||
/>
|
||||
</main>
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user