refactor: enhance 'Almoxarifado' UI with improved layout, updated styling, and efficient data handling for better user experience and performance
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
import { api } from '@sgse-app/backend/convex/_generated/api';
|
||||
import { useConvexClient, useQuery } from 'convex-svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { resolve } from '$app/paths';
|
||||
import {
|
||||
Package,
|
||||
AlertTriangle,
|
||||
@@ -14,69 +15,114 @@
|
||||
const statsQuery = useQuery(api.almoxarifado.obterEstatisticas, {});
|
||||
const alertasQuery = useQuery(api.almoxarifado.listarAlertas, { status: 'ativo' });
|
||||
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;
|
||||
});
|
||||
</script>
|
||||
|
||||
<main class="container mx-auto px-4 py-4">
|
||||
<main class="container mx-auto px-4 py-6">
|
||||
<!-- Breadcrumb -->
|
||||
<div class="breadcrumbs mb-6 text-sm">
|
||||
<ul>
|
||||
<li><a href={resolve('/')} class="text-primary hover:underline">Dashboard</a></li>
|
||||
<li>Almoxarifado</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Cabeçalho -->
|
||||
<div class="mb-8">
|
||||
<h1 class="text-primary mb-2 text-4xl font-bold">Almoxarifado</h1>
|
||||
<p class="text-base-content/70 text-lg">
|
||||
Controle de estoque e gestão de materiais
|
||||
</p>
|
||||
<div class="mb-4 flex items-center gap-4">
|
||||
<div class="rounded-2xl bg-gradient-to-br from-primary/20 to-primary/30 p-4 shadow-lg">
|
||||
<Package class="h-10 w-10 text-primary" strokeWidth={2.5} />
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-primary mb-2 text-4xl font-bold tracking-tight">Almoxarifado</h1>
|
||||
<p class="text-base-content/70 text-lg">
|
||||
Controle de estoque e gestão de materiais
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Estatísticas -->
|
||||
{#if statsQuery.data}
|
||||
<div class="mb-8 grid grid-cols-1 gap-4 md:grid-cols-4">
|
||||
<div class="stats from-primary/10 to-primary/20 bg-linear-to-br shadow-lg">
|
||||
<div class="stat">
|
||||
<div class="stat-figure text-primary">
|
||||
<Package class="h-8 w-8" strokeWidth={2} />
|
||||
{#if statsQuery === undefined}
|
||||
<div class="mb-8 flex items-center justify-center py-12">
|
||||
<span class="loading loading-spinner loading-lg text-primary"></span>
|
||||
</div>
|
||||
{:else if statsQuery.data}
|
||||
<div class="mb-8 grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-4">
|
||||
<div class="card bg-gradient-to-br from-primary/10 via-primary/5 to-base-100 border border-primary/20 shadow-xl hover:shadow-2xl transition-all duration-300 hover:scale-105">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="text-sm font-medium text-base-content/60 mb-1">Total de Materiais</div>
|
||||
<div class="text-3xl font-bold text-primary mb-1">
|
||||
{statsQuery.data.totalMateriais}
|
||||
</div>
|
||||
<div class="text-xs text-base-content/50">Materiais cadastrados</div>
|
||||
</div>
|
||||
<div class="rounded-xl bg-primary/20 p-3">
|
||||
<Package class="h-8 w-8 text-primary" strokeWidth={2.5} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-title">Total de Materiais</div>
|
||||
<div class="stat-value text-primary">
|
||||
{statsQuery.data.totalMateriais}
|
||||
</div>
|
||||
<div class="stat-desc">Materiais cadastrados</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stats from-success/10 to-success/20 bg-linear-to-br shadow-lg">
|
||||
<div class="stat">
|
||||
<div class="stat-figure text-success">
|
||||
<CheckCircle2 class="h-8 w-8" strokeWidth={2} />
|
||||
<div class="card bg-gradient-to-br from-success/10 via-success/5 to-base-100 border border-success/20 shadow-xl hover:shadow-2xl transition-all duration-300 hover:scale-105">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="text-sm font-medium text-base-content/60 mb-1">Materiais Ativos</div>
|
||||
<div class="text-3xl font-bold text-success mb-1">
|
||||
{statsQuery.data.totalMateriaisAtivos}
|
||||
</div>
|
||||
<div class="text-xs text-base-content/50">Em estoque</div>
|
||||
</div>
|
||||
<div class="rounded-xl bg-success/20 p-3">
|
||||
<CheckCircle2 class="h-8 w-8 text-success" strokeWidth={2.5} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-title">Materiais Ativos</div>
|
||||
<div class="stat-value text-success">
|
||||
{statsQuery.data.totalMateriaisAtivos}
|
||||
</div>
|
||||
<div class="stat-desc">Em estoque</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stats from-warning/10 to-warning/20 bg-linear-to-br shadow-lg">
|
||||
<div class="stat">
|
||||
<div class="stat-figure text-warning">
|
||||
<AlertTriangle class="h-8 w-8" strokeWidth={2} />
|
||||
<div class="card bg-gradient-to-br from-warning/10 via-warning/5 to-base-100 border border-warning/20 shadow-xl hover:shadow-2xl transition-all duration-300 hover:scale-105">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="text-sm font-medium text-base-content/60 mb-1">Alertas Ativos</div>
|
||||
<div class="text-3xl font-bold text-warning mb-1">
|
||||
{statsQuery.data.totalAlertasAtivos}
|
||||
</div>
|
||||
<div class="text-xs text-base-content/50">Estoque baixo</div>
|
||||
</div>
|
||||
<div class="rounded-xl bg-warning/20 p-3">
|
||||
<AlertTriangle class="h-8 w-8 text-warning" strokeWidth={2.5} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-title">Alertas Ativos</div>
|
||||
<div class="stat-value text-warning">
|
||||
{statsQuery.data.totalAlertasAtivos}
|
||||
</div>
|
||||
<div class="stat-desc">Estoque baixo</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stats from-info/10 to-info/20 bg-linear-to-br shadow-lg">
|
||||
<div class="stat">
|
||||
<div class="stat-figure text-info">
|
||||
<ArrowLeftRight class="h-8 w-8" strokeWidth={2} />
|
||||
<div class="card bg-gradient-to-br from-info/10 via-info/5 to-base-100 border border-info/20 shadow-xl hover:shadow-2xl transition-all duration-300 hover:scale-105">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="text-sm font-medium text-base-content/60 mb-1">Movimentações</div>
|
||||
<div class="text-3xl font-bold text-info mb-1">
|
||||
{statsQuery.data.movimentacoesMes}
|
||||
</div>
|
||||
<div class="text-xs text-base-content/50">Este mês</div>
|
||||
</div>
|
||||
<div class="rounded-xl bg-info/20 p-3">
|
||||
<ArrowLeftRight class="h-8 w-8 text-info" strokeWidth={2.5} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-title">Movimentações</div>
|
||||
<div class="stat-value text-info">
|
||||
{statsQuery.data.movimentacoesMes}
|
||||
</div>
|
||||
<div class="stat-desc">Este mês</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -84,45 +130,58 @@
|
||||
|
||||
<!-- Alertas Recentes -->
|
||||
<div class="mb-8">
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card bg-base-100 border border-base-300 shadow-xl">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-2xl">
|
||||
<AlertTriangle class="h-6 w-6 text-warning" />
|
||||
Alertas de Estoque
|
||||
<h2 class="card-title text-2xl mb-1">
|
||||
<div class="rounded-lg bg-warning/20 p-2">
|
||||
<AlertTriangle class="h-6 w-6 text-warning" />
|
||||
</div>
|
||||
<span>Alertas de Estoque</span>
|
||||
</h2>
|
||||
{#if alertasQuery.data && alertasQuery.data.length > 0}
|
||||
{#if alertasQuery === undefined}
|
||||
<div class="flex items-center justify-center py-8">
|
||||
<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 class="table table-zebra">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Material</th>
|
||||
<th>Tipo</th>
|
||||
<th>Quantidade Atual</th>
|
||||
<th>Quantidade Mínima</th>
|
||||
<th>Ações</th>
|
||||
<tr class="bg-base-200">
|
||||
<th class="font-semibold">Material</th>
|
||||
<th class="font-semibold">Tipo</th>
|
||||
<th class="font-semibold">Quantidade Atual</th>
|
||||
<th class="font-semibold">Quantidade Mínima</th>
|
||||
<th class="font-semibold">Ações</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each alertasQuery.data.slice(0, 5) as alerta}
|
||||
{@const material = materiaisQuery.data?.find(m => m._id === alerta.materialId)}
|
||||
<tr>
|
||||
{@const material = materiaisMap.get(alerta.materialId)}
|
||||
<tr class="hover:bg-base-200/50 transition-colors">
|
||||
<td>
|
||||
{material?.nome || 'Carregando...'}
|
||||
<div class="font-medium">{material?.nome || 'Carregando...'}</div>
|
||||
{#if material?.codigo}
|
||||
<div class="text-xs text-base-content/50 font-mono">{material.codigo}</div>
|
||||
{/if}
|
||||
</td>
|
||||
<td>
|
||||
{#if alerta.tipo === 'estoque_zerado'}
|
||||
<span class="badge badge-error">Zerado</span>
|
||||
<span class="badge badge-error badge-lg">Zerado</span>
|
||||
{:else if alerta.tipo === 'estoque_minimo'}
|
||||
<span class="badge badge-warning">Mínimo</span>
|
||||
<span class="badge badge-warning badge-lg">Mínimo</span>
|
||||
{:else}
|
||||
<span class="badge badge-info">Reposição</span>
|
||||
<span class="badge badge-info badge-lg">Reposição</span>
|
||||
{/if}
|
||||
</td>
|
||||
<td>{alerta.quantidadeAtual}</td>
|
||||
<td>{alerta.quantidadeMinima}</td>
|
||||
<td>
|
||||
<span class="font-bold text-error">{alerta.quantidadeAtual}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="font-medium">{alerta.quantidadeMinima}</span>
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
class="btn btn-sm btn-primary"
|
||||
class="btn btn-sm btn-primary btn-outline"
|
||||
onclick={() => goto('/almoxarifado/alertas')}
|
||||
>
|
||||
Ver Detalhes
|
||||
@@ -133,7 +192,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="card-actions justify-end">
|
||||
<div class="card-actions justify-end mt-4">
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
onclick={() => goto('/almoxarifado/alertas')}
|
||||
@@ -142,9 +201,9 @@
|
||||
</button>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="alert alert-success">
|
||||
<div class="alert alert-success shadow-lg">
|
||||
<CheckCircle2 class="h-6 w-6" />
|
||||
<span>Nenhum alerta ativo no momento!</span>
|
||||
<span class="font-medium">Nenhum alerta ativo no momento!</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -152,43 +211,49 @@
|
||||
</div>
|
||||
|
||||
<!-- Ações Rápidas -->
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-3">
|
||||
<div class="grid grid-cols-1 gap-6 md:grid-cols-3">
|
||||
<button
|
||||
class="card bg-base-100 shadow-xl hover:shadow-2xl transition-shadow"
|
||||
class="card bg-base-100 border border-base-300 shadow-xl hover:shadow-2xl hover:border-primary/50 transition-all duration-300 hover:scale-[1.02] group"
|
||||
onclick={() => goto('/almoxarifado/materiais/cadastro')}
|
||||
>
|
||||
<div class="card-body">
|
||||
<h3 class="card-title">
|
||||
<Package class="h-6 w-6 text-primary" />
|
||||
Cadastrar Material
|
||||
</h3>
|
||||
<p class="text-base-content/70">Adicionar novo material ao estoque</p>
|
||||
<div class="flex items-center gap-4 mb-2">
|
||||
<div class="rounded-xl bg-primary/20 p-3 group-hover:bg-primary/30 transition-colors">
|
||||
<Package class="h-7 w-7 text-primary" strokeWidth={2.5} />
|
||||
</div>
|
||||
<h3 class="card-title text-lg mb-0">Cadastrar Material</h3>
|
||||
</div>
|
||||
<p class="text-base-content/70 text-sm">Adicionar novo material ao estoque</p>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="card bg-base-100 shadow-xl hover:shadow-2xl transition-shadow"
|
||||
class="card bg-base-100 border border-base-300 shadow-xl hover:shadow-2xl hover:border-info/50 transition-all duration-300 hover:scale-[1.02] group"
|
||||
onclick={() => goto('/almoxarifado/movimentacoes')}
|
||||
>
|
||||
<div class="card-body">
|
||||
<h3 class="card-title">
|
||||
<ArrowLeftRight class="h-6 w-6 text-info" />
|
||||
Registrar Movimentação
|
||||
</h3>
|
||||
<p class="text-base-content/70">Registrar entrada ou saída de material</p>
|
||||
<div class="flex items-center gap-4 mb-2">
|
||||
<div class="rounded-xl bg-info/20 p-3 group-hover:bg-info/30 transition-colors">
|
||||
<ArrowLeftRight class="h-7 w-7 text-info" strokeWidth={2.5} />
|
||||
</div>
|
||||
<h3 class="card-title text-lg mb-0">Registrar Movimentação</h3>
|
||||
</div>
|
||||
<p class="text-base-content/70 text-sm">Registrar entrada ou saída de material</p>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="card bg-base-100 shadow-xl hover:shadow-2xl transition-shadow"
|
||||
class="card bg-base-100 border border-base-300 shadow-xl hover:shadow-2xl hover:border-success/50 transition-all duration-300 hover:scale-[1.02] group"
|
||||
onclick={() => goto('/almoxarifado/relatorios')}
|
||||
>
|
||||
<div class="card-body">
|
||||
<h3 class="card-title">
|
||||
<BarChart3 class="h-6 w-6 text-success" />
|
||||
Relatórios
|
||||
</h3>
|
||||
<p class="text-base-content/70">Visualizar relatórios e estatísticas</p>
|
||||
<div class="flex items-center gap-4 mb-2">
|
||||
<div class="rounded-xl bg-success/20 p-3 group-hover:bg-success/30 transition-colors">
|
||||
<BarChart3 class="h-7 w-7 text-success" strokeWidth={2.5} />
|
||||
</div>
|
||||
<h3 class="card-title text-lg mb-0">Relatórios</h3>
|
||||
</div>
|
||||
<p class="text-base-content/70 text-sm">Visualizar relatórios e estatísticas</p>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -77,15 +77,10 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<main class="container mx-auto px-4 py-4">
|
||||
<main class="container mx-auto px-4 py-6">
|
||||
<!-- Breadcrumb -->
|
||||
<div class="breadcrumbs mb-4 text-sm">
|
||||
<div class="breadcrumbs mb-6 text-sm">
|
||||
<ul>
|
||||
<li>
|
||||
<a href={resolve('/recursos-humanos')} class="text-primary hover:underline"
|
||||
>Recursos Humanos</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href={resolve('/almoxarifado')} class="text-primary hover:underline"
|
||||
>Almoxarifado</a
|
||||
@@ -96,14 +91,14 @@
|
||||
</div>
|
||||
|
||||
<!-- Cabeçalho -->
|
||||
<div class="mb-6">
|
||||
<div class="mb-8">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="rounded-xl bg-warning/20 p-3">
|
||||
<AlertTriangle class="h-8 w-8 text-warning" strokeWidth={2} />
|
||||
<div class="rounded-2xl bg-gradient-to-br from-warning/20 to-warning/30 p-4 shadow-lg">
|
||||
<AlertTriangle class="h-10 w-10 text-warning" strokeWidth={2.5} />
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold">Alertas de Estoque</h1>
|
||||
<p class="text-base-content/70">Visualize e gerencie alertas de estoque baixo</p>
|
||||
<h1 class="text-3xl font-bold tracking-tight">Alertas de Estoque</h1>
|
||||
<p class="text-base-content/70 text-lg">Visualize e gerencie alertas de estoque baixo</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -116,8 +111,9 @@
|
||||
{/if}
|
||||
|
||||
<!-- Filtros -->
|
||||
<div class="card bg-base-100 mb-6 shadow-xl">
|
||||
<div class="card bg-base-100 border border-base-300 mb-6 shadow-xl">
|
||||
<div class="card-body">
|
||||
<h3 class="text-lg font-semibold mb-4">Filtros</h3>
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
@@ -147,39 +143,39 @@
|
||||
</div>
|
||||
|
||||
<!-- Lista de Alertas -->
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card bg-base-100 border border-base-300 shadow-xl">
|
||||
<div class="card-body">
|
||||
{#if alertasQuery.data && alertasQuery.data.length > 0}
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<table class="table table-zebra">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Material</th>
|
||||
<th>Tipo</th>
|
||||
<th>Quantidade Atual</th>
|
||||
<th>Quantidade Mínima</th>
|
||||
<th>Diferença</th>
|
||||
<th>Status</th>
|
||||
<th>Data</th>
|
||||
<th>Ações</th>
|
||||
<tr class="bg-base-200">
|
||||
<th class="font-semibold">Material</th>
|
||||
<th class="font-semibold">Tipo</th>
|
||||
<th class="font-semibold">Quantidade Atual</th>
|
||||
<th class="font-semibold">Quantidade Mínima</th>
|
||||
<th class="font-semibold">Diferença</th>
|
||||
<th class="font-semibold">Status</th>
|
||||
<th class="font-semibold">Data</th>
|
||||
<th class="font-semibold">Ações</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each alertasQuery.data as alerta}
|
||||
{@const material = materiaisQuery.data?.find(m => m._id === alerta.materialId)}
|
||||
{@const diferenca = alerta.quantidadeMinima - alerta.quantidadeAtual}
|
||||
<tr>
|
||||
<tr class="hover:bg-base-200/50 transition-colors">
|
||||
<td>
|
||||
<div class="font-medium">{material?.nome || 'Carregando...'}</div>
|
||||
<div class="text-sm text-base-content/60">{material?.codigo || ''}</div>
|
||||
<div class="text-sm text-base-content/60 font-mono">{material?.codigo || ''}</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge {getTipoBadge(alerta.tipo)}">
|
||||
<span class="badge {getTipoBadge(alerta.tipo)} badge-lg">
|
||||
{getTipoLabel(alerta.tipo)}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="font-bold text-error">{alerta.quantidadeAtual}</div>
|
||||
<div class="font-bold text-error text-lg">{alerta.quantidadeAtual}</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="font-medium">{alerta.quantidadeMinima}</div>
|
||||
@@ -189,14 +185,16 @@
|
||||
</td>
|
||||
<td>
|
||||
{#if alerta.status === 'ativo'}
|
||||
<span class="badge badge-warning">Ativo</span>
|
||||
<span class="badge badge-warning badge-lg">Ativo</span>
|
||||
{:else if alerta.status === 'resolvido'}
|
||||
<span class="badge badge-success">Resolvido</span>
|
||||
<span class="badge badge-success badge-lg">Resolvido</span>
|
||||
{:else}
|
||||
<span class="badge badge-ghost">Ignorado</span>
|
||||
<span class="badge badge-ghost badge-lg">Ignorado</span>
|
||||
{/if}
|
||||
</td>
|
||||
<td>{new Date(alerta.criadoEm).toLocaleDateString('pt-BR')}</td>
|
||||
<td>
|
||||
<span class="text-sm">{new Date(alerta.criadoEm).toLocaleDateString('pt-BR')}</span>
|
||||
</td>
|
||||
<td>
|
||||
{#if alerta.status === 'ativo'}
|
||||
<div class="flex gap-2">
|
||||
@@ -208,7 +206,7 @@
|
||||
Resolver
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm btn-ghost"
|
||||
class="btn btn-sm btn-ghost hover:btn-error"
|
||||
onclick={() => ignorarAlerta(alerta._id)}
|
||||
>
|
||||
<XCircle class="h-4 w-4" />
|
||||
@@ -224,9 +222,9 @@
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-center py-12">
|
||||
<CheckCircle class="mx-auto mb-4 h-16 w-16 text-success" />
|
||||
<h3 class="text-xl font-bold mb-2">Nenhum alerta encontrado</h3>
|
||||
<p class="text-base-content/70">
|
||||
<CheckCircle class="mx-auto mb-4 h-20 w-20 text-success" />
|
||||
<h3 class="text-2xl font-bold mb-2">Nenhum alerta encontrado</h3>
|
||||
<p class="text-base-content/70 text-lg">
|
||||
{#if filtroStatus === 'ativo'}
|
||||
Não há alertas ativos no momento. Todos os materiais estão com estoque adequado!
|
||||
{:else}
|
||||
|
||||
@@ -52,9 +52,9 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<main class="container mx-auto px-4 py-4">
|
||||
<main class="container mx-auto px-4 py-6">
|
||||
<!-- Breadcrumb -->
|
||||
<div class="breadcrumbs mb-4 text-sm">
|
||||
<div class="breadcrumbs mb-6 text-sm">
|
||||
<ul>
|
||||
<li>
|
||||
<a href={resolve('/almoxarifado')} class="text-primary hover:underline">Almoxarifado</a>
|
||||
@@ -64,18 +64,18 @@
|
||||
</div>
|
||||
|
||||
<!-- Cabeçalho -->
|
||||
<div class="mb-6">
|
||||
<div class="mb-8">
|
||||
<div class="flex flex-col items-start justify-between gap-4 sm:flex-row sm:items-center">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="rounded-xl bg-amber-500/20 p-3">
|
||||
<Package class="h-8 w-8 text-amber-600" strokeWidth={2} />
|
||||
<div class="rounded-2xl bg-gradient-to-br from-amber-500/20 to-amber-600/30 p-4 shadow-lg">
|
||||
<Package class="h-10 w-10 text-amber-600" strokeWidth={2.5} />
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold">Materiais</h1>
|
||||
<p class="text-base-content/70">Gerencie o cadastro de materiais do almoxarifado</p>
|
||||
<h1 class="text-3xl font-bold tracking-tight">Materiais</h1>
|
||||
<p class="text-base-content/70 text-lg">Gerencie o cadastro de materiais do almoxarifado</p>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-primary" onclick={navCadastro}>
|
||||
<button class="btn btn-primary shadow-lg hover:shadow-xl transition-all" onclick={navCadastro}>
|
||||
<Plus class="h-5 w-5" />
|
||||
Cadastrar Material
|
||||
</button>
|
||||
@@ -83,8 +83,9 @@
|
||||
</div>
|
||||
|
||||
<!-- Filtros -->
|
||||
<div class="card bg-base-100 mb-6 shadow-xl">
|
||||
<div class="card bg-base-100 border border-base-300 mb-6 shadow-xl">
|
||||
<div class="card-body">
|
||||
<h3 class="text-lg font-semibold mb-4">Filtros de Busca</h3>
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-4">
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
@@ -135,68 +136,74 @@
|
||||
</div>
|
||||
|
||||
<!-- Tabela -->
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card bg-base-100 border border-base-300 shadow-xl">
|
||||
<div class="card-body">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<table class="table table-zebra">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Código</th>
|
||||
<th>Nome</th>
|
||||
<th>Categoria</th>
|
||||
<th>Estoque Atual</th>
|
||||
<th>Estoque Mínimo</th>
|
||||
<th>Unidade</th>
|
||||
<th>Status</th>
|
||||
<th>Ações</th>
|
||||
<tr class="bg-base-200">
|
||||
<th class="font-semibold">Código</th>
|
||||
<th class="font-semibold">Nome</th>
|
||||
<th class="font-semibold">Categoria</th>
|
||||
<th class="font-semibold">Estoque Atual</th>
|
||||
<th class="font-semibold">Estoque Mínimo</th>
|
||||
<th class="font-semibold">Unidade</th>
|
||||
<th class="font-semibold">Status</th>
|
||||
<th class="font-semibold">Ações</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#if filtered.length === 0}
|
||||
<tr>
|
||||
<td colspan="8" class="text-center">
|
||||
<div class="py-8">
|
||||
<Package class="mx-auto mb-4 h-12 w-12 text-base-content/30" />
|
||||
<p class="text-base-content/70">Nenhum material encontrado</p>
|
||||
<div class="py-12">
|
||||
<Package class="mx-auto mb-4 h-16 w-16 text-base-content/30" />
|
||||
<p class="text-base-content/70 text-lg font-medium">Nenhum material encontrado</p>
|
||||
<p class="text-base-content/50 text-sm mt-2">Tente ajustar os filtros de busca</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{:else}
|
||||
{#each filtered as material}
|
||||
<tr>
|
||||
<tr class="hover:bg-base-200/50 transition-colors">
|
||||
<td>
|
||||
<div class="font-mono font-bold">{material.codigo}</div>
|
||||
<div class="font-mono font-bold text-primary">{material.codigo}</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="font-medium">{material.nome}</div>
|
||||
{#if material.descricao}
|
||||
<div class="text-sm text-base-content/60">{material.descricao}</div>
|
||||
<div class="text-sm text-base-content/60 line-clamp-1">{material.descricao}</div>
|
||||
{/if}
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge badge-outline">{material.categoria}</span>
|
||||
<span class="badge badge-outline badge-lg">{material.categoria}</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-bold">{material.estoqueAtual}</span>
|
||||
<span class="font-bold {material.estoqueAtual <= material.estoqueMinimo ? 'text-error' : 'text-success'}">{material.estoqueAtual}</span>
|
||||
{#if material.estoqueAtual <= material.estoqueMinimo}
|
||||
<AlertTriangle class="h-4 w-4 text-warning" />
|
||||
{/if}
|
||||
</div>
|
||||
</td>
|
||||
<td>{material.estoqueMinimo}</td>
|
||||
<td>{material.unidadeMedida}</td>
|
||||
<td>
|
||||
<span class="font-medium">{material.estoqueMinimo}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge badge-ghost">{material.unidadeMedida}</span>
|
||||
</td>
|
||||
<td>
|
||||
{#if material.ativo}
|
||||
<span class="badge badge-success">Ativo</span>
|
||||
<span class="badge badge-success badge-lg">Ativo</span>
|
||||
{:else}
|
||||
<span class="badge badge-error">Inativo</span>
|
||||
<span class="badge badge-error badge-lg">Inativo</span>
|
||||
{/if}
|
||||
</td>
|
||||
<td>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
class="btn btn-sm btn-ghost"
|
||||
class="btn btn-sm btn-ghost hover:btn-primary"
|
||||
title="Visualizar"
|
||||
onclick={() =>
|
||||
goto(
|
||||
resolve(
|
||||
@@ -208,7 +215,8 @@
|
||||
<Eye class="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm btn-ghost"
|
||||
class="btn btn-sm btn-ghost hover:btn-info"
|
||||
title="Editar"
|
||||
onclick={() =>
|
||||
goto(
|
||||
resolve(
|
||||
@@ -230,8 +238,10 @@
|
||||
</div>
|
||||
|
||||
{#if filtered.length > 0}
|
||||
<div class="mt-4 text-sm text-base-content/70">
|
||||
Mostrando {filtered.length} de {materiais.length} materiais
|
||||
<div class="mt-6 flex items-center justify-between border-t border-base-300 pt-4">
|
||||
<div class="text-sm text-base-content/70">
|
||||
Mostrando <span class="font-semibold text-base-content">{filtered.length}</span> de <span class="font-semibold text-base-content">{materiais.length}</span> materiais
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -61,9 +61,9 @@
|
||||
<span class="loading loading-spinner loading-lg"></span>
|
||||
</div>
|
||||
{:else if material}
|
||||
<main class="container mx-auto px-4 py-4">
|
||||
<main class="container mx-auto px-4 py-6">
|
||||
<!-- Breadcrumb -->
|
||||
<div class="breadcrumbs mb-4 text-sm">
|
||||
<div class="breadcrumbs mb-6 text-sm">
|
||||
<ul>
|
||||
<li>
|
||||
<a href={resolve('/almoxarifado')} class="text-primary hover:underline">Almoxarifado</a>
|
||||
@@ -78,7 +78,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Cabeçalho -->
|
||||
<div class="mb-6">
|
||||
<div class="mb-8">
|
||||
<div class="flex flex-col items-start justify-between gap-4 sm:flex-row sm:items-center">
|
||||
<div class="flex items-center gap-4">
|
||||
<button
|
||||
@@ -87,15 +87,15 @@
|
||||
>
|
||||
<ArrowLeft class="h-5 w-5" />
|
||||
</button>
|
||||
<div class="rounded-xl bg-amber-500/20 p-3">
|
||||
<Package class="h-8 w-8 text-amber-600" strokeWidth={2} />
|
||||
<div class="rounded-2xl bg-gradient-to-br from-amber-500/20 to-amber-600/30 p-4 shadow-lg">
|
||||
<Package class="h-10 w-10 text-amber-600" strokeWidth={2.5} />
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold">{material.nome}</h1>
|
||||
<p class="text-base-content/70">Detalhes do material</p>
|
||||
<h1 class="text-3xl font-bold tracking-tight">{material.nome}</h1>
|
||||
<p class="text-base-content/70 text-lg">Detalhes do material</p>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-primary" onclick={navEditar}>
|
||||
<button class="btn btn-primary shadow-lg hover:shadow-xl transition-all" onclick={navEditar}>
|
||||
<Edit class="h-5 w-5" />
|
||||
Editar Material
|
||||
</button>
|
||||
@@ -105,10 +105,12 @@
|
||||
<!-- Informações Principais -->
|
||||
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
<!-- Card: Informações Básicas -->
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card bg-base-100 border border-base-300 shadow-xl">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title mb-4">
|
||||
<Package class="h-5 w-5" />
|
||||
<h2 class="card-title mb-6 text-xl">
|
||||
<div class="rounded-lg bg-primary/20 p-2">
|
||||
<Package class="h-6 w-6 text-primary" />
|
||||
</div>
|
||||
Informações Básicas
|
||||
</h2>
|
||||
<div class="space-y-4">
|
||||
@@ -157,10 +159,12 @@
|
||||
</div>
|
||||
|
||||
<!-- Card: Estoque -->
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card bg-base-100 border border-base-300 shadow-xl">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title mb-4">
|
||||
<Boxes class="h-5 w-5" />
|
||||
<h2 class="card-title mb-6 text-xl">
|
||||
<div class="rounded-lg bg-info/20 p-2">
|
||||
<Boxes class="h-6 w-6 text-info" />
|
||||
</div>
|
||||
Estoque
|
||||
</h2>
|
||||
<div class="space-y-4">
|
||||
@@ -196,10 +200,12 @@
|
||||
</div>
|
||||
|
||||
<!-- Informações Adicionais -->
|
||||
<div class="card bg-base-100 mt-6 shadow-xl">
|
||||
<div class="card bg-base-100 border border-base-300 mt-6 shadow-xl">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title mb-4">
|
||||
<Tag class="h-5 w-5" />
|
||||
<h2 class="card-title mb-6 text-xl">
|
||||
<div class="rounded-lg bg-success/20 p-2">
|
||||
<Tag class="h-6 w-6 text-success" />
|
||||
</div>
|
||||
Informações Adicionais
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
|
||||
@@ -138,9 +138,9 @@
|
||||
<span class="loading loading-spinner loading-lg"></span>
|
||||
</div>
|
||||
{:else}
|
||||
<main class="container mx-auto px-4 py-4">
|
||||
<main class="container mx-auto px-4 py-6">
|
||||
<!-- Breadcrumb -->
|
||||
<div class="breadcrumbs mb-4 text-sm">
|
||||
<div class="breadcrumbs mb-6 text-sm">
|
||||
<ul>
|
||||
<li>
|
||||
<a href={resolve('/almoxarifado')} class="text-primary hover:underline">Almoxarifado</a>
|
||||
@@ -160,7 +160,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Cabeçalho -->
|
||||
<div class="mb-6">
|
||||
<div class="mb-8">
|
||||
<div class="flex items-center gap-4">
|
||||
<button
|
||||
class="btn btn-ghost btn-sm"
|
||||
@@ -168,12 +168,12 @@
|
||||
>
|
||||
<ArrowLeft class="h-5 w-5" />
|
||||
</button>
|
||||
<div class="rounded-xl bg-amber-500/20 p-3">
|
||||
<Package class="h-8 w-8 text-amber-600" strokeWidth={2} />
|
||||
<div class="rounded-2xl bg-gradient-to-br from-amber-500/20 to-amber-600/30 p-4 shadow-lg">
|
||||
<Package class="h-10 w-10 text-amber-600" strokeWidth={2.5} />
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold">Editar Material</h1>
|
||||
<p class="text-base-content/70">Atualize as informações do material</p>
|
||||
<h1 class="text-3xl font-bold tracking-tight">Editar Material</h1>
|
||||
<p class="text-base-content/70 text-lg">Atualize as informações do material</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -186,7 +186,7 @@
|
||||
{/if}
|
||||
|
||||
<!-- Formulário -->
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card bg-base-100 border border-base-300 shadow-xl">
|
||||
<div class="card-body">
|
||||
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }}>
|
||||
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
|
||||
@@ -89,9 +89,9 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<main class="container mx-auto px-4 py-4">
|
||||
<main class="container mx-auto px-4 py-6">
|
||||
<!-- Breadcrumb -->
|
||||
<div class="breadcrumbs mb-4 text-sm">
|
||||
<div class="breadcrumbs mb-6 text-sm">
|
||||
<ul>
|
||||
<li>
|
||||
<a href={resolve('/almoxarifado')} class="text-primary hover:underline">Almoxarifado</a>
|
||||
@@ -106,7 +106,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Cabeçalho -->
|
||||
<div class="mb-6">
|
||||
<div class="mb-8">
|
||||
<div class="flex items-center gap-4">
|
||||
<button
|
||||
class="btn btn-ghost btn-sm"
|
||||
@@ -114,12 +114,12 @@
|
||||
>
|
||||
<ArrowLeft class="h-5 w-5" />
|
||||
</button>
|
||||
<div class="rounded-xl bg-amber-500/20 p-3">
|
||||
<Package class="h-8 w-8 text-amber-600" strokeWidth={2} />
|
||||
<div class="rounded-2xl bg-gradient-to-br from-amber-500/20 to-amber-600/30 p-4 shadow-lg">
|
||||
<Package class="h-10 w-10 text-amber-600" strokeWidth={2.5} />
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold">Cadastrar Material</h1>
|
||||
<p class="text-base-content/70">Adicione um novo material ao almoxarifado</p>
|
||||
<h1 class="text-3xl font-bold tracking-tight">Cadastrar Material</h1>
|
||||
<p class="text-base-content/70 text-lg">Adicione um novo material ao almoxarifado</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -132,7 +132,7 @@
|
||||
{/if}
|
||||
|
||||
<!-- Formulário -->
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card bg-base-100 border border-base-300 shadow-xl">
|
||||
<div class="card-body">
|
||||
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }}>
|
||||
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
|
||||
@@ -39,6 +39,26 @@
|
||||
const setoresQuery = useQuery(api.setores.list, {});
|
||||
const movimentacoesQuery = useQuery(api.almoxarifado.listarMovimentacoes, {});
|
||||
|
||||
// Criar mapa de funcionários para lookup eficiente
|
||||
const funcionariosMap = $derived.by(() => {
|
||||
if (!funcionariosQuery.data) return new Map();
|
||||
const map = new Map();
|
||||
for (const funcionario of funcionariosQuery.data) {
|
||||
map.set(funcionario._id, funcionario);
|
||||
}
|
||||
return map;
|
||||
});
|
||||
|
||||
// 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) {
|
||||
@@ -151,15 +171,10 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<main class="container mx-auto px-4 py-4">
|
||||
<main class="container mx-auto px-4 py-6">
|
||||
<!-- Breadcrumb -->
|
||||
<div class="breadcrumbs mb-4 text-sm">
|
||||
<div class="breadcrumbs mb-6 text-sm">
|
||||
<ul>
|
||||
<li>
|
||||
<a href={resolve('/recursos-humanos')} class="text-primary hover:underline"
|
||||
>Recursos Humanos</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href={resolve('/almoxarifado')} class="text-primary hover:underline"
|
||||
>Almoxarifado</a
|
||||
@@ -170,14 +185,14 @@
|
||||
</div>
|
||||
|
||||
<!-- Cabeçalho -->
|
||||
<div class="mb-6">
|
||||
<div class="mb-8">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="rounded-xl bg-info/20 p-3">
|
||||
<ArrowLeftRight class="h-8 w-8 text-info" strokeWidth={2} />
|
||||
<div class="rounded-2xl bg-gradient-to-br from-info/20 to-info/30 p-4 shadow-lg">
|
||||
<ArrowLeftRight class="h-10 w-10 text-info" strokeWidth={2.5} />
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold">Movimentações de Estoque</h1>
|
||||
<p class="text-base-content/70">Registre entradas, saídas e ajustes de estoque</p>
|
||||
<h1 class="text-3xl font-bold tracking-tight">Movimentações de Estoque</h1>
|
||||
<p class="text-base-content/70 text-lg">Registre entradas, saídas e ajustes de estoque</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -190,30 +205,30 @@
|
||||
{/if}
|
||||
|
||||
<!-- Abas -->
|
||||
<div class="tabs tabs-boxed mb-6">
|
||||
<div class="tabs tabs-boxed mb-6 bg-base-200 shadow-md">
|
||||
<button
|
||||
class="tab {abaAtiva === 'entrada' ? 'tab-active' : ''}"
|
||||
class="tab {abaAtiva === 'entrada' ? 'tab-active' : ''} transition-all"
|
||||
onclick={() => (abaAtiva = 'entrada')}
|
||||
>
|
||||
<ArrowDown class="h-5 w-5 mr-2" />
|
||||
Entrada
|
||||
</button>
|
||||
<button
|
||||
class="tab {abaAtiva === 'saida' ? 'tab-active' : ''}"
|
||||
class="tab {abaAtiva === 'saida' ? 'tab-active' : ''} transition-all"
|
||||
onclick={() => (abaAtiva = 'saida')}
|
||||
>
|
||||
<ArrowUp class="h-5 w-5 mr-2" />
|
||||
Saída
|
||||
</button>
|
||||
<button
|
||||
class="tab {abaAtiva === 'ajuste' ? 'tab-active' : ''}"
|
||||
class="tab {abaAtiva === 'ajuste' ? 'tab-active' : ''} transition-all"
|
||||
onclick={() => (abaAtiva = 'ajuste')}
|
||||
>
|
||||
<Settings class="h-5 w-5 mr-2" />
|
||||
Ajuste
|
||||
</button>
|
||||
<button
|
||||
class="tab {abaAtiva === 'historico' ? 'tab-active' : ''}"
|
||||
class="tab {abaAtiva === 'historico' ? 'tab-active' : ''} transition-all"
|
||||
onclick={() => (abaAtiva = 'historico')}
|
||||
>
|
||||
<History class="h-5 w-5 mr-2" />
|
||||
@@ -223,9 +238,14 @@
|
||||
|
||||
<!-- Conteúdo das Abas -->
|
||||
{#if abaAtiva === 'entrada'}
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card bg-base-100 border border-base-300 shadow-xl">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title mb-4">Registrar Entrada de Material</h2>
|
||||
<h2 class="card-title mb-6 text-xl">
|
||||
<div class="rounded-lg bg-success/20 p-2">
|
||||
<ArrowDown class="h-6 w-6 text-success" />
|
||||
</div>
|
||||
Registrar Entrada de Material
|
||||
</h2>
|
||||
<form onsubmit={(e) => { e.preventDefault(); registrarEntrada(); }}>
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div class="form-control md:col-span-2">
|
||||
@@ -310,9 +330,14 @@
|
||||
</div>
|
||||
</div>
|
||||
{:else if abaAtiva === 'saida'}
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card bg-base-100 border border-base-300 shadow-xl">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title mb-4">Registrar Saída de Material</h2>
|
||||
<h2 class="card-title mb-6 text-xl">
|
||||
<div class="rounded-lg bg-error/20 p-2">
|
||||
<ArrowUp class="h-6 w-6 text-error" />
|
||||
</div>
|
||||
Registrar Saída de Material
|
||||
</h2>
|
||||
<form onsubmit={(e) => { e.preventDefault(); registrarSaida(); }}>
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div class="form-control md:col-span-2">
|
||||
@@ -413,12 +438,17 @@
|
||||
</div>
|
||||
</div>
|
||||
{:else if abaAtiva === 'ajuste'}
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card bg-base-100 border border-base-300 shadow-xl">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title mb-4">Ajustar Estoque</h2>
|
||||
<div class="alert alert-warning mb-4">
|
||||
<h2 class="card-title mb-6 text-xl">
|
||||
<div class="rounded-lg bg-warning/20 p-2">
|
||||
<Settings class="h-6 w-6 text-warning" />
|
||||
</div>
|
||||
Ajustar Estoque
|
||||
</h2>
|
||||
<div class="alert alert-warning mb-6 shadow-lg">
|
||||
<Settings class="h-6 w-6" />
|
||||
<span>Ajustes de estoque devem ser justificados e são registrados no histórico.</span>
|
||||
<span class="font-medium">Ajustes de estoque devem ser justificados e são registrados no histórico.</span>
|
||||
</div>
|
||||
<form onsubmit={(e) => { e.preventDefault(); ajustarEstoque(); }}>
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
@@ -491,50 +521,82 @@
|
||||
</div>
|
||||
</div>
|
||||
{:else if abaAtiva === 'historico'}
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card bg-base-100 border border-base-300 shadow-xl">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title mb-4">Histórico de Movimentações</h2>
|
||||
<h2 class="card-title mb-6 text-xl">
|
||||
<div class="rounded-lg bg-info/20 p-2">
|
||||
<History class="h-6 w-6 text-info" />
|
||||
</div>
|
||||
Histórico de Movimentações
|
||||
</h2>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<table class="table table-zebra">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Data</th>
|
||||
<th>Material</th>
|
||||
<th>Tipo</th>
|
||||
<th>Quantidade</th>
|
||||
<th>Anterior</th>
|
||||
<th>Nova</th>
|
||||
<th>Motivo</th>
|
||||
<tr class="bg-base-200">
|
||||
<th class="font-semibold">Data</th>
|
||||
<th class="font-semibold">Material</th>
|
||||
<th class="font-semibold">Tipo</th>
|
||||
<th class="font-semibold">Quantidade</th>
|
||||
<th class="font-semibold">Anterior</th>
|
||||
<th class="font-semibold">Nova</th>
|
||||
<th class="font-semibold">Funcionário</th>
|
||||
<th class="font-semibold">Motivo</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#if movimentacoesQuery.data && movimentacoesQuery.data.length > 0}
|
||||
{#each movimentacoesQuery.data.slice(0, 50) as mov}
|
||||
{@const material = materiaisQuery.data?.find(m => m._id === mov.materialId)}
|
||||
<tr>
|
||||
<td>{new Date(mov.data).toLocaleString('pt-BR')}</td>
|
||||
<td>{material?.nome || 'Carregando...'}</td>
|
||||
{@const material = materiaisMap.get(mov.materialId)}
|
||||
{@const funcionario = mov.funcionarioId ? funcionariosMap.get(mov.funcionarioId) : null}
|
||||
<tr class="hover:bg-base-200/50 transition-colors">
|
||||
<td>
|
||||
{#if mov.tipo === 'entrada'}
|
||||
<span class="badge badge-success">Entrada</span>
|
||||
{:else if mov.tipo === 'saida'}
|
||||
<span class="badge badge-error">Saída</span>
|
||||
{:else}
|
||||
<span class="badge badge-warning">Ajuste</span>
|
||||
<span class="text-sm">{new Date(mov.data).toLocaleString('pt-BR')}</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="font-medium">{material?.nome || 'Carregando...'}</div>
|
||||
{#if material?.codigo}
|
||||
<div class="text-xs text-base-content/50 font-mono">{material.codigo}</div>
|
||||
{/if}
|
||||
</td>
|
||||
<td>{mov.quantidade}</td>
|
||||
<td>{mov.quantidadeAnterior}</td>
|
||||
<td>{mov.quantidadeNova}</td>
|
||||
<td>{mov.motivo}</td>
|
||||
<td>
|
||||
{#if mov.tipo === 'entrada'}
|
||||
<span class="badge badge-success badge-lg">Entrada</span>
|
||||
{:else if mov.tipo === 'saida'}
|
||||
<span class="badge badge-error badge-lg">Saída</span>
|
||||
{:else}
|
||||
<span class="badge badge-warning badge-lg">Ajuste</span>
|
||||
{/if}
|
||||
</td>
|
||||
<td>
|
||||
<span class="font-bold">{mov.quantidade}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="text-base-content/70">{mov.quantidadeAnterior}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="font-semibold">{mov.quantidadeNova}</span>
|
||||
</td>
|
||||
<td>
|
||||
{#if funcionario}
|
||||
<div class="font-medium">{funcionario.nome}</div>
|
||||
{#if funcionario.matricula}
|
||||
<div class="text-xs text-base-content/50">Mat: {funcionario.matricula}</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<span class="text-base-content/50 text-sm italic">—</span>
|
||||
{/if}
|
||||
</td>
|
||||
<td>
|
||||
<span class="text-sm">{mov.motivo}</span>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
{:else}
|
||||
<tr>
|
||||
<td colspan="7" class="text-center">
|
||||
<div class="py-8">
|
||||
<History class="mx-auto mb-4 h-12 w-12 text-base-content/30" />
|
||||
<p class="text-base-content/70">Nenhuma movimentação registrada</p>
|
||||
<td colspan="8" class="text-center">
|
||||
<div class="py-12">
|
||||
<History class="mx-auto mb-4 h-16 w-16 text-base-content/30" />
|
||||
<p class="text-base-content/70 text-lg font-medium">Nenhuma movimentação registrada</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -51,15 +51,10 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<main class="container mx-auto px-4 py-4">
|
||||
<main class="container mx-auto px-4 py-6">
|
||||
<!-- Breadcrumb -->
|
||||
<div class="breadcrumbs mb-4 text-sm">
|
||||
<div class="breadcrumbs mb-6 text-sm">
|
||||
<ul>
|
||||
<li>
|
||||
<a href={resolve('/recursos-humanos')} class="text-primary hover:underline"
|
||||
>Recursos Humanos</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href={resolve('/almoxarifado')} class="text-primary hover:underline"
|
||||
>Almoxarifado</a
|
||||
@@ -70,62 +65,78 @@
|
||||
</div>
|
||||
|
||||
<!-- Cabeçalho -->
|
||||
<div class="mb-6">
|
||||
<div class="mb-8">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="rounded-xl bg-success/20 p-3">
|
||||
<BarChart3 class="h-8 w-8 text-success" strokeWidth={2} />
|
||||
<div class="rounded-2xl bg-gradient-to-br from-success/20 to-success/30 p-4 shadow-lg">
|
||||
<BarChart3 class="h-10 w-10 text-success" strokeWidth={2.5} />
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold">Relatórios</h1>
|
||||
<p class="text-base-content/70">Estatísticas e relatórios do almoxarifado</p>
|
||||
<h1 class="text-3xl font-bold tracking-tight">Relatórios</h1>
|
||||
<p class="text-base-content/70 text-lg">Estatísticas e relatórios do almoxarifado</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Estatísticas Gerais -->
|
||||
{#if statsQuery.data}
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-4 mb-8">
|
||||
<div class="stats bg-base-100 shadow-lg">
|
||||
<div class="stat">
|
||||
<div class="stat-figure text-primary">
|
||||
<Package class="h-8 w-8" />
|
||||
<div class="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-4 mb-8">
|
||||
<div class="card bg-gradient-to-br from-primary/10 via-primary/5 to-base-100 border border-primary/20 shadow-xl hover:shadow-2xl transition-all duration-300 hover:scale-105">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="text-sm font-medium text-base-content/60 mb-1">Total de Materiais</div>
|
||||
<div class="text-3xl font-bold text-primary mb-1">{statsQuery.data.totalMateriais}</div>
|
||||
<div class="text-xs text-base-content/50">Cadastrados no sistema</div>
|
||||
</div>
|
||||
<div class="rounded-xl bg-primary/20 p-3">
|
||||
<Package class="h-8 w-8 text-primary" strokeWidth={2.5} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-title">Total de Materiais</div>
|
||||
<div class="stat-value text-primary">{statsQuery.data.totalMateriais}</div>
|
||||
<div class="stat-desc">Cadastrados no sistema</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stats bg-base-100 shadow-lg">
|
||||
<div class="stat">
|
||||
<div class="stat-figure text-success">
|
||||
<CheckCircle class="h-8 w-8" />
|
||||
<div class="card bg-gradient-to-br from-success/10 via-success/5 to-base-100 border border-success/20 shadow-xl hover:shadow-2xl transition-all duration-300 hover:scale-105">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="text-sm font-medium text-base-content/60 mb-1">Materiais Ativos</div>
|
||||
<div class="text-3xl font-bold text-success mb-1">{statsQuery.data.totalMateriaisAtivos}</div>
|
||||
<div class="text-xs text-base-content/50">Em estoque</div>
|
||||
</div>
|
||||
<div class="rounded-xl bg-success/20 p-3">
|
||||
<CheckCircle class="h-8 w-8 text-success" strokeWidth={2.5} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-title">Materiais Ativos</div>
|
||||
<div class="stat-value text-success">{statsQuery.data.totalMateriaisAtivos}</div>
|
||||
<div class="stat-desc">Em estoque</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stats bg-base-100 shadow-lg">
|
||||
<div class="stat">
|
||||
<div class="stat-figure text-warning">
|
||||
<AlertTriangle class="h-8 w-8" />
|
||||
<div class="card bg-gradient-to-br from-warning/10 via-warning/5 to-base-100 border border-warning/20 shadow-xl hover:shadow-2xl transition-all duration-300 hover:scale-105">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="text-sm font-medium text-base-content/60 mb-1">Alertas Ativos</div>
|
||||
<div class="text-3xl font-bold text-warning mb-1">{statsQuery.data.totalAlertasAtivos}</div>
|
||||
<div class="text-xs text-base-content/50">Estoque baixo</div>
|
||||
</div>
|
||||
<div class="rounded-xl bg-warning/20 p-3">
|
||||
<AlertTriangle class="h-8 w-8 text-warning" strokeWidth={2.5} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-title">Alertas Ativos</div>
|
||||
<div class="stat-value text-warning">{statsQuery.data.totalAlertasAtivos}</div>
|
||||
<div class="stat-desc">Estoque baixo</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stats bg-base-100 shadow-lg">
|
||||
<div class="stat">
|
||||
<div class="stat-figure text-info">
|
||||
<ArrowLeftRight class="h-8 w-8" />
|
||||
<div class="card bg-gradient-to-br from-info/10 via-info/5 to-base-100 border border-info/20 shadow-xl hover:shadow-2xl transition-all duration-300 hover:scale-105">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="text-sm font-medium text-base-content/60 mb-1">Movimentações</div>
|
||||
<div class="text-3xl font-bold text-info mb-1">{statsQuery.data.movimentacoesMes}</div>
|
||||
<div class="text-xs text-base-content/50">Este mês</div>
|
||||
</div>
|
||||
<div class="rounded-xl bg-info/20 p-3">
|
||||
<ArrowLeftRight class="h-8 w-8 text-info" strokeWidth={2.5} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-title">Movimentações</div>
|
||||
<div class="stat-value text-info">{statsQuery.data.movimentacoesMes}</div>
|
||||
<div class="stat-desc">Este mês</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -134,7 +145,7 @@
|
||||
<!-- Relatórios Disponíveis -->
|
||||
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
<!-- Relatório de Materiais por Categoria -->
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card bg-base-100 border border-base-300 shadow-xl">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="card-title">Materiais por Categoria</h2>
|
||||
@@ -171,7 +182,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Movimentações do Mês -->
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card bg-base-100 border border-base-300 shadow-xl">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="card-title">Movimentações do Mês</h2>
|
||||
@@ -209,7 +220,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Materiais com Estoque Baixo -->
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card bg-base-100 border border-base-300 shadow-xl">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="card-title">Materiais com Estoque Baixo</h2>
|
||||
@@ -264,7 +275,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Alertas Recentes -->
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card bg-base-100 border border-base-300 shadow-xl">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="card-title">Alertas Recentes</h2>
|
||||
|
||||
@@ -184,15 +184,10 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<main class="container mx-auto px-4 py-4">
|
||||
<main class="container mx-auto px-4 py-6">
|
||||
<!-- Breadcrumb -->
|
||||
<div class="breadcrumbs mb-4 text-sm">
|
||||
<div class="breadcrumbs mb-6 text-sm">
|
||||
<ul>
|
||||
<li>
|
||||
<a href={resolve('/recursos-humanos')} class="text-primary hover:underline"
|
||||
>Recursos Humanos</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href={resolve('/almoxarifado')} class="text-primary hover:underline"
|
||||
>Almoxarifado</a
|
||||
@@ -203,18 +198,18 @@
|
||||
</div>
|
||||
|
||||
<!-- Cabeçalho -->
|
||||
<div class="mb-6">
|
||||
<div class="mb-8">
|
||||
<div class="flex flex-col items-start justify-between gap-4 sm:flex-row sm:items-center">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="rounded-xl bg-purple-500/20 p-3">
|
||||
<ClipboardList class="h-8 w-8 text-purple-600" strokeWidth={2} />
|
||||
<div class="rounded-2xl bg-gradient-to-br from-purple-500/20 to-purple-600/30 p-4 shadow-lg">
|
||||
<ClipboardList class="h-10 w-10 text-purple-600" strokeWidth={2.5} />
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold">Requisições de Material</h1>
|
||||
<p class="text-base-content/70">Gerencie requisições de material dos funcionários</p>
|
||||
<h1 class="text-3xl font-bold tracking-tight">Requisições de Material</h1>
|
||||
<p class="text-base-content/70 text-lg">Gerencie requisições de material dos funcionários</p>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-primary" onclick={abrirModalNova}>
|
||||
<button class="btn btn-primary shadow-lg hover:shadow-xl transition-all" onclick={abrirModalNova}>
|
||||
<Plus class="h-5 w-5" />
|
||||
Nova Requisição
|
||||
</button>
|
||||
@@ -229,11 +224,12 @@
|
||||
{/if}
|
||||
|
||||
<!-- Filtros -->
|
||||
<div class="card bg-base-100 mb-6 shadow-xl">
|
||||
<div class="card bg-base-100 border border-base-300 mb-6 shadow-xl">
|
||||
<div class="card-body">
|
||||
<h3 class="text-lg font-semibold mb-4">Filtros</h3>
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text">Filtrar por Status</span>
|
||||
<span class="label-text font-medium">Filtrar por Status</span>
|
||||
</label>
|
||||
<select class="select select-bordered" bind:value={filtroStatus}>
|
||||
<option value="">Todos</option>
|
||||
@@ -248,27 +244,28 @@
|
||||
</div>
|
||||
|
||||
<!-- Lista de Requisições -->
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card bg-base-100 border border-base-300 shadow-xl">
|
||||
<div class="card-body">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<table class="table table-zebra">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Número</th>
|
||||
<th>Solicitante</th>
|
||||
<th>Setor</th>
|
||||
<th>Status</th>
|
||||
<th>Data</th>
|
||||
<th>Ações</th>
|
||||
<tr class="bg-base-200">
|
||||
<th class="font-semibold">Número</th>
|
||||
<th class="font-semibold">Solicitante</th>
|
||||
<th class="font-semibold">Setor</th>
|
||||
<th class="font-semibold">Status</th>
|
||||
<th class="font-semibold">Data</th>
|
||||
<th class="font-semibold">Ações</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#if requisicoes.length === 0}
|
||||
<tr>
|
||||
<td colspan="6" class="text-center">
|
||||
<div class="py-8">
|
||||
<ClipboardList class="mx-auto mb-4 h-12 w-12 text-base-content/30" />
|
||||
<p class="text-base-content/70">Nenhuma requisição encontrada</p>
|
||||
<div class="py-12">
|
||||
<ClipboardList class="mx-auto mb-4 h-16 w-16 text-base-content/30" />
|
||||
<p class="text-base-content/70 text-lg font-medium">Nenhuma requisição encontrada</p>
|
||||
<p class="text-base-content/50 text-sm mt-2">Tente ajustar os filtros ou criar uma nova requisição</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -276,18 +273,24 @@
|
||||
{#each requisicoes as requisicao}
|
||||
{@const solicitante = funcionariosQuery.data?.find(f => f._id === requisicao.solicitanteId)}
|
||||
{@const setor = setoresQuery.data?.find(s => s._id === requisicao.setorId)}
|
||||
<tr>
|
||||
<tr class="hover:bg-base-200/50 transition-colors">
|
||||
<td>
|
||||
<div class="font-mono font-bold">{requisicao.numero}</div>
|
||||
<div class="font-mono font-bold text-primary">{requisicao.numero}</div>
|
||||
</td>
|
||||
<td>{solicitante?.nome || 'Carregando...'}</td>
|
||||
<td>{setor?.nome || 'Carregando...'}</td>
|
||||
<td>
|
||||
<span class="badge {getStatusBadge(requisicao.status)}">
|
||||
<div class="font-medium">{solicitante?.nome || 'Carregando...'}</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge badge-ghost">{setor?.nome || 'Carregando...'}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge {getStatusBadge(requisicao.status)} badge-lg">
|
||||
{getStatusLabel(requisicao.status)}
|
||||
</span>
|
||||
</td>
|
||||
<td>{new Date(requisicao.criadoEm).toLocaleDateString('pt-BR')}</td>
|
||||
<td>
|
||||
<span class="text-sm">{new Date(requisicao.criadoEm).toLocaleDateString('pt-BR')}</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="flex gap-2">
|
||||
{#if requisicao.status === 'pendente'}
|
||||
@@ -321,8 +324,11 @@
|
||||
<!-- Modal Nova Requisição -->
|
||||
{#if showModalNova}
|
||||
<div class="modal modal-open">
|
||||
<div class="modal-box max-w-4xl">
|
||||
<h3 class="text-lg font-bold">Nova Requisição de Material</h3>
|
||||
<div class="modal-box max-w-4xl border border-base-300 shadow-2xl">
|
||||
<h3 class="text-2xl font-bold mb-4 flex items-center gap-2">
|
||||
<ClipboardList class="h-6 w-6 text-primary" />
|
||||
Nova Requisição de Material
|
||||
</h3>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 mt-4">
|
||||
<div class="form-control">
|
||||
|
||||
Reference in New Issue
Block a user